From 6db6965ae403c88ecd3e52d4f73a9340b6d615e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:48:53 +0000 Subject: [PATCH 1/2] chore(deps): bump github.com/pb33f/libopenapi Bumps the go-dependencies group with 1 update in the / directory: [github.com/pb33f/libopenapi](https://github.com/pb33f/libopenapi). Updates `github.com/pb33f/libopenapi` from 0.40.1 to 0.41.6 - [Release notes](https://github.com/pb33f/libopenapi/releases) - [Commits](https://github.com/pb33f/libopenapi/compare/v0.40.1...v0.41.6) --- updated-dependencies: - dependency-name: github.com/pb33f/libopenapi dependency-version: 0.41.6 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] --- go.mod | 7 ++++--- go.sum | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 959fc6f..48fccf0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/giraffesyo/openapi-go-naming v0.1.1 - github.com/pb33f/libopenapi v0.40.1 + github.com/pb33f/libopenapi v0.41.6 github.com/spf13/cobra v1.10.2 go.yaml.in/yaml/v4 v4.0.0-rc.6 ) @@ -13,8 +13,9 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/pb33f/jsonpath v0.8.3 // indirect - github.com/pb33f/ordered-map/v2 v2.3.1 // indirect + github.com/pb33f/go-yaml v0.1.1 // indirect + github.com/pb33f/jsonpath v0.8.4 // indirect + github.com/pb33f/ordered-map/v2 v2.3.2 // indirect github.com/spf13/pflag v1.0.9 // indirect golang.org/x/sync v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index 459b784..047bba8 100644 --- a/go.sum +++ b/go.sum @@ -9,14 +9,16 @@ github.com/giraffesyo/openapi-go-naming v0.1.1 h1:5/kK3FAIPtpEpmwvPk/moYVrdlznzY github.com/giraffesyo/openapi-go-naming v0.1.1/go.mod h1:20Q7TQ9sJpWQ5aUvmMdNlp70IRpl3JlLp6rH7dBD2ns= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/pb33f/jsonpath v0.8.3 h1:gdOUYn31vic8iRSSR4I2SEv7UQf2WxLeDcUnDPpGZNE= -github.com/pb33f/jsonpath v0.8.3/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= -github.com/pb33f/libopenapi v0.40.1 h1:Ma+2kxag5ox70sSP4dcgGY/4QGZ0VS5Ov8S/z4POyIc= -github.com/pb33f/libopenapi v0.40.1/go.mod h1:nISN8KLZBliHRwZY0V53JZJtuzI8P52WGfJ6J56z9Tg= -github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= -github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= -github.com/pb33f/testify v0.1.0 h1:g48/HDU/jn2COspS4nM0scptxiKTJ4DnbX/4ehK6IZ8= -github.com/pb33f/testify v0.1.0/go.mod h1:nq283P/jJ8hXMmdhAqfj7BJIz0y+6IOHj9q0044rKt4= +github.com/pb33f/go-yaml v0.1.1 h1:yWGmhVdwzionRnSc/hEJwcfq6PaZL2QdYzyReXBbX7Q= +github.com/pb33f/go-yaml v0.1.1/go.mod h1:QvWdkHP5VjFYAeHMQUkaPQqREaQnBNOXmWzAjNrnSdo= +github.com/pb33f/jsonpath v0.8.4 h1:Yx7fxsKl8scgERL22WhKDs2c6gYNNUonPSWEG8Y194s= +github.com/pb33f/jsonpath v0.8.4/go.mod h1:eO6mgdhw5RgzCxm/bXTi75VUs0/cCYJzdGjMCCUEI6M= +github.com/pb33f/libopenapi v0.41.6 h1:W1X/5rOaAk3/Mjwrt+DHaiJO6buhruKWrbQ+pumONkA= +github.com/pb33f/libopenapi v0.41.6/go.mod h1:mUldESjO6ownR1TCcywvoYp7e4v/bCdQyiiIqsgWne0= +github.com/pb33f/ordered-map/v2 v2.3.2 h1:wDyaZ2Pv9QLh64X4utCeD5Zoi9nIv2aW3lwv172O5PQ= +github.com/pb33f/ordered-map/v2 v2.3.2/go.mod h1:1OhFrXu3OYw3kM+FXF+Ug3ugmmZ9LE8z0JfQMLvtV0w= +github.com/pb33f/testify v0.1.1 h1:mnHe7uxKt8dyNYEGUspow72VjD264DB5rOJq4bz5tJw= +github.com/pb33f/testify v0.1.1/go.mod h1:keghMqOLECF1ENzESnfM+cwPcKN5uhTOpvbtjgL6aZg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From 3b9ab034c6875968ed9a249b39e76b8e48714821 Mon Sep 17 00:00:00 2001 From: Michael McQuade Date: Tue, 20 Oct 2026 20:22:31 -0500 Subject: [PATCH 2/2] chore(parser): parse JSON specs with libopenapi again libopenapi 0.41 moved to its go-yaml fork, so the JSON fast path's go.yaml.in/yaml/v4 nodes no longer type-check. The fork reads core's 3.3 MB spec in 13ms (60ms on 0.40), leaving the fast path 3ms ahead, which does not pay for mirroring libopenapi's spec-info extraction. End to end the upgrade is faster without it: 129ms vs 143ms median, byte-identical output. --- go.mod | 1 - go.sum | 2 - internal/parser/jsonnode.go | 325 ------------------------------- internal/parser/jsonnode_test.go | 135 ------------- internal/parser/parser.go | 45 +---- 5 files changed, 1 insertion(+), 507 deletions(-) delete mode 100644 internal/parser/jsonnode.go delete mode 100644 internal/parser/jsonnode_test.go diff --git a/go.mod b/go.mod index 48fccf0..66852f1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/giraffesyo/openapi-go-naming v0.1.1 github.com/pb33f/libopenapi v0.41.6 github.com/spf13/cobra v1.10.2 - go.yaml.in/yaml/v4 v4.0.0-rc.6 ) require ( diff --git a/go.sum b/go.sum index 047bba8..3083e00 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,6 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= -go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/parser/jsonnode.go b/internal/parser/jsonnode.go deleted file mode 100644 index fa55136..0000000 --- a/internal/parser/jsonnode.go +++ /dev/null @@ -1,325 +0,0 @@ -package parser - -import ( - "errors" - "strconv" - "unicode/utf16" - "unicode/utf8" - - "go.yaml.in/yaml/v4" -) - -// errNotFastJSON sends a document the JSON fast path cannot reproduce -// exactly back to the YAML parser. -var errNotFastJSON = errors.New("not handled by the JSON fast path") - -// parseJSONNode builds the node tree the YAML parser produces for a JSON -// document (same kinds, tags, styles, values, lines and columns), several -// times faster than tokenizing the JSON as YAML flow syntax. -func parseJSONNode(data []byte) (*yaml.Node, error) { - p := &jsonNodeParser{data: data, line: 1, col: 1, strs: map[string]string{}} - p.skipSpace() - doc := p.node() - doc.Kind, doc.Line, doc.Column = yaml.DocumentNode, p.line, p.col - root, err := p.value() - if err != nil { - return nil, err - } - p.skipSpace() - if p.pos != len(data) { - return nil, errNotFastJSON - } - doc.Content = []*yaml.Node{root} - return doc, nil -} - -type jsonNodeParser struct { - data []byte - pos int - line, col int - slab []yaml.Node - stack []*yaml.Node - strs map[string]string - buf []byte -} - -func (p *jsonNodeParser) node() *yaml.Node { - if len(p.slab) == 0 { - p.slab = make([]yaml.Node, 1024) - } - n := &p.slab[0] - p.slab = p.slab[1:] - return n -} - -func (p *jsonNodeParser) skipSpace() { - for p.pos < len(p.data) { - switch p.data[p.pos] { - case ' ', '\t': - p.col++ - case '\n': - p.line++ - p.col = 1 - case '\r': - if p.pos+1 < len(p.data) && p.data[p.pos+1] == '\n' { - p.pos++ - } - p.line++ - p.col = 1 - default: - return - } - p.pos++ - } -} - -func (p *jsonNodeParser) value() (*yaml.Node, error) { - if p.pos >= len(p.data) { - return nil, errNotFastJSON - } - n := p.node() - n.Line, n.Column = p.line, p.col - switch p.data[p.pos] { - case '{': - return n, p.container(n, yaml.MappingNode, "!!map", '}') - case '[': - return n, p.container(n, yaml.SequenceNode, "!!seq", ']') - case '"': - s, err := p.str() - if err != nil { - return nil, err - } - n.Kind, n.Tag, n.Style, n.Value = yaml.ScalarNode, "!!str", yaml.DoubleQuotedStyle, s - return n, nil - default: - return n, p.literal(n) - } -} - -func (p *jsonNodeParser) container(n *yaml.Node, kind yaml.Kind, tag string, end byte) error { - n.Kind, n.Tag, n.Style = kind, tag, yaml.FlowStyle - p.pos++ - p.col++ - base := len(p.stack) - p.skipSpace() - if p.pos < len(p.data) && p.data[p.pos] == end { - p.pos++ - p.col++ - return nil - } - for { - p.skipSpace() - if kind == yaml.MappingNode { - if p.pos >= len(p.data) || p.data[p.pos] != '"' { - return errNotFastJSON - } - key, err := p.value() - if err != nil { - return err - } - p.stack = append(p.stack, key) - p.skipSpace() - if p.pos >= len(p.data) || p.data[p.pos] != ':' { - return errNotFastJSON - } - p.pos++ - p.col++ - p.skipSpace() - } - v, err := p.value() - if err != nil { - return err - } - p.stack = append(p.stack, v) - p.skipSpace() - if p.pos >= len(p.data) { - return errNotFastJSON - } - c := p.data[p.pos] - p.pos++ - p.col++ - if c == end { - break - } - if c != ',' { - return errNotFastJSON - } - } - n.Content = append([]*yaml.Node(nil), p.stack[base:]...) - clear(p.stack[base:]) - p.stack = p.stack[:base] - return nil -} - -func (p *jsonNodeParser) str() (string, error) { - p.pos++ - p.col++ - start := p.pos - escaped := false - for { - if p.pos >= len(p.data) { - return "", errNotFastJSON - } - c := p.data[p.pos] - if c == '"' { - break - } - if c < 0x20 { - return "", errNotFastJSON - } - if c == '\\' { - escaped = true - p.pos++ - p.col++ - if p.pos >= len(p.data) { - return "", errNotFastJSON - } - c = p.data[p.pos] - } - if c < utf8.RuneSelf || c >= 0xC0 { - p.col++ - } - p.pos++ - } - raw := p.data[start:p.pos] - p.pos++ - p.col++ - if !escaped { - return p.intern(raw), nil - } - return p.unescape(raw) -} - -func (p *jsonNodeParser) intern(b []byte) string { - if len(b) > 64 { - return string(b) - } - if s, ok := p.strs[string(b)]; ok { - return s - } - s := string(b) - p.strs[s] = s - return s -} - -func (p *jsonNodeParser) unescape(raw []byte) (string, error) { - out := p.buf[:0] - for i := 0; i < len(raw); i++ { - c := raw[i] - if c != '\\' { - out = append(out, c) - continue - } - i++ - switch raw[i] { - case '"', '\\': - out = append(out, raw[i]) - case 'b': - out = append(out, '\b') - case 'f': - out = append(out, '\f') - case 'n': - out = append(out, '\n') - case 'r': - out = append(out, '\r') - case 't': - out = append(out, '\t') - case 'u': - if i+5 > len(raw) { - return "", errNotFastJSON - } - r, err := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16) - if err != nil || utf16.IsSurrogate(rune(r)) { - return "", errNotFastJSON - } - out = utf8.AppendRune(out, rune(r)) - i += 4 - default: - // Includes \/, which the YAML parser only accepts after - // libopenapi rewrites it, shifting the columns that follow. - return "", errNotFastJSON - } - } - p.buf = out - return p.intern(out), nil -} - -func (p *jsonNodeParser) literal(n *yaml.Node) error { - start := p.pos - for p.pos < len(p.data) { - c := p.data[p.pos] - if c == ',' || c == '}' || c == ']' || c == ' ' || c == '\t' || c == '\n' || c == '\r' { - break - } - p.pos++ - } - raw := p.data[start:p.pos] - p.col += len(raw) - n.Kind = yaml.ScalarNode - switch string(raw) { - case "true", "false": - n.Tag, n.Value = "!!bool", string(raw) - return nil - case "null": - n.Tag, n.Value = "!!null", "null" - return nil - } - tag, ok := numberTag(raw) - if !ok { - return errNotFastJSON - } - n.Tag, n.Value = tag, p.intern(raw) - return nil -} - -// numberTag resolves a JSON number the way the YAML core schema does. -func numberTag(raw []byte) (string, bool) { - i := 0 - if i < len(raw) && raw[i] == '-' { - i++ - } - digits := func() int { - n := 0 - for i < len(raw) && raw[i] >= '0' && raw[i] <= '9' { - i++ - n++ - } - return n - } - intDigits := digits() - if intDigits == 0 || (intDigits > 1 && raw[i-intDigits] == '0') { - return "", false - } - isFloat := false - if i < len(raw) && raw[i] == '.' { - i++ - if digits() == 0 { - return "", false - } - isFloat = true - } - if i < len(raw) && (raw[i] == 'e' || raw[i] == 'E') { - i++ - if i < len(raw) && (raw[i] == '+' || raw[i] == '-') { - i++ - } - if digits() == 0 { - return "", false - } - isFloat = true - } - if i != len(raw) { - return "", false - } - if !isFloat { - s := string(raw) - if _, err := strconv.ParseInt(s, 10, 64); err == nil { - return "!!int", true - } - if _, err := strconv.ParseUint(s, 10, 64); err == nil { - return "!!int", true - } - return "", false - } - return "!!float", true -} diff --git a/internal/parser/jsonnode_test.go b/internal/parser/jsonnode_test.go deleted file mode 100644 index b405bba..0000000 --- a/internal/parser/jsonnode_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package parser - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/pb33f/libopenapi/datamodel" - "go.yaml.in/yaml/v4" -) - -// diffNodes reports the first place two node trees differ, or "". -func diffNodes(path string, got, want *yaml.Node) string { - if got.Kind != want.Kind || got.Style != want.Style || got.Tag != want.Tag || got.Value != want.Value || - got.Line != want.Line || got.Column != want.Column || len(got.Content) != len(want.Content) { - return fmt.Sprintf("%s:\n got kind=%v style=%v tag=%q value=%q at %d:%d with %d children\n want kind=%v style=%v tag=%q value=%q at %d:%d with %d children", - path, got.Kind, got.Style, got.Tag, got.Value, got.Line, got.Column, len(got.Content), - want.Kind, want.Style, want.Tag, want.Value, want.Line, want.Column, len(want.Content)) - } - for i := range got.Content { - if d := diffNodes(fmt.Sprintf("%s/%d", path, i), got.Content[i], want.Content[i]); d != "" { - return d - } - } - return "" -} - -func assertSpecInfoMatches(t *testing.T, spec []byte) { - t.Helper() - got, err := extractSpecInfo(spec, &datamodel.DocumentConfiguration{}) - if err != nil { - t.Fatalf("extractSpecInfo: %v", err) - } - want, err := datamodel.ExtractSpecInfoWithConfig(spec, &datamodel.DocumentConfiguration{}) - if err != nil { - t.Fatalf("ExtractSpecInfoWithConfig: %v", err) - } - type fields struct { - SpecType, Version, SpecFormat, SpecFileType, APISchema, Self string - VersionNumeric float32 - NumLines, OriginalIndentation int - } - project := func(i *datamodel.SpecInfo) fields { - return fields{i.SpecType, i.Version, i.SpecFormat, i.SpecFileType, i.APISchema, i.Self, i.VersionNumeric, i.NumLines, i.OriginalIndentation} - } - if g, w := project(got), project(want); g != w { - t.Errorf("SpecInfo differs:\n got %+v\n want %+v", g, w) - } - if d := diffNodes("", got.RootNode, want.RootNode); d != "" { - t.Error(d) - } -} - -func assertMatchesYAML(t *testing.T, data []byte) { - t.Helper() - got, err := parseJSONNode(data) - if err != nil { - t.Fatalf("parseJSONNode: %v", err) - } - var want yaml.Node - if err := yaml.Unmarshal(data, &want); err != nil { - t.Fatalf("yaml.Unmarshal: %v", err) - } - if d := diffNodes("", got, &want); d != "" { - t.Error(d) - } -} - -func TestParseJSONNode_MatchesYAMLParser(t *testing.T) { - cases := map[string]string{ - "scalars": `{"s": "x", "e": "", "t": true, "f": false, "n": null, "i": 42, "neg": -7, "zero": 0, - "fl": 1.5, "exp": 1e5, "Exp": -2.5E-3, "big": 18446744073709551615, "nested": {"a": [1, [2, {}], []]}}`, - "escapes": `{"q": "a\"b\\c\nd\te\r\b\f", "u": "caf\u00e9 \u4e2d", "after": "x"}`, - "unicode": "{\"k\u00e9\": \"\u4e2d\u6587\", \"emoji\": \"\U0001F600\", \"after\": [1, 2]}", - "crlf": "{\r\n \"a\": 1,\r\n \"b\": [\r\n true\r\n ]\r\n}\r\n", - "compact": `{"a":{"b":[1,2,{"c":"d"}]},"e":[]}`, - "leadingWS": "\n\n {\"a\": 1}\n", - } - for name, in := range cases { - t.Run(name, func(t *testing.T) { assertMatchesYAML(t, []byte(in)) }) - } - - specs, err := filepath.Glob(filepath.Join(projectRoot(), "testdata", "*.yaml")) - if err != nil || len(specs) == 0 { - t.Fatalf("no testdata specs: %v", err) - } - for _, spec := range specs { - t.Run(filepath.Base(spec), func(t *testing.T) { - src, err := os.ReadFile(spec) - if err != nil { - t.Fatal(err) - } - var v any - if err := yaml.Unmarshal(src, &v); err != nil { - t.Fatal(err) - } - indented, err := json.MarshalIndent(v, "", " ") - if err != nil { - t.Fatal(err) - } - assertMatchesYAML(t, indented) - assertSpecInfoMatches(t, indented) - compact, err := json.Marshal(v) - if err != nil { - t.Fatal(err) - } - assertMatchesYAML(t, compact) - }) - } -} - -func TestParseJSONNode_DefersToYAMLParser(t *testing.T) { - for name, in := range map[string]string{ - "escaped slash": `{"a": "x\/y"}`, - "surrogate pair": `{"a": "\ud83d\ude00"}`, - "lone surrogate": `{"a": "\ud83d"}`, - "bom": "\ufeff{\"a\": 1}", - "trailing comma": `{"a": 1,}`, - "leading zero": `{"a": 01}`, - "int overflow": `{"a": 99999999999999999999}`, - "bare word": `{"a": yes}`, - "trailing data": `{"a": 1} x`, - "unterminated": `{"a": "b`, - "control in text": "{\"a\": \"b\nc\"}", - } { - t.Run(name, func(t *testing.T) { - if _, err := parseJSONNode([]byte(in)); !errors.Is(err, errNotFastJSON) { - t.Errorf("got %v, want errNotFastJSON", err) - } - }) - } -} diff --git a/internal/parser/parser.go b/internal/parser/parser.go index fb6a75f..0c0e9bb 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -1,13 +1,10 @@ package parser import ( - "bytes" - "encoding/json" "errors" "fmt" "os" "path/filepath" - "unicode/utf8" "github.com/pb33f/libopenapi/datamodel" v3high "github.com/pb33f/libopenapi/datamodel/high/v3" @@ -53,7 +50,7 @@ func Parse(specPath string, cfg Config) (*ParseResult, error) { // The root libopenapi package would do these steps for us, but importing it // compiles its diffing and Arazzo models too, which dominate a cold build. - info, err := extractSpecInfo(specBytes, docConfig) + info, err := datamodel.ExtractSpecInfoWithConfig(specBytes, docConfig) if err != nil { return nil, fmt.Errorf("parsing spec: %w", err) } @@ -83,43 +80,3 @@ func Parse(specPath string, cfg Config) (*ParseResult, error) { return &ParseResult{Model: model, Version: info.Version}, nil } - -// extractSpecInfo is datamodel.ExtractSpecInfoWithConfig with a faster parse -// for JSON documents, which are the common machine-generated case. -func extractSpecInfo(spec []byte, cfg *datamodel.DocumentConfiguration) (*datamodel.SpecInfo, error) { - trimmed := bytes.TrimSpace(spec) - if len(trimmed) == 0 || trimmed[0] != '{' || trimmed[len(trimmed)-1] != '}' || !utf8.Valid(spec) { - return datamodel.ExtractSpecInfoWithConfig(spec, cfg) - } - root, err := parseJSONNode(spec) - if err != nil { - return datamodel.ExtractSpecInfoWithConfig(spec, cfg) - } - _, openAPI3 := utils.FindKeyNode(utils.OpenApi3, root.Content) - _, openAPI2 := utils.FindKeyNode(utils.OpenApi2, root.Content) - _, asyncAPI := utils.FindKeyNode(utils.AsyncApi, root.Content) - if openAPI3 == nil || openAPI2 != nil || asyncAPI != nil { - return datamodel.ExtractSpecInfoWithConfig(spec, cfg) - } - - // Every other SpecInfo field follows from the version alone, so a one-key - // document reuses libopenapi's version handling without its slow parse. - stub, err := json.Marshal(map[string]string{utils.OpenApi3: openAPI3.Value}) - if err != nil { - return nil, err - } - info, err := datamodel.ExtractSpecInfoWithConfig(stub, cfg) - if err != nil { - return datamodel.ExtractSpecInfoWithConfig(spec, cfg) - } - info.SpecBytes = &spec - info.RootNode = root - info.NumLines = bytes.Count(spec, []byte{'\n'}) + 1 - info.OriginalIndentation = utils.DetermineWhitespaceLengthBytes(spec) - if info.SpecFormat == datamodel.OAS31 || info.SpecFormat == datamodel.OAS32 { - if _, self := utils.FindKeyNode("$self", root.Content); self != nil && self.Value != "" { - info.Self = self.Value - } - } - return info, nil -}