| // 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 ( |
| "encoding/base64" |
| "errors" |
| "fmt" |
| "math" |
| "strconv" |
| "time" |
| |
| "github.com/mongodb/mongo-go-driver/bson/bsontype" |
| "github.com/mongodb/mongo-go-driver/bson/primitive" |
| ) |
| |
| func wrapperKeyBSONType(key string) bsontype.Type { |
| switch string(key) { |
| case "$numberInt": |
| return bsontype.Int32 |
| case "$numberLong": |
| return bsontype.Int64 |
| case "$oid": |
| return bsontype.ObjectID |
| case "$symbol": |
| return bsontype.Symbol |
| case "$numberDouble": |
| return bsontype.Double |
| case "$numberDecimal": |
| return bsontype.Decimal128 |
| case "$binary": |
| return bsontype.Binary |
| case "$code": |
| return bsontype.JavaScript |
| case "$scope": |
| return bsontype.CodeWithScope |
| case "$timestamp": |
| return bsontype.Timestamp |
| case "$regularExpression": |
| return bsontype.Regex |
| case "$dbPointer": |
| return bsontype.DBPointer |
| case "$date": |
| return bsontype.DateTime |
| case "$ref": |
| fallthrough |
| case "$id": |
| fallthrough |
| case "$db": |
| return bsontype.EmbeddedDocument // dbrefs aren't bson types |
| case "$minKey": |
| return bsontype.MinKey |
| case "$maxKey": |
| return bsontype.MaxKey |
| case "$undefined": |
| return bsontype.Undefined |
| } |
| |
| return bsontype.EmbeddedDocument |
| } |
| |
| func (ejv *extJSONValue) parseBinary() (b []byte, subType byte, err error) { |
| if ejv.t != bsontype.EmbeddedDocument { |
| return nil, 0, fmt.Errorf("$binary value should be object, but instead is %s", ejv.t) |
| } |
| |
| binObj := ejv.v.(*extJSONObject) |
| bFound := false |
| stFound := false |
| |
| for i, key := range binObj.keys { |
| val := binObj.values[i] |
| |
| switch key { |
| case "base64": |
| if bFound { |
| return nil, 0, errors.New("duplicate base64 key in $binary") |
| } |
| |
| if val.t != bsontype.String { |
| return nil, 0, fmt.Errorf("$binary base64 value should be string, but instead is %s", val.t) |
| } |
| |
| base64Bytes, err := base64.StdEncoding.DecodeString(val.v.(string)) |
| if err != nil { |
| return nil, 0, fmt.Errorf("invalid $binary base64 string: %s", val.v.(string)) |
| } |
| |
| b = base64Bytes |
| bFound = true |
| case "subType": |
| if stFound { |
| return nil, 0, errors.New("duplicate subType key in $binary") |
| } |
| |
| if val.t != bsontype.String { |
| return nil, 0, fmt.Errorf("$binary subType value should be string, but instead is %s", val.t) |
| } |
| |
| i, err := strconv.ParseInt(val.v.(string), 16, 64) |
| if err != nil { |
| return nil, 0, fmt.Errorf("invalid $binary subType string: %s", val.v.(string)) |
| } |
| |
| subType = byte(i) |
| stFound = true |
| default: |
| return nil, 0, fmt.Errorf("invalid key in $binary object: %s", key) |
| } |
| } |
| |
| if !bFound { |
| return nil, 0, errors.New("missing base64 field in $binary object") |
| } |
| |
| if !stFound { |
| return nil, 0, errors.New("missing subType field in $binary object") |
| |
| } |
| |
| return b, subType, nil |
| } |
| |
| func (ejv *extJSONValue) parseDBPointer() (ns string, oid primitive.ObjectID, err error) { |
| if ejv.t != bsontype.EmbeddedDocument { |
| return "", primitive.NilObjectID, fmt.Errorf("$dbPointer value should be object, but instead is %s", ejv.t) |
| } |
| |
| dbpObj := ejv.v.(*extJSONObject) |
| oidFound := false |
| nsFound := false |
| |
| for i, key := range dbpObj.keys { |
| val := dbpObj.values[i] |
| |
| switch key { |
| case "$ref": |
| if nsFound { |
| return "", primitive.NilObjectID, errors.New("duplicate $ref key in $dbPointer") |
| } |
| |
| if val.t != bsontype.String { |
| return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $ref value should be string, but instead is %s", val.t) |
| } |
| |
| ns = val.v.(string) |
| nsFound = true |
| case "$id": |
| if oidFound { |
| return "", primitive.NilObjectID, errors.New("duplicate $id key in $dbPointer") |
| } |
| |
| if val.t != bsontype.String { |
| return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $id value should be string, but instead is %s", val.t) |
| } |
| |
| oid, err = primitive.ObjectIDFromHex(val.v.(string)) |
| if err != nil { |
| return "", primitive.NilObjectID, err |
| } |
| |
| oidFound = true |
| default: |
| return "", primitive.NilObjectID, fmt.Errorf("invalid key in $dbPointer object: %s", key) |
| } |
| } |
| |
| if !nsFound { |
| return "", oid, errors.New("missing $ref field in $dbPointer object") |
| } |
| |
| if !oidFound { |
| return "", oid, errors.New("missing $id field in $dbPointer object") |
| } |
| |
| return ns, oid, nil |
| } |
| |
| const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00" |
| |
| func (ejv *extJSONValue) parseDateTime() (int64, error) { |
| switch ejv.t { |
| case bsontype.Int32: |
| return int64(ejv.v.(int32)), nil |
| case bsontype.Int64: |
| return ejv.v.(int64), nil |
| case bsontype.String: |
| return parseDatetimeString(ejv.v.(string)) |
| case bsontype.EmbeddedDocument: |
| return parseDatetimeObject(ejv.v.(*extJSONObject)) |
| default: |
| return 0, fmt.Errorf("$date value should be string or object, but instead is %s", ejv.t) |
| } |
| } |
| |
| func parseDatetimeString(data string) (int64, error) { |
| t, err := time.Parse(rfc3339Milli, data) |
| if err != nil { |
| return 0, fmt.Errorf("invalid $date value string: %s", data) |
| } |
| |
| return t.UnixNano() / 1e6, nil |
| } |
| |
| func parseDatetimeObject(data *extJSONObject) (d int64, err error) { |
| dFound := false |
| |
| for i, key := range data.keys { |
| val := data.values[i] |
| |
| switch key { |
| case "$numberLong": |
| if dFound { |
| return 0, errors.New("duplicate $numberLong key in $date") |
| } |
| |
| if val.t != bsontype.String { |
| return 0, fmt.Errorf("$date $numberLong field should be string, but instead is %s", val.t) |
| } |
| |
| d, err = val.parseInt64() |
| if err != nil { |
| return 0, err |
| } |
| dFound = true |
| default: |
| return 0, fmt.Errorf("invalid key in $date object: %s", key) |
| } |
| } |
| |
| if !dFound { |
| return 0, errors.New("missing $numberLong field in $date object") |
| } |
| |
| return d, nil |
| } |
| |
| func (ejv *extJSONValue) parseDecimal128() (primitive.Decimal128, error) { |
| if ejv.t != bsontype.String { |
| return primitive.Decimal128{}, fmt.Errorf("$numberDecimal value should be string, but instead is %s", ejv.t) |
| } |
| |
| d, err := primitive.ParseDecimal128(ejv.v.(string)) |
| if err != nil { |
| return primitive.Decimal128{}, fmt.Errorf("$invalid $numberDecimal string: %s", ejv.v.(string)) |
| } |
| |
| return d, nil |
| } |
| |
| func (ejv *extJSONValue) parseDouble() (float64, error) { |
| if ejv.t == bsontype.Double { |
| return ejv.v.(float64), nil |
| } |
| |
| if ejv.t != bsontype.String { |
| return 0, fmt.Errorf("$numberDouble value should be string, but instead is %s", ejv.t) |
| } |
| |
| switch string(ejv.v.(string)) { |
| case "Infinity": |
| return math.Inf(1), nil |
| case "-Infinity": |
| return math.Inf(-1), nil |
| case "NaN": |
| return math.NaN(), nil |
| } |
| |
| f, err := strconv.ParseFloat(ejv.v.(string), 64) |
| if err != nil { |
| return 0, err |
| } |
| |
| return f, nil |
| } |
| |
| func (ejv *extJSONValue) parseInt32() (int32, error) { |
| if ejv.t == bsontype.Int32 { |
| return ejv.v.(int32), nil |
| } |
| |
| if ejv.t != bsontype.String { |
| return 0, fmt.Errorf("$numberInt value should be string, but instead is %s", ejv.t) |
| } |
| |
| i, err := strconv.ParseInt(ejv.v.(string), 10, 64) |
| if err != nil { |
| return 0, err |
| } |
| |
| if i < math.MinInt32 || i > math.MaxInt32 { |
| return 0, fmt.Errorf("$numberInt value should be int32 but instead is int64: %d", i) |
| } |
| |
| return int32(i), nil |
| } |
| |
| func (ejv *extJSONValue) parseInt64() (int64, error) { |
| if ejv.t == bsontype.Int64 { |
| return ejv.v.(int64), nil |
| } |
| |
| if ejv.t != bsontype.String { |
| return 0, fmt.Errorf("$numberLong value should be string, but instead is %s", ejv.t) |
| } |
| |
| i, err := strconv.ParseInt(ejv.v.(string), 10, 64) |
| if err != nil { |
| return 0, err |
| } |
| |
| return i, nil |
| } |
| |
| func (ejv *extJSONValue) parseJavascript() (code string, err error) { |
| if ejv.t != bsontype.String { |
| return "", fmt.Errorf("$code value should be string, but instead is %s", ejv.t) |
| } |
| |
| return ejv.v.(string), nil |
| } |
| |
| func (ejv *extJSONValue) parseMinMaxKey(minmax string) error { |
| if ejv.t != bsontype.Int32 { |
| return fmt.Errorf("$%sKey value should be int32, but instead is %s", minmax, ejv.t) |
| } |
| |
| if ejv.v.(int32) != 1 { |
| return fmt.Errorf("$%sKey value must be 1, but instead is %d", minmax, ejv.v.(int32)) |
| } |
| |
| return nil |
| } |
| |
| func (ejv *extJSONValue) parseObjectID() (primitive.ObjectID, error) { |
| if ejv.t != bsontype.String { |
| return primitive.NilObjectID, fmt.Errorf("$oid value should be string, but instead is %s", ejv.t) |
| } |
| |
| return primitive.ObjectIDFromHex(ejv.v.(string)) |
| } |
| |
| func (ejv *extJSONValue) parseRegex() (pattern, options string, err error) { |
| if ejv.t != bsontype.EmbeddedDocument { |
| return "", "", fmt.Errorf("$regularExpression value should be object, but instead is %s", ejv.t) |
| } |
| |
| regexObj := ejv.v.(*extJSONObject) |
| patFound := false |
| optFound := false |
| |
| for i, key := range regexObj.keys { |
| val := regexObj.values[i] |
| |
| switch string(key) { |
| case "pattern": |
| if patFound { |
| return "", "", errors.New("duplicate pattern key in $regularExpression") |
| } |
| |
| if val.t != bsontype.String { |
| return "", "", fmt.Errorf("$regularExpression pattern value should be string, but instead is %s", val.t) |
| } |
| |
| pattern = val.v.(string) |
| patFound = true |
| case "options": |
| if optFound { |
| return "", "", errors.New("duplicate options key in $regularExpression") |
| } |
| |
| if val.t != bsontype.String { |
| return "", "", fmt.Errorf("$regularExpression options value should be string, but instead is %s", val.t) |
| } |
| |
| options = val.v.(string) |
| optFound = true |
| default: |
| return "", "", fmt.Errorf("invalid key in $regularExpression object: %s", key) |
| } |
| } |
| |
| if !patFound { |
| return "", "", errors.New("missing pattern field in $regularExpression object") |
| } |
| |
| if !optFound { |
| return "", "", errors.New("missing options field in $regularExpression object") |
| |
| } |
| |
| return pattern, options, nil |
| } |
| |
| func (ejv *extJSONValue) parseSymbol() (string, error) { |
| if ejv.t != bsontype.String { |
| return "", fmt.Errorf("$symbol value should be string, but instead is %s", ejv.t) |
| } |
| |
| return ejv.v.(string), nil |
| } |
| |
| func (ejv *extJSONValue) parseTimestamp() (t, i uint32, err error) { |
| if ejv.t != bsontype.EmbeddedDocument { |
| return 0, 0, fmt.Errorf("$timestamp value should be object, but instead is %s", ejv.t) |
| } |
| |
| handleKey := func(key string, val *extJSONValue, flag bool) (uint32, error) { |
| if flag { |
| return 0, fmt.Errorf("duplicate %s key in $timestamp", key) |
| } |
| |
| switch val.t { |
| case bsontype.Int32: |
| if val.v.(int32) < 0 { |
| return 0, fmt.Errorf("$timestamp %s number should be uint32: %s", key, string(val.v.(int32))) |
| } |
| |
| return uint32(val.v.(int32)), nil |
| case bsontype.Int64: |
| if val.v.(int64) < 0 || uint32(val.v.(int64)) > math.MaxUint32 { |
| return 0, fmt.Errorf("$timestamp %s number should be uint32: %s", key, string(val.v.(int32))) |
| } |
| |
| return uint32(val.v.(int64)), nil |
| default: |
| return 0, fmt.Errorf("$timestamp %s value should be uint32, but instead is %s", key, val.t) |
| } |
| } |
| |
| tsObj := ejv.v.(*extJSONObject) |
| tFound := false |
| iFound := false |
| |
| for j, key := range tsObj.keys { |
| val := tsObj.values[j] |
| |
| switch key { |
| case "t": |
| if t, err = handleKey(key, val, tFound); err != nil { |
| return 0, 0, err |
| } |
| |
| tFound = true |
| case "i": |
| if i, err = handleKey(key, val, iFound); err != nil { |
| return 0, 0, err |
| } |
| |
| iFound = true |
| default: |
| return 0, 0, fmt.Errorf("invalid key in $timestamp object: %s", key) |
| } |
| } |
| |
| if !tFound { |
| return 0, 0, errors.New("missing t field in $timestamp object") |
| } |
| |
| if !iFound { |
| return 0, 0, errors.New("missing i field in $timestamp object") |
| } |
| |
| return t, i, nil |
| } |
| |
| func (ejv *extJSONValue) parseUndefined() error { |
| if ejv.t != bsontype.Boolean { |
| return fmt.Errorf("undefined value should be boolean, but instead is %s", ejv.t) |
| } |
| |
| if !ejv.v.(bool) { |
| return fmt.Errorf("$undefined balue boolean should be true, but instead is %v", ejv.v.(bool)) |
| } |
| |
| return nil |
| } |