Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
947498e
openapi3: keep a document's origin tree only when it can be read (#1234)
reuvenharrison Jul 30, 2026
b76608b
Spike: native YAML decoding on the stock parser, no fork
reuvenharrison Aug 3, 2026
456a979
Port all 41 types to UnmarshalYAML on the stock parser
reuvenharrison Aug 3, 2026
d090e36
Merge commit from fork
matiasinsaurralde Aug 3, 2026
f5441d6
Merge commit from fork
fenollp Aug 3, 2026
335f4e6
openapi3: store field locations in a slice, not a map (#1235)
reuvenharrison Aug 3, 2026
2b2a07f
Wire the loader to the native path
reuvenharrison Aug 3, 2026
32005a9
Revert the apis-guru fixture churn
reuvenharrison Aug 3, 2026
a50e066
Suppress YAML 1.1 timestamp resolution before decoding
reuvenharrison Aug 3, 2026
7f760b4
Comments describe the code, not the change
reuvenharrison Aug 3, 2026
4a209a4
Generate the repetitive UnmarshalYAML methods
reuvenharrison Aug 3, 2026
9fad1ec
Use a generic helper in the generated UnmarshalYAML bodies
reuvenharrison Aug 3, 2026
0ed2a5a
Fix explicit-null responses and arbitrary-key ref origins
reuvenharrison Aug 3, 2026
d5e60c5
openapi3filter: fix for CI (#1237)
fenollp Aug 3, 2026
fe4d402
Give any-typed values JSON-shaped numbers
reuvenharrison Aug 3, 2026
c45017b
Close three more origin gaps
reuvenharrison Aug 3, 2026
602a7d6
Delete TestOrigin_OriginExistsInProperties
reuvenharrison Aug 3, 2026
4ebab9d
Delete the __origin__ tests
reuvenharrison Aug 3, 2026
aec3e3d
Give a document root its own position as Origin.Key
reuvenharrison Aug 3, 2026
a299f84
Delete the __origin__ consumer layer, and group origins in origin.go
reuvenharrison Aug 3, 2026
8e04ac1
Use the stock parser directly in openapi3
reuvenharrison Aug 3, 2026
ca4456d
Normalise any-element slices and maps, and accept int in an enum
reuvenharrison Aug 3, 2026
ddd9bec
Take openapi2 off the yaml fork, minimally
reuvenharrison Aug 3, 2026
6b321c0
Drop the oasdiff/yaml and yaml3 dependencies
reuvenharrison Aug 3, 2026
3f09ffb
Derive block extents from the node tree
reuvenharrison Aug 3, 2026
517cb7e
Stamp a resolved $ref against the file it came from
reuvenharrison Aug 3, 2026
bdf1fbe
Merge remote-tracking branch 'upstream/master' into spike/native-yaml…
reuvenharrison Aug 3, 2026
c7f66cb
Say what the package-level origin state actually costs
reuvenharrison Aug 3, 2026
ed8329e
Satisfy the generated-artifact and lint gates
reuvenharrison Aug 3, 2026
5d698b7
Guard the per-decode origin state with a lock
reuvenharrison Aug 3, 2026
8197f83
Rescope the yaml-usage guard to the decode layer
reuvenharrison Aug 3, 2026
6315603
Compare the guard's count numerically
reuvenharrison Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/docs/openapi2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ backwards-compatible with version 2, version 3 elements have been used.

See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md

FUNCTIONS

func UnmarshalFromData(data []byte, doc *T) error
UnmarshalFromData loads a document from swagger 2.0 bytes in either JSON or
YAML. The v3 side has Loader for this; here the whole job is the decode.


TYPES

type Header struct {
Expand Down
172 changes: 169 additions & 3 deletions .github/docs/openapi3.txt

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ jobs:
! git grep -IErn '\s$'

- if: runner.os == 'Linux'
name: Ensure use of unmarshal
name: Ensure yaml stays inside the decode layer
run: |
[[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | grep -v origin.go | wc -l)" = 2 ]]
[[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | grep -vE 'openapi3/(origin|marsh|end_positions|refs|loader|native_yaml.*|nativeyaml.*)[.](go|tmpl)' | wc -l)" -eq 0 ]]

- if: runner.os == 'Linux'
name: Use `loader := NewLoader(); loader.Load ...`
Expand Down
6 changes: 3 additions & 3 deletions cmd/validate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"os"
"strings"

"github.com/oasdiff/yaml"
yaml "go.yaml.in/yaml/v3"

"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
Expand Down Expand Up @@ -53,7 +53,7 @@ func main() {
OpenAPI string `json:"openapi" yaml:"openapi"`
Swagger string `json:"swagger" yaml:"swagger"`
}
if _, err := yaml.Unmarshal(data, &vd, yaml.DecodeOpts{DisableTimestamps: true}); err != nil {
if err := yaml.Unmarshal(data, &vd); err != nil {
log.Fatal(err)
}

Expand Down Expand Up @@ -109,7 +109,7 @@ func main() {
}

var doc openapi2.T
if _, err := yaml.Unmarshal(data, &doc, yaml.DecodeOpts{DisableTimestamps: true}); err != nil {
if err := openapi2.UnmarshalFromData(data, &doc); err != nil {
log.Fatalln("Loading error:", err)
}

Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ go 1.25
require (
github.com/go-openapi/jsonpointer v0.22.5
github.com/gorilla/mux v1.8.0
github.com/oasdiff/yaml v0.1.1
github.com/oasdiff/yaml3 v0.0.14
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/stretchr/testify v1.9.0
go.yaml.in/yaml/v3 v3.0.5
)

require (
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY=
github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU=
github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw=
github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg=
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/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
70 changes: 70 additions & 0 deletions internal/yamlconv/yamlconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Package yamlconv converts between YAML and types that describe themselves
// with json tags and MarshalJSON/UnmarshalJSON methods.
//
// YAML reaches such a type through JSON, so its methods run and extensions are
// carried. Two things have to be reconciled first, both of which YAML resolves
// and JSON cannot represent.
package yamlconv

import (
"encoding/json"

yaml "go.yaml.in/yaml/v3"
)

// PrepareForJSON retags the scalars that would not survive the conversion.
//
// A date-shaped scalar resolves to a timestamp, which has no JSON form. A
// non-string mapping key -- the unquoted 200: written in most specs for a
// status code -- decodes to a map[any]any that json.Marshal rejects. Both
// become strings. An explicitly tagged value is left alone, being a deliberate
// request for that type.
func PrepareForJSON(n *yaml.Node) {
if n == nil {
return
}
if n.Kind == yaml.ScalarNode && n.Tag == "!!timestamp" && n.Style != yaml.TaggedStyle {
n.Tag = "!!str"
}
if n.Kind == yaml.MappingNode {
for i := 0; i < len(n.Content); i += 2 {
if k := n.Content[i]; k.Kind == yaml.ScalarNode && k.Tag != "!!str" {
k.Tag = "!!str"
}
}
}
for _, c := range n.Content {
PrepareForJSON(c)
}
}

// Unmarshal decodes YAML into v via JSON, so v's UnmarshalJSON runs.
func Unmarshal(data []byte, v any) error {
var root yaml.Node
if err := yaml.Unmarshal(data, &root); err != nil {
return err
}
PrepareForJSON(&root)
var generic any
if err := root.Decode(&generic); err != nil {
return err
}
j, err := json.Marshal(generic)
if err != nil {
return err
}
return json.Unmarshal(j, v)
}

// Marshal renders v as YAML via JSON, so v's MarshalJSON runs.
func Marshal(v any) ([]byte, error) {
j, err := json.Marshal(v)
if err != nil {
return nil, err
}
var generic any
if err := json.Unmarshal(j, &generic); err != nil {
return nil, err
}
return yaml.Marshal(generic)
}
13 changes: 10 additions & 3 deletions openapi2/marsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"strings"

"github.com/oasdiff/yaml"
"github.com/getkin/kin-openapi/internal/yamlconv"
)

func unmarshalError(jsonUnmarshalErr error) error {
Expand All @@ -24,11 +24,18 @@ func unmarshal(data []byte, v any) error {
return nil
}

// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
if _, yamlErr = yaml.Unmarshal(data, v, yaml.DecodeOpts{DisableTimestamps: true}); yamlErr == nil {
// YAML reaches these types through JSON, since they implement
// UnmarshalJSON and not UnmarshalYAML.
if yamlErr = yamlconv.Unmarshal(data, v); yamlErr == nil {
return nil
}

// If both unmarshaling attempts fail, return a new error that includes both errors
return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr)
}

// UnmarshalFromData loads a document from swagger 2.0 bytes in either JSON or
// YAML. The v3 side has Loader for this; here the whole job is the decode.
func UnmarshalFromData(data []byte, doc *T) error {
return unmarshal(data, doc)
}
7 changes: 3 additions & 4 deletions openapi2/openapi2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
"os"
"reflect"

"github.com/oasdiff/yaml"

"github.com/getkin/kin-openapi/internal/yamlconv"
"github.com/getkin/kin-openapi/openapi2"
)

Expand Down Expand Up @@ -37,12 +36,12 @@ func Example() {
fmt.Println("objects doc & docAgainFromJSON should be the same")
}

outputYAML, err := yaml.Marshal(doc)
outputYAML, err := yamlconv.Marshal(doc)
if err != nil {
panic(err)
}
var docAgainFromYAML openapi2.T
if _, err = yaml.Unmarshal(outputYAML, &docAgainFromYAML, yaml.DecodeOpts{DisableTimestamps: true}); err != nil {
if err = yamlconv.Unmarshal(outputYAML, &docAgainFromYAML); err != nil {
panic(err)
}
if !reflect.DeepEqual(doc, docAgainFromYAML) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into field Schema.items of type openapi2.Schema
json: cannot unmarshal array into field Schema.items of type openapi2.Schema
4 changes: 2 additions & 2 deletions openapi2/v2_apis_guru_openapi_directory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import (
"sync"
"testing"

"github.com/oasdiff/yaml"
"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/internal/yamlconv"
"github.com/getkin/kin-openapi/openapi2"
)

Expand Down Expand Up @@ -189,7 +189,7 @@ func TestV2ApisGuruOpenapiDirectory(t *testing.T) {
require.NoError(t, err)

var doc openapi2.T
_, err = yaml.Unmarshal(data, &doc, yaml.DecodeOpts{DisableTimestamps: true})
err = yamlconv.Unmarshal(data, &doc)
golden(t, err, shortName, "load")
})
}
Expand Down
4 changes: 2 additions & 2 deletions openapi2conv/issue1062_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package openapi2conv_test
import (
"testing"

"github.com/oasdiff/yaml"
"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/internal/yamlconv"
"github.com/getkin/kin-openapi/openapi2conv"
"github.com/getkin/kin-openapi/openapi3"
)
Expand Down Expand Up @@ -59,7 +59,7 @@ components:
`

var doc3 openapi3.T
_, err := yaml.Unmarshal([]byte(v3Spec), &doc3, yaml.DecodeOpts{DisableTimestamps: true})
err := yamlconv.Unmarshal([]byte(v3Spec), &doc3)
require.NoError(t, err, "unmarshal v3 spec")

// Pre-fix: this call panicked with
Expand Down
4 changes: 2 additions & 2 deletions openapi2conv/issue1069_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package openapi2conv_test
import (
"testing"

"github.com/oasdiff/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/internal/yamlconv"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi2conv"
"github.com/getkin/kin-openapi/openapi3"
Expand Down Expand Up @@ -199,7 +199,7 @@ paths:
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var doc3 openapi3.T
_, err := yaml.Unmarshal([]byte(tt.v3Spec), &doc3, yaml.DecodeOpts{DisableTimestamps: true})
err := yamlconv.Unmarshal([]byte(tt.v3Spec), &doc3)
require.NoError(t, err)

v2, err := openapi2conv.FromV3(&doc3)
Expand Down
8 changes: 4 additions & 4 deletions openapi2conv/issue187_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"encoding/json"
"testing"

"github.com/oasdiff/yaml"
"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/internal/yamlconv"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi2conv"
"github.com/getkin/kin-openapi/openapi3"
Expand All @@ -23,7 +23,7 @@ func v2v3JSON(spec2 []byte) (doc3 *openapi3.T, err error) {

func v2v3YAML(spec2 []byte) (doc3 *openapi3.T, err error) {
var doc2 openapi2.T
if _, err = yaml.Unmarshal(spec2, &doc2, yaml.DecodeOpts{DisableTimestamps: true}); err != nil {
if err = yamlconv.Unmarshal(spec2, &doc2); err != nil {
return
}
doc3, err = openapi2conv.ToV3(&doc2)
Expand Down Expand Up @@ -137,7 +137,7 @@ definitions:
doc3, err := v2v3YAML([]byte(spec))
require.NoError(t, err)

spec3, err := yaml.Marshal(doc3)
spec3, err := yamlconv.Marshal(doc3)
require.NoError(t, err)
const expected = `components:
schemas:
Expand Down Expand Up @@ -185,7 +185,7 @@ securityDefinitions:
doc3, err := v2v3YAML([]byte(spec))
require.NoError(t, err)
require.NotNil(t, doc3.Components.SecuritySchemes["OAuth2Application"].Value.Flows.ClientCredentials)
_, err = yaml.Marshal(doc3)
_, err = yamlconv.Marshal(doc3)
require.NoError(t, err)

doc2, err := openapi2conv.FromV3(doc3)
Expand Down
4 changes: 2 additions & 2 deletions openapi2conv/issue558_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package openapi2conv_test
import (
"testing"

"github.com/oasdiff/yaml"
"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/internal/yamlconv"
"github.com/getkin/kin-openapi/openapi2conv"
)

Expand All @@ -30,7 +30,7 @@ paths:
doc3, err := v2v3YAML([]byte(spec))
require.NoError(t, err)
require.NotEmpty(t, doc3.Paths.Value("/test").Get.Deprecated)
_, err = yaml.Marshal(doc3)
_, err = yamlconv.Marshal(doc3)
require.NoError(t, err)

doc2, err := openapi2conv.FromV3(doc3)
Expand Down
2 changes: 1 addition & 1 deletion openapi3/additionalProperties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"os"
"testing"

yaml "github.com/oasdiff/yaml3"
"github.com/stretchr/testify/require"
yaml "go.yaml.in/yaml/v3"

"github.com/getkin/kin-openapi/openapi3"
)
Expand Down
Loading
Loading