this repo has no description
0
fork

Configure Feed

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

encoding/jsonschema: recognize closed structs in Generate

This uses the new `Value.IsClosed` API to determine whether to
specify `additionalProperties`.

Looking forward to Kubernetes structural schemas, we make
provision for avoiding `additionalProperties: false` by
adding a new configuration option `ExplicitOpen`.

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

+211 -20
+2 -2
encoding/jsonschema/external_teststats.txt
··· 9 9 10 10 v3-roundtrip: 11 11 schema extract (pass / total): 231 / 1363 = 16.9% 12 - tests (pass / total): 806 / 4803 = 16.8% 13 - tests on extracted schemas (pass / total): 806 / 895 = 90.1% 12 + tests (pass / total): 808 / 4803 = 16.8% 13 + tests on extracted schemas (pass / total): 808 / 895 = 90.3% 14 14 15 15 Optional tests: 16 16
+21
encoding/jsonschema/generate.go
··· 43 43 // 44 44 // If this is nil, [DefaultNameFunc] will be used. 45 45 NameFunc func(root cue.Value, path cue.Path) string 46 + 47 + // ExplicitOpen, when true, will never close a schema with `additionalProperties: false` 48 + // but _will_ explicitly open a schema with `additionalProperties: true` 49 + // when there is an explicit `...` or universal pattern in a struct. 50 + // 51 + // By default (when ExplicitOpen is false), all structs that are closed will 52 + // have an `additionalProperties: false` added. 53 + ExplicitOpen bool 46 54 } 47 55 48 56 // Generate generates a JSON Schema for the given CUE value, ··· 709 717 710 718 func (g *generator) makeStructItem(v cue.Value) item { 711 719 var props itemProperties 720 + 721 + ellipsis := v.LookupPath(cue.MakePath(cue.AnyString)) 722 + if ellipsis.Exists() { 723 + // All fields are explicitly allowed (either with `...` or `[_]: T`) 724 + props.additionalProperties = g.makeItem(ellipsis) 725 + if _, ok := props.additionalProperties.(*itemTrue); ok && !g.cfg.ExplicitOpen { 726 + // additionalProperties: true is a no-op in JSON Schema in general 727 + // so omit it unless we're explicitly opening up schemas. 728 + props.additionalProperties = nil 729 + } 730 + } else if v.IsClosed() && !g.cfg.ExplicitOpen { 731 + props.additionalProperties = &itemFalse{} 732 + } 712 733 713 734 // TODO include pattern constraints in the results when that's implemented 714 735 iter, err := v.Fields(cue.Optional(true))
+17 -5
encoding/jsonschema/generate_items.go
··· 493 493 494 494 // itemProperties represents object properties and associated keywords. 495 495 type itemProperties struct { 496 - elems []property 497 - required []string 498 - // TODO patternProperties, additionalProperties 496 + elems []property 497 + required []string 498 + additionalProperties item 499 + // TODO patternProperties 499 500 } 500 501 501 502 func (i *itemProperties) generate(g *generator) ast.Expr { ··· 510 511 reqExprs[j] = ast.NewString(r) 511 512 } 512 513 fields = append(fields, makeField("required", ast.NewList(reqExprs...))) 514 + } 515 + if i.additionalProperties != nil { 516 + fields = append(fields, makeField("additionalProperties", i.additionalProperties.generate(g))) 513 517 } 514 518 return makeSchemaStructLit(fields...) 515 519 } ··· 529 533 } 530 534 } 531 535 } 536 + additionalProperties := i.additionalProperties 537 + if additionalProperties != nil { 538 + if ap := f(additionalProperties); ap != additionalProperties { 539 + additionalProperties = ap 540 + changed = true 541 + } 542 + } 532 543 if !changed { 533 544 return i 534 545 } 535 546 return &itemProperties{ 536 - elems: elems, 537 - required: i.required, 547 + elems: elems, 548 + required: i.required, 549 + additionalProperties: additionalProperties, 538 550 } 539 551 } 540 552
+3 -1
encoding/jsonschema/generate_test.go
··· 46 46 if p, ok := t.Value("path"); ok { 47 47 v = v.LookupPath(cue.ParsePath(p)) 48 48 } 49 - r, err := jsonschema.Generate(v, nil) 49 + r, err := jsonschema.Generate(v, &jsonschema.GenerateConfig{ 50 + ExplicitOpen: t.HasTag("explicitOpen"), 51 + }) 50 52 qt.Assert(t, qt.IsNil(err)) 51 53 data, err := format.Node(r) 52 54 qt.Assert(t, qt.IsNil(err))
+1 -1
encoding/jsonschema/testdata/external/tests/draft2020-12/optional/id.json
··· 47 47 "data": "a string to match #/$defs/id_in_enum", 48 48 "valid": true, 49 49 "skip": { 50 - "v3-roundtrip": "conflicting values \"a string to match #/$defs/id_in_enum\" and {$id!:\"https://localhost:1234/draft2020-12/id/my_identifier.json\",type!:\"null\",...} (mismatched types string and struct):\n instance.json:1:1\nconflicting values \"a string to match #/$defs/id_in_enum\" and {...} (mismatched types string and struct):\n instance.json:1:1\ninvalid value \"a string to match #/$defs/id_in_enum\" (does not satisfy matchN): 0 matched, expected \u003e=1:\n instance.json:1:1\n" 50 + "v3-roundtrip": "conflicting values \"a string to match #/$defs/id_in_enum\" and {$id!:\"https://localhost:1234/draft2020-12/id/my_identifier.json\",type!:\"null\"} (mismatched types string and struct):\n instance.json:1:1\nconflicting values \"a string to match #/$defs/id_in_enum\" and {...} (mismatched types string and struct):\n instance.json:1:1\ninvalid value \"a string to match #/$defs/id_in_enum\" (does not satisfy matchN): 0 matched, expected \u003e=1:\n instance.json:1:1\n" 51 51 } 52 52 }, 53 53 {
+2 -8
encoding/jsonschema/testdata/external/tests/draft2020-12/ref.json
··· 32 32 "data": { 33 33 "bar": false 34 34 }, 35 - "valid": false, 36 - "skip": { 37 - "v3-roundtrip": "unexpected success" 38 - } 35 + "valid": false 39 36 }, 40 37 { 41 38 "description": "recursive mismatch", ··· 44 41 "bar": false 45 42 } 46 43 }, 47 - "valid": false, 48 - "skip": { 49 - "v3-roundtrip": "unexpected success" 50 - } 44 + "valid": false 51 45 } 52 46 ] 53 47 },
+3 -3
encoding/jsonschema/testdata/generate/definitions.txtar
··· 21 21 $schema: "https://json-schema.org/draft/2020-12/schema" 22 22 $defs: { 23 23 "#D1": { 24 - type: "object" 24 + type: "object" 25 + additionalProperties: false 25 26 properties: { 26 27 x: { 27 28 type: "integer" ··· 46 47 } 47 48 -- out/generate-v3/simple -- 48 49 -- out/generate-v3/missingX -- 49 - missingX.data.foo.x: field is required but not present: 50 - 1:91 50 + missingX.data.foo.x: field is required but not present
+80
encoding/jsonschema/testdata/generate/struct.txtar
··· 1 + -- test.cue -- 2 + package test 3 + t1?: #S1 4 + t2?: #S2 5 + t3?: close(_foo) 6 + t4?: {a?: int, ...} 7 + t5?: {[_]: int} 8 + t6?: {a?: int} 9 + 10 + #S1: { 11 + a?: int 12 + } 13 + #S2: { 14 + a?: int 15 + ... 16 + } 17 + _foo: a?: int 18 + 19 + -- out/generate-v3/schema -- 20 + { 21 + $schema: "https://json-schema.org/draft/2020-12/schema" 22 + $defs: { 23 + "#S1": { 24 + type: "object" 25 + additionalProperties: false 26 + properties: { 27 + a: { 28 + type: "integer" 29 + } 30 + } 31 + } 32 + "#S2": { 33 + type: "object" 34 + properties: { 35 + a: { 36 + type: "integer" 37 + } 38 + } 39 + } 40 + "_foo": { 41 + type: "object" 42 + properties: { 43 + a: { 44 + type: "integer" 45 + } 46 + } 47 + } 48 + } 49 + type: "object" 50 + properties: { 51 + t1: { 52 + $ref: "#/$defs/#S1" 53 + } 54 + t2: { 55 + $ref: "#/$defs/#S2" 56 + } 57 + t3: { 58 + $ref: "#/$defs/_foo" 59 + } 60 + t4: { 61 + type: "object" 62 + properties: { 63 + a: { 64 + type: "integer" 65 + } 66 + } 67 + } 68 + t5: { 69 + type: "object" 70 + } 71 + t6: { 72 + type: "object" 73 + properties: { 74 + a: { 75 + type: "integer" 76 + } 77 + } 78 + } 79 + } 80 + }
+82
encoding/jsonschema/testdata/generate/struct_explicitopen.txtar
··· 1 + #explicitOpen 2 + -- test.cue -- 3 + package test 4 + t1?: #S1 5 + t2?: #S2 6 + t3?: close(_foo) 7 + t4?: {a?: int, ...} 8 + t5?: {[_]: int} 9 + t6?: {a?: int} 10 + 11 + #S1: { 12 + a?: int 13 + } 14 + #S2: { 15 + a?: int 16 + ... 17 + } 18 + _foo: a?: int 19 + 20 + -- out/generate-v3/schema -- 21 + { 22 + $schema: "https://json-schema.org/draft/2020-12/schema" 23 + $defs: { 24 + "#S1": { 25 + type: "object" 26 + properties: { 27 + a: { 28 + type: "integer" 29 + } 30 + } 31 + } 32 + "#S2": { 33 + type: "object" 34 + additionalProperties: true 35 + properties: { 36 + a: { 37 + type: "integer" 38 + } 39 + } 40 + } 41 + "_foo": { 42 + type: "object" 43 + properties: { 44 + a: { 45 + type: "integer" 46 + } 47 + } 48 + } 49 + } 50 + type: "object" 51 + properties: { 52 + t1: { 53 + $ref: "#/$defs/#S1" 54 + } 55 + t2: { 56 + $ref: "#/$defs/#S2" 57 + } 58 + t3: { 59 + $ref: "#/$defs/_foo" 60 + } 61 + t4: { 62 + type: "object" 63 + additionalProperties: true 64 + properties: { 65 + a: { 66 + type: "integer" 67 + } 68 + } 69 + } 70 + t5: { 71 + type: "object" 72 + } 73 + t6: { 74 + type: "object" 75 + properties: { 76 + a: { 77 + type: "integer" 78 + } 79 + } 80 + } 81 + } 82 + }