this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

encoding/openapi: update API

This updates the encoding/openapi package to avoid using the outdated
`*cue.Instance` API and use `cue.Value` instead.

To avoid backward incompatibility, the entry points are changed to use
`cue.InstanceOrValue` instead of `*cue.Instance` and a new `Config.NameFunc`
is introduced to replace `Config.ReferenceFunc`.

Also fix an issue that `Config.Info` is documented as allowing any type
that marshals to JSON, but that didn't work (it only allowed `*ast.StructLit`).

Issue #1806.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: Ib446a99ebe7176d427f9bb344a51d4ee6d4e1af5
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/542287
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
Unity-Result: CUEcueckoo <cueckoo@cuelang.org>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+452 -123
+110 -57
encoding/openapi/build.go
··· 20 20 "path" 21 21 "regexp" 22 22 "sort" 23 - "strconv" 24 23 "strings" 25 24 26 25 "cuelang.org/go/cue" ··· 29 28 "cuelang.org/go/cue/token" 30 29 "cuelang.org/go/internal" 31 30 "cuelang.org/go/internal/core/adt" 31 + internalvalue "cuelang.org/go/internal/value" 32 32 ) 33 33 34 34 type buildContext struct { 35 - inst *cue.Instance 36 - instExt *cue.Instance 35 + inst cue.Value 36 + instExt cue.Value 37 37 refPrefix string 38 - path []string 38 + path []cue.Selector 39 39 errs errors.Error 40 40 41 41 expandRefs bool 42 42 structural bool 43 43 exclusiveBool bool 44 - nameFunc func(inst *cue.Instance, path []string) string 44 + nameFunc func(inst cue.Value, path cue.Path) string 45 45 descFunc func(v cue.Value) string 46 46 fieldFilter *regexp.Regexp 47 47 evalDepth int // detect cycles when resolving references ··· 58 58 // TODO: consider an option in the CUE API where optional fields are 59 59 // recursively evaluated. 60 60 cycleNodes []*adt.Vertex 61 + 62 + // imports caches values as returned by cue.Value.ReferencePath 63 + // for use by ReferenceFunc. It's only initialised when ReferenceFunc 64 + // is non-nil. 65 + imports map[cue.Value]*cue.Instance 61 66 } 62 67 63 68 type externalType struct { 64 69 ref string 65 - inst *cue.Instance 66 - path []string 70 + inst cue.Value 71 + path cue.Path 67 72 value cue.Value 68 73 } 69 74 ··· 71 76 72 77 type typeFunc func(b *builder, a cue.Value) 73 78 74 - func schemas(g *Generator, inst *cue.Instance) (schemas *ast.StructLit, err error) { 79 + func schemas(g *Generator, inst cue.InstanceOrValue) (schemas *ast.StructLit, err error) { 80 + val := inst.Value() 81 + _, isInstance := inst.(*cue.Instance) 75 82 var fieldFilter *regexp.Regexp 76 83 if g.FieldFilter != "" { 77 84 fieldFilter, err = regexp.Compile(g.FieldFilter) ··· 93 100 g.Version = "3.0.0" 94 101 } 95 102 96 - c := buildContext{ 97 - inst: inst, 98 - instExt: inst, 103 + c := &buildContext{ 104 + inst: val, 105 + instExt: val, 99 106 refPrefix: "components/schemas", 100 107 expandRefs: g.ExpandReferences, 101 108 structural: g.ExpandReferences, 102 - nameFunc: g.ReferenceFunc, 109 + nameFunc: g.NameFunc, 103 110 descFunc: g.DescriptionFunc, 104 111 schemas: &OrderedMap{}, 105 112 externalRefs: map[string]*externalType{}, 106 113 fieldFilter: fieldFilter, 107 114 } 115 + if g.ReferenceFunc != nil { 116 + if !isInstance { 117 + panic("cannot use ReferenceFunc along with cue.Value") 118 + } 119 + if g.NameFunc != nil { 120 + panic("cannot specify both ReferenceFunc and NameFunc") 121 + } 122 + 123 + c.nameFunc = func(val cue.Value, path cue.Path) string { 124 + sels := path.Selectors() 125 + labels := make([]string, len(sels)) 126 + for i, sel := range sels { 127 + labels[i] = selectorLabel(sel) // TODO this is arguably incorrect. 128 + } 129 + inst, ok := c.imports[val] 130 + if !ok { 131 + r, n := internalvalue.ToInternal(val) 132 + buildInst := r.GetInstanceFromNode(n) 133 + var err error 134 + inst, err = (*cue.Runtime)(r).Build(buildInst) 135 + if err != nil { 136 + panic("cannot build instance from value") 137 + } 138 + if c.imports == nil { 139 + c.imports = make(map[cue.Value]*cue.Instance) 140 + } 141 + c.imports[val] = inst 142 + } 143 + return g.ReferenceFunc(inst, labels) 144 + } 145 + } 108 146 109 147 switch g.Version { 110 148 case "3.0.0": ··· 131 169 return nil, err 132 170 } 133 171 for i.Next() { 134 - if !i.IsDefinition() { 172 + sel := i.Selector() 173 + if !sel.IsDefinition() { 135 174 continue 136 175 } 137 176 // message, enum, or constant. 138 - label := i.Label() 139 - if c.isInternal(label) { 177 + if c.isInternal(sel) { 140 178 continue 141 179 } 142 - if i.IsDefinition() && strings.HasPrefix(label, "#") { 143 - label = label[1:] 144 - } 145 - ref := c.makeRef(inst, []string{label}) 180 + ref := c.makeRef(val, cue.MakePath(sel)) 146 181 if ref == "" { 147 182 continue 148 183 } 149 - c.schemas.Set(ref, c.build(label, i.Value())) 184 + c.schemas.Set(ref, c.build(sel, i.Value())) 150 185 } 151 186 152 187 // keep looping until a fixed point is reached. ··· 163 198 for _, k := range external { 164 199 ext := c.externalRefs[k] 165 200 c.instExt = ext.inst 166 - last := len(ext.path) - 1 167 - c.path = ext.path[:last] 168 - name := ext.path[last] 201 + sels := ext.path.Selectors() 202 + last := len(sels) - 1 203 + c.path = sels[:last] 204 + name := sels[last] 169 205 c.schemas.Set(ext.ref, c.build(name, cue.Dereference(ext.value))) 170 206 } 171 207 } ··· 180 216 return (*ast.StructLit)(c.schemas), c.errs 181 217 } 182 218 183 - func (c *buildContext) build(name string, v cue.Value) *ast.StructLit { 219 + func (c *buildContext) build(name cue.Selector, v cue.Value) *ast.StructLit { 184 220 return newCoreBuilder(c).schema(nil, name, v) 185 221 } 186 222 187 223 // isInternal reports whether or not to include this type. 188 - func (c *buildContext) isInternal(name string) bool { 224 + func (c *buildContext) isInternal(sel cue.Selector) bool { 189 225 // TODO: allow a regexp filter in Config. If we have closed structs and 190 226 // definitions, this will likely be unnecessary. 191 - return strings.HasSuffix(name, "_value") 227 + return sel.Type().LabelType() == cue.DefinitionLabel && 228 + strings.HasSuffix(sel.String(), "_value") 192 229 } 193 230 194 231 func (b *builder) failf(v cue.Value, format string, args ...interface{}) { 195 232 panic(&openapiError{ 196 233 errors.NewMessage(format, args), 197 - b.ctx.path, 234 + cue.MakePath(b.ctx.path...), 198 235 v.Pos(), 199 236 }) 200 237 } ··· 212 249 } 213 250 } 214 251 215 - func (b *builder) schema(core *builder, name string, v cue.Value) *ast.StructLit { 252 + func (b *builder) schema(core *builder, name cue.Selector, v cue.Value) *ast.StructLit { 216 253 oldPath := b.ctx.path 217 254 b.ctx.path = append(b.ctx.path, name) 218 255 defer func() { b.ctx.path = oldPath }() ··· 340 377 for _, v := range conjuncts { 341 378 // This may be a reference to an enum. So we need to check references before 342 379 // dissecting them. 343 - switch p, r := v.Reference(); { 344 - case len(r) > 0: 345 - ref := b.ctx.makeRef(p, r) 380 + 381 + switch v1, path := v.ReferencePath(); { 382 + case len(path.Selectors()) > 0: 383 + ref := b.ctx.makeRef(v1, path) 346 384 if ref == "" { 347 385 v = cue.Dereference(v) 348 386 break ··· 352 390 } 353 391 dedup[ref] = true 354 392 355 - b.addRef(v, p, r) 393 + b.addRef(v, v1, path) 356 394 disallowDefault = true 357 395 continue 358 396 } ··· 754 792 } 755 793 756 794 for i, _ := v.Fields(cue.Optional(true), cue.Definitions(true)); i.Next(); { 757 - label := i.Label() 758 - if b.ctx.isInternal(label) { 795 + sel := i.Selector() 796 + if b.ctx.isInternal(sel) { 759 797 continue 760 798 } 761 - if i.IsDefinition() && strings.HasPrefix(label, "#") { 762 - label = label[1:] 763 - } 799 + label := selectorLabel(sel) 764 800 var core *builder 765 801 if b.core != nil { 766 802 core = b.core.properties[label] 767 803 } 768 - schema := b.schema(core, label, i.Value()) 804 + schema := b.schema(core, sel, i.Value()) 769 805 switch { 770 - case i.IsDefinition(): 771 - ref := b.ctx.makeRef(b.ctx.instExt, append(b.ctx.path, label)) 806 + case sel.IsDefinition(): 807 + ref := b.ctx.makeRef(b.ctx.instExt, cue.MakePath(append(b.ctx.path, sel)...)) 772 808 if ref == "" { 773 809 continue 774 810 } ··· 784 820 785 821 if t, ok := v.Elem(); ok && 786 822 (b.core == nil || b.core.items == nil) && b.checkCycle(t) { 787 - schema := b.schema(nil, "*", t) 823 + schema := b.schema(nil, cue.AnyString, t) 788 824 if len(schema.Elts) > 0 { 789 825 b.setSingle("additionalProperties", schema, true) // Not allowed in structural. 790 826 } ··· 859 895 items := []ast.Expr{} 860 896 count := 0 861 897 for i, _ := v.List(); i.Next(); count++ { 862 - items = append(items, b.schema(nil, strconv.Itoa(count), i.Value())) 898 + items = append(items, b.schema(nil, cue.Index(count), i.Value())) 863 899 } 864 900 if len(items) > 0 { 865 901 // TODO: per-item schema are not allowed in OpenAPI, only in JSON Schema. ··· 889 925 if b.core != nil { 890 926 core = b.core.items 891 927 } 892 - t := b.schema(core, "*", typ) 928 + t := b.schema(core, cue.AnyString, typ) 893 929 if len(items) > 0 { 894 930 b.setFilter("Schema", "additionalItems", t) // Not allowed in structural. 895 931 } else if !b.isNonCore() || len(t.Elts) > 0 { ··· 1255 1291 b.add((*ast.StructLit)(c.finish())) 1256 1292 } 1257 1293 1258 - func (b *builder) addRef(v cue.Value, inst *cue.Instance, ref []string) { 1294 + func (b *builder) addRef(v cue.Value, inst cue.Value, ref cue.Path) { 1259 1295 name := b.ctx.makeRef(inst, ref) 1260 1296 b.addConjunct(func(b *builder) { 1261 1297 b.allOf = append(b.allOf, ast.NewStruct( 1262 - "$ref", ast.NewString(path.Join("#", b.ctx.refPrefix, name)))) 1298 + "$ref", 1299 + ast.NewString(path.Join("#", b.ctx.refPrefix, name)), 1300 + )) 1263 1301 }) 1264 1302 1265 1303 if b.ctx.inst != inst { ··· 1272 1310 } 1273 1311 } 1274 1312 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:] 1313 + func (b *buildContext) makeRef(inst cue.Value, ref cue.Path) string { 1314 + if b.nameFunc != nil { 1315 + return b.nameFunc(inst, ref) 1316 + } 1317 + var buf strings.Builder 1318 + for i, sel := range ref.Selectors() { 1319 + if i > 0 { 1320 + buf.WriteByte('.') 1280 1321 } 1322 + // TODO what should this do when it's not a valid identifier? 1323 + buf.WriteString(selectorLabel(sel)) 1281 1324 } 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, ".") 1325 + return buf.String() 1289 1326 } 1290 1327 1291 1328 func (b *builder) int64(v cue.Value) int64 { ··· 1321 1358 v, _ = v.Default() 1322 1359 return v.Syntax(cue.Final()).(ast.Expr) 1323 1360 } 1361 + 1362 + func selectorLabel(sel cue.Selector) string { 1363 + if sel.Type().ConstraintType() == cue.PatternConstraint { 1364 + return "*" 1365 + } 1366 + switch sel.LabelType() { 1367 + case cue.StringLabel: 1368 + return sel.Unquoted() 1369 + case cue.DefinitionLabel: 1370 + return sel.String()[1:] 1371 + } 1372 + // We shouldn't get anything other than non-hidden 1373 + // fields and definitions because we've not asked the 1374 + // Fields iterator for those or created them explicitly. 1375 + panic(fmt.Sprintf("unreachable %v", sel.Type())) 1376 + }
+4 -4
encoding/openapi/crd.go
··· 55 55 return b 56 56 } 57 57 58 - func (b *builder) coreSchemaWithName(name string) *ast.StructLit { 58 + func (b *builder) coreSchemaWithName(name cue.Selector) *ast.StructLit { 59 59 oldPath := b.ctx.path 60 60 b.ctx.path = append(b.ctx.path, name) 61 61 s := b.coreSchema() ··· 69 69 case cue.ListKind: 70 70 if b.items != nil { 71 71 b.setType("array", "") 72 - schema := b.items.coreSchemaWithName("*") 72 + schema := b.items.coreSchemaWithName(cue.AnyString) 73 73 b.setSingle("items", schema, false) 74 74 } 75 75 ··· 77 77 p := &OrderedMap{} 78 78 for _, k := range b.keys { 79 79 sub := b.properties[k] 80 - p.Set(k, sub.coreSchemaWithName(k)) 80 + p.Set(k, sub.coreSchemaWithName(cue.Str(k))) 81 81 } 82 82 if p.len() > 0 || b.items != nil { 83 83 b.setType("object", "") ··· 87 87 } 88 88 // TODO: in Structural schema only one of these is allowed. 89 89 if b.items != nil { 90 - schema := b.items.coreSchemaWithName("*") 90 + schema := b.items.coreSchemaWithName(cue.AnyString) 91 91 b.setSingle("additionalProperties", schema, false) 92 92 } 93 93 }
+1 -1
encoding/openapi/decode.go
··· 29 29 // 30 30 // It currently only converts entries in #/components/schema and extracts some 31 31 // meta data. 32 - func Extract(data *cue.Instance, c *Config) (*ast.File, error) { 32 + func Extract(data cue.InstanceOrValue, c *Config) (*ast.File, error) { 33 33 // TODO: find a good OpenAPI validator. Both go-openapi and kin-openapi 34 34 // seem outdated. The k8s one might be good, but avoid pulling in massive 35 35 // amounts of dependencies.
+11 -2
encoding/openapi/errors.go
··· 15 15 package openapi 16 16 17 17 import ( 18 + "cuelang.org/go/cue" 18 19 "cuelang.org/go/cue/errors" 19 20 "cuelang.org/go/cue/token" 20 21 ) ··· 24 25 // implements cue/Error 25 26 type openapiError struct { 26 27 errors.Message 27 - path []string 28 + path cue.Path 28 29 pos token.Pos 29 30 } 30 31 ··· 37 38 } 38 39 39 40 func (e *openapiError) Path() []string { 40 - return e.path 41 + return pathToStrings(e.path) 42 + } 43 + 44 + // pathToString is a utility function for creating debugging info. 45 + func pathToStrings(p cue.Path) (a []string) { 46 + for _, sel := range p.Selectors() { 47 + a = append(a, sel.String()) 48 + } 49 + return a 41 50 }
+30 -15
encoding/openapi/openapi.go
··· 39 39 // ReferenceFunc allows users to specify an alternative representation 40 40 // for references. An empty string tells the generator to expand the type 41 41 // in place and, if applicable, not generate a schema for that entity. 42 + // 43 + // If this field is non-nil and a cue.Value is passed as the InstanceOrValue, 44 + // there will be a panic. 45 + // 46 + // Deprecated: use NameFunc instead. 42 47 ReferenceFunc func(inst *cue.Instance, path []string) string 43 48 49 + // NameFunc allows users to specify an alternative representation 50 + // for references. It is called with the value passed to the top level 51 + // method or function and the path to the entity being generated. 52 + // If it returns an empty string the generator will expand the type 53 + // in place and, if applicable, not generate a schema for that entity. 54 + // 55 + // Note: this only returns the final element of the /-separated 56 + // reference. 57 + NameFunc func(val cue.Value, path cue.Path) string 58 + 44 59 // DescriptionFunc allows rewriting a description associated with a certain 45 60 // field. A typical implementation compiles the description from the 46 61 // comments obtains from the Doc method. No description field is added if ··· 72 87 73 88 // Gen generates the set OpenAPI schema for all top-level types of the 74 89 // given instance. 75 - func Gen(inst *cue.Instance, c *Config) ([]byte, error) { 90 + func Gen(inst cue.InstanceOrValue, c *Config) ([]byte, error) { 76 91 if c == nil { 77 92 c = defaultConfig 78 93 } ··· 87 102 // given instance. 88 103 // 89 104 // Note: only a limited number of top-level types are supported so far. 90 - func Generate(inst *cue.Instance, c *Config) (*ast.File, error) { 105 + func Generate(inst cue.InstanceOrValue, c *Config) (*ast.File, error) { 91 106 all, err := schemas(c, inst) 92 107 if err != nil { 93 108 return nil, err ··· 103 118 // 104 119 // Note: only a limited number of top-level types are supported so far. 105 120 // Deprecated: use Generate 106 - func (g *Generator) All(inst *cue.Instance) (*OrderedMap, error) { 121 + func (g *Generator) All(inst cue.InstanceOrValue) (*OrderedMap, error) { 107 122 all, err := schemas(g, inst) 108 123 if err != nil { 109 124 return nil, err ··· 125 140 126 141 } 127 142 128 - func (c *Config) compose(inst *cue.Instance, schemas *ast.StructLit) (x *ast.StructLit, err error) { 129 - 143 + func (c *Config) compose(inst cue.InstanceOrValue, schemas *ast.StructLit) (x *ast.StructLit, err error) { 144 + val := inst.Value() 130 145 var errs errors.Error 131 146 132 147 var title, version string 133 148 var info *ast.StructLit 134 149 135 - for i, _ := inst.Value().Fields(cue.Definitions(true)); i.Next(); { 150 + for i, _ := val.Fields(cue.Definitions(true)); i.Next(); { 136 151 if i.IsDefinition() { 137 152 continue 138 153 } ··· 164 179 case nil: 165 180 if title == "" { 166 181 title = "Generated by cue." 167 - for _, d := range inst.Doc() { 182 + for _, d := range val.Doc() { 168 183 title = strings.TrimSpace(d.Text()) 169 184 break 170 185 } 171 - if p := inst.ImportPath; title == "" && p != "" { 172 - title = fmt.Sprintf("Generated by cue from package %q", p) 173 - } 174 186 } 175 187 176 188 if version == "" { 177 - version, _ = inst.Lookup("$version").String() 189 + version, _ = val.Lookup("$version").String() 178 190 if version == "" { 179 191 version = "no version" 180 192 } ··· 202 214 if err != nil { 203 215 return nil, err 204 216 } 205 - info, _ = x.(*ast.StructLit) 206 - errs = errors.Append(errs, errors.Newf(token.NoPos, 207 - "Info field supplied must be an *ast.StructLit")) 217 + var ok bool 218 + info, ok = x.(*ast.StructLit) 219 + if !ok { 220 + errs = errors.Append(errs, errors.Newf(token.NoPos, 221 + "Info field supplied must marshal to a struct but got %s", fmt.Sprintf("%T", x))) 222 + } 208 223 } 209 224 210 225 return ast.NewStruct( ··· 216 231 } 217 232 218 233 // Schemas extracts component/schemas from the CUE top-level types. 219 - func (g *Generator) Schemas(inst *cue.Instance) (*OrderedMap, error) { 234 + func (g *Generator) Schemas(inst cue.InstanceOrValue) (*OrderedMap, error) { 220 235 comps, err := schemas(g, inst) 221 236 if err != nil { 222 237 return nil, err
+135 -44
encoding/openapi/openapi_test.go
··· 25 25 "github.com/kylelemons/godebug/diff" 26 26 27 27 "cuelang.org/go/cue" 28 - "cuelang.org/go/cue/ast" 28 + "cuelang.org/go/cue/build" 29 + "cuelang.org/go/cue/cuecontext" 29 30 "cuelang.org/go/cue/errors" 30 31 "cuelang.org/go/cue/load" 31 32 "cuelang.org/go/encoding/openapi" ··· 33 34 ) 34 35 35 36 func TestParseDefinitions(t *testing.T) { 36 - info := *(*openapi.OrderedMap)(ast.NewStruct( 37 - "title", ast.NewString("test"), 38 - "version", ast.NewString("v1"), 39 - )) 37 + info := struct { 38 + Title string `json:"title"` 39 + Version string `json:"version"` 40 + }{"test", "v1"} 40 41 defaultConfig := &openapi.Config{} 41 42 resolveRefs := &openapi.Config{Info: info, ExpandReferences: true} 42 43 43 44 testCases := []struct { 44 - in, out string 45 - config *openapi.Config 46 - err string 45 + in, out string 46 + variant string 47 + instanceOnly bool 48 + valueOnly bool 49 + config *openapi.Config 50 + err string 47 51 }{{ 48 52 in: "structural.cue", 49 53 out: "structural.json", 50 54 config: resolveRefs, 55 + }, { 56 + in: "structural.cue", 57 + variant: "+ReferenceFunc", 58 + out: "structural.json", 59 + instanceOnly: true, 60 + config: &openapi.Config{ 61 + Info: info, 62 + ExpandReferences: true, 63 + ReferenceFunc: func(v *cue.Instance, path []string) string { 64 + return strings.Join(path, "_") 65 + }, 66 + }, 67 + }, { 68 + in: "protobuf.cue", 69 + out: "protobuf.json", 70 + instanceOnly: true, 71 + config: &openapi.Config{ 72 + Info: info, 73 + ExpandReferences: true, 74 + ReferenceFunc: func(p *cue.Instance, path []string) string { 75 + return strings.Join(path, ".") 76 + }, 77 + }, 51 78 }, { 52 79 in: "nested.cue", 53 80 out: "nested.json", ··· 117 144 out: "oneof-funcs.json", 118 145 config: &openapi.Config{ 119 146 Info: info, 120 - ReferenceFunc: func(inst *cue.Instance, path []string) string { 147 + NameFunc: func(v cue.Value, path cue.Path) string { 148 + var buf strings.Builder 149 + for i, sel := range path.Selectors() { 150 + if i > 0 { 151 + buf.WriteByte('_') 152 + } 153 + s := sel.String() 154 + s = strings.TrimPrefix(s, "#") 155 + buf.WriteString(strings.ToUpper(s)) 156 + } 157 + return buf.String() 158 + }, 159 + DescriptionFunc: func(v cue.Value) string { 160 + return "Randomly picked description from a set of size one." 161 + }, 162 + }, 163 + }, { 164 + in: "oneof.cue", 165 + out: "oneof-funcs.json", 166 + variant: "+ReferenceFunc", 167 + instanceOnly: true, 168 + config: &openapi.Config{ 169 + Info: info, 170 + ReferenceFunc: func(v *cue.Instance, path []string) string { 121 171 return strings.ToUpper(strings.Join(path, "_")) 122 172 }, 123 173 DescriptionFunc: func(v cue.Value) string { ··· 129 179 out: "refs.json", 130 180 config: &openapi.Config{ 131 181 Info: info, 132 - ReferenceFunc: func(inst *cue.Instance, path []string) string { 182 + NameFunc: func(v cue.Value, path cue.Path) string { 183 + switch { 184 + case strings.HasPrefix(path.Selectors()[0].String(), "#Excluded"): 185 + return "" 186 + } 187 + return strings.TrimPrefix(path.String(), "#") 188 + }, 189 + }, 190 + }, { 191 + in: "refs.cue", 192 + out: "refs.json", 193 + variant: "+ReferenceFunc", 194 + instanceOnly: true, 195 + config: &openapi.Config{ 196 + Info: info, 197 + ReferenceFunc: func(v *cue.Instance, path []string) string { 133 198 switch { 134 199 case strings.HasPrefix(path[0], "Excluded"): 135 200 return "" ··· 151 216 in: "cycle.cue", 152 217 config: &openapi.Config{Info: info, ExpandReferences: true}, 153 218 err: "cycle", 219 + }, { 220 + in: "quotedfield.cue", 221 + out: "quotedfield.json", 222 + config: defaultConfig, 223 + }, { 224 + in: "omitvalue.cue", 225 + out: "omitvalue.json", 226 + config: defaultConfig, 154 227 }} 155 228 for _, tc := range testCases { 156 - t.Run(tc.out, func(t *testing.T) { 157 - filename := filepath.FromSlash(tc.in) 229 + t.Run(tc.out+tc.variant, func(t *testing.T) { 230 + run := func(t *testing.T, inst cue.InstanceOrValue) { 231 + b, err := openapi.Gen(inst, tc.config) 232 + if err != nil { 233 + if tc.err == "" { 234 + t.Fatal("unexpected error:", errors.Details(err, nil)) 235 + } 236 + return 237 + } 238 + 239 + if tc.err != "" { 240 + t.Fatal("unexpected success:", tc.err) 241 + } else { 242 + all, err := tc.config.All(inst) 243 + if err != nil { 244 + t.Fatal(err) 245 + } 246 + walk(all) 247 + } 158 248 159 - inst := cue.Build(load.Instances([]string{filename}, &load.Config{ 160 - Dir: "./testdata", 161 - }))[0] 162 - if inst.Err != nil { 163 - t.Fatal(errors.Details(inst.Err, nil)) 164 - } 249 + var out = &bytes.Buffer{} 250 + _ = json.Indent(out, b, "", " ") 165 251 166 - b, err := openapi.Gen(inst, tc.config) 167 - if err != nil { 168 - if tc.err == "" { 169 - t.Fatal("unexpected error:", errors.Details(inst.Err, nil)) 252 + wantFile := filepath.Join("testdata", tc.out) 253 + if cuetest.UpdateGoldenFiles { 254 + _ = ioutil.WriteFile(wantFile, out.Bytes(), 0644) 255 + return 170 256 } 171 - return 172 - } 173 257 174 - if tc.err != "" { 175 - t.Fatal("unexpected success:", tc.err) 176 - } else { 177 - all, err := tc.config.All(inst) 258 + b, err = ioutil.ReadFile(wantFile) 178 259 if err != nil { 179 260 t.Fatal(err) 180 261 } 181 - walk(all) 182 - } 183 262 184 - var out = &bytes.Buffer{} 185 - _ = json.Indent(out, b, "", " ") 186 - 187 - wantFile := filepath.Join("testdata", tc.out) 188 - if cuetest.UpdateGoldenFiles { 189 - _ = ioutil.WriteFile(wantFile, out.Bytes(), 0644) 190 - return 263 + if d := diff.Diff(string(b), out.String()); d != "" { 264 + t.Errorf("files differ:\n%v", d) 265 + } 191 266 } 192 - 193 - b, err = ioutil.ReadFile(wantFile) 194 - if err != nil { 195 - t.Fatal(err) 267 + filename := filepath.FromSlash(tc.in) 268 + inst := load.Instances([]string{filename}, &load.Config{ 269 + Dir: "./testdata", 270 + })[0] 271 + if !tc.valueOnly { 272 + t.Run("Instance", func(t *testing.T) { 273 + // Old style call, with *cue.Instance. 274 + inst := cue.Build([]*build.Instance{inst})[0] 275 + if inst.Err != nil { 276 + t.Fatal(errors.Details(inst.Err, nil)) 277 + } 278 + run(t, inst) 279 + }) 196 280 } 197 - 198 - if d := diff.Diff(string(b), out.String()); d != "" { 199 - t.Errorf("files differ:\n%v", d) 281 + if !tc.instanceOnly { 282 + t.Run("Value", func(t *testing.T) { 283 + // New style call, wih cue.Value 284 + ctx := cuecontext.New() 285 + v := ctx.BuildInstance(inst) 286 + if err := v.Err(); err != nil { 287 + t.Fatal(errors.Details(err, nil)) 288 + } 289 + run(t, v) 290 + }) 200 291 } 201 292 }) 202 293 }
+8
encoding/openapi/testdata/omitvalue.cue
··· 1 + #D: { 2 + a: int 3 + #b: int 4 + a_value: int 5 + // Only definitions ending in _value are omitted. 6 + #b_value: int 7 + "a b_value": int 8 + }
+34
encoding/openapi/testdata/omitvalue.json
··· 1 + { 2 + "openapi": "3.0.0", 3 + "info": { 4 + "title": "Generated by cue.", 5 + "version": "no version" 6 + }, 7 + "paths": {}, 8 + "components": { 9 + "schemas": { 10 + "D": { 11 + "type": "object", 12 + "required": [ 13 + "a", 14 + "a_value", 15 + "a b_value" 16 + ], 17 + "properties": { 18 + "a": { 19 + "type": "integer" 20 + }, 21 + "a_value": { 22 + "type": "integer" 23 + }, 24 + "a b_value": { 25 + "type": "integer" 26 + } 27 + } 28 + }, 29 + "D.b": { 30 + "type": "integer" 31 + } 32 + } 33 + } 34 + }
+12
encoding/openapi/testdata/protobuf.cue
··· 1 + // Note: This example originally derived from a CUE file converted from protobuf. 2 + 3 + #HTTPRoute: { 4 + route: [...#HTTPRouteDestination] 5 + } 6 + #HTTPRouteDestination: { 7 + headers: #Headers 8 + } 9 + #Headers: { 10 + request: #HeaderOperations 11 + #HeaderOperations: {} 12 + }
+81
encoding/openapi/testdata/protobuf.json
··· 1 + { 2 + "openapi": "3.0.0", 3 + "info": { 4 + "title": "test", 5 + "version": "v1" 6 + }, 7 + "paths": {}, 8 + "components": { 9 + "schemas": { 10 + "HTTPRoute": { 11 + "type": "object", 12 + "required": [ 13 + "route" 14 + ], 15 + "properties": { 16 + "route": { 17 + "type": "array", 18 + "items": { 19 + "type": "object", 20 + "required": [ 21 + "headers" 22 + ], 23 + "properties": { 24 + "headers": { 25 + "type": "object", 26 + "required": [ 27 + "request" 28 + ], 29 + "properties": { 30 + "request": { 31 + "type": "object" 32 + } 33 + } 34 + } 35 + } 36 + } 37 + } 38 + } 39 + }, 40 + "HTTPRoute.route.*.headers.HeaderOperations": { 41 + "type": "object" 42 + }, 43 + "HTTPRouteDestination": { 44 + "type": "object", 45 + "required": [ 46 + "headers" 47 + ], 48 + "properties": { 49 + "headers": { 50 + "type": "object", 51 + "required": [ 52 + "request" 53 + ], 54 + "properties": { 55 + "request": { 56 + "type": "object" 57 + } 58 + } 59 + } 60 + } 61 + }, 62 + "HTTPRouteDestination.headers.HeaderOperations": { 63 + "type": "object" 64 + }, 65 + "Headers": { 66 + "type": "object", 67 + "required": [ 68 + "request" 69 + ], 70 + "properties": { 71 + "request": { 72 + "type": "object" 73 + } 74 + } 75 + }, 76 + "Headers.HeaderOperations": { 77 + "type": "object" 78 + } 79 + } 80 + } 81 + }
+3
encoding/openapi/testdata/quotedfield.cue
··· 1 + #Def: { 2 + "@type": string 3 + }
+23
encoding/openapi/testdata/quotedfield.json
··· 1 + { 2 + "openapi": "3.0.0", 3 + "info": { 4 + "title": "Generated by cue.", 5 + "version": "no version" 6 + }, 7 + "paths": {}, 8 + "components": { 9 + "schemas": { 10 + "Def": { 11 + "type": "object", 12 + "required": [ 13 + "@type" 14 + ], 15 + "properties": { 16 + "@type": { 17 + "type": "string" 18 + } 19 + } 20 + } 21 + } 22 + } 23 + }