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 unified 8 9 import ( 10 "context" 11 "fmt" 12 13 "go.mongodb.org/mongo-driver/bson" 14 "go.mongodb.org/mongo-driver/bson/bsontype" 15 ) 16 17 // operationResult holds the result and/or error returned by an op. 18 type operationResult struct { 19 // For operations that return a single result, this field holds a BSON representation. 20 Result bson.RawValue 21 22 // CursorResult holds the documents retrieved by iterating a Cursor. 23 CursorResult []bson.Raw 24 25 // Err holds the error returned by an operation. This is mutually exclusive with CursorResult but not with Result 26 // because some operations (e.g. bulkWrite) can return both a result and an error. 27 Err error 28 } 29 30 // newEmptyResult returns an operationResult with no fields set. This should be used if the operation does not check 31 // results or errors. 32 func newEmptyResult() *operationResult { 33 return &operationResult{} 34 } 35 36 // newDocumentResult is a helper to create a value result where the value is a BSON document. 37 func newDocumentResult(result []byte, err error) *operationResult { 38 return newValueResult(bsontype.EmbeddedDocument, result, err) 39 } 40 41 // newValueResult creates an operationResult where the result is a BSON value of an arbitrary type. Because some 42 // operations can return both a result and an error (e.g. bulkWrite), the err parameter should be the error returned 43 // by the op, if any. 44 func newValueResult(valueType bsontype.Type, data []byte, err error) *operationResult { 45 return &operationResult{ 46 Result: bson.RawValue{Type: valueType, Value: data}, 47 Err: err, 48 } 49 } 50 51 // newCursorResult creates an operationResult that contains documents retrieved by fully iterating a cursor. 52 func newCursorResult(arr []bson.Raw) *operationResult { 53 // If the operation returned no documents, the array might be nil. It isn't possible to distinguish between this 54 // case and the case where there is no cursor result, so we overwrite the result with an non-nil empty slice. 55 result := arr 56 if result == nil { 57 result = make([]bson.Raw, 0) 58 } 59 60 return &operationResult{ 61 CursorResult: result, 62 } 63 } 64 65 // newErrorResult creates an operationResult that only holds an error. This should only be used when executing an 66 // operation that can return a result or an error, but not both. 67 func newErrorResult(err error) *operationResult { 68 return &operationResult{ 69 Err: err, 70 } 71 } 72 73 // verifyOperationResult checks that the actual and expected results match 74 func verifyOperationResult(ctx context.Context, expected bson.RawValue, actual *operationResult) error { 75 actualVal := actual.Result 76 if actual.CursorResult != nil { 77 _, data, err := bson.MarshalValue(actual.CursorResult) 78 if err != nil { 79 return fmt.Errorf("error converting cursorResult array to BSON: %v", err) 80 } 81 82 actualVal = bson.RawValue{ 83 Type: bsontype.Array, 84 Value: data, 85 } 86 } 87 88 // For document results and arrays of root documents (i.e. cursor results), the actual value can have additional 89 // top-level keys. Single-value array results (e.g. from distinct) must match exactly, so we set extraKeysAllowed to 90 // false only for that case. 91 extraKeysAllowed := actual.Result.Type != bsontype.Array 92 return verifyValuesMatch(ctx, expected, actualVal, extraKeysAllowed) 93 } 94