...

Source file src/cuelang.org/go/encoding/openapi/build.go

Documentation: cuelang.org/go/encoding/openapi

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

View as plain text