Meteor method + check() + audit-argument-checks + async/await causes exception

Viewed 61

I’m using meteor with check() and audit-check-arguments package.

When I use a meteor method using async/await and pass a parameter, even though I use check() to validate the function parametrs, the audit package still throws an exception indicating that not all input parameters have been checked. If I remove the async/await implementation, the package does not crib. What am I missing?

Example:

Meteor.methods({

    test: async function(param1){
          check(param1, String);
          ...
          await ....
    }
});

Throws an exception:

=> Client modified -- refreshing
I20200513-10:43:27.978(5.5)? Exception while invoking method 'test' Error: Did not check() all arguments during call to 'test'
I20200513-10:43:27.979(5.5)?     at ArgumentChecker.throwUnlessAllArgumentsHaveBeenChecked (packages/check/match.js:515:13)

Whereas this traditional meteor method does not throw any exceptions

Meteor.methods({

    test:  function(param1){
          check(param1, String);
          ...

    }
});

I know for sure that I am passing exactly one parameter.

1 Answers

It looks like audit-argument-checks only works for synchronous functions.

I don't have this issue because we use mdg:validated-method, which uses requires you to specify an argument validator for each method.

It shuts up the argument checker by wrapping the method function with this:

    // Silence audit-argument-checks since arguments are always checked when using this package
    check(args, Match.Any);

The simplest solution I can think of, is to separate the check from the async function. You could use a wrapper function to do this:

function checkAndRun(check, run) {
  return function(...args) {
    check.apply(this, args);
    return run.apply(this, args);
  }
}

Meteor.methods({
  'example': checkAndRun(
    function(exampleID){
      check(exampleID, String);
    },
    async function(exampleID) {
      const result = await doSomethingAsync(exampleID);
      SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
      return result.status;
    }
  }
});

or you could even do it inline with an async IIFE:

Meteor.methods({
  example(exampleID) {
    check(exampleID, String);
    return (async () => {
      const result = await doSomethingAsync(exampleID);
      SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
      return result.status;
    })()
  }
});

Which, come to think of it, is much simpler than the simplest solution I could think of at first

You just want to separate the sync check from the async method body somehow


In case you're curious, let diving through the source to see where it's called. When the method is called (in ddp-server/livedata-server), we end up here, a sync method call for the first reference of audit-argument-checks: https://github.com/meteor/meteor/blob/master/packages/ddp-server/livedata_server.js#L1767-L1770

Which takes us into check/Match for another sync call here: https://github.com/meteor/meteor/blob/71f67d9dbac34aba34ceef733b2b51c2bd44b665/packages/check/match.js#L114-L123

Which uses the strange Meteor.EnvironmentVariable construct, which under the hood has another sync call: https://github.com/meteor/meteor/blob/master/packages/meteor/dynamics_nodejs.js#L57

Related