...

Source file src/cuelang.org/go/encoding/openapi/crd.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  // This file contains functionality for structural schema, a subset of OpenAPI
    18  // used for CRDs.
    19  //
    20  // See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/ for details.
    21  //
    22  // Insofar definitions are compatible, openapi normalizes to structural whenever
    23  // possible.
    24  //
    25  // A core structural schema is only made out of the following fields:
    26  //
    27  // - properties
    28  // - items
    29  // - additionalProperties
    30  // - type
    31  // - nullable
    32  // - title
    33  // - descriptions.
    34  //
    35  // Where the types must be defined for all fields.
    36  //
    37  // In addition, the value validations constraints may be used as defined in
    38  // OpenAPI, with the restriction that
    39  //  - within the logical constraints anyOf, allOf, oneOf, and not
    40  //    additionalProperties, type, nullable, title, and description may not be used.
    41  //  - all mentioned fields must be defined in the core schema.
    42  //
    43  // It appears that CRDs do not allow references.
    44  //
    45  
    46  import (
    47  	"cuelang.org/go/cue"
    48  	"cuelang.org/go/cue/ast"
    49  )
    50  
    51  // newCoreBuilder returns a builder that represents a structural schema.
    52  func newCoreBuilder(c *buildContext) *builder {
    53  	b := newRootBuilder(c)
    54  	b.properties = map[string]*builder{}
    55  	return b
    56  }
    57  
    58  func (b *builder) coreSchemaWithName(name cue.Selector) *ast.StructLit {
    59  	oldPath := b.ctx.path
    60  	b.ctx.path = append(b.ctx.path, name)
    61  	s := b.coreSchema()
    62  	b.ctx.path = oldPath
    63  	return s
    64  }
    65  
    66  // coreSchema creates the core part of a structural OpenAPI.
    67  func (b *builder) coreSchema() *ast.StructLit {
    68  	switch b.kind {
    69  	case cue.ListKind:
    70  		if b.items != nil {
    71  			b.setType("array", "")
    72  			schema := b.items.coreSchemaWithName(cue.AnyString)
    73  			b.setSingle("items", schema, false)
    74  		}
    75  
    76  	case cue.StructKind:
    77  		p := &OrderedMap{}
    78  		for _, k := range b.keys {
    79  			sub := b.properties[k]
    80  			p.Set(k, sub.coreSchemaWithName(cue.Str(k)))
    81  		}
    82  		if p.len() > 0 || b.items != nil {
    83  			b.setType("object", "")
    84  		}
    85  		if p.len() > 0 {
    86  			b.setSingle("properties", (*ast.StructLit)(p), false)
    87  		}
    88  		// TODO: in Structural schema only one of these is allowed.
    89  		if b.items != nil {
    90  			schema := b.items.coreSchemaWithName(cue.AnyString)
    91  			b.setSingle("additionalProperties", schema, false)
    92  		}
    93  	}
    94  
    95  	// If there was only a single value associated with this node, we can
    96  	// safely assume there were no disjunctions etc. In structural mode this
    97  	// is the only chance we get to set certain properties.
    98  	if len(b.values) == 1 {
    99  		return b.fillSchema(b.values[0])
   100  	}
   101  
   102  	// TODO: do type analysis if we have multiple values and piece out more
   103  	// information that applies to all possible instances.
   104  
   105  	return b.finish()
   106  }
   107  
   108  // buildCore collects the CUE values for the structural OpenAPI tree.
   109  // To this extent, all fields of both conjunctions and disjunctions are
   110  // collected in a single properties map.
   111  func (b *builder) buildCore(v cue.Value) {
   112  	b.pushNode(v)
   113  	defer b.popNode()
   114  
   115  	if !b.ctx.expandRefs {
   116  		_, r := v.Reference()
   117  		if len(r) > 0 {
   118  			return
   119  		}
   120  	}
   121  	b.getDoc(v)
   122  	format := extractFormat(v)
   123  	if format != "" {
   124  		b.format = format
   125  	} else {
   126  		v = v.Eval()
   127  		b.kind = v.IncompleteKind()
   128  
   129  		switch b.kind {
   130  		case cue.StructKind:
   131  			if typ, ok := v.Elem(); ok {
   132  				if !b.checkCycle(typ) {
   133  					return
   134  				}
   135  				if b.items == nil {
   136  					b.items = newCoreBuilder(b.ctx)
   137  				}
   138  				b.items.buildCore(typ)
   139  			}
   140  			b.buildCoreStruct(v)
   141  
   142  		case cue.ListKind:
   143  			if typ, ok := v.Elem(); ok {
   144  				if !b.checkCycle(typ) {
   145  					return
   146  				}
   147  				if b.items == nil {
   148  					b.items = newCoreBuilder(b.ctx)
   149  				}
   150  				b.items.buildCore(typ)
   151  			}
   152  		}
   153  	}
   154  
   155  	for _, bv := range b.values {
   156  		if bv.Equals(v) {
   157  			return
   158  		}
   159  	}
   160  	b.values = append(b.values, v)
   161  }
   162  
   163  func (b *builder) buildCoreStruct(v cue.Value) {
   164  	op, args := v.Expr()
   165  	switch op {
   166  	case cue.OrOp, cue.AndOp:
   167  		for _, v := range args {
   168  			b.buildCore(v)
   169  		}
   170  	}
   171  	for i, _ := v.Fields(cue.Optional(true), cue.Hidden(false)); i.Next(); {
   172  		label := i.Label()
   173  		sub, ok := b.properties[label]
   174  		if !ok {
   175  			sub = newCoreBuilder(b.ctx)
   176  			b.properties[label] = sub
   177  			b.keys = append(b.keys, label)
   178  		}
   179  		sub.buildCore(i.Value())
   180  	}
   181  }
   182  

View as plain text