// ParseSpecialKeys takes a JSON document and inspects it for any extended JSON // type (e.g $numberLong) and replaces any such values with the corresponding // BSON type. func ParseSpecialKeys(special interface{}) (interface{}, error) { // first ensure we are using a correct document type var doc map[string]interface{} switch v := special.(type) { case bson.D: doc = v.Map() case map[string]interface{}: doc = v default: return nil, fmt.Errorf("%v (type %T) is not valid input to ParseSpecialKeys", special, special) } // check document to see if it is special switch len(doc) { case 1: // document has a single field if jsonValue, ok := doc["$date"]; ok { switch v := jsonValue.(type) { case string: return util.FormatDate(v) case bson.D: asMap := v.Map() if jsonValue, ok := asMap["$numberLong"]; ok { n, err := parseNumberLongField(jsonValue) if err != nil { return nil, err } return time.Unix(n/1e3, n%1e3*1e6), err } return nil, errors.New("expected $numberLong field in $date") case map[string]interface{}: if jsonValue, ok := v["$numberLong"]; ok { n, err := parseNumberLongField(jsonValue) if err != nil { return nil, err } return time.Unix(n/1e3, n%1e3*1e6), err } return nil, errors.New("expected $numberLong field in $date") case json.Number: n, err := v.Int64() return time.Unix(n/1e3, n%1e3*1e6), err case float64: n := int64(v) return time.Unix(n/1e3, n%1e3*1e6), nil case int32: n := int64(v) return time.Unix(n/1e3, n%1e3*1e6), nil case int64: return time.Unix(v/1e3, v%1e3*1e6), nil case json.ISODate: return v, nil default: return nil, errors.New("invalid type for $date field") } } if jsonValue, ok := doc["$code"]; ok { switch v := jsonValue.(type) { case string: return bson.JavaScript{Code: v}, nil default: return nil, errors.New("expected $code field to have string value") } } if jsonValue, ok := doc["$oid"]; ok { switch v := jsonValue.(type) { case string: if !bson.IsObjectIdHex(v) { return nil, errors.New("expected $oid field to contain 24 hexadecimal character") } return bson.ObjectIdHex(v), nil default: return nil, errors.New("expected $oid field to have string value") } } if jsonValue, ok := doc["$numberLong"]; ok { return parseNumberLongField(jsonValue) } if jsonValue, ok := doc["$numberInt"]; ok { switch v := jsonValue.(type) { case string: // all of decimal, hex, and octal are supported here n, err := strconv.ParseInt(v, 0, 32) return int32(n), err default: return nil, errors.New("expected $numberInt field to have string value") } } if jsonValue, ok := doc["$timestamp"]; ok { ts := json.Timestamp{} var tsDoc map[string]interface{} switch internalDoc := jsonValue.(type) { case map[string]interface{}: tsDoc = internalDoc case bson.D: tsDoc = internalDoc.Map() default: return nil, errors.New("expected $timestamp key to have internal document") } if seconds, ok := tsDoc["t"]; ok { if asUint32, err := util.ToUInt32(seconds); err == nil { ts.Seconds = asUint32 } else { return nil, errors.New("expected $timestamp 't' field to be a numeric type") } } else { return nil, errors.New("expected $timestamp to have 't' field") } if inc, ok := tsDoc["i"]; ok { if asUint32, err := util.ToUInt32(inc); err == nil { ts.Increment = asUint32 } else { return nil, errors.New("expected $timestamp 'i' field to be a numeric type") } } else { return nil, errors.New("expected $timestamp to have 'i' field") } // see BSON spec for details on the bit fiddling here return bson.MongoTimestamp(int64(ts.Seconds)<<32 | int64(ts.Increment)), nil } if jsonValue, ok := doc["$numberDecimal"]; ok { switch v := jsonValue.(type) { case string: return bson.ParseDecimal128(v) default: return nil, errors.New("expected $numberDecimal field to have string value") } } if _, ok := doc["$undefined"]; ok { return bson.Undefined, nil } if _, ok := doc["$maxKey"]; ok { return bson.MaxKey, nil } if _, ok := doc["$minKey"]; ok { return bson.MinKey, nil } case 2: // document has two fields if jsonValue, ok := doc["$code"]; ok { code := bson.JavaScript{} switch v := jsonValue.(type) { case string: code.Code = v default: return nil, errors.New("expected $code field to have string value") } if jsonValue, ok = doc["$scope"]; ok { switch v2 := jsonValue.(type) { case map[string]interface{}, bson.D: x, err := ParseSpecialKeys(v2) if err != nil { return nil, err } code.Scope = x return code, nil default: return nil, errors.New("expected $scope field to contain map") } } else { return nil, errors.New("expected $scope field with $code field") } } if jsonValue, ok := doc["$regex"]; ok { regex := bson.RegEx{} switch pattern := jsonValue.(type) { case string: regex.Pattern = pattern default: return nil, errors.New("expected $regex field to have string value") } if jsonValue, ok = doc["$options"]; !ok { return nil, errors.New("expected $options field with $regex field") } switch options := jsonValue.(type) { case string: regex.Options = options default: return nil, errors.New("expected $options field to have string value") } // Validate regular expression options for i := range regex.Options { switch o := regex.Options[i]; o { default: return nil, fmt.Errorf("invalid regular expression option '%v'", o) case 'g', 'i', 'm', 's': // allowed } } return regex, nil } if jsonValue, ok := doc["$binary"]; ok { binary := bson.Binary{} switch data := jsonValue.(type) { case string: bytes, err := base64.StdEncoding.DecodeString(data) if err != nil { return nil, err } binary.Data = bytes default: return nil, errors.New("expected $binary field to have string value") } if jsonValue, ok = doc["$type"]; !ok { return nil, errors.New("expected $type field with $binary field") } switch typ := jsonValue.(type) { case string: kind, err := hex.DecodeString(typ) if err != nil { return nil, err } else if len(kind) != 1 { return nil, errors.New("expected single byte (as hexadecimal string) for $type field") } binary.Kind = kind[0] default: return nil, errors.New("expected $type field to have string value") } return binary, nil } if jsonValue, ok := doc["$ref"]; ok { dbRef := mgo.DBRef{} switch data := jsonValue.(type) { case string: dbRef.Collection = data default: return nil, errors.New("expected string for $ref field") } if jsonValue, ok = doc["$id"]; ok { switch v2 := jsonValue.(type) { case map[string]interface{}, bson.D: x, err := ParseSpecialKeys(v2) if err != nil { return nil, fmt.Errorf("error parsing $id field: %v", err) } dbRef.Id = x default: dbRef.Id = v2 } return dbRef, nil } } case 3: if jsonValue, ok := doc["$ref"]; ok { dbRef := mgo.DBRef{} switch data := jsonValue.(type) { case string: dbRef.Collection = data default: return nil, errors.New("expected string for $ref field") } if jsonValue, ok = doc["$id"]; ok { switch v2 := jsonValue.(type) { case map[string]interface{}, bson.D: x, err := ParseSpecialKeys(v2) if err != nil { return nil, fmt.Errorf("error parsing $id field: %v", err) } dbRef.Id = x default: dbRef.Id = v2 } if dbValue, ok := doc["$db"]; ok { switch v3 := dbValue.(type) { case string: dbRef.Database = v3 default: return nil, errors.New("expected string for $db field") } return dbRef, nil } } } } // nothing matched, so we recurse deeper switch v := special.(type) { case bson.D: return GetExtendedBsonD(v) case map[string]interface{}: return ConvertJSONValueToBSON(v) default: return nil, fmt.Errorf("%v (type %T) is not valid input to ParseSpecialKeys", special, special) } }
func TestFieldParsers(t *testing.T) { testutil.VerifyTestType(t, testutil.UnitTestType) Convey("Using FieldAutoParser", t, func() { var p, _ = NewFieldParser(ctAuto, "") var value interface{} var err error Convey("parses integers when it can", func() { value, err = p.Parse("2147483648") So(value.(int64), ShouldEqual, int64(2147483648)) So(err, ShouldBeNil) value, err = p.Parse("42") So(value.(int32), ShouldEqual, 42) So(err, ShouldBeNil) value, err = p.Parse("-2147483649") So(value.(int64), ShouldEqual, int64(-2147483649)) }) Convey("parses decimals when it can", func() { value, err = p.Parse("3.14159265") So(value.(float64), ShouldEqual, 3.14159265) So(err, ShouldBeNil) value, err = p.Parse("0.123123") So(value.(float64), ShouldEqual, 0.123123) So(err, ShouldBeNil) value, err = p.Parse("-123456.789") So(value.(float64), ShouldEqual, -123456.789) So(err, ShouldBeNil) value, err = p.Parse("-1.") So(value.(float64), ShouldEqual, -1.0) So(err, ShouldBeNil) }) Convey("leaves everything else as a string", func() { value, err = p.Parse("12345-6789") So(value.(string), ShouldEqual, "12345-6789") So(err, ShouldBeNil) value, err = p.Parse("06/02/1997") So(value.(string), ShouldEqual, "06/02/1997") So(err, ShouldBeNil) value, err = p.Parse("") So(value.(string), ShouldEqual, "") So(err, ShouldBeNil) }) }) Convey("Using FieldBooleanParser", t, func() { var p, _ = NewFieldParser(ctBoolean, "") var value interface{} var err error Convey("parses representations of true correctly", func() { value, err = p.Parse("true") So(value.(bool), ShouldBeTrue) So(err, ShouldBeNil) value, err = p.Parse("TrUe") So(value.(bool), ShouldBeTrue) So(err, ShouldBeNil) value, err = p.Parse("1") So(value.(bool), ShouldBeTrue) So(err, ShouldBeNil) }) Convey("parses representations of false correctly", func() { value, err = p.Parse("false") So(value.(bool), ShouldBeFalse) So(err, ShouldBeNil) value, err = p.Parse("FaLsE") So(value.(bool), ShouldBeFalse) So(err, ShouldBeNil) value, err = p.Parse("0") So(value.(bool), ShouldBeFalse) So(err, ShouldBeNil) }) Convey("does not parse other boolean representations", func() { _, err = p.Parse("") So(err, ShouldNotBeNil) _, err = p.Parse("t") So(err, ShouldNotBeNil) _, err = p.Parse("f") So(err, ShouldNotBeNil) _, err = p.Parse("yes") So(err, ShouldNotBeNil) _, err = p.Parse("no") So(err, ShouldNotBeNil) }) }) Convey("Using FieldBinaryParser", t, func() { var value interface{} var err error Convey("using hex encoding", func() { var p, _ = NewFieldParser(ctBinary, "hex") Convey("parses valid hex values correctly", func() { value, err = p.Parse("400a11") So(value.([]byte), ShouldResemble, []byte{64, 10, 17}) So(err, ShouldBeNil) value, err = p.Parse("400A11") So(value.([]byte), ShouldResemble, []byte{64, 10, 17}) So(err, ShouldBeNil) value, err = p.Parse("0b400A11") So(value.([]byte), ShouldResemble, []byte{11, 64, 10, 17}) So(err, ShouldBeNil) value, err = p.Parse("") So(value.([]byte), ShouldResemble, []byte{}) So(err, ShouldBeNil) }) }) Convey("using base32 encoding", func() { var p, _ = NewFieldParser(ctBinary, "base32") Convey("parses valid base32 values correctly", func() { value, err = p.Parse("") So(value.([]uint8), ShouldResemble, []uint8{}) So(err, ShouldBeNil) value, err = p.Parse("MZXW6YTBOI======") So(value.([]uint8), ShouldResemble, []uint8{102, 111, 111, 98, 97, 114}) So(err, ShouldBeNil) }) }) Convey("using base64 encoding", func() { var p, _ = NewFieldParser(ctBinary, "base64") Convey("parses valid base64 values correctly", func() { value, err = p.Parse("") So(value.([]uint8), ShouldResemble, []uint8{}) So(err, ShouldBeNil) value, err = p.Parse("Zm9vYmFy") So(value.([]uint8), ShouldResemble, []uint8{102, 111, 111, 98, 97, 114}) So(err, ShouldBeNil) }) }) }) Convey("Using FieldDateParser", t, func() { var value interface{} var err error Convey("with Go's format", func() { var p, _ = NewFieldParser(ctDateGo, "01/02/2006 3:04:05pm MST") Convey("parses valid timestamps correctly", func() { value, err = p.Parse("01/04/2000 5:38:10pm UTC") So(value.(time.Time), ShouldResemble, time.Date(2000, 1, 4, 17, 38, 10, 0, time.UTC)) So(err, ShouldBeNil) }) Convey("does not parse invalid dates", func() { _, err = p.Parse("01/04/2000 5:38:10pm") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000 5:38:10 pm UTC") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000") So(err, ShouldNotBeNil) }) }) Convey("with MS's format", func() { var p, _ = NewFieldParser(ctDateMS, "MM/dd/yyyy h:mm:sstt") Convey("parses valid timestamps correctly", func() { value, err = p.Parse("01/04/2000 5:38:10PM") So(value.(time.Time), ShouldResemble, time.Date(2000, 1, 4, 17, 38, 10, 0, time.UTC)) So(err, ShouldBeNil) }) Convey("does not parse invalid dates", func() { _, err = p.Parse("01/04/2000 :) 05:38:10PM") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000 005:38:10PM") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000 5:38:10 PM") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000") So(err, ShouldNotBeNil) }) }) Convey("with Oracle's format", func() { var p, _ = NewFieldParser(ctDateOracle, "mm/Dd/yYYy hh:MI:SsAm") Convey("parses valid timestamps correctly", func() { value, err = p.Parse("01/04/2000 05:38:10PM") So(value.(time.Time), ShouldResemble, time.Date(2000, 1, 4, 17, 38, 10, 0, time.UTC)) So(err, ShouldBeNil) }) Convey("does not parse invalid dates", func() { _, err = p.Parse("01/04/2000 :) 05:38:10PM") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000 005:38:10PM") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000 5:38:10 PM") So(err, ShouldNotBeNil) _, err = p.Parse("01/04/2000") So(err, ShouldNotBeNil) }) }) }) Convey("Using FieldDoubleParser", t, func() { var p, _ = NewFieldParser(ctDouble, "") var value interface{} var err error Convey("parses valid decimal values correctly", func() { value, err = p.Parse("3.14159265") So(value.(float64), ShouldEqual, 3.14159265) So(err, ShouldBeNil) value, err = p.Parse("0.123123") So(value.(float64), ShouldEqual, 0.123123) So(err, ShouldBeNil) value, err = p.Parse("-123456.789") So(value.(float64), ShouldEqual, -123456.789) So(err, ShouldBeNil) value, err = p.Parse("-1.") So(value.(float64), ShouldEqual, -1.0) So(err, ShouldBeNil) }) Convey("does not parse invalid numbers", func() { _, err = p.Parse("") So(err, ShouldNotBeNil) _, err = p.Parse("1.1.1") So(err, ShouldNotBeNil) _, err = p.Parse("1-2.0") So(err, ShouldNotBeNil) _, err = p.Parse("80-") So(err, ShouldNotBeNil) }) }) Convey("Using FieldInt32Parser", t, func() { var p, _ = NewFieldParser(ctInt32, "") var value interface{} var err error Convey("parses valid integer values correctly", func() { value, err = p.Parse("2147483647") So(value.(int32), ShouldEqual, 2147483647) So(err, ShouldBeNil) value, err = p.Parse("42") So(value.(int32), ShouldEqual, 42) So(err, ShouldBeNil) value, err = p.Parse("-2147483648") So(value.(int32), ShouldEqual, -2147483648) }) Convey("does not parse invalid numbers", func() { _, err = p.Parse("") So(err, ShouldNotBeNil) _, err = p.Parse("42.0") So(err, ShouldNotBeNil) _, err = p.Parse("1-2") So(err, ShouldNotBeNil) _, err = p.Parse("80-") So(err, ShouldNotBeNil) value, err = p.Parse("2147483648") So(err, ShouldNotBeNil) value, err = p.Parse("-2147483649") So(err, ShouldNotBeNil) }) }) Convey("Using FieldInt64Parser", t, func() { var p, _ = NewFieldParser(ctInt64, "") var value interface{} var err error Convey("parses valid integer values correctly", func() { value, err = p.Parse("2147483648") So(value.(int64), ShouldEqual, int64(2147483648)) So(err, ShouldBeNil) value, err = p.Parse("42") So(value.(int64), ShouldEqual, 42) So(err, ShouldBeNil) value, err = p.Parse("-2147483649") So(value.(int64), ShouldEqual, int64(-2147483649)) }) Convey("does not parse invalid numbers", func() { _, err = p.Parse("") So(err, ShouldNotBeNil) _, err = p.Parse("42.0") So(err, ShouldNotBeNil) _, err = p.Parse("1-2") So(err, ShouldNotBeNil) _, err = p.Parse("80-") So(err, ShouldNotBeNil) }) }) Convey("Using FieldDecimalParser", t, func() { var p, _ = NewFieldParser(ctDecimal, "") var err error Convey("parses valid decimal values correctly", func() { for _, ts := range []string{"12235.2355", "42", "0", "-124", "-124.55"} { testVal, err := bson.ParseDecimal128(ts) So(err, ShouldBeNil) parsedValue, err := p.Parse(ts) So(err, ShouldBeNil) So(testVal, ShouldResemble, parsedValue.(bson.Decimal128)) } }) Convey("does not parse invalid decimal values", func() { for _, ts := range []string{"", "1-2", "abcd"} { _, err = p.Parse(ts) So(err, ShouldNotBeNil) } }) }) Convey("Using FieldStringParser", t, func() { var p, _ = NewFieldParser(ctString, "") var value interface{} var err error Convey("parses strings as strings only", func() { value, err = p.Parse("42") So(value.(string), ShouldEqual, "42") So(err, ShouldBeNil) value, err = p.Parse("true") So(value.(string), ShouldEqual, "true") So(err, ShouldBeNil) value, err = p.Parse("") So(value.(string), ShouldEqual, "") So(err, ShouldBeNil) }) }) }
func (ip *FieldDecimalParser) Parse(in string) (interface{}, error) { return bson.ParseDecimal128(in) }