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

Improvement for DataSnapshot paths (#10, #26) #32

Closed
wants to merge 9 commits into from

Conversation

abeisgoat
Copy link
Contributor

@abeisgoat abeisgoat commented Nov 28, 2018

Description

Fixes common UX issue where context.params are not populated from wildcards in path. (See #10 and #26)

Code sample

New Behavior

const func = functions.database
  .ref("/different/{wildcard}/nested/{anotherWildcard}")
  .onCreate((snap, context) => {
    console.log(context.params.wildcard == "at") // true
    console.log(context.params.anotherWildcard == "bat") // true
    console.log(snap.key == "bat") // true
  }

const snap = makeDataSnapshot(
    'hello world',
    '/different/at/nested/bat',
);
const params = {};
wrap(func)(snap, {params});

New Behavior

const func = functions.database
  .ref("/different/{wildcard}/nested/{anotherWildcard}")
  .onCreate((snap, context) => {
    console.log(context.params.wildcard == "at") // true
    console.log(context.params.anotherWildcard == "bat") // true
    console.log(snap.key == "bat") // true
  }

const snap = makeDataSnapshot(
    'hello world',
    '/different/at/nested/bat',
);
const params = {wildcard: "that"};
wrap(func)(snap, {params}); // Throws error due to overlapping "wildcard" definition

I've also added some better logic for ensuring a DataSnapshot's path fits the trigger it is passed to.

Previous / Current Behavior (DataSnapshot Path Checking)

const func = functions.database
  .ref("/good_path/{id}")
  .onCreate(snap => {
    console.log(snap.ref.path == "/bad_path/test") // true, previously silently wrong
  }

const snap = makeDataSnapshot(
    'hello world',
    '/bad_path/{id}', // Note mismatch with trigger ref
);
const params = {
    id: "test"
};
wrap(func)(snap, {params}); // New functionality throws an error on invocation due to mismatch between snap and trigger.

Tests

Added various tests for new functionality which changes DataSnapshot path, _isValidWildcardMatch, and _extractParamsFromPath.

All test pass, canonical uppercase sample tests also pass without change.

Copy link
Contributor

@laurenzlong laurenzlong left a comment

Choose a reason for hiding this comment

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

I still need to take a more detailed look, but my high level feedback is that path supplied to makeDataSnapshot should populate context.params, not the other way around. Snapshots represent a particular database write that happened, so wildcards should not be supported in their construction.

@abeisgoat
Copy link
Contributor Author

That seems reasonable. Let me know what you think once you take a good look and we'll make it work one way or the other!

@abeisgoat
Copy link
Contributor Author

Alright, dropped out snapshot path population from params, so now params is populated from snapshot path and not the other way around.

Also added some better logging for when a dev accidentally passes URL with wildcards.

@abeisgoat abeisgoat requested a review from laurenzlong December 5, 2018 21:22
Copy link
Contributor

@laurenzlong laurenzlong left a comment

Choose a reason for hiding this comment

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

You haven't fully removed the functionality for populating the snapshot's path from params. See inline comments.

As well, I would like to avoid casting the data as any, since bugs are harder to catch and the code is harder to maintain down the road. Your code currently also does not handle the case when the function is onWrite, therefore data is actually a Change object as opposed to a snapshot. It also doesn't seem to handle Firestore, which faces the same usability challenge.

Some suggestions:

  • Modify database.makeSnapshot and firestore.makeDocumentSnapshot to throw if there's a wildcard in the path provided
  • Within wrap, check __trigger.eventTrigger.eventType to see if the function being wrapped is RTDB or Firestore, and whether it's onWrite or onCreate/onDelete
  • Depending on the check above, check data to ensure it is an instance of DataSnapshot, DocumentSnapshot, Change<DataSnapshot>, or Change<DocumentSnapshot>, throw error otherwise.
  • Once you are assured the data is the right type, you can cast it as such. Proceed to get its path, and construct the params from it.

spec/main.spec.ts Outdated Show resolved Hide resolved
'hello world',
'/ref/{wildcard}/nested/{anotherWildcard}',
);
const params = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you make makeDataSnapshot throw, instead of only catching it later when wrapped is called.

spec/main.spec.ts Outdated Show resolved Hide resolved
expect(result.context.resource.name).to.equal('ref/cat/nested/that');
});

it('should error when DataSnapshot wildcard path does not match resource path', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

this test is no longer relevant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test checks the snapshot's provided path against the route in the function definition, I think that's still relevant because a dev could still path a snapshot which has a completely wrong path for a trigger, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see now, it is a poorly written test, but the intent is still useful, I think. I've changed it to..

    it('should error when DataSnapshot path does not match trigger template', () => {
      const fft = new FirebaseFunctionsTest();
      fft.init();

      const snap = makeDataSnapshot(
          'hello world',
          '/different/at/nested/bat',
      );
      const wrapped = wrap(constructCF('google.firebase.database.ref.write'));
      expect(() => wrapped(snap, { params })).to.throw();
    });
  });

Does that seem more right?

spec/main.spec.ts Outdated Show resolved Hide resolved
it('should not match a path which has different chunks', () => {
const params = _extractParamsFromPath(
'locations/{company}/users/{user}',
'companies/firebase/users/{user}');
Copy link
Contributor

Choose a reason for hiding this comment

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

companies/firebase/users/user (second parameter to _extractParamsFromPath should not have wildcard, under the normal use of this function)

it('should not match a path which has different chunks', () => {
const differentChunk = _isValidWildcardMatch(
'locations/{company}/users/{user}',
'companies/firebase/users/{user}');
Copy link
Contributor

Choose a reason for hiding this comment

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

companies/firebase/users/user for same reason as above

const formattedSnapshotPath = _compiledWildcardPath(
unsafeData._path,
eventContextOptions ? eventContextOptions.params : null);
data = makeDataSnapshot(unsafeData._data, formattedSnapshotPath, unsafeData.app) as any;
Copy link
Contributor

Choose a reason for hiding this comment

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

Remaking the data snapshot isn't necessary if you are not populating the snapshot's path from params.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I thought, It is necessary for standardizing the path to remove extra /, however I'll move this logic to makeSnapshot as you're right that it's a better place for it.


// Ensure that the same param wasn't specified by the path and the options.params bundle
const pathParams = _extractParamsFromPath(cloudFunction.__trigger.eventTrigger.resource, unsafeData._path);
const overlappingWildcards = differenceWith(
Copy link
Contributor

Choose a reason for hiding this comment

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

this is also not necessary if you do a check within makeSnapshot to ensure the snapshot path doesn't have wildcards.

@laurenzlong
Copy link
Contributor

@abeisgoat Do you have bandwidth to pick this up again? The test is broken and there's been some merge conflicts, but I think we're close!

@rhodgkins
Copy link
Contributor

I think this has been superseded by #114

@TheIronDev
Copy link
Contributor

I think this has been superseded by #114

I agree with this. Marking this as closed, but happy to reevaluate too.

@TheIronDev TheIronDev closed this Aug 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants