cuelang.org/go@v0.10.1/encoding/jsonschema/constraints_object.go (about) 1 // Copyright 2019 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package jsonschema 16 17 import ( 18 "cuelang.org/go/cue" 19 "cuelang.org/go/cue/ast" 20 "cuelang.org/go/cue/token" 21 "cuelang.org/go/internal" 22 ) 23 24 // Object constraints 25 26 func constraintAdditionalProperties(key string, n cue.Value, s *state) { 27 switch n.Kind() { 28 case cue.BoolKind: 29 s.closeStruct = !s.boolValue(n) 30 31 case cue.StructKind: 32 s.closeStruct = true 33 obj := s.object(n) 34 if len(obj.Elts) == 0 { 35 obj.Elts = append(obj.Elts, &ast.Field{ 36 Label: ast.NewList(ast.NewIdent("string")), 37 Value: s.schema(n), 38 }) 39 return 40 } 41 // [!~(properties|patternProperties)]: schema 42 existing := append(s.patterns, excludeFields(obj.Elts)) 43 f := internal.EmbedStruct(ast.NewStruct(&ast.Field{ 44 Label: ast.NewList(ast.NewBinExpr(token.AND, existing...)), 45 Value: s.schema(n), 46 })) 47 obj.Elts = append(obj.Elts, f) 48 49 default: 50 s.errf(n, `value of "additionalProperties" must be an object or boolean`) 51 } 52 } 53 54 func constraintDependencies(key string, n cue.Value, s *state) { 55 // Schema and property dependencies. 56 // TODO: the easiest implementation is with comprehensions. 57 // The nicer implementation is with disjunctions. This has to be done 58 // at the very end, replacing properties. 59 /* 60 *{ property?: _|_ } | { 61 property: _ 62 schema 63 } 64 */ 65 } 66 67 func constraintMaxProperties(key string, n cue.Value, s *state) { 68 pkg := s.addImport(n, "struct") 69 x := ast.NewCall(ast.NewSel(pkg, "MaxFields"), s.uint(n)) 70 s.add(n, objectType, x) 71 } 72 func constraintPatternProperties(key string, n cue.Value, s *state) { 73 if n.Kind() != cue.StructKind { 74 s.errf(n, `value of "patternProperties" must be an object, found %v`, n.Kind()) 75 } 76 obj := s.object(n) 77 existing := excludeFields(s.obj.Elts) 78 s.processMap(n, func(key string, n cue.Value) { 79 // [!~(properties) & pattern]: schema 80 s.patterns = append(s.patterns, 81 &ast.UnaryExpr{Op: token.NMAT, X: ast.NewString(key)}) 82 f := internal.EmbedStruct(ast.NewStruct(&ast.Field{ 83 Label: ast.NewList(ast.NewBinExpr(token.AND, 84 &ast.UnaryExpr{Op: token.MAT, X: ast.NewString(key)}, 85 existing)), 86 Value: s.schema(n), 87 })) 88 ast.SetRelPos(f, token.NewSection) 89 obj.Elts = append(obj.Elts, f) 90 }) 91 } 92 93 func constraintProperties(key string, n cue.Value, s *state) { 94 obj := s.object(n) 95 96 if n.Kind() != cue.StructKind { 97 s.errf(n, `"properties" expected an object, found %v`, n.Kind()) 98 } 99 100 s.processMap(n, func(key string, n cue.Value) { 101 // property?: value 102 name := ast.NewString(key) 103 expr, state := s.schemaState(n, allTypes, []label{{name: key}}, false) 104 f := &ast.Field{Label: name, Value: expr} 105 state.doc(f) 106 f.Optional = token.Blank.Pos() 107 if len(obj.Elts) > 0 && len(f.Comments()) > 0 { 108 // TODO: change formatter such that either a NewSection on the 109 // field or doc comment will cause a new section. 110 ast.SetRelPos(f.Comments()[0], token.NewSection) 111 } 112 if state.deprecated { 113 switch expr.(type) { 114 case *ast.StructLit: 115 obj.Elts = append(obj.Elts, addTag(name, "deprecated", "")) 116 default: 117 f.Attrs = append(f.Attrs, internal.NewAttr("deprecated", "")) 118 } 119 } 120 obj.Elts = append(obj.Elts, f) 121 s.setField(label{name: key}, f) 122 }) 123 } 124 125 func constraintPropertyNames(key string, n cue.Value, s *state) { 126 // [=~pattern]: _ 127 if names, _ := s.schemaState(n, cue.StringKind, nil, false); !isAny(names) { 128 x := ast.NewStruct(ast.NewList(names), ast.NewIdent("_")) 129 s.add(n, objectType, x) 130 } 131 } 132 133 func constraintRequired(key string, n cue.Value, s *state) { 134 if n.Kind() != cue.ListKind { 135 s.errf(n, `value of "required" must be list of strings, found %v`, n.Kind()) 136 return 137 } 138 139 // TODO: detect that properties is defined somewhere. 140 // s.errf(n, `"required" without a "properties" field`) 141 obj := s.object(n) 142 143 // Create field map 144 fields := map[string]*ast.Field{} 145 for _, d := range obj.Elts { 146 f, ok := d.(*ast.Field) 147 if !ok { 148 continue // Could be embedding? See cirrus.json 149 } 150 str, _, err := ast.LabelName(f.Label) 151 if err == nil { 152 fields[str] = f 153 } 154 } 155 156 for _, n := range s.listItems("required", n, true) { 157 str, ok := s.strValue(n) 158 f := fields[str] 159 if f == nil && ok { 160 f := &ast.Field{ 161 Label: ast.NewString(str), 162 Value: ast.NewIdent("_"), 163 Constraint: token.NOT, 164 } 165 fields[str] = f 166 obj.Elts = append(obj.Elts, f) 167 continue 168 } 169 if f.Optional == token.NoPos { 170 s.errf(n, "duplicate required field %q", str) 171 } 172 f.Constraint = token.NOT 173 f.Optional = token.NoPos 174 } 175 }