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  }