goext/mongo/bson/bsonrw/extjson_parser_test.go

789 lines
27 KiB
Go

// 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)
}
})
}