this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

encoding/jsonschema: fix two bugs in CUE-to-JSON-Schema generation

Fix two pre-existing bugs in the JSON Schema generator:

1. Use itemItemsBounds (maxItems/minItems) instead of itemLengthBounds
(maxLength/minLength) when generating array length constraints for
closed lists with prefix items. The old code incorrectly generated
string length keywords for array length bounds.

2. Add support for generating uniqueItems from list.UniqueItems().
Previously, list.UniqueItems() was not recognized and silently
mapped to a true schema, losing the uniqueness constraint during
CUE-to-JSON-Schema generation.

Also cater for cases where we know that something is of list type
but we don't know have a size or an ellipsis. The behavior is still
not correct (it's unduly lax) but it's better than the error.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I477023f63843776f3709e264260b7c6a9e683dbe
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1232502
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+130 -73
+2 -2
encoding/jsonschema/external_teststats.txt
··· 9 9 10 10 v3-roundtrip: 11 11 schema extract (pass / total): 240 / 1363 = 17.6% 12 - tests (pass / total): 845 / 4803 = 17.6% 13 - tests on extracted schemas (pass / total): 845 / 920 = 91.8% 12 + tests (pass / total): 860 / 4803 = 17.9% 13 + tests on extracted schemas (pass / total): 860 / 920 = 93.5% 14 14 15 15 Optional tests: 16 16
+14 -3
encoding/jsonschema/generate.go
··· 652 652 g.unique.intern(&itemFormat{format: format}), 653 653 }, 654 654 } 655 + case "list.UniqueItems", "list.UniqueItems()": 656 + return &itemAllOf{ 657 + elems: []internItem{ 658 + g.unique.intern(&itemType{kinds: []string{"array"}}), 659 + g.unique.intern(&itemUniqueItems{}), 660 + }, 661 + } 655 662 case "list.MinItems", "list.MaxItems": 656 663 if len(args) != 2 { 657 664 g.addError(v, fmt.Errorf("%s expects 1 argument, got %d", funcName, len(args)-1)) ··· 1055 1062 var err error 1056 1063 n, err = lenv.Int64() 1057 1064 if err != nil { 1058 - g.addErrorf(v, "cannot extract concrete list length from %v: %v", v, err) 1065 + // This can happen legitimately when we know that the type is a list 1066 + // but we don't know anything about the number of items, 1067 + // for example, a list validator. We'll treat this as if it's [... _] 1068 + n = 0 1069 + ellipsis = v.Context().CompileString("_") 1059 1070 } 1060 1071 } 1061 1072 prefix := make([]internItem, n) ··· 1072 1083 } 1073 1084 items := &itemItems{} 1074 1085 if len(prefix) > 0 { 1075 - a.elems = append(a.elems, g.unique.intern(&itemLengthBounds{ 1086 + a.elems = append(a.elems, g.unique.intern(&itemItemsBounds{ 1076 1087 constraint: cue.GreaterThanEqualOp, 1077 1088 n: len(prefix), 1078 1089 })) ··· 1081 1092 if ellipsis.Exists() { 1082 1093 items.rest = trueAsNil(g.makeItem(ellipsis, mode)) 1083 1094 } else { 1084 - a.elems = append(a.elems, g.unique.intern(&itemLengthBounds{ 1095 + a.elems = append(a.elems, g.unique.intern(&itemItemsBounds{ 1085 1096 constraint: cue.LessThanEqualOp, 1086 1097 n: len(prefix), 1087 1098 }))
+14
encoding/jsonschema/generate_items.go
··· 566 566 return &itemItems{prefix: prefix, rest: rest} 567 567 } 568 568 569 + // itemUniqueItems represents the uniqueItems constraint for arrays. 570 + type itemUniqueItems struct{} 571 + 572 + func (it *itemUniqueItems) hash(h *maphash.Hash, u *uniqueItems) { 573 + } 574 + 575 + func (i *itemUniqueItems) generate(g *generator) ast.Expr { 576 + return singleKeyword("uniqueItems", ast.NewBool(true)) 577 + } 578 + 579 + func (i *itemUniqueItems) apply(f func(internItem, *uniqueItems) internItem, u *uniqueItems) item { 580 + return i 581 + } 582 + 569 583 // itemContains represents a contains constraint for arrays 570 584 type itemContains struct { 571 585 elem internItem
+15 -60
encoding/jsonschema/testdata/external/tests/draft2020-12/uniqueItems.json
··· 20 20 1, 21 21 1 22 22 ], 23 - "valid": false, 24 - "skip": { 25 - "v3-roundtrip": "unexpected success" 26 - } 23 + "valid": false 27 24 }, 28 25 { 29 26 "description": "non-unique array of more than two integers is invalid", ··· 32 29 2, 33 30 1 34 31 ], 35 - "valid": false, 36 - "skip": { 37 - "v3-roundtrip": "unexpected success" 38 - } 32 + "valid": false 39 33 }, 40 34 { 41 35 "description": "numbers are unique if mathematically unequal", ··· 44 38 1.00, 45 39 1 46 40 ], 47 - "valid": false, 48 - "skip": { 49 - "v3-roundtrip": "unexpected success" 50 - } 41 + "valid": false 51 42 }, 52 43 { 53 44 "description": "false is not equal to zero", ··· 81 72 "bar", 82 73 "foo" 83 74 ], 84 - "valid": false, 85 - "skip": { 86 - "v3-roundtrip": "unexpected success" 87 - } 75 + "valid": false 88 76 }, 89 77 { 90 78 "description": "unique array of objects is valid", ··· 108 96 "foo": "bar" 109 97 } 110 98 ], 111 - "valid": false, 112 - "skip": { 113 - "v3-roundtrip": "unexpected success" 114 - } 99 + "valid": false 115 100 }, 116 101 { 117 102 "description": "property order of array of objects is ignored", ··· 125 110 "foo": "bar" 126 111 } 127 112 ], 128 - "valid": false, 129 - "skip": { 130 - "v3-roundtrip": "unexpected success" 131 - } 113 + "valid": false 132 114 }, 133 115 { 134 116 "description": "unique array of nested objects is valid", ··· 168 150 } 169 151 } 170 152 ], 171 - "valid": false, 172 - "skip": { 173 - "v3-roundtrip": "unexpected success" 174 - } 153 + "valid": false 175 154 }, 176 155 { 177 156 "description": "unique array of arrays is valid", ··· 195 174 "foo" 196 175 ] 197 176 ], 198 - "valid": false, 199 - "skip": { 200 - "v3-roundtrip": "unexpected success" 201 - } 177 + "valid": false 202 178 }, 203 179 { 204 180 "description": "non-unique array of more than two arrays is invalid", ··· 213 189 "foo" 214 190 ] 215 191 ], 216 - "valid": false, 217 - "skip": { 218 - "v3-roundtrip": "unexpected success" 219 - } 192 + "valid": false 220 193 }, 221 194 { 222 195 "description": "1 and true are unique", ··· 320 293 {}, 321 294 1 322 295 ], 323 - "valid": false, 324 - "skip": { 325 - "v3-roundtrip": "unexpected success" 326 - } 296 + "valid": false 327 297 }, 328 298 { 329 299 "description": "different objects are unique", ··· 351 321 "a": 1 352 322 } 353 323 ], 354 - "valid": false, 355 - "skip": { 356 - "v3-roundtrip": "unexpected success" 357 - } 324 + "valid": false 358 325 }, 359 326 { 360 327 "description": "{\"a\": false} and {\"a\": 0} are unique", ··· 419 386 false, 420 387 false 421 388 ], 422 - "valid": false, 423 - "skip": { 424 - "v3-roundtrip": "unexpected success" 425 - } 389 + "valid": false 426 390 }, 427 391 { 428 392 "description": "[true, true] from items array is not valid", ··· 430 394 true, 431 395 true 432 396 ], 433 - "valid": false, 434 - "skip": { 435 - "v3-roundtrip": "unexpected success" 436 - } 397 + "valid": false 437 398 }, 438 399 { 439 400 "description": "unique array extended from [false, true] is valid", ··· 463 424 "foo", 464 425 "foo" 465 426 ], 466 - "valid": false, 467 - "skip": { 468 - "v3-roundtrip": "unexpected success" 469 - } 427 + "valid": false 470 428 }, 471 429 { 472 430 "description": "non-unique array extended from [true, false] is not valid", ··· 476 434 "foo", 477 435 "foo" 478 436 ], 479 - "valid": false, 480 - "skip": { 481 - "v3-roundtrip": "unexpected success" 482 - } 437 + "valid": false 483 438 } 484 439 ] 485 440 },
+7 -8
encoding/jsonschema/testdata/generate/lists.txtar
··· 66 66 }, { 67 67 type: "boolean" 68 68 }] 69 - maxLength: 3 70 - minLength: 3 69 + maxItems: 3 70 + minItems: 3 71 71 } 72 72 numberList: { 73 73 type: "array" ··· 88 88 }, { 89 89 type: "integer" 90 90 }] 91 - maxLength: 2 92 - minLength: 2 91 + maxItems: 2 92 + minItems: 2 93 93 } 94 94 } 95 95 } ··· 102 102 ./datatest/tests.cue:14:27 103 103 -- out/generate-v3/badTupleLength -- 104 104 badTupleLength.data.tuple: incompatible list lengths (1 and 3): 105 - 1:406 105 + 1:404 106 + badTupleLength.data.tuple: incompatible list lengths (1 and 3): 107 + 1:470 106 108 -- out/generate-v3/badTupleType -- 107 109 badTupleType.data.tuple.0: conflicting values 1 and string (mismatched types int and string): 108 110 ./datatest/tests.cue:24:16 109 - badTupleType.data.tuple.1: conflicting values "bar" and int (mismatched types string and int): 110 - ./datatest/tests.cue:24:19 111 - 1:440 112 111 -- out/generate-v3/ok1 -- 113 112 -- out/generate-v3/ok2 --
+78
encoding/jsonschema/testdata/generate/uniqueitems.txtar
··· 1 + -- test.cue -- 2 + package test 3 + 4 + import "list" 5 + 6 + uniqueList?: list.UniqueItems() 7 + 8 + uniqueListNoCall?: list.UniqueItems 9 + 10 + // TODO this doesn't work - the code currently just looks 11 + // for call expressions but this isn't a call expression. 12 + uniqueBools?: list.UniqueItems() & [bool, bool] 13 + 14 + -- datatest/tests.cue -- 15 + package datatest 16 + 17 + ok: data: { 18 + uniqueList: [1, 2, 3] 19 + uniqueListNoCall: [1, 2] 20 + uniqueBools: [true, false] 21 + } 22 + 23 + badUnique: { 24 + data: uniqueList: [1, 1, 2] 25 + error: true 26 + } 27 + 28 + badUniqueNoCall: { 29 + data: uniqueListNoCall: [1, 1, 2] 30 + // TODO this should be an error. 31 + //error: true 32 + } 33 + 34 + badUniqueBools: { 35 + data: uniqueBools: [true, true] 36 + error: true 37 + } 38 + -- out/generate-v3/schema -- 39 + { 40 + $schema: "https://json-schema.org/draft/2020-12/schema" 41 + $defs: { 42 + UniqueItems: { 43 + type: "array" 44 + } 45 + } 46 + type: "object" 47 + properties: { 48 + uniqueBools: { 49 + type: "array" 50 + prefixItems: [{ 51 + type: "boolean" 52 + }, { 53 + type: "boolean" 54 + }] 55 + maxItems: 2 56 + minItems: 2 57 + uniqueItems: true 58 + } 59 + uniqueList: { 60 + type: "array" 61 + uniqueItems: true 62 + } 63 + uniqueListNoCall: { 64 + $ref: "#/$defs/UniqueItems" 65 + } 66 + } 67 + } 68 + -- out/generate-v3/ok -- 69 + -- out/generate-v3/badUnique -- 70 + badUnique.data.uniqueList: invalid value [1,1,2] (does not satisfy list.UniqueItems): equal value (1) at position 0 and 1: 71 + 1:289 72 + ./datatest/tests.cue:10:20 73 + -- out/generate-v3/badUniqueBools -- 74 + badUniqueBools.data.uniqueBools: invalid value [true,true] (does not satisfy list.UniqueItems): equal value (true) at position 0 and 1: 75 + 1:240 76 + ./datatest/tests.cue:21:21 77 + 1:214 78 + -- out/generate-v3/badUniqueNoCall --