Commit f8d1ab946205440ed3c44511ef42e5ad13fb9e5e

Authored by Jay Berkenbilt
1 parent b3e6d445

JSON schema -- accept single item in place of array

When the schema wants a variable-length array, allow a single item as
well as allowing an array.
ChangeLog
1 1 2022-07-24 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * include/qpdf/JSON.hh: Schema validation: allow a single item to
  4 + appear anywhere that the schema has an array of a single item.
  5 + This makes it possible to change an element of the schema from an
  6 + item to an array to allow the data to accept an array where a
  7 + single value was previously required. This change is needed to
  8 + allow QPDFJob JSON to start accepting multiple items where a
  9 + single item used to be expected without breaking backward
  10 + compatibility. Without this change, the earlier fix to
  11 + removeAttachment would be a breaking change.
  12 +
3 13 * QPDFObjectHandle: for the methods insertItem, appendItem,
4 14 eraseItem, replaceKey, and removeKey, add a corresponding
5 15 "AndGetNew" and/or "AndGetOld" methods. The ones that end with
... ...
... ... @@ -19,10 +19,6 @@ Pending changes:
19 19 appimage build specifically is setting the runpath, which is
20 20 actually desirable in this case. Make sure to understand and
21 21 document this. Maybe add a check for it in the build.
22   -* Make job JSON accept a single element and treat as an array of one
23   - when an array is expected. This allows for making things repeatable
24   - in the future without breaking compatibility and is needed for the
25   - remote-attachment fix to be backward-compatible.
26 22 * Decide what to do about #664 (get*Box)
27 23 * Add an option --ignore-encryption to ignore encryption information
28 24 and treat encrypted files as if they weren't encrypted. This should
... ...
include/qpdf/JSON.hh
... ... @@ -184,6 +184,14 @@ class JSON
184 184 // * The schema is a nested structure containing dictionaries,
185 185 // single-element arrays, and strings only.
186 186 // * Recursively walk the schema.
  187 + // * Whenever the schema has an array of length 1 and the object
  188 + // does not have an array in the corresponding location,
  189 + // validate the object against the array's single element.
  190 + // This effectively enables a single element to appear in
  191 + // place of an array and be treated as if it were an array of
  192 + // one element. This makes it possible to decide later that
  193 + // something that used to contain a single element now allows
  194 + // an array without invalidating any old data.
187 195 // * If the current value is a dictionary, this object must have
188 196 // a dictionary in the same place with the same keys. If flags
189 197 // contains f_optional, a key in the schema does not have to
... ...
libqpdf/JSON.cc
... ... @@ -538,26 +538,27 @@ JSON::checkSchemaInternal(
538 538 }
539 539 }
540 540 } else if (sch_arr) {
541   - if (!this_arr) {
542   - QTC::TC("libtests", "JSON wanted array");
543   - errors.push_back(err_prefix + " is supposed to be an array");
544   - return false;
545   - }
546 541 if (sch_arr->elements.size() != 1) {
547 542 QTC::TC("libtests", "JSON schema array error");
548 543 errors.push_back(
549 544 err_prefix + " schema array contains other than one item");
550 545 return false;
551 546 }
552   - int i = 0;
553   - for (auto const& element: this_arr->elements) {
  547 + if (this_arr) {
  548 + int i = 0;
  549 + for (auto const& element: this_arr->elements) {
  550 + checkSchemaInternal(
  551 + element.get(),
  552 + sch_arr->elements.at(0).get(),
  553 + flags,
  554 + errors,
  555 + prefix + "." + QUtil::int_to_string(i));
  556 + ++i;
  557 + }
  558 + } else {
  559 + QTC::TC("libtests", "JSON schema array for single item");
554 560 checkSchemaInternal(
555   - element.get(),
556   - sch_arr->elements.at(0).get(),
557   - flags,
558   - errors,
559   - prefix + "." + QUtil::int_to_string(i));
560   - ++i;
  561 + this_v, sch_arr->elements.at(0).get(), flags, errors, prefix);
561 562 }
562 563 } else if (!sch_str) {
563 564 QTC::TC("libtests", "JSON schema other type");
... ...
libtests/json.cc
... ... @@ -162,7 +162,9 @@ test_schema()
162 162 "x": "ecks"
163 163 },
164 164 "s": [
165   - "esses"
  165 + {
  166 + "ss": "esses"
  167 + }
166 168 ]
167 169 }
168 170 },
... ... @@ -236,6 +238,8 @@ test_schema()
236 238 JSON bad_schema = JSON::parse(R"({"a": true, "b": "potato?"})");
237 239 check_schema(bad_schema, bad_schema, 0, false, "bad schema field type");
238 240  
  241 + // "two" exercises the case of the JSON containing a single
  242 + // element where the schema has an array.
239 243 JSON good = JSON::parse(R"(
240 244 {
241 245 "one": {
... ... @@ -245,17 +249,15 @@ test_schema()
245 249 "x": [1, null]
246 250 },
247 251 "s": [
248   - null,
249   - "anything"
  252 + {"ss": null},
  253 + {"ss": "anything"}
250 254 ]
251 255 }
252 256 },
253   - "two": [
254   - {
255   - "glarp": "enspliel",
256   - "goose": 3.14
257   - }
258   - ],
  257 + "two": {
  258 + "glarp": "enspliel",
  259 + "goose": 3.14
  260 + },
259 261 "three": {
260 262 "<objid>": {
261 263 "z": "ebra"
... ...
libtests/libtests.testcov
... ... @@ -36,7 +36,6 @@ Pl_PNGFilter decodePaeth 0
36 36 Pl_TIFFPredictor processRow 1
37 37 JSON wanted dictionary 0
38 38 JSON key missing in object 0
39   -JSON wanted array 0
40 39 JSON schema array error 0
41 40 JSON key extra in object 0
42 41 QPDFArgParser read args from stdin 0
... ... @@ -93,3 +92,4 @@ JSON 16 high high 0
93 92 JSON 16 low not after high 0
94 93 JSON 16 dangling high 0
95 94 JSON parse duplicate key 0
  95 +JSON schema array for single item 0
... ...
libtests/qtest/json/json.out
... ... @@ -4,7 +4,8 @@ top-level object is supposed to be a dictionary
4 4 --- missing items
5 5 json key ".one.a": key "q" is present in schema but missing in object
6 6 json key ".one.a.r" is supposed to be a dictionary
7   -json key ".one.a.s" is supposed to be an array
  7 +json key ".one.a.s": key "ss" is present in schema but missing in object
  8 +json key ".one.a.s": key "z" is not present in schema but appears in object
8 9 json key ".one.a": key "t" is not present in schema but appears in object
9 10 json key ".three.anything": key "z" is present in schema but missing in object
10 11 json key ".three.anything": key "x" is not present in schema but appears in object
... ...