cuelang.org/go@v0.10.1/encoding/openapi/crd.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 // 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 }