...

Source file src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_parser_test.go

Documentation: go.mongodb.org/mongo-driver/bson/bsonrw

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package bsonrw
     8  
     9  import (
    10  	"errors"
    11  	"io"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"go.mongodb.org/mongo-driver/bson/bsontype"
    17  )
    18  
    19  var (
    20  	keyDiff = specificDiff("key")
    21  	typDiff = specificDiff("type")
    22  	valDiff = specificDiff("value")
    23  
    24  	expectErrEOF = expectSpecificError(io.EOF)
    25  	expectErrEOD = expectSpecificError(ErrEOD)
    26  	expectErrEOA = expectSpecificError(ErrEOA)
    27  )
    28  
    29  type expectedErrorFunc func(t *testing.T, err error, desc string)
    30  
    31  type peekTypeTestCase struct {
    32  	desc  string
    33  	input string
    34  	typs  []bsontype.Type
    35  	errFs []expectedErrorFunc
    36  }
    37  
    38  type readKeyValueTestCase struct {
    39  	desc  string
    40  	input string
    41  	keys  []string
    42  	typs  []bsontype.Type
    43  	vals  []*extJSONValue
    44  
    45  	keyEFs []expectedErrorFunc
    46  	valEFs []expectedErrorFunc
    47  }
    48  
    49  func expectSpecificError(expected error) expectedErrorFunc {
    50  	return func(t *testing.T, err error, desc string) {
    51  		if !errors.Is(err, expected) {
    52  			t.Helper()
    53  			t.Errorf("%s: Expected %v but got: %v", desc, expected, err)
    54  			t.FailNow()
    55  		}
    56  	}
    57  }
    58  
    59  func specificDiff(name string) func(t *testing.T, expected, actual interface{}, desc string) {
    60  	return func(t *testing.T, expected, actual interface{}, desc string) {
    61  		if diff := cmp.Diff(expected, actual); diff != "" {
    62  			t.Helper()
    63  			t.Errorf("%s: Incorrect JSON %s (-want, +got): %s\n", desc, name, diff)
    64  			t.FailNow()
    65  		}
    66  	}
    67  }
    68  
    69  func expectErrorNOOP(_ *testing.T, _ error, _ string) {
    70  }
    71  
    72  func readKeyDiff(t *testing.T, eKey, aKey string, eTyp, aTyp bsontype.Type, err error, errF expectedErrorFunc, desc string) {
    73  	keyDiff(t, eKey, aKey, desc)
    74  	typDiff(t, eTyp, aTyp, desc)
    75  	errF(t, err, desc)
    76  }
    77  
    78  func readValueDiff(t *testing.T, eVal, aVal *extJSONValue, err error, errF expectedErrorFunc, desc string) {
    79  	if aVal != nil {
    80  		typDiff(t, eVal.t, aVal.t, desc)
    81  		valDiff(t, eVal.v, aVal.v, desc)
    82  	} else {
    83  		valDiff(t, eVal, aVal, desc)
    84  	}
    85  
    86  	errF(t, err, desc)
    87  }
    88  
    89  func TestExtJSONParserPeekType(t *testing.T) {
    90  	makeValidPeekTypeTestCase := func(input string, typ bsontype.Type, desc string) peekTypeTestCase {
    91  		return peekTypeTestCase{
    92  			desc: desc, input: input,
    93  			typs:  []bsontype.Type{typ},
    94  			errFs: []expectedErrorFunc{expectNoError},
    95  		}
    96  	}
    97  	makeInvalidTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase {
    98  		return peekTypeTestCase{
    99  			desc: desc, input: input,
   100  			typs:  []bsontype.Type{bsontype.Type(0)},
   101  			errFs: []expectedErrorFunc{lastEF},
   102  		}
   103  	}
   104  
   105  	makeInvalidPeekTypeTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase {
   106  		return peekTypeTestCase{
   107  			desc: desc, input: input,
   108  			typs:  []bsontype.Type{bsontype.Array, bsontype.String, bsontype.Type(0)},
   109  			errFs: []expectedErrorFunc{expectNoError, expectNoError, lastEF},
   110  		}
   111  	}
   112  
   113  	cases := []peekTypeTestCase{
   114  		makeValidPeekTypeTestCase(`null`, bsontype.Null, "Null"),
   115  		makeValidPeekTypeTestCase(`"string"`, bsontype.String, "String"),
   116  		makeValidPeekTypeTestCase(`true`, bsontype.Boolean, "Boolean--true"),
   117  		makeValidPeekTypeTestCase(`false`, bsontype.Boolean, "Boolean--false"),
   118  		makeValidPeekTypeTestCase(`{"$minKey": 1}`, bsontype.MinKey, "MinKey"),
   119  		makeValidPeekTypeTestCase(`{"$maxKey": 1}`, bsontype.MaxKey, "MaxKey"),
   120  		makeValidPeekTypeTestCase(`{"$numberInt": "42"}`, bsontype.Int32, "Int32"),
   121  		makeValidPeekTypeTestCase(`{"$numberLong": "42"}`, bsontype.Int64, "Int64"),
   122  		makeValidPeekTypeTestCase(`{"$symbol": "symbol"}`, bsontype.Symbol, "Symbol"),
   123  		makeValidPeekTypeTestCase(`{"$numberDouble": "42.42"}`, bsontype.Double, "Double"),
   124  		makeValidPeekTypeTestCase(`{"$undefined": true}`, bsontype.Undefined, "Undefined"),
   125  		makeValidPeekTypeTestCase(`{"$numberDouble": "NaN"}`, bsontype.Double, "Double--NaN"),
   126  		makeValidPeekTypeTestCase(`{"$numberDecimal": "1234"}`, bsontype.Decimal128, "Decimal"),
   127  		makeValidPeekTypeTestCase(`{"foo": "bar"}`, bsontype.EmbeddedDocument, "Toplevel document"),
   128  		makeValidPeekTypeTestCase(`{"$date": {"$numberLong": "0"}}`, bsontype.DateTime, "Datetime"),
   129  		makeValidPeekTypeTestCase(`{"$code": "function() {}"}`, bsontype.JavaScript, "Code no scope"),
   130  		makeValidPeekTypeTestCase(`[{"$numberInt": "1"},{"$numberInt": "2"}]`, bsontype.Array, "Array"),
   131  		makeValidPeekTypeTestCase(`{"$timestamp": {"t": 42, "i": 1}}`, bsontype.Timestamp, "Timestamp"),
   132  		makeValidPeekTypeTestCase(`{"$oid": "57e193d7a9cc81b4027498b5"}`, bsontype.ObjectID, "Object ID"),
   133  		makeValidPeekTypeTestCase(`{"$binary": {"base64": "AQIDBAU=", "subType": "80"}}`, bsontype.Binary, "Binary"),
   134  		makeValidPeekTypeTestCase(`{"$code": "function() {}", "$scope": {}}`, bsontype.CodeWithScope, "Code With Scope"),
   135  		makeValidPeekTypeTestCase(`{"$binary": {"base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03"}}`, bsontype.Binary, "Binary"),
   136  		makeValidPeekTypeTestCase(`{"$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03"}`, bsontype.Binary, "Binary"),
   137  		makeValidPeekTypeTestCase(`{"$regularExpression": {"pattern": "foo*", "options": "ix"}}`, bsontype.Regex, "Regular expression"),
   138  		makeValidPeekTypeTestCase(`{"$dbPointer": {"$ref": "db.collection", "$id": {"$oid": "57e193d7a9cc81b4027498b1"}}}`, bsontype.DBPointer, "DBPointer"),
   139  		makeValidPeekTypeTestCase(`{"$ref": "collection", "$id": {"$oid": "57fd71e96e32ab4225b723fb"}, "$db": "database"}`, bsontype.EmbeddedDocument, "DBRef"),
   140  		makeInvalidPeekTypeTestCase("invalid array--missing ]", `["a"`, expectError),
   141  		makeInvalidPeekTypeTestCase("invalid array--colon in array", `["a":`, expectError),
   142  		makeInvalidPeekTypeTestCase("invalid array--extra comma", `["a",,`, expectError),
   143  		makeInvalidPeekTypeTestCase("invalid array--trailing comma", `["a",]`, expectError),
   144  		makeInvalidPeekTypeTestCase("peekType after end of array", `["a"]`, expectErrEOA),
   145  		{
   146  			desc:  "invalid array--leading comma",
   147  			input: `[,`,
   148  			typs:  []bsontype.Type{bsontype.Array, bsontype.Type(0)},
   149  			errFs: []expectedErrorFunc{expectNoError, expectError},
   150  		},
   151  		makeInvalidTestCase("lone $scope", `{"$scope": {}}`, expectError),
   152  		makeInvalidTestCase("empty code with unknown extra key", `{"$code":"", "0":""}`, expectError),
   153  		makeInvalidTestCase("non-empty code with unknown extra key", `{"$code":"foobar", "0":""}`, expectError),
   154  	}
   155  
   156  	for _, tc := range cases {
   157  		t.Run(tc.desc, func(t *testing.T) {
   158  			ejp := newExtJSONParser(strings.NewReader(tc.input), true)
   159  			// Manually set the parser's starting state to jpsSawColon so peekType will read ahead to find the extjson
   160  			// type of the value. If not set, the parser will be in jpsStartState and advance to jpsSawKey, which will
   161  			// cause it to return without peeking the extjson type.
   162  			ejp.s = jpsSawColon
   163  
   164  			for i, eTyp := range tc.typs {
   165  				errF := tc.errFs[i]
   166  
   167  				typ, err := ejp.peekType()
   168  				errF(t, err, tc.desc)
   169  				if err != nil {
   170  					// Don't inspect the type if there was an error
   171  					return
   172  				}
   173  
   174  				typDiff(t, eTyp, typ, tc.desc)
   175  			}
   176  		})
   177  	}
   178  }
   179  
   180  func TestExtJSONParserReadKeyReadValue(t *testing.T) {
   181  	// several test cases will use the same keys, types, and values, and only differ on input structure
   182  
   183  	keys := []string{"_id", "Symbol", "String", "Int32", "Int64", "Int", "MinKey"}
   184  	types := []bsontype.Type{bsontype.ObjectID, bsontype.Symbol, bsontype.String, bsontype.Int32, bsontype.Int64, bsontype.Int32, bsontype.MinKey}
   185  	values := []*extJSONValue{
   186  		{t: bsontype.String, v: "57e193d7a9cc81b4027498b5"},
   187  		{t: bsontype.String, v: "symbol"},
   188  		{t: bsontype.String, v: "string"},
   189  		{t: bsontype.String, v: "42"},
   190  		{t: bsontype.String, v: "42"},
   191  		{t: bsontype.Int32, v: int32(42)},
   192  		{t: bsontype.Int32, v: int32(1)},
   193  	}
   194  
   195  	errFuncs := make([]expectedErrorFunc, 7)
   196  	for i := 0; i < 7; i++ {
   197  		errFuncs[i] = expectNoError
   198  	}
   199  
   200  	firstKeyError := func(desc, input string) readKeyValueTestCase {
   201  		return readKeyValueTestCase{
   202  			desc:   desc,
   203  			input:  input,
   204  			keys:   []string{""},
   205  			typs:   []bsontype.Type{bsontype.Type(0)},
   206  			vals:   []*extJSONValue{nil},
   207  			keyEFs: []expectedErrorFunc{expectError},
   208  			valEFs: []expectedErrorFunc{expectErrorNOOP},
   209  		}
   210  	}
   211  
   212  	secondKeyError := func(desc, input, firstKey string, firstType bsontype.Type, firstValue *extJSONValue) readKeyValueTestCase {
   213  		return readKeyValueTestCase{
   214  			desc:   desc,
   215  			input:  input,
   216  			keys:   []string{firstKey, ""},
   217  			typs:   []bsontype.Type{firstType, bsontype.Type(0)},
   218  			vals:   []*extJSONValue{firstValue, nil},
   219  			keyEFs: []expectedErrorFunc{expectNoError, expectError},
   220  			valEFs: []expectedErrorFunc{expectNoError, expectErrorNOOP},
   221  		}
   222  	}
   223  
   224  	cases := []readKeyValueTestCase{
   225  		{
   226  			desc: "normal spacing",
   227  			input: `{
   228  					"_id": { "$oid": "57e193d7a9cc81b4027498b5" },
   229  					"Symbol": { "$symbol": "symbol" },
   230  					"String": "string",
   231  					"Int32": { "$numberInt": "42" },
   232  					"Int64": { "$numberLong": "42" },
   233  					"Int": 42,
   234  					"MinKey": { "$minKey": 1 }
   235  				}`,
   236  			keys: keys, typs: types, vals: values,
   237  			keyEFs: errFuncs, valEFs: errFuncs,
   238  		},
   239  		{
   240  			desc: "new line before comma",
   241  			input: `{ "_id": { "$oid": "57e193d7a9cc81b4027498b5" }
   242  				 , "Symbol": { "$symbol": "symbol" }
   243  				 , "String": "string"
   244  				 , "Int32": { "$numberInt": "42" }
   245  				 , "Int64": { "$numberLong": "42" }
   246  				 , "Int": 42
   247  				 , "MinKey": { "$minKey": 1 }
   248  				 }`,
   249  			keys: keys, typs: types, vals: values,
   250  			keyEFs: errFuncs, valEFs: errFuncs,
   251  		},
   252  		{
   253  			desc: "tabs around colons",
   254  			input: `{
   255  					"_id":    { "$oid"       : "57e193d7a9cc81b4027498b5" },
   256  					"Symbol": { "$symbol"    : "symbol" },
   257  					"String": "string",
   258  					"Int32":  { "$numberInt" : "42" },
   259  					"Int64":  { "$numberLong": "42" },
   260  					"Int":    42,
   261  					"MinKey": { "$minKey": 1 }
   262  				}`,
   263  			keys: keys, typs: types, vals: values,
   264  			keyEFs: errFuncs, valEFs: errFuncs,
   265  		},
   266  		{
   267  			desc:  "no whitespace",
   268  			input: `{"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Symbol":{"$symbol":"symbol"},"String":"string","Int32":{"$numberInt":"42"},"Int64":{"$numberLong":"42"},"Int":42,"MinKey":{"$minKey":1}}`,
   269  			keys:  keys, typs: types, vals: values,
   270  			keyEFs: errFuncs, valEFs: errFuncs,
   271  		},
   272  		{
   273  			desc: "mixed whitespace",
   274  			input: `	{
   275  					"_id"		: { "$oid": "57e193d7a9cc81b4027498b5" },
   276  			        "Symbol"	: { "$symbol": "symbol" }	,
   277  				    "String"	: "string",
   278  					"Int32"		: { "$numberInt": "42" }    ,
   279  					"Int64"		: {"$numberLong" : "42"},
   280  					"Int"		: 42,
   281  			      	"MinKey"	: { "$minKey": 1 } 	}	`,
   282  			keys: keys, typs: types, vals: values,
   283  			keyEFs: errFuncs, valEFs: errFuncs,
   284  		},
   285  		{
   286  			desc:  "nested object",
   287  			input: `{"k1": 1, "k2": { "k3": { "k4": 4 } }, "k5": 5}`,
   288  			keys:  []string{"k1", "k2", "k3", "k4", "", "", "k5", ""},
   289  			typs:  []bsontype.Type{bsontype.Int32, bsontype.EmbeddedDocument, bsontype.EmbeddedDocument, bsontype.Int32, bsontype.Type(0), bsontype.Type(0), bsontype.Int32, bsontype.Type(0)},
   290  			vals: []*extJSONValue{
   291  				{t: bsontype.Int32, v: int32(1)}, nil, nil, {t: bsontype.Int32, v: int32(4)}, nil, nil, {t: bsontype.Int32, v: int32(5)}, nil,
   292  			},
   293  			keyEFs: []expectedErrorFunc{
   294  				expectNoError, expectNoError, expectNoError, expectNoError, expectErrEOD,
   295  				expectErrEOD, expectNoError, expectErrEOD,
   296  			},
   297  			valEFs: []expectedErrorFunc{
   298  				expectNoError, expectError, expectError, expectNoError, expectErrorNOOP,
   299  				expectErrorNOOP, expectNoError, expectErrorNOOP,
   300  			},
   301  		},
   302  		{
   303  			desc:   "invalid input: invalid values for extended type",
   304  			input:  `{"a": {"$numberInt": "1", "x"`,
   305  			keys:   []string{"a"},
   306  			typs:   []bsontype.Type{bsontype.Int32},
   307  			vals:   []*extJSONValue{nil},
   308  			keyEFs: []expectedErrorFunc{expectNoError},
   309  			valEFs: []expectedErrorFunc{expectError},
   310  		},
   311  		firstKeyError("invalid input: missing key--EOF", "{"),
   312  		firstKeyError("invalid input: missing key--colon first", "{:"),
   313  		firstKeyError("invalid input: missing value", `{"a":`),
   314  		firstKeyError("invalid input: missing colon", `{"a" 1`),
   315  		firstKeyError("invalid input: extra colon", `{"a"::`),
   316  		secondKeyError("invalid input: missing }", `{"a": 1`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
   317  		secondKeyError("invalid input: missing comma", `{"a": 1 "b"`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
   318  		secondKeyError("invalid input: extra comma", `{"a": 1,, "b"`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
   319  		secondKeyError("invalid input: trailing comma in object", `{"a": 1,}`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
   320  		{
   321  			desc:   "invalid input: lone scope after a complete value",
   322  			input:  `{"a": "", "b": {"$scope: ""}}`,
   323  			keys:   []string{"a"},
   324  			typs:   []bsontype.Type{bsontype.String},
   325  			vals:   []*extJSONValue{{bsontype.String, ""}},
   326  			keyEFs: []expectedErrorFunc{expectNoError, expectNoError},
   327  			valEFs: []expectedErrorFunc{expectNoError, expectError},
   328  		},
   329  		{
   330  			desc:   "invalid input: lone scope nested",
   331  			input:  `{"a":{"b":{"$scope":{`,
   332  			keys:   []string{},
   333  			typs:   []bsontype.Type{},
   334  			vals:   []*extJSONValue{nil},
   335  			keyEFs: []expectedErrorFunc{expectNoError},
   336  			valEFs: []expectedErrorFunc{expectError},
   337  		},
   338  	}
   339  
   340  	for _, tc := range cases {
   341  		t.Run(tc.desc, func(t *testing.T) {
   342  			ejp := newExtJSONParser(strings.NewReader(tc.input), true)
   343  
   344  			for i, eKey := range tc.keys {
   345  				eTyp := tc.typs[i]
   346  				eVal := tc.vals[i]
   347  
   348  				keyErrF := tc.keyEFs[i]
   349  				valErrF := tc.valEFs[i]
   350  
   351  				k, typ, err := ejp.readKey()
   352  				readKeyDiff(t, eKey, k, eTyp, typ, err, keyErrF, tc.desc)
   353  
   354  				v, err := ejp.readValue(typ)
   355  				readValueDiff(t, eVal, v, err, valErrF, tc.desc)
   356  			}
   357  		})
   358  	}
   359  }
   360  
   361  type ejpExpectationTest func(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{})
   362  
   363  type ejpTestCase struct {
   364  	f ejpExpectationTest
   365  	p *extJSONParser
   366  	k string
   367  	t bsontype.Type
   368  	v interface{}
   369  }
   370  
   371  // expectSingleValue is used for simple JSON types (strings, numbers, literals) and for extended JSON types that
   372  // have single key-value pairs (i.e. { "$minKey": 1 }, { "$numberLong": "42.42" })
   373  func expectSingleValue(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) {
   374  	eVal := expectedValue.(*extJSONValue)
   375  
   376  	k, typ, err := p.readKey()
   377  	readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey)
   378  
   379  	v, err := p.readValue(typ)
   380  	readValueDiff(t, eVal, v, err, expectNoError, expectedKey)
   381  }
   382  
   383  // expectMultipleValues is used for values that are subdocuments of known size and with known keys (such as extended
   384  // JSON types { "$timestamp": {"t": 1, "i": 1} } and { "$regularExpression": {"pattern": "", options: ""} })
   385  func expectMultipleValues(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) {
   386  	k, typ, err := p.readKey()
   387  	readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey)
   388  
   389  	v, err := p.readValue(typ)
   390  	expectNoError(t, err, "")
   391  	typDiff(t, bsontype.EmbeddedDocument, v.t, expectedKey)
   392  
   393  	actObj := v.v.(*extJSONObject)
   394  	expObj := expectedValue.(*extJSONObject)
   395  
   396  	for i, actKey := range actObj.keys {
   397  		expKey := expObj.keys[i]
   398  		actVal := actObj.values[i]
   399  		expVal := expObj.values[i]
   400  
   401  		keyDiff(t, expKey, actKey, expectedKey)
   402  		typDiff(t, expVal.t, actVal.t, expectedKey)
   403  		valDiff(t, expVal.v, actVal.v, expectedKey)
   404  	}
   405  }
   406  
   407  type ejpKeyTypValTriple struct {
   408  	key string
   409  	typ bsontype.Type
   410  	val *extJSONValue
   411  }
   412  
   413  type ejpSubDocumentTestValue struct {
   414  	code string               // code is only used for TypeCodeWithScope (and is ignored for TypeEmbeddedDocument
   415  	ktvs []ejpKeyTypValTriple // list of (key, type, value) triples; this is "scope" for TypeCodeWithScope
   416  }
   417  
   418  // expectSubDocument is used for embedded documents and code with scope types; it reads all the keys and values
   419  // in the embedded document (or scope for codeWithScope) and compares them to the expectedValue's list of (key, type,
   420  // value) triples
   421  func expectSubDocument(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) {
   422  	subdoc := expectedValue.(ejpSubDocumentTestValue)
   423  
   424  	k, typ, err := p.readKey()
   425  	readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey)
   426  
   427  	if expectedType == bsontype.CodeWithScope {
   428  		v, err := p.readValue(typ)
   429  		readValueDiff(t, &extJSONValue{t: bsontype.String, v: subdoc.code}, v, err, expectNoError, expectedKey)
   430  	}
   431  
   432  	for _, ktv := range subdoc.ktvs {
   433  		eKey := ktv.key
   434  		eTyp := ktv.typ
   435  		eVal := ktv.val
   436  
   437  		k, typ, err = p.readKey()
   438  		readKeyDiff(t, eKey, k, eTyp, typ, err, expectNoError, expectedKey)
   439  
   440  		v, err := p.readValue(typ)
   441  		readValueDiff(t, eVal, v, err, expectNoError, expectedKey)
   442  	}
   443  
   444  	if expectedType == bsontype.CodeWithScope {
   445  		// expect scope doc to close
   446  		k, typ, err = p.readKey()
   447  		readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, expectedKey)
   448  	}
   449  
   450  	// expect subdoc to close
   451  	k, typ, err = p.readKey()
   452  	readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, expectedKey)
   453  }
   454  
   455  // expectArray takes the expectedKey, ignores the expectedType, and uses the expectedValue
   456  // as a slice of (type Type, value *extJSONValue) pairs
   457  func expectArray(t *testing.T, p *extJSONParser, expectedKey string, _ bsontype.Type, expectedValue interface{}) {
   458  	ktvs := expectedValue.([]ejpKeyTypValTriple)
   459  
   460  	k, typ, err := p.readKey()
   461  	readKeyDiff(t, expectedKey, k, bsontype.Array, typ, err, expectNoError, expectedKey)
   462  
   463  	for _, ktv := range ktvs {
   464  		eTyp := ktv.typ
   465  		eVal := ktv.val
   466  
   467  		typ, err = p.peekType()
   468  		typDiff(t, eTyp, typ, expectedKey)
   469  		expectNoError(t, err, expectedKey)
   470  
   471  		v, err := p.readValue(typ)
   472  		readValueDiff(t, eVal, v, err, expectNoError, expectedKey)
   473  	}
   474  
   475  	// expect array to end
   476  	typ, err = p.peekType()
   477  	typDiff(t, bsontype.Type(0), typ, expectedKey)
   478  	expectErrEOA(t, err, expectedKey)
   479  }
   480  
   481  func TestExtJSONParserAllTypes(t *testing.T) {
   482  	in := ` { "_id"					: { "$oid": "57e193d7a9cc81b4027498b5"}
   483  			, "Symbol"				: { "$symbol": "symbol"}
   484  			, "String"				: "string"
   485  			, "Int32"				: { "$numberInt": "42"}
   486  			, "Int64"				: { "$numberLong": "42"}
   487  			, "Double"				: { "$numberDouble": "42.42"}
   488  			, "SpecialFloat"		: { "$numberDouble": "NaN" }
   489  			, "Decimal"				: { "$numberDecimal": "1234" }
   490  			, "Binary"			 	: { "$binary": { "base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03" } }
   491  			, "BinaryLegacy"  : { "$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03" }
   492  			, "BinaryUserDefined"	: { "$binary": { "base64": "AQIDBAU=", "subType": "80" } }
   493  			, "Code"				: { "$code": "function() {}" }
   494  			, "CodeWithEmptyScope"	: { "$code": "function() {}", "$scope": {} }
   495  			, "CodeWithScope"		: { "$code": "function() {}", "$scope": { "x": 1 } }
   496  			, "EmptySubdocument"    : {}
   497  			, "Subdocument"			: { "foo": "bar", "baz": { "$numberInt": "42" } }
   498  			, "Array"				: [{"$numberInt": "1"}, {"$numberLong": "2"}, {"$numberDouble": "3"}, 4, "string", 5.0]
   499  			, "Timestamp"			: { "$timestamp": { "t": 42, "i": 1 } }
   500  			, "RegularExpression"	: { "$regularExpression": { "pattern": "foo*", "options": "ix" } }
   501  			, "DatetimeEpoch"		: { "$date": { "$numberLong": "0" } }
   502  			, "DatetimePositive"	: { "$date": { "$numberLong": "9223372036854775807" } }
   503  			, "DatetimeNegative"	: { "$date": { "$numberLong": "-9223372036854775808" } }
   504  			, "True"				: true
   505  			, "False"				: false
   506  			, "DBPointer"			: { "$dbPointer": { "$ref": "db.collection", "$id": { "$oid": "57e193d7a9cc81b4027498b1" } } }
   507  			, "DBRef"				: { "$ref": "collection", "$id": { "$oid": "57fd71e96e32ab4225b723fb" }, "$db": "database" }
   508  			, "DBRefNoDB"			: { "$ref": "collection", "$id": { "$oid": "57fd71e96e32ab4225b723fb" } }
   509  			, "MinKey"				: { "$minKey": 1 }
   510  			, "MaxKey"				: { "$maxKey": 1 }
   511  			, "Null"				: null
   512  			, "Undefined"			: { "$undefined": true }
   513  			}`
   514  
   515  	ejp := newExtJSONParser(strings.NewReader(in), true)
   516  
   517  	cases := []ejpTestCase{
   518  		{
   519  			f: expectSingleValue, p: ejp,
   520  			k: "_id", t: bsontype.ObjectID, v: &extJSONValue{t: bsontype.String, v: "57e193d7a9cc81b4027498b5"},
   521  		},
   522  		{
   523  			f: expectSingleValue, p: ejp,
   524  			k: "Symbol", t: bsontype.Symbol, v: &extJSONValue{t: bsontype.String, v: "symbol"},
   525  		},
   526  		{
   527  			f: expectSingleValue, p: ejp,
   528  			k: "String", t: bsontype.String, v: &extJSONValue{t: bsontype.String, v: "string"},
   529  		},
   530  		{
   531  			f: expectSingleValue, p: ejp,
   532  			k: "Int32", t: bsontype.Int32, v: &extJSONValue{t: bsontype.String, v: "42"},
   533  		},
   534  		{
   535  			f: expectSingleValue, p: ejp,
   536  			k: "Int64", t: bsontype.Int64, v: &extJSONValue{t: bsontype.String, v: "42"},
   537  		},
   538  		{
   539  			f: expectSingleValue, p: ejp,
   540  			k: "Double", t: bsontype.Double, v: &extJSONValue{t: bsontype.String, v: "42.42"},
   541  		},
   542  		{
   543  			f: expectSingleValue, p: ejp,
   544  			k: "SpecialFloat", t: bsontype.Double, v: &extJSONValue{t: bsontype.String, v: "NaN"},
   545  		},
   546  		{
   547  			f: expectSingleValue, p: ejp,
   548  			k: "Decimal", t: bsontype.Decimal128, v: &extJSONValue{t: bsontype.String, v: "1234"},
   549  		},
   550  		{
   551  			f: expectMultipleValues, p: ejp,
   552  			k: "Binary", t: bsontype.Binary,
   553  			v: &extJSONObject{
   554  				keys: []string{"base64", "subType"},
   555  				values: []*extJSONValue{
   556  					{t: bsontype.String, v: "o0w498Or7cijeBSpkquNtg=="},
   557  					{t: bsontype.String, v: "03"},
   558  				},
   559  			},
   560  		},
   561  		{
   562  			f: expectMultipleValues, p: ejp,
   563  			k: "BinaryLegacy", t: bsontype.Binary,
   564  			v: &extJSONObject{
   565  				keys: []string{"base64", "subType"},
   566  				values: []*extJSONValue{
   567  					{t: bsontype.String, v: "o0w498Or7cijeBSpkquNtg=="},
   568  					{t: bsontype.String, v: "03"},
   569  				},
   570  			},
   571  		},
   572  		{
   573  			f: expectMultipleValues, p: ejp,
   574  			k: "BinaryUserDefined", t: bsontype.Binary,
   575  			v: &extJSONObject{
   576  				keys: []string{"base64", "subType"},
   577  				values: []*extJSONValue{
   578  					{t: bsontype.String, v: "AQIDBAU="},
   579  					{t: bsontype.String, v: "80"},
   580  				},
   581  			},
   582  		},
   583  		{
   584  			f: expectSingleValue, p: ejp,
   585  			k: "Code", t: bsontype.JavaScript, v: &extJSONValue{t: bsontype.String, v: "function() {}"},
   586  		},
   587  		{
   588  			f: expectSubDocument, p: ejp,
   589  			k: "CodeWithEmptyScope", t: bsontype.CodeWithScope,
   590  			v: ejpSubDocumentTestValue{
   591  				code: "function() {}",
   592  				ktvs: []ejpKeyTypValTriple{},
   593  			},
   594  		},
   595  		{
   596  			f: expectSubDocument, p: ejp,
   597  			k: "CodeWithScope", t: bsontype.CodeWithScope,
   598  			v: ejpSubDocumentTestValue{
   599  				code: "function() {}",
   600  				ktvs: []ejpKeyTypValTriple{
   601  					{"x", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}},
   602  				},
   603  			},
   604  		},
   605  		{
   606  			f: expectSubDocument, p: ejp,
   607  			k: "EmptySubdocument", t: bsontype.EmbeddedDocument,
   608  			v: ejpSubDocumentTestValue{
   609  				ktvs: []ejpKeyTypValTriple{},
   610  			},
   611  		},
   612  		{
   613  			f: expectSubDocument, p: ejp,
   614  			k: "Subdocument", t: bsontype.EmbeddedDocument,
   615  			v: ejpSubDocumentTestValue{
   616  				ktvs: []ejpKeyTypValTriple{
   617  					{"foo", bsontype.String, &extJSONValue{t: bsontype.String, v: "bar"}},
   618  					{"baz", bsontype.Int32, &extJSONValue{t: bsontype.String, v: "42"}},
   619  				},
   620  			},
   621  		},
   622  		{
   623  			f: expectArray, p: ejp,
   624  			k: "Array", t: bsontype.Array,
   625  			v: []ejpKeyTypValTriple{
   626  				{typ: bsontype.Int32, val: &extJSONValue{t: bsontype.String, v: "1"}},
   627  				{typ: bsontype.Int64, val: &extJSONValue{t: bsontype.String, v: "2"}},
   628  				{typ: bsontype.Double, val: &extJSONValue{t: bsontype.String, v: "3"}},
   629  				{typ: bsontype.Int32, val: &extJSONValue{t: bsontype.Int32, v: int32(4)}},
   630  				{typ: bsontype.String, val: &extJSONValue{t: bsontype.String, v: "string"}},
   631  				{typ: bsontype.Double, val: &extJSONValue{t: bsontype.Double, v: 5.0}},
   632  			},
   633  		},
   634  		{
   635  			f: expectMultipleValues, p: ejp,
   636  			k: "Timestamp", t: bsontype.Timestamp,
   637  			v: &extJSONObject{
   638  				keys: []string{"t", "i"},
   639  				values: []*extJSONValue{
   640  					{t: bsontype.Int32, v: int32(42)},
   641  					{t: bsontype.Int32, v: int32(1)},
   642  				},
   643  			},
   644  		},
   645  		{
   646  			f: expectMultipleValues, p: ejp,
   647  			k: "RegularExpression", t: bsontype.Regex,
   648  			v: &extJSONObject{
   649  				keys: []string{"pattern", "options"},
   650  				values: []*extJSONValue{
   651  					{t: bsontype.String, v: "foo*"},
   652  					{t: bsontype.String, v: "ix"},
   653  				},
   654  			},
   655  		},
   656  		{
   657  			f: expectMultipleValues, p: ejp,
   658  			k: "DatetimeEpoch", t: bsontype.DateTime,
   659  			v: &extJSONObject{
   660  				keys: []string{"$numberLong"},
   661  				values: []*extJSONValue{
   662  					{t: bsontype.String, v: "0"},
   663  				},
   664  			},
   665  		},
   666  		{
   667  			f: expectMultipleValues, p: ejp,
   668  			k: "DatetimePositive", t: bsontype.DateTime,
   669  			v: &extJSONObject{
   670  				keys: []string{"$numberLong"},
   671  				values: []*extJSONValue{
   672  					{t: bsontype.String, v: "9223372036854775807"},
   673  				},
   674  			},
   675  		},
   676  		{
   677  			f: expectMultipleValues, p: ejp,
   678  			k: "DatetimeNegative", t: bsontype.DateTime,
   679  			v: &extJSONObject{
   680  				keys: []string{"$numberLong"},
   681  				values: []*extJSONValue{
   682  					{t: bsontype.String, v: "-9223372036854775808"},
   683  				},
   684  			},
   685  		},
   686  		{
   687  			f: expectSingleValue, p: ejp,
   688  			k: "True", t: bsontype.Boolean, v: &extJSONValue{t: bsontype.Boolean, v: true},
   689  		},
   690  		{
   691  			f: expectSingleValue, p: ejp,
   692  			k: "False", t: bsontype.Boolean, v: &extJSONValue{t: bsontype.Boolean, v: false},
   693  		},
   694  		{
   695  			f: expectMultipleValues, p: ejp,
   696  			k: "DBPointer", t: bsontype.DBPointer,
   697  			v: &extJSONObject{
   698  				keys: []string{"$ref", "$id"},
   699  				values: []*extJSONValue{
   700  					{t: bsontype.String, v: "db.collection"},
   701  					{t: bsontype.String, v: "57e193d7a9cc81b4027498b1"},
   702  				},
   703  			},
   704  		},
   705  		{
   706  			f: expectSubDocument, p: ejp,
   707  			k: "DBRef", t: bsontype.EmbeddedDocument,
   708  			v: ejpSubDocumentTestValue{
   709  				ktvs: []ejpKeyTypValTriple{
   710  					{"$ref", bsontype.String, &extJSONValue{t: bsontype.String, v: "collection"}},
   711  					{"$id", bsontype.ObjectID, &extJSONValue{t: bsontype.String, v: "57fd71e96e32ab4225b723fb"}},
   712  					{"$db", bsontype.String, &extJSONValue{t: bsontype.String, v: "database"}},
   713  				},
   714  			},
   715  		},
   716  		{
   717  			f: expectSubDocument, p: ejp,
   718  			k: "DBRefNoDB", t: bsontype.EmbeddedDocument,
   719  			v: ejpSubDocumentTestValue{
   720  				ktvs: []ejpKeyTypValTriple{
   721  					{"$ref", bsontype.String, &extJSONValue{t: bsontype.String, v: "collection"}},
   722  					{"$id", bsontype.ObjectID, &extJSONValue{t: bsontype.String, v: "57fd71e96e32ab4225b723fb"}},
   723  				},
   724  			},
   725  		},
   726  		{
   727  			f: expectSingleValue, p: ejp,
   728  			k: "MinKey", t: bsontype.MinKey, v: &extJSONValue{t: bsontype.Int32, v: int32(1)},
   729  		},
   730  		{
   731  			f: expectSingleValue, p: ejp,
   732  			k: "MaxKey", t: bsontype.MaxKey, v: &extJSONValue{t: bsontype.Int32, v: int32(1)},
   733  		},
   734  		{
   735  			f: expectSingleValue, p: ejp,
   736  			k: "Null", t: bsontype.Null, v: &extJSONValue{t: bsontype.Null, v: nil},
   737  		},
   738  		{
   739  			f: expectSingleValue, p: ejp,
   740  			k: "Undefined", t: bsontype.Undefined, v: &extJSONValue{t: bsontype.Boolean, v: true},
   741  		},
   742  	}
   743  
   744  	// run the test cases
   745  	for _, tc := range cases {
   746  		tc.f(t, tc.p, tc.k, tc.t, tc.v)
   747  	}
   748  
   749  	// expect end of whole document: read final }
   750  	k, typ, err := ejp.readKey()
   751  	readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, "")
   752  
   753  	// expect end of whole document: read EOF
   754  	k, typ, err = ejp.readKey()
   755  	readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOF, "")
   756  	if diff := cmp.Diff(jpsDoneState, ejp.s); diff != "" {
   757  		t.Errorf("expected parser to be in done state but instead is in %v\n", ejp.s)
   758  		t.FailNow()
   759  	}
   760  }
   761  
   762  func TestExtJSONValue(t *testing.T) {
   763  	t.Run("Large Date", func(t *testing.T) {
   764  		val := &extJSONValue{
   765  			t: bsontype.String,
   766  			v: "3001-01-01T00:00:00Z",
   767  		}
   768  
   769  		intVal, err := val.parseDateTime()
   770  		if err != nil {
   771  			t.Fatalf("error parsing date time: %v", err)
   772  		}
   773  
   774  		if intVal <= 0 {
   775  			t.Fatalf("expected value above 0, got %v", intVal)
   776  		}
   777  	})
   778  	t.Run("fallback time format", func(t *testing.T) {
   779  		val := &extJSONValue{
   780  			t: bsontype.String,
   781  			v: "2019-06-04T14:54:31.416+0000",
   782  		}
   783  
   784  		_, err := val.parseDateTime()
   785  		if err != nil {
   786  			t.Fatalf("error parsing date time: %v", err)
   787  		}
   788  	})
   789  }
   790  

View as plain text