mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-02 22:50:20 +00:00
Allow optional fields in json "schema" checks
This commit is contained in:
parent
558ba2823e
commit
8dea480c9f
@ -1,5 +1,9 @@
|
||||
2022-01-22 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* JSON: for (qpdf-specific, not official) "schema" checking, add
|
||||
the ability to treat missing fields as optional. Also ensure that
|
||||
values in the schema are dictionary, array, or string.
|
||||
|
||||
* Add convenience methods isNameAndEquals and isDictionaryOfType
|
||||
to QPDFObjectHandle with corresponding functions added to the C
|
||||
API. Thanks to m-holger for the contribution.
|
||||
|
@ -107,21 +107,39 @@ class JSON
|
||||
// single-element arrays, and strings only.
|
||||
// * Recursively walk the schema
|
||||
// * If the current value is a dictionary, this object must have
|
||||
// a dictionary in the same place with the same keys
|
||||
// a dictionary in the same place with the same keys. If flags
|
||||
// contains f_optional, a key in the schema does not have to
|
||||
// be present in the object. Otherwise, all keys have to be
|
||||
// present. Any key in the object must be present in the
|
||||
// schema.
|
||||
// * If the current value is an array, this object must have an
|
||||
// array in the same place. The schema's array must contain a
|
||||
// single element, which is used as a schema to validate each
|
||||
// element of this object's corresponding array.
|
||||
// * Otherwise, the value is ignored.
|
||||
// * Otherwise, the value must be a string whose value is a
|
||||
// description of the object's corresponding value, which may
|
||||
// have any type.
|
||||
//
|
||||
// QPDF's JSON output conforms to certain strict compatibility
|
||||
// rules as discussed in the manual. The idea is that a JSON
|
||||
// structure created manually in qpdf.cc doubles as both JSON help
|
||||
// information and a schema for validating the JSON that qpdf
|
||||
// generates. Any discrepancies are a bug in qpdf.
|
||||
//
|
||||
// Flags is a bitwise or of values from check_flags_e.
|
||||
enum check_flags_e {
|
||||
f_none = 0,
|
||||
f_optional = 1 << 0,
|
||||
};
|
||||
QPDF_DLL
|
||||
bool checkSchema(JSON schema, unsigned long flags,
|
||||
std::list<std::string>& errors);
|
||||
|
||||
// Same as passing 0 for flags
|
||||
QPDF_DLL
|
||||
bool checkSchema(JSON schema, std::list<std::string>& errors);
|
||||
|
||||
|
||||
// Create a JSON object from a string.
|
||||
QPDF_DLL
|
||||
static JSON parse(std::string const&);
|
||||
@ -180,6 +198,7 @@ class JSON
|
||||
|
||||
static bool
|
||||
checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||
unsigned long flags,
|
||||
std::list<std::string>& errors,
|
||||
std::string prefix);
|
||||
|
||||
|
@ -394,12 +394,21 @@ JSON::checkSchema(JSON schema, std::list<std::string>& errors)
|
||||
{
|
||||
return checkSchemaInternal(this->m->value.getPointer(),
|
||||
schema.m->value.getPointer(),
|
||||
errors, "");
|
||||
0, errors, "");
|
||||
}
|
||||
|
||||
bool
|
||||
JSON::checkSchema(JSON schema, unsigned long flags,
|
||||
std::list<std::string>& errors)
|
||||
{
|
||||
return checkSchemaInternal(this->m->value.getPointer(),
|
||||
schema.m->value.getPointer(),
|
||||
flags, errors, "");
|
||||
}
|
||||
|
||||
bool
|
||||
JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||
unsigned long flags,
|
||||
std::list<std::string>& errors,
|
||||
std::string prefix)
|
||||
{
|
||||
@ -409,6 +418,8 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||
JSON_array* sch_arr = dynamic_cast<JSON_array*>(sch_v);
|
||||
JSON_dictionary* sch_dict = dynamic_cast<JSON_dictionary*>(sch_v);
|
||||
|
||||
JSON_string* sch_str = dynamic_cast<JSON_string*>(sch_v);
|
||||
|
||||
std::string err_prefix;
|
||||
if (prefix.empty())
|
||||
{
|
||||
@ -446,24 +457,27 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||
{
|
||||
std::string const& key = iter.first;
|
||||
checkSchemaInternal(
|
||||
this_dict->members[key].getPointer(),
|
||||
pattern_schema,
|
||||
errors, prefix + "." + key);
|
||||
this_dict->members[key].getPointer(), pattern_schema,
|
||||
flags, errors, prefix + "." + key);
|
||||
}
|
||||
}
|
||||
else if (sch_dict)
|
||||
{
|
||||
for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
|
||||
sch_dict->members.begin();
|
||||
iter != sch_dict->members.end(); ++iter)
|
||||
for (auto& iter: sch_dict->members)
|
||||
{
|
||||
std::string const& key = (*iter).first;
|
||||
std::string const& key = iter.first;
|
||||
if (this_dict->members.count(key))
|
||||
{
|
||||
checkSchemaInternal(
|
||||
this_dict->members[key].getPointer(),
|
||||
(*iter).second.getPointer(),
|
||||
errors, prefix + "." + key);
|
||||
iter.second.getPointer(),
|
||||
flags, errors, prefix + "." + key);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags & f_optional)
|
||||
{
|
||||
QTC::TC("libtests", "JSON optional key");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -473,6 +487,7 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||
"\" is present in schema but missing in object");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::map<std::string, PointerHolder<JSON_value>>::iterator iter =
|
||||
this_dict->members.begin();
|
||||
iter != this_dict->members.end(); ++iter)
|
||||
@ -510,9 +525,16 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||
checkSchemaInternal(
|
||||
(*iter).getPointer(),
|
||||
sch_arr->elements.at(0).getPointer(),
|
||||
errors, prefix + "." + QUtil::int_to_string(i));
|
||||
flags, errors, prefix + "." + QUtil::int_to_string(i));
|
||||
}
|
||||
}
|
||||
else if (! sch_str)
|
||||
{
|
||||
QTC::TC("libtests", "JSON schema other type");
|
||||
errors.push_back(err_prefix +
|
||||
" schema value is not dictionary, array, or string");
|
||||
return false;
|
||||
}
|
||||
|
||||
return errors.empty();
|
||||
}
|
||||
|
@ -112,12 +112,12 @@ static void test_main()
|
||||
assert(dvalue == xdvalue);
|
||||
}
|
||||
|
||||
static void check_schema(JSON& obj, JSON& schema, bool exp,
|
||||
std::string const& description)
|
||||
static void check_schema(JSON& obj, JSON& schema, unsigned long flags,
|
||||
bool exp, std::string const& description)
|
||||
{
|
||||
std::list<std::string> errors;
|
||||
std::cout << "--- " << description << std::endl;
|
||||
assert(exp == obj.checkSchema(schema, errors));
|
||||
assert(exp == obj.checkSchema(schema, flags, errors));
|
||||
for (std::list<std::string>::iterator iter = errors.begin();
|
||||
iter != errors.end(); ++iter)
|
||||
{
|
||||
@ -134,8 +134,7 @@ static void test_schema()
|
||||
"a": {
|
||||
"q": "queue",
|
||||
"r": {
|
||||
"x": "ecks",
|
||||
"y": "(bool) why"
|
||||
"x": "ecks"
|
||||
},
|
||||
"s": [
|
||||
"esses"
|
||||
@ -151,14 +150,14 @@ static void test_schema()
|
||||
"three": {
|
||||
"<objid>": {
|
||||
"z": "ebra",
|
||||
"o": "(optional, string) optional"
|
||||
"o": "ptional"
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
JSON a = JSON::parse(R"(["not a", "dictionary"])");
|
||||
check_schema(a, schema, false, "top-level type mismatch");
|
||||
check_schema(a, schema, 0, false, "top-level type mismatch");
|
||||
JSON b = JSON::parse(R"(
|
||||
{
|
||||
"one": {
|
||||
@ -205,10 +204,42 @@ static void test_schema()
|
||||
}
|
||||
)");
|
||||
|
||||
check_schema(b, schema, false, "missing items");
|
||||
check_schema(a, a, false, "top-level schema array error");
|
||||
check_schema(b, b, false, "lower-level schema array error");
|
||||
check_schema(schema, schema, true, "pass");
|
||||
check_schema(b, schema, 0, false, "missing items");
|
||||
check_schema(a, a, 0, false, "top-level schema array error");
|
||||
check_schema(b, b, 0, false, "lower-level schema array error");
|
||||
|
||||
JSON bad_schema = JSON::parse(R"({"a": true, "b": "potato?"})");
|
||||
check_schema(bad_schema, bad_schema, 0, false, "bad schema field type");
|
||||
|
||||
JSON good = JSON::parse(R"(
|
||||
{
|
||||
"one": {
|
||||
"a": {
|
||||
"q": "potato",
|
||||
"r": {
|
||||
"x": [1, null]
|
||||
},
|
||||
"s": [
|
||||
null,
|
||||
"anything"
|
||||
]
|
||||
}
|
||||
},
|
||||
"two": [
|
||||
{
|
||||
"glarp": "enspliel",
|
||||
"goose": 3.14
|
||||
}
|
||||
],
|
||||
"three": {
|
||||
"<objid>": {
|
||||
"z": "ebra"
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
check_schema(good, schema, 0, false, "not optional");
|
||||
check_schema(good, schema, JSON::f_optional, true, "pass");
|
||||
}
|
||||
|
||||
int main()
|
||||
|
@ -89,3 +89,5 @@ JSON parse premature end of u 0
|
||||
JSON parse bad hex after u 0
|
||||
JSONHandler unhandled value 0
|
||||
JSONHandler unexpected key 0
|
||||
JSON schema other type 0
|
||||
JSON optional key 0
|
||||
|
@ -21,6 +21,12 @@ top-level object schema array contains other than one item
|
||||
json key ".one.a.r" schema array contains other than one item
|
||||
json key ".two" schema array contains other than one item
|
||||
---
|
||||
--- bad schema field type
|
||||
json key ".a" schema value is not dictionary, array, or string
|
||||
---
|
||||
--- not optional
|
||||
json key ".three.<objid>": key "o" is present in schema but missing in object
|
||||
---
|
||||
--- pass
|
||||
---
|
||||
end of json tests
|
||||
|
Loading…
Reference in New Issue
Block a user