github.com/mendersoftware/go-lib-micro@v0.0.0-20240304135804-e8e39c59b148/mongo/oid/objectid.go (about) 1 // Copyright 2023 Northern.tech AS 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package oid contains wrappers for creating bson ObjectIDs that marshals 16 // correctly according to the bson specification: http://bsonspec.org/spec.html 17 package oid 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "encoding/json" 23 24 "github.com/google/uuid" 25 "github.com/pkg/errors" 26 "go.mongodb.org/mongo-driver/bson" 27 "go.mongodb.org/mongo-driver/bson/bsontype" 28 "go.mongodb.org/mongo-driver/bson/primitive" 29 ) 30 31 type Type int 32 33 const ( 34 TypeNil Type = iota 35 TypeUUID 36 TypeBSONID 37 TypeString 38 ) 39 40 // Size of a UUID in bytes. 41 const Size = 16 42 43 // ObjectID implements a bson compatible fusion of bson ObjectID, UUID and 44 // string types. Depending on the initialization, this type marshals to the 45 // correct bson type (and sub-type) for each type. 46 type ObjectID struct { 47 id interface{} 48 } 49 50 // NewBSONID initialize a new bson-type ObjectID (used by default by mongo) 51 func NewBSONID() ObjectID { 52 return ObjectID{id: primitive.NewObjectID()} 53 } 54 55 // NewUUIDv4 creates a new ObjectID initialized with a UUID v4 (random). 56 // In the rare event that the RNG returns an error, the null UUID is returned. 57 func NewUUIDv4() ObjectID { 58 uid, _ := uuid.NewRandom() 59 return ObjectID{id: uid} 60 } 61 62 // NewUUIDv5 returns a new version 5 uuid in the objectID namespace 63 func NewUUIDv5(id string) ObjectID { 64 ret := uuid.NewSHA1(uuid.NameSpaceOID, []byte(id)) 65 return ObjectID{id: ret} 66 } 67 68 func fromString(id string) interface{} { 69 var ret interface{} 70 switch len(id) { 71 case 24: // Hex-encoded bson-type objectID type. 72 mgoID, e := primitive.ObjectIDFromHex(id) 73 if e != nil { 74 // Fall back on assigning the raw string if value does 75 // not comply. 76 ret = id 77 } else { 78 ret = mgoID 79 } 80 81 case 32, 36, 38, 41, 45: // All valid hex-encoded uuid formats. 82 uid, e := uuid.Parse(id) 83 if e != nil { 84 // Fall back on using the string. 85 ret = id 86 } else { 87 ret = uid 88 } 89 90 default: 91 ret = id 92 } 93 return ret 94 } 95 96 // FromString tries to parse a hex-encoded mongo ObjectID/UUID string, and 97 // returns an error if the string is not a valid UUID. 98 func FromString(id string) ObjectID { 99 return ObjectID{id: fromString(id)} 100 } 101 102 func marshalBSONUUID(u uuid.UUID) (bsontype.Type, []byte, error) { 103 buf := make([]byte, Size+4+1) 104 binary.LittleEndian.PutUint32(buf, Size) 105 buf[4] = bsontype.BinaryUUID 106 copy(buf[5:], u[:]) 107 return bsontype.Binary, buf, nil 108 109 } 110 111 // MarshalBSONValue provides the bson.ValueMarshaler interface. 112 func (oid ObjectID) MarshalBSONValue() (bsontype.Type, []byte, error) { 113 switch objectID := oid.id.(type) { 114 case uuid.UUID: 115 return marshalBSONUUID(objectID) 116 case primitive.ObjectID: 117 return bson.MarshalValue(objectID) 118 case string: 119 return bson.MarshalValue(objectID) 120 } 121 return bsontype.Null, nil, errors.New( 122 "unable to marshal ObjectID: not initialized", 123 ) 124 } 125 126 // UnmarshalBSONValue provides the bson.ValueUnmarshaler interace. 127 func (oid *ObjectID) UnmarshalBSONValue(t bsontype.Type, b []byte) error { 128 var err error 129 switch t { 130 case bsontype.Binary: // Assume UUID type 131 l := binary.LittleEndian.Uint32(b) 132 if l != Size { 133 return errors.Errorf("illegal uuid length: %d", l) 134 } 135 if b[4] != bsontype.BinaryUUID { 136 return errors.Errorf( 137 "illegal bson sub-type: %0x02X, expected: 0x%02X", 138 b[4], bsontype.BinaryUUID, 139 ) 140 } 141 oid.id, err = uuid.FromBytes(b[5:]) 142 case bsontype.ObjectID: 143 if len(b) != 12 { 144 return errors.Errorf( 145 "illegal objectID length: %d", 146 len(b), 147 ) 148 } 149 bsonID := primitive.ObjectID{} 150 copy(bsonID[:], b) 151 oid.id = bsonID 152 return nil 153 case bsontype.String: 154 l := binary.LittleEndian.Uint32(b) 155 strLen := len(b) - 4 156 if int(l) != strLen { 157 return errors.Errorf( 158 "illegal string length; buffer length: "+ 159 "%d != header length: %d", 160 l, strLen, 161 ) 162 } 163 // Length include terminating zero-byte 164 buf := make([]byte, l-1) 165 copy(buf, b[4:]) 166 oid.id = string(buf) 167 return nil 168 default: 169 return errors.Errorf( 170 "illegal bson-type %s, expected ObjectID", t, 171 ) 172 } 173 return err 174 } 175 176 // MarshalJSON ensures the ObjectID marhsals correctly. 177 func (oid ObjectID) MarshalJSON() ([]byte, error) { 178 // All supported types provides json.Marshaller interface. 179 return json.Marshal(oid.id) 180 } 181 182 // UnmarshalJSON unmarshal string-type json to the appropriate ObjectID type. 183 func (oid *ObjectID) UnmarshalJSON(b []byte) error { 184 b = bytes.Trim(b, `"`) 185 oid.id = fromString(string(b)) 186 return nil 187 } 188 189 func (oid ObjectID) String() string { 190 switch id := oid.id.(type) { 191 case uuid.UUID: 192 return id.String() 193 case primitive.ObjectID: 194 return id.Hex() 195 case string: 196 return id 197 default: 198 return "" 199 } 200 } 201 202 func (oid ObjectID) Type() Type { 203 switch oid.id.(type) { 204 case uuid.UUID: 205 return TypeUUID 206 case primitive.ObjectID: 207 return TypeBSONID 208 case string: 209 return TypeString 210 default: 211 return TypeNil 212 } 213 }