summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
authorMihai Andrei <mihai.andrei@10gen.com>2020-10-01 16:50:21 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-10-21 20:09:04 +0000
commit69c03caa427e1d31b81e7bc98a56d9eb7e6ab06b (patch)
tree0618b69d836eb2573f795120c6a0ce906311ac17 /src/mongo/db/matcher
parent306f04029f01d3bfb7bc565fb2139862daa57397 (diff)
downloadmongo-69c03caa427e1d31b81e7bc98a56d9eb7e6ab06b.tar.gz
SERVER-49446 Implement validation error generation for required keyword
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/doc_validation_error.cpp224
-rw-r--r--src/mongo/db/matcher/doc_validation_error.h4
-rw-r--r--src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp769
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp28
4 files changed, 912 insertions, 113 deletions
diff --git a/src/mongo/db/matcher/doc_validation_error.cpp b/src/mongo/db/matcher/doc_validation_error.cpp
index 913b6b0906a..68c8e88df6c 100644
--- a/src/mongo/db/matcher/doc_validation_error.cpp
+++ b/src/mongo/db/matcher/doc_validation_error.cpp
@@ -899,7 +899,8 @@ public:
kNormalReason,
kInvertedReason,
&kExpectedTypes,
- LeafArrayBehavior::kNoTraversal);
+ LeafArrayBehavior::kNoTraversal,
+ true /* isJsonSchemaKeyword */);
}
void visit(const InternalSchemaMatchArrayIndexMatchExpression* expr) final {
_context->pushNewFrame(*expr);
@@ -950,9 +951,11 @@ public:
void visit(const InternalSchemaObjectMatchExpression* expr) final {
// This node should never be responsible for generating an error directly.
invariant(expr->getErrorAnnotation()->mode != AnnotationMode::kGenerateError);
+
// As part of pushing a new frame onto the stack, the runtime state may be set to
// 'kNoError' if 'expr' matches the current document.
_context->pushNewFrame(*expr);
+
// Only attempt to find a subdocument if this node failed to match.
if (_context->getCurrentRuntimeState() != RuntimeState::kNoError) {
ElementPath path(expr->path(), LeafArrayBehavior::kNoTraversal);
@@ -984,12 +987,13 @@ public:
}
void visit(const InternalSchemaRootDocEqMatchExpression* expr) final {}
void visit(const InternalSchemaTypeExpression* expr) final {
- generateTypeError(expr, LeafArrayBehavior::kNoTraversal);
+ generateTypeError(*expr, LeafArrayBehavior::kNoTraversal, true /* isJsonSchemaKeyword */);
}
void visit(const InternalSchemaUniqueItemsMatchExpression* expr) final {
static constexpr auto normalReason = "found a duplicate item";
_context->pushNewFrame(*expr);
- if (auto attributeValue = getValueForArrayKeywordExpressionIfShouldGenerateError(*expr)) {
+ if (auto attributeValue =
+ getValueForKeywordExpressionIfShouldGenerateError(*expr, {BSONType::Array})) {
appendErrorDetails(*expr);
appendErrorReason(normalReason, "");
auto attributeValueAsArray = BSONArray(attributeValue.embeddedObject());
@@ -1070,7 +1074,8 @@ public:
if (expr->getErrorAnnotation()->tag == "enum") {
static constexpr auto kNormalReason = "value was not found in enum";
static constexpr auto kInvertedReason = "value was found in enum";
- generateLogicalLeafError(*expr, kNormalReason, kInvertedReason);
+ generateLogicalLeafError(
+ *expr, kNormalReason, kInvertedReason, true /* isJsonSchemaKeyword */);
} else {
preVisitTreeOperator(expr);
// An OR needs its children to call 'matches' in an inverted context to discern which
@@ -1085,7 +1090,13 @@ public:
static constexpr auto kInvertedReason = "regular expression did match";
static const std::set<BSONType> kExpectedTypes{
BSONType::String, BSONType::Symbol, BSONType::RegEx};
- generatePathError(*expr, kNormalReason, kInvertedReason, &kExpectedTypes);
+ bool isJsonSchemaKeyword = expr->getErrorAnnotation()->tag == "pattern";
+ generatePathError(*expr,
+ kNormalReason,
+ kInvertedReason,
+ &kExpectedTypes,
+ LeafArrayBehavior::kTraverseOmitArray,
+ isJsonSchemaKeyword);
}
void visit(const SizeMatchExpression* expr) final {
static constexpr auto kNormalReason = "array length was not equal to given size";
@@ -1104,7 +1115,7 @@ public:
// traversed array elements as considered values since, when we have predicate "{$type:
// 'array'}" and a field is an array, that is a match. Therefore we use
// LeafArrayBehavior::kTraverseOmitArray as the traversal behavior.
- generateTypeError(expr, LeafArrayBehavior::kTraverseOmitArray);
+ generateTypeError(*expr, LeafArrayBehavior::kTraverseOmitArray);
}
void visit(const WhereMatchExpression* expr) final {
MONGO_UNREACHABLE;
@@ -1339,9 +1350,19 @@ private:
const std::string& normalReason,
const std::string& invertedReason,
const std::set<BSONType>* expectedTypes = nullptr,
- LeafArrayBehavior leafArrayBehavior = LeafArrayBehavior::kTraverseOmitArray) {
+ LeafArrayBehavior leafArrayBehavior = LeafArrayBehavior::kTraverseOmitArray,
+ bool isJsonSchemaKeyword = false) {
_context->pushNewFrame(expr);
if (_context->shouldGenerateError(expr)) {
+ // If this is a jsonSchema keyword, we must verify that expr's path exists and the
+ // value of the path matches the expected type. Otherwise, this node will not be
+ // responsible for an error; either the parent of expr will not match, or another
+ // node in the tree will generate an appropriate error.
+ if (isJsonSchemaKeyword &&
+ !getValueForKeywordExpressionIfShouldGenerateError(expr, *expectedTypes)) {
+ _context->setCurrentRuntimeState(RuntimeState::kNoError);
+ return;
+ }
appendErrorDetails(expr);
auto arr = createValuesArray(expr.path(), leafArrayBehavior);
appendMissingField(arr);
@@ -1354,7 +1375,23 @@ private:
void generateComparisonError(const ComparisonMatchExpression* expr) {
static constexpr auto kNormalReason = "comparison failed";
static constexpr auto kInvertedReason = "comparison succeeded";
- generatePathError(*expr, kNormalReason, kInvertedReason);
+ // Determine whether 'expr' represents a jsonSchema minimum/maximum keyword.
+ static const std::set<std::string> kJsonSchemaKeywords = {"minimum", "maximum"};
+ if (kJsonSchemaKeywords.find(expr->getErrorAnnotation()->tag) !=
+ kJsonSchemaKeywords.end()) {
+ static const std::set<BSONType> kExpectedTypes{BSONType::NumberLong,
+ BSONType::NumberDouble,
+ BSONType::NumberDecimal,
+ BSONType::NumberInt};
+ generatePathError(*expr,
+ kNormalReason,
+ kInvertedReason,
+ &kExpectedTypes,
+ LeafArrayBehavior::kNoTraversal,
+ true /* isJsonSchemaKeyword */);
+ } else {
+ generatePathError(*expr, kNormalReason, kInvertedReason);
+ }
}
void generateElemMatchError(const ArrayMatchingMatchExpression* expr) {
@@ -1372,13 +1409,21 @@ private:
}
template <class T>
- void generateTypeError(const TypeMatchExpressionBase<T>* expr, LeafArrayBehavior behavior) {
- _context->pushNewFrame(*expr);
+ void generateTypeError(const TypeMatchExpressionBase<T>& expr,
+ LeafArrayBehavior behavior,
+ bool isJsonSchemaKeyword = false) {
+ _context->pushNewFrame(expr);
static constexpr auto kNormalReason = "type did not match";
static constexpr auto kInvertedReason = "type did match";
- if (_context->shouldGenerateError(*expr)) {
- appendErrorDetails(*expr);
- auto arr = createValuesArray(expr->path(), behavior);
+ if (_context->shouldGenerateError(expr)) {
+ auto arr = createValuesArray(expr.path(), behavior);
+ // If the path of 'expr' is missing and this is a jsonSchema keyword, then this node
+ // should not generate an error.
+ if (isJsonSchemaKeyword && !arr) {
+ _context->setCurrentRuntimeState(RuntimeState::kNoError);
+ return;
+ }
+ appendErrorDetails(expr);
appendMissingField(arr);
appendErrorReason(kNormalReason, kInvertedReason);
appendConsideredValues(arr);
@@ -1408,11 +1453,18 @@ private:
_context->pushNewFrame(*expr);
if (_context->shouldGenerateError(*expr)) {
auto annotation = expr->getErrorAnnotation();
+ auto tag = annotation->tag;
// Only append the operator name if it will produce an object error corresponding to
// a user-facing operator.
- if (!_context->producesArray(*expr))
+ if (tag[0] != '_')
appendOperatorName(*expr);
- _context->getCurrentObjBuilder().appendElements(annotation->annotation);
+ auto& builder = _context->getCurrentObjBuilder();
+ // Append the keyword specification when 'expr' corresponds to the 'required' keyword.
+ if (tag == "required") {
+ appendSpecifiedAs(*annotation, &builder);
+ } else {
+ _context->getCurrentObjBuilder().appendElements(annotation->annotation);
+ }
}
}
/**
@@ -1421,7 +1473,8 @@ private:
*/
void generateLogicalLeafError(const ListOfMatchExpression& expr,
const std::string& normalReason,
- const std::string& invertedReason) {
+ const std::string& invertedReason,
+ bool isJsonSchemaKeyword = false) {
_context->pushNewFrame(expr);
if (_context->shouldGenerateError(expr)) {
// $all with no children should not translate to an 'AndMatchExpression' and 'enum'
@@ -1430,6 +1483,13 @@ private:
appendErrorDetails(expr);
auto childExpr = expr.getChild(0);
auto arr = createValuesArray(childExpr->path(), LeafArrayBehavior::kNoTraversal);
+
+ // If this is a jsonSchema keyword and the value doesn't exist, then this node will
+ // not generate an error.
+ if (isJsonSchemaKeyword && !arr) {
+ _context->setCurrentRuntimeState(RuntimeState::kNoError);
+ return;
+ }
appendMissingField(arr);
appendErrorReason(normalReason, invertedReason);
appendConsideredValues(arr);
@@ -1463,19 +1523,23 @@ private:
static constexpr auto kNormalReason = "specified string length was not satisfied";
static constexpr auto kInvertedReason = "specified string length was satisfied";
static const std::set<BSONType> expectedTypes{BSONType::String};
- generatePathError(
- expr, kNormalReason, kInvertedReason, &expectedTypes, LeafArrayBehavior::kNoTraversal);
+ generatePathError(expr,
+ kNormalReason,
+ kInvertedReason,
+ &expectedTypes,
+ LeafArrayBehavior::kNoTraversal,
+ true /* isJsonSchemaKeyword */);
}
/**
- * Determines if a validation error should be generated for a JSON Schema array keyword match
- * expression 'expr' given the current document validation context and returns the array 'expr'
- * expression applies over. If a validation error should not be generated, then the
- * End-Of-Object (EOO) value is returned. If a validation error should be generated, then the
- * type of the value of the returned BSONElement is always an array.
+ * Determines if a validation error should be generated for a JsonSchema keyword MatchExpression
+ * 'expr' given the current document validation context. Returns the element 'expr' applies
+ * over if the found element matches one of the 'expectedTypes'. By returning a non-empty
+ * element, this indicates that 'expr' should generate an error. Returns End-Of-Object (EOO)
+ * value otherwise, which indicates that 'expr' should not generate an error.
*/
- BSONElement getValueForArrayKeywordExpressionIfShouldGenerateError(
- const MatchExpression& expr) {
+ BSONElement getValueForKeywordExpressionIfShouldGenerateError(
+ const MatchExpression& expr, const std::set<BSONType>& expectedTypes) {
if (!_context->shouldGenerateError(expr)) {
return {};
}
@@ -1489,39 +1553,40 @@ private:
expr.path(), LeafArrayBehavior::kNoTraversal, NonLeafArrayBehavior::kNoTraversal);
auto attributeValue = getValueAt(path);
- // If attribute value is either not present or is not an array, do not generate an error,
- // since related match expressions do that instead. There are 4 cases of how an array
- // keyword can be defined in combination with 'required' and 'type' keywords (in the
- // explanation below parameter 'expr' corresponds to '(array keyword match expression)'):
+ // If attribute value is either not present or does not match the types in 'expectedTypes',
+ // do not generate an error, since related match expressions do that instead. There are 4
+ // cases of how a keyword can be defined in combination with 'required' and 'type' keywords
+ // (in the explanation below parameter 'expr' corresponds to '(keyword match expression)'):
//
- // 1) 'required' is not present, {type: 'array'} is not present. In this case the expression
- // tree corresponds to ((array keyword match expression) OR NOT (is array)) OR (NOT
- // (attribute exists)). This tree can fail to match only if the attribute is present and is
- // an array.
+ // 1) 'required' is not present, {type: <expectedTypes>} is not present. In this case the
+ // expression tree corresponds to ((keyword match expression) OR NOT (matches type)) OR
+ // (NOT (attribute exists)). This tree can fail to match only if the attribute is present
+ // and matches a type in 'expectedTypes'.
//
- // 2) 'required' is not present, {type: 'array'} is present. In this case the expression
- // tree corresponds to ((array keyword match expression) AND (is array)) OR (NOT (attribute
- // exists)). If the input is an attribute of a non-array type, then both (array keyword
- // match expression) and (is array) expressions fail to match and are asked to contribute to
- // the validation error. We expect only (is array) expression, not an (array keyword match
- // expression), to report a type mismatch, since otherwise the error would contain redundant
- // elements.
+ // 2) 'required' is not present, {type: <expectedTypes>} is present. In this case the
+ // expression tree corresponds to ((keyword match expression) AND (matches type)) OR (NOT
+ // (attribute exists)). If the input is an element of a non-matching type, then both
+ // (keyword match expression) and (matches type) expressions fail to match and are asked
+ // to contribute to the validation error. We expect only (matches type) expression, not a
+ // (keyword match expression), to report a type mismatch, since otherwise the error would
+ // contain redundant elements.
//
- // 3) 'required' is present, {type: 'array'} is not present. In this case the expression
- // tree corresponds to ((array keyword match expression) OR NOT (is array)) AND (attribute
- // exists). This tree can fail to match if the attribute is present and is an array, and
- // fails to match when the attribute is not present. In the latter case expression part
- // ((array keyword match expression) OR NOT (is array)) matches and (array keyword match
- // expression) is not asked to contribute to the error.
+ // 3) 'required' is present, {type: <expectedTypes>} is not present. In this case the
+ // expression tree corresponds to ((keyword match expression) OR NOT (matches type)) AND
+ // (attribute exists). This tree can fail to match if the attribute is present and
+ // matches a type, and fails to match when the attribute is not present. In the latter
+ // case, the expression part ((keyword match expression) OR NOT (matches type)) matches and
+ // (keyword match expression) is not asked to contribute to the error.
//
- // 4) 'required' is present, {type: 'array'} is present. In this case the expression tree
- // corresponds to ((array keyword match expression) AND (is array)) AND (attribute exists).
- // This tree can fail to match if the attribute is present and is an array, and fails to
- // match when the attribute is not present or is not an array. In the case when the
+ // 4) 'required' is present, {type: <expectedTypes>} is present. In this case the expression
+ // tree corresponds to ((keyword match expression) AND (matches type)) AND (attribute
+ // exists). This tree can fail to match if the attribute is present and matches a type,
+ // or if the attribute is not present or does not match a type. In the case when the
// attribute is not present all parts of the expression fail to match and are asked to
// contribute to the error, but we expect only (attribute exists) expression to contribute,
- // since otherwise the error would contain redundant elements.
- return (attributeValue.type() == BSONType::Array) ? attributeValue : BSONElement{};
+ // since otherwise the error would contain redundant elements.
+ return expectedTypes.find(attributeValue.type()) != expectedTypes.end() ? attributeValue
+ : BSONElement{};
}
/**
@@ -1531,7 +1596,8 @@ private:
const InternalSchemaNumArrayItemsMatchExpression* expr) {
static constexpr auto normalReason = "array did not match specified length";
_context->pushNewFrame(*expr);
- if (auto attributeValue = getValueForArrayKeywordExpressionIfShouldGenerateError(*expr)) {
+ if (auto attributeValue =
+ getValueForKeywordExpressionIfShouldGenerateError(*expr, {BSONType::Array})) {
appendErrorDetails(*expr);
appendErrorReason(normalReason, "");
auto attributeValueAsArray = BSONArray(attributeValue.embeddedObject());
@@ -1548,7 +1614,8 @@ private:
const InternalSchemaAllElemMatchFromIndexMatchExpression* expr) {
static constexpr auto normalReason = "found additional items";
_context->pushNewFrame(*expr);
- if (auto attributeValue = getValueForArrayKeywordExpressionIfShouldGenerateError(*expr)) {
+ if (auto attributeValue =
+ getValueForKeywordExpressionIfShouldGenerateError(*expr, {BSONType::Array})) {
appendErrorDetails(*expr);
appendErrorReason(normalReason, "");
appendAdditionalItems(BSONArray(attributeValue.embeddedObject()), expr->startIndex());
@@ -1575,7 +1642,8 @@ private:
}
invariant(expr.getChild(0)->matchType() ==
MatchExpression::MatchType::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX);
- if (getValueForArrayKeywordExpressionIfShouldGenerateError(*expr.getChild(0))) {
+ if (getValueForKeywordExpressionIfShouldGenerateError(*expr.getChild(0),
+ {BSONType::Array})) {
appendOperatorName(expr);
// Since the "items" keyword set to an array of subschemas logically behaves as "$and",
@@ -1615,7 +1683,8 @@ private:
const std::string& normalReason,
const std::string& invertedReason) {
_context->pushNewFrame(*expr);
- if (auto attributeValue = getValueForArrayKeywordExpressionIfShouldGenerateError(*expr)) {
+ if (auto attributeValue =
+ getValueForKeywordExpressionIfShouldGenerateError(*expr, {BSONType::Array})) {
appendOperatorName(*expr);
appendErrorReason(normalReason, invertedReason);
auto failingElement =
@@ -1819,6 +1888,8 @@ public:
{"_propertiesExistList", {"", ""}},
{"items", {"details", ""}},
{"dependencies", {"failingDependencies", ""}},
+ {"required", {"missingProperties", ""}},
+ {"_property", {"details", ""}},
{"", {"details", ""}}};
auto detailsStringPair = detailsStringMap.find(tag);
invariant(detailsStringPair != detailsStringMap.end());
@@ -2055,6 +2126,19 @@ private:
void postVisitTreeOperator(const ListOfMatchExpression* expr,
const std::string& detailsString) {
finishLogicalOperatorChildError(expr, _context);
+ // If this node represents a 'properties' keyword or an individual property schema (denoted
+ // by '_property') and the current array builder has no elements, then this node will not
+ // contribute to the error output. As an example, consider the document {} against the
+ // following schema: {required: ['a'], properties: {'a': {minimum: 2, type: 'int'}}}.
+ // Though the AND representing 'properties' will fail and as such, is expected to construct
+ // an error, its children will not contribute to the generated error. As such, we
+ // retroactively mark an AND representing a 'properties' keyword or an individual
+ // 'property' as 'RuntimeState::kNoError' if no error details were produced.
+ auto tag = expr->getErrorAnnotation()->tag;
+ if (_context->shouldGenerateError(*expr) && (tag == "properties" || tag == "_property") &&
+ _context->getCurrentArrayBuilder().arrSize() == 0) {
+ _context->setCurrentRuntimeState(RuntimeState::kNoError);
+ }
// Append the result of the current array builder to the current object builder under the
// field name 'detailsString' unless this node produces an array (i.e. in the case of a
// subschema).
@@ -2069,18 +2153,18 @@ private:
};
/**
- * Returns true if each node in the tree rooted at 'validatorExpr' has an error annotation, false
- * otherwise.
+ * Verifies that each node in the tree rooted at 'validatorExpr' has an error annotation.
*/
-bool hasErrorAnnotations(const MatchExpression& validatorExpr) {
- if (!validatorExpr.getErrorAnnotation())
- return false;
+void assertHasErrorAnnotations(const MatchExpression& validatorExpr) {
+ uassert(4994600,
+ str::stream() << "Cannot generate validation error details: no annotation found for "
+ "expression "
+ << validatorExpr.toString(),
+ validatorExpr.getErrorAnnotation());
for (const auto childExpr : validatorExpr) {
- if (!childExpr || !hasErrorAnnotations(*childExpr)) {
- return false;
- }
+ if (childExpr)
+ assertHasErrorAnnotations(*childExpr);
}
- return true;
}
/**
@@ -2134,13 +2218,9 @@ BSONObj generateErrorHelper(const MatchExpression& validatorExpr,
ValidationErrorInVisitor inVisitor{&context};
ValidationErrorPostVisitor postVisitor{&context};
- // TODO SERVER-49446: Once all nodes have ErrorAnnotations, this check should be converted to an
- // invariant check that all nodes have an annotation. Also add an invariant to the
- // DocumentValidationFailureInfo constructor to check that it is initialized with a non-empty
- // object.
- if (!hasErrorAnnotations(validatorExpr)) {
- return BSONObj();
- }
+ // Verify that all nodes have error annotations.
+ assertHasErrorAnnotations(validatorExpr);
+
MatchExpressionWalker walker{&preVisitor, &inVisitor, &postVisitor};
tree_walker::walk<true, MatchExpression>(&validatorExpr, &walker);
diff --git a/src/mongo/db/matcher/doc_validation_error.h b/src/mongo/db/matcher/doc_validation_error.h
index e610138de21..62d86289cd6 100644
--- a/src/mongo/db/matcher/doc_validation_error.h
+++ b/src/mongo/db/matcher/doc_validation_error.h
@@ -44,7 +44,9 @@ class DocumentValidationFailureInfo final : public ErrorExtraInfo {
public:
static constexpr auto code = ErrorCodes::DocumentValidationFailure;
static std::shared_ptr<const ErrorExtraInfo> parse(const BSONObj& obj);
- explicit DocumentValidationFailureInfo(const BSONObj& err) : _details(err.getOwned()) {}
+ explicit DocumentValidationFailureInfo(const BSONObj& err) : _details(err.getOwned()) {
+ invariant(!err.isEmpty());
+ }
const BSONObj& getDetails() const;
void serialize(BSONObjBuilder* bob) const override;
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
index d4b391fdfac..7712ab9827a 100644
--- a/src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp
+++ b/src/mongo/db/matcher/doc_validation_error_json_schema_test.cpp
@@ -51,7 +51,170 @@ TEST(JSONSchemaValidation, BasicProperties) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+// minimum
+TEST(JSONSchemaValidation, MinimumNonNumericWithType) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'properties': {'a': {'type': 'number','minimum': 1}}}}");
+ BSONObj document = fromjson("{'a': 'foo'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {operatorName: 'properties', 'propertiesNotSatisfied': ["
+ " {propertyName: 'a', 'details': ["
+ " {'operatorName': 'type', "
+ " 'specifiedAs': { 'type': 'number' }, "
+ " 'reason': 'type did not match', "
+ " 'consideredValue': 'foo', "
+ " 'consideredType': 'string'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinimumNonNumericWithBSONType) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'properties': {'a': {'bsonType': 'int','minimum': 1}}}}");
+ BSONObj document = fromjson("{'a': 1.1}");
+ // The value satisfies the minimum keyword, but not the bsonType keyword.
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {operatorName: 'properties', 'propertiesNotSatisfied': ["
+ " {propertyName: 'a', 'details': ["
+ " {'operatorName': 'bsonType', "
+ " 'specifiedAs': {'bsonType': 'int'}, "
+ " 'reason': 'type did not match', "
+ " 'consideredValue': 1.1, "
+ " 'consideredType': 'double'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinimumWithRequiredNoTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'minimum': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'minimum'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinimumWithRequiredAndTypeMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'],"
+ " 'properties': "
+ " {a: {'type': 'number','minimum': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'minimum', nor 'type'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinimumRequiredWithTypeAndScalarFailedMinimum) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'properties': {'a': {minimum: 2, 'bsonType': 'int'}}, 'required': ['a','b']}}");
+ BSONObj document = fromjson("{a: 1, b: 1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'minimum',"
+ " 'specifiedAs': {'minimum' : 2},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 1}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+// maximum
+TEST(JSONSchemaValidation, MaximumNonNumericWithType) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'properties': {'a': {'type': 'number','maximum': 1}}}}");
+ BSONObj document = fromjson("{'a': 'foo'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {operatorName: 'properties', 'propertiesNotSatisfied': ["
+ " {propertyName: 'a', 'details': ["
+ " {'operatorName': 'type', "
+ " 'specifiedAs': { 'type': 'number' }, "
+ " 'reason': 'type did not match', "
+ " 'consideredValue': 'foo', "
+ " 'consideredType': 'string'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaximumNonNumericWithBSONType) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'properties': {'a': {'bsonType': 'int','maximum': 1}}}}");
+ BSONObj document = fromjson("{'a': 0.9}");
+ // The value satisfies the maximum keyword, but not the bsonType keyword.
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {operatorName: 'properties', 'propertiesNotSatisfied': ["
+ " {propertyName: 'a', 'details': ["
+ " {'operatorName': 'bsonType', "
+ " 'specifiedAs': {'bsonType': 'int'}, "
+ " 'reason': 'type did not match', "
+ " 'consideredValue': 0.9, "
+ " 'consideredType': 'double'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaximumWithRequiredNoTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'maximum': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'maximum'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaximumWithRequiredAndTypeMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'],"
+ " 'properties': "
+ " {a: {'type': 'number','maximum': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'maximum', nor 'type'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaximumRequiredWithTypeAndScalarFailedMaximum) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'properties': {'a': {maximum: 2, 'bsonType': 'int'}}, 'required': ['a','b']}}");
+ BSONObj document = fromjson("{a: 3, b: 1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'maximum',"
+ " 'specifiedAs': {'maximum' : 2},"
+ " 'reason': 'comparison failed',"
+ " 'consideredValue': 3}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
// Exclusive minimum/maximum
+
TEST(JSONSchemaValidation, ExclusiveMinimum) {
BSONObj query = fromjson(
"{'$jsonSchema': {'properties': {'a': {'minimum': 1, 'exclusiveMinimum': true}}}}}");
@@ -130,7 +293,6 @@ TEST(JSONSchemaValidation, ExclusiveMaximum) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
-// TODO: Update the test when SERVER-50859 is implemented.
TEST(JSONSchemaValidation, MaximumTypeNumberWithEmptyArray) {
BSONObj query =
fromjson("{'$jsonSchema': {'properties': {'a': {'maximum': 1, type: 'number'}}}}");
@@ -140,12 +302,8 @@ TEST(JSONSchemaValidation, MaximumTypeNumberWithEmptyArray) {
" 'schemaRulesNotSatisfied': ["
" {'operatorName': 'properties',"
" 'propertiesNotSatisfied': ["
- " {'propertyName': 'a', 'details': "
- " [{'operatorName': 'maximum',"
- " 'specifiedAs': {'maximum' : 1},"
- " 'reason': 'comparison failed',"
- " 'consideredValues': []},"
- " {'operatorName': 'type',"
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
" 'specifiedAs': {'type': 'number'},"
" 'reason': 'type did not match',"
" 'consideredValue': [],"
@@ -543,12 +701,6 @@ TEST(JSONSchemaValidation, MinLengthNonString) {
"{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
" {'operatorName': 'properties', 'propertiesNotSatisfied': ["
" {'propertyName': 'a', 'details': ["
- " {'operatorName': 'minLength', "
- " 'specifiedAs': { 'minLength': 4 }, "
- " 'reason': 'type did not match', "
- " 'consideredType': 'int', "
- " 'expectedType': 'string', "
- " 'consideredValue': 1 }, "
" {'operatorName': 'type', "
" 'specifiedAs': { 'type': 'string' }, "
" 'reason': 'type did not match', "
@@ -557,6 +709,54 @@ TEST(JSONSchemaValidation, MinLengthNonString) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, MinLengthRequiredNoTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'minLength': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'minLength'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinLengthWithRequiredAndTypeMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'],"
+ " 'properties': "
+ " {a: {'type': 'string','minLength': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'minimum', nor 'type'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MinLengthRequiredWithTypeAndScalarFailedMinLength) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'properties': {'a': "
+ " {minLength: 2, 'bsonType': 'string'}}, 'required': ['a','b']}}");
+ BSONObj document = fromjson("{a: 'a', b: 1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'minLength',"
+ " 'specifiedAs': {'minLength' : 2},"
+ " 'reason': 'specified string length was not satisfied',"
+ " 'consideredValue': 'a'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, MinLengthNested) {
BSONObj query = fromjson(
"{'$jsonSchema': {"
@@ -637,6 +837,54 @@ TEST(JSONSchemaValidation, BasicMaxLength) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, MaxLengthRequiredNoTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'minLength': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'minLength'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaxLengthWithRequiredAndTypeMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'],"
+ " 'properties': "
+ " {a: {'type': 'string','minLength': 1}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'maxLength', nor 'type'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MaxLengthRequiredWithTypeAndScalarFailedMaxLength) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'properties': {'a': "
+ " {maxLength: 2, 'bsonType': 'string'}}, 'required': ['a','b']}}");
+ BSONObj document = fromjson("{a: 'aaaa', b: 1}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties',"
+ " 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': "
+ " [{'operatorName': 'maxLength',"
+ " 'specifiedAs': {'maxLength' : 2},"
+ " 'reason': 'specified string length was not satisfied',"
+ " 'consideredValue': 'aaaa'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, MaxLengthNoExplicitType) {
BSONObj query = fromjson("{'$jsonSchema': {'properties': {'a': {'maxLength': 4}}}}");
BSONObj document = fromjson("{'a': 'foo, bar, baz'}");
@@ -660,12 +908,6 @@ TEST(JSONSchemaValidation, MaxLengthNonString) {
"{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
" {'operatorName': 'properties', 'propertiesNotSatisfied': ["
" {'propertyName': 'a', 'details': ["
- " {'operatorName': 'maxLength', "
- " 'specifiedAs': { 'maxLength': 4 }, "
- " 'reason': 'type did not match', "
- " 'consideredType': 'int', "
- " 'expectedType': 'string', "
- " 'consideredValue': 1 }, "
" {'operatorName': 'type', "
" 'specifiedAs': { 'type': 'string' }, "
" 'reason': 'type did not match', "
@@ -769,20 +1011,14 @@ TEST(JSONSchemaValidation, PatternNoExplicitType) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
-TEST(JSONSchemaValidation, PatternNonString) {
+TEST(JSONSchemaValidation, PatternNonStringWithType) {
BSONObj query =
fromjson("{'$jsonSchema': {'properties': {'a': {'type': 'string','pattern': '^S'}}}}");
BSONObj document = fromjson("{'a': 1}");
BSONObj expectedError = fromjson(
"{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
- " { operatorName: 'properties', 'propertiesNotSatisfied': ["
+ " {operatorName: 'properties', 'propertiesNotSatisfied': ["
" {propertyName: 'a', 'details': ["
- " {'operatorName': 'pattern',"
- " 'specifiedAs': { pattern: '^S' }, "
- " 'reason': 'type did not match', "
- " 'consideredType': 'int', "
- " 'expectedTypes': [ 'regex', 'string', 'symbol' ], "
- " 'consideredValue': 1 }, "
" {'operatorName': 'type', "
" 'specifiedAs': { 'type': 'string' }, "
" 'reason': 'type did not match', "
@@ -791,6 +1027,50 @@ TEST(JSONSchemaValidation, PatternNonString) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, PatternWithRequiredNoTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'pattern': '^S'}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'pattern'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, PatternWithRequiredAndTypeMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'required': ['a'],"
+ " 'properties': {'a': {'type': 'string', 'pattern': '^S'}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'pattern', nor 'type'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, PatternWithRequiredAndTypePatternFails) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'required': ['a'],"
+ " 'properties': {'a': {'type': 'string', 'pattern': '^S'}}}}");
+ BSONObj document = fromjson("{a: 'floo'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'pattern',"
+ " 'specifiedAs': {'pattern': '^S'},"
+ " 'reason': 'regular expression did not match',"
+ " 'consideredValue': 'floo'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, PatternNested) {
BSONObj query = fromjson(
"{'$jsonSchema': {"
@@ -897,12 +1177,6 @@ TEST(JSONSchemaValidation, MultipleOfNonNumeric) {
"{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
" {'operatorName': 'properties', 'propertiesNotSatisfied': ["
" {'propertyName': 'a', 'details': ["
- " {'operatorName': 'multipleOf', "
- " 'specifiedAs': { 'multipleOf': 2.1 }, "
- " 'reason': 'type did not match', "
- " 'consideredType': 'string', "
- " 'expectedTypes': ['decimal', 'double', 'int', 'long'], "
- " 'consideredValue': 'foo' }, "
" {'operatorName': 'type', "
" 'specifiedAs': { 'type': 'number' }, "
" 'reason': 'type did not match', "
@@ -911,6 +1185,49 @@ TEST(JSONSchemaValidation, MultipleOfNonNumeric) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, MultipleOfWithRequiredNoTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'multipleOf': 2}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'pattern'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MultipleOfWithRequiredAndTypeMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'multipleOf': 2}}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'pattern', nor 'type'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, MultipleOfWithRequiredAndTypeMultipleOfFails) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'required': ['a'],"
+ " 'properties': {'a': {'type': 'number', 'multipleOf': 2}}}}");
+ BSONObj document = fromjson("{a: 3}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ "'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'multipleOf',"
+ " 'specifiedAs': {'multipleOf': 2},"
+ " 'reason': 'considered value is not a multiple of the specified value',"
+ " 'consideredValue': 3}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, MultipleOfNested) {
BSONObj query = fromjson(
"{'$jsonSchema': {"
@@ -1414,6 +1731,19 @@ TEST(JSONSchemaLogicalKeywordValidation, BasicEnum) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaLogicalKeywordValidation, EnumWithRequiredMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'properties': {'a': {'enum':[1,2,3]}}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaLogicalKeywordValidation, TopLevelEnum) {
BSONObj query =
fromjson("{'$jsonSchema': {'enum': [{'a': 1, 'b': 1}, {'a': 0, 'b': {'c': [1,2,3]}}]}}");
@@ -1516,6 +1846,22 @@ TEST(JSONSchemaValidation, ArrayMinItemsTypeArray) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayMinItemsTypeArrayRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'],"
+ " 'properties': "
+ " {'a': {'type': 'array', 'minItems': 2}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayMinItems) {
BSONObj query = fromjson(
" {'$jsonSchema':"
@@ -1534,6 +1880,22 @@ TEST(JSONSchemaValidation, ArrayMinItems) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayMinItemsRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'],"
+ " 'properties': "
+ " {'a': {'minItems': 2}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayMinItemsAlwaysTrue) {
BSONObj query = fromjson("{$nor: [{'$jsonSchema': {'minItems': 2}}]}");
BSONObj document = fromjson("{}");
@@ -1581,6 +1943,21 @@ TEST(JSONSchemaValidation, ArrayMaxItems) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayMaxItemsRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'],"
+ " 'properties': {'a': {'maxItems': 2}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayMaxItemsAlwaysTrue) {
BSONObj query = fromjson("{$nor: [{'$jsonSchema': {'maxItems': 2}}]}");
BSONObj document = fromjson("{}");
@@ -1610,6 +1987,21 @@ TEST(JSONSchemaValidation, ArrayUniqueItems) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayUniqueItemsRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'], 'properties': "
+ " {'a': {'uniqueItems': true}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayUniqueItemTypeArray) {
BSONObj query = fromjson(
" {'$jsonSchema':"
@@ -1629,6 +2021,21 @@ TEST(JSONSchemaValidation, ArrayUniqueItemTypeArray) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayUniqueItemTypeArrayRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'],"
+ " 'properties': {'a': {'type': 'array', 'uniqueItems': true}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayUniqueItemsTypeArrayOnNonArrayAttribute) {
BSONObj query = fromjson(
" {'$jsonSchema':"
@@ -1675,6 +2082,21 @@ TEST(JSONSchemaValidation, ArrayItemsSingleSchema) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayItemsSingleSchemaRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'], 'properties': "
+ " {'a': {'items': {'type': 'string'}}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayItemsSingleSchemaTypeArrayOnNonArrayAttribute) {
BSONObj query = fromjson(
" {'$jsonSchema':"
@@ -1694,6 +2116,21 @@ TEST(JSONSchemaValidation, ArrayItemsSingleSchemaTypeArrayOnNonArrayAttribute) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayItemsSingleSchemaTypeArrayRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'], 'properties': "
+ " {'a': {'items': {'type': 'string'}, 'type': 'array'}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
// Verifies that "items" with a single schema does not produce any unwanted artifacts when it does
// not fail. We use "minItems" that fails validation to check that.
TEST(JSONSchemaValidation, ArrayItemsSingleSchemaCombinedWithMinItems) {
@@ -1742,6 +2179,24 @@ TEST(JSONSchemaValidation, ArrayItemsSingleSchemaNested) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayItemsSingleSchemaNestedRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'properties': "
+ " {'a': {'items': {'required': ['b'], 'properties': {'b': {'minItems': 2}}}}}}}");
+ BSONObj document = fromjson("{'a': [{}]}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'items',"
+ " 'reason': 'At least one item did not match the sub-schema',"
+ " 'itemIndex': 0, "
+ " 'details':["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['b']},'missingProperties': ['b']}]}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayItemsSingleSchema2DArray) {
BSONObj query =
fromjson("{'$jsonSchema': {'properties': {'a': {'items': {'items': {'minimum': 0}}}}}}");
@@ -1795,6 +2250,21 @@ TEST(JSONSchemaValidation, ArrayItemsSchemaArray) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayItemsSchemaArrayRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'], 'properties': "
+ " {'a': {'items': [{'type': 'number'}, {'type': 'string'}]}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
// Verifies that "items" with an array of schemas does not produce any unwanted artifacts when it
// does not fail. We use "minItems" that fails validation to check that.
TEST(JSONSchemaValidation, ArrayItemsSchemaArrayCombinedWithMinItems) {
@@ -1855,6 +2325,21 @@ TEST(JSONSchemaValidation, ArrayItemsSchemaArrayTypeArrayOnNonArrayAttribute) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayItemsSchemaArrayTypeArrayRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'],'properties': "
+ " {'a': {'items': [{'type': 'number'}, {'type': 'string'}], 'type': 'array'}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
// Verifies that "items" with an empty array of schemas does not produce any unwanted artifacts. We
// use "minItems" that fails validation to check that.
TEST(JSONSchemaValidation, ArrayItemsEmptySchemaArrayCombinedWithMinItems) {
@@ -1913,6 +2398,22 @@ TEST(JSONSchemaValidation, ArrayAdditionalItemsSchema) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayAdditionalItemsRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ " {'$jsonSchema':"
+ " {'required': ['a'], 'properties': "
+ " {'a': {'type': 'array', 'items': [{'type': 'number'}, {'type': 'string'}], "
+ "'additionalItems': {'type': 'object'}}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayAdditionalItemsSchemaItemsAndItemsSchemaFail) {
BSONObj query = fromjson(
" {'$jsonSchema':"
@@ -2030,6 +2531,22 @@ TEST(JSONSchemaValidation, ArrayAdditionalItemsFalse) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, ArrayAdditionalItemsFalseRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'], 'properties': {'a': "
+ " {'items': [{'type': 'number'}, {'type': 'string'}], "
+ "'additionalItems': false}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, ArrayAdditionalItemsFalseTypeArrayOnNonArrayAttribute) {
BSONObj query = fromjson(
"{'$jsonSchema': "
@@ -2127,6 +2644,38 @@ TEST(JSONSchemaValidation, NestedMinPropertiesTypeMismatch) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, NestedMinPropertiesWithRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'],'properties': {'a': {type: 'object', minProperties: 10}}}}}");
+ BSONObj document = fromjson("{}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, NestedMinPropertiesWithRequiredNonObject) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a'],'properties': {'a': {type: 'object', minProperties: 10}}}}}");
+ BSONObj document = fromjson("{a: []}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'a', 'details': ["
+ " {'operatorName': 'type',"
+ " 'specifiedAs': {'type': 'object'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': [],"
+ " 'consideredType': 'array'}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
// maxProperties
TEST(JSONSchemaValidation, BasicMaxProperties) {
BSONObj query = fromjson("{'$jsonSchema': {maxProperties: 2}}");
@@ -2238,6 +2787,31 @@ TEST(JSONSchemaValidation, PropertyFailingBiconditionalDependency) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, PropertyDependencyWithRequiredMissingProperty) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'dependencies': {'a': ['b', 'c']}}}");
+ BSONObj document = fromjson("{}");
+ // Should not mention 'dependencies'.
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'required',"
+ " specifiedAs: {required: ['a']}, "
+ " missingProperties: ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, PropertyDependencyWithRequiredMissingDependency) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'required': ['a'], 'dependencies': {'a': ['b', 'c']}}}");
+ BSONObj document = fromjson("{'a': 1}");
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'dependencies', failingDependencies: ["
+ " {conditionalProperty: 'a', "
+ " missingProperties: ['b', 'c']}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
// schema dependencies
TEST(JSONSchemaValidation, BasicSchemaDependency) {
BSONObj query = fromjson(
@@ -2279,6 +2853,39 @@ TEST(JSONSchemaValidation, NestedSchemaDependency) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, SchemaDependencyWithRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'required': ['a'], 'dependencies': {'a': {'properties': {'b': {'type': "
+ "'number'}}}}}}");
+ BSONObj document = fromjson("{'b': 'foo'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, SchemaDependencyWithRequiredFailedDependency) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ "{'required': ['a'], 'dependencies': {'a': {'properties': {'b': {'type': 'number'}}}}}}");
+ BSONObj document = fromjson("{a: 1, 'b': 'foo'}");
+ BSONObj expectedError = fromjson(
+ "{operatorName: '$jsonSchema', schemaRulesNotSatisfied: ["
+ " {operatorName: 'dependencies', failingDependencies: ["
+ " {conditionalProperty: 'a', details: ["
+ " {operatorName: 'properties',propertiesNotSatisfied: ["
+ " {propertyName: 'b', details: ["
+ " {operatorName: 'type', "
+ " specifiedAs: {type: 'number'}, "
+ " reason: 'type did not match', "
+ " consideredValue:'foo',"
+ " consideredType: 'string'}]}]}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, BiconditionalSchemaDependency) {
BSONObj query = fromjson(
"{'$jsonSchema': {'dependencies': "
@@ -2401,6 +3008,22 @@ TEST(JSONSchemaValidation, BasicAdditionalPropertiesFalse) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, BasicAdditionalPropertiesFalseRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'required': ['a'], 'properties': {'b': {'type': 'number'}}, "
+ "'additionalProperties': false}}}");
+ BSONObj document = fromjson("{'_id': 0, 'b': 2}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'additionalProperties', "
+ " 'specifiedAs': {additionalProperties: false},"
+ " 'additionalProperties': ['_id']},"
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, AdditionalPropertiesTrueProducesNoError) {
BSONObj query =
fromjson("{'$jsonSchema': {'minProperties': 100, 'additionalProperties': true}}");
@@ -2471,6 +3094,29 @@ TEST(JSONSchemaValidation, BasicAdditionalPropertiesSchema) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, BasicAdditionalPropertiesSchemaRequiredMissingProperty) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['a', 'b'], 'properties': {'a': {'type': 'number'}}, "
+ " 'additionalProperties': {'type': 'string'}}}}");
+ BSONObj document = fromjson("{'a': 1, 'c': 'not this one, but the next one', 'd': 1 }");
+ // Should produce an error for 'd' and 'b'.
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema', 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'additionalProperties',"
+ " 'reason':'at least one additional property did not match the subschema',"
+ " 'failingProperty': 'd', 'details': [ "
+ " {'operatorName': 'type', "
+ " 'specifiedAs': {type: 'string'},"
+ " 'reason': 'type did not match',"
+ " 'consideredValue': 1,"
+ " 'consideredType': 'int'}]},"
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a','b']},"
+ " 'missingProperties': ['b']}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, BasicAdditionalPropertiesSchemaNested) {
BSONObj query = fromjson(
"{'$jsonSchema': "
@@ -2528,6 +3174,20 @@ TEST(JSONSchemaValidation, BasicPatternProperties) {
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+TEST(JSONSchemaValidation, BasicPatternPropertiesRequired) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': "
+ " {'required': ['Super'], 'patternProperties': {'^S': {'type':'number'}}}}");
+ BSONObj document = fromjson("{'super': 1, 'slow': '67'}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['Super']},"
+ " 'missingProperties': ['Super']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
TEST(JSONSchemaValidation, NestedPatternProperties) {
BSONObj query = fromjson(
"{'$jsonSchema': {'properties': {'a': {'patternProperties': {'^S': {'type': "
@@ -2798,5 +3458,48 @@ TEST(JSONSchemaValidation, PatternPropertiesAndAdditionalPropertiesSchemaNeither
" 'numberOfProperties': 4}]}");
doc_validation_error::verifyGeneratedError(query, document, expectedError);
}
+
+// required
+TEST(JSONSchemaValidation, BasicRequired) {
+ BSONObj query = fromjson("{'$jsonSchema': {required: ['a','b','c']}}");
+ BSONObj document = fromjson("{'c': 1, 'd': 2}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a','b','c']},"
+ " 'missingProperties': ['a', 'b']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, RequiredMixedWithProperties) {
+ BSONObj query = fromjson(
+ "{'$jsonSchema': {'properties': {'a': {minimum: 2}, 'd': {maximum: 5}}, 'required': "
+ "['a','b','c']}}}");
+ BSONObj document = fromjson("{'c': 1, 'b': 2}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a','b','c']},"
+ " 'missingProperties': ['a']}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(JSONSchemaValidation, RequiredNested) {
+ BSONObj query =
+ fromjson("{'$jsonSchema': {'properties': {'topLevelField': {'required': ['a','b','c']}}}}");
+ BSONObj document = fromjson("{'topLevelField': {'c': 1, 'd': 2}}");
+ BSONObj expectedError = fromjson(
+ "{'operatorName': '$jsonSchema',"
+ " 'schemaRulesNotSatisfied': ["
+ " {'operatorName': 'properties', 'propertiesNotSatisfied': ["
+ " {'propertyName': 'topLevelField', 'details': ["
+ " {'operatorName': 'required',"
+ " 'specifiedAs': {'required': ['a','b','c']},"
+ " 'missingProperties': ['a', 'b']}]}]}]}");
+ doc_validation_error::verifyGeneratedError(query, document, expectedError);
+}
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index 86f3139b5fd..ec980d71ebc 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -498,14 +498,20 @@ StatusWith<StringDataSet> parseRequired(BSONElement requiredElt) {
*/
StatusWithMatchExpression translateRequired(const boost::intrusive_ptr<ExpressionContext>& expCtx,
const StringDataSet& requiredProperties,
+ BSONElement requiredElt,
StringData path,
InternalSchemaTypeExpression* typeExpr) {
- auto andExpr = std::make_unique<AndMatchExpression>();
+ auto andExpr = std::make_unique<AndMatchExpression>(
+ doc_validation_error::createAnnotation(expCtx, "required", requiredElt.wrap()));
std::vector<StringData> sortedProperties(requiredProperties.begin(), requiredProperties.end());
std::sort(sortedProperties.begin(), sortedProperties.end());
for (auto&& propertyName : sortedProperties) {
- andExpr->add(new ExistsMatchExpression(propertyName));
+ // This node is tagged as '_propertyExists' to indicate that it will produce a path instead
+ // of a detailed BSONObj error during error generation.
+ andExpr->add(new ExistsMatchExpression(
+ propertyName,
+ doc_validation_error::createAnnotation(expCtx, "_propertyExists", BSONObj())));
}
// If this is a top-level schema, then we know that we are matching against objects, and there
@@ -514,8 +520,10 @@ StatusWithMatchExpression translateRequired(const boost::intrusive_ptr<Expressio
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(expCtx, BSONType::Object, path, std::move(objectMatch), typeExpr);
}
@@ -554,7 +562,9 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
}
nestedSchemaMatch.getValue()->setErrorAnnotation(doc_validation_error::createAnnotation(
- expCtx, "", BSON("propertyName" << property.fieldNameStringData().toString())));
+ expCtx,
+ "_property",
+ 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'.
@@ -787,7 +797,7 @@ StatusWithMatchExpression makeDependencyExistsClause(
StringData path,
StringData dependencyName) {
// This node is tagged as '_propertyExists' to indicate that it will produce a path instead
- // of a detail BSONObj error during error generation.
+ // of a detailed BSONObj error during error generation.
auto existsExpr = std::make_unique<ExistsMatchExpression>(
dependencyName,
doc_validation_error::createAnnotation(expCtx, "_propertyExists", BSONObj()));
@@ -1357,7 +1367,11 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
}
if (!requiredProperties.empty()) {
- auto requiredExpr = translateRequired(expCtx, requiredProperties, path, typeExpr);
+ auto requiredExpr = translateRequired(expCtx,
+ requiredProperties,
+ keywordMap[JSONSchemaParser::kSchemaRequiredKeyword],
+ path,
+ typeExpr);
if (!requiredExpr.isOK()) {
return requiredExpr.getStatus();
}