cuelang.org/go@v0.10.1/encoding/jsonschema/decode.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  // TODO:
    18  // - replace converter from YAML to CUE to CUE (schema) to CUE.
    19  // - define OpenAPI definitions als CUE.
    20  
    21  import (
    22  	"fmt"
    23  	"net/url"
    24  	"sort"
    25  	"strings"
    26  
    27  	"cuelang.org/go/cue"
    28  	"cuelang.org/go/cue/ast"
    29  	"cuelang.org/go/cue/ast/astutil"
    30  	"cuelang.org/go/cue/errors"
    31  	"cuelang.org/go/cue/token"
    32  	"cuelang.org/go/internal"
    33  )
    34  
    35  // rootDefs defines the top-level name of the map of definitions that do not
    36  // have a valid identifier name.
    37  //
    38  // TODO: find something more principled, like allowing #."a-b" or `#a-b`.
    39  const rootDefs = "#"
    40  
    41  // A decoder converts JSON schema to CUE.
    42  type decoder struct {
    43  	cfg          *Config
    44  	errs         errors.Error
    45  	numID        int // for creating unique numbers: increment on each use
    46  	mapURLErrors map[string]bool
    47  }
    48  
    49  // addImport registers
    50  func (d *decoder) addImport(n cue.Value, pkg string) *ast.Ident {
    51  	spec := ast.NewImport(nil, pkg)
    52  	info, err := astutil.ParseImportSpec(spec)
    53  	if err != nil {
    54  		d.errf(cue.Value{}, "invalid import %q", pkg)
    55  	}
    56  	ident := ast.NewIdent(info.Ident)
    57  	ident.Node = spec
    58  	ast.SetPos(ident, n.Pos())
    59  
    60  	return ident
    61  }
    62  
    63  func (d *decoder) decode(v cue.Value) *ast.File {
    64  	f := &ast.File{}
    65  
    66  	if pkgName := d.cfg.PkgName; pkgName != "" {
    67  		pkg := &ast.Package{Name: ast.NewIdent(pkgName)}
    68  		f.Decls = append(f.Decls, pkg)
    69  	}
    70  
    71  	var a []ast.Decl
    72  
    73  	if d.cfg.Root == "" {
    74  		a = append(a, d.schema(nil, v)...)
    75  	} else {
    76  		ref := d.parseRef(token.NoPos, d.cfg.Root)
    77  		if ref == nil {
    78  			return f
    79  		}
    80  		var selectors []cue.Selector
    81  		for _, r := range ref {
    82  			selectors = append(selectors, cue.Str(r))
    83  		}
    84  		i, err := v.LookupPath(cue.MakePath(selectors...)).Fields()
    85  		if err != nil {
    86  			d.errs = errors.Append(d.errs, errors.Promote(err, ""))
    87  			return nil
    88  		}
    89  		for i.Next() {
    90  			ref := append(ref, i.Label())
    91  			lab := d.mapRef(i.Value().Pos(), "", ref)
    92  			if len(lab) == 0 {
    93  				return nil
    94  			}
    95  			decls := d.schema(lab, i.Value())
    96  			a = append(a, decls...)
    97  		}
    98  	}
    99  
   100  	f.Decls = append(f.Decls, a...)
   101  
   102  	_ = astutil.Sanitize(f)
   103  
   104  	return f
   105  }
   106  
   107  func (d *decoder) schema(ref []ast.Label, v cue.Value) (a []ast.Decl) {
   108  	root := state{decoder: d}
   109  
   110  	var name ast.Label
   111  	inner := len(ref) - 1
   112  
   113  	if inner >= 0 {
   114  		name = ref[inner]
   115  		root.isSchema = true
   116  	}
   117  
   118  	expr, state := root.schemaState(v, allTypes, nil, false)
   119  
   120  	tags := []string{}
   121  	if state.schemaVersionPresent {
   122  		// TODO use cue/literal.String
   123  		tags = append(tags, fmt.Sprintf("schema=%q", state.schemaVersion))
   124  	}
   125  
   126  	if name == nil {
   127  		if len(tags) > 0 {
   128  			body := strings.Join(tags, ",")
   129  			a = append(a, &ast.Attribute{
   130  				Text: fmt.Sprintf("@jsonschema(%s)", body)})
   131  		}
   132  
   133  		if state.deprecated {
   134  			a = append(a, &ast.Attribute{Text: "@deprecated()"})
   135  		}
   136  	} else {
   137  		if len(tags) > 0 {
   138  			a = append(a, addTag(name, "jsonschema", strings.Join(tags, ",")))
   139  		}
   140  
   141  		if state.deprecated {
   142  			a = append(a, addTag(name, "deprecated", ""))
   143  		}
   144  	}
   145  
   146  	if name != nil {
   147  		f := &ast.Field{
   148  			Label: name,
   149  			Value: expr,
   150  		}
   151  
   152  		a = append(a, f)
   153  	} else if st, ok := expr.(*ast.StructLit); ok {
   154  		a = append(a, st.Elts...)
   155  	} else {
   156  		a = append(a, &ast.EmbedDecl{Expr: expr})
   157  	}
   158  
   159  	state.doc(a[0])
   160  
   161  	for i := inner - 1; i >= 0; i-- {
   162  		a = []ast.Decl{&ast.Field{
   163  			Label: ref[i],
   164  			Value: &ast.StructLit{Elts: a},
   165  		}}
   166  		expr = ast.NewStruct(ref[i], expr)
   167  	}
   168  
   169  	if root.hasSelfReference {
   170  		return []ast.Decl{
   171  			&ast.EmbedDecl{Expr: ast.NewIdent(topSchema)},
   172  			&ast.Field{
   173  				Label: ast.NewIdent(topSchema),
   174  				Value: &ast.StructLit{Elts: a},
   175  			},
   176  		}
   177  	}
   178  
   179  	return a
   180  }
   181  
   182  func (d *decoder) errf(n cue.Value, format string, args ...interface{}) ast.Expr {
   183  	d.warnf(n.Pos(), format, args...)
   184  	return &ast.BadExpr{From: n.Pos()}
   185  }
   186  
   187  func (d *decoder) warnf(p token.Pos, format string, args ...interface{}) {
   188  	d.addErr(errors.Newf(p, format, args...))
   189  }
   190  
   191  func (d *decoder) addErr(err errors.Error) {
   192  	d.errs = errors.Append(d.errs, err)
   193  }
   194  
   195  func (d *decoder) number(n cue.Value) ast.Expr {
   196  	return n.Syntax(cue.Final()).(ast.Expr)
   197  }
   198  
   199  func (d *decoder) uint(n cue.Value) ast.Expr {
   200  	_, err := n.Uint64()
   201  	if err != nil {
   202  		d.errf(n, "invalid uint")
   203  	}
   204  	return n.Syntax(cue.Final()).(ast.Expr)
   205  }
   206  
   207  func (d *decoder) boolValue(n cue.Value) bool {
   208  	x, err := n.Bool()
   209  	if err != nil {
   210  		d.errf(n, "invalid bool")
   211  	}
   212  	return x
   213  }
   214  
   215  func (d *decoder) string(n cue.Value) ast.Expr {
   216  	return n.Syntax(cue.Final()).(ast.Expr)
   217  }
   218  
   219  func (d *decoder) strValue(n cue.Value) (s string, ok bool) {
   220  	s, err := n.String()
   221  	if err != nil {
   222  		d.errf(n, "invalid string")
   223  		return "", false
   224  	}
   225  	return s, true
   226  }
   227  
   228  // const draftCutoff = 5
   229  
   230  type coreType int
   231  
   232  const (
   233  	nullType coreType = iota
   234  	boolType
   235  	numType
   236  	stringType
   237  	arrayType
   238  	objectType
   239  
   240  	numCoreTypes
   241  )
   242  
   243  var coreToCUE = []cue.Kind{
   244  	nullType:   cue.NullKind,
   245  	boolType:   cue.BoolKind,
   246  	numType:    cue.NumberKind, // Note: both int and float.
   247  	stringType: cue.StringKind,
   248  	arrayType:  cue.ListKind,
   249  	objectType: cue.StructKind,
   250  }
   251  
   252  func kindToAST(k cue.Kind) ast.Expr {
   253  	switch k {
   254  	case cue.NullKind:
   255  		// TODO: handle OpenAPI restrictions.
   256  		return ast.NewNull()
   257  	case cue.BoolKind:
   258  		return ast.NewIdent("bool")
   259  	case cue.NumberKind:
   260  		return ast.NewIdent("number")
   261  	case cue.IntKind:
   262  		return ast.NewIdent("int")
   263  	case cue.FloatKind:
   264  		return ast.NewIdent("float")
   265  	case cue.StringKind:
   266  		return ast.NewIdent("string")
   267  	case cue.ListKind:
   268  		return ast.NewList(&ast.Ellipsis{})
   269  	case cue.StructKind:
   270  		return ast.NewStruct(&ast.Ellipsis{})
   271  	}
   272  	panic(fmt.Errorf("unexpected kind %v", k))
   273  }
   274  
   275  var coreTypeName = []string{
   276  	nullType:   "null",
   277  	boolType:   "bool",
   278  	numType:    "number",
   279  	stringType: "string",
   280  	arrayType:  "array",
   281  	objectType: "object",
   282  }
   283  
   284  type constraintInfo struct {
   285  	// typ is an identifier for the root type, if present.
   286  	// This can be omitted if there are constraints.
   287  	typ         ast.Expr
   288  	constraints []ast.Expr
   289  }
   290  
   291  func (c *constraintInfo) setTypeUsed(n cue.Value, t coreType) {
   292  	c.typ = kindToAST(coreToCUE[t])
   293  	setPos(c.typ, n)
   294  	ast.SetRelPos(c.typ, token.NoRelPos)
   295  }
   296  
   297  func (c *constraintInfo) add(n cue.Value, x ast.Expr) {
   298  	if !isAny(x) {
   299  		setPos(x, n)
   300  		ast.SetRelPos(x, token.NoRelPos)
   301  		c.constraints = append(c.constraints, x)
   302  	}
   303  }
   304  
   305  func (s *state) add(n cue.Value, t coreType, x ast.Expr) {
   306  	s.types[t].add(n, x)
   307  }
   308  
   309  func (s *state) setTypeUsed(n cue.Value, t coreType) {
   310  	if int(t) >= len(s.types) {
   311  		panic(fmt.Errorf("type out of range %v/%v", int(t), len(s.types)))
   312  	}
   313  	s.types[t].setTypeUsed(n, t)
   314  }
   315  
   316  type state struct {
   317  	*decoder
   318  
   319  	isSchema bool // for omitting ellipsis in an ast.File
   320  
   321  	up     *state
   322  	parent *state
   323  
   324  	path []string
   325  
   326  	// idRef is used to refer to this schema in case it defines an $id.
   327  	idRef []label
   328  
   329  	pos cue.Value
   330  
   331  	// The constraints in types represent disjunctions per type.
   332  	types    [numCoreTypes]constraintInfo
   333  	all      constraintInfo // values and oneOf etc.
   334  	nullable *ast.BasicLit  // nullable
   335  
   336  	// allowedTypes holds the set of types that
   337  	// this node is allowed to be.
   338  	allowedTypes cue.Kind
   339  
   340  	// knownTypes holds the set of types that this node
   341  	// is known to be one of by virtue of the constraints inside
   342  	// all. This is used to avoid adding redundant elements
   343  	// to the disjunction created by [state.finalize].
   344  	knownTypes cue.Kind
   345  
   346  	default_     ast.Expr
   347  	examples     []ast.Expr
   348  	title        string
   349  	description  string
   350  	deprecated   bool
   351  	exclusiveMin bool // For OpenAPI and legacy support.
   352  	exclusiveMax bool // For OpenAPI and legacy support.
   353  
   354  	schemaVersion        schemaVersion
   355  	schemaVersionPresent bool
   356  
   357  	id    *url.URL // base URI for $ref
   358  	idPos token.Pos
   359  
   360  	definitions []ast.Decl
   361  
   362  	// Used for inserting definitions, properties, etc.
   363  	hasSelfReference bool
   364  	obj              *ast.StructLit
   365  	// Complete at finalize.
   366  	fieldRefs map[label]refs
   367  
   368  	closeStruct bool
   369  	patterns    []ast.Expr
   370  
   371  	list *ast.ListLit
   372  }
   373  
   374  type label struct {
   375  	name  string
   376  	isDef bool
   377  }
   378  
   379  type refs struct {
   380  	field *ast.Field
   381  	ident string
   382  	refs  []*ast.Ident
   383  }
   384  
   385  func (s *state) idTag() *ast.Attribute {
   386  	return &ast.Attribute{
   387  		At:   s.idPos,
   388  		Text: fmt.Sprintf("@jsonschema(id=%q)", s.id)}
   389  }
   390  
   391  func (s *state) object(n cue.Value) *ast.StructLit {
   392  	if s.obj == nil {
   393  		s.obj = &ast.StructLit{}
   394  
   395  		if s.id != nil {
   396  			s.obj.Elts = append(s.obj.Elts, s.idTag())
   397  		}
   398  		s.add(n, objectType, s.obj)
   399  	}
   400  	return s.obj
   401  }
   402  
   403  func (s *state) hasConstraints() bool {
   404  	if len(s.all.constraints) > 0 {
   405  		return true
   406  	}
   407  	for _, t := range s.types {
   408  		if len(t.constraints) > 0 {
   409  			return true
   410  		}
   411  	}
   412  	return len(s.patterns) > 0 ||
   413  		s.title != "" ||
   414  		s.description != "" ||
   415  		s.obj != nil ||
   416  		s.id != nil
   417  }
   418  
   419  const allTypes = cue.NullKind | cue.BoolKind | cue.NumberKind | cue.IntKind |
   420  	cue.StringKind | cue.ListKind | cue.StructKind
   421  
   422  // finalize constructs a CUE type from the collected constraints.
   423  func (s *state) finalize() (e ast.Expr) {
   424  	if s.allowedTypes == 0 {
   425  		// Nothing is possible.
   426  		s.addErr(errors.Newf(s.pos.Pos(), "constraints are not possible to satisfy"))
   427  		return &ast.BottomLit{}
   428  	}
   429  
   430  	conjuncts := []ast.Expr{}
   431  	disjuncts := []ast.Expr{}
   432  
   433  	// Sort literal structs and list last for nicer formatting.
   434  	sort.SliceStable(s.types[arrayType].constraints, func(i, j int) bool {
   435  		_, ok := s.types[arrayType].constraints[i].(*ast.ListLit)
   436  		return !ok
   437  	})
   438  	sort.SliceStable(s.types[objectType].constraints, func(i, j int) bool {
   439  		_, ok := s.types[objectType].constraints[i].(*ast.StructLit)
   440  		return !ok
   441  	})
   442  
   443  	type excludeInfo struct {
   444  		pos      token.Pos
   445  		typIndex int
   446  	}
   447  	var excluded []excludeInfo
   448  
   449  	needsTypeDisjunction := s.allowedTypes != s.knownTypes
   450  	if !needsTypeDisjunction {
   451  		for i, t := range s.types {
   452  			k := coreToCUE[i]
   453  			if len(t.constraints) > 0 && s.allowedTypes&k != 0 {
   454  				// We need to include at least one type-specific
   455  				// constraint in the disjunction.
   456  				needsTypeDisjunction = true
   457  				break
   458  			}
   459  		}
   460  	}
   461  
   462  	if needsTypeDisjunction {
   463  		npossible := 0
   464  		nexcluded := 0
   465  		for i, t := range s.types {
   466  			k := coreToCUE[i]
   467  			allowed := s.allowedTypes&k != 0
   468  			switch {
   469  			case len(t.constraints) > 0:
   470  				npossible++
   471  				if !allowed {
   472  					nexcluded++
   473  					for _, c := range t.constraints {
   474  						excluded = append(excluded, excludeInfo{c.Pos(), i})
   475  					}
   476  					continue
   477  				}
   478  				x := ast.NewBinExpr(token.AND, t.constraints...)
   479  				disjuncts = append(disjuncts, x)
   480  			case allowed:
   481  				npossible++
   482  				if s.knownTypes&k != 0 {
   483  					disjuncts = append(disjuncts, kindToAST(k))
   484  				}
   485  			}
   486  		}
   487  		if nexcluded == npossible {
   488  			// All possibilities have been excluded: this is an impossible
   489  			// schema.
   490  			for _, e := range excluded {
   491  				s.addErr(errors.Newf(e.pos,
   492  					"constraint not allowed because type %s is excluded",
   493  					coreTypeName[e.typIndex],
   494  				))
   495  			}
   496  		}
   497  	}
   498  	conjuncts = append(conjuncts, s.all.constraints...)
   499  	obj := s.obj
   500  	if obj == nil {
   501  		obj, _ = s.types[objectType].typ.(*ast.StructLit)
   502  	}
   503  	if obj != nil {
   504  		// TODO: may need to explicitly close.
   505  		if !s.closeStruct {
   506  			obj.Elts = append(obj.Elts, &ast.Ellipsis{})
   507  		}
   508  	}
   509  
   510  	if len(disjuncts) > 0 {
   511  		conjuncts = append(conjuncts, ast.NewBinExpr(token.OR, disjuncts...))
   512  	}
   513  
   514  	if len(conjuncts) == 0 {
   515  		// There are no conjuncts, which can only happen when there
   516  		// are no disjuncts, which can only happen when the entire
   517  		// set of disjuncts is redundant with respect to the types
   518  		// already implied by s.all. As we've already checked that
   519  		// s.allowedTypes is non-zero (so we know that
   520  		// it's not bottom) and we need _some_ expression
   521  		// to be part of the subequent syntax, we use top.
   522  		e = ast.NewIdent("_")
   523  	} else {
   524  		e = ast.NewBinExpr(token.AND, conjuncts...)
   525  	}
   526  
   527  	a := []ast.Expr{e}
   528  	if s.nullable != nil {
   529  		a = []ast.Expr{s.nullable, e}
   530  	}
   531  
   532  outer:
   533  	switch {
   534  	case s.default_ != nil:
   535  		// check conditions where default can be skipped.
   536  		switch x := s.default_.(type) {
   537  		case *ast.ListLit:
   538  			if s.allowedTypes == cue.ListKind && len(x.Elts) == 0 {
   539  				break outer
   540  			}
   541  		}
   542  		a = append(a, &ast.UnaryExpr{Op: token.MUL, X: s.default_})
   543  	}
   544  
   545  	e = ast.NewBinExpr(token.OR, a...)
   546  
   547  	if len(s.definitions) > 0 {
   548  		if st, ok := e.(*ast.StructLit); ok {
   549  			st.Elts = append(st.Elts, s.definitions...)
   550  		} else {
   551  			st = ast.NewStruct()
   552  			st.Elts = append(st.Elts, &ast.EmbedDecl{Expr: e})
   553  			st.Elts = append(st.Elts, s.definitions...)
   554  			e = st
   555  		}
   556  	}
   557  
   558  	s.linkReferences()
   559  
   560  	// If an "$id" exists and has not been included in any object constraints
   561  	if s.id != nil && s.obj == nil {
   562  		if st, ok := e.(*ast.StructLit); ok {
   563  			st.Elts = append([]ast.Decl{s.idTag()}, st.Elts...)
   564  		} else {
   565  			st = &ast.StructLit{Elts: []ast.Decl{s.idTag()}}
   566  			st.Elts = append(st.Elts, &ast.EmbedDecl{Expr: e})
   567  			e = st
   568  		}
   569  	}
   570  
   571  	// Now that we've expressed the schema as actual syntax,
   572  	// all the allowed types are actually explicit and will not
   573  	// need to be mentioned again.
   574  	s.knownTypes = s.allowedTypes
   575  	return e
   576  }
   577  
   578  func isAny(s ast.Expr) bool {
   579  	i, ok := s.(*ast.Ident)
   580  	return ok && i.Name == "_"
   581  }
   582  
   583  func (s *state) comment() *ast.CommentGroup {
   584  	// Create documentation.
   585  	doc := strings.TrimSpace(s.title)
   586  	if s.description != "" {
   587  		if doc != "" {
   588  			doc += "\n\n"
   589  		}
   590  		doc += s.description
   591  		doc = strings.TrimSpace(doc)
   592  	}
   593  	// TODO: add examples as well?
   594  	if doc == "" {
   595  		return nil
   596  	}
   597  	return internal.NewComment(true, doc)
   598  }
   599  
   600  func (s *state) doc(n ast.Node) {
   601  	doc := s.comment()
   602  	if doc != nil {
   603  		ast.SetComments(n, []*ast.CommentGroup{doc})
   604  	}
   605  }
   606  
   607  func (s *state) schema(n cue.Value, idRef ...label) ast.Expr {
   608  	expr, _ := s.schemaState(n, allTypes, idRef, false)
   609  	// TODO: report unused doc.
   610  	return expr
   611  }
   612  
   613  // schemaState returns a new state value derived from s.
   614  // n holds the JSONSchema node to translate to a schema.
   615  // types holds the set of possible types that the value can hold.
   616  // idRef holds the path to the value.
   617  // isLogical specifies whether the caller is a logical operator like anyOf, allOf, oneOf, or not.
   618  func (s *state) schemaState(n cue.Value, types cue.Kind, idRef []label, isLogical bool) (_e ast.Expr, _ *state) {
   619  	state := &state{
   620  		up:            s,
   621  		schemaVersion: s.schemaVersion,
   622  		isSchema:      s.isSchema,
   623  		decoder:       s.decoder,
   624  		allowedTypes:  types,
   625  		knownTypes:    allTypes,
   626  		path:          s.path,
   627  		idRef:         idRef,
   628  		pos:           n,
   629  	}
   630  	if isLogical {
   631  		state.parent = s
   632  	}
   633  
   634  	if n.Kind() != cue.StructKind {
   635  		return s.errf(n, "schema expects mapping node, found %s", n.Kind()), state
   636  	}
   637  
   638  	// do multiple passes over the constraints to ensure they are done in order.
   639  	for pass := 0; pass < 4; pass++ {
   640  		state.processMap(n, func(key string, value cue.Value) {
   641  			// Convert each constraint into a either a value or a functor.
   642  			c := constraintMap[key]
   643  			if c == nil {
   644  				if pass == 0 && s.cfg.Strict {
   645  					// TODO: value is not the correct position, albeit close. Fix this.
   646  					s.warnf(value.Pos(), "unsupported constraint %q", key)
   647  				}
   648  				return
   649  			}
   650  			if c.phase == pass {
   651  				c.fn(key, value, state)
   652  			}
   653  		})
   654  	}
   655  
   656  	return state.finalize(), state
   657  }
   658  
   659  func (s *state) value(n cue.Value) ast.Expr {
   660  	k := n.Kind()
   661  	switch k {
   662  	case cue.ListKind:
   663  		a := []ast.Expr{}
   664  		for i, _ := n.List(); i.Next(); {
   665  			a = append(a, s.value(i.Value()))
   666  		}
   667  		return setPos(ast.NewList(a...), n)
   668  
   669  	case cue.StructKind:
   670  		a := []ast.Decl{}
   671  		s.processMap(n, func(key string, n cue.Value) {
   672  			a = append(a, &ast.Field{
   673  				Label: ast.NewString(key),
   674  				Value: s.value(n),
   675  			})
   676  		})
   677  		// TODO: only open when s.isSchema?
   678  		a = append(a, &ast.Ellipsis{})
   679  		return setPos(&ast.StructLit{Elts: a}, n)
   680  
   681  	default:
   682  		if !n.IsConcrete() {
   683  			s.errf(n, "invalid non-concrete value")
   684  		}
   685  		return n.Syntax(cue.Final()).(ast.Expr)
   686  	}
   687  }
   688  
   689  // processMap processes a yaml node, expanding merges.
   690  //
   691  // TODO: in some cases we can translate merges into CUE embeddings.
   692  // This may also prevent exponential blow-up (as may happen when
   693  // converting YAML to JSON).
   694  func (s *state) processMap(n cue.Value, f func(key string, n cue.Value)) {
   695  	saved := s.path
   696  	defer func() { s.path = saved }()
   697  
   698  	// TODO: intercept references to allow for optimized performance.
   699  	for i, _ := n.Fields(); i.Next(); {
   700  		key := i.Label()
   701  		s.path = append(saved, key)
   702  		f(key, i.Value())
   703  	}
   704  }
   705  
   706  func (s *state) listItems(name string, n cue.Value, allowEmpty bool) (a []cue.Value) {
   707  	if n.Kind() != cue.ListKind {
   708  		s.errf(n, `value of %q must be an array, found %v`, name, n.Kind())
   709  	}
   710  	for i, _ := n.List(); i.Next(); {
   711  		a = append(a, i.Value())
   712  	}
   713  	if !allowEmpty && len(a) == 0 {
   714  		s.errf(n, `array for %q must be non-empty`, name)
   715  	}
   716  	return a
   717  }
   718  
   719  // excludeFields returns a CUE expression that can be used to exclude the
   720  // fields of the given declaration in a label expression. For instance, for
   721  //
   722  //	{ foo: 1, bar: int }
   723  //
   724  // it creates
   725  //
   726  //	"^(foo|bar)$"
   727  //
   728  // which can be used in a label expression to define types for all fields but
   729  // those existing:
   730  //
   731  //	[!~"^(foo|bar)$"]: string
   732  func excludeFields(decls []ast.Decl) ast.Expr {
   733  	var a []string
   734  	for _, d := range decls {
   735  		f, ok := d.(*ast.Field)
   736  		if !ok {
   737  			continue
   738  		}
   739  		str, _, _ := ast.LabelName(f.Label)
   740  		if str != "" {
   741  			a = append(a, str)
   742  		}
   743  	}
   744  	re := fmt.Sprintf("^(%s)$", strings.Join(a, "|"))
   745  	return &ast.UnaryExpr{Op: token.NMAT, X: ast.NewString(re)}
   746  }
   747  
   748  func addTag(field ast.Label, tag, value string) *ast.Field {
   749  	return &ast.Field{
   750  		Label: field,
   751  		Value: ast.NewIdent("_"),
   752  		Attrs: []*ast.Attribute{
   753  			{Text: fmt.Sprintf("@%s(%s)", tag, value)},
   754  		},
   755  	}
   756  }
   757  
   758  func setPos(e ast.Expr, v cue.Value) ast.Expr {
   759  	ast.SetPos(e, v.Pos())
   760  	return e
   761  }