Skip to content

Commit 772e044

Browse files
committed
[json_schema] Implement several keyword validations
- minimum - maximum - const - contains - minContains - maxContains Also fix bug in dynamic_schema's allocateJson
1 parent 305e61c commit 772e044

File tree

3 files changed

+61
-49
lines changed

3 files changed

+61
-49
lines changed

json_schema/include/json_schema/2019-09/schema_validator.h

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,19 @@ class SchemaValidator {
3737

3838
template <typename JSON>
3939
constexpr ValidationResult validate(const JSON &theJson) const {
40+
return validate(theJson, itsSchema);
41+
}
42+
43+
template <typename JSON>
44+
constexpr ValidationResult validate(const JSON &theJson,
45+
const SchemaRef theSchema) const {
4046
using json_type = decltype(theJson.getType());
41-
SchemaObjectAccessor aSchema{itsContext, itsSchema};
47+
SchemaObjectAccessor aSchema{itsContext, theSchema};
4248
if (aSchema.isTrueSchema())
4349
return std::nullopt;
4450
if (aSchema.isFalseSchema())
4551
return makeError("Schema to validate against is `false`");
52+
const auto &aApplicator = aSchema.template getSection<SchemaApplicator>();
4653
const auto &aValidation = aSchema.template getSection<SchemaValidation>();
4754
// minProperties
4855
if (const auto &aMinProps = aValidation.getMinProperties();
@@ -80,6 +87,55 @@ class SchemaValidator {
8087
if (!aIsAllowed)
8188
return makeError("Type is not allowed");
8289
}
90+
if (theJson.getType() == json_type::NUMBER) {
91+
// maximum
92+
if (const auto &aMaximum = aValidation.getMaximum()) {
93+
if (aMaximum < theJson.toNumber()) {
94+
return makeError("Value above maximum");
95+
}
96+
}
97+
98+
// minimum
99+
if (const auto &aMinimum = aValidation.getMinimum()) {
100+
if (aMinimum > theJson.toNumber()) {
101+
return makeError("Value below minimum exceeded");
102+
}
103+
}
104+
}
105+
// const
106+
if (const auto &aConst = aValidation.getConst()) {
107+
if (*aConst != theJson) {
108+
return makeError(
109+
"Element does not match the expected constant (const)");
110+
}
111+
}
112+
113+
if (theJson.getType() == json_type::ARRAY) {
114+
if (aValidation.getMaxContains() < aValidation.getMinContains()) {
115+
return makeError(
116+
"Impossible for array to satisfy maxContains<minContains "
117+
"expectation (probably schema error)");
118+
}
119+
// contains
120+
if (const auto &aContains = aApplicator.getContains()) {
121+
size_t aNumMatching{0};
122+
for (const auto &aElm : theJson.toArray()) {
123+
ValidationResult aSubVal =
124+
validate(aElm, aContains->getRefInternal());
125+
if (!aSubVal)
126+
++aNumMatching;
127+
}
128+
if (aNumMatching == 0u && aValidation.getMinContains() > 0) {
129+
return makeError("Expected element not found in array (contains)");
130+
}
131+
if (aNumMatching < aValidation.getMinContains()) {
132+
return makeError("Fewer array elements than expected match");
133+
}
134+
if (aNumMatching > aValidation.getMaxContains()) {
135+
return makeError("More array elements than expected match");
136+
}
137+
}
138+
}
83139
return std::nullopt;
84140
}
85141

json_schema/include/json_schema/dynamic_schema.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ template <typename Standard_> struct DynamicSchemaContext {
136136
cjson::DocumentInfo aDocInfo = cjson::DocumentInfo::read(theJson);
137137
auto aJson = std::make_shared<JsonStorage>(aDocInfo);
138138
cjson::DocumentAllocator<JsonStorage, cjson::ErrorWillThrow> aAlloc;
139-
aAlloc.allocateJson(*aJson, theJson);
139+
aJson->itsEntities[0] = aAlloc.allocateJson(*aJson, theJson);
140140
JsonRef aJsonRef = *aJson;
141141
theContext.itsAllocations.emplace_back(std::move(aJson));
142142
return aJsonRef;

json_schema/test/JSON-Schema-Test-Suite/driver.cc

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static cl::list<fs::path>
4141
gInputs(cl::meta("files"), cl::desc("File(s) to be validated"),
4242
cl::init({JSON_SCHEMA_TESTSUITE_BUILTIN_TESTDIRS}));
4343

44-
using Standard = json_schema::Standard_2019_09</*Lenient=*/false>;
44+
using Standard = json_schema::Standard_2019_09</*Lenient=*/true>;
4545
using Context = json_schema::DynamicSchemaContext<Standard>;
4646
using Reader = Standard::template SchemaReader<Context, cjson::ErrorWillThrow>;
4747
using ReadResult = typename Reader::template ReadResult<1>;
@@ -104,27 +104,6 @@ static std::pair<std::string_view, std::string_view> gDisabledList[] = {
104104
{"byint", "intbyintfail"},
105105
{"bynumber", "35isnotmultipleof1.5"},
106106
{"bysmallnumber", "0.00751isnotmultipleof0.0001"},
107-
{"constvalidation", "anothertypeisinvalid"},
108-
{"constvalidation", "anothervalueisinvalid"},
109-
{"constwith[false]doesnotmatch[0]", "[0.0]isinvalid"},
110-
{"constwith[false]doesnotmatch[0]", "[0]isinvalid"},
111-
{"constwith[true]doesnotmatch[1]", "[1.0]isinvalid"},
112-
{"constwith[true]doesnotmatch[1]", "[1]isinvalid"},
113-
{"constwith1doesnotmatchtrue", "trueisinvalid"},
114-
{"constwitharray", "anotherarrayitemisinvalid"},
115-
{"constwitharray", "arraywithadditionalitemsisinvalid"},
116-
{"constwithfalsedoesnotmatch0", "floatzeroisinvalid"},
117-
{"constwithfalsedoesnotmatch0", "integerzeroisinvalid"},
118-
{"constwithnull", "notnullisinvalid"},
119-
{"constwithobject", "anotherobjectisinvalid"},
120-
{"constwithobject", "anothertypeisinvalid"},
121-
{"constwithtruedoesnotmatch1", "floatoneisinvalid"},
122-
{"constwithtruedoesnotmatch1", "integeroneisinvalid"},
123-
{"containskeywordvalidation", "arraywithoutitemsmatchingschemaisinvalid"},
124-
{"containskeywordvalidation", "emptyarrayisinvalid"},
125-
{"containskeywordwithbooleanschemafalse", "emptyarrayisinvalid"},
126-
{"containskeywordwithbooleanschematrue", "emptyarrayisinvalid"},
127-
{"containskeywordwithconstkeyword", "arraywithoutitem5isinvalid"},
128107
{"cousinunevaluatedProperties,trueandfalse,falsewithproperties",
129108
"withnestedunevaluatedproperties"},
130109
{"cousinunevaluatedProperties,trueandfalse,truewithproperties",
@@ -152,9 +131,8 @@ static std::pair<std::string_view, std::string_view> gDisabledList[] = {
152131
{"escapedpointerref", "percentinvalid"},
153132
{"escapedpointerref", "slashinvalid"},
154133
{"escapedpointerref", "tildeinvalid"},
155-
{"evaluatingthesameschemalocationagainstthesamedatalocationtwiceisnotasigno"
156-
"fa"
157-
"ninfiniteloop",
134+
{"evaluatingthesameschemalocationagainstthesamedatalocationtwiceisnotasign"
135+
"ofaninfiniteloop",
158136
"failingcase"},
159137
{"exclusiveMaximumvalidation", "abovetheexclusiveMaximumisinvalid"},
160138
{"exclusiveMaximumvalidation", "boundarypointisinvalid"},
@@ -187,30 +165,8 @@ static std::pair<std::string_view, std::string_view> gDisabledList[] = {
187165
{"itemsvalidationadjuststhestartingindexforadditionalItems",
188166
"wrongtypeofseconditem"},
189167
{"itemswithbooleanschemas", "arraywithtwoitemsisinvalid"},
190-
{"maxContains<minContains", "emptydata"},
191-
{"maxContains<minContains", "invalidmaxContains"},
192-
{"maxContains<minContains", "invalidmaxContainsandminContains"},
193-
{"maxContains<minContains", "invalidminContains"},
194-
{"maxContains=minContains", "allelementsmatch,invalidmaxContains"},
195-
{"maxContains=minContains", "allelementsmatch,invalidminContains"},
196-
{"maxContains=minContains", "emptydata"},
197-
{"maxContainswithcontains", "allelementsmatch,invalidmaxContains"},
198-
{"maxContainswithcontains", "emptydata"},
199-
{"maxContainswithcontains", "someelementsmatch,invalidmaxContains"},
200-
{"maximumvalidation", "abovethemaximumisinvalid"},
201-
{"maximumvalidationwithunsignedinteger", "abovethemaximumisinvalid"},
202168
{"maxItemsvalidation", "toolongisinvalid"},
203169
{"maxLengthvalidation", "toolongisinvalid"},
204-
{"minContains<maxContains", "actual<minContains<maxContains"},
205-
{"minContains<maxContains", "minContains<maxContains<actual"},
206-
{"minContains=1withcontains", "emptydata"},
207-
{"minContains=1withcontains", "noelementsmatch"},
208-
{"minContains=2withcontains", "allelementsmatch,invalidminContains"},
209-
{"minContains=2withcontains", "emptydata"},
210-
{"minContains=2withcontains", "someelementsmatch,invalidminContains"},
211-
{"minimumvalidation", "belowtheminimumisinvalid"},
212-
{"minimumvalidationwithsignedinteger", "floatbelowtheminimumisinvalid"},
213-
{"minimumvalidationwithsignedinteger", "intbelowtheminimumisinvalid"},
214170
{"minItemsvalidation", "tooshortisinvalid"},
215171
{"minLengthvalidation", "onesupplementaryUnicodecodepointisnotlongenough"},
216172
{"minLengthvalidation", "tooshortisinvalid"},

0 commit comments

Comments
 (0)