cuelang.org/go@v0.13.0/encoding/openapi/build.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 openapi
    16  
    17  import (
    18  	"cmp"
    19  	"fmt"
    20  	"maps"
    21  	"math"
    22  	"path"
    23  	"regexp"
    24  	"slices"
    25  	"strings"
    26  
    27  	"cuelang.org/go/cue"
    28  	"cuelang.org/go/cue/ast"
    29  	"cuelang.org/go/cue/errors"
    30  	"cuelang.org/go/cue/token"
    31  	"cuelang.org/go/internal"
    32  	"cuelang.org/go/internal/core/adt"
    33  )
    34  
    35  type buildContext struct {
    36  	inst      cue.Value
    37  	instExt   cue.Value
    38  	refPrefix string
    39  	path      []cue.Selector
    40  	errs      errors.Error
    41  
    42  	expandRefs    bool
    43  	structural    bool
    44  	exclusiveBool bool
    45  	nameFunc      func(inst cue.Value, path cue.Path) string
    46  	descFunc      func(v cue.Value) string
    47  	fieldFilter   *regexp.Regexp
    48  
    49  	schemas *orderedMap
    50  
    51  	// Track external schemas.
    52  	externalRefs map[string]*externalType
    53  
    54  	// Used for cycle detection in case of using ExpandReferences. At the
    55  	// moment, CUE does not detect cycles when a user forcefully steps into a
    56  	// pattern constraint.
    57  	//
    58  	// TODO: consider an option in the CUE API where optional fields are
    59  	// recursively evaluated.
    60  	cycleNodes []*adt.Vertex
    61  }
    62  
    63  type externalType struct {
    64  	ref   string
    65  	inst  cue.Value
    66  	path  cue.Path
    67  	value cue.Value
    68  }
    69  
    70  type typeFunc func(b *builder, a cue.Value)
    71  
    72  func schemas(g *Generator, inst cue.InstanceOrValue) (schemas *ast.StructLit, err error) {
    73  	val := inst.Value()
    74  	var fieldFilter *regexp.Regexp
    75  	if g.FieldFilter != "" {
    76  		fieldFilter, err = regexp.Compile(g.FieldFilter)
    77  		if err != nil {
    78  			return nil, errors.Newf(token.NoPos, "invalid field filter: %v", err)
    79  		}
    80  
    81  		// verify that certain elements are still passed.
    82  		for _, f := range strings.Split(
    83  			"version,title,allOf,anyOf,not,enum,Schema/properties,Schema/items"+
    84  				"nullable,type", ",") {
    85  			if fieldFilter.MatchString(f) {
    86  				return nil, errors.Newf(token.NoPos, "field filter may not exclude %q", f)
    87  			}
    88  		}
    89  	}
    90  
    91  	if g.Version == "" {
    92  		g.Version = "3.0.0"
    93  	}
    94  
    95  	c := &buildContext{
    96  		inst:         val,
    97  		instExt:      val,
    98  		refPrefix:    "components/schemas",
    99  		expandRefs:   g.ExpandReferences,
   100  		structural:   g.ExpandReferences,
   101  		nameFunc:     g.NameFunc,
   102  		descFunc:     g.DescriptionFunc,
   103  		schemas:      &orderedMap{},
   104  		externalRefs: map[string]*externalType{},
   105  		fieldFilter:  fieldFilter,
   106  	}
   107  
   108  	switch g.Version {
   109  	case "3.0.0":
   110  		c.exclusiveBool = true
   111  	case "3.1.0":
   112  	default:
   113  		return nil, errors.Newf(token.NoPos, "unsupported version %s", g.Version)
   114  	}
   115  
   116  	defer func() {
   117  		switch x := recover().(type) {
   118  		case nil:
   119  		case *openapiError:
   120  			err = x
   121  		default:
   122  			panic(x)
   123  		}
   124  	}()
   125  
   126  	// Although paths is empty for now, it makes it valid OpenAPI spec.
   127  
   128  	i, err := inst.Value().Fields(cue.Definitions(true))
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	for i.Next() {
   133  		sel := i.Selector()
   134  		if !sel.IsDefinition() {
   135  			continue
   136  		}
   137  		// message, enum, or constant.
   138  		if c.isInternal(sel) {
   139  			continue
   140  		}
   141  		ref := c.makeRef(val, cue.MakePath(sel))
   142  		if ref == "" {
   143  			continue
   144  		}
   145  		c.schemas.setExpr(ref, c.build(sel, i.Value()))
   146  	}
   147  
   148  	// keep looping until a fixed point is reached.
   149  	for done := 0; len(c.externalRefs) != done; {
   150  		done = len(c.externalRefs)
   151  
   152  		// From now on, all references need to be expanded
   153  		for _, k := range slices.Sorted(maps.Keys(c.externalRefs)) {
   154  			ext := c.externalRefs[k]
   155  			c.instExt = ext.inst
   156  			sels := ext.path.Selectors()
   157  			last := len(sels) - 1
   158  			c.path = sels[:last]
   159  			name := sels[last]
   160  			c.schemas.setExpr(ext.ref, c.build(name, cue.Dereference(ext.value)))
   161  		}
   162  	}
   163  
   164  	slices.SortFunc(c.schemas.Elts, func(a, b ast.Decl) int {
   165  		return cmp.Compare(label(a), label(b))
   166  	})
   167  
   168  	return (*ast.StructLit)(c.schemas), c.errs
   169  }
   170  
   171  func (c *buildContext) build(name cue.Selector, v cue.Value) *ast.StructLit {
   172  	return newCoreBuilder(c).schema(nil, name, v)
   173  }
   174  
   175  // isInternal reports whether or not to include this type.
   176  func (c *buildContext) isInternal(sel cue.Selector) bool {
   177  	// TODO: allow a regexp filter in Config. If we have closed structs and
   178  	// definitions, this will likely be unnecessary.
   179  	return sel.Type().LabelType() == cue.DefinitionLabel &&
   180  		strings.HasSuffix(sel.String(), "_value")
   181  }
   182  
   183  func (b *builder) failf(v cue.Value, format string, args ...interface{}) {
   184  	panic(&openapiError{
   185  		errors.NewMessagef(format, args...),
   186  		cue.MakePath(b.ctx.path...),
   187  		v.Pos(),
   188  	})
   189  }
   190  
   191  func (b *builder) unsupported(v cue.Value) {
   192  	if b.format == "" {
   193  		// Not strictly an error, but consider listing it as a warning
   194  		// in strict mode.
   195  	}
   196  }
   197  
   198  func (b *builder) checkArgs(a []cue.Value, n int) {
   199  	if len(a)-1 != n {
   200  		b.failf(a[0], "%v must be used with %d arguments", a[0], len(a)-1)
   201  	}
   202  }
   203  
   204  func (b *builder) schema(core *builder, name cue.Selector, v cue.Value) *ast.StructLit {
   205  	oldPath := b.ctx.path
   206  	b.ctx.path = append(b.ctx.path, name)
   207  	defer func() { b.ctx.path = oldPath }()
   208  
   209  	var c *builder
   210  	if core == nil && b.ctx.structural {
   211  		c = newCoreBuilder(b.ctx)
   212  		c.buildCore(v) // initialize core structure
   213  		c.coreSchema()
   214  	} else {
   215  		c = newRootBuilder(b.ctx)
   216  		c.core = core
   217  	}
   218  
   219  	return c.fillSchema(v)
   220  }
   221  
   222  func (b *builder) getDoc(v cue.Value) {
   223  	doc := []string{}
   224  	if b.ctx.descFunc != nil {
   225  		if str := b.ctx.descFunc(v); str != "" {
   226  			doc = append(doc, str)
   227  		}
   228  	} else {
   229  		for _, d := range v.Doc() {
   230  			doc = append(doc, d.Text())
   231  		}
   232  	}
   233  	if len(doc) > 0 {
   234  		str := strings.TrimSpace(strings.Join(doc, "\n\n"))
   235  		b.setSingle("description", ast.NewString(str), true)
   236  	}
   237  }
   238  
   239  func (b *builder) fillSchema(v cue.Value) *ast.StructLit {
   240  	if b.filled != nil {
   241  		return b.filled
   242  	}
   243  
   244  	b.setValueType(v)
   245  	b.format = extractFormat(v)
   246  	b.deprecated = getDeprecated(v)
   247  
   248  	if b.core == nil || len(b.core.values) > 1 {
   249  		isRef := b.value(v, nil)
   250  		if isRef {
   251  			b.typ = ""
   252  		}
   253  
   254  		if !isRef && !b.ctx.structural {
   255  			b.getDoc(v)
   256  		}
   257  	}
   258  
   259  	schema := b.finish()
   260  	s := schema
   261  
   262  	simplify(b, s)
   263  
   264  	sortSchema(s)
   265  
   266  	b.filled = s
   267  	return s
   268  }
   269  
   270  func label(d ast.Decl) string {
   271  	f := d.(*ast.Field)
   272  	s, _, _ := ast.LabelName(f.Label)
   273  	return s
   274  }
   275  
   276  func value(d ast.Decl) ast.Expr {
   277  	return d.(*ast.Field).Value
   278  }
   279  
   280  func sortSchema(s *ast.StructLit) {
   281  	slices.SortFunc(s.Elts, func(a, b ast.Decl) int {
   282  		aName := label(a)
   283  		bName := label(b)
   284  		aOrder := fieldOrder[aName]
   285  		bOrder := fieldOrder[bName]
   286  		return cmp.Or(-cmp.Compare(aOrder, bOrder), cmp.Compare(aName, bName))
   287  	})
   288  }
   289  
   290  var fieldOrder = map[string]int{
   291  	"description":      31,
   292  	"type":             30,
   293  	"format":           29,
   294  	"required":         28,
   295  	"properties":       27,
   296  	"minProperties":    26,
   297  	"maxProperties":    25,
   298  	"minimum":          24,
   299  	"exclusiveMinimum": 23,
   300  	"maximum":          22,
   301  	"exclusiveMaximum": 21,
   302  	"minItems":         18,
   303  	"maxItems":         17,
   304  	"minLength":        16,
   305  	"maxLength":        15,
   306  	"items":            14,
   307  	"enum":             13,
   308  	"default":          12,
   309  }
   310  
   311  func (b *builder) value(v cue.Value, f typeFunc) (isRef bool) {
   312  	b.pushNode(v)
   313  	defer b.popNode()
   314  
   315  	count := 0
   316  	disallowDefault := false
   317  	var values cue.Value
   318  	if b.ctx.expandRefs || b.format != "" {
   319  		values = cue.Dereference(v)
   320  		count = 1
   321  	} else {
   322  		dedup := map[string]bool{}
   323  		hasNoRef := false
   324  		accept := v
   325  		conjuncts := appendSplit(nil, cue.AndOp, v)
   326  		for _, v := range conjuncts {
   327  			// This may be a reference to an enum. So we need to check references before
   328  			// dissecting them.
   329  
   330  			switch v1, path := v.ReferencePath(); {
   331  			case len(path.Selectors()) > 0:
   332  				ref := b.ctx.makeRef(v1, path)
   333  				if ref == "" {
   334  					v = cue.Dereference(v)
   335  					break
   336  				}
   337  				if dedup[ref] {
   338  					continue
   339  				}
   340  				dedup[ref] = true
   341  
   342  				b.addRef(v, v1, path)
   343  				disallowDefault = true
   344  				continue
   345  			}
   346  			hasNoRef = true
   347  			count++
   348  			values = values.UnifyAccept(v, accept)
   349  		}
   350  		isRef = !hasNoRef && len(dedup) == 1
   351  	}
   352  
   353  	if count > 0 { // TODO: implement IsAny.
   354  		// TODO: perhaps find optimal representation. For now we assume the
   355  		// representation as is already optimized for human consumption.
   356  		if values.IncompleteKind()&cue.StructKind != cue.StructKind && !isRef {
   357  			values = values.Eval()
   358  		}
   359  
   360  		conjuncts := appendSplit(nil, cue.AndOp, values)
   361  		for i, v := range conjuncts {
   362  			switch {
   363  			case isConcrete(v):
   364  				b.dispatch(f, v)
   365  				if !b.isNonCore() {
   366  					b.set("enum", ast.NewList(b.decode(v)))
   367  				}
   368  			default:
   369  				a := appendSplit(nil, cue.OrOp, v)
   370  				for i, v := range a {
   371  					if _, r := v.ReferencePath(); len(r.Selectors()) == 0 {
   372  						a[i] = v.Eval()
   373  					}
   374  				}
   375  
   376  				_ = i
   377  				// TODO: it matters here whether a conjunct is obtained
   378  				// from embedding or normal unification. Fix this at some
   379  				// point.
   380  				//
   381  				// if len(a) > 1 {
   382  				// 	// Filter disjuncts that cannot unify with other conjuncts,
   383  				// 	// and thus can never be satisfied.
   384  				// 	// TODO: there should be generalized simplification logic
   385  				// 	// in CUE (outside of the usual implicit simplifications).
   386  				// 	k := 0
   387  				// outer:
   388  				// 	for _, d := range a {
   389  				// 		for j, w := range conjuncts {
   390  				// 			if i == j {
   391  				// 				continue
   392  				// 			}
   393  				// 			if d.Unify(w).Err() != nil {
   394  				// 				continue outer
   395  				// 			}
   396  				// 		}
   397  				// 		a[k] = d
   398  				// 		k++
   399  				// 	}
   400  				// 	a = a[:k]
   401  				// }
   402  				switch len(a) {
   403  				case 0:
   404  					// Conjunct entirely eliminated.
   405  				case 1:
   406  					v = a[0]
   407  					if err := v.Err(); err != nil {
   408  						b.failf(v, "openapi: %v", err)
   409  						return
   410  					}
   411  					b.dispatch(f, v)
   412  				default:
   413  					b.disjunction(a, f)
   414  				}
   415  			}
   416  		}
   417  	}
   418  
   419  	if v, ok := v.Default(); ok && v.IsConcrete() && !disallowDefault {
   420  		// TODO: should we show the empty list default? This would be correct
   421  		// but perhaps a bit too pedantic and noisy.
   422  		switch {
   423  		case v.Kind() == cue.ListKind:
   424  			iter, _ := v.List()
   425  			if !iter.Next() {
   426  				// Don't show default for empty list.
   427  				break
   428  			}
   429  			fallthrough
   430  		default:
   431  			if !b.isNonCore() {
   432  				e := v.Syntax(cue.Concrete(true)).(ast.Expr)
   433  				b.setFilter("Schema", "default", e)
   434  			}
   435  		}
   436  	}
   437  	return isRef
   438  }
   439  
   440  func appendSplit(a []cue.Value, splitBy cue.Op, v cue.Value) []cue.Value {
   441  	op, args := v.Expr()
   442  	// dedup elements.
   443  	k := 1
   444  outer:
   445  	for i := 1; i < len(args); i++ {
   446  		for j := 0; j < k; j++ {
   447  			if args[i].Subsume(args[j], cue.Raw()) == nil &&
   448  				args[j].Subsume(args[i], cue.Raw()) == nil {
   449  				continue outer
   450  			}
   451  		}
   452  		args[k] = args[i]
   453  		k++
   454  	}
   455  	args = args[:k]
   456  
   457  	if op == cue.NoOp && len(args) == 1 {
   458  		// TODO: this is to deal with default value removal. This may change
   459  		// when we completely separate default values from values.
   460  		a = append(a, args...)
   461  	} else if op != splitBy {
   462  		a = append(a, v)
   463  	} else {
   464  		for _, v := range args {
   465  			a = appendSplit(a, splitBy, v)
   466  		}
   467  	}
   468  	return a
   469  }
   470  
   471  // isConcrete reports whether v is concrete and not a struct (recursively).
   472  // structs are not supported as the result of a struct enum depends on how
   473  // conjunctions and disjunctions are distributed. We could consider still doing
   474  // this if we define a normal form.
   475  func isConcrete(v cue.Value) bool {
   476  	if !v.IsConcrete() {
   477  		return false
   478  	}
   479  	if v.Kind() == cue.StructKind || v.Kind() == cue.ListKind {
   480  		return false // TODO: handle struct and list kinds
   481  	}
   482  	return true
   483  }
   484  
   485  func (b *builder) disjunction(a []cue.Value, f typeFunc) {
   486  	disjuncts := []cue.Value{}
   487  	enums := []ast.Expr{} // TODO: unique the enums
   488  	nullable := false     // Only supported in OpenAPI, not JSON schema
   489  
   490  	for _, v := range a {
   491  		switch {
   492  		case v.Null() == nil:
   493  			// TODO: for JSON schema, we need to fall through.
   494  			nullable = true
   495  
   496  		case isConcrete(v):
   497  			enums = append(enums, b.decode(v))
   498  
   499  		default:
   500  			disjuncts = append(disjuncts, v)
   501  		}
   502  	}
   503  
   504  	// Only one conjunct?
   505  	if len(disjuncts) == 0 || (len(disjuncts) == 1 && len(enums) == 0) {
   506  		if len(disjuncts) == 1 {
   507  			b.value(disjuncts[0], f)
   508  		}
   509  		if len(enums) > 0 && !b.isNonCore() {
   510  			b.set("enum", ast.NewList(enums...))
   511  		}
   512  		if nullable {
   513  			b.setSingle("nullable", ast.NewBool(true), true) // allowed in Structural
   514  		}
   515  		return
   516  	}
   517  
   518  	anyOf := []ast.Expr{}
   519  	if len(enums) > 0 {
   520  		anyOf = append(anyOf, b.kv("enum", ast.NewList(enums...)))
   521  	}
   522  
   523  	if nullable {
   524  		b.setSingle("nullable", ast.NewBool(true), true)
   525  	}
   526  
   527  	schemas := make([]*ast.StructLit, len(disjuncts))
   528  	for i, v := range disjuncts {
   529  		c := newOASBuilder(b)
   530  		c.value(v, f)
   531  		t := c.finish()
   532  		schemas[i] = t
   533  		if len(t.Elts) == 0 {
   534  			if c.typ == "" {
   535  				return
   536  			}
   537  		}
   538  	}
   539  
   540  	for i, v := range disjuncts {
   541  		// In OpenAPI schema are open by default. To ensure forward compatibility,
   542  		// we do not represent closed structs with additionalProperties: false
   543  		// (this is discouraged and often disallowed by implementions), but
   544  		// rather enforce this by ensuring uniqueness of the disjuncts.
   545  		//
   546  		// TODO: subsumption may currently give false negatives. We are extra
   547  		// conservative in these instances.
   548  		subsumed := []ast.Expr{}
   549  		for j, w := range disjuncts {
   550  			if i == j {
   551  				continue
   552  			}
   553  			err := v.Subsume(w, cue.Schema())
   554  			if err == nil || errors.Is(err, internal.ErrInexact) {
   555  				subsumed = append(subsumed, schemas[j])
   556  			}
   557  		}
   558  
   559  		t := schemas[i]
   560  		if len(subsumed) > 0 {
   561  			// TODO: elide anyOf if there is only one element. This should be
   562  			// rare if originating from oneOf.
   563  			exclude := ast.NewStruct("not",
   564  				ast.NewStruct("anyOf", ast.NewList(subsumed...)))
   565  			if len(t.Elts) == 0 {
   566  				t = exclude
   567  			} else {
   568  				t = ast.NewStruct("allOf", ast.NewList(t, exclude))
   569  			}
   570  		}
   571  		anyOf = append(anyOf, t)
   572  	}
   573  
   574  	b.set("oneOf", ast.NewList(anyOf...))
   575  }
   576  
   577  func (b *builder) setValueType(v cue.Value) {
   578  	if b.core != nil {
   579  		return
   580  	}
   581  
   582  	k := v.IncompleteKind() &^ adt.NullKind
   583  	switch k {
   584  	case cue.BoolKind:
   585  		b.typ = "boolean"
   586  	case cue.FloatKind, cue.NumberKind:
   587  		b.typ = "number"
   588  	case cue.IntKind:
   589  		b.typ = "integer"
   590  	case cue.BytesKind:
   591  		b.typ = "string"
   592  	case cue.StringKind:
   593  		b.typ = "string"
   594  	case cue.StructKind:
   595  		b.typ = "object"
   596  	case cue.ListKind:
   597  		b.typ = "array"
   598  	}
   599  }
   600  
   601  func (b *builder) dispatch(f typeFunc, v cue.Value) {
   602  	if f != nil {
   603  		f(b, v)
   604  		return
   605  	}
   606  
   607  	switch v.IncompleteKind() {
   608  	case cue.NullKind:
   609  		// TODO: for JSON schema we would set the type here. For OpenAPI,
   610  		// it must be nullable.
   611  		b.setSingle("nullable", ast.NewBool(true), true)
   612  
   613  	case cue.BoolKind:
   614  		b.setType("boolean", "")
   615  		// No need to call.
   616  
   617  	case cue.FloatKind, cue.NumberKind:
   618  		// TODO:
   619  		// Common   Name	type	format	Comments
   620  		// float	number	float
   621  		// double	number	double
   622  		b.setType("number", "") // may be overridden to integer
   623  		b.number(v)
   624  
   625  	case cue.IntKind:
   626  		// integer	integer	int32	signed 	32 bits
   627  		// long		integer	int64	signed 	64 bits
   628  		b.setType("integer", "") // may be overridden to integer
   629  		b.number(v)
   630  
   631  		// TODO: for JSON schema, consider adding multipleOf: 1.
   632  
   633  	case cue.BytesKind:
   634  		// byte		string	byte	base64 	encoded characters
   635  		// binary	string	binary	any 	sequence of octets
   636  		b.setType("string", "byte")
   637  		b.bytes(v)
   638  	case cue.StringKind:
   639  		// date		string			date	   As defined by full-date - RFC3339
   640  		// dateTime	string			date-time  As defined by date-time - RFC3339
   641  		// password	string			password   A hint to UIs to obscure input
   642  		b.setType("string", "")
   643  		b.string(v)
   644  	case cue.StructKind:
   645  		b.setType("object", "")
   646  		b.object(v)
   647  	case cue.ListKind:
   648  		b.setType("array", "")
   649  		b.array(v)
   650  	}
   651  }
   652  
   653  // object supports the following
   654  //   - maxProperties: maximum allowed fields in this struct.
   655  //   - minProperties: minimum required fields in this struct.
   656  //   - patternProperties: [regexp]: schema
   657  //     TODO: we can support this once .kv(key, value) allow
   658  //     foo [=~"pattern"]: type
   659  //     An instance field must match all schemas for which a regexp matches.
   660  //     Even though it is not supported in OpenAPI, we should still accept it
   661  //     when receiving from OpenAPI. We could possibly use disjunctions to encode
   662  //     this.
   663  //   - dependencies: what?
   664  //   - propertyNames: schema
   665  //     every property name in the enclosed schema matches that of
   666  func (b *builder) object(v cue.Value) {
   667  	// TODO: discriminator objects: we could theoretically derive discriminator
   668  	// objects automatically: for every object in a oneOf/allOf/anyOf, or any
   669  	// object composed of the same type, if a property is required and set to a
   670  	// constant value for each type, it is a discriminator.
   671  
   672  	switch op, a := v.Expr(); op {
   673  	case cue.CallOp:
   674  		name := fmt.Sprint(a[0])
   675  		switch name {
   676  		case "struct.MinFields":
   677  			b.checkArgs(a, 1)
   678  			b.setFilter("Schema", "minProperties", b.int(a[1]))
   679  			return
   680  
   681  		case "struct.MaxFields":
   682  			b.checkArgs(a, 1)
   683  			b.setFilter("Schema", "maxProperties", b.int(a[1]))
   684  			return
   685  
   686  		default:
   687  			b.unsupported(a[0])
   688  			return
   689  		}
   690  
   691  	case cue.NoOp:
   692  		// TODO: extract format from specific type.
   693  
   694  	default:
   695  		b.failf(v, "unsupported op %v for object type (%v)", op, v)
   696  		return
   697  	}
   698  
   699  	required := []ast.Expr{}
   700  	for i, _ := v.Fields(); i.Next(); {
   701  		required = append(required, ast.NewString(i.Selector().Unquoted()))
   702  	}
   703  	if len(required) > 0 {
   704  		b.setFilter("Schema", "required", ast.NewList(required...))
   705  	}
   706  
   707  	var properties *orderedMap
   708  	if b.singleFields != nil {
   709  		properties = b.singleFields.getMap("properties")
   710  	}
   711  	hasProps := properties != nil
   712  	if !hasProps {
   713  		properties = &orderedMap{}
   714  	}
   715  
   716  	for i, _ := v.Fields(cue.Optional(true), cue.Definitions(true)); i.Next(); {
   717  		sel := i.Selector()
   718  		if b.ctx.isInternal(sel) {
   719  			continue
   720  		}
   721  		label := selectorLabel(sel)
   722  		var core *builder
   723  		if b.core != nil {
   724  			core = b.core.properties[label]
   725  		}
   726  		schema := b.schema(core, sel, i.Value())
   727  		switch {
   728  		case sel.IsDefinition():
   729  			ref := b.ctx.makeRef(b.ctx.instExt, cue.MakePath(append(b.ctx.path, sel)...))
   730  			if ref == "" {
   731  				continue
   732  			}
   733  			b.ctx.schemas.setExpr(ref, schema)
   734  		case !b.isNonCore() || len(schema.Elts) > 0:
   735  			properties.setExpr(label, schema)
   736  		}
   737  	}
   738  
   739  	if !hasProps && properties.len() > 0 {
   740  		b.setSingle("properties", (*ast.StructLit)(properties), false)
   741  	}
   742  
   743  	if t := v.LookupPath(cue.MakePath(cue.AnyString)); t.Exists() &&
   744  		(b.core == nil || b.core.items == nil) && b.checkCycle(t) {
   745  		schema := b.schema(nil, cue.AnyString, t)
   746  		if len(schema.Elts) > 0 {
   747  			b.setSingle("additionalProperties", schema, true) // Not allowed in structural.
   748  		}
   749  	}
   750  
   751  	// TODO: maxProperties, minProperties: can be done once we allow cap to
   752  	// unify with structs.
   753  }
   754  
   755  // List constraints:
   756  //
   757  // Max and min items.
   758  //   - maxItems: int (inclusive)
   759  //   - minItems: int (inclusive)
   760  //   - items (item type)
   761  //     schema: applies to all items
   762  //     array of schemas:
   763  //     schema at pos must match if both value and items are defined.
   764  //   - additional items:
   765  //     schema: where items must be an array of schemas, intstance elements
   766  //     succeed for if they match this value for any value at a position
   767  //     greater than that covered by items.
   768  //   - uniqueItems: bool
   769  //     TODO: support with list.Unique() unique() or comprehensions.
   770  //     For the latter, we need equality for all values, which is doable,
   771  //     but not done yet.
   772  //
   773  // NOT SUPPORTED IN OpenAPI:
   774  //   - contains:
   775  //     schema: an array instance is valid if at least one element matches
   776  //     this schema.
   777  func (b *builder) array(v cue.Value) {
   778  
   779  	switch op, a := v.Expr(); op {
   780  	case cue.CallOp:
   781  		name := fmt.Sprint(a[0])
   782  		switch name {
   783  		case "list.UniqueItems", "list.UniqueItems()":
   784  			b.checkArgs(a, 0)
   785  			b.setFilter("Schema", "uniqueItems", ast.NewBool(true))
   786  			return
   787  
   788  		case "list.MinItems":
   789  			b.checkArgs(a, 1)
   790  			b.setFilter("Schema", "minItems", b.int(a[1]))
   791  			return
   792  
   793  		case "list.MaxItems":
   794  			b.checkArgs(a, 1)
   795  			b.setFilter("Schema", "maxItems", b.int(a[1]))
   796  			return
   797  
   798  		default:
   799  			b.unsupported(a[0])
   800  			return
   801  		}
   802  
   803  	case cue.NoOp:
   804  		// TODO: extract format from specific type.
   805  
   806  	default:
   807  		b.failf(v, "unsupported op %v for array type", op)
   808  		return
   809  	}
   810  
   811  	// Possible conjuncts:
   812  	//   - one list (CUE guarantees merging all conjuncts)
   813  	//   - no cap: is unified with list
   814  	//   - unique items: at most one, but idempotent if multiple.
   815  	// There is never a need for allOf or anyOf. Note that a CUE list
   816  	// corresponds almost one-to-one to OpenAPI lists.
   817  	items := []ast.Expr{}
   818  	count := 0
   819  	for i, _ := v.List(); i.Next(); count++ {
   820  		items = append(items, b.schema(nil, cue.Index(count), i.Value()))
   821  	}
   822  	if len(items) > 0 {
   823  		// TODO: per-item schema are not allowed in OpenAPI, only in JSON Schema.
   824  		// Perhaps we should turn this into an OR after first normalizing
   825  		// the entries.
   826  		b.set("items", ast.NewList(items...))
   827  		// panic("per-item types not supported in OpenAPI")
   828  	}
   829  
   830  	// TODO:
   831  	// A CUE cap can be a set of discontinuous ranges. If we encounter this,
   832  	// we can create an allOf(list type, anyOf(ranges)).
   833  	cap := v.Len()
   834  	hasMax := false
   835  	maxLength := int64(math.MaxInt64)
   836  
   837  	if n, capErr := cap.Int64(); capErr == nil {
   838  		maxLength = n
   839  		hasMax = true
   840  	} else {
   841  		b.value(cap, (*builder).listCap)
   842  	}
   843  
   844  	if !hasMax || int64(len(items)) < maxLength {
   845  		if typ := v.LookupPath(cue.MakePath(cue.AnyIndex)); typ.Exists() && b.checkCycle(typ) {
   846  			var core *builder
   847  			if b.core != nil {
   848  				core = b.core.items
   849  			}
   850  			t := b.schema(core, cue.AnyString, typ)
   851  			if len(items) > 0 {
   852  				b.setFilter("Schema", "additionalItems", t) // Not allowed in structural.
   853  			} else if !b.isNonCore() || len(t.Elts) > 0 {
   854  				b.setSingle("items", t, true)
   855  			}
   856  		}
   857  	}
   858  }
   859  
   860  func (b *builder) listCap(v cue.Value) {
   861  	switch op, a := v.Expr(); op {
   862  	case cue.LessThanOp:
   863  		b.setFilter("Schema", "maxItems", b.inta(a[0], -1))
   864  	case cue.LessThanEqualOp:
   865  		b.setFilter("Schema", "maxItems", b.inta(a[0], 0))
   866  	case cue.GreaterThanOp:
   867  		b.setFilter("Schema", "minItems", b.inta(a[0], 1))
   868  	case cue.GreaterThanEqualOp:
   869  		if b.int64(a[0]) > 0 {
   870  			b.setFilter("Schema", "minItems", b.inta(a[0], 0))
   871  		}
   872  	case cue.NoOp:
   873  		// must be type, so okay.
   874  	case cue.NotEqualOp:
   875  		i := b.int(a[0])
   876  		b.setNot("allOff", ast.NewList(
   877  			b.kv("minItems", i),
   878  			b.kv("maxItems", i),
   879  		))
   880  
   881  	default:
   882  		b.failf(v, "unsupported op for list capacity %v", op)
   883  		return
   884  	}
   885  }
   886  
   887  func (b *builder) number(v cue.Value) {
   888  	// Multiple conjuncts mostly means just additive constraints.
   889  	// Type may be number of float.
   890  
   891  	switch op, a := v.Expr(); op {
   892  	case cue.LessThanOp:
   893  		if b.ctx.exclusiveBool {
   894  			b.setFilter("Schema", "exclusiveMaximum", ast.NewBool(true))
   895  			b.setFilter("Schema", "maximum", b.big(a[0]))
   896  		} else {
   897  			b.setFilter("Schema", "exclusiveMaximum", b.big(a[0]))
   898  		}
   899  
   900  	case cue.LessThanEqualOp:
   901  		b.setFilter("Schema", "maximum", b.big(a[0]))
   902  
   903  	case cue.GreaterThanOp:
   904  		if b.ctx.exclusiveBool {
   905  			b.setFilter("Schema", "exclusiveMinimum", ast.NewBool(true))
   906  			b.setFilter("Schema", "minimum", b.big(a[0]))
   907  		} else {
   908  			b.setFilter("Schema", "exclusiveMinimum", b.big(a[0]))
   909  		}
   910  
   911  	case cue.GreaterThanEqualOp:
   912  		b.setFilter("Schema", "minimum", b.big(a[0]))
   913  
   914  	case cue.NotEqualOp:
   915  		i := b.big(a[0])
   916  		b.setNot("allOff", ast.NewList(
   917  			b.kv("minimum", i),
   918  			b.kv("maximum", i),
   919  		))
   920  
   921  	case cue.CallOp:
   922  		name := fmt.Sprint(a[0])
   923  		switch name {
   924  		case "math.MultipleOf":
   925  			b.checkArgs(a, 1)
   926  			b.setFilter("Schema", "multipleOf", b.int(a[1]))
   927  
   928  		default:
   929  			b.unsupported(a[0])
   930  			return
   931  		}
   932  
   933  	case cue.NoOp:
   934  		// TODO: extract format from specific type.
   935  
   936  	default:
   937  		b.failf(v, "unsupported op for number %v", op)
   938  	}
   939  }
   940  
   941  // Multiple Regexp conjuncts are represented as allOf all other
   942  // constraints can be combined unless in the even of discontinuous
   943  // lengths.
   944  
   945  // string supports the following options:
   946  //
   947  // - maxLength (Unicode codepoints)
   948  // - minLength (Unicode codepoints)
   949  // - pattern (a regexp)
   950  //
   951  // The regexp pattern is as follows, and is limited to be a  strict subset of RE2:
   952  // Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-3.3
   953  //
   954  // JSON schema requires ECMA 262 regular expressions, but
   955  // limited to the following constructs:
   956  //   - simple character classes: [abc]
   957  //   - range character classes: [a-z]
   958  //   - complement character classes: [^abc], [^a-z]
   959  //   - simple quantifiers: +, *, ?, and lazy versions +? *? ??
   960  //   - range quantifiers: {x}, {x,y}, {x,}, {x}?, {x,y}?, {x,}?
   961  //   - begin and end anchors: ^ and $
   962  //   - simple grouping: (...)
   963  //   - alteration: |
   964  //
   965  // This is a subset of RE2 used by CUE.
   966  //
   967  // Most notably absent:
   968  //   - the '.' for any character (not sure if that is a doc bug)
   969  //   - character classes \d \D [[::]] \pN \p{Name} \PN \P{Name}
   970  //   - word boundaries
   971  //   - capturing directives.
   972  //   - flag setting
   973  //   - comments
   974  //
   975  // The capturing directives and comments can be removed without
   976  // compromising the meaning of the regexp (TODO). Removing
   977  // flag setting will be tricky. Unicode character classes,
   978  // boundaries, etc can be compiled into simple character classes,
   979  // although the resulting regexp will look cumbersome.
   980  func (b *builder) string(v cue.Value) {
   981  	switch op, a := v.Expr(); op {
   982  
   983  	case cue.RegexMatchOp, cue.NotRegexMatchOp:
   984  		s, err := a[0].String()
   985  		if err != nil {
   986  			// TODO: this may be an unresolved interpolation or expression. Consider
   987  			// whether it is reasonable to treat unevaluated operands as wholes and
   988  			// generate a compound regular expression.
   989  			b.failf(v, "regexp value must be a string: %v", err)
   990  			return
   991  		}
   992  		if op == cue.RegexMatchOp {
   993  			b.setFilter("Schema", "pattern", ast.NewString(s))
   994  		} else {
   995  			b.setNot("pattern", ast.NewString(s))
   996  		}
   997  
   998  	case cue.NoOp, cue.SelectorOp:
   999  
  1000  	case cue.CallOp:
  1001  		name := fmt.Sprint(a[0])
  1002  		switch name {
  1003  		case "strings.MinRunes":
  1004  			b.checkArgs(a, 1)
  1005  			b.setFilter("Schema", "minLength", b.int(a[1]))
  1006  			return
  1007  
  1008  		case "strings.MaxRunes":
  1009  			b.checkArgs(a, 1)
  1010  			b.setFilter("Schema", "maxLength", b.int(a[1]))
  1011  			return
  1012  
  1013  		default:
  1014  			b.unsupported(a[0])
  1015  			return
  1016  		}
  1017  
  1018  	default:
  1019  		b.failf(v, "unsupported op %v for string type", op)
  1020  	}
  1021  }
  1022  
  1023  func (b *builder) bytes(v cue.Value) {
  1024  	switch op, a := v.Expr(); op {
  1025  
  1026  	case cue.RegexMatchOp, cue.NotRegexMatchOp:
  1027  		s, err := a[0].Bytes()
  1028  		if err != nil {
  1029  			// TODO: this may be an unresolved interpolation or expression. Consider
  1030  			// whether it is reasonable to treat unevaluated operands as wholes and
  1031  			// generate a compound regular expression.
  1032  			b.failf(v, "regexp value must be of type bytes: %v", err)
  1033  			return
  1034  		}
  1035  
  1036  		e := ast.NewString(string(s))
  1037  		if op == cue.RegexMatchOp {
  1038  			b.setFilter("Schema", "pattern", e)
  1039  		} else {
  1040  			b.setNot("pattern", e)
  1041  		}
  1042  
  1043  		// TODO: support the following JSON schema constraints
  1044  		// - maxLength
  1045  		// - minLength
  1046  
  1047  	case cue.NoOp, cue.SelectorOp:
  1048  
  1049  	default:
  1050  		b.failf(v, "unsupported op %v for bytes type", op)
  1051  	}
  1052  }
  1053  
  1054  type builder struct {
  1055  	ctx          *buildContext
  1056  	typ          string
  1057  	format       string
  1058  	singleFields *orderedMap
  1059  	current      *orderedMap
  1060  	allOf        []*ast.StructLit
  1061  	deprecated   bool
  1062  
  1063  	// Building structural schema
  1064  	core       *builder
  1065  	kind       cue.Kind
  1066  	filled     *ast.StructLit
  1067  	values     []cue.Value // in structural mode, all values of not and *Of.
  1068  	keys       []string
  1069  	properties map[string]*builder
  1070  	items      *builder
  1071  }
  1072  
  1073  func newRootBuilder(c *buildContext) *builder {
  1074  	return &builder{ctx: c}
  1075  }
  1076  
  1077  func newOASBuilder(parent *builder) *builder {
  1078  	core := parent
  1079  	if parent.core != nil {
  1080  		core = parent.core
  1081  	}
  1082  	b := &builder{
  1083  		core:   core,
  1084  		ctx:    parent.ctx,
  1085  		typ:    parent.typ,
  1086  		format: parent.format,
  1087  	}
  1088  	return b
  1089  }
  1090  
  1091  func (b *builder) isNonCore() bool {
  1092  	return b.core != nil
  1093  }
  1094  
  1095  func (b *builder) setType(t, format string) {
  1096  	if b.typ == "" {
  1097  		b.typ = t
  1098  		if format != "" {
  1099  			b.format = format
  1100  		}
  1101  	}
  1102  }
  1103  
  1104  func setType(t *orderedMap, b *builder) {
  1105  	if b.typ != "" {
  1106  		if b.core == nil || (b.core.typ != b.typ && !b.ctx.structural) {
  1107  			if !t.exists("type") {
  1108  				t.setExpr("type", ast.NewString(b.typ))
  1109  			}
  1110  		}
  1111  	}
  1112  	if b.format != "" {
  1113  		if b.core == nil || b.core.format != b.format {
  1114  			t.setExpr("format", ast.NewString(b.format))
  1115  		}
  1116  	}
  1117  }
  1118  
  1119  // setFilter is like set, but allows the key-value pair to be filtered.
  1120  func (b *builder) setFilter(schema, key string, v ast.Expr) {
  1121  	if re := b.ctx.fieldFilter; re != nil && re.MatchString(path.Join(schema, key)) {
  1122  		return
  1123  	}
  1124  	b.set(key, v)
  1125  }
  1126  
  1127  // setSingle sets a value of which there should only be one.
  1128  func (b *builder) setSingle(key string, v ast.Expr, drop bool) {
  1129  	if b.singleFields == nil {
  1130  		b.singleFields = &orderedMap{}
  1131  	}
  1132  	if b.singleFields.exists(key) {
  1133  		if !drop {
  1134  			b.failf(cue.Value{}, "more than one value added for key %q", key)
  1135  		}
  1136  	}
  1137  	b.singleFields.setExpr(key, v)
  1138  }
  1139  
  1140  func (b *builder) set(key string, v ast.Expr) {
  1141  	if b.current == nil {
  1142  		b.current = &orderedMap{}
  1143  		b.allOf = append(b.allOf, (*ast.StructLit)(b.current))
  1144  	} else if b.current.exists(key) {
  1145  		b.current = &orderedMap{}
  1146  		b.allOf = append(b.allOf, (*ast.StructLit)(b.current))
  1147  	}
  1148  	b.current.setExpr(key, v)
  1149  }
  1150  
  1151  func (b *builder) kv(key string, value ast.Expr) *ast.StructLit {
  1152  	return ast.NewStruct(key, value)
  1153  }
  1154  
  1155  func (b *builder) setNot(key string, value ast.Expr) {
  1156  	b.add(ast.NewStruct("not", b.kv(key, value)))
  1157  }
  1158  
  1159  func (b *builder) finish() *ast.StructLit {
  1160  	var t *orderedMap
  1161  
  1162  	if b.filled != nil {
  1163  		return b.filled
  1164  	}
  1165  	switch len(b.allOf) {
  1166  	case 0:
  1167  		t = &orderedMap{}
  1168  
  1169  	case 1:
  1170  		hasRef := false
  1171  		for _, e := range b.allOf[0].Elts {
  1172  			if f, ok := e.(*ast.Field); ok {
  1173  				name, _, _ := ast.LabelName(f.Label)
  1174  				hasRef = hasRef || name == "$ref"
  1175  			}
  1176  		}
  1177  		if !hasRef || b.singleFields == nil {
  1178  			t = (*orderedMap)(b.allOf[0])
  1179  			break
  1180  		}
  1181  		fallthrough
  1182  
  1183  	default:
  1184  		exprs := []ast.Expr{}
  1185  		for _, s := range b.allOf {
  1186  			exprs = append(exprs, s)
  1187  		}
  1188  		t = &orderedMap{}
  1189  		t.setExpr("allOf", ast.NewList(exprs...))
  1190  	}
  1191  	if b.singleFields != nil {
  1192  		b.singleFields.Elts = append(b.singleFields.Elts, t.Elts...)
  1193  		t = b.singleFields
  1194  	}
  1195  	if b.deprecated {
  1196  		t.setExpr("deprecated", ast.NewBool(true))
  1197  	}
  1198  	setType(t, b)
  1199  	sortSchema((*ast.StructLit)(t))
  1200  	return (*ast.StructLit)(t)
  1201  }
  1202  
  1203  func (b *builder) add(t *ast.StructLit) {
  1204  	b.allOf = append(b.allOf, t)
  1205  }
  1206  
  1207  func (b *builder) addConjunct(f func(*builder)) {
  1208  	c := newOASBuilder(b)
  1209  	f(c)
  1210  	b.add(c.finish())
  1211  }
  1212  
  1213  func (b *builder) addRef(v cue.Value, inst cue.Value, ref cue.Path) {
  1214  	name := b.ctx.makeRef(inst, ref)
  1215  	b.addConjunct(func(b *builder) {
  1216  		b.allOf = append(b.allOf, ast.NewStruct(
  1217  			"$ref",
  1218  			ast.NewString(path.Join("#", b.ctx.refPrefix, name)),
  1219  		))
  1220  	})
  1221  
  1222  	if b.ctx.inst != inst {
  1223  		b.ctx.externalRefs[name] = &externalType{
  1224  			ref:   name,
  1225  			inst:  inst,
  1226  			path:  ref,
  1227  			value: v,
  1228  		}
  1229  	}
  1230  }
  1231  
  1232  func (b *buildContext) makeRef(inst cue.Value, ref cue.Path) string {
  1233  	if b.nameFunc != nil {
  1234  		return b.nameFunc(inst, ref)
  1235  	}
  1236  	var buf strings.Builder
  1237  	for i, sel := range ref.Selectors() {
  1238  		if i > 0 {
  1239  			buf.WriteByte('.')
  1240  		}
  1241  		// TODO what should this do when it's not a valid identifier?
  1242  		buf.WriteString(selectorLabel(sel))
  1243  	}
  1244  	return buf.String()
  1245  }
  1246  
  1247  func (b *builder) int64(v cue.Value) int64 {
  1248  	v, _ = v.Default()
  1249  	i, err := v.Int64()
  1250  	if err != nil {
  1251  		b.failf(v, "could not retrieve int: %v", err)
  1252  	}
  1253  	return i
  1254  }
  1255  
  1256  func (b *builder) intExpr(i int64) ast.Expr {
  1257  	return &ast.BasicLit{
  1258  		Kind:  token.INT,
  1259  		Value: fmt.Sprint(i),
  1260  	}
  1261  }
  1262  
  1263  func (b *builder) int(v cue.Value) ast.Expr {
  1264  	return b.intExpr(b.int64(v))
  1265  }
  1266  
  1267  func (b *builder) inta(v cue.Value, offset int64) ast.Expr {
  1268  	return b.intExpr(b.int64(v) + offset)
  1269  }
  1270  
  1271  func (b *builder) decode(v cue.Value) ast.Expr {
  1272  	v, _ = v.Default()
  1273  	return v.Syntax(cue.Final()).(ast.Expr)
  1274  }
  1275  
  1276  func (b *builder) big(v cue.Value) ast.Expr {
  1277  	v, _ = v.Default()
  1278  	return v.Syntax(cue.Final()).(ast.Expr)
  1279  }
  1280  
  1281  func selectorLabel(sel cue.Selector) string {
  1282  	if sel.Type().ConstraintType() == cue.PatternConstraint {
  1283  		return "*"
  1284  	}
  1285  	switch sel.LabelType() {
  1286  	case cue.StringLabel:
  1287  		return sel.Unquoted()
  1288  	case cue.DefinitionLabel:
  1289  		return sel.String()[1:]
  1290  	}
  1291  	// We shouldn't get anything other than non-hidden
  1292  	// fields and definitions because we've not asked the
  1293  	// Fields iterator for those or created them explicitly.
  1294  	panic(fmt.Sprintf("unreachable %v", sel.Type()))
  1295  }