Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make storage bucket parameterizeable. Create const for bucket picker #1518

Merged
merged 9 commits into from
Feb 7, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
- Fixes access on deeply nested, nonexistent property. (#1432)
- Add IteratedDataSnapshot interface to match with firebase admin v12 (#1517).
- Make bucket parameterizeable in storage functions (#1518)
- Introduce helper library for select and multi-select input (#1518)
10 changes: 10 additions & 0 deletions src/params/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ import {
InternalExpression,
} from "./types";

export {
BUCKET_PICKER,
TextInput,
SelectInput,
SelectOptions,
MultiSelectInput,
select,
multiSelect,
} from "./types";

export { ParamOptions, Expression };

type SecretOrExpr = Param<any> | SecretParam;
Expand Down
92 changes: 73 additions & 19 deletions src/params/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,53 @@ export class CompareExpression<
/** @hidden */
type ParamValueType = "string" | "list" | "boolean" | "int" | "float" | "secret";

/** Create a select input from a series of values. */
export function select<T>(options: T[]): SelectInput<T>;

/** Create a select input from a map of labels to vaues. */
export function select<T>(optionsWithLabels: Record<string, T>): SelectInput<T>;

/** Create a select input from a series of values or a map of labels to values */
export function select<T>(options: T[] | Record<string, T>): SelectInput<T> {
let wireOpts: SelectOptions<T>[];
if (Array.isArray(options)) {
wireOpts = options.map((opt) => ({ value: opt }));
} else {
wireOpts = Object.entries(options).map(([label, value]) => ({ label, value }));
}
return {
select: {
options: wireOpts,
},
};
}

/** Create a multi-select input from a series of values. */
export function multiSelect(options: string[]): MultiSelectInput;

/** Create a multi-select input from map of labels to values. */
export function multiSelect(options: Record<string, string>): MultiSelectInput;

/** Create a multi-select input from a series of values or map of labels to values. */
export function multiSelect(options: string[] | Record<string, string>): MultiSelectInput {
let wireOpts: SelectOptions<string>[];
if (Array.isArray(options)) {
wireOpts = options.map((opt) => ({ value: opt }));
} else {
wireOpts = Object.entries(options).map(([label, value]) => ({ label, value }));
}
return {
multiSelect: {
options: wireOpts,
},
};
}

type ParamInput<T> =
| { text: TextInput<T> }
| { select: SelectInput<T> }
| { multiSelect: MultiSelectInput }
| { resource: ResourceInput };
| TextInput<T>
| SelectInput<T>
| (T extends string[] ? MultiSelectInput : never)
| (T extends string ? ResourceInput : never);

/**
* Specifies that a Param's value should be determined by prompting the user
Expand All @@ -184,18 +226,20 @@ type ParamInput<T> =
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface TextInput<T = unknown> {
example?: string;
/**
* A regular expression (or an escaped string to compile into a regular
* expression) which the prompted text must satisfy; the prompt will retry
* until input matching the regex is provided.
*/
validationRegex?: string | RegExp;
/**
* A custom error message to display when retrying the prompt based on input
* failing to conform to the validationRegex,
*/
validationErrorMessage?: string;
text: {
example?: string;
/**
* A regular expression (or an escaped string to compile into a regular
* expression) which the prompted text must satisfy; the prompt will retry
* until input matching the regex is provided.
*/
validationRegex?: string | RegExp;
/**
* A custom error message to display when retrying the prompt based on input
* failing to conform to the validationRegex,
*/
validationErrorMessage?: string;
};
}

/**
Expand All @@ -205,16 +249,24 @@ export interface TextInput<T = unknown> {
*/
export interface ResourceInput {
resource: {
type: string;
type: "storage.googleapis.com/Bucket";
};
}

export const BUCKET_PICKER: ResourceInput = {
resource: {
type: "storage.googleapis.com/Bucket",
},
};

Comment on lines +256 to +261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the helper constant even needed given VS Code will autocomplete the enum string for the resource type?

If so, might prefer naming of SELECT_STORAGE_BUCKET over BUCKET_PICKER.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is much less cringe to type a constant rather than a complex structure that has only one possible configuration. There's precedence here with the ServerValues consts/functions in RTDB/Firestore. Switched to your preferred name though.

/**
* Specifies that a Param's value should be determined by having the user select
* from a list of pre-canned options interactively at deploy-time.
*/
export interface SelectInput<T = unknown> {
options: Array<SelectOptions<T>>;
select: {
options: Array<SelectOptions<T>>;
};
}

/**
Expand All @@ -223,7 +275,9 @@ export interface SelectInput<T = unknown> {
* Will result in errors if used on Params of type other than string[].
*/
export interface MultiSelectInput {
options: Array<SelectOptions<string>>;
multiSelect: {
options: Array<SelectOptions<string>>;
};
}

/**
Expand Down
51 changes: 36 additions & 15 deletions src/v2/providers/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export const metadataUpdatedEvent = "google.cloud.storage.object.v1.metadataUpda
/** StorageOptions extend EventHandlerOptions with a bucket name */
export interface StorageOptions extends options.EventHandlerOptions {
/** The name of the bucket containing this object. */
bucket?: string;
bucket?: string | Expression<string>;

/**
* If true, do not deploy or emulate this function.
Expand Down Expand Up @@ -324,7 +324,7 @@ export function onObjectArchived(
* @param handler - Event handler which is run every time a Google Cloud Storage archival occurs.
*/
export function onObjectArchived(
bucket: string,
bucket: string | Expression<string>,
handler: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent>;

Expand Down Expand Up @@ -352,7 +352,11 @@ export function onObjectArchived(
* @param handler - Event handler which is run every time a Google Cloud Storage archival occurs.
*/
export function onObjectArchived(
bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise<any>),
bucketOrOptsOrHandler:
| string
| Expression<string>
| StorageOptions
| ((event: StorageEvent) => any | Promise<any>),
handler?: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent> {
return onOperation(archivedEvent, bucketOrOptsOrHandler, handler);
Expand Down Expand Up @@ -384,7 +388,7 @@ export function onObjectFinalized(
* @param handler - Event handler which is run every time a Google Cloud Storage object creation occurs.
*/
export function onObjectFinalized(
bucket: string,
bucket: string | Expression<string>,
handler: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent>;

Expand Down Expand Up @@ -416,7 +420,11 @@ export function onObjectFinalized(
* @param handler - Event handler which is run every time a Google Cloud Storage object creation occurs.
*/
export function onObjectFinalized(
bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise<any>),
bucketOrOptsOrHandler:
| string
| Expression<string>
| StorageOptions
| ((event: StorageEvent) => any | Promise<any>),
handler?: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent> {
return onOperation(finalizedEvent, bucketOrOptsOrHandler, handler);
Expand Down Expand Up @@ -450,7 +458,7 @@ export function onObjectDeleted(
* @param handler - Event handler which is run every time a Google Cloud Storage object deletion occurs.
*/
export function onObjectDeleted(
bucket: string,
bucket: string | Expression<string>,
handler: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent>;

Expand Down Expand Up @@ -484,7 +492,11 @@ export function onObjectDeleted(
* @param handler - Event handler which is run every time a Google Cloud Storage object deletion occurs.
*/
export function onObjectDeleted(
bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise<any>),
bucketOrOptsOrHandler:
| string
| Expression<string>
| StorageOptions
| ((event: StorageEvent) => any | Promise<any>),
handler?: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent> {
return onOperation(deletedEvent, bucketOrOptsOrHandler, handler);
Expand All @@ -509,7 +521,7 @@ export function onObjectMetadataUpdated(
* @param handler - Event handler which is run every time a Google Cloud Storage object metadata update occurs.
*/
export function onObjectMetadataUpdated(
bucket: string,
bucket: string | Expression<string>,
handler: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent>;

Expand All @@ -533,7 +545,11 @@ export function onObjectMetadataUpdated(
* @param handler - Event handler which is run every time a Google Cloud Storage object metadata update occurs.
*/
export function onObjectMetadataUpdated(
bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise<any>),
bucketOrOptsOrHandler:
| string
| Expression<string>
| StorageOptions
| ((event: StorageEvent) => any | Promise<any>),
handler?: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent> {
return onOperation(metadataUpdatedEvent, bucketOrOptsOrHandler, handler);
Expand All @@ -542,7 +558,11 @@ export function onObjectMetadataUpdated(
/** @internal */
export function onOperation(
eventType: string,
bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise<any>),
bucketOrOptsOrHandler:
| string
| Expression<string>
| StorageOptions
| ((event: StorageEvent) => any | Promise<any>),
handler: (event: StorageEvent) => any | Promise<any>
): CloudFunction<StorageEvent> {
if (typeof bucketOrOptsOrHandler === "function") {
Expand Down Expand Up @@ -616,11 +636,12 @@ export function onOperation(

/** @internal */
export function getOptsAndBucket(
bucketOrOpts: string | StorageOptions
): [options.EventHandlerOptions, string] {
let bucket: string;
bucketOrOpts: string | Expression<string> | StorageOptions
): [options.EventHandlerOptions, string | Expression<string>] {
let bucket: string | Expression<string>;
let opts: options.EventHandlerOptions;
if (typeof bucketOrOpts === "string") {
// If bucket is a string or Expression<string>
if (typeof bucketOrOpts === "string" || "value" in bucketOrOpts) {
bucket = bucketOrOpts;
opts = {};
} else {
Expand All @@ -635,7 +656,7 @@ export function getOptsAndBucket(
" by providing bucket name directly in the event handler or by setting process.env.FIREBASE_CONFIG."
);
}
if (!/^[a-z\d][a-z\d\\._-]{1,230}[a-z\d]$/.test(bucket)) {
if (typeof bucket === "string" && !/^[a-z\d][a-z\d\\._-]{1,230}[a-z\d]$/.test(bucket)) {
throw new Error(`Invalid bucket name ${bucket}`);
}

Expand Down
Loading