this repo has no description
0
fork

Configure Feed

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

encoding/jsonschema: recognize different forms of "const"

There are several different forms of CUE that might correspond to
a JSON Schema `const` keyword's value.

This updates the Generate logic to recognize several of them,
including notably the form used by Extract to represent
constant structs: `close({a!: int})`.

This relies on the newly added `Patterns` option to determine
that a struct is actually closed.

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

+222 -85
+3 -3
encoding/jsonschema/external_teststats.txt
··· 8 8 tests on extracted schemas (pass / total): 3908 / 4041 = 96.7% 9 9 10 10 v3-roundtrip: 11 - schema extract (pass / total): 231 / 1363 = 16.9% 12 - tests (pass / total): 808 / 4803 = 16.8% 13 - tests on extracted schemas (pass / total): 808 / 895 = 90.3% 11 + schema extract (pass / total): 233 / 1363 = 17.1% 12 + tests (pass / total): 814 / 4803 = 16.9% 13 + tests on extracted schemas (pass / total): 814 / 900 = 90.4% 14 14 15 15 Optional tests: 16 16
+92 -20
encoding/jsonschema/generate.go
··· 349 349 case cue.CallOp: 350 350 return g.makeCallItem(v, args) 351 351 } 352 - if isConcreteScalar(v) && !v.IsNull() { 353 - if err := v.Err(); err != nil { 354 - g.addError(v, fmt.Errorf("error found in schema: %v", err)) 355 - return &itemFalse{} 356 - } 357 - syntax := v.Syntax() 358 - expr, ok := syntax.(ast.Expr) 359 - if !ok { 360 - g.addError(v, fmt.Errorf("expected expression from Syntax, got %T", syntax)) 361 - return &itemFalse{} 362 - } 363 - return &itemConst{ 364 - value: expr, 352 + if !v.IsNull() { 353 + // We want to encode null as {type: "null"} not {const: null} 354 + // so then there's a possibility of collapsing it together in 355 + // the same type keyword. 356 + if e, ok := g.constValueOf(v); ok { 357 + return &itemConst{ 358 + value: e, 359 + } 365 360 } 366 361 } 367 362 kind := v.IncompleteKind() ··· 395 390 } 396 391 } 397 392 393 + // constValueOf returns the "constant" value of a given 394 + // cue value. There are a few possible ways to represent 395 + // a JSON Schema const in CUE; some examples: 396 + // 397 + // true 398 + // ==true 399 + // close({a!: true}) // Note: this is the representation Extract uses 400 + // [==true] 401 + // [true] 402 + // 403 + // There's some overlap here with the unary == treatment 404 + // in [generator.makeItem] but in that case we know that 405 + // the argument must be constant, and this case we don't. 406 + func (g *generator) constValueOf(v cue.Value) (ast.Expr, bool) { 407 + // Check for unary == operator (e.g., ==1, ==true) 408 + op, args := v.Expr() 409 + if op == cue.EqualOp && len(args) == 1 { 410 + // It's a unary equals, recurse on the argument 411 + return g.constValueOf(args[0]) 412 + } 413 + 414 + switch kind := v.Kind(); kind { 415 + case cue.BottomKind: 416 + return nil, false 417 + case cue.StructKind: 418 + if !v.IsClosed() { 419 + // Open struct is not const. 420 + return nil, false 421 + } 422 + // Closed struct: all fields must be required (no optional fields) 423 + // and we need to recursively check all field values are const 424 + iter, err := v.Fields(cue.Optional(true), cue.Patterns(true)) 425 + if err != nil { 426 + return nil, false 427 + } 428 + var fields []ast.Decl 429 + for iter.Next() { 430 + sel := iter.Selector() 431 + // All fields must be required for the struct to be const 432 + if sel.ConstraintType() != cue.RequiredConstraint { 433 + return nil, false 434 + } 435 + // Recursively check if the field value is const 436 + fieldExpr, ok := g.constValueOf(iter.Value()) 437 + if !ok { 438 + return nil, false 439 + } 440 + // Create a regular field (not required marker) 441 + fields = append(fields, makeField(sel.Unquoted(), fieldExpr)) 442 + } 443 + return &ast.StructLit{Elts: fields}, true 444 + case cue.ListKind: 445 + if v.LookupPath(cue.MakePath(cue.AnyIndex)).Exists() { 446 + // Open list is not const. 447 + return nil, false 448 + } 449 + // Closed list: recursively check all elements are const 450 + iter, err := v.List() 451 + if err != nil { 452 + return nil, false 453 + } 454 + var elems []ast.Expr 455 + for iter.Next() { 456 + elemExpr, ok := g.constValueOf(iter.Value()) 457 + if !ok { 458 + return nil, false 459 + } 460 + elems = append(elems, elemExpr) 461 + } 462 + return &ast.ListLit{Elts: elems}, true 463 + } 464 + // For other kinds (atoms), if it's concrete, return its syntax 465 + if !v.IsConcrete() { 466 + return nil, false 467 + } 468 + expr, ok := v.Syntax().(ast.Expr) 469 + if !ok { 470 + return nil, false 471 + } 472 + return expr, true 473 + } 474 + 398 475 func (g *generator) makeCallItem(v cue.Value, args []cue.Value) item { 399 476 if len(args) < 1 { 400 477 // Invalid call - not enough arguments ··· 414 491 // we include "error()" as well as "error" 415 492 return &itemFalse{} 416 493 case "close": 417 - // TODO incorporate closedness into the model 418 - // For now, just treat close(x) the same as x. 419 - if len(args) != 2 { 420 - g.addError(v, fmt.Errorf("close expects 1 argument, got %d", len(args)-1)) 421 - return &itemFalse{} 422 - } 423 - return g.makeItem(args[1]) 494 + // We'll detect closedness with IsClosed further down. 495 + return g.makeItem(v.Eval()) 424 496 case "strings.MinRunes": 425 497 if len(args) != 2 { 426 498 g.addError(v, fmt.Errorf("strings.MinRunes expects 1 argument, got %d", len(args)-1))
+7 -11
encoding/jsonschema/testdata/external/tests/draft2020-12/additionalProperties.json
··· 27 27 "bar": 2, 28 28 "quux": "boom" 29 29 }, 30 - "valid": false, 31 - "skip": { 32 - "v3-roundtrip": "unexpected success" 33 - } 30 + "valid": false 34 31 }, 35 32 { 36 33 "description": "ignores arrays", ··· 57 54 "foo": 1, 58 55 "vroom": 2 59 56 }, 60 - "valid": true 57 + "valid": true, 58 + "skip": { 59 + "v3-roundtrip": "conflicting values [...] and {foo:1,vroom:2} (mismatched types list and struct):\n instance.json:1:1\nconflicting values bool and {foo:1,vroom:2} (mismatched types bool and struct):\n instance.json:1:1\nconflicting values null and {foo:1,vroom:2} (mismatched types null and struct):\n instance.json:1:1\nconflicting values number and {foo:1,vroom:2} (mismatched types number and struct):\n instance.json:1:1\nconflicting values string and {foo:1,vroom:2} (mismatched types string and struct):\n instance.json:1:1\ninvalid value {foo:1,vroom:2} (does not satisfy matchN): 0 matched, expected \u003e=1:\n instance.json:1:1\nvroom: field not allowed:\n instance.json:1:18\n" 60 + } 61 61 } 62 62 ] 63 63 }, ··· 304 304 "data": { 305 305 "bar": "" 306 306 }, 307 - "valid": false, 308 - "skip": { 309 - "v3-roundtrip": "unexpected success" 310 - } 307 + "valid": false 311 308 }, 312 309 { 313 310 "description": "additionalProperties can't see bar even when foo2 is present", ··· 317 314 }, 318 315 "valid": false, 319 316 "skip": { 320 - "v3": "unexpected success", 321 - "v3-roundtrip": "unexpected success" 317 + "v3": "unexpected success" 322 318 } 323 319 } 324 320 ]
+1 -4
encoding/jsonschema/testdata/external/tests/draft2020-12/enum.json
··· 67 67 "foo": 12, 68 68 "boo": 42 69 69 }, 70 - "valid": false, 71 - "skip": { 72 - "v3-roundtrip": "unexpected success" 73 - } 70 + "valid": false 74 71 } 75 72 ] 76 73 },
+3 -8
encoding/jsonschema/testdata/external/tests/draft2020-12/prefixItems.json
··· 78 78 false 79 79 ] 80 80 }, 81 - "skip": { 82 - "v3-roundtrip": "generate error: error found in schema: 1: disallowed" 83 - }, 84 81 "tests": [ 85 82 { 86 83 "description": "array with one item is valid", ··· 89 86 ], 90 87 "valid": true, 91 88 "skip": { 92 - "v3": "7 errors in empty disjunction:\nconflicting values [1] and bool (mismatched types list and bool):\n generated.cue:3:1\n generated.cue:3:8\n instance.json:1:1\nconflicting values [1] and null (mismatched types list and null):\n generated.cue:3:1\n instance.json:1:1\nconflicting values [1] and number (mismatched types list and number):\n generated.cue:3:1\n generated.cue:3:15\n instance.json:1:1\nconflicting values [1] and string (mismatched types list and string):\n generated.cue:3:1\n generated.cue:3:24\n instance.json:1:1\nconflicting values [1] and {...} (mismatched types list and struct):\n generated.cue:3:1\n generated.cue:3:65\n instance.json:1:1\nincompatible list lengths (1 and 3):\n generated.cue:3:33\n1: disallowed:\n generated.cue:3:37\n", 93 - "v3-roundtrip": "could not extract schema" 89 + "v3": "7 errors in empty disjunction:\nconflicting values [1] and bool (mismatched types list and bool):\n generated.cue:3:1\n generated.cue:3:8\n instance.json:1:1\nconflicting values [1] and null (mismatched types list and null):\n generated.cue:3:1\n instance.json:1:1\nconflicting values [1] and number (mismatched types list and number):\n generated.cue:3:1\n generated.cue:3:15\n instance.json:1:1\nconflicting values [1] and string (mismatched types list and string):\n generated.cue:3:1\n generated.cue:3:24\n instance.json:1:1\nconflicting values [1] and {...} (mismatched types list and struct):\n generated.cue:3:1\n generated.cue:3:65\n instance.json:1:1\nincompatible list lengths (1 and 3):\n generated.cue:3:33\n1: disallowed:\n generated.cue:3:37\n" 94 90 } 95 91 }, 96 92 { ··· 101 97 ], 102 98 "valid": false, 103 99 "skip": { 104 - "v3-roundtrip": "could not extract schema" 100 + "v3-roundtrip": "unexpected success" 105 101 } 106 102 }, 107 103 { ··· 109 105 "data": [], 110 106 "valid": true, 111 107 "skip": { 112 - "v3": "6 errors in empty disjunction:\nconflicting values [] and bool (mismatched types list and bool):\n generated.cue:3:1\n generated.cue:3:8\n instance.json:1:1\nconflicting values [] and null (mismatched types list and null):\n generated.cue:3:1\n instance.json:1:1\nconflicting values [] and number (mismatched types list and number):\n generated.cue:3:1\n generated.cue:3:15\n instance.json:1:1\nconflicting values [] and string (mismatched types list and string):\n generated.cue:3:1\n generated.cue:3:24\n instance.json:1:1\nconflicting values [] and {...} (mismatched types list and struct):\n generated.cue:3:1\n generated.cue:3:65\n instance.json:1:1\nincompatible list lengths (0 and 3):\n generated.cue:3:33\n", 113 - "v3-roundtrip": "could not extract schema" 108 + "v3": "6 errors in empty disjunction:\nconflicting values [] and bool (mismatched types list and bool):\n generated.cue:3:1\n generated.cue:3:8\n instance.json:1:1\nconflicting values [] and null (mismatched types list and null):\n generated.cue:3:1\n instance.json:1:1\nconflicting values [] and number (mismatched types list and number):\n generated.cue:3:1\n generated.cue:3:15\n instance.json:1:1\nconflicting values [] and string (mismatched types list and string):\n generated.cue:3:1\n generated.cue:3:24\n instance.json:1:1\nconflicting values [] and {...} (mismatched types list and struct):\n generated.cue:3:1\n generated.cue:3:65\n instance.json:1:1\nincompatible list lengths (0 and 3):\n generated.cue:3:33\n" 114 109 } 115 110 } 116 111 ]
+2 -6
encoding/jsonschema/testdata/external/tests/draft2020-12/propertyNames.json
··· 106 106 "$schema": "https://json-schema.org/draft/2020-12/schema", 107 107 "propertyNames": false 108 108 }, 109 - "skip": { 110 - "v3-roundtrip": "generate error: error found in schema: disallowed" 111 - }, 112 109 "tests": [ 113 110 { 114 111 "description": "object with any properties is invalid", ··· 118 115 "valid": false, 119 116 "skip": { 120 117 "v3": "unexpected success", 121 - "v3-roundtrip": "could not extract schema" 118 + "v3-roundtrip": "unexpected success" 122 119 } 123 120 }, 124 121 { ··· 126 123 "data": {}, 127 124 "valid": true, 128 125 "skip": { 129 - "v3": "disallowed:\n generated.cue:4:3\n generated.cue:3:1\n instance.json:1:1\n", 130 - "v3-roundtrip": "could not extract schema" 126 + "v3": "disallowed:\n generated.cue:4:3\n generated.cue:3:1\n instance.json:1:1\n" 131 127 } 132 128 } 133 129 ]
+84 -10
encoding/jsonschema/testdata/generate/const.txtar
··· 6 6 bool?: true 7 7 float?: 1.5 8 8 null?: null 9 + 10 + // Test different forms of const as mentioned in constValueOf doc comment: 11 + unaryEquals?: ==42 12 + closedStruct?: close({a!: "foo", b!: 123}) 13 + closedStructWithNull?: close({a!: null}) 14 + listWithUnaryEquals?: [==1, ==2, ==3] 15 + listConcrete?: ["a", "b", "c"] 9 16 -- datatest/tests.cue -- 10 17 package datatest 11 18 ··· 15 22 int: 2 16 23 null: null 17 24 string: "something" 25 + unaryEquals: 42 26 + closedStruct: {a: "foo", b: 123} 27 + listWithUnaryEquals: [1, 2, 3] 28 + listConcrete: ["a", "b", "c"] 18 29 } 19 30 errorFloatForInt: { 20 31 data: { ··· 45 56 data: string: "other" 46 57 error: true 47 58 } 59 + errorNonUnaryEquals: { 60 + data: unaryEquals: 99 61 + error: true 62 + } 63 + errorNonClosedStructFieldA: { 64 + data: closedStruct: {a: "bar", b: 123} 65 + error: true 66 + } 67 + errorNonClosedStructFieldB: { 68 + data: closedStruct: {a: "foo", b: 456} 69 + error: true 70 + } 71 + errorClosedStructExtraField: { 72 + data: closedStruct: {a: "foo", b: 123, c: "extra"} 73 + error: true 74 + } 75 + errorListWrongElement: { 76 + data: listWithUnaryEquals: [1, 99, 3] 77 + error: true 78 + } 79 + errorListWrongLength: { 80 + data: listConcrete: ["a", "b"] 81 + error: true 82 + } 48 83 -- out/generate-v3/schema -- 49 84 { 50 85 $schema: "https://json-schema.org/draft/2020-12/schema" ··· 53 88 bool: { 54 89 const: true 55 90 } 91 + closedStruct: { 92 + const: { 93 + a: "foo" 94 + b: 123 95 + } 96 + } 97 + closedStructWithNull: { 98 + const: { 99 + a: null 100 + } 101 + } 56 102 float: { 57 103 const: 1.5 58 104 } 59 105 int: { 60 106 const: 2 61 107 } 108 + listConcrete: { 109 + const: ["a", "b", "c"] 110 + } 111 + listWithUnaryEquals: { 112 + const: [1, 2, 3] 113 + } 62 114 null: { 63 115 type: "null" 64 116 } 65 117 string: { 66 118 const: "something" 67 119 } 120 + unaryEquals: { 121 + const: 42 122 + } 68 123 } 69 124 } 70 125 -- out/generate-v3/errorFloatForInt -- 71 126 errorFloatForInt.data.int: conflicting values 2.0 and 2 (mismatched types float and int): 72 - 1:140 73 - ./datatest/tests.cue:12:8 127 + 1:229 128 + ./datatest/tests.cue:16:8 74 129 -- out/generate-v3/errorNonBool -- 75 130 errorNonBool.data.bool: conflicting values "foo" and true (mismatched types string and bool): 76 131 1:97 77 - ./datatest/tests.cue:20:14 132 + ./datatest/tests.cue:24:14 78 133 -- out/generate-v3/errorNonEqualFloat -- 79 134 errorNonEqualFloat.data.float: conflicting values 1.5 and 1.6: 80 - 1:120 81 - ./datatest/tests.cue:24:15 135 + 1:209 136 + ./datatest/tests.cue:28:15 82 137 -- out/generate-v3/errorNonEqualInt -- 83 138 errorNonEqualInt.data.int: conflicting values 2 and 9: 84 - 1:140 85 - ./datatest/tests.cue:28:13 139 + 1:229 140 + ./datatest/tests.cue:32:13 86 141 -- out/generate-v3/errorNonNull -- 87 142 errorNonNull.data.null: conflicting values 1 and null (mismatched types int and null): 88 - ./datatest/tests.cue:32:14 143 + ./datatest/tests.cue:36:14 89 144 -- out/generate-v3/errorNonString -- 90 145 errorNonString.data.string: conflicting values "something" and "other": 91 - 1:184 92 - ./datatest/tests.cue:36:16 146 + 1:352 147 + ./datatest/tests.cue:40:16 93 148 -- out/generate-v3/ok1 -- 149 + -- out/generate-v3/errorClosedStructExtraField -- 150 + errorClosedStructExtraField.data.closedStruct.c: field not allowed: 151 + ./datatest/tests.cue:56:41 152 + -- out/generate-v3/errorListWrongElement -- 153 + errorListWrongElement.data.listWithUnaryEquals.1: conflicting values 2 and 99: 154 + ./datatest/tests.cue:60:33 155 + -- out/generate-v3/errorListWrongLength -- 156 + errorListWrongLength.data.listConcrete: incompatible list lengths (2 and 3): 157 + 1:256 158 + -- out/generate-v3/errorNonClosedStructFieldA -- 159 + errorNonClosedStructFieldA.data.closedStruct.a: conflicting values "foo" and "bar": 160 + ./datatest/tests.cue:48:26 161 + -- out/generate-v3/errorNonClosedStructFieldB -- 162 + errorNonClosedStructFieldB.data.closedStruct.b: conflicting values 123 and 456: 163 + ./datatest/tests.cue:52:36 164 + -- out/generate-v3/errorNonUnaryEquals -- 165 + errorNonUnaryEquals.data.unaryEquals: conflicting values 42 and 99: 166 + 1:388 167 + ./datatest/tests.cue:44:21
+3 -4
encoding/jsonschema/testdata/generate/lists.txtar
··· 52 52 type: "object" 53 53 properties: { 54 54 emptyList: { 55 - type: "array" 56 - maxLength: 0 55 + const: [] 57 56 } 58 57 emptyOpenList: { 59 58 type: "array" ··· 103 102 ./datatest/tests.cue:14:27 104 103 -- out/generate-v3/badTupleLength -- 105 104 badTupleLength.data.tuple: incompatible list lengths (1 and 3): 106 - 1:424 105 + 1:406 107 106 -- out/generate-v3/badTupleType -- 108 107 badTupleType.data.tuple.0: conflicting values 1 and string (mismatched types int and string): 109 108 ./datatest/tests.cue:24:16 110 109 badTupleType.data.tuple.1: conflicting values "bar" and int (mismatched types string and int): 111 - 1:458 110 + 1:440 112 111 ./datatest/tests.cue:24:19 113 112 -- out/generate-v3/ok1 -- 114 113 -- out/generate-v3/ok2 --
+21 -10
encoding/jsonschema/testdata/generate/struct.txtar
··· 6 6 t4?: {a?: int, ...} 7 7 t5?: {[_]: int} 8 8 t6?: {a?: int} 9 - 9 + t7?: close({a!: string, b!: int}) 10 10 #S1: { 11 11 a?: int 12 12 } ··· 37 37 } 38 38 } 39 39 } 40 - "_foo": { 41 - type: "object" 42 - properties: { 43 - a: { 44 - type: "integer" 45 - } 46 - } 47 - } 48 40 } 49 41 type: "object" 50 42 properties: { ··· 55 47 $ref: "#/$defs/#S2" 56 48 } 57 49 t3: { 58 - $ref: "#/$defs/_foo" 50 + type: "object" 51 + additionalProperties: false 52 + properties: { 53 + a: { 54 + type: "integer" 55 + } 56 + } 59 57 } 60 58 t4: { 61 59 type: "object" ··· 75 73 type: "integer" 76 74 } 77 75 } 76 + } 77 + t7: { 78 + type: "object" 79 + additionalProperties: false 80 + properties: { 81 + a: { 82 + type: "string" 83 + } 84 + b: { 85 + type: "integer" 86 + } 87 + } 88 + required: ["a", "b"] 78 89 } 79 90 } 80 91 }
+6 -9
encoding/jsonschema/testdata/generate/struct_explicitopen.txtar
··· 38 38 } 39 39 } 40 40 } 41 - "_foo": { 42 - type: "object" 43 - properties: { 44 - a: { 45 - type: "integer" 46 - } 47 - } 48 - } 49 41 } 50 42 type: "object" 51 43 properties: { ··· 56 48 $ref: "#/$defs/#S2" 57 49 } 58 50 t3: { 59 - $ref: "#/$defs/_foo" 51 + type: "object" 52 + properties: { 53 + a: { 54 + type: "integer" 55 + } 56 + } 60 57 } 61 58 t4: { 62 59 type: "object"