mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-03 07:12:28 +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>
|
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
|
* Add convenience methods isNameAndEquals and isDictionaryOfType
|
||||||
to QPDFObjectHandle with corresponding functions added to the C
|
to QPDFObjectHandle with corresponding functions added to the C
|
||||||
API. Thanks to m-holger for the contribution.
|
API. Thanks to m-holger for the contribution.
|
||||||
|
@ -107,21 +107,39 @@ class JSON
|
|||||||
// single-element arrays, and strings only.
|
// single-element arrays, and strings only.
|
||||||
// * Recursively walk the schema
|
// * Recursively walk the schema
|
||||||
// * If the current value is a dictionary, this object must have
|
// * 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
|
// * If the current value is an array, this object must have an
|
||||||
// array in the same place. The schema's array must contain a
|
// array in the same place. The schema's array must contain a
|
||||||
// single element, which is used as a schema to validate each
|
// single element, which is used as a schema to validate each
|
||||||
// element of this object's corresponding array.
|
// 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
|
// QPDF's JSON output conforms to certain strict compatibility
|
||||||
// rules as discussed in the manual. The idea is that a JSON
|
// rules as discussed in the manual. The idea is that a JSON
|
||||||
// structure created manually in qpdf.cc doubles as both JSON help
|
// structure created manually in qpdf.cc doubles as both JSON help
|
||||||
// information and a schema for validating the JSON that qpdf
|
// information and a schema for validating the JSON that qpdf
|
||||||
// generates. Any discrepancies are a bug in 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
|
QPDF_DLL
|
||||||
bool checkSchema(JSON schema, std::list<std::string>& errors);
|
bool checkSchema(JSON schema, std::list<std::string>& errors);
|
||||||
|
|
||||||
|
|
||||||
// Create a JSON object from a string.
|
// Create a JSON object from a string.
|
||||||
QPDF_DLL
|
QPDF_DLL
|
||||||
static JSON parse(std::string const&);
|
static JSON parse(std::string const&);
|
||||||
@ -180,6 +198,7 @@ class JSON
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||||
|
unsigned long flags,
|
||||||
std::list<std::string>& errors,
|
std::list<std::string>& errors,
|
||||||
std::string prefix);
|
std::string prefix);
|
||||||
|
|
||||||
|
@ -394,12 +394,21 @@ JSON::checkSchema(JSON schema, std::list<std::string>& errors)
|
|||||||
{
|
{
|
||||||
return checkSchemaInternal(this->m->value.getPointer(),
|
return checkSchemaInternal(this->m->value.getPointer(),
|
||||||
schema.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
|
bool
|
||||||
JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
||||||
|
unsigned long flags,
|
||||||
std::list<std::string>& errors,
|
std::list<std::string>& errors,
|
||||||
std::string prefix)
|
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_array* sch_arr = dynamic_cast<JSON_array*>(sch_v);
|
||||||
JSON_dictionary* sch_dict = dynamic_cast<JSON_dictionary*>(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;
|
std::string err_prefix;
|
||||||
if (prefix.empty())
|
if (prefix.empty())
|
||||||
{
|
{
|
||||||
@ -446,34 +457,38 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
|||||||
{
|
{
|
||||||
std::string const& key = iter.first;
|
std::string const& key = iter.first;
|
||||||
checkSchemaInternal(
|
checkSchemaInternal(
|
||||||
this_dict->members[key].getPointer(),
|
this_dict->members[key].getPointer(), pattern_schema,
|
||||||
pattern_schema,
|
flags, errors, prefix + "." + key);
|
||||||
errors, prefix + "." + key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (sch_dict)
|
else if (sch_dict)
|
||||||
{
|
{
|
||||||
for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
|
for (auto& iter: sch_dict->members)
|
||||||
sch_dict->members.begin();
|
|
||||||
iter != sch_dict->members.end(); ++iter)
|
|
||||||
{
|
{
|
||||||
std::string const& key = (*iter).first;
|
std::string const& key = iter.first;
|
||||||
if (this_dict->members.count(key))
|
if (this_dict->members.count(key))
|
||||||
{
|
{
|
||||||
checkSchemaInternal(
|
checkSchemaInternal(
|
||||||
this_dict->members[key].getPointer(),
|
this_dict->members[key].getPointer(),
|
||||||
(*iter).second.getPointer(),
|
iter.second.getPointer(),
|
||||||
errors, prefix + "." + key);
|
flags, errors, prefix + "." + key);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QTC::TC("libtests", "JSON key missing in object");
|
if (flags & f_optional)
|
||||||
errors.push_back(
|
{
|
||||||
err_prefix + ": key \"" + key +
|
QTC::TC("libtests", "JSON optional key");
|
||||||
"\" is present in schema but missing in object");
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "JSON key missing in object");
|
||||||
|
errors.push_back(
|
||||||
|
err_prefix + ": key \"" + key +
|
||||||
|
"\" is present in schema but missing in object");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
|
for (std::map<std::string, PointerHolder<JSON_value>>::iterator iter =
|
||||||
this_dict->members.begin();
|
this_dict->members.begin();
|
||||||
iter != this_dict->members.end(); ++iter)
|
iter != this_dict->members.end(); ++iter)
|
||||||
{
|
{
|
||||||
@ -510,9 +525,16 @@ JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
|
|||||||
checkSchemaInternal(
|
checkSchemaInternal(
|
||||||
(*iter).getPointer(),
|
(*iter).getPointer(),
|
||||||
sch_arr->elements.at(0).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();
|
return errors.empty();
|
||||||
}
|
}
|
||||||
|
@ -112,12 +112,12 @@ static void test_main()
|
|||||||
assert(dvalue == xdvalue);
|
assert(dvalue == xdvalue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_schema(JSON& obj, JSON& schema, bool exp,
|
static void check_schema(JSON& obj, JSON& schema, unsigned long flags,
|
||||||
std::string const& description)
|
bool exp, std::string const& description)
|
||||||
{
|
{
|
||||||
std::list<std::string> errors;
|
std::list<std::string> errors;
|
||||||
std::cout << "--- " << description << std::endl;
|
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();
|
for (std::list<std::string>::iterator iter = errors.begin();
|
||||||
iter != errors.end(); ++iter)
|
iter != errors.end(); ++iter)
|
||||||
{
|
{
|
||||||
@ -134,8 +134,7 @@ static void test_schema()
|
|||||||
"a": {
|
"a": {
|
||||||
"q": "queue",
|
"q": "queue",
|
||||||
"r": {
|
"r": {
|
||||||
"x": "ecks",
|
"x": "ecks"
|
||||||
"y": "(bool) why"
|
|
||||||
},
|
},
|
||||||
"s": [
|
"s": [
|
||||||
"esses"
|
"esses"
|
||||||
@ -151,14 +150,14 @@ static void test_schema()
|
|||||||
"three": {
|
"three": {
|
||||||
"<objid>": {
|
"<objid>": {
|
||||||
"z": "ebra",
|
"z": "ebra",
|
||||||
"o": "(optional, string) optional"
|
"o": "ptional"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
|
||||||
JSON a = JSON::parse(R"(["not a", "dictionary"])");
|
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"(
|
JSON b = JSON::parse(R"(
|
||||||
{
|
{
|
||||||
"one": {
|
"one": {
|
||||||
@ -205,10 +204,42 @@ static void test_schema()
|
|||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
|
||||||
check_schema(b, schema, false, "missing items");
|
check_schema(b, schema, 0, false, "missing items");
|
||||||
check_schema(a, a, false, "top-level schema array error");
|
check_schema(a, a, 0, false, "top-level schema array error");
|
||||||
check_schema(b, b, false, "lower-level schema array error");
|
check_schema(b, b, 0, false, "lower-level schema array error");
|
||||||
check_schema(schema, schema, true, "pass");
|
|
||||||
|
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()
|
int main()
|
||||||
|
@ -89,3 +89,5 @@ JSON parse premature end of u 0
|
|||||||
JSON parse bad hex after u 0
|
JSON parse bad hex after u 0
|
||||||
JSONHandler unhandled value 0
|
JSONHandler unhandled value 0
|
||||||
JSONHandler unexpected key 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 ".one.a.r" schema array contains other than one item
|
||||||
json key ".two" 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
|
--- pass
|
||||||
---
|
---
|
||||||
end of json tests
|
end of json tests
|
||||||
|
Loading…
Reference in New Issue
Block a user