// Copyright 2019 CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openapi_test import ( "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" "cuelang.org/go/cue" "cuelang.org/go/cue/build" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" "cuelang.org/go/cue/load" "cuelang.org/go/encoding/openapi" "cuelang.org/go/internal/cuetest" ) func TestParseDefinitions(t *testing.T) { info := struct { Title string `json:"title"` Version string `json:"version"` }{"test", "v1"} defaultConfig := &openapi.Config{} resolveRefs := &openapi.Config{Info: info, ExpandReferences: true} testCases := []struct { in, out string variant string instanceOnly bool valueOnly bool config *openapi.Config err string }{{ in: "structural.cue", out: "structural.json", config: resolveRefs, }, { in: "structural.cue", variant: "+ReferenceFunc", out: "structural.json", instanceOnly: true, config: &openapi.Config{ Info: info, ExpandReferences: true, ReferenceFunc: func(v *cue.Instance, path []string) string { return strings.Join(path, "_") }, }, }, { in: "protobuf.cue", out: "protobuf.json", instanceOnly: true, config: &openapi.Config{ Info: info, ExpandReferences: true, ReferenceFunc: func(p *cue.Instance, path []string) string { return strings.Join(path, ".") }, }, }, { in: "nested.cue", out: "nested.json", config: defaultConfig, }, { in: "simple.cue", out: "simple.json", config: resolveRefs, }, { in: "simple.cue", out: "simple-filter.json", config: &openapi.Config{Info: info, FieldFilter: "min.*|max.*"}, }, { in: "array.cue", out: "array.json", config: defaultConfig, }, { in: "enum.cue", out: "enum.json", config: defaultConfig, }, { in: "struct.cue", out: "struct.json", config: defaultConfig, }, { in: "strings.cue", out: "strings.json", config: defaultConfig, }, { in: "nums.cue", out: "nums.json", config: defaultConfig, }, { in: "nums.cue", out: "nums-v3.1.0.json", config: &openapi.Config{Info: info, Version: "3.1.0"}, }, { in: "builtins.cue", out: "builtins.json", config: defaultConfig, }, { in: "oneof.cue", out: "oneof.json", config: defaultConfig, }, { in: "oneof.cue", out: "oneof-resolve.json", config: resolveRefs, }, { in: "openapi.cue", out: "openapi.json", config: defaultConfig, }, { in: "openapi.cue", out: "openapi-norefs.json", config: resolveRefs, }, { in: "embed.cue", out: "embed.json", config: defaultConfig, }, { in: "embed.cue", out: "embed-norefs.json", config: resolveRefs, }, { in: "oneof.cue", out: "oneof-funcs.json", config: &openapi.Config{ Info: info, NameFunc: func(v cue.Value, path cue.Path) string { var buf strings.Builder for i, sel := range path.Selectors() { if i > 0 { buf.WriteByte('_') } s := sel.String() s = strings.TrimPrefix(s, "#") buf.WriteString(strings.ToUpper(s)) } return buf.String() }, DescriptionFunc: func(v cue.Value) string { return "Randomly picked description from a set of size one." }, }, }, { in: "oneof.cue", out: "oneof-funcs.json", variant: "+ReferenceFunc", instanceOnly: true, config: &openapi.Config{ Info: info, ReferenceFunc: func(v *cue.Instance, path []string) string { return strings.ToUpper(strings.Join(path, "_")) }, DescriptionFunc: func(v cue.Value) string { return "Randomly picked description from a set of size one." }, }, }, { in: "refs.cue", out: "refs.json", config: &openapi.Config{ Info: info, NameFunc: func(v cue.Value, path cue.Path) string { switch { case strings.HasPrefix(path.Selectors()[0].String(), "#Excluded"): return "" } return strings.TrimPrefix(path.String(), "#") }, }, }, { in: "refs.cue", out: "refs.json", variant: "+ReferenceFunc", instanceOnly: true, config: &openapi.Config{ Info: info, ReferenceFunc: func(v *cue.Instance, path []string) string { switch { case strings.HasPrefix(path[0], "Excluded"): return "" } return strings.Join(path, ".") }, }, }, { in: "issue131.cue", out: "issue131.json", config: &openapi.Config{Info: info, SelfContained: true}, }, { // Issue #915 in: "cycle.cue", out: "cycle.json", config: &openapi.Config{Info: info}, }, { // Issue #915 in: "cycle.cue", config: &openapi.Config{Info: info, ExpandReferences: true}, err: "cycle", }, { in: "quotedfield.cue", out: "quotedfield.json", config: defaultConfig, }, { in: "omitvalue.cue", out: "omitvalue.json", config: defaultConfig, }} for _, tc := range testCases { t.Run(tc.out+tc.variant, func(t *testing.T) { run := func(t *testing.T, inst cue.InstanceOrValue) { b, err := openapi.Gen(inst, tc.config) if err != nil { if tc.err == "" { t.Fatal("unexpected error:", errors.Details(err, nil)) } return } if tc.err != "" { t.Fatal("unexpected success:", tc.err) } else { all, err := tc.config.All(inst) if err != nil { t.Fatal(err) } walk(all) } var out = &bytes.Buffer{} _ = json.Indent(out, b, "", " ") wantFile := filepath.Join("testdata", tc.out) if cuetest.UpdateGoldenFiles { _ = os.WriteFile(wantFile, out.Bytes(), 0644) return } b, err = os.ReadFile(wantFile) if err != nil { t.Fatal(err) } if d := cmp.Diff(string(b), out.String()); d != "" { t.Errorf("files differ:\n%v", d) } } filename := filepath.FromSlash(tc.in) inst := load.Instances([]string{filename}, &load.Config{ Dir: "./testdata", })[0] if !tc.valueOnly { t.Run("Instance", func(t *testing.T) { // Old style call, with *cue.Instance. inst := cue.Build([]*build.Instance{inst})[0] if inst.Err != nil { t.Fatal(errors.Details(inst.Err, nil)) } run(t, inst) }) } if !tc.instanceOnly { t.Run("Value", func(t *testing.T) { // New style call, with cue.Value ctx := cuecontext.New() v := ctx.BuildInstance(inst) if err := v.Err(); err != nil { t.Fatal(errors.Details(err, nil)) } run(t, v) }) } }) } } // walk traverses an openapi.OrderedMap. This is a helper function // used to ensure that a generated OpenAPI value is well-formed. func walk(om *openapi.OrderedMap) { for _, p := range om.Pairs() { switch p := p.Value.(type) { case *openapi.OrderedMap: walk(p) case []*openapi.OrderedMap: for _, om := range p { walk(om) } } } } // TODO: move OpenAPI testing to txtar and allow errors. func TestIssue1234(t *testing.T) { var r cue.Runtime inst, err := r.Compile("test", ` #Test: or([]) `) if err != nil { t.Fatal(err) } _, err = openapi.Gen(inst, &openapi.Config{}) if err == nil { t.Fatal("expected error") } } // This is for debugging purposes. Do not remove. func TestX(t *testing.T) { t.Skip() var r cue.Runtime inst, err := r.Compile("test", ` `) if err != nil { t.Fatal(err) } b, err := openapi.Gen(inst, &openapi.Config{ // ExpandReferences: true, }) if err != nil { t.Fatal(errors.Details(err, nil)) } var out = &bytes.Buffer{} _ = json.Indent(out, b, "", " ") t.Error(out.String()) }