From 3902b80e70d53f6f1ebd33852ed67aa222409fc8 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 3 Apr 2024 14:57:57 -0700 Subject: [PATCH] Ugly type safety (#1548) * Ugly typesafety * Formatter * consolidate make firestore event fns and simplify typings --------- Co-authored-by: Brian Li --- spec/v2/providers/firestore.spec.ts | 40 +++++++- src/v2/providers/firestore.ts | 147 ++++++++++++++++++---------- 2 files changed, 135 insertions(+), 52 deletions(-) diff --git a/spec/v2/providers/firestore.spec.ts b/spec/v2/providers/firestore.spec.ts index 77ed0aa1d..24a10e4a2 100644 --- a/spec/v2/providers/firestore.spec.ts +++ b/spec/v2/providers/firestore.spec.ts @@ -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", @@ -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: { @@ -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", () => { @@ -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( diff --git a/src/v2/providers/firestore.ts b/src/v2/providers/firestore.ts index 2afcfc332..039125108 100644 --- a/src/v2/providers/firestore.ts +++ b/src/v2/providers/firestore.ts @@ -93,7 +93,11 @@ export interface RawFirestoreEvent extends CloudEvent> 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=**} @@ -136,6 +136,14 @@ export interface FirestoreEvent> extends Clou params: Params; } +export interface FirestoreAuthEvent> + extends FirestoreEvent { + /** 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 extends EventHandlerOptions { /** The document path */ @@ -197,9 +205,9 @@ export function onDocumentWritten( export function onDocumentWrittenWithAuthContext( document: Document, handler: ( - event: FirestoreEvent | undefined, ParamsOf> + event: FirestoreAuthEvent | undefined, ParamsOf> ) => any | Promise -): CloudFunction | undefined, ParamsOf>>; +): CloudFunction | undefined, ParamsOf>>; /** * Event handler that triggers when a document is created, updated, or deleted in Firestore. @@ -211,9 +219,9 @@ export function onDocumentWrittenWithAuthContext( export function onDocumentWrittenWithAuthContext( opts: DocumentOptions, handler: ( - event: FirestoreEvent | undefined, ParamsOf> + event: FirestoreAuthEvent | undefined, ParamsOf> ) => any | Promise -): CloudFunction | undefined, ParamsOf>>; +): CloudFunction | undefined, ParamsOf>>; /** * Event handler that triggers when a document is created, updated, or deleted in Firestore. @@ -223,12 +231,19 @@ export function onDocumentWrittenWithAuthContext( * @param handler - Event handler which is run every time a Firestore create, update, or delete occurs. */ export function onDocumentWrittenWithAuthContext( - documentorOpts: Document | DocumentOptions, + documentOrOpts: Document | DocumentOptions, handler: ( - event: FirestoreEvent | undefined, ParamsOf> + event: FirestoreAuthEvent | undefined, ParamsOf> ) => any | Promise -): CloudFunction | undefined, ParamsOf>> { - return onChangedOperation(writtenEventWithAuthContextType, documentorOpts, handler); +): CloudFunction | undefined, ParamsOf>> { + // const fn = ( + // event: FirestoreEvent | undefined, ParamsOf> & { + // foo: string; + // } + // ): any => { + // return event; + // }; + return onChangedOperation(writtenEventWithAuthContextType, documentOrOpts, handler); } /** @@ -282,9 +297,9 @@ export function onDocumentCreated( export function onDocumentCreatedWithAuthContext( document: Document, handler: ( - event: FirestoreEvent> + event: FirestoreAuthEvent> ) => any | Promise -): CloudFunction>>; +): CloudFunction>>; /** * Event handler that triggers when a document is created in Firestore. @@ -296,9 +311,9 @@ export function onDocumentCreatedWithAuthContext( export function onDocumentCreatedWithAuthContext( opts: DocumentOptions, handler: ( - event: FirestoreEvent> + event: FirestoreAuthEvent> ) => any | Promise -): CloudFunction>>; +): CloudFunction>>; /** * Event handler that triggers when a document is created in Firestore. @@ -309,9 +324,9 @@ export function onDocumentCreatedWithAuthContext( export function onDocumentCreatedWithAuthContext( documentOrOpts: Document | DocumentOptions, handler: ( - event: FirestoreEvent> + event: FirestoreAuthEvent> ) => any | Promise -): CloudFunction>> { +): CloudFunction>> { return onOperation(createdEventWithAuthContextType, documentOrOpts, handler); } @@ -365,9 +380,10 @@ export function onDocumentUpdated( export function onDocumentUpdatedWithAuthContext( document: Document, handler: ( - event: FirestoreEvent | undefined, ParamsOf> + event: FirestoreAuthEvent | undefined, ParamsOf> ) => any | Promise -): CloudFunction | undefined, ParamsOf>>; +): CloudFunction | undefined, ParamsOf>>; + /** * 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. @@ -378,9 +394,9 @@ export function onDocumentUpdatedWithAuthContext( export function onDocumentUpdatedWithAuthContext( opts: DocumentOptions, handler: ( - event: FirestoreEvent | undefined, ParamsOf> + event: FirestoreAuthEvent | undefined, ParamsOf> ) => any | Promise -): CloudFunction | undefined, ParamsOf>>; +): CloudFunction | undefined, ParamsOf>>; /** * Event handler that triggers when a document is updated in Firestore. @@ -391,9 +407,11 @@ export function onDocumentUpdatedWithAuthContext( export function onDocumentUpdatedWithAuthContext( documentOrOpts: Document | DocumentOptions, handler: ( - event: FirestoreEvent | undefined, ParamsOf> + event: FirestoreAuthEvent | undefined, ParamsOf> ) => any | Promise -): CloudFunction | undefined, ParamsOf>> { +): CloudFunction< + FirestoreAuthEvent | undefined, ParamsOf> +> { return onChangedOperation(updatedEventWithAuthContextType, documentOrOpts, handler); } @@ -448,9 +466,9 @@ export function onDocumentDeleted( export function onDocumentDeletedWithAuthContext( document: Document, handler: ( - event: FirestoreEvent> + event: FirestoreAuthEvent> ) => any | Promise -): CloudFunction>>; +): CloudFunction>>; /** * Event handler that triggers when a document is deleted in Firestore. @@ -462,9 +480,9 @@ export function onDocumentDeletedWithAuthContext( export function onDocumentDeletedWithAuthContext( opts: DocumentOptions, handler: ( - event: FirestoreEvent> + event: FirestoreAuthEvent> ) => any | Promise -): CloudFunction>>; +): CloudFunction>>; /** * Event handler that triggers when a document is deleted in Firestore. @@ -475,9 +493,9 @@ export function onDocumentDeletedWithAuthContext( export function onDocumentDeletedWithAuthContext( documentOrOpts: Document | DocumentOptions, handler: ( - event: FirestoreEvent> + event: FirestoreAuthEvent> ) => any | Promise -): CloudFunction>> { +): CloudFunction>> { return onOperation(deletedEventWithAuthContextType, documentOrOpts, handler); } @@ -566,11 +584,13 @@ export function makeParams(document: string, documentPattern: PathPattern) { /** @internal */ export function makeFirestoreEvent( eventType: string, - event: RawFirestoreEvent, + event: RawFirestoreEvent | RawFirestoreAuthEvent, params: Params -): FirestoreEvent { +): + | FirestoreEvent + | FirestoreAuthEvent { const data = event.data - ? eventType === createdEventType + ? eventType === createdEventType || eventType === createdEventWithAuthContextType ? createSnapshot(event) : createBeforeSnapshot(event) : undefined; @@ -578,19 +598,32 @@ export function makeFirestoreEvent( ...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( - event: RawFirestoreEvent, + event: RawFirestoreEvent | RawFirestoreAuthEvent, params: Params -): FirestoreEvent | undefined, Params> { +): + | FirestoreEvent | undefined, Params> + | FirestoreAuthEvent | undefined, Params> { const data = event.data ? Change.fromObjects(createBeforeSnapshot(event), createSnapshot(event)) : undefined; @@ -598,11 +631,21 @@ export function makeChangedFirestoreEvent( ...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; } @@ -649,16 +692,19 @@ export function makeEndpoint( } /** @internal */ -export function onOperation( +export function onOperation< + Document extends string, + Event extends FirestoreEvent> +>( eventType: string, documentOrOpts: Document | DocumentOptions, - handler: (event: FirestoreEvent>) => any | Promise -): CloudFunction>> { + handler: (event: Event) => any | Promise +): CloudFunction { const { document, database, namespace, opts } = getOpts(documentOrOpts); // wrap the handler const func = (raw: CloudEvent) => { - const event = raw as RawFirestoreEvent; + const event = raw as RawFirestoreEvent | RawFirestoreAuthEvent; const documentPattern = new PathPattern( typeof document === "string" ? document : document.value() ); @@ -675,18 +721,19 @@ export function onOperation( } /** @internal */ -export function onChangedOperation( +export function onChangedOperation< + Document extends string, + Event extends FirestoreEvent, ParamsOf> +>( eventType: string, documentOrOpts: Document | DocumentOptions, - handler: ( - event: FirestoreEvent, ParamsOf> - ) => any | Promise -): CloudFunction, ParamsOf>> { + handler: (event: Event) => any | Promise +): CloudFunction { const { document, database, namespace, opts } = getOpts(documentOrOpts); // wrap the handler const func = (raw: CloudEvent) => { - const event = raw as RawFirestoreEvent; + const event = raw as RawFirestoreEvent | RawFirestoreAuthEvent; const documentPattern = new PathPattern( typeof document === "string" ? document : document.value() );