Loopback: Loopback standard model REST endpoints automatically round BIGINT params

Created on 18 May 2016  路  7Comments  路  Source: strongloop/loopback

Summary

This one is causing me a lot of pain.

After a lot of debugging it looks like Loopback is automatically assuming an ID value on the URI (e.g. /api/myModel/:id) is a Number.

So something like this:
curl -X GET --header "Accept: application/json" "http://192.168.99.100:3000/api/profiles/5828128208445124611"

Does not work because Loopback is automatically converting the BIGINT value to Number, which rounds 5828128208445124611 to 5828128208445125000.

So the above GET request example, results in an error:

{
  "error": {
    "name": "Error",
    "status": 404,
    "message": "Unknown \"Profile\" id \"4828128208445124611\".",
    "statusCode": 404,
    "code": "MODEL_NOT_FOUND",
    "stack": "Error: Unknown \"Profile\" id \"4828128208445124611\".\n    at Function.convertNullToNotFoundError (/usr/src/app/node_modules/loopback/lib/persisted-model.js:81:17)\n    at invokeRestAfter (/usr/src/app/node_modules/loopback/node_modules/strong-remoting/lib/rest-adapter.js:453:25)\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:607:21\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:246:17\n    at iterate (/usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:146:13)\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:157:25\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:248:21\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:612:34\n    at interceptInvocationErrors (/usr/src/app/node_modules/loopback/node_modules/strong-remoting/lib/remote-objects.js:681:22)\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:154:25\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:154:25\n    at /usr/src/app/node_modules/loopback/node_modules/async/lib/async.js:154:25\n    at execStack (/usr/src/app/node_modules/loopback/node_modules/strong-remoting/lib/remote-objects.js:488:7)\n    at RemoteObjects.execHooks (/usr/src/app/node_modules/loopback/node_modules/strong-remoting/lib/remote-objects.js:492:10)\n    at phaseAfterInvoke (/usr/src/app/node_modules/loopback/node_modules/strong-remoting/lib/remote-objects.js:652:10)\n    at runHandler (/usr/src/app/node_modules/loopback/node_modules/loopback-phase/lib/phase.js:135:5)"
  }
}

Enabling debugging output in Loopback shows:

Tue, 17 May 2016 19:52:05 GMT loopback:connector:postgresql SQL: SELECT "user_id","first_name","last_name","email","phone","created_at","updated_at","created_by","updated_by" FROM "user_profile"."profile" WHERE "user_id"=$1 ORDER BY "user_id" LIMIT 1
Parameters: ["4828128208445125000"]

So clearly the value 4828128208445124611 gets rounded into 4828128208445125000.

I verified that this happens right at the beginning, using an operations hook:

  Profile.observe('access', function logQuery(ctx, next) {
    console.log('Context', ctx);
    next();
  });

The output shows the ID is already truncated:

query:
 { where: { userId: '4828128208445125000' },
   limit: 1,
   offset: 0,
   skip: 0 },

Is this a known issue? I couldn't find documentation or bug reports etc. on this.

Custom Remote Method

Btw, adding my own remoteMethod where I have control over the Input Argument (i.e. forcing it to be a string) and then calling findById programmatically works correctly:

  Profile.get = function(id, cb) {
    console.log('ID:', id);
    Profile.findById(id).then(function(profile) {
      console.log('FindById: profile', profile);
      cb(null, profile);
    });
  };
  Profile.remoteMethod(
    'get',
    {
      accepts: [
        {arg: 'id', type: 'string', required: true}
      ],
      http: {path: '/:id/get', verb: 'get'},
      returns: {arg: 'profile', type: 'object'}
    }
  );

You can see above I simply am copying the default findById REST endpoint, only difference is I add get to the end of the URI /api/profiles/:id/get. The logic just defers to Profile.findById().

Calling it:

curl -X GET --header "Accept: application/json" "http://192.168.99.100:3000/api/profiles/4828128208445124611/get"

Works with no issues.

bug major

All 7 comments

Looks like this is the same issue as https://github.com/strongloop/loopback/issues/1870 which is supposed to be fixed by https://github.com/strongloop/loopback/pull/1221.

@raymondfeng are there still plans to land #1221? Maybe someone else should take it over if not.

@bajtos, is this related to coercion clean-up in strong-remoting? Do you have any PRs that fix the issue?

@richardpringle I think it may be related to coercion clean-up, thank you for pointing it out!

This problem has been fixed by #343 in LoopBack 3.0 (alpha). When converting "any"-typed argument from a string-source like a query string, we preserve string values that are too large to store in an integer (greater than MAX_SAFE_INTEGER).

@bajtos Any chance this can this be backported to 2.x? We've been bitten by this a couple of times.

@lukewendling @justinlueders
I am afraid there is no easy way how to backport the fix to 2.x, as the coercion code was completely rewritten in 3.0.

I think this is the place where to fix the problem in 2.x, if you feel like contributing the fix yourself:
https://github.com/strongloop/strong-remoting/blob/c88d61c3334bcc64e94727bf482904900f134590/lib/http-context.js#L298

I am using version loopback ver 3.22.0 with loopback-connector-mssql ver 3.3.0.
when setting a model properties as the following:

"objectId": {
"type": "Number",
"id": true,
"generated": true,
"required": false,
"length": null,
"precision": null,
"scale": null,
"mssql": {
"columnName": "objectId",
"dataType": "bigint",
"dataLength": null,
"dataPrecision": null,
"dataScale": null,
"nullable": "NO"
}

(notice the "id":true and "dataType": "bigint")
LB round the BIGINT params.

Changing the id property to false fixes the issue, but loopback does not allow models with no id when connecting a DB, I used a very unprofessional work around at the moment)

Is this issue still marked as an open bug?

Was this page helpful?
0 / 5 - 0 ratings