extjson_wrappers.go raw

   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  	"encoding/base64"
  11  	"errors"
  12  	"fmt"
  13  	"math"
  14  	"strconv"
  15  	"time"
  16  
  17  	"go.mongodb.org/mongo-driver/bson/bsontype"
  18  	"go.mongodb.org/mongo-driver/bson/primitive"
  19  )
  20  
  21  func wrapperKeyBSONType(key string) bsontype.Type {
  22  	switch key {
  23  	case "$numberInt":
  24  		return bsontype.Int32
  25  	case "$numberLong":
  26  		return bsontype.Int64
  27  	case "$oid":
  28  		return bsontype.ObjectID
  29  	case "$symbol":
  30  		return bsontype.Symbol
  31  	case "$numberDouble":
  32  		return bsontype.Double
  33  	case "$numberDecimal":
  34  		return bsontype.Decimal128
  35  	case "$binary":
  36  		return bsontype.Binary
  37  	case "$code":
  38  		return bsontype.JavaScript
  39  	case "$scope":
  40  		return bsontype.CodeWithScope
  41  	case "$timestamp":
  42  		return bsontype.Timestamp
  43  	case "$regularExpression":
  44  		return bsontype.Regex
  45  	case "$dbPointer":
  46  		return bsontype.DBPointer
  47  	case "$date":
  48  		return bsontype.DateTime
  49  	case "$minKey":
  50  		return bsontype.MinKey
  51  	case "$maxKey":
  52  		return bsontype.MaxKey
  53  	case "$undefined":
  54  		return bsontype.Undefined
  55  	}
  56  
  57  	return bsontype.EmbeddedDocument
  58  }
  59  
  60  func (ejv *extJSONValue) parseBinary() (b []byte, subType byte, err error) {
  61  	if ejv.t != bsontype.EmbeddedDocument {
  62  		return nil, 0, fmt.Errorf("$binary value should be object, but instead is %s", ejv.t)
  63  	}
  64  
  65  	binObj := ejv.v.(*extJSONObject)
  66  	bFound := false
  67  	stFound := false
  68  
  69  	for i, key := range binObj.keys {
  70  		val := binObj.values[i]
  71  
  72  		switch key {
  73  		case "base64":
  74  			if bFound {
  75  				return nil, 0, errors.New("duplicate base64 key in $binary")
  76  			}
  77  
  78  			if val.t != bsontype.String {
  79  				return nil, 0, fmt.Errorf("$binary base64 value should be string, but instead is %s", val.t)
  80  			}
  81  
  82  			base64Bytes, err := base64.StdEncoding.DecodeString(val.v.(string))
  83  			if err != nil {
  84  				return nil, 0, fmt.Errorf("invalid $binary base64 string: %s", val.v.(string))
  85  			}
  86  
  87  			b = base64Bytes
  88  			bFound = true
  89  		case "subType":
  90  			if stFound {
  91  				return nil, 0, errors.New("duplicate subType key in $binary")
  92  			}
  93  
  94  			if val.t != bsontype.String {
  95  				return nil, 0, fmt.Errorf("$binary subType value should be string, but instead is %s", val.t)
  96  			}
  97  
  98  			i, err := strconv.ParseInt(val.v.(string), 16, 64)
  99  			if err != nil {
 100  				return nil, 0, fmt.Errorf("invalid $binary subType string: %s", val.v.(string))
 101  			}
 102  
 103  			subType = byte(i)
 104  			stFound = true
 105  		default:
 106  			return nil, 0, fmt.Errorf("invalid key in $binary object: %s", key)
 107  		}
 108  	}
 109  
 110  	if !bFound {
 111  		return nil, 0, errors.New("missing base64 field in $binary object")
 112  	}
 113  
 114  	if !stFound {
 115  		return nil, 0, errors.New("missing subType field in $binary object")
 116  
 117  	}
 118  
 119  	return b, subType, nil
 120  }
 121  
 122  func (ejv *extJSONValue) parseDBPointer() (ns string, oid primitive.ObjectID, err error) {
 123  	if ejv.t != bsontype.EmbeddedDocument {
 124  		return "", primitive.NilObjectID, fmt.Errorf("$dbPointer value should be object, but instead is %s", ejv.t)
 125  	}
 126  
 127  	dbpObj := ejv.v.(*extJSONObject)
 128  	oidFound := false
 129  	nsFound := false
 130  
 131  	for i, key := range dbpObj.keys {
 132  		val := dbpObj.values[i]
 133  
 134  		switch key {
 135  		case "$ref":
 136  			if nsFound {
 137  				return "", primitive.NilObjectID, errors.New("duplicate $ref key in $dbPointer")
 138  			}
 139  
 140  			if val.t != bsontype.String {
 141  				return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $ref value should be string, but instead is %s", val.t)
 142  			}
 143  
 144  			ns = val.v.(string)
 145  			nsFound = true
 146  		case "$id":
 147  			if oidFound {
 148  				return "", primitive.NilObjectID, errors.New("duplicate $id key in $dbPointer")
 149  			}
 150  
 151  			if val.t != bsontype.String {
 152  				return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $id value should be string, but instead is %s", val.t)
 153  			}
 154  
 155  			oid, err = primitive.ObjectIDFromHex(val.v.(string))
 156  			if err != nil {
 157  				return "", primitive.NilObjectID, err
 158  			}
 159  
 160  			oidFound = true
 161  		default:
 162  			return "", primitive.NilObjectID, fmt.Errorf("invalid key in $dbPointer object: %s", key)
 163  		}
 164  	}
 165  
 166  	if !nsFound {
 167  		return "", oid, errors.New("missing $ref field in $dbPointer object")
 168  	}
 169  
 170  	if !oidFound {
 171  		return "", oid, errors.New("missing $id field in $dbPointer object")
 172  	}
 173  
 174  	return ns, oid, nil
 175  }
 176  
 177  const (
 178  	rfc3339Milli = "2006-01-02T15:04:05.999Z07:00"
 179  )
 180  
 181  var (
 182  	timeFormats = []string{rfc3339Milli, "2006-01-02T15:04:05.999Z0700"}
 183  )
 184  
 185  func (ejv *extJSONValue) parseDateTime() (int64, error) {
 186  	switch ejv.t {
 187  	case bsontype.Int32:
 188  		return int64(ejv.v.(int32)), nil
 189  	case bsontype.Int64:
 190  		return ejv.v.(int64), nil
 191  	case bsontype.String:
 192  		return parseDatetimeString(ejv.v.(string))
 193  	case bsontype.EmbeddedDocument:
 194  		return parseDatetimeObject(ejv.v.(*extJSONObject))
 195  	default:
 196  		return 0, fmt.Errorf("$date value should be string or object, but instead is %s", ejv.t)
 197  	}
 198  }
 199  
 200  func parseDatetimeString(data string) (int64, error) {
 201  	var t time.Time
 202  	var err error
 203  	// try acceptable time formats until one matches
 204  	for _, format := range timeFormats {
 205  		t, err = time.Parse(format, data)
 206  		if err == nil {
 207  			break
 208  		}
 209  	}
 210  	if err != nil {
 211  		return 0, fmt.Errorf("invalid $date value string: %s", data)
 212  	}
 213  
 214  	return int64(primitive.NewDateTimeFromTime(t)), nil
 215  }
 216  
 217  func parseDatetimeObject(data *extJSONObject) (d int64, err error) {
 218  	dFound := false
 219  
 220  	for i, key := range data.keys {
 221  		val := data.values[i]
 222  
 223  		switch key {
 224  		case "$numberLong":
 225  			if dFound {
 226  				return 0, errors.New("duplicate $numberLong key in $date")
 227  			}
 228  
 229  			if val.t != bsontype.String {
 230  				return 0, fmt.Errorf("$date $numberLong field should be string, but instead is %s", val.t)
 231  			}
 232  
 233  			d, err = val.parseInt64()
 234  			if err != nil {
 235  				return 0, err
 236  			}
 237  			dFound = true
 238  		default:
 239  			return 0, fmt.Errorf("invalid key in $date object: %s", key)
 240  		}
 241  	}
 242  
 243  	if !dFound {
 244  		return 0, errors.New("missing $numberLong field in $date object")
 245  	}
 246  
 247  	return d, nil
 248  }
 249  
 250  func (ejv *extJSONValue) parseDecimal128() (primitive.Decimal128, error) {
 251  	if ejv.t != bsontype.String {
 252  		return primitive.Decimal128{}, fmt.Errorf("$numberDecimal value should be string, but instead is %s", ejv.t)
 253  	}
 254  
 255  	d, err := primitive.ParseDecimal128(ejv.v.(string))
 256  	if err != nil {
 257  		return primitive.Decimal128{}, fmt.Errorf("$invalid $numberDecimal string: %s", ejv.v.(string))
 258  	}
 259  
 260  	return d, nil
 261  }
 262  
 263  func (ejv *extJSONValue) parseDouble() (float64, error) {
 264  	if ejv.t == bsontype.Double {
 265  		return ejv.v.(float64), nil
 266  	}
 267  
 268  	if ejv.t != bsontype.String {
 269  		return 0, fmt.Errorf("$numberDouble value should be string, but instead is %s", ejv.t)
 270  	}
 271  
 272  	switch ejv.v.(string) {
 273  	case "Infinity":
 274  		return math.Inf(1), nil
 275  	case "-Infinity":
 276  		return math.Inf(-1), nil
 277  	case "NaN":
 278  		return math.NaN(), nil
 279  	}
 280  
 281  	f, err := strconv.ParseFloat(ejv.v.(string), 64)
 282  	if err != nil {
 283  		return 0, err
 284  	}
 285  
 286  	return f, nil
 287  }
 288  
 289  func (ejv *extJSONValue) parseInt32() (int32, error) {
 290  	if ejv.t == bsontype.Int32 {
 291  		return ejv.v.(int32), nil
 292  	}
 293  
 294  	if ejv.t != bsontype.String {
 295  		return 0, fmt.Errorf("$numberInt value should be string, but instead is %s", ejv.t)
 296  	}
 297  
 298  	i, err := strconv.ParseInt(ejv.v.(string), 10, 64)
 299  	if err != nil {
 300  		return 0, err
 301  	}
 302  
 303  	if i < math.MinInt32 || i > math.MaxInt32 {
 304  		return 0, fmt.Errorf("$numberInt value should be int32 but instead is int64: %d", i)
 305  	}
 306  
 307  	return int32(i), nil
 308  }
 309  
 310  func (ejv *extJSONValue) parseInt64() (int64, error) {
 311  	if ejv.t == bsontype.Int64 {
 312  		return ejv.v.(int64), nil
 313  	}
 314  
 315  	if ejv.t != bsontype.String {
 316  		return 0, fmt.Errorf("$numberLong value should be string, but instead is %s", ejv.t)
 317  	}
 318  
 319  	i, err := strconv.ParseInt(ejv.v.(string), 10, 64)
 320  	if err != nil {
 321  		return 0, err
 322  	}
 323  
 324  	return i, nil
 325  }
 326  
 327  func (ejv *extJSONValue) parseJavascript() (code string, err error) {
 328  	if ejv.t != bsontype.String {
 329  		return "", fmt.Errorf("$code value should be string, but instead is %s", ejv.t)
 330  	}
 331  
 332  	return ejv.v.(string), nil
 333  }
 334  
 335  func (ejv *extJSONValue) parseMinMaxKey(minmax string) error {
 336  	if ejv.t != bsontype.Int32 {
 337  		return fmt.Errorf("$%sKey value should be int32, but instead is %s", minmax, ejv.t)
 338  	}
 339  
 340  	if ejv.v.(int32) != 1 {
 341  		return fmt.Errorf("$%sKey value must be 1, but instead is %d", minmax, ejv.v.(int32))
 342  	}
 343  
 344  	return nil
 345  }
 346  
 347  func (ejv *extJSONValue) parseObjectID() (primitive.ObjectID, error) {
 348  	if ejv.t != bsontype.String {
 349  		return primitive.NilObjectID, fmt.Errorf("$oid value should be string, but instead is %s", ejv.t)
 350  	}
 351  
 352  	return primitive.ObjectIDFromHex(ejv.v.(string))
 353  }
 354  
 355  func (ejv *extJSONValue) parseRegex() (pattern, options string, err error) {
 356  	if ejv.t != bsontype.EmbeddedDocument {
 357  		return "", "", fmt.Errorf("$regularExpression value should be object, but instead is %s", ejv.t)
 358  	}
 359  
 360  	regexObj := ejv.v.(*extJSONObject)
 361  	patFound := false
 362  	optFound := false
 363  
 364  	for i, key := range regexObj.keys {
 365  		val := regexObj.values[i]
 366  
 367  		switch key {
 368  		case "pattern":
 369  			if patFound {
 370  				return "", "", errors.New("duplicate pattern key in $regularExpression")
 371  			}
 372  
 373  			if val.t != bsontype.String {
 374  				return "", "", fmt.Errorf("$regularExpression pattern value should be string, but instead is %s", val.t)
 375  			}
 376  
 377  			pattern = val.v.(string)
 378  			patFound = true
 379  		case "options":
 380  			if optFound {
 381  				return "", "", errors.New("duplicate options key in $regularExpression")
 382  			}
 383  
 384  			if val.t != bsontype.String {
 385  				return "", "", fmt.Errorf("$regularExpression options value should be string, but instead is %s", val.t)
 386  			}
 387  
 388  			options = val.v.(string)
 389  			optFound = true
 390  		default:
 391  			return "", "", fmt.Errorf("invalid key in $regularExpression object: %s", key)
 392  		}
 393  	}
 394  
 395  	if !patFound {
 396  		return "", "", errors.New("missing pattern field in $regularExpression object")
 397  	}
 398  
 399  	if !optFound {
 400  		return "", "", errors.New("missing options field in $regularExpression object")
 401  
 402  	}
 403  
 404  	return pattern, options, nil
 405  }
 406  
 407  func (ejv *extJSONValue) parseSymbol() (string, error) {
 408  	if ejv.t != bsontype.String {
 409  		return "", fmt.Errorf("$symbol value should be string, but instead is %s", ejv.t)
 410  	}
 411  
 412  	return ejv.v.(string), nil
 413  }
 414  
 415  func (ejv *extJSONValue) parseTimestamp() (t, i uint32, err error) {
 416  	if ejv.t != bsontype.EmbeddedDocument {
 417  		return 0, 0, fmt.Errorf("$timestamp value should be object, but instead is %s", ejv.t)
 418  	}
 419  
 420  	handleKey := func(key string, val *extJSONValue, flag bool) (uint32, error) {
 421  		if flag {
 422  			return 0, fmt.Errorf("duplicate %s key in $timestamp", key)
 423  		}
 424  
 425  		switch val.t {
 426  		case bsontype.Int32:
 427  			value := val.v.(int32)
 428  
 429  			if value < 0 {
 430  				return 0, fmt.Errorf("$timestamp %s number should be uint32: %d", key, value)
 431  			}
 432  
 433  			return uint32(value), nil
 434  		case bsontype.Int64:
 435  			value := val.v.(int64)
 436  			if value < 0 || value > int64(math.MaxUint32) {
 437  				return 0, fmt.Errorf("$timestamp %s number should be uint32: %d", key, value)
 438  			}
 439  
 440  			return uint32(value), nil
 441  		default:
 442  			return 0, fmt.Errorf("$timestamp %s value should be uint32, but instead is %s", key, val.t)
 443  		}
 444  	}
 445  
 446  	tsObj := ejv.v.(*extJSONObject)
 447  	tFound := false
 448  	iFound := false
 449  
 450  	for j, key := range tsObj.keys {
 451  		val := tsObj.values[j]
 452  
 453  		switch key {
 454  		case "t":
 455  			if t, err = handleKey(key, val, tFound); err != nil {
 456  				return 0, 0, err
 457  			}
 458  
 459  			tFound = true
 460  		case "i":
 461  			if i, err = handleKey(key, val, iFound); err != nil {
 462  				return 0, 0, err
 463  			}
 464  
 465  			iFound = true
 466  		default:
 467  			return 0, 0, fmt.Errorf("invalid key in $timestamp object: %s", key)
 468  		}
 469  	}
 470  
 471  	if !tFound {
 472  		return 0, 0, errors.New("missing t field in $timestamp object")
 473  	}
 474  
 475  	if !iFound {
 476  		return 0, 0, errors.New("missing i field in $timestamp object")
 477  	}
 478  
 479  	return t, i, nil
 480  }
 481  
 482  func (ejv *extJSONValue) parseUndefined() error {
 483  	if ejv.t != bsontype.Boolean {
 484  		return fmt.Errorf("undefined value should be boolean, but instead is %s", ejv.t)
 485  	}
 486  
 487  	if !ejv.v.(bool) {
 488  		return fmt.Errorf("$undefined balue boolean should be true, but instead is %v", ejv.v.(bool))
 489  	}
 490  
 491  	return nil
 492  }
 493