this repo has no description
at master 181 lines 4.7 kB view raw
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 15package 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 46import ( 47 "cuelang.org/go/cue" 48 "cuelang.org/go/cue/ast" 49) 50 51// newCoreBuilder returns a builder that represents a structural schema. 52func newCoreBuilder(c *buildContext) *builder { 53 b := newRootBuilder(c) 54 b.properties = map[string]*builder{} 55 return b 56} 57 58func (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. 67func (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.setExpr(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. 111func (b *builder) buildCore(v cue.Value) { 112 b.pushNode(v) 113 defer b.popNode() 114 115 if !b.ctx.expandRefs { 116 _, r := v.ReferencePath() 117 if len(r.Selectors()) > 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 := v.LookupPath(cue.MakePath(cue.AnyString)); typ.Exists() { 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 := v.LookupPath(cue.MakePath(cue.AnyIndex)); typ.Exists() { 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 163func (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.Selector().Unquoted() 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}