summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
authorMihai Andrei <mihai.andrei@10gen.com>2020-08-20 09:29:03 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-08-21 21:52:49 +0000
commitf719f1bee7f548ca74774e92b4fa150a3db15f82 (patch)
tree3ee4245229fa02697fce78a9325b396610d290fa /src/mongo/db/matcher
parentd633834a120784ffd3d63e149398ea4af4ffe987 (diff)
downloadmongo-f719f1bee7f548ca74774e92b4fa150a3db15f82.tar.gz
SERVER-50008 Implement basic jsonSchema validation error generation
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/SConscript2
-rw-r--r--src/mongo/db/matcher/doc_validation_error.cpp249
-rw-r--r--src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp444
-rw-r--r--src/mongo/db/matcher/doc_validation_error_test.cpp283
-rw-r--r--src/mongo/db/matcher/doc_validation_error_test.h45
-rw-r--r--src/mongo/db/matcher/doc_validation_util.cpp62
-rw-r--r--src/mongo/db/matcher/doc_validation_util.h55
-rw-r--r--src/mongo/db/matcher/expression_always_boolean.h5
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp257
-rw-r--r--src/mongo/db/matcher/expression_type.h11
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp11
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_object_match.h4
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp197
13 files changed, 1201 insertions, 424 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index 7ef7f899c9a..10977b44289 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -20,6 +20,7 @@ env.Library(
target='expressions',
source=[
'doc_validation_error.cpp',
+ 'doc_validation_util.cpp',
'expression.cpp',
'expression_algo.cpp',
'expression_array.cpp',
@@ -97,6 +98,7 @@ env.Library(
env.CppUnitTest(
target='db_matcher_test',
source=[
+ 'doc_validation_error_json_schema_test.cpp',
'doc_validation_error_test.cpp',
'expression_algo_test.cpp',
'expression_always_boolean_test.cpp',
diff --git a/src/mongo/db/matcher/doc_validation_error.cpp b/src/mongo/db/matcher/doc_validation_error.cpp
index 52c3aaed3c3..f3e84fef1ae 100644
--- a/src/mongo/db/matcher/doc_validation_error.cpp
+++ b/src/mongo/db/matcher/doc_validation_error.cpp
@@ -44,6 +44,7 @@
#include "mongo/db/matcher/expression_type.h"
#include "mongo/db/matcher/expression_visitor.h"
#include "mongo/db/matcher/match_expression_walker.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_object_match.h"
namespace mongo::doc_validation_error {
namespace {
@@ -51,6 +52,7 @@ MONGO_INIT_REGISTER_ERROR_EXTRA_INFO(DocumentValidationFailureInfo);
using ErrorAnnotation = MatchExpression::ErrorAnnotation;
using AnnotationMode = ErrorAnnotation::Mode;
+using LeafArrayBehavior = ElementPath::LeafArrayBehavior;
/**
* Enumerated type which describes whether an error should be described normally or in an
@@ -80,7 +82,8 @@ struct ValidationErrorFrame {
kErrorNeedChildrenInfo,
};
- ValidationErrorFrame(RuntimeState runtimeState) : runtimeState(runtimeState) {}
+ ValidationErrorFrame(RuntimeState runtimeState, BSONObj currentDoc)
+ : runtimeState(runtimeState), currentDoc(std::move(currentDoc)) {}
// BSONBuilders which construct the generated error.
BSONObjBuilder objBuilder;
@@ -89,6 +92,8 @@ struct ValidationErrorFrame {
size_t childIndex = 0;
// Tracks runtime information about how the current node should generate an error.
RuntimeState runtimeState;
+ // Tracks the current subdocument that an error should be generated over.
+ BSONObj currentDoc;
};
using RuntimeState = ValidationErrorFrame::RuntimeState;
@@ -97,38 +102,42 @@ using RuntimeState = ValidationErrorFrame::RuntimeState;
* A struct which tracks context during error generation.
*/
struct ValidationErrorContext {
- ValidationErrorContext(const MatchableDocument* doc) : doc(doc) {}
+ ValidationErrorContext(const BSONObj& rootDoc) : rootDoc(rootDoc) {}
/**
* Utilities which add/remove ValidationErrorFrames from 'frames'.
*/
- void pushNewFrame(const MatchExpression& expr) {
+ void pushNewFrame(const MatchExpression& expr, const BSONObj& subDoc) {
// Clear the last error that was generated.
latestCompleteError = BSONObj();
+
+ // If this is the first frame, then we know that we've failed validation, so we must be
+ // generating an error.
if (frames.empty()) {
- // If this is the first frame, then we know that we've failed validation, so we must be
- // generating an error.
- frames.emplace(RuntimeState::kError);
+ frames.emplace(RuntimeState::kError, subDoc);
return;
}
+
auto parentRuntimeState = getCurrentRuntimeState();
+
// If we've determined at runtime or at parse time that this node shouldn't contribute to
// error generation, then push a frame indicating that this node should not produce an
// error and return.
if (parentRuntimeState == RuntimeState::kNoError ||
expr.getErrorAnnotation()->mode == AnnotationMode::kIgnore) {
- frames.emplace(RuntimeState::kNoError);
+ frames.emplace(RuntimeState::kNoError, subDoc);
return;
}
// If our parent needs more information, call 'matches()' to determine whether we are
// contributing to error output.
if (parentRuntimeState == RuntimeState::kErrorNeedChildrenInfo) {
- bool generateErrorValue = expr.matches(doc) ? inversion == InvertError::kInverted
- : inversion == InvertError::kNormal;
- frames.emplace(generateErrorValue ? RuntimeState::kError : RuntimeState::kNoError);
+ bool generateErrorValue = expr.matchesBSON(subDoc) ? inversion == InvertError::kInverted
+ : inversion == InvertError::kNormal;
+ frames.emplace(generateErrorValue ? RuntimeState::kError : RuntimeState::kNoError,
+ subDoc);
return;
}
- frames.emplace(RuntimeState::kError);
+ frames.emplace(RuntimeState::kError, subDoc);
}
void popFrame() {
invariant(!frames.empty());
@@ -158,9 +167,20 @@ struct ValidationErrorContext {
invariant(!frames.empty());
return frames.top().runtimeState;
}
- RuntimeState setCurrentRuntimeState(RuntimeState runtimeState) {
+ void setCurrentRuntimeState(RuntimeState runtimeState) {
invariant(!frames.empty());
- return frames.top().runtimeState = runtimeState;
+
+ // If a node has RuntimeState::kNoError, then its runtime state value should never be
+ // modified since the node should never contribute to error generation.
+ if (getCurrentRuntimeState() != RuntimeState::kNoError) {
+ frames.top().runtimeState = runtimeState;
+ }
+ }
+ const BSONObj& getCurrentDocument() {
+ if (!frames.empty()) {
+ return frames.top().currentDoc;
+ }
+ return rootDoc;
}
BSONObj getLatestCompleteError() const {
return latestCompleteError;
@@ -200,7 +220,7 @@ struct ValidationErrorContext {
// Tracks the most recently completed error. The final error will be stored here.
BSONObj latestCompleteError;
// Document which failed to match against the collection's validator.
- const MatchableDocument* doc;
+ const BSONObj& rootDoc;
// Tracks whether the generated error should be described normally or in an inverted context.
InvertError inversion = InvertError::kNormal;
};
@@ -213,10 +233,18 @@ void finishLogicalOperatorChildError(const ListOfMatchExpression* expr,
ValidationErrorContext* ctx) {
BSONObj childError = ctx->latestCompleteError;
if (!childError.isEmpty() && ctx->shouldGenerateError(*expr)) {
- BSONObjBuilder subBuilder = ctx->getCurrentArrayBuilder().subobjStart();
- subBuilder.appendNumber("index", ctx->getCurrentChildIndex());
- subBuilder.append("details", childError);
- subBuilder.done();
+ auto operatorName = expr->getErrorAnnotation()->operatorName;
+
+ // Only provide the indexes of non-matching clauses for explicit $and/$or/$nor in the
+ // user's query.
+ if (operatorName == "$and" || operatorName == "$or" || operatorName == "$nor") {
+ BSONObjBuilder subBuilder = ctx->getCurrentArrayBuilder().subobjStart();
+ subBuilder.appendNumber("index", ctx->getCurrentChildIndex());
+ subBuilder.append("details", childError);
+ subBuilder.done();
+ } else {
+ ctx->getCurrentArrayBuilder().append(childError);
+ }
}
ctx->incrementCurrentChildIndex();
}
@@ -228,20 +256,11 @@ class ValidationErrorPreVisitor final : public MatchExpressionConstVisitor {
public:
ValidationErrorPreVisitor(ValidationErrorContext* context) : _context(context) {}
void visit(const AlwaysFalseMatchExpression* expr) final {
- _context->pushNewFrame(*expr);
- if (_context->shouldGenerateError(*expr)) {
- appendErrorDetails(*expr);
- static constexpr auto kNormalReason = "expression always evaluates to false";
- // It is not possible to generate an error for 'AlwaysFalseMatchExpression' in an
- // inverted context, since this means that an ancestor of this expression is either a
- // 'not' or a 'nor', in which case the either the ancestor expression evaluates to true
- // or this expression will not have contributed to the failure of its ancestor.
- invariant(_context->inversion == InvertError::kNormal);
- static constexpr auto kInvertedReason = "";
- appendErrorReason(*expr, kNormalReason, kInvertedReason);
- }
+ generateAlwaysBooleanError(*expr);
+ }
+ void visit(const AlwaysTrueMatchExpression* expr) final {
+ generateAlwaysBooleanError(*expr);
}
- void visit(const AlwaysTrueMatchExpression* expr) final {}
void visit(const AndMatchExpression* expr) final {
// $all is treated as a leaf operator.
if (expr->getErrorAnnotation()->operatorName == "$all") {
@@ -279,7 +298,7 @@ public:
void visit(const ExistsMatchExpression* expr) final {
static constexpr auto normalReason = "path does not exist";
static constexpr auto invertedReason = "path does exist";
- _context->pushNewFrame(*expr);
+ _context->pushNewFrame(*expr, _context->getCurrentDocument());
if (_context->shouldGenerateError(*expr)) {
appendErrorDetails(*expr);
appendErrorReason(*expr, normalReason, invertedReason);
@@ -288,7 +307,7 @@ public:
void visit(const ExprMatchExpression* expr) final {
static constexpr auto normalReason = "$expr did not match";
static constexpr auto invertedReason = "$expr did match";
- _context->pushNewFrame(*expr);
+ _context->pushNewFrame(*expr, _context->getCurrentDocument());
if (_context->shouldGenerateError(*expr)) {
appendErrorDetails(*expr);
appendErrorReason(*expr, normalReason, invertedReason);
@@ -349,9 +368,41 @@ public:
void visit(const InternalSchemaMinItemsMatchExpression* expr) final {}
void visit(const InternalSchemaMinLengthMatchExpression* expr) final {}
void visit(const InternalSchemaMinPropertiesMatchExpression* expr) final {}
- void visit(const InternalSchemaObjectMatchExpression* expr) final {}
+ void visit(const InternalSchemaObjectMatchExpression* expr) final {
+ // This node should never be responsible for generating an error directly.
+ invariant(expr->getErrorAnnotation()->mode != AnnotationMode::kGenerateError);
+ BSONObj subDocument = _context->getCurrentDocument();
+ ElementPath path(expr->path(), LeafArrayBehavior::kNoTraversal);
+ BSONMatchableDocument doc(_context->getCurrentDocument());
+ MatchableDocument::IteratorHolder cursor(&doc, &path);
+ invariant(cursor->more());
+ auto elem = cursor->next().element();
+
+ // If we do not find an object at expr's path, then the subtree rooted at this node will
+ // not contribute to error generation as there will either be an explicit
+ // ExistsMatchExpression which will explain a missing path error or an explicit
+ // InternalSchemaTypeExpression that will explain a type did not match error.
+ bool ignoreSubTree = false;
+ if (elem.type() == BSONType::Object) {
+ subDocument = elem.embeddedObject();
+ } else {
+ ignoreSubTree = true;
+ }
+
+ // This expression should match exactly one object; if there are any more elements, then
+ // ignore the subtree.
+ if (cursor->more()) {
+ ignoreSubTree = true;
+ }
+ _context->pushNewFrame(*expr, subDocument);
+ if (ignoreSubTree) {
+ _context->setCurrentRuntimeState(RuntimeState::kNoError);
+ }
+ }
void visit(const InternalSchemaRootDocEqMatchExpression* expr) final {}
- void visit(const InternalSchemaTypeExpression* expr) final {}
+ void visit(const InternalSchemaTypeExpression* expr) final {
+ generateTypeError(expr, LeafArrayBehavior::kNoTraversal);
+ }
void visit(const InternalSchemaUniqueItemsMatchExpression* expr) final {}
void visit(const InternalSchemaXorMatchExpression* expr) final {}
void visit(const LTEMatchExpression* expr) final {
@@ -407,17 +458,7 @@ public:
}
void visit(const TwoDPtInAnnulusExpression* expr) final {}
void visit(const TypeMatchExpression* expr) final {
- static constexpr auto normalReason = "no matching types found for specified typeset";
- static constexpr auto invertedReason = "matching types found for specified typeset";
- _context->pushNewFrame(*expr);
- if (_context->shouldGenerateError(*expr)) {
- appendErrorDetails(*expr);
- BSONArray attributeValues = createValuesArray(expr->path());
- appendMissingField(attributeValues);
- appendErrorReason(*expr, normalReason, invertedReason);
- appendConsideredValues(attributeValues);
- appendConsideredTypes(attributeValues);
- }
+ generateTypeError(expr, LeafArrayBehavior::kTraverse);
}
void visit(const WhereMatchExpression* expr) final {
MONGO_UNREACHABLE;
@@ -429,7 +470,11 @@ public:
private:
// Set of utilities responsible for appending various fields to build a descriptive error.
void appendOperatorName(const ErrorAnnotation& annotation, BSONObjBuilder* bob) {
- bob->append("operatorName", annotation.operatorName);
+ auto operatorName = annotation.operatorName;
+ // Only append the operator name if 'annotation' has one.
+ if (!operatorName.empty()) {
+ bob->append("operatorName", operatorName);
+ }
}
void appendSpecifiedAs(const ErrorAnnotation& annotation, BSONObjBuilder* bob) {
bob->append("specifiedAs", annotation.annotation);
@@ -442,7 +487,8 @@ private:
}
BSONArray createValuesArray(const ElementPath& path) {
- MatchableDocument::IteratorHolder cursor(_context->doc, &path);
+ BSONMatchableDocument doc(_context->getCurrentDocument());
+ MatchableDocument::IteratorHolder cursor(&doc, &path);
BSONArrayBuilder bab;
while (cursor->more()) {
auto elem = cursor->next().element();
@@ -480,7 +526,7 @@ private:
return; // an element has one of the expected types
}
}
- bob.append("reason", "type mismatch");
+ bob.append("reason", "type did not match");
appendConsideredTypes(arr);
std::set<std::string> types;
for (auto&& elem : *expectedTypes) {
@@ -538,8 +584,8 @@ private:
* BSONObjBuilder tracked by '_context' describing why the document failed to match against
* 'expr'. In particular:
* - Appends "reason: field was missing" if expr's path is missing from the document.
- * - Appends "reason: type mismatch" along with 'expectedTypes' and 'consideredTypes' if none
- * of the values at expr's path match any of the types specified in 'expectedTypes'.
+ * - Appends "reason: type did not match" along with 'expectedTypes' and 'consideredTypes' if
+ * none of the values at expr's path match any of the types specified in 'expectedTypes'.
* - Appends the specified 'reason' along with 'consideredValue' if the 'path' in the
* document resolves to a single value.
* - Appends the specified 'reason' along with 'consideredValues' if the 'path' in the
@@ -549,9 +595,8 @@ private:
const std::string& normalReason,
const std::string& invertedReason,
const std::set<BSONType>* expectedTypes = nullptr,
- ElementPath::LeafArrayBehavior leafArrayBehavior =
- ElementPath::LeafArrayBehavior::kTraverse) {
- _context->pushNewFrame(expr);
+ LeafArrayBehavior leafArrayBehavior = LeafArrayBehavior::kTraverse) {
+ _context->pushNewFrame(expr, _context->getCurrentDocument());
if (_context->shouldGenerateError(expr)) {
appendErrorDetails(expr);
ElementPath path(expr.path(), leafArrayBehavior);
@@ -579,11 +624,24 @@ private:
const std::string& normalReason,
const std::string& invertedReason) {
static const std::set<BSONType> expectedTypes{BSONType::Array};
- generatePathError(*expr,
- normalReason,
- invertedReason,
- &expectedTypes,
- ElementPath::LeafArrayBehavior::kNoTraversal);
+ generatePathError(
+ *expr, normalReason, invertedReason, &expectedTypes, LeafArrayBehavior::kNoTraversal);
+ }
+
+ template <class T>
+ void generateTypeError(const TypeMatchExpressionBase<T>* expr, LeafArrayBehavior behavior) {
+ _context->pushNewFrame(*expr, _context->getCurrentDocument());
+ static constexpr auto kNormalReason = "type did not match";
+ static constexpr auto kInvertedReason = "type did match";
+ if (_context->shouldGenerateError(*expr)) {
+ appendErrorDetails(*expr);
+ ElementPath path(expr->path(), behavior);
+ BSONArray arr = createValuesArray(path);
+ appendMissingField(arr);
+ appendErrorReason(*expr, kNormalReason, kInvertedReason);
+ appendConsideredValues(arr);
+ appendConsideredTypes(arr);
+ }
}
/**
* Generates a document validation error for a bit test expression 'expr'.
@@ -604,10 +662,11 @@ private:
*/
void preVisitTreeOperator(const MatchExpression* expr) {
invariant(expr->numChildren() > 0);
- _context->pushNewFrame(*expr);
+ _context->pushNewFrame(*expr, _context->getCurrentDocument());
if (_context->shouldGenerateError(*expr)) {
auto annotation = expr->getErrorAnnotation();
appendOperatorName(*annotation, &_context->getCurrentObjBuilder());
+ _context->getCurrentObjBuilder().appendElements(annotation->annotation);
}
}
/**
@@ -616,14 +675,14 @@ private:
* reporting.
*/
void processAll(const AndMatchExpression& expr) {
- _context->pushNewFrame(expr);
+ _context->pushNewFrame(expr, _context->getCurrentDocument());
if (_context->shouldGenerateError(expr)) {
invariant(expr.numChildren() > 0);
appendErrorDetails(expr);
auto childExpr = expr.getChild(0);
static constexpr auto kNormalReason = "array did not contain all specified values";
static constexpr auto kInvertedReason = "array did contain all specified values";
- ElementPath path(childExpr->path(), ElementPath::LeafArrayBehavior::kNoTraversal);
+ ElementPath path(childExpr->path(), LeafArrayBehavior::kNoTraversal);
auto arr = createValuesArray(path);
appendMissingField(arr);
appendErrorReason(expr, kNormalReason, kInvertedReason);
@@ -631,6 +690,29 @@ private:
}
}
+ /**
+ * For an AlwaysBooleanMatchExpression, we simply output the error information obtained at
+ * parse time.
+ */
+ void generateAlwaysBooleanError(const AlwaysBooleanMatchExpression& expr) {
+ _context->pushNewFrame(expr, _context->getCurrentDocument());
+ if (_context->shouldGenerateError(expr)) {
+ // An AlwaysBooleanMatchExpression can only contribute to error generation when the
+ // inversion matches the value of the 'expr'. More precisely, it is only possible
+ // to generate an error for 'expr' if it evaluates to false in a normal context or
+ // if it evaluates to true an inverted context.
+ if (expr.isTriviallyFalse()) {
+ invariant(_context->inversion == InvertError::kNormal);
+ } else {
+ invariant(_context->inversion == InvertError::kInverted);
+ }
+ appendErrorDetails(expr);
+ static constexpr auto kNormalReason = "expression always evaluates to false";
+ static constexpr auto kInvertedReason = "expression always evaluates to true";
+ appendErrorReason(expr, kNormalReason, kInvertedReason);
+ }
+ }
+
ValidationErrorContext* _context;
};
@@ -724,14 +806,27 @@ public:
void visit(const AlwaysFalseMatchExpression* expr) final {
_context->finishCurrentError(expr);
}
- void visit(const AlwaysTrueMatchExpression* expr) final {}
+ void visit(const AlwaysTrueMatchExpression* expr) final {
+ _context->finishCurrentError(expr);
+ }
void visit(const AndMatchExpression* expr) final {
auto operatorName = expr->getErrorAnnotation()->operatorName;
- if (operatorName == "$all") {
+ // Clean up the frame for this node if we're finishing the error for an $all or this node
+ // shouldn't generate an error.
+ if (operatorName == "$all" || !_context->shouldGenerateError(*expr)) {
_context->finishCurrentError(expr);
- } else {
- postVisitTreeOperator(expr);
+ return;
}
+ // Specify a different details string based on the operatorName. Note that if our node
+ // doesn't have an operator name specified, the default reason string is 'details'.
+ static const StringMap<std::string> detailsStringMap = {
+ {"$and", "clausesNotSatisfied"},
+ {"properties", "propertiesNotSatisfied"},
+ {"$jsonSchema", "schemaRulesNotSatisfied"},
+ {"", "details"}};
+ auto detailsString = detailsStringMap.find(operatorName);
+ invariant(detailsString != detailsStringMap.end());
+ postVisitTreeOperator(expr, detailsString->second);
}
void visit(const BitsAllClearMatchExpression* expr) final {
_context->finishCurrentError(expr);
@@ -790,9 +885,13 @@ public:
void visit(const InternalSchemaMinItemsMatchExpression* expr) final {}
void visit(const InternalSchemaMinLengthMatchExpression* expr) final {}
void visit(const InternalSchemaMinPropertiesMatchExpression* expr) final {}
- void visit(const InternalSchemaObjectMatchExpression* expr) final {}
+ void visit(const InternalSchemaObjectMatchExpression* expr) final {
+ _context->finishCurrentError(expr);
+ }
void visit(const InternalSchemaRootDocEqMatchExpression* expr) final {}
- void visit(const InternalSchemaTypeExpression* expr) final {}
+ void visit(const InternalSchemaTypeExpression* expr) final {
+ _context->finishCurrentError(expr);
+ }
void visit(const InternalSchemaUniqueItemsMatchExpression* expr) final {}
void visit(const InternalSchemaXorMatchExpression* expr) final {}
void visit(const LTEMatchExpression* expr) final {
@@ -806,7 +905,8 @@ public:
}
void visit(const NorMatchExpression* expr) final {
_context->flipInversion();
- postVisitTreeOperator(expr);
+ static constexpr auto detailsString = "clausesNotSatisfied";
+ postVisitTreeOperator(expr, detailsString);
}
void visit(const NotMatchExpression* expr) final {
_context->flipInversion();
@@ -816,7 +916,8 @@ public:
_context->finishCurrentError(expr);
}
void visit(const OrMatchExpression* expr) final {
- postVisitTreeOperator(expr);
+ static constexpr auto detailsString = "clausesNotSatisfied";
+ postVisitTreeOperator(expr, detailsString);
}
void visit(const RegexMatchExpression* expr) final {
_context->finishCurrentError(expr);
@@ -842,11 +943,12 @@ public:
}
private:
- void postVisitTreeOperator(const ListOfMatchExpression* expr) {
+ void postVisitTreeOperator(const ListOfMatchExpression* expr,
+ const std::string& detailsString) {
finishLogicalOperatorChildError(expr, _context);
if (_context->shouldGenerateError(*expr)) {
auto failedClauses = _context->getCurrentArrayBuilder().arr();
- _context->getCurrentObjBuilder().append("clausesNotSatisfied", failedClauses);
+ _context->getCurrentObjBuilder().append(detailsString, failedClauses);
}
_context->finishCurrentError(expr);
}
@@ -886,8 +988,7 @@ const BSONObj& DocumentValidationFailureInfo::getDetails() const {
return _details;
}
BSONObj generateError(const MatchExpression& validatorExpr, const BSONObj& doc) {
- BSONMatchableDocument matchableDoc(doc);
- ValidationErrorContext context(&matchableDoc);
+ ValidationErrorContext context(doc);
ValidationErrorPreVisitor preVisitor{&context};
ValidationErrorInVisitor inVisitor{&context};
ValidationErrorPostVisitor postVisitor{&context};
diff --git a/src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp b/src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp
new file mode 100644
index 00000000000..7de748f1f43
--- /dev/null
+++ b/src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp
@@ -0,0 +1,444 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/matcher/doc_validation_error_test.h"
+
+namespace mongo {
+namespace {
+
+// properties
+TEST(JSONSchemaValidation, BasicProperties) {
+ BSONObj query = fromjson("{'$jsonSchema': {'properties': {'a': {'minimum': 1}}}}}");
+ BSONObj document = fromjson("{a: 0}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum' : 1},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 0}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+// Exclusive minimum/maximum
+TEST(JSONSchemaValidation, ExclusiveMinimum) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'properties': {'a': {'minimum': 1, 'exclusiveMinimum': true}}}}}");
+ BSONObj document = fromjson("{a: 1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum' : 1, 'exclusiveMinimum': true},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 1}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinimumAtTopLevelHasNoEffect) {
+ BSONObj query = fromjson("{'$nor': [{'$jsonSchema': {'minimum': 1}}]}");
+ BSONObj document = fromjson("{a: 2}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$nor',"
+ " 'clausesNotSatisfied': [{'index': 0, 'details': "
+ " {'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'minimum', "
+ " 'specifiedAs': {'minimum': 1}, "
+ " 'reason': 'expression always evaluates to true'}]}}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, ExclusiveMaximum) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'properties': {'a': {'maximum': 1, 'exclusiveMaximum': true}}}}}");
+ BSONObj document = fromjson("{a: 1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'maximum',"
+ " 'specifiedAs': {'maximum' : 1, 'exclusiveMaximum': true},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 1}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaximumAtTopLevelHasNoEffect) {
+ BSONObj query = fromjson("{'$nor': [{'$jsonSchema': {'maximum': 1}}]}");
+ BSONObj document = fromjson("{a: 2}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$nor',"
+ " 'clausesNotSatisfied': [{'index': 0, 'details': "
+ " {'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'maximum', "
+ " 'specifiedAs': {'maximum': 1}, "
+ " 'reason': 'expression always evaluates to true'}]}}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, NestedProperties) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {"
+ " 'properties': {"
+ " 'a': {'properties': "
+ " {'b': {'minimum': 1}}}}}}}}");
+ BSONObj document = fromjson("{'a': {'b': 0}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', "
+ " 'details':"
+ " [{'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'b', "
+ " 'details': ["
+ " {'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum': 1},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 0}]}]}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, NestedPropertiesMultipleFailingClauses) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {"
+ " 'properties': {"
+ " 'a': {'properties': "
+ " {'b': {'minimum': 10, 'maximum': -10}}}}}}}}");
+ BSONObj document = fromjson("{'a': {'b': 5}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', "
+ " 'details':"
+ " [{'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'b', "
+ " 'details': ["
+ " {'operatorName': 'maximum',"
+ " 'specifiedAs': {'maximum': -10},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 5},"
+ " {'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum': 10},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 5}]}]}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, InternalSchemaObjectMatchDottedPathsHandledCorrectly) {
+ BSONObj query = fromjson("{'a.b': {'$_internalSchemaObjectMatch': {'c': 1}}}");
+ BSONObj document = fromjson("{'a': {'b': {'c': 0}}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$eq',"
+ " 'specifiedAs': {'c' : 1},"
+ "'reason': 'comparison failed',"
+ "'consideredValue': 0}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, PropertiesObjectTypeMismatch) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {"
+ " 'properties': {"
+ " 'a': {'type': 'object', 'properties': {"
+ " 'b': {'minimum': 10}}}}}}");
+ BSONObj document = fromjson("{'a': 'eleven'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'object'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': 'eleven',"
+ " 'consideredType': 'string'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, TypeRestrictionContradictsSpecifiedType) {
+ BSONObj query =
+ fromjson("{$jsonSchema: {type: 'object', properties: {a: {type: 'string', minimum: 5}}}}");
+ BSONObj document = fromjson("{a: 6}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'string'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': 6,"
+ " 'consideredType': 'int'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MultipleNestedProperties) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': { "
+ " 'properties': {"
+ " 'a': {'properties': {'b': {'minimum': 1}, 'c': {'minimum': 10}}},"
+ " 'd': {'properties': {'e': {'minimum': 50}, 'f': {'minimum': 100}}}}}}}}}");
+ BSONObj document = fromjson("{'a': {'b': 0, 'c': 5}, 'd': {'e': 25, 'f': 50}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', "
+ " 'details':"
+ " [{'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'b', "
+ " 'details': ["
+ " {'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum': 1},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 0}]},"
+ " {'propertyName': 'c', "
+ " 'details': ["
+ " {'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum': 10},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 5}]}]}]},"
+ " {'propertyName': 'd', "
+ " 'details':"
+ " [{'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'e', "
+ " 'details': ["
+ " {'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum': 50},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 25}]},"
+ " {'propertyName': 'f', "
+ " 'details': ["
+ " {'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum': 100},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 50}]}]}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+TEST(JSONSchemaValidation, JSONSchemaAndQueryOperators) {
+ BSONObj query = fromjson(
+ "{$and: ["
+ "{'$jsonSchema': {'properties': {'a': {'minimum': 1}}}},"
+ "{'$jsonSchema': {'properties': {'b': {'minimum': 1}}}},"
+ "{'$jsonSchema': {'properties': {'c': {'minimum': 1}}}}]}");
+ BSONObj document = fromjson("{a: 0, b: 2, c: -1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$and',"
+ " 'clausesNotSatisfied': ["
+ " {'index': 0, 'details': "
+ " {'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum' : 1},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 0}]}]}]}},"
+ " {'index': 2, 'details': "
+ " {'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'c', 'details': "
+ " [{'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum' : 1},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': -1}]}]}]}}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, NoTopLevelObjectTypeRejectsAllDocuments) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':{ 'type': 'number',"
+ " 'properties': {"
+ " 'a': {'properties': "
+ " {'b': {'minimum': 1}}}}}}}}");
+ BSONObj document = fromjson("{'a': {'b': 1}}");
+ BSONObj expectedError = BSON("operatorName"
+ << "$jsonSchema"
+ << "specifiedAs" << query << "reason"
+ << "expression always evaluates to false");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+// type
+TEST(JSONSchemaValidation, BasicType) {
+ BSONObj query = fromjson(" {'$jsonSchema': {'properties': {'a': {'type': 'string'}}}}");
+ BSONObj document = fromjson("{'a': {'b': 1}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'string'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': {'b': 1},"
+ " 'consideredType': 'object'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MultipleTypeFailures) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'properties':"
+ " {'a': {'type': 'string'}, "
+ " 'b': {'type': 'number'}, "
+ " 'c': {'type': 'object'}}}}}");
+ BSONObj document = fromjson("{'a': {'b': 1}, 'b': 4, 'c': 'foo'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'string'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': {'b': 1},"
+ " 'consideredType': 'object'}]},"
+ " {'propertyName': 'c', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'object'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': 'foo',"
+ " 'consideredType': 'string'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, TypeNoImplicitArrayTraversal) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'properties': "
+ " {'a': {'type': 'string'}}}}");
+ // Even though 'a' is an array of strings, this is a type mismatch in the world of $jsonSchema.
+ BSONObj document = fromjson("{'a': ['Mihai', 'was', 'here']}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'string'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': ['Mihai', 'was', 'here'],"
+ " 'consideredType': 'array'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+// bsonType
+TEST(JSONSchemaValidation, BasicBSONType) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'properties': "
+ " {'a': {'bsonType': 'double'}}}}");
+ BSONObj document = fromjson("{'a': {'b': 1}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'bsonType',"
+ " 'specifiedAs': {'bsonType': 'double'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': {'b': 1},"
+ " 'consideredType': 'object'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MultipleBSONTypeFailures) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'properties': "
+ " {'a': {'bsonType': 'double'}, "
+ " 'b': {'bsonType': 'int'}, "
+ " 'c': {'bsonType': 'decimal'}}}}}");
+ BSONObj document = fromjson("{'a': {'b': 1}, 'b': 4, 'c': 'foo'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'bsonType',"
+ " 'specifiedAs': {'bsonType': 'double'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': {'b': 1},"
+ " 'consideredType': 'object'}]},"
+ " {'propertyName': 'c', 'details': ["
+ " {'operatorName': 'bsonType',"
+ " 'specifiedAs': {'bsonType': 'decimal'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': 'foo',"
+ " 'consideredType': 'string'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, BSONTypeNoImplicitArrayTraversal) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'properties': "
+ " {'a': {'bsonType': 'string'}}}}");
+ // Even though 'a' is an array of strings, this is a type mismatch in the world of $jsonSchema.
+ BSONObj document = fromjson("{'a': ['Mihai', 'was', 'here']}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'bsonType',"
+ " 'specifiedAs': {'bsonType': 'string'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': ['Mihai', 'was', 'here'],"
+ " 'consideredType': 'array'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/doc_validation_error_test.cpp b/src/mongo/db/matcher/doc_validation_error_test.cpp
index ee85db99849..a36786412e5 100644
--- a/src/mongo/db/matcher/doc_validation_error_test.cpp
+++ b/src/mongo/db/matcher/doc_validation_error_test.cpp
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018-present MongoDB, Inc.
+ * Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
@@ -29,27 +29,28 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/matcher/doc_validation_error.h"
-#include "mongo/db/matcher/expression_parser.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/unittest/unittest.h"
+#include "mongo/db/matcher/doc_validation_error_test.h"
-namespace mongo {
-namespace {
-/**
- * Utility function which parses a MatchExpression from 'query' and verifies that the error
- * generated by the parsed MatchExpression and 'document' matches 'expectedError'.
- */
-void verifyGeneratedError(BSONObj query, BSONObj document, BSONObj expectedError) {
+namespace mongo::doc_validation_error {
+void verifyGeneratedError(const BSONObj& query,
+ const BSONObj& document,
+ const BSONObj& expectedError) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
expCtx->isParsingCollectionValidator = true;
StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx);
ASSERT_OK(result.getStatus());
MatchExpression* expr = result.getValue().get();
BSONObj generatedError = doc_validation_error::generateError(*expr, document);
+
+ // Verify that the document fails to match against the query.
+ ASSERT_FALSE(expr->matchesBSON(document));
+
+ // Verify that the generated error matches the expected error.
ASSERT_BSONOBJ_EQ(generatedError, expectedError);
}
+namespace {
+
// Comparison operators.
// $eq
TEST(ComparisonMatchExpression, BasicEq) {
@@ -60,7 +61,7 @@ TEST(ComparisonMatchExpression, BasicEq) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 1);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, EqMissingPath) {
@@ -70,7 +71,7 @@ TEST(ComparisonMatchExpression, EqMissingPath) {
<< "$eq"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, EqImplicitArrayTraversal) {
@@ -82,7 +83,7 @@ TEST(ComparisonMatchExpression, EqImplicitArrayTraversal) {
<< "comparison failed"
<< "consideredValues"
<< BSON_ARRAY(3 << 4 << 5 << BSON_ARRAY(3 << 4 << 5)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocumentSingleElement) {
@@ -93,7 +94,7 @@ TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocumentSingleElem
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 3);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocument) {
@@ -104,7 +105,7 @@ TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocument) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValues" << BSON_ARRAY(3 << 4 << 5));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedArrays) {
@@ -118,7 +119,7 @@ TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedArrays) {
<< "consideredValues"
<< BSON_ARRAY(1 << 2 << BSON_ARRAY(1 << 2) << 3 << 4
<< BSON_ARRAY(3 << 4)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, EqNoOperator) {
@@ -129,7 +130,7 @@ TEST(ComparisonMatchExpression, EqNoOperator) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 1);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $ne
@@ -141,7 +142,7 @@ TEST(ComparisonMatchExpression, BasicNe) {
<< "specifiedAs" << query << "reason"
<< "comparison succeeded"
<< "consideredValue" << 2);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, NeImplicitArrayTraversal) {
@@ -153,7 +154,7 @@ TEST(ComparisonMatchExpression, NeImplicitArrayTraversal) {
<< "comparison succeeded"
<< "consideredValues"
<< BSON_ARRAY(1 << 2 << 3 << BSON_ARRAY(1 << 2 << 3)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $lt
@@ -165,7 +166,7 @@ TEST(ComparisonMatchExpression, BasicLt) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 1);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, LtMissingPath) {
@@ -175,7 +176,7 @@ TEST(ComparisonMatchExpression, LtMissingPath) {
<< "$lt"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, LtImplicitArrayTraversal) {
@@ -187,7 +188,7 @@ TEST(ComparisonMatchExpression, LtImplicitArrayTraversal) {
<< "comparison failed"
<< "consideredValues"
<< BSON_ARRAY(3 << 4 << 5 << BSON_ARRAY(3 << 4 << 5)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $lte
@@ -199,7 +200,7 @@ TEST(ComparisonMatchExpression, BasicLte) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 1);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, LteMissingPath) {
@@ -209,7 +210,7 @@ TEST(ComparisonMatchExpression, LteMissingPath) {
<< "$lte"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, LteImplicitArrayTraversal) {
@@ -221,7 +222,7 @@ TEST(ComparisonMatchExpression, LteImplicitArrayTraversal) {
<< "comparison failed"
<< "consideredValues"
<< BSON_ARRAY(3 << 4 << 5 << BSON_ARRAY(3 << 4 << 5)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $gt
@@ -233,7 +234,7 @@ TEST(ComparisonMatchExpression, BasicGt) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 0);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, GtMissingPath) {
@@ -243,7 +244,7 @@ TEST(ComparisonMatchExpression, GtMissingPath) {
<< "$gt"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, GtImplicitArrayTraversal) {
@@ -255,7 +256,7 @@ TEST(ComparisonMatchExpression, GtImplicitArrayTraversal) {
<< "comparison failed"
<< "consideredValues"
<< BSON_ARRAY(0 << 1 << 2 << BSON_ARRAY(0 << 1 << 2)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $gte
@@ -267,7 +268,7 @@ TEST(ComparisonMatchExpression, BasicGte) {
<< "specifiedAs" << query << "reason"
<< "comparison failed"
<< "consideredValue" << 0);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, GteMissingPath) {
@@ -277,7 +278,7 @@ TEST(ComparisonMatchExpression, GteMissingPath) {
<< "$gte"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, GteImplicitArrayTraversal) {
@@ -289,7 +290,7 @@ TEST(ComparisonMatchExpression, GteImplicitArrayTraversal) {
<< "comparison failed"
<< "consideredValues"
<< BSON_ARRAY(0 << 1 << 2 << BSON_ARRAY(0 << 1 << 2)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $in
@@ -301,7 +302,7 @@ TEST(ComparisonMatchExpression, BasicIn) {
<< "specifiedAs" << query << "reason"
<< "no matching value found in array"
<< "consideredValue" << 4);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, InMissingPath) {
@@ -311,7 +312,7 @@ TEST(ComparisonMatchExpression, InMissingPath) {
<< "$in"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, InNestedDocumentsAndArrays) {
@@ -326,7 +327,7 @@ TEST(ComparisonMatchExpression, InNestedDocumentsAndArrays) {
<< "consideredValues"
<< BSON_ARRAY(1 << 2 << BSON_ARRAY(1 << 2) << 3 << 4
<< BSON_ARRAY(3 << 4)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $nin
@@ -338,7 +339,7 @@ TEST(ComparisonMatchExpression, BasicNin) {
<< "specifiedAs" << query << "reason"
<< "matching value found in array"
<< "consideredValue" << 3);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, NinNestedDocumentsAndArrays) {
@@ -352,7 +353,7 @@ TEST(ComparisonMatchExpression, NinNestedDocumentsAndArrays) {
<< "consideredValues"
<< BSON_ARRAY(1 << 2 << BSON_ARRAY(1 << 2) << 3 << 4
<< BSON_ARRAY(3 << 4)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verify that Comparison operators which accept a regex ($in and $nin) work as expected.
@@ -367,7 +368,7 @@ TEST(ComparisonMatchExpression, InAcceptsRegex) {
<< "no matching value found in array"
<< "consideredValue"
<< "Validation");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ComparisonMatchExpression, NinAcceptsRegex) {
@@ -381,7 +382,7 @@ TEST(ComparisonMatchExpression, NinAcceptsRegex) {
<< "matching value found in array"
<< "consideredValue"
<< "berry");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Logical operators
@@ -400,7 +401,7 @@ TEST(LogicalMatchExpression, BasicAnd) {
<< failingClause << "reason"
<< "comparison failed"
<< "consideredValue" << 11))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, ImplicitAnd) {
@@ -417,7 +418,7 @@ TEST(LogicalMatchExpression, ImplicitAnd) {
<< failingClause << "reason"
<< "comparison failed"
<< "consideredValue" << 11))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, AndMultipleFailingClauses) {
@@ -441,7 +442,20 @@ TEST(LogicalMatchExpression, AndMultipleFailingClauses) {
<< "specifiedAs" << secondFailingClause << "reason"
<< "comparison failed"
<< "consideredValue" << 15))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(LogicalMatchExpression, NestedAndDoesNotReportErrorDetailsIfItMatches) {
+ BSONObj query = fromjson("{$and: [{$and: [{a: 1}]}, {$and: [{b: 1}]}]}");
+ BSONObj document = fromjson("{a: 1, b: 2}");
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$and', clausesNotSatisfied: [{index: 1, details: {"
+ " operatorName: '$and', clausesNotSatisfied: [{index: 0, details: {"
+ " operatorName: '$eq', "
+ " specifiedAs: {b: 1}, "
+ " reason: 'comparison failed', "
+ " consideredValue: 2}}]}}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $or
@@ -459,7 +473,7 @@ TEST(LogicalMatchExpression, BasicOr) {
<< failingClause << "reason"
<< "comparison failed"
<< "consideredValue" << 11))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, OrMultipleFailingClauses) {
@@ -483,7 +497,7 @@ TEST(LogicalMatchExpression, OrMultipleFailingClauses) {
<< "specifiedAs" << secondFailingClause << "reason"
<< "comparison failed"
<< "consideredValue" << 15))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $nor
@@ -502,7 +516,7 @@ TEST(LogicalMatchExpression, BasicNor) {
<< "specifiedAs" << secondFailingClause << "reason"
<< "comparison succeeded"
<< "consideredValue" << 9))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, NorAllSuccessfulClauses) {
@@ -526,7 +540,7 @@ TEST(LogicalMatchExpression, NorAllSuccessfulClauses) {
<< "specifiedAs" << secondFailingClause << "reason"
<< "comparison succeeded"
<< "consideredValue" << 15))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $not
@@ -543,7 +557,7 @@ TEST(LogicalMatchExpression, BasicNot) {
<< "specifiedAs" << failingQuery << "reason"
<< "comparison succeeded"
<< "consideredValue" << 9));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, NotOverImplicitAnd) {
@@ -572,7 +586,7 @@ TEST(LogicalMatchExpression, NotOverImplicitAnd) {
<< BSON("a" << BSON("$gt" << 5)) << "reason"
<< "comparison succeeded"
<< "consideredValue" << 10)))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, NestedNot) {
@@ -591,7 +605,7 @@ TEST(LogicalMatchExpression, NestedNot) {
<< "specifiedAs" << failingQuery << "reason"
<< "comparison failed"
<< "consideredValue" << 11)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Combine logical operators
@@ -625,7 +639,7 @@ TEST(LogicalMatchExpression, NestedAndOr) {
" 'specifiedAs': {'qty': {'$lt': 10}},"
" 'reason': 'comparison failed',"
" 'consideredValue': 30}}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, NestedAndOrOneFailingClause) {
@@ -643,7 +657,7 @@ TEST(LogicalMatchExpression, NestedAndOrOneFailingClause) {
" 'specifiedAs': {'qty': {'$lt': 10}},"
" 'reason': 'comparison failed',"
" 'consideredValue': 30}}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, NestedAndOrNorOneSuccessfulClause) {
@@ -666,7 +680,7 @@ TEST(LogicalMatchExpression, NestedAndOrNorOneSuccessfulClause) {
" 'specifiedAs': {'qty': {'$lt': 20}},"
" 'reason': 'comparison succeeded',"
" 'consideredValue': 15}}]}}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(LogicalMatchExpression, NestedAndOrNorNotOneFailingClause) {
@@ -691,7 +705,7 @@ TEST(LogicalMatchExpression, NestedAndOrNorNotOneFailingClause) {
" 'specifiedAs': {'qty': {'$lt': 20}},"
" 'reason': 'comparison failed',"
" 'consideredValue': 25}}}]}}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Miscellaneous operators.
// $exists
@@ -702,7 +716,7 @@ TEST(MiscellaneousMatchExpression, BasicExists) {
<< "$exists"
<< "specifiedAs" << query << "reason"
<< "path does not exist");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, NotExists) {
BSONObj query = BSON("a" << BSON("$exists" << false));
@@ -711,7 +725,7 @@ TEST(MiscellaneousMatchExpression, NotExists) {
<< "$exists"
<< "specifiedAs" << query << "reason"
<< "path does exist");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $type
TEST(MiscellaneousMatchExpression, BasicType) {
@@ -722,12 +736,12 @@ TEST(MiscellaneousMatchExpression, BasicType) {
BSONObj expectedError = BSON("operatorName"
<< "$type"
<< "specifiedAs" << query << "reason"
- << "no matching types found for specified typeset"
+ << "type did not match"
<< "consideredValue"
<< "one"
<< "consideredType"
<< "string");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, NotType) {
BSONObj failingClause = BSON("$type"
@@ -742,12 +756,12 @@ TEST(MiscellaneousMatchExpression, NotType) {
<< BSON("operatorName"
<< "$type"
<< "specifiedAs" << failingQuery << "reason"
- << "matching types found for specified typeset"
+ << "type did match"
<< "consideredValue"
<< "words"
<< "consideredType"
<< "string"));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, TypeMissingPath) {
BSONObj query = BSON("a" << BSON("$type"
@@ -757,7 +771,7 @@ TEST(MiscellaneousMatchExpression, TypeMissingPath) {
<< "$type"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, TypeImplicitArrayTraversal) {
BSONObj query = BSON("a" << BSON("$type"
@@ -768,7 +782,7 @@ TEST(MiscellaneousMatchExpression, TypeImplicitArrayTraversal) {
BSONObj expectedError = BSON("operatorName"
<< "$type"
<< "specifiedAs" << query << "reason"
- << "no matching types found for specified typeset"
+ << "type did not match"
<< "consideredValues"
<< BSON_ARRAY("x"
<< "y"
@@ -779,7 +793,7 @@ TEST(MiscellaneousMatchExpression, TypeImplicitArrayTraversal) {
<< "consideredTypes"
<< BSON_ARRAY("array"
<< "string"));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $expr
TEST(MiscellaneousMatchExpression, BasicExpr) {
@@ -791,7 +805,7 @@ TEST(MiscellaneousMatchExpression, BasicExpr) {
<< "specifiedAs" << query << "reason"
<< "$expr did not match"
<< "expressionResult" << false);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, NorExpr) {
BSONObj failingClause = BSON("$eq" << BSON_ARRAY("$a"
@@ -809,7 +823,7 @@ TEST(MiscellaneousMatchExpression, NorExpr) {
<< "specifiedAs" << failingQuery << "reason"
<< "$expr did match"
<< "expressionResult" << true))));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, ExprImplicitArrayTraversal) {
BSONObj query = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a"
@@ -820,7 +834,7 @@ TEST(MiscellaneousMatchExpression, ExprImplicitArrayTraversal) {
<< "specifiedAs" << query << "reason"
<< "$expr did not match"
<< "expressionResult" << false);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $mod
TEST(MiscellaneousMatchExpression, BasicMod) {
@@ -831,10 +845,10 @@ TEST(MiscellaneousMatchExpression, BasicMod) {
<< "specifiedAs" << query << "reason"
<< "$mod did not evaluate to expected remainder"
<< "consideredValue" << 2);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, NotMod) {
- BSONObj failingClause = BSON("$mod" << BSON_ARRAY(2 << 1));
+ BSONObj failingClause = BSON("$mod" << BSON_ARRAY(2 << 0));
BSONObj failingQuery = BSON("a" << failingClause);
BSONObj query = BSON("a" << BSON("$not" << failingClause));
BSONObj document = BSON("a" << 2);
@@ -846,7 +860,7 @@ TEST(MiscellaneousMatchExpression, NotMod) {
<< "specifiedAs" << failingQuery << "reason"
<< "$mod did evaluate to expected remainder"
<< "consideredValue" << 2));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, ModMissingPath) {
BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1)));
@@ -855,7 +869,7 @@ TEST(MiscellaneousMatchExpression, ModMissingPath) {
<< "$mod"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversal) {
BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1)));
@@ -866,7 +880,7 @@ TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversal) {
<< "$mod did not evaluate to expected remainder"
<< "consideredValues"
<< BSON_ARRAY(0 << 2 << 4 << BSON_ARRAY(0 << 2 << 4)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, ModNonNumeric) {
BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1)));
@@ -875,7 +889,7 @@ TEST(MiscellaneousMatchExpression, ModNonNumeric) {
BSONObj expectedError = BSON("operatorName"
<< "$mod"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredType"
<< "string"
<< "expectedTypes"
@@ -885,7 +899,7 @@ TEST(MiscellaneousMatchExpression, ModNonNumeric) {
<< "long")
<< "consideredValue"
<< "two");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalNonNumeric) {
BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1)));
@@ -895,7 +909,7 @@ TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalNonNumeric) {
BSONObj expectedError = BSON("operatorName"
<< "$mod"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredTypes"
<< BSON_ARRAY("array"
<< "string")
@@ -911,7 +925,7 @@ TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalNonNumeric) {
<< BSON_ARRAY("zero"
<< "two"
<< "four")));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalMixedTypes) {
BSONObj query = BSON("a" << BSON("$mod" << BSON_ARRAY(2 << 1)));
@@ -926,7 +940,7 @@ TEST(MiscellaneousMatchExpression, ModImplicitArrayTraversalMixedTypes) {
<< "four"
<< BSON_ARRAY(0 << "two"
<< "four")));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $regex
TEST(MiscellaneousMatchExpression, BasicRegex) {
@@ -940,15 +954,15 @@ TEST(MiscellaneousMatchExpression, BasicRegex) {
<< "regular expression did not match"
<< "consideredValue"
<< "one");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, NotRegex) {
- BSONObj failingClause = BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options"
+ BSONObj failingClause = BSON("$regex" << BSONRegEx("myRegex", "") << "$options"
<< "");
BSONObj failingQuery = BSON("a" << failingClause);
BSONObj query = BSON("a" << BSON("$not" << failingClause));
BSONObj document = BSON("a"
- << "one");
+ << "myRegex");
BSONObj expectedError = BSON("operatorName"
<< "$not"
<< "details"
@@ -957,8 +971,8 @@ TEST(MiscellaneousMatchExpression, NotRegex) {
<< "specifiedAs" << failingQuery << "reason"
<< "regular expression did match"
<< "consideredValue"
- << "one"));
- verifyGeneratedError(query, document, expectedError);
+ << "myRegex"));
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, RegexMissingPath) {
BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options"
@@ -969,7 +983,7 @@ TEST(MiscellaneousMatchExpression, RegexMissingPath) {
<< "$regex"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversal) {
BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options"
@@ -988,7 +1002,7 @@ TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversal) {
<< BSON_ARRAY("x"
<< "y"
<< "z")));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, RegexNonString) {
BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options"
@@ -997,7 +1011,7 @@ TEST(MiscellaneousMatchExpression, RegexNonString) {
BSONObj expectedError = BSON("operatorName"
<< "$regex"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredType"
<< "int"
<< "expectedTypes"
@@ -1005,7 +1019,7 @@ TEST(MiscellaneousMatchExpression, RegexNonString) {
<< "string"
<< "symbol")
<< "consideredValue" << 1);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalNonString) {
BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options"
@@ -1014,7 +1028,7 @@ TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalNonString) {
BSONObj expectedError = BSON("operatorName"
<< "$regex"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredTypes"
<< BSON_ARRAY("array"
<< "int")
@@ -1024,7 +1038,7 @@ TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalNonString) {
<< "symbol")
<< "consideredValues"
<< BSON_ARRAY(0 << 1 << 2 << BSON_ARRAY(0 << 1 << 2)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalMixedTypes) {
BSONObj query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options"
@@ -1036,7 +1050,7 @@ TEST(MiscellaneousMatchExpression, RegexImplicitArrayTraversalMixedTypes) {
<< "regular expression did not match"
<< "consideredValues"
<< BSON_ARRAY("x" << 1 << 2 << BSON_ARRAY("x" << 1 << 2)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllClear expression with numeric bitmask correctly generates a validation
@@ -1049,7 +1063,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearNumeric) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 7);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllClear expression with numeric bitmask correctly generates a validation
@@ -1066,7 +1080,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearNumericOnValueM
<< "reason"
<< "bitwise operator matched successfully"
<< "consideredValue" << 5));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllClear expression with position list correctly generates a validation
@@ -1079,7 +1093,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearPositionList) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 7);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllClear expression with BinData bitmask correctly generates a validation
@@ -1094,7 +1108,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearBinData) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 7);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllSet expression correctly generates a validation error.
@@ -1106,7 +1120,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllSetNumeric) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 5);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllSet expression with position list correctly generates a validation
@@ -1119,7 +1133,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllSetPositionList) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 5);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllSet expression with BinData bitmask correctly generates a validation
@@ -1134,7 +1148,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllSetBinData) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 5);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnyClear expression correctly generates a validation error.
@@ -1146,7 +1160,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearNumeric) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 7);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnyClear expression with position list correctly generates a validation
@@ -1159,7 +1173,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearPositionList) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 7);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnyClear expression with BinData bitmask correctly generates a validation
@@ -1174,7 +1188,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearBinData) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 7);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnySet expression correctly generates a validation error.
@@ -1186,7 +1200,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnySetNumeric) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 0);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnySet expression with position list correctly generates a validation
@@ -1199,7 +1213,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnySetPositionList) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 0);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnySet expression with BinData bitmask correctly generates a validation
@@ -1214,7 +1228,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnySetBinData) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValue" << 0);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAnyClear expression correctly generates a validation error on value type
@@ -1226,7 +1240,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearOnTypeMismatch)
BSONObj expectedError = BSON("operatorName"
<< "$bitsAnyClear"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredType"
<< "string"
<< "expectedTypes"
@@ -1237,7 +1251,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAnyClearOnTypeMismatch)
<< "long")
<< "consideredValue"
<< "someString");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $bitsAllClear expression with numeric bitmask correctly generates a validation
@@ -1251,7 +1265,7 @@ TEST(BitTestMatchExpression, GeneratesValidationErrorBitsAllClearOnValueArray) {
<< "specifiedAs" << query << "reason"
<< "bitwise operator failed to match"
<< "consideredValues" << BSON_ARRAY(7 << 3 << attributeValue));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $geoIntersects expression correctly generates a validation error.
@@ -1269,7 +1283,7 @@ TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersects) {
<< "specifiedAs" << query << "reason"
<< "none of considered geometries intersected the expression’s geometry"
<< "consideredValue" << point);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $geoIntersects expression correctly generates a validation error on unexpected
@@ -1293,7 +1307,7 @@ TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsOnValueMatch) {
<< "specifiedAs" << BSON("a" << subquery) << "reason"
<< "at least one of considered geometries intersected the expression’s geometry"
<< "consideredValue" << point));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $geoIntersects expression correctly generates a correct validation error on value
@@ -1309,14 +1323,14 @@ TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsOnTypeMismatch) {
BSONObj expectedError = BSON("operatorName"
<< "$geoIntersects"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredType"
<< "int"
<< "expectedTypes"
<< BSON_ARRAY("array"
<< "object")
<< "consideredValue" << 2);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $geoIntersects expression correctly generates a validation error when applied on an
@@ -1339,7 +1353,7 @@ TEST(GeoMatchExpression, GeneratesValidationErrorGeoIntersectsOnValueArray) {
<< "specifiedAs" << query << "reason"
<< "none of considered geometries intersected the expression’s geometry"
<< "consideredValues" << BSON_ARRAY(point1 << point2 << points));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $geoWithin expression correctly generates a validation error.
@@ -1357,7 +1371,7 @@ TEST(GeoMatchExpression, GeneratesValidationErrorGeoWithin) {
<< "specifiedAs" << query << "reason"
<< "none of considered geometries was contained within the expression’s geometry"
<< "consideredValue" << point);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Verifies that $geoWithin expression correctly generates an inverse validation error.
@@ -1379,7 +1393,7 @@ TEST(GeoMatchExpression, GeneratesValidationErrorForMatchGeoWithin) {
<< "at least one of considered geometries was contained "
"within the expression’s geometry"
<< "consideredValue" << point));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// Array operators.
@@ -1393,7 +1407,7 @@ TEST(ArrayMatchingMatchExpression, BasicSize) {
<< "specifiedAs" << query << "reason"
<< "array length was not equal to given size"
<< "consideredValue" << BSON_ARRAY(1 << 2 << 3));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, SizeNonArray) {
@@ -1402,13 +1416,13 @@ TEST(ArrayMatchingMatchExpression, SizeNonArray) {
BSONObj expectedError = BSON("operatorName"
<< "$size"
<< "specifiedAs" << query << "reason"
- << "type mismatch"
+ << "type did not match"
<< "consideredType"
<< "int"
<< "expectedType"
<< "array"
<< "consideredValue" << 3);
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
@@ -1419,10 +1433,9 @@ TEST(ArrayMatchingMatchExpression, SizeMissingPath) {
<< "$size"
<< "specifiedAs" << query << "reason"
<< "field was missing");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
-
TEST(ArrayMatchingMatchExpression, NotOverSize) {
BSONObj query = BSON("a" << BSON("$not" << BSON("$size" << 2)));
BSONObj document = BSON("a" << BSON_ARRAY(1 << 2));
@@ -1435,7 +1448,7 @@ TEST(ArrayMatchingMatchExpression, NotOverSize) {
<< "reason"
<< "array length was equal to given size"
<< "consideredValue" << BSON_ARRAY(1 << 2)));
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $all
@@ -1447,7 +1460,7 @@ TEST(ArrayMatchingMatchExpression, BasicAll) {
"'specifiedAs': {'a': {'$all': [1,2,3]}},"
"'reason': 'array did not contain all specified values',"
"'consideredValue': [1,2,4]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, AllRegex) {
@@ -1458,7 +1471,7 @@ TEST(ArrayMatchingMatchExpression, AllRegex) {
"'specifiedAs': {'a': {'$all': [/^a/,/^b/]}},"
"'reason': 'array did not contain all specified values',"
"'consideredValue': ['abc', 'cbc']}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, AllMissingPath) {
@@ -1468,7 +1481,7 @@ TEST(ArrayMatchingMatchExpression, AllMissingPath) {
"{'operatorName': '$all',"
"'specifiedAs': {'a': {'$all': [1,2,3]}},"
"'reason': 'field was missing'}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, AllNoValues) {
@@ -1478,7 +1491,7 @@ TEST(ArrayMatchingMatchExpression, AllNoValues) {
"{'operatorName': '$all',"
"'specifiedAs': {'a': {'$all': []}},"
"'reason': 'expression always evaluates to false'}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, NotOverAll) {
@@ -1491,7 +1504,7 @@ TEST(ArrayMatchingMatchExpression, NotOverAll) {
" 'specifiedAs': {'a': {'$all': [1,2,3]}},"
" 'reason': 'array did contain all specified values',"
" 'consideredValue': [1,2,3]}}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $elemMatch
@@ -1503,7 +1516,7 @@ TEST(ArrayMatchingMatchExpression, BasicElemMatchValue) {
"'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}},"
"'reason': 'array did not satisfy the child predicate',"
"'consideredValue': [10,11,12]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, ElemMatchValueMissingPath) {
@@ -1513,7 +1526,7 @@ TEST(ArrayMatchingMatchExpression, ElemMatchValueMissingPath) {
"{'operatorName': '$elemMatch',"
"'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}},"
"'reason': 'field was missing'}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, ElemMatchValueNonArray) {
@@ -1522,11 +1535,11 @@ TEST(ArrayMatchingMatchExpression, ElemMatchValueNonArray) {
BSONObj expectedError = fromjson(
"{'operatorName': '$elemMatch',"
"'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}},"
- "'reason': 'type mismatch',"
+ "'reason': 'type did not match',"
"'consideredType': 'int',"
"'expectedType': 'array',"
"'consideredValue': 5}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, NotOverElemMatchValue) {
@@ -1538,7 +1551,7 @@ TEST(ArrayMatchingMatchExpression, NotOverElemMatchValue) {
" 'specifiedAs': {'a':{'$elemMatch':{'$gt': 0,'$lt': 10}}},"
" 'reason': 'array did satisfy the child predicate',"
" 'consideredValue': [3,4,5]}}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, BasicElemMatchObject) {
@@ -1549,7 +1562,7 @@ TEST(ArrayMatchingMatchExpression, BasicElemMatchObject) {
"'specifiedAs': {'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}},"
"'reason': 'array did not satisfy the child predicate',"
"'consideredValue': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, ElemMatchObjectMissingPath) {
@@ -1559,7 +1572,7 @@ TEST(ArrayMatchingMatchExpression, ElemMatchObjectMissingPath) {
"{'operatorName': '$elemMatch',"
"'specifiedAs': {'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}},"
"'reason': 'field was missing'}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, ElemMatchObjectNonArray) {
@@ -1568,11 +1581,11 @@ TEST(ArrayMatchingMatchExpression, ElemMatchObjectNonArray) {
BSONObj expectedError = fromjson(
"{'operatorName': '$elemMatch',"
"'specifiedAs': {'a': {'$elemMatch': {'b': {'$gt': 0}, 'c': {'$lt': 0}}}},"
- "'reason': 'type mismatch',"
+ "'reason': 'type did not match',"
"'consideredType': 'string',"
"'expectedType': 'array',"
"'consideredValue': 'foo'}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, NestedElemMatchObject) {
@@ -1583,7 +1596,7 @@ TEST(ArrayMatchingMatchExpression, NestedElemMatchObject) {
"'specifiedAs': {'a': {'$elemMatch': {'b': {$elemMatch: {'c': {'$lt': 0}}}}}},"
"'reason': 'array did not satisfy the child predicate',"
"'consideredValue': [{'b': [{'c': [1,2,3]}, {'c': [4,5,6]}]}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
TEST(ArrayMatchingMatchExpression, NotOverElemMatchObject) {
@@ -1596,7 +1609,7 @@ TEST(ArrayMatchingMatchExpression, NotOverElemMatchObject) {
" 'specifiedAs': {'a':{'$elemMatch': {'b': {'$gte': 0}, 'c': {'$lt': 10}}}},"
" 'reason': 'array did satisfy the child predicate',"
" 'consideredValue': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
// $all and $elemMatch
@@ -1612,7 +1625,7 @@ TEST(ArrayMatchingMatchExpression, AllOverElemMatch) {
" [{'$elemMatch': {'b': {'$gte': 0}}}, {'$elemMatch': {'c': {'$lt': 0}}}]}},"
"'reason': 'array did not contain all specified values',"
"'consideredValue': [{'b': 0, 'c': 0}, {'b': 1, 'c': 1}]}");
- verifyGeneratedError(query, document, expectedError);
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
} // namespace
-} // namespace mongo
+} // namespace mongo::doc_validation_error
diff --git a/src/mongo/db/matcher/doc_validation_error_test.h b/src/mongo/db/matcher/doc_validation_error_test.h
new file mode 100644
index 00000000000..ba134a889fa
--- /dev/null
+++ b/src/mongo/db/matcher/doc_validation_error_test.h
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/db/matcher/doc_validation_error.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo::doc_validation_error {
+/**
+ * Utility function which parses a MatchExpression from 'query' and verifies that the error
+ * generated by the parsed MatchExpression and 'document' matches 'expectedError'.
+ */
+void verifyGeneratedError(const BSONObj& query,
+ const BSONObj& document,
+ const BSONObj& expectedError);
+} // namespace mongo::doc_validation_error \ No newline at end of file
diff --git a/src/mongo/db/matcher/doc_validation_util.cpp b/src/mongo/db/matcher/doc_validation_util.cpp
new file mode 100644
index 00000000000..92d85f466ad
--- /dev/null
+++ b/src/mongo/db/matcher/doc_validation_util.cpp
@@ -0,0 +1,62 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/matcher/doc_validation_util.h"
+
+namespace mongo::doc_validation_error {
+std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const std::string& operatorName,
+ const BSONObj& annotation) {
+ if (expCtx->isParsingCollectionValidator) {
+ return std::make_unique<MatchExpression::ErrorAnnotation>(operatorName, annotation);
+ } else {
+ return nullptr;
+ }
+}
+
+std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ MatchExpression::ErrorAnnotation::Mode mode) {
+ if (expCtx->isParsingCollectionValidator) {
+ return std::make_unique<MatchExpression::ErrorAnnotation>(mode);
+ } else {
+ return nullptr;
+ }
+}
+
+void annotateTreeToIgnoreForErrorDetails(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ MatchExpression* expr) {
+ expr->setErrorAnnotation(
+ createAnnotation(expCtx, MatchExpression::ErrorAnnotation::Mode::kIgnore));
+ for (const auto childExpr : *expr) {
+ annotateTreeToIgnoreForErrorDetails(expCtx, childExpr);
+ }
+}
+} // namespace mongo::doc_validation_error \ No newline at end of file
diff --git a/src/mongo/db/matcher/doc_validation_util.h b/src/mongo/db/matcher/doc_validation_util.h
new file mode 100644
index 00000000000..b8793c4d05b
--- /dev/null
+++ b/src/mongo/db/matcher/doc_validation_util.h
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/pipeline/expression_context.h"
+
+namespace mongo::doc_validation_error {
+/**
+ * Set of functions which create an ErrorAnnotation provided that a validator expression is being
+ * parsed.
+ */
+std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const std::string& operatorName,
+ const BSONObj& annotation);
+
+std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ MatchExpression::ErrorAnnotation::Mode mode);
+
+/**
+ * Utility which tags an entire tree with 'AnnotationMode::kIgnore'.
+ */
+void annotateTreeToIgnoreForErrorDetails(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ MatchExpression* expr);
+} // namespace mongo::doc_validation_error \ No newline at end of file
diff --git a/src/mongo/db/matcher/expression_always_boolean.h b/src/mongo/db/matcher/expression_always_boolean.h
index 3b8a7b5f647..74587158040 100644
--- a/src/mongo/db/matcher/expression_always_boolean.h
+++ b/src/mongo/db/matcher/expression_always_boolean.h
@@ -126,14 +126,15 @@ class AlwaysTrueMatchExpression final : public AlwaysBooleanMatchExpression {
public:
static constexpr StringData kName = "$alwaysTrue"_sd;
- AlwaysTrueMatchExpression() : AlwaysBooleanMatchExpression(MatchType::ALWAYS_TRUE, true) {}
+ AlwaysTrueMatchExpression(clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : AlwaysBooleanMatchExpression(MatchType::ALWAYS_TRUE, true, std::move(annotation)) {}
StringData name() const final {
return kName;
}
std::unique_ptr<MatchExpression> shallowClone() const final {
- return std::make_unique<AlwaysTrueMatchExpression>();
+ return std::make_unique<AlwaysTrueMatchExpression>(_errorAnnotation);
}
bool isTriviallyTrue() const final {
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 7dbe5dc0dc5..1077c1ef8ec 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -39,6 +39,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
+#include "mongo/db/matcher/doc_validation_util.h"
#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_array.h"
#include "mongo/db/matcher/expression_expr.h"
@@ -89,43 +90,6 @@ bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) {
return false;
}
-/**
- * Set of functions which create an ErrorAnnotation provided that a validator expression is being
- * parsed.
- */
-std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- const std::string& operatorName,
- const BSONObj& annotation) {
- if (expCtx->isParsingCollectionValidator) {
- return std::make_unique<MatchExpression::ErrorAnnotation>(operatorName, annotation);
- } else {
- return nullptr;
- }
-}
-
-std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- MatchExpression::ErrorAnnotation::Mode mode) {
- if (expCtx->isParsingCollectionValidator) {
- return std::make_unique<MatchExpression::ErrorAnnotation>(mode);
- } else {
- return nullptr;
- }
-}
-
-/**
- * Utility which tags an entire tree with 'AnnotationMode::kIgnore'.
- */
-void annotateTreeToIgnoreForErrorDetails(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- MatchExpression* expr) {
- expr->setErrorAnnotation(
- createAnnotation(expCtx, MatchExpression::ErrorAnnotation::Mode::kIgnore));
- for (const auto childExpr : *expr) {
- annotateTreeToIgnoreForErrorDetails(expCtx, childExpr);
- }
-}
-
} // namespace
namespace mongo {
@@ -283,7 +247,8 @@ StatusWithMatchExpression parse(const BSONObj& obj,
const ExtensionsCallback* extensionsCallback,
MatchExpressionParser::AllowedFeatureSet allowedFeatures,
DocumentParseLevel currentLevel) {
- auto root = std::make_unique<AndMatchExpression>(createAnnotation(expCtx, "$and", BSONObj()));
+ auto root = std::make_unique<AndMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, "$and", BSONObj()));
const DocumentParseLevel nextLevel = (currentLevel == DocumentParseLevel::kPredicateTopLevel)
? DocumentParseLevel::kUserDocumentTopLevel
@@ -339,13 +304,15 @@ StatusWithMatchExpression parse(const BSONObj& obj,
continue;
}
- auto eq = parseComparison(
- e.fieldNameStringData(),
- std::make_unique<EqualityMatchExpression>(
- e.fieldNameStringData(), e, createAnnotation(expCtx, "$eq", e.wrap())),
- e,
- expCtx,
- allowedFeatures);
+ auto eq =
+ parseComparison(e.fieldNameStringData(),
+ std::make_unique<EqualityMatchExpression>(
+ e.fieldNameStringData(),
+ e,
+ doc_validation_error::createAnnotation(expCtx, "$eq", e.wrap())),
+ e,
+ expCtx,
+ allowedFeatures);
if (!eq.isOK())
return eq;
@@ -518,7 +485,8 @@ StatusWithMatchExpression parseExpr(StringData name,
return {std::make_unique<ExprMatchExpression>(
std::move(elem),
expCtx,
- createAnnotation(expCtx, elem.fieldNameStringData().toString(), elem.wrap()))};
+ doc_validation_error::createAnnotation(
+ expCtx, elem.fieldNameStringData().toString(), elem.wrap()))};
}
StatusWithMatchExpression parseMOD(StringData name,
@@ -548,7 +516,7 @@ StatusWithMatchExpression parseMOD(StringData name,
name,
divisor.numberInt(),
remainder.numberInt(),
- createAnnotation(
+ doc_validation_error::createAnnotation(
expCtx, elem.fieldNameStringData().toString(), BSON(name << elem.wrap())))};
}
@@ -599,7 +567,10 @@ StatusWithMatchExpression parseRegexDocument(
}
return {std::make_unique<RegexMatchExpression>(
- name, regex, regexOptions, createAnnotation(expCtx, "$regex", BSON(name << doc)))};
+ name,
+ regex,
+ regexOptions,
+ doc_validation_error::createAnnotation(expCtx, "$regex", BSON(name << doc)))};
}
Status parseInExpression(InMatchExpression* inExpression,
@@ -642,7 +613,8 @@ StatusWithMatchExpression parseType(StringData name,
return {std::make_unique<T>(
name,
std::move(typeSet.getValue()),
- createAnnotation(expCtx, elt.fieldNameStringData().toString(), BSON(name << elt.wrap())))};
+ doc_validation_error::createAnnotation(
+ expCtx, elt.fieldNameStringData().toString(), BSON(name << elt.wrap())))};
}
/**
@@ -718,8 +690,8 @@ StatusWithMatchExpression parseBitTest(StringData name,
BSONElement e,
const boost::intrusive_ptr<ExpressionContext>& expCtx) {
std::unique_ptr<BitTestMatchExpression> bitTestMatchExpression;
- auto annotation =
- createAnnotation(expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()));
+ auto annotation = doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()));
if (e.type() == BSONType::Array) {
// Array of bit positions provided as value.
@@ -1148,7 +1120,7 @@ StatusWithMatchExpression parseGeo(StringData name,
name,
gq.release(),
section,
- createAnnotation(expCtx, operatorName, BSON(name << section)))};
+ doc_validation_error::createAnnotation(expCtx, operatorName, BSON(name << section)))};
} else {
invariant(PathAcceptingKeyword::GEO_NEAR == type);
@@ -1178,8 +1150,8 @@ StatusWithMatchExpression parseTreeTopLevel(
return {Status(ErrorCodes::BadValue, str::stream() << T::kName << " must be an array")};
}
- auto temp = std::make_unique<T>(
- createAnnotation(expCtx, elem.fieldNameStringData().toString(), BSONObj()));
+ auto temp = std::make_unique<T>(doc_validation_error::createAnnotation(
+ expCtx, elem.fieldNameStringData().toString(), BSONObj()));
auto arr = elem.Obj();
if (arr.isEmpty()) {
@@ -1242,9 +1214,10 @@ StatusWithMatchExpression parseElemMatch(StringData name,
auto emValueExpr = std::make_unique<ElemMatchValueMatchExpression>(
name,
- createAnnotation(expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
- annotateTreeToIgnoreForErrorDetails(expCtx, &theAnd);
+ doc_validation_error::annotateTreeToIgnoreForErrorDetails(expCtx, &theAnd);
for (size_t i = 0; i < theAnd.numChildren(); i++) {
emValueExpr->add(theAnd.getChild(i));
}
@@ -1271,12 +1244,13 @@ StatusWithMatchExpression parseElemMatch(StringData name,
return {Status(ErrorCodes::BadValue, "$elemMatch cannot contain $where expression")};
}
- annotateTreeToIgnoreForErrorDetails(expCtx, sub.get());
+ doc_validation_error::annotateTreeToIgnoreForErrorDetails(expCtx, sub.get());
return {std::make_unique<ElemMatchObjectMatchExpression>(
name,
sub.release(),
- createAnnotation(expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())))};
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())))};
}
StatusWithMatchExpression parseAll(StringData name,
@@ -1288,8 +1262,8 @@ StatusWithMatchExpression parseAll(StringData name,
return {Status(ErrorCodes::BadValue, "$all needs an array")};
auto arr = e.Obj();
- auto myAnd = std::make_unique<AndMatchExpression>(
- createAnnotation(expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
+ auto myAnd = std::make_unique<AndMatchExpression>(doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
BSONObjIterator i(arr);
if (arr.firstElement().type() == BSONType::Object &&
@@ -1317,7 +1291,8 @@ StatusWithMatchExpression parseAll(StringData name,
allowedFeatures);
if (!inner.isOK())
return inner;
- annotateTreeToIgnoreForErrorDetails(expCtx, inner.getValue().get());
+ doc_validation_error::annotateTreeToIgnoreForErrorDetails(expCtx,
+ inner.getValue().get());
myAnd->add(inner.getValue().release());
}
@@ -1329,22 +1304,22 @@ StatusWithMatchExpression parseAll(StringData name,
if (e.type() == BSONType::RegEx) {
auto expr = std::make_unique<RegexMatchExpression>(
- name, e, createAnnotation(expCtx, AnnotationMode::kIgnore));
+ name, e, doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
myAnd->add(expr.release());
} else if (e.type() == BSONType::Object &&
MatchExpressionParser::parsePathAcceptingKeyword(e.Obj().firstElement())) {
return {Status(ErrorCodes::BadValue, "no $ expressions in $all")};
} else {
auto expr = std::make_unique<EqualityMatchExpression>(
- name, e, createAnnotation(expCtx, AnnotationMode::kIgnore));
+ name, e, doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
expr->setCollator(expCtx->getCollator());
myAnd->add(expr.release());
}
}
if (myAnd->numChildren() == 0) {
- return {std::make_unique<AlwaysFalseMatchExpression>(
- createAnnotation(expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())))};
+ return {std::make_unique<AlwaysFalseMatchExpression>(doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())))};
}
return {std::move(myAnd)};
@@ -1423,7 +1398,8 @@ StatusWithMatchExpression parseNot(StringData name,
return {ErrorCodes::BadValue, "$not cannot be empty"};
}
- auto theAnd = std::make_unique<AndMatchExpression>(createAnnotation(expCtx, "$and", BSONObj()));
+ auto theAnd = std::make_unique<AndMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, "$and", BSONObj()));
auto parseStatus = parseSub(
name, notObject, theAnd.get(), expCtx, extensionsCallback, allowedFeatures, currentLevel);
if (!parseStatus.isOK()) {
@@ -1432,11 +1408,12 @@ StatusWithMatchExpression parseNot(StringData name,
// If the and has one child, it can be ignored when generating a document validation error.
if (theAnd->numChildren() == 1 && theAnd->getErrorAnnotation()) {
- theAnd->setErrorAnnotation(createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend));
+ theAnd->setErrorAnnotation(
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend));
}
- return {std::make_unique<NotMatchExpression>(theAnd.release(),
- createAnnotation(expCtx, "$not", BSONObj()))};
+ return {std::make_unique<NotMatchExpression>(
+ theAnd.release(), doc_validation_error::createAnnotation(expCtx, "$not", BSONObj()))};
}
StatusWithMatchExpression parseInternalSchemaBinDataSubType(StringData name, BSONElement e) {
@@ -1481,16 +1458,16 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
invariant(e);
if ("$eq"_sd == e.fieldNameStringData()) {
- return parseComparison(name,
- std::make_unique<EqualityMatchExpression>(
- name,
- e,
- createAnnotation(expCtx,
- e.fieldNameStringData().toString(),
- BSON(name << e.wrap()))),
- e,
- expCtx,
- allowedFeatures);
+ return parseComparison(
+ name,
+ std::make_unique<EqualityMatchExpression>(
+ name,
+ e,
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
}
if ("$not"_sd == e.fieldNameStringData()) {
@@ -1510,49 +1487,49 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
switch (*parseExpMatchType) {
case PathAcceptingKeyword::LESS_THAN:
- return parseComparison(name,
- std::make_unique<LTMatchExpression>(
- name,
- e,
- createAnnotation(expCtx,
- e.fieldNameStringData().toString(),
- BSON(name << e.wrap()))),
- e,
- expCtx,
- allowedFeatures);
+ return parseComparison(
+ name,
+ std::make_unique<LTMatchExpression>(
+ name,
+ e,
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::LESS_THAN_OR_EQUAL:
- return parseComparison(name,
- std::make_unique<LTEMatchExpression>(
- name,
- e,
- createAnnotation(expCtx,
- e.fieldNameStringData().toString(),
- BSON(name << e.wrap()))),
- e,
- expCtx,
- allowedFeatures);
+ return parseComparison(
+ name,
+ std::make_unique<LTEMatchExpression>(
+ name,
+ e,
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::GREATER_THAN:
- return parseComparison(name,
- std::make_unique<GTMatchExpression>(
- name,
- e,
- createAnnotation(expCtx,
- e.fieldNameStringData().toString(),
- BSON(name << e.wrap()))),
- e,
- expCtx,
- allowedFeatures);
+ return parseComparison(
+ name,
+ std::make_unique<GTMatchExpression>(
+ name,
+ e,
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::GREATER_THAN_OR_EQUAL:
- return parseComparison(name,
- std::make_unique<GTEMatchExpression>(
- name,
- e,
- createAnnotation(expCtx,
- e.fieldNameStringData().toString(),
- BSON(name << e.wrap()))),
- e,
- expCtx,
- allowedFeatures);
+ return parseComparison(
+ name,
+ std::make_unique<GTEMatchExpression>(
+ name,
+ e,
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::NOT_EQUAL: {
if (BSONType::RegEx == e.type()) {
// Just because $ne can be rewritten as the negation of an equality does not mean
@@ -1564,26 +1541,26 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
std::make_unique<EqualityMatchExpression>(
name,
e,
- createAnnotation(
+ doc_validation_error::createAnnotation(
expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
e,
expCtx,
allowedFeatures);
return {std::make_unique<NotMatchExpression>(
s.getValue().release(),
- createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
}
case PathAcceptingKeyword::EQUALITY:
- return parseComparison(name,
- std::make_unique<EqualityMatchExpression>(
- name,
- e,
- createAnnotation(expCtx,
- e.fieldNameStringData().toString(),
- BSON(name << e.wrap()))),
- e,
- expCtx,
- allowedFeatures);
+ return parseComparison(
+ name,
+ std::make_unique<EqualityMatchExpression>(
+ name,
+ e,
+ doc_validation_error::createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::IN_EXPR: {
if (e.type() != BSONType::Array) {
@@ -1591,7 +1568,7 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
}
auto temp = std::make_unique<InMatchExpression>(
name,
- createAnnotation(
+ doc_validation_error::createAnnotation(
expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
auto parseStatus = parseInExpression(temp.get(), e.Obj(), expCtx);
if (!parseStatus.isOK()) {
@@ -1606,14 +1583,15 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
}
auto temp = std::make_unique<InMatchExpression>(
name,
- createAnnotation(
+ doc_validation_error::createAnnotation(
expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
auto parseStatus = parseInExpression(temp.get(), e.Obj(), expCtx);
if (!parseStatus.isOK()) {
return parseStatus;
}
return {std::make_unique<NotMatchExpression>(
- temp.release(), createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
+ temp.release(),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
}
case PathAcceptingKeyword::SIZE: {
@@ -1643,7 +1621,7 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
return {std::make_unique<SizeMatchExpression>(
name,
size,
- createAnnotation(
+ doc_validation_error::createAnnotation(
expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())))};
}
@@ -1654,14 +1632,15 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
auto existsExpr = std::make_unique<ExistsMatchExpression>(
name,
- createAnnotation(
+ doc_validation_error::createAnnotation(
expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
if (e.trueValue()) {
return {std::move(existsExpr)};
}
return {std::make_unique<NotMatchExpression>(
- existsExpr.release(), createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
+ existsExpr.release(),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
}
case PathAcceptingKeyword::TYPE:
@@ -1760,7 +1739,9 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
}
return {std::make_unique<InternalSchemaObjectMatchExpression>(
- name, std::move(parsedSubObjExpr.getValue()))};
+ name,
+ std::move(parsedSubObjExpr.getValue()),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
}
case PathAcceptingKeyword::INTERNAL_SCHEMA_UNIQUE_ITEMS: {
diff --git a/src/mongo/db/matcher/expression_type.h b/src/mongo/db/matcher/expression_type.h
index 13839e47190..437390c4139 100644
--- a/src/mongo/db/matcher/expression_type.h
+++ b/src/mongo/db/matcher/expression_type.h
@@ -197,11 +197,14 @@ class InternalSchemaBinDataSubTypeExpression final : public LeafMatchExpression
public:
static constexpr StringData kName = "$_internalSchemaBinDataSubType"_sd;
- InternalSchemaBinDataSubTypeExpression(StringData path, BinDataType binDataSubType)
+ InternalSchemaBinDataSubTypeExpression(StringData path,
+ BinDataType binDataSubType,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
: LeafMatchExpression(MatchExpression::INTERNAL_SCHEMA_BIN_DATA_SUBTYPE,
path,
ElementPath::LeafArrayBehavior::kNoTraversal,
- ElementPath::NonLeafArrayBehavior::kTraverse),
+ ElementPath::NonLeafArrayBehavior::kTraverse,
+ std::move(annotation)),
_binDataSubType(binDataSubType) {}
StringData name() const {
@@ -214,8 +217,8 @@ public:
}
std::unique_ptr<MatchExpression> shallowClone() const final {
- auto expr =
- std::make_unique<InternalSchemaBinDataSubTypeExpression>(path(), _binDataSubType);
+ auto expr = std::make_unique<InternalSchemaBinDataSubTypeExpression>(
+ path(), _binDataSubType, _errorAnnotation);
if (getTag()) {
expr->setTag(getTag()->clone());
}
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp
index 22940d101fa..ec5a0943fad 100644
--- a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp
@@ -36,11 +36,14 @@ namespace mongo {
constexpr StringData InternalSchemaObjectMatchExpression::kName;
InternalSchemaObjectMatchExpression::InternalSchemaObjectMatchExpression(
- StringData path, std::unique_ptr<MatchExpression> expr)
+ StringData path,
+ std::unique_ptr<MatchExpression> expr,
+ clonable_ptr<ErrorAnnotation> annotation)
: PathMatchExpression(INTERNAL_SCHEMA_OBJECT_MATCH,
path,
ElementPath::LeafArrayBehavior::kNoTraversal,
- ElementPath::NonLeafArrayBehavior::kTraverse),
+ ElementPath::NonLeafArrayBehavior::kTraverse,
+ std::move(annotation)),
_sub(std::move(expr)) {}
bool InternalSchemaObjectMatchExpression::matchesSingleElement(const BSONElement& elem,
@@ -75,8 +78,8 @@ bool InternalSchemaObjectMatchExpression::equivalent(const MatchExpression* othe
}
std::unique_ptr<MatchExpression> InternalSchemaObjectMatchExpression::shallowClone() const {
- auto clone =
- std::make_unique<InternalSchemaObjectMatchExpression>(path(), _sub->shallowClone());
+ auto clone = std::make_unique<InternalSchemaObjectMatchExpression>(
+ path(), _sub->shallowClone(), _errorAnnotation);
if (getTag()) {
clone->setTag(getTag()->clone());
}
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h
index e3b40eb9329..dee55fbb5c5 100644
--- a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h
@@ -39,7 +39,9 @@ class InternalSchemaObjectMatchExpression final : public PathMatchExpression {
public:
static constexpr StringData kName = "$_internalSchemaObjectMatch"_sd;
- InternalSchemaObjectMatchExpression(StringData path, std::unique_ptr<MatchExpression> expr);
+ InternalSchemaObjectMatchExpression(StringData path,
+ std::unique_ptr<MatchExpression> expr,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr);
bool matchesSingleElement(const BSONElement& elem, MatchDetails* details = nullptr) const final;
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index a3fec7baf1d..9e11324c732 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -38,6 +38,7 @@
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/unordered_fields_bsonelement_comparator.h"
#include "mongo/db/commands/feature_compatibility_version_documentation.h"
+#include "mongo/db/matcher/doc_validation_util.h"
#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/matcher_type_set.h"
@@ -68,6 +69,8 @@ namespace mongo {
using PatternSchema = InternalSchemaAllowedPropertiesMatchExpression::PatternSchema;
using Pattern = InternalSchemaAllowedPropertiesMatchExpression::Pattern;
using AllowedFeatureSet = MatchExpressionParser::AllowedFeatureSet;
+using ErrorAnnotation = MatchExpression::ErrorAnnotation;
+using AnnotationMode = ErrorAnnotation::Mode;
namespace {
@@ -114,10 +117,12 @@ StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>&
* enforce this restriction, should the types match (e.g. $_internalSchemaMaxItems). 'statedType' is
* a parsed representation of the JSON Schema type keyword which is in effect.
*/
-std::unique_ptr<MatchExpression> makeRestriction(const MatcherTypeSet& restrictionType,
- StringData path,
- std::unique_ptr<MatchExpression> restrictionExpr,
- InternalSchemaTypeExpression* statedType) {
+std::unique_ptr<MatchExpression> makeRestriction(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const MatcherTypeSet& restrictionType,
+ StringData path,
+ std::unique_ptr<MatchExpression> restrictionExpr,
+ InternalSchemaTypeExpression* statedType) {
invariant(restrictionType.isSingleType());
if (statedType && statedType->typeSet().isSingleType()) {
@@ -132,8 +137,9 @@ std::unique_ptr<MatchExpression> makeRestriction(const MatcherTypeSet& restricti
return restrictionExpr;
} else {
// This restriction doesn't take any effect, since the type of the schema is different
- // from the type to which this retriction applies.
- return std::make_unique<AlwaysTrueMatchExpression>();
+ // from the type to which this restriction applies.
+ return std::make_unique<AlwaysTrueMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
}
}
@@ -143,11 +149,17 @@ std::unique_ptr<MatchExpression> makeRestriction(const MatcherTypeSet& restricti
//
// We need to do this because restriction keywords do not apply when a field is either not
// present or of a different type.
- auto typeExpr = std::make_unique<InternalSchemaTypeExpression>(path, restrictionType);
+ auto typeExpr = std::make_unique<InternalSchemaTypeExpression>(
+ path,
+ restrictionType,
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
- auto notExpr = std::make_unique<NotMatchExpression>(typeExpr.release());
+ auto notExpr = std::make_unique<NotMatchExpression>(
+ typeExpr.release(),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
- auto orExpr = std::make_unique<OrMatchExpression>();
+ auto orExpr = std::make_unique<OrMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend));
orExpr->add(notExpr.release());
orExpr->add(restrictionExpr.release());
@@ -155,6 +167,7 @@ std::unique_ptr<MatchExpression> makeRestriction(const MatcherTypeSet& restricti
}
StatusWith<std::unique_ptr<InternalSchemaTypeExpression>> parseType(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
StringData keywordName,
BSONElement typeElt,
@@ -171,13 +184,17 @@ StatusWith<std::unique_ptr<InternalSchemaTypeExpression>> parseType(
<< "' must name at least one type")};
}
- auto typeExpr =
- std::make_unique<InternalSchemaTypeExpression>(path, std::move(typeSet.getValue()));
+ auto typeExpr = std::make_unique<InternalSchemaTypeExpression>(
+ path,
+ std::move(typeSet.getValue()),
+ doc_validation_error::createAnnotation(
+ expCtx, typeElt.fieldNameStringData().toString(), typeElt.wrap()));
return {std::move(typeExpr)};
}
-StatusWithMatchExpression parseMaximum(StringData path,
+StatusWithMatchExpression parseMaximum(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringData path,
BSONElement maximum,
InternalSchemaTypeExpression* typeExpr,
bool isExclusiveMaximum) {
@@ -188,24 +205,36 @@ StatusWithMatchExpression parseMaximum(StringData path,
<< "' must be a number")};
}
+ clonable_ptr<ErrorAnnotation> annotation;
+ if (isExclusiveMaximum) {
+ annotation =
+ doc_validation_error::createAnnotation(expCtx,
+ maximum.fieldNameStringData().toString(),
+ BSON(maximum << "exclusiveMaximum" << true));
+ } else {
+ annotation = doc_validation_error::createAnnotation(
+ expCtx, maximum.fieldNameStringData().toString(), maximum.wrap());
+ }
+
if (path.empty()) {
// This restriction has no effect in a top-level schema, since we only store objects.
- return {std::make_unique<AlwaysTrueMatchExpression>()};
+ return {std::make_unique<AlwaysTrueMatchExpression>(std::move(annotation))};
}
std::unique_ptr<ComparisonMatchExpression> expr;
if (isExclusiveMaximum) {
- expr = std::make_unique<LTMatchExpression>(path, maximum);
+ expr = std::make_unique<LTMatchExpression>(path, maximum, std::move(annotation));
} else {
- expr = std::make_unique<LTEMatchExpression>(path, maximum);
+ expr = std::make_unique<LTEMatchExpression>(path, maximum, std::move(annotation));
}
MatcherTypeSet restrictionType;
restrictionType.allNumbers = true;
- return makeRestriction(restrictionType, path, std::move(expr), typeExpr);
+ return makeRestriction(expCtx, restrictionType, path, std::move(expr), typeExpr);
}
-StatusWithMatchExpression parseMinimum(StringData path,
+StatusWithMatchExpression parseMinimum(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringData path,
BSONElement minimum,
InternalSchemaTypeExpression* typeExpr,
bool isExclusiveMinimum) {
@@ -216,28 +245,40 @@ StatusWithMatchExpression parseMinimum(StringData path,
<< "' must be a number")};
}
+ clonable_ptr<ErrorAnnotation> annotation;
+ if (isExclusiveMinimum) {
+ annotation =
+ doc_validation_error::createAnnotation(expCtx,
+ minimum.fieldNameStringData().toString(),
+ BSON(minimum << "exclusiveMinimum" << true));
+ } else {
+ annotation = doc_validation_error::createAnnotation(
+ expCtx, minimum.fieldNameStringData().toString(), minimum.wrap());
+ }
+
if (path.empty()) {
// This restriction has no effect in a top-level schema, since we only store objects.
- return {std::make_unique<AlwaysTrueMatchExpression>()};
+ return {std::make_unique<AlwaysTrueMatchExpression>(std::move(annotation))};
}
std::unique_ptr<ComparisonMatchExpression> expr;
if (isExclusiveMinimum) {
- expr = std::make_unique<GTMatchExpression>(path, minimum);
+ expr = std::make_unique<GTMatchExpression>(path, minimum, std::move(annotation));
} else {
- expr = std::make_unique<GTEMatchExpression>(path, minimum);
+ expr = std::make_unique<GTEMatchExpression>(path, minimum, std::move(annotation));
}
MatcherTypeSet restrictionType;
restrictionType.allNumbers = true;
- return makeRestriction(restrictionType, path, std::move(expr), typeExpr);
+ return makeRestriction(expCtx, restrictionType, path, std::move(expr), typeExpr);
}
/**
* Parses length-related keywords that expect a nonnegative long as an argument.
*/
template <class T>
-StatusWithMatchExpression parseLength(StringData path,
+StatusWithMatchExpression parseLength(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringData path,
BSONElement length,
InternalSchemaTypeExpression* typeExpr,
BSONType restrictionType) {
@@ -251,10 +292,11 @@ StatusWithMatchExpression parseLength(StringData path,
}
auto expr = std::make_unique<T>(path, parsedLength.getValue());
- return makeRestriction(restrictionType, path, std::move(expr), typeExpr);
+ return makeRestriction(expCtx, restrictionType, path, std::move(expr), typeExpr);
}
-StatusWithMatchExpression parsePattern(StringData path,
+StatusWithMatchExpression parsePattern(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringData path,
BSONElement pattern,
InternalSchemaTypeExpression* typeExpr) {
if (pattern.type() != BSONType::String) {
@@ -272,10 +314,11 @@ StatusWithMatchExpression parsePattern(StringData path,
constexpr auto emptyFlags = "";
auto expr = std::make_unique<RegexMatchExpression>(path, pattern.valueStringData(), emptyFlags);
- return makeRestriction(BSONType::String, path, std::move(expr), typeExpr);
+ return makeRestriction(expCtx, BSONType::String, path, std::move(expr), typeExpr);
}
-StatusWithMatchExpression parseMultipleOf(StringData path,
+StatusWithMatchExpression parseMultipleOf(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringData path,
BSONElement multipleOf,
InternalSchemaTypeExpression* typeExpr) {
if (!multipleOf.isNumber()) {
@@ -300,7 +343,7 @@ StatusWithMatchExpression parseMultipleOf(StringData path,
MatcherTypeSet restrictionType;
restrictionType.allNumbers = true;
- return makeRestriction(restrictionType, path, std::move(expr), typeExpr);
+ return makeRestriction(expCtx, restrictionType, path, std::move(expr), typeExpr);
}
template <class T>
@@ -438,7 +481,8 @@ StatusWith<StringDataSet> parseRequired(BSONElement requiredElt) {
* Given the already-parsed set of required properties, returns a MatchExpression which ensures that
* those properties exist. Returns a parsing error if the translation fails.
*/
-StatusWithMatchExpression translateRequired(const StringDataSet& requiredProperties,
+StatusWithMatchExpression translateRequired(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const StringDataSet& requiredProperties,
StringData path,
InternalSchemaTypeExpression* typeExpr) {
auto andExpr = std::make_unique<AndMatchExpression>();
@@ -458,7 +502,7 @@ StatusWithMatchExpression translateRequired(const StringDataSet& requiredPropert
auto objectMatch =
std::make_unique<InternalSchemaObjectMatchExpression>(path, std::move(andExpr));
- return makeRestriction(BSONType::Object, path, std::move(objectMatch), typeExpr);
+ return makeRestriction(expCtx, BSONType::Object, path, std::move(objectMatch), typeExpr);
}
StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionContext>& expCtx,
@@ -476,7 +520,8 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
}
auto propertiesObj = propertiesElt.embeddedObject();
- auto andExpr = std::make_unique<AndMatchExpression>();
+ auto andExpr = std::make_unique<AndMatchExpression>(doc_validation_error::createAnnotation(
+ expCtx, propertiesElt.fieldNameStringData().toString(), BSONObj()));
for (auto&& property : propertiesObj) {
if (property.type() != BSONType::Object) {
return {ErrorCodes::TypeMismatch,
@@ -493,6 +538,8 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
return nestedSchemaMatch.getStatus();
}
+ nestedSchemaMatch.getValue()->setErrorAnnotation(doc_validation_error::createAnnotation(
+ expCtx, "", BSON("propertyName" << property.fieldNameStringData().toString())));
if (requiredProperties.find(property.fieldNameStringData()) != requiredProperties.end()) {
// The field name for which we created the nested schema is a required property. This
// property must exist and therefore must match 'nestedSchemaMatch'.
@@ -500,12 +547,16 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
} else {
// This property either must not exist or must match the nested schema. Therefore, we
// generate the match expression (OR (NOT (EXISTS)) <nestedSchemaMatch>).
- auto existsExpr =
- std::make_unique<ExistsMatchExpression>(property.fieldNameStringData());
+ auto existsExpr = std::make_unique<ExistsMatchExpression>(
+ property.fieldNameStringData(),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
- auto notExpr = std::make_unique<NotMatchExpression>(existsExpr.release());
+ auto notExpr = std::make_unique<NotMatchExpression>(
+ existsExpr.release(),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnore));
- auto orExpr = std::make_unique<OrMatchExpression>();
+ auto orExpr = std::make_unique<OrMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend));
orExpr->add(notExpr.release());
orExpr->add(nestedSchemaMatch.getValue().release());
@@ -519,10 +570,12 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
return {std::move(andExpr)};
}
- auto objectMatch =
- std::make_unique<InternalSchemaObjectMatchExpression>(path, std::move(andExpr));
+ auto objectMatch = std::make_unique<InternalSchemaObjectMatchExpression>(
+ path,
+ std::move(andExpr),
+ doc_validation_error::createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend));
- return makeRestriction(BSONType::Object, path, std::move(objectMatch), typeExpr);
+ return makeRestriction(expCtx, BSONType::Object, path, std::move(objectMatch), typeExpr);
}
StatusWith<std::vector<PatternSchema>> parsePatternProperties(
@@ -665,14 +718,15 @@ StatusWithMatchExpression parseAllowedProperties(
auto objectMatch = std::make_unique<InternalSchemaObjectMatchExpression>(
path, std::move(allowedPropertiesExpr));
- return makeRestriction(BSONType::Object, path, std::move(objectMatch), typeExpr);
+ return makeRestriction(expCtx, BSONType::Object, path, std::move(objectMatch), typeExpr);
}
/**
* Parses 'minProperties' and 'maxProperties' JSON Schema keywords.
*/
template <class T>
-StatusWithMatchExpression parseNumProperties(StringData path,
+StatusWithMatchExpression parseNumProperties(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringData path,
BSONElement numProperties,
InternalSchemaTypeExpression* typeExpr) {
auto parsedNumProps = numProperties.parseIntegerElementToNonNegativeLong();
@@ -689,7 +743,7 @@ StatusWithMatchExpression parseNumProperties(StringData path,
auto objectMatch = std::make_unique<InternalSchemaObjectMatchExpression>(path, std::move(expr));
- return makeRestriction(BSONType::Object, path, std::move(objectMatch), typeExpr);
+ return makeRestriction(expCtx, BSONType::Object, path, std::move(objectMatch), typeExpr);
}
StatusWithMatchExpression makeDependencyExistsClause(StringData path, StringData dependencyName) {
@@ -825,7 +879,8 @@ StatusWithMatchExpression parseDependencies(const boost::intrusive_ptr<Expressio
return {std::move(andExpr)};
}
-StatusWithMatchExpression parseUniqueItems(BSONElement uniqueItemsElt,
+StatusWithMatchExpression parseUniqueItems(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ BSONElement uniqueItemsElt,
StringData path,
InternalSchemaTypeExpression* typeExpr) {
if (!uniqueItemsElt.isBoolean()) {
@@ -837,7 +892,7 @@ StatusWithMatchExpression parseUniqueItems(BSONElement uniqueItemsElt,
return {std::make_unique<AlwaysTrueMatchExpression>()};
} else if (uniqueItemsElt.boolean()) {
auto uniqueItemsExpr = std::make_unique<InternalSchemaUniqueItemsMatchExpression>(path);
- return makeRestriction(BSONType::Array, path, std::move(uniqueItemsExpr), typeExpr);
+ return makeRestriction(expCtx, BSONType::Array, path, std::move(uniqueItemsExpr), typeExpr);
}
return {std::make_unique<AlwaysTrueMatchExpression>()};
@@ -894,7 +949,8 @@ StatusWith<boost::optional<long long>> parseItems(
andExpr->add(std::make_unique<AlwaysTrueMatchExpression>().release());
} else {
andExpr->add(
- makeRestriction(BSONType::Array, path, std::move(andExprForSubschemas), typeExpr)
+ makeRestriction(
+ expCtx, BSONType::Array, path, std::move(andExprForSubschemas), typeExpr)
.release());
}
} else if (itemsElt.type() == BSONType::Object) {
@@ -919,8 +975,9 @@ StatusWith<boost::optional<long long>> parseItems(
auto allElemMatch =
std::make_unique<InternalSchemaAllElemMatchFromIndexMatchExpression>(
path, startIndexForItems, std::move(exprWithPlaceholder));
- andExpr->add(makeRestriction(BSONType::Array, path, std::move(allElemMatch), typeExpr)
- .release());
+ andExpr->add(
+ makeRestriction(expCtx, BSONType::Array, path, std::move(allElemMatch), typeExpr)
+ .release());
}
} else {
return {ErrorCodes::TypeMismatch,
@@ -976,8 +1033,9 @@ Status parseAdditionalItems(const boost::intrusive_ptr<ExpressionContext>& expCt
auto allElemMatch =
std::make_unique<InternalSchemaAllElemMatchFromIndexMatchExpression>(
path, *startIndexForAdditionalItems, std::move(otherwiseExpr));
- andExpr->add(makeRestriction(BSONType::Array, path, std::move(allElemMatch), typeExpr)
- .release());
+ andExpr->add(
+ makeRestriction(expCtx, BSONType::Array, path, std::move(allElemMatch), typeExpr)
+ .release());
}
}
return Status::OK();
@@ -1106,7 +1164,7 @@ Status translateArrayKeywords(StringMap<BSONElement>& keywordMap,
AndMatchExpression* andExpr) {
if (auto minItemsElt = keywordMap[JSONSchemaParser::kSchemaMinItemsKeyword]) {
auto minItemsExpr = parseLength<InternalSchemaMinItemsMatchExpression>(
- path, minItemsElt, typeExpr, BSONType::Array);
+ expCtx, path, minItemsElt, typeExpr, BSONType::Array);
if (!minItemsExpr.isOK()) {
return minItemsExpr.getStatus();
}
@@ -1115,7 +1173,7 @@ Status translateArrayKeywords(StringMap<BSONElement>& keywordMap,
if (auto maxItemsElt = keywordMap[JSONSchemaParser::kSchemaMaxItemsKeyword]) {
auto maxItemsExpr = parseLength<InternalSchemaMaxItemsMatchExpression>(
- path, maxItemsElt, typeExpr, BSONType::Array);
+ expCtx, path, maxItemsElt, typeExpr, BSONType::Array);
if (!maxItemsExpr.isOK()) {
return maxItemsExpr.getStatus();
}
@@ -1123,7 +1181,7 @@ Status translateArrayKeywords(StringMap<BSONElement>& keywordMap,
}
if (auto uniqueItemsElt = keywordMap[JSONSchemaParser::kSchemaUniqueItemsKeyword]) {
- auto uniqueItemsExpr = parseUniqueItems(uniqueItemsElt, path, typeExpr);
+ auto uniqueItemsExpr = parseUniqueItems(expCtx, uniqueItemsElt, path, typeExpr);
if (!uniqueItemsExpr.isOK()) {
return uniqueItemsExpr.getStatus();
}
@@ -1200,7 +1258,7 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
}
if (!requiredProperties.empty()) {
- auto requiredExpr = translateRequired(requiredProperties, path, typeExpr);
+ auto requiredExpr = translateRequired(expCtx, requiredProperties, path, typeExpr);
if (!requiredExpr.isOK()) {
return requiredExpr.getStatus();
}
@@ -1209,7 +1267,7 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
if (auto minPropertiesElt = keywordMap[JSONSchemaParser::kSchemaMinPropertiesKeyword]) {
auto minPropExpr = parseNumProperties<InternalSchemaMinPropertiesMatchExpression>(
- path, minPropertiesElt, typeExpr);
+ expCtx, path, minPropertiesElt, typeExpr);
if (!minPropExpr.isOK()) {
return minPropExpr.getStatus();
}
@@ -1218,7 +1276,7 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
if (auto maxPropertiesElt = keywordMap[JSONSchemaParser::kSchemaMaxPropertiesKeyword]) {
auto maxPropExpr = parseNumProperties<InternalSchemaMaxPropertiesMatchExpression>(
- path, maxPropertiesElt, typeExpr);
+ expCtx, path, maxPropertiesElt, typeExpr);
if (!maxPropExpr.isOK()) {
return maxPropExpr.getStatus();
}
@@ -1251,13 +1309,14 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
* - pattern
* - multipleOf
*/
-Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
+Status translateScalarKeywords(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ StringMap<BSONElement>& keywordMap,
StringData path,
InternalSchemaTypeExpression* typeExpr,
AndMatchExpression* andExpr) {
// String keywords.
if (auto patternElt = keywordMap[JSONSchemaParser::kSchemaPatternKeyword]) {
- auto patternExpr = parsePattern(path, patternElt, typeExpr);
+ auto patternExpr = parsePattern(expCtx, path, patternElt, typeExpr);
if (!patternExpr.isOK()) {
return patternExpr.getStatus();
}
@@ -1266,7 +1325,7 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
if (auto maxLengthElt = keywordMap[JSONSchemaParser::kSchemaMaxLengthKeyword]) {
auto maxLengthExpr = parseLength<InternalSchemaMaxLengthMatchExpression>(
- path, maxLengthElt, typeExpr, BSONType::String);
+ expCtx, path, maxLengthElt, typeExpr, BSONType::String);
if (!maxLengthExpr.isOK()) {
return maxLengthExpr.getStatus();
}
@@ -1275,7 +1334,7 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
if (auto minLengthElt = keywordMap[JSONSchemaParser::kSchemaMinLengthKeyword]) {
auto minLengthExpr = parseLength<InternalSchemaMinLengthMatchExpression>(
- path, minLengthElt, typeExpr, BSONType::String);
+ expCtx, path, minLengthElt, typeExpr, BSONType::String);
if (!minLengthExpr.isOK()) {
return minLengthExpr.getStatus();
}
@@ -1284,7 +1343,7 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
// Numeric keywords.
if (auto multipleOfElt = keywordMap[JSONSchemaParser::kSchemaMultipleOfKeyword]) {
- auto multipleOfExpr = parseMultipleOf(path, multipleOfElt, typeExpr);
+ auto multipleOfExpr = parseMultipleOf(expCtx, path, multipleOfElt, typeExpr);
if (!multipleOfExpr.isOK()) {
return multipleOfExpr.getStatus();
}
@@ -1304,7 +1363,7 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
isExclusiveMaximum = exclusiveMaximumElt.boolean();
}
}
- auto maxExpr = parseMaximum(path, maximumElt, typeExpr, isExclusiveMaximum);
+ auto maxExpr = parseMaximum(expCtx, path, maximumElt, typeExpr, isExclusiveMaximum);
if (!maxExpr.isOK()) {
return maxExpr.getStatus();
}
@@ -1330,7 +1389,7 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
isExclusiveMinimum = exclusiveMinimumElt.boolean();
}
}
- auto minExpr = parseMinimum(path, minimumElt, typeExpr, isExclusiveMinimum);
+ auto minExpr = parseMinimum(expCtx, path, minimumElt, typeExpr, isExclusiveMinimum);
if (!minExpr.isOK()) {
return minExpr.getStatus();
}
@@ -1543,7 +1602,8 @@ StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>&
std::unique_ptr<InternalSchemaTypeExpression> typeExpr;
if (typeElem) {
- auto parsed = parseType(path,
+ auto parsed = parseType(expCtx,
+ path,
JSONSchemaParser::kSchemaTypeKeyword,
typeElem,
MatcherTypeSet::findJsonSchemaTypeAlias);
@@ -1552,8 +1612,11 @@ StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>&
}
typeExpr = std::move(parsed.getValue());
} else if (bsonTypeElem) {
- auto parseBsonTypeResult = parseType(
- path, JSONSchemaParser::kSchemaBsonTypeKeyword, bsonTypeElem, findBSONTypeAlias);
+ auto parseBsonTypeResult = parseType(expCtx,
+ path,
+ JSONSchemaParser::kSchemaBsonTypeKeyword,
+ bsonTypeElem,
+ findBSONTypeAlias);
if (!parseBsonTypeResult.isOK()) {
return parseBsonTypeResult.getStatus();
}
@@ -1565,10 +1628,11 @@ StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>&
std::make_unique<InternalSchemaTypeExpression>(path, MatcherTypeSet(BSONType::BinData));
}
- auto andExpr = std::make_unique<AndMatchExpression>();
+ auto andExpr = std::make_unique<AndMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, "$jsonSchema", BSONObj()));
auto translationStatus =
- translateScalarKeywords(keywordMap, path, typeExpr.get(), andExpr.get());
+ translateScalarKeywords(expCtx, keywordMap, path, typeExpr.get(), andExpr.get());
if (!translationStatus.isOK()) {
return translationStatus;
}
@@ -1610,7 +1674,8 @@ StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>&
if (path.empty() && typeExpr && !typeExpr->typeSet().hasType(BSONType::Object)) {
// This is a top-level schema which requires that the type is something other than
// "object". Since we only know how to store objects, this schema matches nothing.
- return {std::make_unique<AlwaysFalseMatchExpression>()};
+ return {std::make_unique<AlwaysFalseMatchExpression>(doc_validation_error::createAnnotation(
+ expCtx, "$jsonSchema", BSON("$jsonSchema" << schema)))};
}
if (!path.empty() && typeExpr) {