Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
39 changes: 39 additions & 0 deletions .claude/skills/pre-commit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: pre-commit
description: Run pre-commit checks for kin-openapi before committing. Use when about to commit, after making code changes, or when the user asks to validate changes.
disable-model-invocation: true
allowed-tools: Bash
---

# Pre-commit checks for kin-openapi

Before committing changes to kin-openapi, run these steps in order:

## 1. Regenerate docs

```bash
./docs.sh
```

This regenerates `.github/docs/*.txt` from `go doc` output. CI checks that these files match and will fail if they are stale.

If `docs.sh` fails with missing mentions, it means a public API symbol was changed or removed. Add an entry to the `## CHANGELOG: Sub-v1 breaking API changes` section in `README.md` describing the change. The script uses `grep -F` to find the symbol name, so the mention must contain the exact symbol text.

Stage any updated `.github/docs/*.txt` and `README.md` files.

## 2. Run tests

```bash
go test ./...
```

## 3. Vet and format

```bash
go vet ./...
go fmt ./...
```

## CI lint rule

Never use `require.Contains(t, err.Error(), ...)` in tests. Use `require.ErrorContains(t, err, ...)` instead. CI greps for `require[.].+err.Error` and fails.
20 changes: 12 additions & 8 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -842,8 +842,10 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error)
ResolveRefsIn expands references if for instance spec was just unmarshaled

type Location struct {
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
File string `json:"file,omitempty" yaml:"file,omitempty"`
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
Location is a struct that contains the location of a field.

Expand Down Expand Up @@ -1041,12 +1043,14 @@ func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOpti
spec.

type Origin struct {
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
Sequences map[string][]Location `json:"sequences,omitempty" yaml:"sequences,omitempty"`
}
Origin contains the origin of a collection. Key is the location of the
collection itself. Fields is a map of the location of each field in the
collection.
collection itself. Fields is a map of the location of each scalar field
in the collection. Sequences is a map of the location of each item in
sequence-valued fields.

type Parameter struct {
Extensions map[string]any `json:"-" yaml:"-"`
Expand Down Expand Up @@ -1308,8 +1312,8 @@ type Ref struct {
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object

type RefNameResolver func(*T, ComponentRef) string
RefNameResolver maps a component to an name that is used as it's
internalized name.
RefNameResolver maps a component to a name that is used as it's internalized
name.

The function should avoid name collisions (i.e. be a injective mapping). It
must only contain characters valid for fixed field names: IdentifierRegExp.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ for _, path := range doc.Paths.InMatchingOrder() {

## CHANGELOG: Sub-v1 breaking API changes

### v0.134.0
* `openapi3.Location` gained `File` and `Name` fields (`string` type, replacing previous `int`-only struct layout)
* `openapi3.Origin` gained `Sequences` field (`map[string][]Location`, extending previous `map[string]Location`-only struct)

### v0.131.0
* No longer `openapi3filter.RegisterBodyDecoder` the `openapi3filter.ZipFileBodyDecoder` by default.

Expand Down
2 changes: 1 addition & 1 deletion docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ count_missing_mentions() {
| grep -Eo '^-[^ ]+ ([^ (]+)[ (]' \
| sed 's%(% %' \
| cut -d' ' -f2); do
if ! grep -A999999 '## Sub-v0 breaking API changes' README.md | grep -F "$thing"; then
if ! grep -A999999 'breaking API changes' README.md | grep -F "$thing"; then
((errors++)) || true
fi
done
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require (
github.com/go-openapi/jsonpointer v0.21.0
github.com/gorilla/mux v1.8.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90
github.com/oasdiff/yaml v0.0.0-20260217201108-4f1c3d02ddd4
github.com/oasdiff/yaml3 v0.0.0-20260217201013-8a620da6102a
github.com/perimeterx/marshmallow v1.1.5
github.com/stretchr/testify v1.9.0
github.com/woodsbury/decimal128 v1.3.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/oasdiff/yaml v0.0.0-20260217201108-4f1c3d02ddd4 h1:WM2g1YIFwlbz3xcGO/RtaT0LBykfe0Cd3seWa4jZELM=
github.com/oasdiff/yaml v0.0.0-20260217201108-4f1c3d02ddd4/go.mod h1:ZTJR/EwUBsFK7J01Ybq7z/XCz8ioB4TMdr72QTeuqoY=
github.com/oasdiff/yaml3 v0.0.0-20260217201013-8a620da6102a h1:so9gdCU1AyG+EFCCp6ORLut4MBi/wIh4J3jGrEjNZnI=
github.com/oasdiff/yaml3 v0.0.0-20260217201013-8a620da6102a/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
2 changes: 1 addition & 1 deletion openapi3/internalize_refs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"
)

// RefNameResolver maps a component to an name that is used as it's internalized name.
// RefNameResolver maps a component to a name that is used as it's internalized name.
//
// The function should avoid name collisions (i.e. be a injective mapping).
// It must only contain characters valid for fixed field names: [IdentifierRegExp].
Expand Down
8 changes: 4 additions & 4 deletions openapi3/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, el
if err != nil {
return nil, err
}
if err := unmarshal(data, element, IncludeOrigin); err != nil {
if err := unmarshal(data, element, IncludeOrigin, resolvedPath); err != nil {
return nil, err
}

Expand Down Expand Up @@ -144,7 +144,7 @@ func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error) {
func (loader *Loader) LoadFromData(data []byte) (*T, error) {
loader.resetVisitedPathItemRefs()
doc := &T{}
if err := unmarshal(data, doc, IncludeOrigin); err != nil {
if err := unmarshal(data, doc, IncludeOrigin, nil); err != nil {
return nil, err
}
if err := loader.ResolveRefsIn(doc, nil); err != nil {
Expand Down Expand Up @@ -173,7 +173,7 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR
doc := &T{}
loader.visitedDocuments[uri] = doc

if err := unmarshal(data, doc, IncludeOrigin); err != nil {
if err := unmarshal(data, doc, IncludeOrigin, location); err != nil {
return nil, err
}

Expand Down Expand Up @@ -427,7 +427,7 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv
if err2 != nil {
return nil, nil, err
}
if err2 = unmarshal(data, &cursor, IncludeOrigin); err2 != nil {
if err2 = unmarshal(data, &cursor, IncludeOrigin, path); err2 != nil {
return nil, nil, err
}
if cursor, err2 = drill(cursor); err2 != nil || cursor == nil {
Expand Down
9 changes: 7 additions & 2 deletions openapi3/marsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package openapi3
import (
"encoding/json"
"fmt"
"net/url"
"strings"

"github.com/oasdiff/yaml"
Expand All @@ -16,7 +17,7 @@ func unmarshalError(jsonUnmarshalErr error) error {
return jsonUnmarshalErr
}

func unmarshal(data []byte, v any, includeOrigin bool) error {
func unmarshal(data []byte, v any, includeOrigin bool, location *url.URL) error {
var jsonErr, yamlErr error

// See https://github.com/getkin/kin-openapi/issues/680
Expand All @@ -25,7 +26,11 @@ func unmarshal(data []byte, v any, includeOrigin bool) error {
}

// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
if yamlErr = yaml.UnmarshalWithOrigin(data, v, includeOrigin); yamlErr == nil {
var file string
if location != nil {
file = location.Path
}
if yamlErr = yaml.UnmarshalWithOrigin(data, v, includeOrigin, file); yamlErr == nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! This line needs to be fixed.
A lot of my works services are screaming "vendor/github.com/getkin/kin-openapi/openapi3/marsh.go:28:49: not enough arguments in call to yaml.UnmarshalWithOrigin"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please provide a spec that generates this error?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, just chipping-in, here's response of the maintainers on this issue (tldr; they're reverting it). I've also closed #1130 based on this.

return nil
}

Expand Down
14 changes: 9 additions & 5 deletions openapi3/origin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ const originKey = "__origin__"

// Origin contains the origin of a collection.
// Key is the location of the collection itself.
// Fields is a map of the location of each field in the collection.
// Fields is a map of the location of each scalar field in the collection.
// Sequences is a map of the location of each item in sequence-valued fields.
type Origin struct {
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
Sequences map[string][]Location `json:"sequences,omitempty" yaml:"sequences,omitempty"`
}

// Location is a struct that contains the location of a field.
type Location struct {
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
File string `json:"file,omitempty" yaml:"file,omitempty"`
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
Loading
Loading