Skip to content

Commit

Permalink
Ugly type safety (#1548)
Browse files Browse the repository at this point in the history
* Ugly typesafety

* Formatter

* consolidate make firestore event fns and simplify typings

---------

Co-authored-by: Brian Li <blidd@google.com>
  • Loading branch information
inlined and blidd-google committed Apr 3, 2024
1 parent 8c83dd8 commit 3902b80
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 52 deletions.
40 changes: 38 additions & 2 deletions spec/v2/providers/firestore.spec.ts
Expand Up @@ -40,8 +40,6 @@ const eventBase = {
datacontenttype: "application/protobuf",
dataschema:
"https://github.com/googleapis/google-cloudevents/blob/main/proto/google/events/cloud/firestore/v1/data.proto",
authtype: "unknown",
authid: "1234",
id: "379ad868-5ef9-4c84-a8ba-f75f1b056663",
source: "projects/my-project/databases/my-db/documents/d",
subject: "documents/foo/fGRodw71mHutZ4wGDuT8",
Expand Down Expand Up @@ -86,6 +84,15 @@ function makeEvent(data?: any): firestore.RawFirestoreEvent {
} as firestore.RawFirestoreEvent;
}

function makeAuthEvent(data?: any): firestore.RawFirestoreAuthEvent {
return {
...eventBase,
data,
authid: "userId",
authtype: "unknown",
} as firestore.RawFirestoreAuthEvent;
}

const createdData = {
value: {
fields: {
Expand Down Expand Up @@ -910,6 +917,26 @@ describe("firestore", () => {

expect(event.data.data()).to.deep.eq({ hello: "delete world" });
});

it("should make event from a created event with auth context", () => {
const event = firestore.makeFirestoreEvent(
firestore.createdEventWithAuthContextType,
makeAuthEvent(makeEncodedProtobuf(createdProto)),
firestore.makeParams("foo/fGRodw71mHutZ4wGDuT8", new PathPattern("foo/{bar}"))
);

expect(event.data.data()).to.deep.eq({ hello: "create world" });
});

it("should include auth fields if provided in raw event", () => {
const event = firestore.makeFirestoreEvent(
firestore.createdEventWithAuthContextType,
makeAuthEvent(makeEncodedProtobuf(createdProto)),
firestore.makeParams("foo/fGRodw71mHutZ4wGDuT8", new PathPattern("foo/{bar}"))
);

expect(event).to.include({ authId: "userId", authType: "unknown" });
});
});

describe("makeChangedFirestoreEvent", () => {
Expand Down Expand Up @@ -943,6 +970,15 @@ describe("firestore", () => {
});
});

it("should include auth fields if provided in raw event", () => {
const event = firestore.makeChangedFirestoreEvent(
makeAuthEvent(makeEncodedProtobuf(writtenProto)),
firestore.makeParams("foo/fGRodw71mHutZ4wGDuT8", new PathPattern("foo/{bar}"))
);

expect(event).to.include({ authId: "userId", authType: "unknown" });
});

describe("makeEndpoint", () => {
it("should make an endpoint with a document path pattern", () => {
const expectedEp = makeExpectedEp(
Expand Down
147 changes: 97 additions & 50 deletions src/v2/providers/firestore.ts
Expand Up @@ -93,7 +93,11 @@ export interface RawFirestoreEvent extends CloudEvent<Uint8Array | RawFirestoreD
document: string;
datacontenttype?: string;
dataschema: string;
authtype?: string;
}

/** @internal */
export interface RawFirestoreAuthEvent extends RawFirestoreEvent {
authtype?: AuthType;
authid?: string;
}

Expand Down Expand Up @@ -125,17 +129,21 @@ export interface FirestoreEvent<T, Params = Record<string, string>> extends Clou
namespace: string;
/** The document path */
document: string;
/** The type of principal that triggered the event */
authType?: AuthType;
/** The unique identifier for the principal */
authId?: string;
/**
* An object containing the values of the path patterns.
* Only named capture groups will be populated - {key}, {key=*}, {key=**}
*/
params: Params;
}

export interface FirestoreAuthEvent<T, Params = Record<string, string>>
extends FirestoreEvent<T, Params> {
/** The type of principal that triggered the event */
authType: AuthType;
/** The unique identifier for the principal */
authId?: string;
}

/** DocumentOptions extend EventHandlerOptions with provided document and optional database and namespace. */
export interface DocumentOptions<Document extends string = string> extends EventHandlerOptions {
/** The document path */
Expand Down Expand Up @@ -197,9 +205,9 @@ export function onDocumentWritten<Document extends string>(
export function onDocumentWrittenWithAuthContext<Document extends string>(
document: Document,
handler: (
event: FirestoreEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is created, updated, or deleted in Firestore.
Expand All @@ -211,9 +219,9 @@ export function onDocumentWrittenWithAuthContext<Document extends string>(
export function onDocumentWrittenWithAuthContext<Document extends string>(
opts: DocumentOptions<Document>,
handler: (
event: FirestoreEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is created, updated, or deleted in Firestore.
Expand All @@ -223,12 +231,19 @@ export function onDocumentWrittenWithAuthContext<Document extends string>(
* @param handler - Event handler which is run every time a Firestore create, update, or delete occurs.
*/
export function onDocumentWrittenWithAuthContext<Document extends string>(
documentorOpts: Document | DocumentOptions<Document>,
documentOrOpts: Document | DocumentOptions<Document>,
handler: (
event: FirestoreEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>> {
return onChangedOperation(writtenEventWithAuthContextType, documentorOpts, handler);
): CloudFunction<FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, ParamsOf<Document>>> {
// const fn = (
// event: FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>> & {
// foo: string;
// }
// ): any => {
// return event;
// };
return onChangedOperation(writtenEventWithAuthContextType, documentOrOpts, handler);
}

/**
Expand Down Expand Up @@ -282,9 +297,9 @@ export function onDocumentCreated<Document extends string>(
export function onDocumentCreatedWithAuthContext<Document extends string>(
document: Document,
handler: (
event: FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is created in Firestore.
Expand All @@ -296,9 +311,9 @@ export function onDocumentCreatedWithAuthContext<Document extends string>(
export function onDocumentCreatedWithAuthContext<Document extends string>(
opts: DocumentOptions<Document>,
handler: (
event: FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is created in Firestore.
Expand All @@ -309,9 +324,9 @@ export function onDocumentCreatedWithAuthContext<Document extends string>(
export function onDocumentCreatedWithAuthContext<Document extends string>(
documentOrOpts: Document | DocumentOptions<Document>,
handler: (
event: FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>> {
): CloudFunction<FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>> {
return onOperation(createdEventWithAuthContextType, documentOrOpts, handler);
}

Expand Down Expand Up @@ -365,9 +380,10 @@ export function onDocumentUpdated<Document extends string>(
export function onDocumentUpdatedWithAuthContext<Document extends string>(
document: Document,
handler: (
event: FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is updated in Firestore.
* This trigger also provides the authentication context of the principal who triggered the event.
Expand All @@ -378,9 +394,9 @@ export function onDocumentUpdatedWithAuthContext<Document extends string>(
export function onDocumentUpdatedWithAuthContext<Document extends string>(
opts: DocumentOptions<Document>,
handler: (
event: FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is updated in Firestore.
Expand All @@ -391,9 +407,11 @@ export function onDocumentUpdatedWithAuthContext<Document extends string>(
export function onDocumentUpdatedWithAuthContext<Document extends string>(
documentOrOpts: Document | DocumentOptions<Document>,
handler: (
event: FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>> {
): CloudFunction<
FirestoreAuthEvent<Change<QueryDocumentSnapshot> | undefined, ParamsOf<Document>>
> {
return onChangedOperation(updatedEventWithAuthContextType, documentOrOpts, handler);
}

Expand Down Expand Up @@ -448,9 +466,9 @@ export function onDocumentDeleted<Document extends string>(
export function onDocumentDeletedWithAuthContext<Document extends string>(
document: Document,
handler: (
event: FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is deleted in Firestore.
Expand All @@ -462,9 +480,9 @@ export function onDocumentDeletedWithAuthContext<Document extends string>(
export function onDocumentDeletedWithAuthContext<Document extends string>(
opts: DocumentOptions<Document>,
handler: (
event: FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;
): CloudFunction<FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>>;

/**
* Event handler that triggers when a document is deleted in Firestore.
Expand All @@ -475,9 +493,9 @@ export function onDocumentDeletedWithAuthContext<Document extends string>(
export function onDocumentDeletedWithAuthContext<Document extends string>(
documentOrOpts: Document | DocumentOptions<Document>,
handler: (
event: FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
event: FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>> {
): CloudFunction<FirestoreAuthEvent<QueryDocumentSnapshot | undefined, ParamsOf<Document>>> {
return onOperation(deletedEventWithAuthContextType, documentOrOpts, handler);
}

Expand Down Expand Up @@ -566,43 +584,68 @@ export function makeParams(document: string, documentPattern: PathPattern) {
/** @internal */
export function makeFirestoreEvent<Params>(
eventType: string,
event: RawFirestoreEvent,
event: RawFirestoreEvent | RawFirestoreAuthEvent,
params: Params
): FirestoreEvent<QueryDocumentSnapshot | undefined, Params> {
):
| FirestoreEvent<QueryDocumentSnapshot | undefined, Params>
| FirestoreAuthEvent<QueryDocumentSnapshot | undefined, Params> {
const data = event.data
? eventType === createdEventType
? eventType === createdEventType || eventType === createdEventWithAuthContextType
? createSnapshot(event)
: createBeforeSnapshot(event)
: undefined;
const firestoreEvent: FirestoreEvent<QueryDocumentSnapshot | undefined, Params> = {
...event,
params,
data,
authType: event.authtype as AuthType,
authId: event.authid,
};

delete (firestoreEvent as any).datacontenttype;
delete (firestoreEvent as any).dataschema;

if ("authtype" in event) {
const eventWithAuth = {
...firestoreEvent,
authType: event.authtype,
authId: event.authid,
};
delete (eventWithAuth as any).authtype;
delete (eventWithAuth as any).authid;
return eventWithAuth;
}

return firestoreEvent;
}

/** @internal */
export function makeChangedFirestoreEvent<Params>(
event: RawFirestoreEvent,
event: RawFirestoreEvent | RawFirestoreAuthEvent,
params: Params
): FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, Params> {
):
| FirestoreEvent<Change<DocumentSnapshot> | undefined, Params>
| FirestoreAuthEvent<Change<DocumentSnapshot> | undefined, Params> {
const data = event.data
? Change.fromObjects(createBeforeSnapshot(event), createSnapshot(event))
: undefined;
const firestoreEvent: FirestoreEvent<Change<QueryDocumentSnapshot> | undefined, Params> = {
...event,
params,
data,
authType: event.authtype as AuthType,
authId: event.authid,
};
delete (firestoreEvent as any).datacontenttype;
delete (firestoreEvent as any).dataschema;

if ("authtype" in event) {
const eventWithAuth = {
...firestoreEvent,
authType: event.authtype,
authId: event.authid,
};
delete (eventWithAuth as any).authtype;
delete (eventWithAuth as any).authid;
return eventWithAuth;
}

return firestoreEvent;
}

Expand Down Expand Up @@ -649,16 +692,19 @@ export function makeEndpoint(
}

/** @internal */
export function onOperation<Document extends string>(
export function onOperation<
Document extends string,
Event extends FirestoreEvent<QueryDocumentSnapshot, ParamsOf<Document>>
>(
eventType: string,
documentOrOpts: Document | DocumentOptions<Document>,
handler: (event: FirestoreEvent<QueryDocumentSnapshot, ParamsOf<Document>>) => any | Promise<any>
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot, ParamsOf<Document>>> {
handler: (event: Event) => any | Promise<any>
): CloudFunction<Event> {
const { document, database, namespace, opts } = getOpts(documentOrOpts);

// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawFirestoreEvent;
const event = raw as RawFirestoreEvent | RawFirestoreAuthEvent;
const documentPattern = new PathPattern(
typeof document === "string" ? document : document.value()
);
Expand All @@ -675,18 +721,19 @@ export function onOperation<Document extends string>(
}

/** @internal */
export function onChangedOperation<Document extends string>(
export function onChangedOperation<
Document extends string,
Event extends FirestoreEvent<Change<DocumentSnapshot>, ParamsOf<Document>>
>(
eventType: string,
documentOrOpts: Document | DocumentOptions<Document>,
handler: (
event: FirestoreEvent<Change<QueryDocumentSnapshot>, ParamsOf<Document>>
) => any | Promise<any>
): CloudFunction<FirestoreEvent<Change<QueryDocumentSnapshot>, ParamsOf<Document>>> {
handler: (event: Event) => any | Promise<any>
): CloudFunction<Event> {
const { document, database, namespace, opts } = getOpts(documentOrOpts);

// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawFirestoreEvent;
const event = raw as RawFirestoreEvent | RawFirestoreAuthEvent;
const documentPattern = new PathPattern(
typeof document === "string" ? document : document.value()
);
Expand Down

0 comments on commit 3902b80

Please sign in to comment.