// Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bsonrw import ( "io" "strings" "testing" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/bson/bsontype" ) var ( keyDiff = specificDiff("key") typDiff = specificDiff("type") valDiff = specificDiff("value") expectErrEOF = expectSpecificError(io.EOF) expectErrEOD = expectSpecificError(ErrEOD) expectErrEOA = expectSpecificError(ErrEOA) ) type expectedErrorFunc func(t *testing.T, err error, desc string) type peekTypeTestCase struct { desc string input string typs []bsontype.Type errFs []expectedErrorFunc } type readKeyValueTestCase struct { desc string input string keys []string typs []bsontype.Type vals []*extJSONValue keyEFs []expectedErrorFunc valEFs []expectedErrorFunc } func expectSpecificError(expected error) expectedErrorFunc { return func(t *testing.T, err error, desc string) { if err != expected { t.Helper() t.Errorf("%s: Expected %v but got: %v", desc, expected, err) t.FailNow() } } } func specificDiff(name string) func(t *testing.T, expected, actual interface{}, desc string) { return func(t *testing.T, expected, actual interface{}, desc string) { if diff := cmp.Diff(expected, actual); diff != "" { t.Helper() t.Errorf("%s: Incorrect JSON %s (-want, +got): %s\n", desc, name, diff) t.FailNow() } } } func expectErrorNOOP(_ *testing.T, _ error, _ string) { } func readKeyDiff(t *testing.T, eKey, aKey string, eTyp, aTyp bsontype.Type, err error, errF expectedErrorFunc, desc string) { keyDiff(t, eKey, aKey, desc) typDiff(t, eTyp, aTyp, desc) errF(t, err, desc) } func readValueDiff(t *testing.T, eVal, aVal *extJSONValue, err error, errF expectedErrorFunc, desc string) { if aVal != nil { typDiff(t, eVal.t, aVal.t, desc) valDiff(t, eVal.v, aVal.v, desc) } else { valDiff(t, eVal, aVal, desc) } errF(t, err, desc) } func TestExtJSONParserPeekType(t *testing.T) { makeValidPeekTypeTestCase := func(input string, typ bsontype.Type, desc string) peekTypeTestCase { return peekTypeTestCase{ desc: desc, input: input, typs: []bsontype.Type{typ}, errFs: []expectedErrorFunc{expectNoError}, } } makeInvalidTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase { return peekTypeTestCase{ desc: desc, input: input, typs: []bsontype.Type{bsontype.Type(0)}, errFs: []expectedErrorFunc{lastEF}, } } makeInvalidPeekTypeTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase { return peekTypeTestCase{ desc: desc, input: input, typs: []bsontype.Type{bsontype.Array, bsontype.String, bsontype.Type(0)}, errFs: []expectedErrorFunc{expectNoError, expectNoError, lastEF}, } } cases := []peekTypeTestCase{ makeValidPeekTypeTestCase(`null`, bsontype.Null, "Null"), makeValidPeekTypeTestCase(`"string"`, bsontype.String, "String"), makeValidPeekTypeTestCase(`true`, bsontype.Boolean, "Boolean--true"), makeValidPeekTypeTestCase(`false`, bsontype.Boolean, "Boolean--false"), makeValidPeekTypeTestCase(`{"$minKey": 1}`, bsontype.MinKey, "MinKey"), makeValidPeekTypeTestCase(`{"$maxKey": 1}`, bsontype.MaxKey, "MaxKey"), makeValidPeekTypeTestCase(`{"$numberInt": "42"}`, bsontype.Int32, "Int32"), makeValidPeekTypeTestCase(`{"$numberLong": "42"}`, bsontype.Int64, "Int64"), makeValidPeekTypeTestCase(`{"$symbol": "symbol"}`, bsontype.Symbol, "Symbol"), makeValidPeekTypeTestCase(`{"$numberDouble": "42.42"}`, bsontype.Double, "Double"), makeValidPeekTypeTestCase(`{"$undefined": true}`, bsontype.Undefined, "Undefined"), makeValidPeekTypeTestCase(`{"$numberDouble": "NaN"}`, bsontype.Double, "Double--NaN"), makeValidPeekTypeTestCase(`{"$numberDecimal": "1234"}`, bsontype.Decimal128, "Decimal"), makeValidPeekTypeTestCase(`{"foo": "bar"}`, bsontype.EmbeddedDocument, "Toplevel document"), makeValidPeekTypeTestCase(`{"$date": {"$numberLong": "0"}}`, bsontype.DateTime, "Datetime"), makeValidPeekTypeTestCase(`{"$code": "function() {}"}`, bsontype.JavaScript, "Code no scope"), makeValidPeekTypeTestCase(`[{"$numberInt": "1"},{"$numberInt": "2"}]`, bsontype.Array, "Array"), makeValidPeekTypeTestCase(`{"$timestamp": {"t": 42, "i": 1}}`, bsontype.Timestamp, "Timestamp"), makeValidPeekTypeTestCase(`{"$oid": "57e193d7a9cc81b4027498b5"}`, bsontype.ObjectID, "Object ID"), makeValidPeekTypeTestCase(`{"$binary": {"base64": "AQIDBAU=", "subType": "80"}}`, bsontype.Binary, "Binary"), makeValidPeekTypeTestCase(`{"$code": "function() {}", "$scope": {}}`, bsontype.CodeWithScope, "Code With Scope"), makeValidPeekTypeTestCase(`{"$binary": {"base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03"}}`, bsontype.Binary, "Binary"), makeValidPeekTypeTestCase(`{"$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03"}`, bsontype.Binary, "Binary"), makeValidPeekTypeTestCase(`{"$regularExpression": {"pattern": "foo*", "options": "ix"}}`, bsontype.Regex, "Regular expression"), makeValidPeekTypeTestCase(`{"$dbPointer": {"$ref": "db.collection", "$id": {"$oid": "57e193d7a9cc81b4027498b1"}}}`, bsontype.DBPointer, "DBPointer"), makeValidPeekTypeTestCase(`{"$ref": "collection", "$id": {"$oid": "57fd71e96e32ab4225b723fb"}, "$db": "database"}`, bsontype.EmbeddedDocument, "DBRef"), makeInvalidPeekTypeTestCase("invalid array--missing ]", `["a"`, expectError), makeInvalidPeekTypeTestCase("invalid array--colon in array", `["a":`, expectError), makeInvalidPeekTypeTestCase("invalid array--extra comma", `["a",,`, expectError), makeInvalidPeekTypeTestCase("invalid array--trailing comma", `["a",]`, expectError), makeInvalidPeekTypeTestCase("peekType after end of array", `["a"]`, expectErrEOA), { desc: "invalid array--leading comma", input: `[,`, typs: []bsontype.Type{bsontype.Array, bsontype.Type(0)}, errFs: []expectedErrorFunc{expectNoError, expectError}, }, makeInvalidTestCase("lone $scope", `{"$scope": {}}`, expectError), makeInvalidTestCase("empty code with unknown extra key", `{"$code":"", "0":""}`, expectError), makeInvalidTestCase("non-empty code with unknown extra key", `{"$code":"foobar", "0":""}`, expectError), } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { ejp := newExtJSONParser(strings.NewReader(tc.input), true) // Manually set the parser's starting state to jpsSawColon so peekType will read ahead to find the extjson // type of the value. If not set, the parser will be in jpsStartState and advance to jpsSawKey, which will // cause it to return without peeking the extjson type. ejp.s = jpsSawColon for i, eTyp := range tc.typs { errF := tc.errFs[i] typ, err := ejp.peekType() errF(t, err, tc.desc) if err != nil { // Don't inspect the type if there was an error return } typDiff(t, eTyp, typ, tc.desc) } }) } } func TestExtJSONParserReadKeyReadValue(t *testing.T) { // several test cases will use the same keys, types, and values, and only differ on input structure keys := []string{"_id", "Symbol", "String", "Int32", "Int64", "Int", "MinKey"} types := []bsontype.Type{bsontype.ObjectID, bsontype.Symbol, bsontype.String, bsontype.Int32, bsontype.Int64, bsontype.Int32, bsontype.MinKey} values := []*extJSONValue{ {t: bsontype.String, v: "57e193d7a9cc81b4027498b5"}, {t: bsontype.String, v: "symbol"}, {t: bsontype.String, v: "string"}, {t: bsontype.String, v: "42"}, {t: bsontype.String, v: "42"}, {t: bsontype.Int32, v: int32(42)}, {t: bsontype.Int32, v: int32(1)}, } errFuncs := make([]expectedErrorFunc, 7) for i := 0; i < 7; i++ { errFuncs[i] = expectNoError } firstKeyError := func(desc, input string) readKeyValueTestCase { return readKeyValueTestCase{ desc: desc, input: input, keys: []string{""}, typs: []bsontype.Type{bsontype.Type(0)}, vals: []*extJSONValue{nil}, keyEFs: []expectedErrorFunc{expectError}, valEFs: []expectedErrorFunc{expectErrorNOOP}, } } secondKeyError := func(desc, input, firstKey string, firstType bsontype.Type, firstValue *extJSONValue) readKeyValueTestCase { return readKeyValueTestCase{ desc: desc, input: input, keys: []string{firstKey, ""}, typs: []bsontype.Type{firstType, bsontype.Type(0)}, vals: []*extJSONValue{firstValue, nil}, keyEFs: []expectedErrorFunc{expectNoError, expectError}, valEFs: []expectedErrorFunc{expectNoError, expectErrorNOOP}, } } cases := []readKeyValueTestCase{ { desc: "normal spacing", input: `{ "_id": { "$oid": "57e193d7a9cc81b4027498b5" }, "Symbol": { "$symbol": "symbol" }, "String": "string", "Int32": { "$numberInt": "42" }, "Int64": { "$numberLong": "42" }, "Int": 42, "MinKey": { "$minKey": 1 } }`, keys: keys, typs: types, vals: values, keyEFs: errFuncs, valEFs: errFuncs, }, { desc: "new line before comma", input: `{ "_id": { "$oid": "57e193d7a9cc81b4027498b5" } , "Symbol": { "$symbol": "symbol" } , "String": "string" , "Int32": { "$numberInt": "42" } , "Int64": { "$numberLong": "42" } , "Int": 42 , "MinKey": { "$minKey": 1 } }`, keys: keys, typs: types, vals: values, keyEFs: errFuncs, valEFs: errFuncs, }, { desc: "tabs around colons", input: `{ "_id": { "$oid" : "57e193d7a9cc81b4027498b5" }, "Symbol": { "$symbol" : "symbol" }, "String": "string", "Int32": { "$numberInt" : "42" }, "Int64": { "$numberLong": "42" }, "Int": 42, "MinKey": { "$minKey": 1 } }`, keys: keys, typs: types, vals: values, keyEFs: errFuncs, valEFs: errFuncs, }, { desc: "no whitespace", input: `{"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Symbol":{"$symbol":"symbol"},"String":"string","Int32":{"$numberInt":"42"},"Int64":{"$numberLong":"42"},"Int":42,"MinKey":{"$minKey":1}}`, keys: keys, typs: types, vals: values, keyEFs: errFuncs, valEFs: errFuncs, }, { desc: "mixed whitespace", input: ` { "_id" : { "$oid": "57e193d7a9cc81b4027498b5" }, "Symbol" : { "$symbol": "symbol" } , "String" : "string", "Int32" : { "$numberInt": "42" } , "Int64" : {"$numberLong" : "42"}, "Int" : 42, "MinKey" : { "$minKey": 1 } } `, keys: keys, typs: types, vals: values, keyEFs: errFuncs, valEFs: errFuncs, }, { desc: "nested object", input: `{"k1": 1, "k2": { "k3": { "k4": 4 } }, "k5": 5}`, keys: []string{"k1", "k2", "k3", "k4", "", "", "k5", ""}, typs: []bsontype.Type{bsontype.Int32, bsontype.EmbeddedDocument, bsontype.EmbeddedDocument, bsontype.Int32, bsontype.Type(0), bsontype.Type(0), bsontype.Int32, bsontype.Type(0)}, vals: []*extJSONValue{ {t: bsontype.Int32, v: int32(1)}, nil, nil, {t: bsontype.Int32, v: int32(4)}, nil, nil, {t: bsontype.Int32, v: int32(5)}, nil, }, keyEFs: []expectedErrorFunc{ expectNoError, expectNoError, expectNoError, expectNoError, expectErrEOD, expectErrEOD, expectNoError, expectErrEOD, }, valEFs: []expectedErrorFunc{ expectNoError, expectError, expectError, expectNoError, expectErrorNOOP, expectErrorNOOP, expectNoError, expectErrorNOOP, }, }, { desc: "invalid input: invalid values for extended type", input: `{"a": {"$numberInt": "1", "x"`, keys: []string{"a"}, typs: []bsontype.Type{bsontype.Int32}, vals: []*extJSONValue{nil}, keyEFs: []expectedErrorFunc{expectNoError}, valEFs: []expectedErrorFunc{expectError}, }, firstKeyError("invalid input: missing key--EOF", "{"), firstKeyError("invalid input: missing key--colon first", "{:"), firstKeyError("invalid input: missing value", `{"a":`), firstKeyError("invalid input: missing colon", `{"a" 1`), firstKeyError("invalid input: extra colon", `{"a"::`), secondKeyError("invalid input: missing }", `{"a": 1`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}), secondKeyError("invalid input: missing comma", `{"a": 1 "b"`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}), secondKeyError("invalid input: extra comma", `{"a": 1,, "b"`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}), secondKeyError("invalid input: trailing comma in object", `{"a": 1,}`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}), { desc: "invalid input: lone scope after a complete value", input: `{"a": "", "b": {"$scope: ""}}`, keys: []string{"a"}, typs: []bsontype.Type{bsontype.String}, vals: []*extJSONValue{{bsontype.String, ""}}, keyEFs: []expectedErrorFunc{expectNoError, expectNoError}, valEFs: []expectedErrorFunc{expectNoError, expectError}, }, { desc: "invalid input: lone scope nested", input: `{"a":{"b":{"$scope":{`, keys: []string{}, typs: []bsontype.Type{}, vals: []*extJSONValue{nil}, keyEFs: []expectedErrorFunc{expectNoError}, valEFs: []expectedErrorFunc{expectError}, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { ejp := newExtJSONParser(strings.NewReader(tc.input), true) for i, eKey := range tc.keys { eTyp := tc.typs[i] eVal := tc.vals[i] keyErrF := tc.keyEFs[i] valErrF := tc.valEFs[i] k, typ, err := ejp.readKey() readKeyDiff(t, eKey, k, eTyp, typ, err, keyErrF, tc.desc) v, err := ejp.readValue(typ) readValueDiff(t, eVal, v, err, valErrF, tc.desc) } }) } } type ejpExpectationTest func(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) type ejpTestCase struct { f ejpExpectationTest p *extJSONParser k string t bsontype.Type v interface{} } // expectSingleValue is used for simple JSON types (strings, numbers, literals) and for extended JSON types that // have single key-value pairs (i.e. { "$minKey": 1 }, { "$numberLong": "42.42" }) func expectSingleValue(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) { eVal := expectedValue.(*extJSONValue) k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey) v, err := p.readValue(typ) readValueDiff(t, eVal, v, err, expectNoError, expectedKey) } // expectMultipleValues is used for values that are subdocuments of known size and with known keys (such as extended // JSON types { "$timestamp": {"t": 1, "i": 1} } and { "$regularExpression": {"pattern": "", options: ""} }) func expectMultipleValues(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) { k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey) v, err := p.readValue(typ) expectNoError(t, err, "") typDiff(t, bsontype.EmbeddedDocument, v.t, expectedKey) actObj := v.v.(*extJSONObject) expObj := expectedValue.(*extJSONObject) for i, actKey := range actObj.keys { expKey := expObj.keys[i] actVal := actObj.values[i] expVal := expObj.values[i] keyDiff(t, expKey, actKey, expectedKey) typDiff(t, expVal.t, actVal.t, expectedKey) valDiff(t, expVal.v, actVal.v, expectedKey) } } type ejpKeyTypValTriple struct { key string typ bsontype.Type val *extJSONValue } type ejpSubDocumentTestValue struct { code string // code is only used for TypeCodeWithScope (and is ignored for TypeEmbeddedDocument ktvs []ejpKeyTypValTriple // list of (key, type, value) triples; this is "scope" for TypeCodeWithScope } // expectSubDocument is used for embedded documents and code with scope types; it reads all the keys and values // in the embedded document (or scope for codeWithScope) and compares them to the expectedValue's list of (key, type, // value) triples func expectSubDocument(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) { subdoc := expectedValue.(ejpSubDocumentTestValue) k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey) if expectedType == bsontype.CodeWithScope { v, err := p.readValue(typ) readValueDiff(t, &extJSONValue{t: bsontype.String, v: subdoc.code}, v, err, expectNoError, expectedKey) } for _, ktv := range subdoc.ktvs { eKey := ktv.key eTyp := ktv.typ eVal := ktv.val k, typ, err = p.readKey() readKeyDiff(t, eKey, k, eTyp, typ, err, expectNoError, expectedKey) v, err := p.readValue(typ) readValueDiff(t, eVal, v, err, expectNoError, expectedKey) } if expectedType == bsontype.CodeWithScope { // expect scope doc to close k, typ, err = p.readKey() readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, expectedKey) } // expect subdoc to close k, typ, err = p.readKey() readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, expectedKey) } // expectArray takes the expectedKey, ignores the expectedType, and uses the expectedValue // as a slice of (type Type, value *extJSONValue) pairs func expectArray(t *testing.T, p *extJSONParser, expectedKey string, _ bsontype.Type, expectedValue interface{}) { ktvs := expectedValue.([]ejpKeyTypValTriple) k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, bsontype.Array, typ, err, expectNoError, expectedKey) for _, ktv := range ktvs { eTyp := ktv.typ eVal := ktv.val typ, err = p.peekType() typDiff(t, eTyp, typ, expectedKey) expectNoError(t, err, expectedKey) v, err := p.readValue(typ) readValueDiff(t, eVal, v, err, expectNoError, expectedKey) } // expect array to end typ, err = p.peekType() typDiff(t, bsontype.Type(0), typ, expectedKey) expectErrEOA(t, err, expectedKey) } func TestExtJSONParserAllTypes(t *testing.T) { in := ` { "_id" : { "$oid": "57e193d7a9cc81b4027498b5"} , "Symbol" : { "$symbol": "symbol"} , "String" : "string" , "Int32" : { "$numberInt": "42"} , "Int64" : { "$numberLong": "42"} , "Double" : { "$numberDouble": "42.42"} , "SpecialFloat" : { "$numberDouble": "NaN" } , "Decimal" : { "$numberDecimal": "1234" } , "Binary" : { "$binary": { "base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03" } } , "BinaryLegacy" : { "$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03" } , "BinaryUserDefined" : { "$binary": { "base64": "AQIDBAU=", "subType": "80" } } , "Code" : { "$code": "function() {}" } , "CodeWithEmptyScope" : { "$code": "function() {}", "$scope": {} } , "CodeWithScope" : { "$code": "function() {}", "$scope": { "x": 1 } } , "EmptySubdocument" : {} , "Subdocument" : { "foo": "bar", "baz": { "$numberInt": "42" } } , "Array" : [{"$numberInt": "1"}, {"$numberLong": "2"}, {"$numberDouble": "3"}, 4, "string", 5.0] , "Timestamp" : { "$timestamp": { "t": 42, "i": 1 } } , "RegularExpression" : { "$regularExpression": { "pattern": "foo*", "options": "ix" } } , "DatetimeEpoch" : { "$date": { "$numberLong": "0" } } , "DatetimePositive" : { "$date": { "$numberLong": "9223372036854775807" } } , "DatetimeNegative" : { "$date": { "$numberLong": "-9223372036854775808" } } , "True" : true , "False" : false , "DBPointer" : { "$dbPointer": { "$ref": "db.collection", "$id": { "$oid": "57e193d7a9cc81b4027498b1" } } } , "DBRef" : { "$ref": "collection", "$id": { "$oid": "57fd71e96e32ab4225b723fb" }, "$db": "database" } , "DBRefNoDB" : { "$ref": "collection", "$id": { "$oid": "57fd71e96e32ab4225b723fb" } } , "MinKey" : { "$minKey": 1 } , "MaxKey" : { "$maxKey": 1 } , "Null" : null , "Undefined" : { "$undefined": true } }` ejp := newExtJSONParser(strings.NewReader(in), true) cases := []ejpTestCase{ { f: expectSingleValue, p: ejp, k: "_id", t: bsontype.ObjectID, v: &extJSONValue{t: bsontype.String, v: "57e193d7a9cc81b4027498b5"}, }, { f: expectSingleValue, p: ejp, k: "Symbol", t: bsontype.Symbol, v: &extJSONValue{t: bsontype.String, v: "symbol"}, }, { f: expectSingleValue, p: ejp, k: "String", t: bsontype.String, v: &extJSONValue{t: bsontype.String, v: "string"}, }, { f: expectSingleValue, p: ejp, k: "Int32", t: bsontype.Int32, v: &extJSONValue{t: bsontype.String, v: "42"}, }, { f: expectSingleValue, p: ejp, k: "Int64", t: bsontype.Int64, v: &extJSONValue{t: bsontype.String, v: "42"}, }, { f: expectSingleValue, p: ejp, k: "Double", t: bsontype.Double, v: &extJSONValue{t: bsontype.String, v: "42.42"}, }, { f: expectSingleValue, p: ejp, k: "SpecialFloat", t: bsontype.Double, v: &extJSONValue{t: bsontype.String, v: "NaN"}, }, { f: expectSingleValue, p: ejp, k: "Decimal", t: bsontype.Decimal128, v: &extJSONValue{t: bsontype.String, v: "1234"}, }, { f: expectMultipleValues, p: ejp, k: "Binary", t: bsontype.Binary, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{ {t: bsontype.String, v: "o0w498Or7cijeBSpkquNtg=="}, {t: bsontype.String, v: "03"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "BinaryLegacy", t: bsontype.Binary, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{ {t: bsontype.String, v: "o0w498Or7cijeBSpkquNtg=="}, {t: bsontype.String, v: "03"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "BinaryUserDefined", t: bsontype.Binary, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{ {t: bsontype.String, v: "AQIDBAU="}, {t: bsontype.String, v: "80"}, }, }, }, { f: expectSingleValue, p: ejp, k: "Code", t: bsontype.JavaScript, v: &extJSONValue{t: bsontype.String, v: "function() {}"}, }, { f: expectSubDocument, p: ejp, k: "CodeWithEmptyScope", t: bsontype.CodeWithScope, v: ejpSubDocumentTestValue{ code: "function() {}", ktvs: []ejpKeyTypValTriple{}, }, }, { f: expectSubDocument, p: ejp, k: "CodeWithScope", t: bsontype.CodeWithScope, v: ejpSubDocumentTestValue{ code: "function() {}", ktvs: []ejpKeyTypValTriple{ {"x", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}}, }, }, }, { f: expectSubDocument, p: ejp, k: "EmptySubdocument", t: bsontype.EmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{}, }, }, { f: expectSubDocument, p: ejp, k: "Subdocument", t: bsontype.EmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{ {"foo", bsontype.String, &extJSONValue{t: bsontype.String, v: "bar"}}, {"baz", bsontype.Int32, &extJSONValue{t: bsontype.String, v: "42"}}, }, }, }, { f: expectArray, p: ejp, k: "Array", t: bsontype.Array, v: []ejpKeyTypValTriple{ {typ: bsontype.Int32, val: &extJSONValue{t: bsontype.String, v: "1"}}, {typ: bsontype.Int64, val: &extJSONValue{t: bsontype.String, v: "2"}}, {typ: bsontype.Double, val: &extJSONValue{t: bsontype.String, v: "3"}}, {typ: bsontype.Int32, val: &extJSONValue{t: bsontype.Int32, v: int32(4)}}, {typ: bsontype.String, val: &extJSONValue{t: bsontype.String, v: "string"}}, {typ: bsontype.Double, val: &extJSONValue{t: bsontype.Double, v: 5.0}}, }, }, { f: expectMultipleValues, p: ejp, k: "Timestamp", t: bsontype.Timestamp, v: &extJSONObject{ keys: []string{"t", "i"}, values: []*extJSONValue{ {t: bsontype.Int32, v: int32(42)}, {t: bsontype.Int32, v: int32(1)}, }, }, }, { f: expectMultipleValues, p: ejp, k: "RegularExpression", t: bsontype.Regex, v: &extJSONObject{ keys: []string{"pattern", "options"}, values: []*extJSONValue{ {t: bsontype.String, v: "foo*"}, {t: bsontype.String, v: "ix"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "DatetimeEpoch", t: bsontype.DateTime, v: &extJSONObject{ keys: []string{"$numberLong"}, values: []*extJSONValue{ {t: bsontype.String, v: "0"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "DatetimePositive", t: bsontype.DateTime, v: &extJSONObject{ keys: []string{"$numberLong"}, values: []*extJSONValue{ {t: bsontype.String, v: "9223372036854775807"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "DatetimeNegative", t: bsontype.DateTime, v: &extJSONObject{ keys: []string{"$numberLong"}, values: []*extJSONValue{ {t: bsontype.String, v: "-9223372036854775808"}, }, }, }, { f: expectSingleValue, p: ejp, k: "True", t: bsontype.Boolean, v: &extJSONValue{t: bsontype.Boolean, v: true}, }, { f: expectSingleValue, p: ejp, k: "False", t: bsontype.Boolean, v: &extJSONValue{t: bsontype.Boolean, v: false}, }, { f: expectMultipleValues, p: ejp, k: "DBPointer", t: bsontype.DBPointer, v: &extJSONObject{ keys: []string{"$ref", "$id"}, values: []*extJSONValue{ {t: bsontype.String, v: "db.collection"}, {t: bsontype.String, v: "57e193d7a9cc81b4027498b1"}, }, }, }, { f: expectSubDocument, p: ejp, k: "DBRef", t: bsontype.EmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{ {"$ref", bsontype.String, &extJSONValue{t: bsontype.String, v: "collection"}}, {"$id", bsontype.ObjectID, &extJSONValue{t: bsontype.String, v: "57fd71e96e32ab4225b723fb"}}, {"$db", bsontype.String, &extJSONValue{t: bsontype.String, v: "database"}}, }, }, }, { f: expectSubDocument, p: ejp, k: "DBRefNoDB", t: bsontype.EmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{ {"$ref", bsontype.String, &extJSONValue{t: bsontype.String, v: "collection"}}, {"$id", bsontype.ObjectID, &extJSONValue{t: bsontype.String, v: "57fd71e96e32ab4225b723fb"}}, }, }, }, { f: expectSingleValue, p: ejp, k: "MinKey", t: bsontype.MinKey, v: &extJSONValue{t: bsontype.Int32, v: int32(1)}, }, { f: expectSingleValue, p: ejp, k: "MaxKey", t: bsontype.MaxKey, v: &extJSONValue{t: bsontype.Int32, v: int32(1)}, }, { f: expectSingleValue, p: ejp, k: "Null", t: bsontype.Null, v: &extJSONValue{t: bsontype.Null, v: nil}, }, { f: expectSingleValue, p: ejp, k: "Undefined", t: bsontype.Undefined, v: &extJSONValue{t: bsontype.Boolean, v: true}, }, } // run the test cases for _, tc := range cases { tc.f(t, tc.p, tc.k, tc.t, tc.v) } // expect end of whole document: read final } k, typ, err := ejp.readKey() readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, "") // expect end of whole document: read EOF k, typ, err = ejp.readKey() readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOF, "") if diff := cmp.Diff(jpsDoneState, ejp.s); diff != "" { t.Errorf("expected parser to be in done state but instead is in %v\n", ejp.s) t.FailNow() } } func TestExtJSONValue(t *testing.T) { t.Run("Large Date", func(t *testing.T) { val := &extJSONValue{ t: bsontype.String, v: "3001-01-01T00:00:00Z", } intVal, err := val.parseDateTime() if err != nil { t.Fatalf("error parsing date time: %v", err) } if intVal <= 0 { t.Fatalf("expected value above 0, got %v", intVal) } }) t.Run("fallback time format", func(t *testing.T) { val := &extJSONValue{ t: bsontype.String, v: "2019-06-04T14:54:31.416+0000", } _, err := val.parseDateTime() if err != nil { t.Fatalf("error parsing date time: %v", err) } }) }