diff --git a/README.md b/README.md index f8bd440..6164ebe 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,26 @@ [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/loads/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/loads?status.svg)](http://godoc.org/github.com/go-openapi/loads) [![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/loads)](https://goreportcard.com/report/github.com/go-openapi/loads) -Loading of OAI specification documents from local or remote locations. Supports JSON and YAML documents. +Loading of OAI v2 API specification documents from local or remote locations. Supports JSON and YAML documents. + +Primary usage: + +```go + import ( + "github.com/go-openapi/loads" + ) + + ... + + // loads a YAML spec from a http file + doc, err := loads.Spec(ts.URL) + + ... + + // retrieves the object model for the API specification + spec := doc.Spec() + + ... +``` + +See also the provided [examples](https://pkg.go.dev/github.com/go-openapi/loads#pkg-examples). diff --git a/doc.go b/doc.go index 5bcaef5..1ca47bc 100644 --- a/doc.go +++ b/doc.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package loads provides document loading methods for swagger (OAI) specifications. +// Package loads provides document loading methods for swagger (OAI v2) API specifications. // // It is used by other go-openapi packages to load and run analysis on local or remote spec documents. package loads diff --git a/doc_test.go b/doc_test.go index 0803a79..a1325a4 100644 --- a/doc_test.go +++ b/doc_test.go @@ -7,11 +7,11 @@ import ( "path/filepath" "github.com/go-openapi/loads" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/loading" ) -func ExampleSpec() { - // Example with default loaders defined at the package level +// Example with default loaders defined at the package level +func ExampleSpec_file() { path := "fixtures/yaml/swagger/spec.yml" doc, err := loads.Spec(path) @@ -25,14 +25,13 @@ func ExampleSpec() { // Output: Spec loaded: "api.example.com" } +// Example with custom loaders passed as options func ExampleLoaderOption() { - // Example with custom loaders passed as options - path := "fixtures/yaml/swagger/spec.yml" // a simpler version of loads.JSONDoc jsonLoader := loads.NewDocLoaderWithMatch( - func(pth string) (json.RawMessage, error) { + func(pth string, _ ...loading.Option) (json.RawMessage, error) { buf, err := os.ReadFile(pth) return json.RawMessage(buf), err }, @@ -43,9 +42,9 @@ func ExampleLoaderOption() { // equivalent to the default loader at the package level, which does: // - // loads.AddLoader(swag.YAMLMatcher, swag.YAMLDoc) + // loads.AddLoader(loading.YAMLMatcher, loading.YAMLDoc) yamlLoader := loads.NewDocLoaderWithMatch( - swag.YAMLDoc, + loading.YAMLDoc, func(pth string) bool { return filepath.Ext(pth) == ".yml" }, diff --git a/example_test.go b/example_test.go deleted file mode 100644 index 5f0f2ee..0000000 --- a/example_test.go +++ /dev/null @@ -1 +0,0 @@ -package loads diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..940b0d6 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,135 @@ +package loads_test + +import ( + "embed" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path" + "path/filepath" + + "github.com/go-openapi/loads" + "github.com/go-openapi/swag/loading" +) + +//go:embed fixtures +var embeddedFixtures embed.FS + +// Loads a JSON document from http, with a custom header +func ExampleJSONSpec_http_custom_header() { + ts := serveSomeJSONDocument() + defer ts.Close() + + doc, err := loads.JSONSpec(ts.URL, + loads.WithLoadingOptions(loading.WithCustomHeaders(map[string]string{ + "X-API-key": "my-api-key", + }))) + if err != nil { + panic(err) + } + fmt.Println(doc.Host()) + fmt.Println(doc.Version()) + + // Output: + // api.example.com + // 2.0 +} + +// Loads a YAML document and get the deserialized [spec.Swagger] specification. +func ExampleSpec_http_yaml() { + ts := serveSomeYAMLDocument() + defer ts.Close() + + // loads a YAML spec from a http URL + doc, err := loads.Spec(ts.URL) + if err != nil { + panic(err) + } + + fmt.Println(doc.Host()) + fmt.Println(doc.Version()) + + spec := doc.Spec() + if spec == nil { + panic("spec should not be nil") + } + + // Output: + // api.example.com + // 2.0 +} + +// Loads a JSON document and get the deserialized [spec.Swagger] specification. +func ExampleSpec_http_json() { + ts := serveSomeJSONDocument() + defer ts.Close() + + // loads a YAML spec from a http URL + doc, err := loads.Spec(ts.URL) + if err != nil { + panic(err) + } + + fmt.Println(doc.Host()) + fmt.Println(doc.Version()) + + spec := doc.Spec() + if spec == nil { + panic("spec should not be nil") + } + + // Output: + // api.example.com + // 2.0 +} + +// Loads a JSON document from the embedded file system and get the deserialized [spec.Swagger] specification. +func ExampleSpec_embedded_yaml() { + // loads a YAML spec from a file on an embedded file system + doc, err := loads.Spec( + path.Join("fixtures", "yaml", "swagger", "spec.yml"), // [embed.FS] sep is "/" even on windows + loads.WithLoadingOptions( + loading.WithFS(embeddedFixtures), + )) + if err != nil { + panic(err) + } + + fmt.Println(doc.Host()) + fmt.Println(doc.Version()) + + spec := doc.Spec() + if spec == nil { + panic("spec should not be nil") + } + + // Output: + // api.example.com + // 2.0 +} + +func serveSomeYAMLDocument() *httptest.Server { + source, err := os.Open(filepath.Join("fixtures", "yaml", "swagger", "spec.yml")) + if err != nil { + panic(err) + } + + return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusOK) + _, _ = io.Copy(rw, source) + })) +} + +func serveSomeJSONDocument() *httptest.Server { + source, err := os.Open(filepath.Join("fixtures", "json", "resources", "pathLoaderIssue.json")) + if err != nil { + panic(err) + } + + return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusOK) + _, _ = io.Copy(rw, source) + })) +} diff --git a/fmts/yaml.go b/fmts/yaml.go index 1cef2ac..ad148c0 100644 --- a/fmts/yaml.go +++ b/fmts/yaml.go @@ -14,17 +14,20 @@ package fmts -import "github.com/go-openapi/swag" +import ( + "github.com/go-openapi/swag/loading" + "github.com/go-openapi/swag/yamlutils" +) var ( // YAMLMatcher matches yaml - YAMLMatcher = swag.YAMLMatcher + YAMLMatcher = loading.YAMLMatcher // YAMLToJSON converts YAML unmarshaled data into json compatible data - YAMLToJSON = swag.YAMLToJSON + YAMLToJSON = yamlutils.YAMLToJSON // BytesToYAMLDoc converts raw bytes to a map[string]interface{} - BytesToYAMLDoc = swag.BytesToYAMLDoc + BytesToYAMLDoc = yamlutils.BytesToYAMLDoc // YAMLDoc loads a yaml document from either http or a file and converts it to json - YAMLDoc = swag.YAMLDoc + YAMLDoc = loading.YAMLDoc // YAMLData loads a yaml document from either http or a file - YAMLData = swag.YAMLData + YAMLData = loading.YAMLData ) diff --git a/fmts/yaml_test.go b/fmts/yaml_test.go index 7adbfd1..7d57dd3 100644 --- a/fmts/yaml_test.go +++ b/fmts/yaml_test.go @@ -23,7 +23,7 @@ import ( yaml "gopkg.in/yaml.v3" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/loading" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +38,7 @@ func (f failJSONMarshal) MarshalJSON() ([]byte, error) { } func TestLoadHTTPBytes(t *testing.T) { - _, err := swag.LoadFromFileOrHTTP("httx://12394:abd") + _, err := loading.LoadFromFileOrHTTP("httx://12394:abd") require.Error(t, err) serv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { @@ -46,7 +46,7 @@ func TestLoadHTTPBytes(t *testing.T) { })) defer serv.Close() - _, err = swag.LoadFromFileOrHTTP(serv.URL) + _, err = loading.LoadFromFileOrHTTP(serv.URL) require.Error(t, err) ts2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { @@ -55,7 +55,7 @@ func TestLoadHTTPBytes(t *testing.T) { })) defer ts2.Close() - d, err := swag.LoadFromFileOrHTTP(ts2.URL) + d, err := loading.LoadFromFileOrHTTP(ts2.URL) require.NoError(t, err) assert.Equal(t, []byte("the content"), d) } @@ -138,7 +138,7 @@ func TestLoadStrategy(t *testing.T) { return []byte("not it"), nil } - ld := swag.LoadStrategy("blah", loader, remLoader) + ld := loading.LoadStrategy("blah", loader, remLoader) b, _ := ld("") assert.YAMLEq(t, yamlPetStore, string(b)) diff --git a/go.mod b/go.mod index cd3a62d..e590a7b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/go-openapi/loads require ( github.com/go-openapi/analysis v0.23.0 github.com/go-openapi/spec v0.21.0 - github.com/go-openapi/swag v0.24.1 + github.com/go-openapi/swag/loading v0.24.0 + github.com/go-openapi/swag/yamlutils v0.24.0 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -11,28 +12,27 @@ require ( require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/errors v0.22.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/errors v0.22.2 // indirect + github.com/go-openapi/jsonpointer v0.22.0 // indirect + github.com/go-openapi/jsonreference v0.21.1 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.24.1 // indirect github.com/go-openapi/swag/cmdutils v0.24.0 // indirect github.com/go-openapi/swag/conv v0.24.0 // indirect github.com/go-openapi/swag/fileutils v0.24.0 // indirect github.com/go-openapi/swag/jsonname v0.24.0 // indirect github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect github.com/go-openapi/swag/mangling v0.24.0 // indirect github.com/go-openapi/swag/netutils v0.24.0 // indirect github.com/go-openapi/swag/stringutils v0.24.0 // indirect github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.mongodb.org/mongo-driver v1.14.0 // indirect + go.mongodb.org/mongo-driver v1.17.4 // indirect ) -go 1.20 +go 1.24.0 diff --git a/go.sum b/go.sum index 1846ce9..cc4372c 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= -github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg= +github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= +github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= +github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= +github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= +github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= @@ -38,13 +38,16 @@ github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zib github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -54,11 +57,13 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= -go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= +go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/loaders.go b/loaders.go index 7c8a759..5e1f550 100644 --- a/loaders.go +++ b/loaders.go @@ -6,7 +6,7 @@ import ( "net/url" "github.com/go-openapi/spec" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/loading" ) var ( @@ -30,8 +30,8 @@ func init() { loaders = jsonLoader.WithHead(&loader{ DocLoaderWithMatch: DocLoaderWithMatch{ - Match: swag.YAMLMatcher, - Fn: swag.YAMLDoc, + Match: loading.YAMLMatcher, + Fn: loading.YAMLDoc, }, }) @@ -40,7 +40,7 @@ func init() { } // DocLoader represents a doc loader type -type DocLoader func(string) (json.RawMessage, error) +type DocLoader func(string, ...loading.Option) (json.RawMessage, error) // DocMatcher represents a predicate to check if a loader matches type DocMatcher func(string) bool @@ -62,6 +62,8 @@ func NewDocLoaderWithMatch(fn DocLoader, matcher DocMatcher) DocLoaderWithMatch type loader struct { DocLoaderWithMatch + loadingOptions []loading.Option + Next *loader } @@ -94,7 +96,7 @@ func (l *loader) Load(path string) (json.RawMessage, error) { } // try then move to next one if there is an error - b, err := ldr.Fn(path) + b, err := ldr.Fn(path, l.loadingOptions...) if err == nil { return b, nil } @@ -105,9 +107,12 @@ func (l *loader) Load(path string) (json.RawMessage, error) { return nil, errors.Join(lastErr, ErrLoads) } -// JSONDoc loads a json document from either a file or a remote url -func JSONDoc(path string) (json.RawMessage, error) { - data, err := swag.LoadFromFileOrHTTP(path) +// JSONDoc loads a json document from either a file or a remote url. +// +// See [loading.Option] for available options (e.g. configuring authentifaction, +// headers or using embedded file system resources). +func JSONDoc(path string, opts ...loading.Option) (json.RawMessage, error) { + data, err := loading.LoadFromFileOrHTTP(path, opts...) if err != nil { return nil, errors.Join(err, ErrLoads) } diff --git a/options.go b/options.go index f8305d5..21892a6 100644 --- a/options.go +++ b/options.go @@ -1,7 +1,10 @@ package loads +import "github.com/go-openapi/swag/loading" + type options struct { - loader *loader + loader *loader + loadingOptions []loading.Option } func defaultOptions() *options { @@ -16,7 +19,10 @@ func loaderFromOptions(options []LoaderOption) *loader { apply(opts) } - return opts.loader + l := opts.loader + l.loadingOptions = opts.loadingOptions + + return l } // LoaderOption allows to fine-tune the spec loader behavior @@ -59,3 +65,10 @@ func WithDocLoaderMatches(l ...DocLoaderWithMatch) LoaderOption { opt.loader = final } } + +// WithLoadingOptions adds some [loading.Option] to be added when calling a registered loader. +func WithLoadingOptions(loadingOptions ...loading.Option) LoaderOption { + return func(opt *options) { + opt.loadingOptions = loadingOptions + } +} diff --git a/options_test.go b/options_test.go index 9aa2e93..c20759e 100644 --- a/options_test.go +++ b/options_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/loading" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -17,7 +17,7 @@ const optionFixture = "fixtures/json/resources/pathLoaderIssue.json" var errTest = errors.New("test") func TestOptionsWithDocLoader(t *testing.T) { - document, err := Spec(optionFixture, WithDocLoader(func(pth string) (json.RawMessage, error) { + document, err := Spec(optionFixture, WithDocLoader(func(pth string, _ ...loading.Option) (json.RawMessage, error) { buf, err := os.ReadFile(pth) return json.RawMessage(buf), err })) @@ -43,12 +43,12 @@ func TestOptionsLoaderFromOptions(t *testing.T) { // not chaining here, just replacing with the last one l := loaderFromOptions([]LoaderOption{ - WithDocLoader(func(pth string) (json.RawMessage, error) { + WithDocLoader(func(pth string, _ ...loading.Option) (json.RawMessage, error) { called = 1 buf, err := os.ReadFile(pth) return json.RawMessage(buf), err }), - WithDocLoader(func(pth string) (json.RawMessage, error) { + WithDocLoader(func(pth string, _ ...loading.Option) (json.RawMessage, error) { called = 2 buf, err := os.ReadFile(pth) return json.RawMessage(buf), err @@ -65,7 +65,7 @@ func TestOptionsLoaderFromOptions(t *testing.T) { func TestOptionsWithDocLoaderMatches(t *testing.T) { jsonLoader := NewDocLoaderWithMatch( - func(pth string) (json.RawMessage, error) { + func(pth string, _ ...loading.Option) (json.RawMessage, error) { buf, err := os.ReadFile(pth) return json.RawMessage(buf), err }, @@ -80,7 +80,7 @@ func TestOptionsWithDocLoaderMatches(t *testing.T) { require.NotNil(t, document.pathLoader) yamlLoader := NewDocLoaderWithMatch( - swag.YAMLDoc, + loading.YAMLDoc, func(pth string) bool { return filepath.Ext(pth) == ".yaml" }, @@ -106,7 +106,7 @@ func TestOptionsWithDocLoaderMatches(t *testing.T) { require.NotNil(t, document) // the nil matcher always matches - nilMatcher := NewDocLoaderWithMatch(func(_ string) (json.RawMessage, error) { + nilMatcher := NewDocLoaderWithMatch(func(_ string, _ ...loading.Option) (json.RawMessage, error) { return nil, errTest }, nil) _, err = Spec(optionFixture, WithDocLoaderMatches(nilMatcher)) diff --git a/spec.go b/spec.go index 433a4c4..838231a 100644 --- a/spec.go +++ b/spec.go @@ -20,15 +20,16 @@ import ( "encoding/json" "errors" "fmt" + "maps" "github.com/go-openapi/analysis" "github.com/go-openapi/spec" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/yamlutils" ) func init() { - gob.Register(map[string]interface{}{}) - gob.Register([]interface{}{}) + gob.Register(map[string]any{}) + gob.Register([]any{}) } // Document represents a swagger spec document @@ -43,14 +44,21 @@ type Document struct { raw json.RawMessage } -// JSONSpec loads a spec from a json document -func JSONSpec(path string, options ...LoaderOption) (*Document, error) { - data, err := JSONDoc(path) +// JSONSpec loads a spec from a json document, using the [JSONDoc] loader. +// +// A set of [loading.Option] may be passed to this loader using [WithLoadingOptions]. +func JSONSpec(path string, opts ...LoaderOption) (*Document, error) { + var o options + for _, apply := range opts { + apply(&o) + } + + data, err := JSONDoc(path, o.loadingOptions...) if err != nil { return nil, err } // convert to json - doc, err := Analyzed(data, "", options...) + doc, err := Analyzed(data, "", opts...) if err != nil { return nil, err } @@ -60,8 +68,8 @@ func JSONSpec(path string, options ...LoaderOption) (*Document, error) { return doc, nil } -// Embedded returns a Document based on embedded specs. No analysis is required -func Embedded(orig, flat json.RawMessage, options ...LoaderOption) (*Document, error) { +// Embedded returns a Document based on embedded specs (i.e. as a raw [json.RawMessage]). No analysis is required +func Embedded(orig, flat json.RawMessage, opts ...LoaderOption) (*Document, error) { var origSpec, flatSpec spec.Swagger if err := json.Unmarshal(orig, &origSpec); err != nil { return nil, err @@ -73,20 +81,22 @@ func Embedded(orig, flat json.RawMessage, options ...LoaderOption) (*Document, e raw: orig, origSpec: &origSpec, spec: &flatSpec, - pathLoader: loaderFromOptions(options), + pathLoader: loaderFromOptions(opts), }, nil } -// Spec loads a new spec document from a local or remote path -func Spec(path string, options ...LoaderOption) (*Document, error) { - ldr := loaderFromOptions(options) +// Spec loads a new spec document from a local or remote path. +// +// By default it uses a JSON or YAML loader, with auto-detection based on the resource extension. +func Spec(path string, opts ...LoaderOption) (*Document, error) { + ldr := loaderFromOptions(opts) b, err := ldr.Load(path) if err != nil { return nil, err } - document, err := Analyzed(b, "", options...) + document, err := Analyzed(b, "", opts...) if err != nil { return nil, err } @@ -144,12 +154,12 @@ func trimData(in json.RawMessage) (json.RawMessage, error) { } // assume yaml doc: convert it to json - yml, err := swag.BytesToYAMLDoc(trimmed) + yml, err := yamlutils.BytesToYAMLDoc(trimmed) if err != nil { return nil, fmt.Errorf("analyzed: %v: %w", err, ErrLoads) } - d, err := swag.YAMLToJSON(yml) + d, err := yamlutils.YAMLToJSON(yml) if err != nil { return nil, fmt.Errorf("analyzed: %v: %w", err, ErrLoads) } @@ -157,7 +167,7 @@ func trimData(in json.RawMessage) (json.RawMessage, error) { return d, nil } -// Expanded expands the $ref fields in the spec document and returns a new spec document +// Expanded expands the $ref fields in the spec [Document] and returns a new expanded [Document] func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) { swspec := new(spec.Swagger) if err := json.Unmarshal(d.raw, swspec); err != nil { @@ -209,17 +219,17 @@ func (d *Document) BasePath() string { return d.spec.BasePath } -// Version returns the version of this spec +// Version returns the OpenAPI version of this spec (e.g. 2.0) func (d *Document) Version() string { return d.spec.Swagger } -// Schema returns the swagger 2.0 schema +// Schema returns the swagger 2.0 meta-schema func (d *Document) Schema() *spec.Schema { return d.schema } -// Spec returns the swagger spec object model +// Spec returns the swagger object model for this API specification func (d *Document) Spec() *spec.Swagger { return d.spec } @@ -239,14 +249,11 @@ func (d *Document) OrigSpec() *spec.Swagger { return d.origSpec } -// ResetDefinitions gives a shallow copy with the models reset to the original spec +// ResetDefinitions yields a shallow copy with the models reset to the original spec func (d *Document) ResetDefinitions() *Document { - defs := make(map[string]spec.Schema, len(d.origSpec.Definitions)) - for k, v := range d.origSpec.Definitions { - defs[k] = v - } + d.spec.Definitions = make(map[string]spec.Schema, len(d.origSpec.Definitions)) + maps.Copy(d.spec.Definitions, d.origSpec.Definitions) - d.spec.Definitions = defs return d }