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