aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2022-01-22 20:33:26 +0100
committerJay Berkenbilt <ejb@ql.org>2022-01-30 19:11:03 +0100
commit8dea480c9f065fdac76f848ed9ec7a07fd1e9870 (patch)
tree8334231ae683dcf239f439f1602268fa98f39f10
parent558ba2823e40867eac47686edea3fe318ce3c446 (diff)
downloadqpdf-8dea480c9f065fdac76f848ed9ec7a07fd1e9870.tar.zst
Allow optional fields in json "schema" checks
-rw-r--r--ChangeLog4
-rw-r--r--include/qpdf/JSON.hh23
-rw-r--r--libqpdf/JSON.cc54
-rw-r--r--libtests/json.cc53
-rw-r--r--libtests/libtests.testcov2
-rw-r--r--libtests/qtest/json/json.out6
6 files changed, 113 insertions, 29 deletions
diff --git a/ChangeLog b/ChangeLog
index b70d6699..2433a76a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
diff --git a/include/qpdf/JSON.hh b/include/qpdf/JSON.hh
index 3b13b4fe..d45fb3e1 100644
--- a/include/qpdf/JSON.hh
+++ b/include/qpdf/JSON.hh
@@ -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);
diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc
index af98553e..f70145a7 100644
--- a/libqpdf/JSON.cc
+++ b/libqpdf/JSON.cc
@@ -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,34 +457,38 @@ 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
{
- QTC::TC("libtests", "JSON key missing in object");
- errors.push_back(
- err_prefix + ": key \"" + key +
- "\" is present in schema but missing in object");
+ if (flags & f_optional)
+ {
+ QTC::TC("libtests", "JSON optional key");
+ }
+ 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();
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();
}
diff --git a/libtests/json.cc b/libtests/json.cc
index 006f00cf..754c2b3e 100644
--- a/libtests/json.cc
+++ b/libtests/json.cc
@@ -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()
diff --git a/libtests/libtests.testcov b/libtests/libtests.testcov
index 6284c0e8..907e6579 100644
--- a/libtests/libtests.testcov
+++ b/libtests/libtests.testcov
@@ -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
diff --git a/libtests/qtest/json/json.out b/libtests/qtest/json/json.out
index 1a320dcf..c7cb85a7 100644
--- a/libtests/qtest/json/json.out
+++ b/libtests/qtest/json/json.out
@@ -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