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