-
Notifications
You must be signed in to change notification settings - Fork 94
fix: field deserialize documents VSCODE-772 #1303
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
Open
tculig
wants to merge
11
commits into
mongodb-js:main
Choose a base branch
from
tculig:VSCODE-772-field-deserialize-documents
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
75a4c3e
handle deserialize issues
tculig 15c673e
simplify display
tculig 5dfa18e
avoid quotes on invalid values
tculig da4e2a0
Merge branch 'main' into VSCODE-772-field-deserialize-documents
tculig fedc52d
fix bug
tculig 191097e
lint
tculig 5661442
Merge branch 'main' into VSCODE-772-field-deserialize-documents
tculig 89b5fe9
Fix labelFromDollarKey
tculig e7184d5
Add SAFE_KEY concept
tculig 9eec013
update comment
tculig 1f8d559
lint
tculig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
241 changes: 241 additions & 0 deletions
241
src/test/suite/views/data-browsing-app/gracefulEjsonDeserialize.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| import { expect } from 'chai'; | ||
|
|
||
| import { | ||
| gracefullyDeserializeEjson, | ||
| toDisplayString, | ||
| InvalidBSONValue, | ||
| } from '../../../../views/data-browsing-app/gracefulEjsonDeserialize'; | ||
|
|
||
| describe('gracefulEjsonDeserialize', function () { | ||
| describe('gracefullyDeserializeEjson', function () { | ||
| it('should deserialize a valid EJSON document', function () { | ||
| const ejson = { | ||
| _id: { $oid: '507f1f77bcf86cd799439011' }, | ||
| count: { $numberLong: '42' }, | ||
| name: 'hello', | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result._id).to.have.property('_bsontype', 'ObjectId'); | ||
| expect(result.count).to.have.property('_bsontype', 'Long'); | ||
| expect(result.name).to.equal('hello'); | ||
| }); | ||
|
|
||
| it('should substitute InvalidBSONValue for an invalid $numberLong', function () { | ||
| const ejson = { | ||
| good: { $numberLong: '42' }, | ||
| bad: { $numberLong: 'pineapple' }, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result.good).to.have.property('_bsontype', 'Long'); | ||
| expect(result.bad).to.be.instanceOf(InvalidBSONValue); | ||
| expect((result.bad as InvalidBSONValue).typeName).to.equal('NumberLong'); | ||
| }); | ||
|
|
||
| it('should substitute InvalidBSONValue for an invalid $date', function () { | ||
| const ejson = { | ||
| good: { $date: '2024-01-01T00:00:00Z' }, | ||
| bad: { $date: 'pineapple' }, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result.good).to.be.instanceOf(Date); | ||
| expect(result.bad).to.be.instanceOf(InvalidBSONValue); | ||
| expect((result.bad as InvalidBSONValue).typeName).to.equal('Date'); | ||
| }); | ||
|
|
||
| it('should substitute InvalidBSONValue for an invalid $oid', function () { | ||
| const ejson = { bad: { $oid: 'not-an-objectid' } }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result.bad).to.be.instanceOf(InvalidBSONValue); | ||
| expect((result.bad as InvalidBSONValue).typeName).to.equal('Oid'); | ||
| }); | ||
|
|
||
| it('should substitute InvalidBSONValue for an invalid $numberDecimal', function () { | ||
| const ejson = { | ||
| good: { $numberDecimal: '3.14' }, | ||
| bad: { $numberDecimal: 'not-a-number' }, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result.good).to.have.property('_bsontype', 'Decimal128'); | ||
| expect(result.bad).to.be.instanceOf(InvalidBSONValue); | ||
| expect((result.bad as InvalidBSONValue).typeName).to.equal( | ||
| 'NumberDecimal', | ||
| ); | ||
| }); | ||
|
|
||
| it('should pass through regular objects with $-prefixed keys', function () { | ||
| const ejson = { | ||
| query: { $set: { x: 1 } }, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| // $set is not an EJSON type wrapper, so it should be passed through | ||
| // as a regular object. | ||
| expect(result.query).to.not.be.instanceOf(InvalidBSONValue); | ||
| expect(result.query).to.have.property('$set'); | ||
| }); | ||
|
|
||
| it('should not treat a subdocument with mixed $-prefixed and plain keys as an EJSON wrapper', function () { | ||
| const ejson = { | ||
| item: { $date: '2024-01-01T00:00:00Z', extra: 'field' }, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| // Has a non-$-prefixed key alongside $date, so it's a plain object, | ||
| // not an EJSON type wrapper. | ||
| expect(result.item).to.not.be.instanceOf(InvalidBSONValue); | ||
| const item = result.item as Record<string, unknown>; | ||
| expect(item.extra).to.equal('field'); | ||
| // The $date field inside should be deserialized individually. | ||
| expect(item.$date).to.equal('2024-01-01T00:00:00Z'); | ||
| }); | ||
|
|
||
| it('should handle nested documents with mixed valid/invalid values', function () { | ||
| const ejson = { | ||
| _id: { $oid: '507f1f77bcf86cd799439011' }, | ||
| nested: { | ||
| ok: { $numberLong: '42' }, | ||
| bad: { $numberLong: 'NaN' }, | ||
| deep: { | ||
| name: 'test', | ||
| also_bad: { $date: 'garbage' }, | ||
| }, | ||
| }, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result._id).to.have.property('_bsontype', 'ObjectId'); | ||
| const nested = result.nested as Record<string, unknown>; | ||
| expect(nested.ok).to.have.property('_bsontype', 'Long'); | ||
| expect(nested.bad).to.be.instanceOf(InvalidBSONValue); | ||
| const deep = nested.deep as Record<string, unknown>; | ||
| expect(deep.name).to.equal('test'); | ||
| expect(deep.also_bad).to.be.instanceOf(InvalidBSONValue); | ||
| }); | ||
|
|
||
| it('should handle arrays with mixed valid/invalid values', function () { | ||
| const ejson = { | ||
| items: [{ $numberLong: '1' }, { $numberLong: 'bad' }, 'plain string'], | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| const items = result.items as unknown[]; | ||
| expect(items[0]).to.have.property('_bsontype', 'Long'); | ||
| expect(items[1]).to.be.instanceOf(InvalidBSONValue); | ||
| expect(items[2]).to.equal('plain string'); | ||
| }); | ||
|
|
||
| it('should handle primitives and nulls', function () { | ||
| const ejson = { | ||
| str: 'hello', | ||
| num: 42, | ||
| bool: true, | ||
| nil: null, | ||
| }; | ||
| const result = gracefullyDeserializeEjson(ejson); | ||
|
|
||
| expect(result.str).to.equal('hello'); | ||
| expect(result.num).to.equal(42); | ||
| expect(result.bool).to.equal(true); | ||
| expect(result.nil).to.equal(null); | ||
| }); | ||
| }); | ||
|
|
||
| describe('toDisplayString', function () { | ||
| it('should render InvalidBSONValue as unquoted text', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| bad: { $numberLong: 'pineapple' }, | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| // Should contain "Invalid NumberLong" without quotes around it | ||
| expect(output).to.include('Invalid NumberLong'); | ||
| // Should NOT be quoted like a string | ||
| expect(output).to.not.include("'Invalid NumberLong'"); | ||
| expect(output).to.not.include('"Invalid NumberLong"'); | ||
| }); | ||
|
|
||
| it('should render valid BSON types in shell syntax', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| _id: { $oid: '507f1f77bcf86cd799439011' }, | ||
| count: { $numberLong: '42' }, | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| expect(output).to.include("ObjectId('507f1f77bcf86cd799439011')"); | ||
| expect(output).to.include("NumberLong('42')"); | ||
| }); | ||
|
|
||
| it('should render a mix of valid, invalid, and primitive values', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| _id: { $oid: '507f1f77bcf86cd799439011' }, | ||
| bad: { $numberLong: 'NaN' }, | ||
| name: 'hello', | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| expect(output).to.include("ObjectId('507f1f77bcf86cd799439011')"); | ||
| expect(output).to.include('Invalid NumberLong'); | ||
| expect(output).to.include("'hello'"); | ||
| }); | ||
|
|
||
| it('should render nested objects with proper indentation', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| nested: { | ||
| ok: { $numberLong: '42' }, | ||
| bad: { $date: 'garbage' }, | ||
| }, | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| expect(output).to.include('nested: {'); | ||
| expect(output).to.include("NumberLong('42')"); | ||
| expect(output).to.include('Invalid Date'); | ||
| }); | ||
|
|
||
| it('should render arrays', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| items: [{ $numberLong: '1' }, { $numberLong: 'bad' }, 'plain'], | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| expect(output).to.include("NumberLong('1')"); | ||
| expect(output).to.include('Invalid NumberLong'); | ||
| expect(output).to.include("'plain'"); | ||
| }); | ||
|
|
||
| it('should quote keys that are not valid JS identifiers', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| valid_key: 'a', | ||
| 'with space': 'b', | ||
| '123start': 'c', | ||
| 'with-hyphen': 'd', | ||
| "it's": 'e', | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| expect(output).to.include("valid_key: 'a'"); | ||
| expect(output).to.include("'with space': 'b'"); | ||
| expect(output).to.include("'123start': 'c'"); | ||
| expect(output).to.include("'with-hyphen': 'd'"); | ||
| expect(output).to.include("'it\\'s': 'e'"); | ||
| }); | ||
|
|
||
| it('should render empty objects and arrays', function () { | ||
| const doc = gracefullyDeserializeEjson({ | ||
| emptyObj: {}, | ||
| emptyArr: [], | ||
| }); | ||
| const output = toDisplayString(doc); | ||
|
|
||
| // The formatter might represent these differently, but they should | ||
| // not cause errors. | ||
| expect(output).to.be.a('string'); | ||
| expect(output.length).to.be.greaterThan(0); | ||
| }); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says “The $date field inside should be deserialized individually”, but the assertion expects the raw string value and the implementation does not deserialize
$datewhen it’s just a normal field in a mixed-key subdocument. Update the comment to reflect the actual behavior (i.e.,$dateis treated as a regular field here).