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  }