-
Notifications
You must be signed in to change notification settings - Fork 29
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
fix: properly handle large numbers in responses #74
Conversation
Codecov Report
@@ Coverage Diff @@
## master #74 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 1 1
Lines 50 55 +5
Branches 12 14 +2
=====================================
+ Hits 50 55 +5
Continue to review full report at Codecov.
|
It looks like this change would stop
|
Nope. This does not work. Axios always does a JSON.parse for strings.
There is potential to be breaking, if there are users who expect a JS object to be returned. I am not aware of any such place in the users of this module in the client libraries though. AFAIK, all users expect strings today. Users will have to JSON.parse themselves if they want an object. I'm fine with marking this semver major. |
An alternative thing to do here would be to look at the response |
Thanks for showing me the example of users expecting objects. I've added selective parsing heregit. PTAL. |
So the metadata server only sometimes responds in JSON? Shouldn't it be consistent, or is this more complicated than it seems? |
Given our history with the metadata server (aye, it be a fickle beast), I would feel a lot better if we could validate it actually returns the correct contentType header in both GCE and GCF. Sorry for asking you do more work @ofrobots, this library has always scared the crap out of me. |
@stephenplusplus documented on this page: https://cloud.google.com/compute/docs/storing-retrieving-metadata Specifically:
In theory we should be able to pass Example:
|
Ah, interesting. Is that the only scenario-- out of bounds integers? |
I'm off on vacation in a coupla hours. I'll go ahead and assign it to you @JustinBeckwith 😜. |
NOT IT! NOT IT! 🤣 No problem. Enjoy vacation! |
Please correct me if I'm wrong: If we ask for a specific value that is an integer, it has the potential to be out of bounds. It would be returned un-stringified, like: If we request several values from the metadata server, it will return it as key-value JSON response. But, isn't the same problem possible? {
someNumber: 4520031799277581759,
someBool: true,
etc: '...'
} In other words, I'm wondering if we're always at risk of losing precision when we are returned a non-stringified Number value. |
@stephenplusplus You're right. Recursive queries return JSON by default. Those would still be invalid JSON. This needs to be fixed in the metadata service itself. I have opened a bug for that. In the meanwhile I think this works around the issue in the common case where folks are not doing recursive queries? |
Is a fix possible for the metadata service, though? If it converts number values to strings, I'm not sure how we would we know the difference when returning it to the user. Or how they would know it's out of bounds or not, and how to handle that.
My only concern is that this PR leaves things in a bit of a jumbled state. It will return a string for all returned single-property values, even where other data types are expected. I believe that we used to return the parsed value, i.e. The jumbled part comes in where we won't JSON.parse single properties, but we will JSON.parse when the user requests multiple properties. I'm thinking that the user probably wants JSON.parse most of the time, since to use the values returned from the metadata service in any meaningful way, they would have to do the same on their end. From that assumption (correct if wrong), maybe allowing the user to specify the If we just need an immediate fix, and this is no big deal, 'cause only we use this thing, then YOLO. |
0cbe54b
to
63e81f0
Compare
Okay, I did some more research. It seems that as far as RFC 4627 is concerned, large numbers are valid JSON. The spec doesn't specify any precision for numbers. This means that using I pushed another commit that takes another approach to the problem. We now use This would be a semver major change, but only for the cases where we already has precision loss. WDYT? |
Implementation has change substantially.
Codecov Report
@@ Coverage Diff @@
## master #74 +/- ##
=========================================
Coverage ? 98.18%
=========================================
Files ? 1
Lines ? 55
Branches ? 13
=========================================
Hits ? 54
Misses ? 1
Partials ? 0
Continue to review full report at Codecov.
|
For reference: https://www.npmjs.com/package/bignumber.js |
It's hard to believe how perfectly this module targets what we're doing here. The only thing that could be difficult for a user is receiving a String sometimes and a Number others. If we were writing docs to show how number values will come through, (which, maybe we should), would we show: "if (string) ... else if (number) ..."? For comparison, in Spanner, when the user or API provides a number that is out of bounds, we throw when the user attempts to use it for anything other than logging / simply passing the value around: > i = new Int('4520031799277581759')
Int { value: '4520031799277581759' }
> i.value
'4520031799277581759'
> i + 3
Error: Integer 4520031799277581759 is out of bounds.
at Int.valueOf (repl:1:184) Here's the same pattern with BigInt: > b = bigint.parse('{"value":4520031799277581759}')
{ value: BigNumber { s: 1, e: 18, c: [ 45200, 31799277581759 ] } }
> b.value
BigNumber { s: 1, e: 18, c: [ 45200, 31799277581759 ] }
> b.value.valueOf()
'4520031799277581759'
> b.value + 3
'45200317992775817593' |
So logging on GCE is simply not working correctly at the moment because of this bug. See googleapis/nodejs-logging-bunyan#148. We need to figure out a solution.
The problem here is that we have very little semantic knowledge about the value and their types. Based on that I argue that it is reasonable for our contract to be: for values that are expected to be returned as a number, users will get either a number or |
I think that's reasonable. Could you just leave a note about that in the readme? |
By default axios would parse strings as JSON [1]. The strings returned by the metadata service are not JSON. This causes strings with numbers to be parsed as JavaScript Numbers which loses precision. Strings should be left unperturbed. 1: https://github.com/axios/axios/blob/9005a54a8b42be41ca49a31dcfda915d1a91c388/lib/defaults.js#L60
ae83f19
to
b5b27b2
Compare
By default axios would parse strings as JSON [1]. The strings returned
by the metadata service are not JSON. This causes strings with numbers
to be parsed as JavaScript Numbers which loses precision. Strings should
be left unperturbed.
1: https://github.com/axios/axios/blob/9005a54a8b42be41ca49a31dcfda915d1a91c388/lib/defaults.js#L60