├── .gitattributes ├── .github ├── FUNDING.yml ├── docs │ ├── openapi2.txt │ ├── openapi2conv.txt │ ├── openapi3.txt │ ├── openapi3filter.txt │ ├── openapi3gen.txt │ ├── routers.txt │ ├── routers_gorillamux.txt │ ├── routers_legacy.txt │ └── routers_legacy_pathpattern.txt └── workflows │ ├── go.yml │ └── shellcheck.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── validate │ └── main.go ├── docs.sh ├── go.mod ├── go.sum ├── maps.sh ├── openapi2 ├── doc.go ├── header.go ├── helpers.go ├── issues1010_test.go ├── marsh.go ├── marsh_test.go ├── openapi2.go ├── openapi2_test.go ├── operation.go ├── parameter.go ├── path_item.go ├── ref.go ├── refs.go ├── response.go ├── schema.go ├── security_scheme.go └── testdata │ └── swagger.json ├── openapi2conv ├── doc.go ├── issue1008_test.go ├── issue1016_test.go ├── issue1049_test.go ├── issue1069_test.go ├── issue187_test.go ├── issue440_test.go ├── issue558_test.go ├── issue573_test.go ├── issue847_test.go ├── issue979_test.go ├── openapi2_conv.go ├── openapi2_conv_test.go └── testdata │ └── swagger.json ├── openapi3 ├── additionalProperties_test.go ├── callback.go ├── components.go ├── contact.go ├── content.go ├── content_test.go ├── datetime_schema_test.go ├── discriminator.go ├── discriminator_test.go ├── doc.go ├── encoding.go ├── encoding_test.go ├── errors.go ├── example.go ├── example_test.go ├── example_validation.go ├── example_validation_test.go ├── extension.go ├── external_docs.go ├── external_docs_test.go ├── header.go ├── helpers.go ├── helpers_test.go ├── info.go ├── internalize_refs.go ├── internalize_refs_test.go ├── issue136_test.go ├── issue241_test.go ├── issue301_test.go ├── issue341_test.go ├── issue344_test.go ├── issue376_test.go ├── issue382_test.go ├── issue495_test.go ├── issue499_test.go ├── issue513_test.go ├── issue542_test.go ├── issue570_test.go ├── issue594_test.go ├── issue601_test.go ├── issue615_test.go ├── issue618_test.go ├── issue638_test.go ├── issue652_test.go ├── issue657_test.go ├── issue689_test.go ├── issue697_test.go ├── issue735_test.go ├── issue741_test.go ├── issue746_test.go ├── issue753_test.go ├── issue759_test.go ├── issue767_test.go ├── issue794_test.go ├── issue796_test.go ├── issue819_test.go ├── issue82_test.go ├── issue883_test.go ├── issue961_test.go ├── issue972_test.go ├── license.go ├── link.go ├── load_cicular_ref_with_external_file_test.go ├── load_with_go_embed_test.go ├── loader.go ├── loader_circular_test.go ├── loader_empty_response_description_test.go ├── loader_http_error_test.go ├── loader_issue212_test.go ├── loader_issue220_test.go ├── loader_issue235_test.go ├── loader_outside_refs_test.go ├── loader_paths_test.go ├── loader_read_from_uri_func_test.go ├── loader_recursive_ref_test.go ├── loader_relative_refs_test.go ├── loader_test.go ├── loader_uri_reader.go ├── maplike.go ├── maplike_test.go ├── mapping_test.go ├── marsh.go ├── marsh_test.go ├── media_type.go ├── media_type_test.go ├── openapi3.go ├── openapi3_test.go ├── operation.go ├── operation_test.go ├── origin.go ├── origin_test.go ├── parameter.go ├── parameter_issue223_test.go ├── parameter_issue834_test.go ├── path_item.go ├── paths.go ├── paths_test.go ├── race_test.go ├── ref.go ├── refs.go ├── refs.tmpl ├── refs_issue222_test.go ├── refs_issue247_test.go ├── refs_test.go ├── refs_test.tmpl ├── refsgenerator.go ├── request_body.go ├── response.go ├── response_issue224_test.go ├── schema.go ├── schema_formats.go ├── schema_formats_test.go ├── schema_issue289_test.go ├── schema_issue492_test.go ├── schema_issue940_test.go ├── schema_oneOf_test.go ├── schema_pattern.go ├── schema_pattern_test.go ├── schema_test.go ├── schema_validation_settings.go ├── schema_validation_settings_test.go ├── security_requirements.go ├── security_requirements_test.go ├── security_scheme.go ├── security_scheme_test.go ├── serialization_method.go ├── server.go ├── server_test.go ├── stringmap.go ├── tag.go ├── testdata │ ├── 303bis │ │ ├── common │ │ │ └── properties.yaml │ │ └── service.yaml │ ├── Test_param_override.yml │ ├── callback-transactioned.yml │ ├── callbacks.yml │ ├── callbacks.yml.internalized.yml │ ├── circularRef │ │ ├── base.yml │ │ ├── baz.yml │ │ └── other.yml │ ├── circularRef2 │ │ ├── AwsEnvironmentSettings.yaml │ │ └── circular2.yaml │ ├── circularref.openapi.yml │ ├── components.openapi.json │ ├── components.openapi.yml │ ├── draft04.yml │ ├── ext.json │ ├── interalizationNameCollision │ │ ├── api.yml │ │ ├── api.yml.internalized.yml │ │ └── schemas │ │ │ ├── book │ │ │ └── record.yml │ │ │ └── cd │ │ │ └── record.yml │ ├── issue235.spec0-typo.yml │ ├── issue235.spec0.yml │ ├── issue235.spec1.yml │ ├── issue235.spec2.yml │ ├── issue241.yml │ ├── issue409.yml │ ├── issue499 │ │ ├── foo.yml │ │ └── main.yml │ ├── issue570.json │ ├── issue638 │ │ ├── test1.yaml │ │ └── test2.yaml │ ├── issue652 │ │ ├── definitions.yml │ │ └── nested │ │ │ └── schema.yml │ ├── issue697.yml │ ├── issue753.yml │ ├── issue794.yml │ ├── issue796.yml │ ├── issue831 │ │ ├── path.yml │ │ ├── testref.internalizepath.openapi.yml │ │ └── testref.internalizepath.openapi.yml.internalized.yml │ ├── issue959 │ │ ├── components.yml │ │ ├── openapi.yml │ │ └── openapi.yml.internalized.yml │ ├── issue961 │ │ ├── config_param.yml │ │ └── main.yml │ ├── link-example.yaml │ ├── lxkns.yaml │ ├── main.yaml │ ├── my-openapi.json │ ├── my-other-openapi.json │ ├── nesteddir │ │ ├── nestedcomponents.openapi.json │ │ └── nestedcomponentsref.openapi.json │ ├── oai_v3_stoplight.json │ ├── origin │ │ ├── additional_properties.yaml │ │ ├── example.yaml │ │ ├── external_docs.yaml │ │ ├── parameters.yaml │ │ ├── request_body.yaml │ │ ├── security.yaml │ │ ├── simple.yaml │ │ └── xml.yaml │ ├── pathref.openapi.yml │ ├── recursiveRef │ │ ├── components │ │ │ ├── Bar.yml │ │ │ ├── Cat.yml │ │ │ ├── Foo.yml │ │ │ ├── Foo │ │ │ │ └── Foo2.yml │ │ │ └── models │ │ │ │ └── error.yaml │ │ ├── issue615.yml │ │ ├── openapi.yml │ │ ├── openapi.yml.internalized.yml │ │ ├── parameters │ │ │ └── number.yml │ │ └── paths │ │ │ └── foo.yml │ ├── refInLocalRef │ │ ├── messages │ │ │ ├── data.json │ │ │ ├── dataPart.json │ │ │ ├── request.json │ │ │ └── response.json │ │ └── openapi.json │ ├── refInLocalRefInParentsSubdir │ │ ├── messages │ │ │ ├── data.json │ │ │ ├── dataPart.json │ │ │ ├── request.json │ │ │ └── response.json │ │ └── spec │ │ │ └── openapi.json │ ├── refInRef │ │ ├── messages │ │ │ ├── definitions.json │ │ │ ├── request.json │ │ │ └── response.json │ │ └── openapi.json │ ├── refInRefInProperty │ │ ├── common-data-objects │ │ │ └── problem-details-0.0.1.schema.json │ │ ├── components │ │ │ └── errors.yaml │ │ └── openapi.yaml │ ├── refsToRoot │ │ ├── openapi.yml │ │ ├── other │ │ │ ├── example.yml │ │ │ ├── header.yml │ │ │ ├── parameter.yml │ │ │ └── response.yml │ │ └── schemas │ │ │ ├── book │ │ │ ├── record.yml │ │ │ └── records.yml │ │ │ ├── cd │ │ │ ├── record.yml │ │ │ └── records.yml │ │ │ └── error.yml │ ├── relativeDocs │ │ ├── CustomTestExample.yml │ │ ├── CustomTestHeader.yml │ │ ├── CustomTestParameter.yml │ │ ├── CustomTestPath.yml │ │ ├── CustomTestRequestBody.yml │ │ ├── CustomTestResponse.yml │ │ ├── CustomTestSchema.yml │ │ ├── CustomTestSecurityScheme.yml │ │ └── openapi │ │ │ ├── openapi.yml │ │ │ └── responses │ │ │ └── custom │ │ │ └── CustomTestResponse.yml │ ├── relativeDocsUseDocumentPath │ │ ├── CustomTestExample.yml │ │ ├── CustomTestHeader.yml │ │ ├── CustomTestHeader1.yml │ │ ├── CustomTestHeader1bis.yml │ │ ├── CustomTestHeader2.yml │ │ ├── CustomTestHeader2bis.yml │ │ ├── CustomTestParameter.yml │ │ ├── CustomTestRequestBody.yml │ │ ├── CustomTestResponse.yml │ │ ├── CustomTestSchema.yml │ │ └── openapi │ │ │ ├── openapi.yml │ │ │ ├── paths │ │ │ └── nesteddir │ │ │ │ ├── CustomTestPath.yml │ │ │ │ └── morenested │ │ │ │ └── CustomTestPath.yml │ │ │ └── responses │ │ │ └── nesteddir │ │ │ └── CustomTestResponse.yml │ ├── schema618.yml │ ├── singleresponse.openapi.json │ ├── singleresponse.openapi.yml │ ├── spec.yaml │ ├── spec.yaml.internalized.yml │ ├── test.openapi.additionalproperties.yml │ ├── test.openapi.json │ ├── test.openapi.yml │ ├── testpath.yaml │ ├── testref.openapi.json │ ├── testref.openapi.yml │ ├── testref.openapi.yml.internalized.yml │ ├── testrefsinglecomponent.openapi.json │ └── testrefsinglecomponent.openapi.yml ├── unique_items_checker_test.go ├── validation_issue409_test.go ├── validation_options.go ├── visited.go └── xml.go ├── openapi3filter ├── authentication_input.go ├── csv_file_upload_test.go ├── errors.go ├── internal.go ├── issue1045_test.go ├── issue201_test.go ├── issue267_test.go ├── issue436_test.go ├── issue624_test.go ├── issue625_test.go ├── issue639_test.go ├── issue641_test.go ├── issue689_test.go ├── issue707_test.go ├── issue722_test.go ├── issue733_test.go ├── issue743_test.go ├── issue789_test.go ├── issue884_test.go ├── issue991_test.go ├── middleware.go ├── middleware_test.go ├── options.go ├── options_test.go ├── req_resp_decoder.go ├── req_resp_decoder_test.go ├── req_resp_encoder.go ├── req_resp_encoder_test.go ├── testdata │ ├── fixtures │ │ └── petstore.json │ └── petstore.yaml ├── unpack_errors_test.go ├── validate_readonly_test.go ├── validate_request.go ├── validate_request_example_test.go ├── validate_request_input.go ├── validate_request_test.go ├── validate_response.go ├── validate_response_input.go ├── validate_response_test.go ├── validate_set_default_test.go ├── validation_discriminator_test.go ├── validation_enum_test.go ├── validation_error.go ├── validation_error_encoder.go ├── validation_error_test.go ├── validation_handler.go ├── validation_kit.go ├── validation_test.go └── zip_file_upload_test.go ├── openapi3gen ├── field_info.go ├── internal │ └── subpkg │ │ └── sub_type.go ├── openapi3gen.go ├── openapi3gen_newschemarefforvalue_test.go ├── openapi3gen_test.go ├── simple_test.go └── type_info.go └── routers ├── gorillamux ├── example_test.go ├── router.go └── router_test.go ├── issue356_test.go ├── legacy ├── issue444_test.go ├── pathpattern │ ├── node.go │ └── node_test.go ├── router.go ├── router_test.go └── validate_request_test.go └── types.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.yml text eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [fenollp] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/docs/routers.txt: -------------------------------------------------------------------------------- 1 | package routers // import "github.com/getkin/kin-openapi/routers" 2 | 3 | 4 | VARIABLES 5 | 6 | var ErrMethodNotAllowed error = &RouteError{"method not allowed"} 7 | ErrMethodNotAllowed is returned when no method of the matched route matches 8 | 9 | var ErrPathNotFound error = &RouteError{"no matching operation was found"} 10 | ErrPathNotFound is returned when no route match is found 11 | 12 | 13 | TYPES 14 | 15 | type Route struct { 16 | Spec *openapi3.T 17 | Server *openapi3.Server 18 | Path string 19 | PathItem *openapi3.PathItem 20 | Method string 21 | Operation *openapi3.Operation 22 | } 23 | Route describes the operation an http.Request can match 24 | 25 | type RouteError struct { 26 | Reason string 27 | } 28 | RouteError describes Router errors 29 | 30 | func (e *RouteError) Error() string 31 | 32 | type Router interface { 33 | // FindRoute matches an HTTP request with the operation it resolves to. 34 | // Hosts are matched from the OpenAPIv3 servers key. 35 | // 36 | // If you experience ErrPathNotFound and have localhost hosts specified as your servers, 37 | // turning these server URLs as relative (leaving only the path) should resolve this. 38 | // 39 | // See openapi3filter for example uses with request and response validation. 40 | FindRoute(req *http.Request) (route *Route, pathParams map[string]string, err error) 41 | } 42 | Router helps link http.Request.s and an OpenAPIv3 spec 43 | 44 | -------------------------------------------------------------------------------- /.github/docs/routers_gorillamux.txt: -------------------------------------------------------------------------------- 1 | package gorillamux // import "github.com/getkin/kin-openapi/routers/gorillamux" 2 | 3 | Package gorillamux implements a router. 4 | 5 | It differs from the legacy router: * it provides somewhat granular errors: "path 6 | not found", "method not allowed". * it handles matching routes with extensions 7 | (e.g. /books/{id}.json) * it handles path patterns with a different syntax (e.g. 8 | /params/{x}/{y}/{z:.*}) 9 | 10 | FUNCTIONS 11 | 12 | func NewRouter(doc *openapi3.T) (routers.Router, error) 13 | NewRouter creates a gorilla/mux router. Assumes spec is .Validate()d Note 14 | that a variable for the port number MUST have a default value and only this 15 | value will match as the port (see issue #367). 16 | 17 | 18 | TYPES 19 | 20 | type Router struct { 21 | // Has unexported fields. 22 | } 23 | Router helps link http.Request.s and an OpenAPIv3 spec 24 | 25 | func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) 26 | FindRoute extracts the route and parameters of an http.Request 27 | 28 | -------------------------------------------------------------------------------- /.github/docs/routers_legacy.txt: -------------------------------------------------------------------------------- 1 | package legacy // import "github.com/getkin/kin-openapi/routers/legacy" 2 | 3 | Package legacy implements a router. 4 | 5 | It differs from the gorilla/mux router: * it provides granular errors: "path 6 | not found", "method not allowed", "variable missing from path" * it does not 7 | handle matching routes with extensions (e.g. /books/{id}.json) * it handles path 8 | patterns with a different syntax (e.g. /params/{x}/{y}/{z.*}) 9 | 10 | FUNCTIONS 11 | 12 | func NewRouter(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error) 13 | NewRouter creates a new router. 14 | 15 | If the given OpenAPIv3 document has servers, router will use them. All 16 | operations of the document will be added to the router. 17 | 18 | 19 | TYPES 20 | 21 | type Router struct { 22 | // Has unexported fields. 23 | } 24 | Router maps a HTTP request to an OpenAPI operation. 25 | 26 | func (router *Router) AddRoute(route *routers.Route) error 27 | AddRoute adds a route in the router. 28 | 29 | func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) 30 | FindRoute extracts the route and parameters of an http.Request 31 | 32 | type Routers []*Router 33 | Routers maps a HTTP request to a Router. 34 | 35 | func (rs Routers) FindRoute(req *http.Request) (routers.Router, *routers.Route, map[string]string, error) 36 | FindRoute extracts the route and parameters of an http.Request 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: ShellCheck 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | shellcheck: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Run shellcheck 15 | uses: ludeeus/action-shellcheck@1.1.0 16 | with: 17 | check_together: 'yes' 18 | severity: error 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Macos file system 17 | .DS_Store 18 | 19 | # IntelliJ / GoLand 20 | .idea 21 | .vscode 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 the project authors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eux 2 | set -o pipefail 3 | 4 | outdir=.github/docs 5 | mkdir -p "$outdir" 6 | for pkgpath in $(git ls-files | grep / | while read -r path; do dirname "$path"; done | sort -u | grep -vE '[.]git|testdata|internal|cmd/'); do 7 | echo $pkgpath 8 | go doc -all ./"$pkgpath" | tee "$outdir/${pkgpath////_}.txt" 9 | done 10 | 11 | git --no-pager diff -- .github/docs/ 12 | 13 | count_missing_mentions() { 14 | local errors=0 15 | for thing in $(git --no-pager diff -- .github/docs/ \ 16 | | grep -vE '[-]{3}' \ 17 | | grep -Eo '^-[^ ]+ ([^ (]+)[ (]' \ 18 | | sed 's%(% %' \ 19 | | cut -d' ' -f2); do 20 | if ! grep -A999999 '## Sub-v0 breaking API changes' README.md | grep -F "$thing"; then 21 | ((errors++)) || true 22 | fi 23 | done 24 | return $errors 25 | } 26 | count_missing_mentions 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/getkin/kin-openapi 2 | 3 | go 1.22.5 4 | 5 | require ( 6 | github.com/go-openapi/jsonpointer v0.21.0 7 | github.com/gorilla/mux v1.8.0 8 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 9 | github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 10 | github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 11 | github.com/perimeterx/marshmallow v1.1.5 12 | github.com/stretchr/testify v1.9.0 13 | github.com/woodsbury/decimal128 v1.3.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/go-openapi/swag v0.23.0 // indirect 19 | github.com/josharian/intern v1.0.0 // indirect 20 | github.com/mailru/easyjson v0.7.7 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | github.com/rogpeppe/go-internal v1.12.0 // indirect 23 | gopkg.in/yaml.v3 v3.0.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /openapi2/doc.go: -------------------------------------------------------------------------------- 1 | // Package openapi2 parses and writes OpenAPIv2 specification documents. 2 | // 3 | // Does not cover all elements of OpenAPIv2. 4 | // When OpenAPI version 3 is backwards-compatible with version 2, version 3 elements have been used. 5 | // 6 | // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md 7 | package openapi2 8 | -------------------------------------------------------------------------------- /openapi2/header.go: -------------------------------------------------------------------------------- 1 | package openapi2 2 | 3 | type Header struct { 4 | Parameter 5 | } 6 | 7 | // MarshalJSON returns the JSON encoding of Header. 8 | func (header Header) MarshalJSON() ([]byte, error) { 9 | return header.Parameter.MarshalJSON() 10 | } 11 | 12 | // UnmarshalJSON sets Header to a copy of data. 13 | func (header *Header) UnmarshalJSON(data []byte) error { 14 | return header.Parameter.UnmarshalJSON(data) 15 | } 16 | -------------------------------------------------------------------------------- /openapi2/helpers.go: -------------------------------------------------------------------------------- 1 | package openapi2 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | // copyURI makes a copy of the pointer. 8 | func copyURI(u *url.URL) *url.URL { 9 | if u == nil { 10 | return nil 11 | } 12 | 13 | c := *u // shallow-copy 14 | return &c 15 | } 16 | -------------------------------------------------------------------------------- /openapi2/marsh.go: -------------------------------------------------------------------------------- 1 | package openapi2 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/oasdiff/yaml" 9 | ) 10 | 11 | func unmarshalError(jsonUnmarshalErr error) error { 12 | if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" { 13 | before = strings.ReplaceAll(before, " Go struct ", " ") 14 | return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", "")) 15 | } 16 | return jsonUnmarshalErr 17 | } 18 | 19 | func unmarshal(data []byte, v any) error { 20 | var jsonErr, yamlErr error 21 | 22 | // See https://github.com/getkin/kin-openapi/issues/680 23 | if jsonErr = json.Unmarshal(data, v); jsonErr == nil { 24 | return nil 25 | } 26 | 27 | // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys 28 | if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil { 29 | return nil 30 | } 31 | 32 | // If both unmarshaling attempts fail, return a new error that includes both errors 33 | return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr) 34 | } 35 | -------------------------------------------------------------------------------- /openapi2/marsh_test.go: -------------------------------------------------------------------------------- 1 | package openapi2 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestUnmarshalError(t *testing.T) { 10 | { 11 | v2 := []byte(` 12 | openapi: '2.0' 13 | info: 14 | version: '1.10' 15 | title: title 16 | paths: 17 | "/ping": 18 | post: 19 | consumes: 20 | - multipart/form-data 21 | parameters: 22 | name: file # <-- Missing dash 23 | in: formData 24 | description: file 25 | required: true 26 | type: file 27 | responses: 28 | '200': 29 | description: OK 30 | `[1:]) 31 | 32 | var doc T 33 | err := unmarshal(v2, &doc) 34 | require.ErrorContains(t, err, `json: cannot unmarshal object into field Operation.parameters of type openapi2.Parameters`) 35 | } 36 | 37 | v2 := []byte(` 38 | openapi: '2.0' 39 | info: 40 | version: '1.10' 41 | title: title 42 | paths: 43 | "/ping": 44 | post: 45 | consumes: 46 | - multipart/form-data 47 | parameters: 48 | - name: file # <-- 49 | in: formData 50 | description: file 51 | required: true 52 | type: file 53 | responses: 54 | '200': 55 | description: OK 56 | `[1:]) 57 | 58 | var doc T 59 | err := unmarshal(v2, &doc) 60 | require.NoError(t, err) 61 | } 62 | -------------------------------------------------------------------------------- /openapi2/openapi2_test.go: -------------------------------------------------------------------------------- 1 | package openapi2_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "reflect" 8 | 9 | "github.com/oasdiff/yaml" 10 | 11 | "github.com/getkin/kin-openapi/openapi2" 12 | ) 13 | 14 | func Example() { 15 | input, err := os.ReadFile("testdata/swagger.json") 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | var doc openapi2.T 21 | if err = json.Unmarshal(input, &doc); err != nil { 22 | panic(err) 23 | } 24 | if doc.ExternalDocs.Description != "Find out more about Swagger" { 25 | panic(`doc.ExternalDocs was parsed incorrectly!`) 26 | } 27 | 28 | outputJSON, err := json.Marshal(doc) 29 | if err != nil { 30 | panic(err) 31 | } 32 | var docAgainFromJSON openapi2.T 33 | if err = json.Unmarshal(outputJSON, &docAgainFromJSON); err != nil { 34 | panic(err) 35 | } 36 | if !reflect.DeepEqual(doc, docAgainFromJSON) { 37 | fmt.Println("objects doc & docAgainFromJSON should be the same") 38 | } 39 | 40 | outputYAML, err := yaml.Marshal(doc) 41 | if err != nil { 42 | panic(err) 43 | } 44 | var docAgainFromYAML openapi2.T 45 | if err = yaml.Unmarshal(outputYAML, &docAgainFromYAML); err != nil { 46 | panic(err) 47 | } 48 | if !reflect.DeepEqual(doc, docAgainFromYAML) { 49 | fmt.Println("objects doc & docAgainFromYAML should be the same") 50 | } 51 | 52 | // Output: 53 | } 54 | -------------------------------------------------------------------------------- /openapi2/ref.go: -------------------------------------------------------------------------------- 1 | package openapi2 2 | 3 | //go:generate go run refsgenerator.go 4 | 5 | // Ref is specified by OpenAPI/Swagger 2.0 standard. 6 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object 7 | type Ref struct { 8 | Ref string `json:"$ref" yaml:"$ref"` 9 | } 10 | -------------------------------------------------------------------------------- /openapi2/response.go: -------------------------------------------------------------------------------- 1 | package openapi2 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/getkin/kin-openapi/openapi3" 7 | ) 8 | 9 | type Response struct { 10 | Extensions map[string]any `json:"-" yaml:"-"` 11 | 12 | Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` 13 | 14 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 15 | Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` 16 | Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` 17 | Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"` 18 | } 19 | 20 | // MarshalJSON returns the JSON encoding of Response. 21 | func (response Response) MarshalJSON() ([]byte, error) { 22 | if ref := response.Ref; ref != "" { 23 | return json.Marshal(openapi3.Ref{Ref: ref}) 24 | } 25 | 26 | m := make(map[string]any, 4+len(response.Extensions)) 27 | for k, v := range response.Extensions { 28 | m[k] = v 29 | } 30 | if x := response.Description; x != "" { 31 | m["description"] = x 32 | } 33 | if x := response.Schema; x != nil { 34 | m["schema"] = x 35 | } 36 | if x := response.Headers; len(x) != 0 { 37 | m["headers"] = x 38 | } 39 | if x := response.Examples; len(x) != 0 { 40 | m["examples"] = x 41 | } 42 | return json.Marshal(m) 43 | } 44 | 45 | // UnmarshalJSON sets Response to a copy of data. 46 | func (response *Response) UnmarshalJSON(data []byte) error { 47 | type ResponseBis Response 48 | var x ResponseBis 49 | if err := json.Unmarshal(data, &x); err != nil { 50 | return unmarshalError(err) 51 | } 52 | _ = json.Unmarshal(data, &x.Extensions) 53 | delete(x.Extensions, "$ref") 54 | delete(x.Extensions, "description") 55 | delete(x.Extensions, "schema") 56 | delete(x.Extensions, "headers") 57 | delete(x.Extensions, "examples") 58 | if len(x.Extensions) == 0 { 59 | x.Extensions = nil 60 | } 61 | *response = Response(x) 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /openapi2conv/doc.go: -------------------------------------------------------------------------------- 1 | // Package openapi2conv converts an OpenAPI v2 specification document to v3. 2 | package openapi2conv 3 | -------------------------------------------------------------------------------- /openapi2conv/issue1008_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIssue1008(t *testing.T) { 12 | v2 := []byte(` 13 | swagger: '2.0' 14 | info: 15 | version: '1.10' 16 | title: title 17 | paths: 18 | "/ping": 19 | post: 20 | consumes: 21 | - multipart/form-data 22 | parameters: 23 | - name: zebra 24 | in: formData 25 | description: stripes 26 | required: true 27 | type: string 28 | - name: alpaca 29 | in: formData 30 | description: chewy 31 | required: true 32 | type: string 33 | - name: bee 34 | in: formData 35 | description: buzz 36 | required: true 37 | type: string 38 | responses: 39 | '200': 40 | description: OK 41 | `) 42 | 43 | v3, err := v2v3YAML(v2) 44 | require.NoError(t, err) 45 | 46 | err = v3.Validate(context.Background()) 47 | require.NoError(t, err) 48 | assert.Equal(t, []string{"alpaca", "bee", "zebra"}, v3.Paths.Value("/ping").Post.RequestBody.Value.Content.Get("multipart/form-data").Schema.Value.Required) 49 | } 50 | -------------------------------------------------------------------------------- /openapi2conv/issue1016_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/getkin/kin-openapi/openapi2" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestIssue1016(t *testing.T) { 13 | v2 := []byte(` 14 | { 15 | "basePath": "/v2", 16 | "host": "test.example.com", 17 | "info": { 18 | "title": "MyAPI", 19 | "version": "0.1", 20 | "x-info": "info extension" 21 | }, 22 | "paths": { 23 | "/foo": { 24 | "get": { 25 | "operationId": "getFoo", 26 | "responses": { 27 | "200": { 28 | "description": "returns all information", 29 | "schema": { 30 | "$ref": "#/definitions/PetDirectory" 31 | } 32 | }, 33 | "default": { 34 | "description": "OK" 35 | } 36 | }, 37 | "summary": "get foo" 38 | } 39 | } 40 | }, 41 | "schemes": [ 42 | "http" 43 | ], 44 | "swagger": "2.0", 45 | "definitions": { 46 | "Pet": { 47 | "type": "object", 48 | "required": ["petType"], 49 | "properties": { 50 | "petType": { 51 | "type": "string" 52 | }, 53 | "name": { 54 | "type": "string" 55 | }, 56 | "age": { 57 | "type": "integer" 58 | } 59 | } 60 | }, 61 | "PetDirectory":{ 62 | "type": "object", 63 | "additionalProperties": { 64 | "$ref": "#/definitions/Pet" 65 | } 66 | } 67 | } 68 | } 69 | `) 70 | 71 | var doc2 openapi2.T 72 | err := json.Unmarshal(v2, &doc2) 73 | require.NoError(t, err) 74 | 75 | doc3, err := v2v3YAML(v2) 76 | require.NoError(t, err) 77 | 78 | err = doc3.Validate(context.Background()) 79 | require.NoError(t, err) 80 | require.Equal(t, "#/components/schemas/Pet", doc3.Components.Schemas["PetDirectory"].Value.AdditionalProperties.Schema.Ref) 81 | } 82 | -------------------------------------------------------------------------------- /openapi2conv/issue440_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/getkin/kin-openapi/openapi2" 12 | "github.com/getkin/kin-openapi/openapi3" 13 | ) 14 | 15 | func TestIssue440(t *testing.T) { 16 | doc2file, err := os.Open("testdata/swagger.json") 17 | require.NoError(t, err) 18 | defer doc2file.Close() 19 | var doc2 openapi2.T 20 | err = json.NewDecoder(doc2file).Decode(&doc2) 21 | require.NoError(t, err) 22 | 23 | doc3, err := ToV3(&doc2) 24 | require.NoError(t, err) 25 | err = doc3.Validate(context.Background()) 26 | require.NoError(t, err) 27 | require.Equal(t, openapi3.Servers{ 28 | {URL: "https://petstore.swagger.io/v2"}, 29 | {URL: "http://petstore.swagger.io/v2"}, 30 | }, doc3.Servers) 31 | 32 | doc2.Host = "your-bot-domain.de" 33 | doc2.Schemes = nil 34 | doc2.BasePath = "" 35 | doc3, err = ToV3(&doc2) 36 | require.NoError(t, err) 37 | err = doc3.Validate(context.Background()) 38 | require.NoError(t, err) 39 | require.Equal(t, openapi3.Servers{ 40 | {URL: "https://your-bot-domain.de/"}, 41 | }, doc3.Servers) 42 | 43 | doc2.Host = "https://your-bot-domain.de" 44 | doc2.Schemes = nil 45 | doc2.BasePath = "" 46 | doc3, err = ToV3(&doc2) 47 | require.Error(t, err) 48 | require.ErrorContains(t, err, `invalid host`) 49 | } 50 | -------------------------------------------------------------------------------- /openapi2conv/issue558_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oasdiff/yaml" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestPR558(t *testing.T) { 11 | spec := ` 12 | swagger: '2.0' 13 | info: 14 | version: 1.0.0 15 | title: title 16 | paths: 17 | /test: 18 | get: 19 | deprecated: true 20 | parameters: 21 | - in: body 22 | schema: 23 | type: object 24 | responses: 25 | '200': 26 | description: description 27 | ` 28 | doc3, err := v2v3YAML([]byte(spec)) 29 | require.NoError(t, err) 30 | require.NotEmpty(t, doc3.Paths.Value("/test").Get.Deprecated) 31 | _, err = yaml.Marshal(doc3) 32 | require.NoError(t, err) 33 | 34 | doc2, err := FromV3(doc3) 35 | require.NoError(t, err) 36 | require.NotEmpty(t, doc2.Paths["/test"].Get.Deprecated) 37 | } 38 | -------------------------------------------------------------------------------- /openapi2conv/issue573_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue573(t *testing.T) { 10 | spec := []byte(`paths: 11 | /ping: 12 | get: 13 | produces: 14 | - application/toml 15 | - application/xml 16 | responses: 17 | 200: 18 | schema: 19 | type: object 20 | properties: 21 | username: 22 | type: string 23 | description: The user name. 24 | post: 25 | responses: 26 | 200: 27 | schema: 28 | type: object 29 | properties: 30 | username: 31 | type: string 32 | description: The user name.`) 33 | 34 | v3, err := v2v3YAML(spec) 35 | require.NoError(t, err) 36 | 37 | // Make sure the response content appears for each mime-type originally 38 | // appeared in "produces". 39 | pingGetContent := v3.Paths.Value("/ping").Get.Responses.Value("200").Value.Content 40 | require.Len(t, pingGetContent, 2) 41 | require.Contains(t, pingGetContent, "application/toml") 42 | require.Contains(t, pingGetContent, "application/xml") 43 | 44 | // Is "produces" is not explicitly specified, default to "application/json". 45 | pingPostContent := v3.Paths.Value("/ping").Post.Responses.Value("200").Value.Content 46 | require.Len(t, pingPostContent, 1) 47 | require.Contains(t, pingPostContent, "application/json") 48 | } 49 | -------------------------------------------------------------------------------- /openapi2conv/issue847_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestIssue847(t *testing.T) { 11 | v2 := []byte(` 12 | swagger: '2.0' 13 | info: 14 | version: '1.10' 15 | title: title 16 | paths: 17 | "/ping": 18 | post: 19 | consumes: 20 | - multipart/form-data 21 | parameters: 22 | - name: file 23 | in: formData 24 | description: file 25 | required: true 26 | type: file 27 | responses: 28 | '200': 29 | description: OK 30 | `) 31 | 32 | v3, err := v2v3YAML(v2) 33 | require.NoError(t, err) 34 | 35 | err = v3.Validate(context.Background()) 36 | require.NoError(t, err) 37 | 38 | require.Equal(t, []string{"file"}, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Required) 39 | 40 | require.Nil(t, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties["file"].Value.Required) 41 | } 42 | -------------------------------------------------------------------------------- /openapi2conv/issue979_test.go: -------------------------------------------------------------------------------- 1 | package openapi2conv 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/getkin/kin-openapi/openapi2" 11 | "github.com/getkin/kin-openapi/openapi3" 12 | ) 13 | 14 | func TestIssue979(t *testing.T) { 15 | v2 := []byte(` 16 | { 17 | "basePath": "/v2", 18 | "host": "test.example.com", 19 | "info": { 20 | "title": "MyAPI", 21 | "version": "0.1", 22 | "x-info": "info extension" 23 | }, 24 | "paths": { 25 | "/foo": { 26 | "get": { 27 | "operationId": "getFoo", 28 | "produces": [ 29 | "application/pdf", 30 | "application/json" 31 | ], 32 | "responses": { 33 | "200": { 34 | "description": "returns all information", 35 | "schema": { 36 | "type": "file" 37 | } 38 | }, 39 | "default": { 40 | "description": "OK" 41 | } 42 | }, 43 | "summary": "get foo" 44 | } 45 | } 46 | }, 47 | "schemes": [ 48 | "http" 49 | ], 50 | "swagger": "2.0" 51 | } 52 | `) 53 | 54 | var doc2 openapi2.T 55 | err := json.Unmarshal(v2, &doc2) 56 | require.NoError(t, err) 57 | 58 | doc3, err := ToV3(&doc2) 59 | require.NoError(t, err) 60 | err = doc3.Validate(context.Background()) 61 | require.NoError(t, err) 62 | 63 | require.Equal(t, &openapi3.Types{"string"}, doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value.Type) 64 | require.Equal(t, "binary", doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value.Format) 65 | 66 | require.Equal(t, &openapi3.Types{"string"}, doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/pdf").Schema.Value.Type) 67 | require.Equal(t, "binary", doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/pdf").Schema.Value.Format) 68 | } 69 | -------------------------------------------------------------------------------- /openapi2conv/testdata/swagger.json: -------------------------------------------------------------------------------- 1 | ../../openapi2/testdata/swagger.json -------------------------------------------------------------------------------- /openapi3/additionalProperties_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os" 7 | "testing" 8 | 9 | "github.com/oasdiff/yaml3" 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/getkin/kin-openapi/openapi3" 13 | ) 14 | 15 | func TestMarshalAdditionalProperties(t *testing.T) { 16 | ctx := context.Background() 17 | data, err := os.ReadFile("testdata/test.openapi.additionalproperties.yml") 18 | require.NoError(t, err) 19 | 20 | loader := openapi3.NewLoader() 21 | loader.IsExternalRefsAllowed = true 22 | spec, err := loader.LoadFromData(data) 23 | require.NoError(t, err) 24 | 25 | err = spec.Validate(ctx) 26 | require.NoError(t, err) 27 | 28 | var buf bytes.Buffer 29 | enc := yaml.NewEncoder(&buf) 30 | enc.SetIndent(2) 31 | err = enc.Encode(spec) 32 | require.NoError(t, err) 33 | 34 | // Load the doc from the serialized yaml. 35 | spec2, err := loader.LoadFromData(buf.Bytes()) 36 | require.NoError(t, err) 37 | 38 | err = spec2.Validate(ctx) 39 | require.NoError(t, err) 40 | } 41 | -------------------------------------------------------------------------------- /openapi3/callback.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "sort" 6 | ) 7 | 8 | // Callback is specified by OpenAPI/Swagger standard version 3. 9 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object 10 | type Callback struct { 11 | Extensions map[string]any `json:"-" yaml:"-"` 12 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 13 | 14 | m map[string]*PathItem 15 | } 16 | 17 | // NewCallback builds a Callback object with path items in insertion order. 18 | func NewCallback(opts ...NewCallbackOption) *Callback { 19 | Callback := NewCallbackWithCapacity(len(opts)) 20 | for _, opt := range opts { 21 | opt(Callback) 22 | } 23 | return Callback 24 | } 25 | 26 | // NewCallbackOption describes options to NewCallback func 27 | type NewCallbackOption func(*Callback) 28 | 29 | // WithCallback adds Callback as an option to NewCallback 30 | func WithCallback(cb string, pathItem *PathItem) NewCallbackOption { 31 | return func(callback *Callback) { 32 | if p := pathItem; p != nil && cb != "" { 33 | callback.Set(cb, p) 34 | } 35 | } 36 | } 37 | 38 | // Validate returns an error if Callback does not comply with the OpenAPI spec. 39 | func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error { 40 | ctx = WithValidationOptions(ctx, opts...) 41 | 42 | keys := make([]string, 0, callback.Len()) 43 | for key := range callback.Map() { 44 | keys = append(keys, key) 45 | } 46 | sort.Strings(keys) 47 | for _, key := range keys { 48 | v := callback.Value(key) 49 | if err := v.Validate(ctx); err != nil { 50 | return err 51 | } 52 | } 53 | 54 | return validateExtensions(ctx, callback.Extensions) 55 | } 56 | 57 | // UnmarshalJSON sets Callbacks to a copy of data. 58 | func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) { 59 | *callbacks, _, err = unmarshalStringMapP[CallbackRef](data) 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /openapi3/contact.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // Contact is specified by OpenAPI/Swagger standard version 3. 9 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object 10 | type Contact struct { 11 | Extensions map[string]any `json:"-" yaml:"-"` 12 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 13 | 14 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 15 | URL string `json:"url,omitempty" yaml:"url,omitempty"` 16 | Email string `json:"email,omitempty" yaml:"email,omitempty"` 17 | } 18 | 19 | // MarshalJSON returns the JSON encoding of Contact. 20 | func (contact Contact) MarshalJSON() ([]byte, error) { 21 | x, err := contact.MarshalYAML() 22 | if err != nil { 23 | return nil, err 24 | } 25 | return json.Marshal(x) 26 | } 27 | 28 | // MarshalYAML returns the YAML encoding of Contact. 29 | func (contact Contact) MarshalYAML() (any, error) { 30 | m := make(map[string]any, 3+len(contact.Extensions)) 31 | for k, v := range contact.Extensions { 32 | m[k] = v 33 | } 34 | if x := contact.Name; x != "" { 35 | m["name"] = x 36 | } 37 | if x := contact.URL; x != "" { 38 | m["url"] = x 39 | } 40 | if x := contact.Email; x != "" { 41 | m["email"] = x 42 | } 43 | return m, nil 44 | } 45 | 46 | // UnmarshalJSON sets Contact to a copy of data. 47 | func (contact *Contact) UnmarshalJSON(data []byte) error { 48 | type ContactBis Contact 49 | var x ContactBis 50 | if err := json.Unmarshal(data, &x); err != nil { 51 | return unmarshalError(err) 52 | } 53 | _ = json.Unmarshal(data, &x.Extensions) 54 | 55 | delete(x.Extensions, originKey) 56 | delete(x.Extensions, "name") 57 | delete(x.Extensions, "url") 58 | delete(x.Extensions, "email") 59 | if len(x.Extensions) == 0 { 60 | x.Extensions = nil 61 | } 62 | *contact = Contact(x) 63 | return nil 64 | } 65 | 66 | // Validate returns an error if Contact does not comply with the OpenAPI spec. 67 | func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error { 68 | ctx = WithValidationOptions(ctx, opts...) 69 | 70 | return validateExtensions(ctx, contact.Extensions) 71 | } 72 | -------------------------------------------------------------------------------- /openapi3/discriminator.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // Discriminator is specified by OpenAPI/Swagger standard version 3. 9 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object 10 | type Discriminator struct { 11 | Extensions map[string]any `json:"-" yaml:"-"` 12 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 13 | 14 | PropertyName string `json:"propertyName" yaml:"propertyName"` // required 15 | Mapping StringMap `json:"mapping,omitempty" yaml:"mapping,omitempty"` 16 | } 17 | 18 | // MarshalJSON returns the JSON encoding of Discriminator. 19 | func (discriminator Discriminator) MarshalJSON() ([]byte, error) { 20 | x, err := discriminator.MarshalYAML() 21 | if err != nil { 22 | return nil, err 23 | } 24 | return json.Marshal(x) 25 | } 26 | 27 | // MarshalYAML returns the YAML encoding of Discriminator. 28 | func (discriminator Discriminator) MarshalYAML() (any, error) { 29 | m := make(map[string]any, 2+len(discriminator.Extensions)) 30 | for k, v := range discriminator.Extensions { 31 | m[k] = v 32 | } 33 | m["propertyName"] = discriminator.PropertyName 34 | if x := discriminator.Mapping; len(x) != 0 { 35 | m["mapping"] = x 36 | } 37 | return m, nil 38 | } 39 | 40 | // UnmarshalJSON sets Discriminator to a copy of data. 41 | func (discriminator *Discriminator) UnmarshalJSON(data []byte) error { 42 | type DiscriminatorBis Discriminator 43 | var x DiscriminatorBis 44 | if err := json.Unmarshal(data, &x); err != nil { 45 | return unmarshalError(err) 46 | } 47 | _ = json.Unmarshal(data, &x.Extensions) 48 | 49 | delete(x.Extensions, originKey) 50 | delete(x.Extensions, "propertyName") 51 | delete(x.Extensions, "mapping") 52 | if len(x.Extensions) == 0 { 53 | x.Extensions = nil 54 | } 55 | *discriminator = Discriminator(x) 56 | return nil 57 | } 58 | 59 | // Validate returns an error if Discriminator does not comply with the OpenAPI spec. 60 | func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error { 61 | ctx = WithValidationOptions(ctx, opts...) 62 | 63 | return validateExtensions(ctx, discriminator.Extensions) 64 | } 65 | -------------------------------------------------------------------------------- /openapi3/discriminator_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestParsingDiscriminator(t *testing.T) { 10 | const spec = ` 11 | { 12 | "openapi": "3.0.0", 13 | "info": { 14 | "version": "1.0.0", 15 | "title": "title", 16 | "description": "desc", 17 | "contact": { 18 | "email": "email" 19 | } 20 | }, 21 | "paths": {}, 22 | "components": { 23 | "schemas": { 24 | "MyResponseType": { 25 | "discriminator": { 26 | "mapping": { 27 | "cat": "#/components/schemas/Cat", 28 | "dog": "#/components/schemas/Dog" 29 | }, 30 | "propertyName": "pet_type" 31 | }, 32 | "oneOf": [ 33 | { 34 | "$ref": "#/components/schemas/Cat" 35 | }, 36 | { 37 | "$ref": "#/components/schemas/Dog" 38 | } 39 | ] 40 | }, 41 | "Cat": {"enum": ["chat"]}, 42 | "Dog": {"enum": ["chien"]} 43 | } 44 | } 45 | } 46 | ` 47 | 48 | loader := NewLoader() 49 | doc, err := loader.LoadFromData([]byte(spec)) 50 | require.NoError(t, err) 51 | 52 | err = doc.Validate(loader.Context) 53 | require.NoError(t, err) 54 | 55 | require.Len(t, doc.Components.Schemas["MyResponseType"].Value.Discriminator.Mapping, 2) 56 | } 57 | -------------------------------------------------------------------------------- /openapi3/doc.go: -------------------------------------------------------------------------------- 1 | // Package openapi3 parses and writes OpenAPI 3 specification documents. 2 | // 3 | // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md 4 | package openapi3 5 | -------------------------------------------------------------------------------- /openapi3/errors.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | ) 7 | 8 | // MultiError is a collection of errors, intended for when 9 | // multiple issues need to be reported upstream 10 | type MultiError []error 11 | 12 | func (me MultiError) Error() string { 13 | return spliceErr(" | ", me) 14 | } 15 | 16 | func spliceErr(sep string, errs []error) string { 17 | buff := &bytes.Buffer{} 18 | for i, e := range errs { 19 | buff.WriteString(e.Error()) 20 | if i != len(errs)-1 { 21 | buff.WriteString(sep) 22 | } 23 | } 24 | return buff.String() 25 | } 26 | 27 | // Is allows you to determine if a generic error is in fact a MultiError using `errors.Is()` 28 | // It will also return true if any of the contained errors match target 29 | func (me MultiError) Is(target error) bool { 30 | if _, ok := target.(MultiError); ok { 31 | return true 32 | } 33 | for _, e := range me { 34 | if errors.Is(e, target) { 35 | return true 36 | } 37 | } 38 | return false 39 | } 40 | 41 | // As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type 42 | func (me MultiError) As(target any) bool { 43 | for _, e := range me { 44 | if errors.As(e, target) { 45 | return true 46 | } 47 | } 48 | return false 49 | } 50 | 51 | type multiErrorForOneOf MultiError 52 | 53 | func (meo multiErrorForOneOf) Error() string { 54 | return spliceErr(" Or ", meo) 55 | } 56 | 57 | func (meo multiErrorForOneOf) Unwrap() error { 58 | return MultiError(meo) 59 | } 60 | -------------------------------------------------------------------------------- /openapi3/example_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestExampleJSON(t *testing.T) { 11 | t.Log("Marshal *openapi3.Example to JSON") 12 | data, err := json.Marshal(example()) 13 | require.NoError(t, err) 14 | require.NotEmpty(t, data) 15 | 16 | t.Log("Unmarshal *openapi3.Example from JSON") 17 | docA := &Example{} 18 | err = json.Unmarshal(exampleJSON, &docA) 19 | require.NoError(t, err) 20 | require.NotEmpty(t, data) 21 | 22 | t.Log("Ensure representations match") 23 | dataA, err := json.Marshal(docA) 24 | require.NoError(t, err) 25 | require.JSONEq(t, string(data), string(exampleJSON)) 26 | require.JSONEq(t, string(data), string(dataA)) 27 | } 28 | 29 | var exampleJSON = []byte(` 30 | { 31 | "summary": "An example of a cat", 32 | "value": { 33 | "name": "Fluffy", 34 | "petType": "Cat", 35 | "color": "White", 36 | "gender": "male", 37 | "breed": "Persian" 38 | } 39 | } 40 | `) 41 | 42 | func example() *Example { 43 | value := map[string]string{ 44 | "name": "Fluffy", 45 | "petType": "Cat", 46 | "color": "White", 47 | "gender": "male", 48 | "breed": "Persian", 49 | } 50 | return &Example{ 51 | Summary: "An example of a cat", 52 | Value: value, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /openapi3/example_validation.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import "context" 4 | 5 | func validateExampleValue(ctx context.Context, input any, schema *Schema) error { 6 | opts := make([]SchemaValidationOption, 0, 2) 7 | 8 | if vo := getValidationOptions(ctx); vo.examplesValidationAsReq { 9 | opts = append(opts, VisitAsRequest()) 10 | } else if vo.examplesValidationAsRes { 11 | opts = append(opts, VisitAsResponse()) 12 | } 13 | opts = append(opts, MultiErrors()) 14 | 15 | return schema.VisitJSON(input, opts...) 16 | } 17 | -------------------------------------------------------------------------------- /openapi3/extension.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | func validateExtensions(ctx context.Context, extensions map[string]any) error { // FIXME: newtype + Validate(...) 11 | allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed 12 | 13 | var unknowns []string 14 | for k := range extensions { 15 | if strings.HasPrefix(k, "x-") { 16 | continue 17 | } 18 | if allowed != nil { 19 | if _, ok := allowed[k]; ok { 20 | continue 21 | } 22 | } 23 | unknowns = append(unknowns, k) 24 | } 25 | 26 | if len(unknowns) != 0 { 27 | sort.Strings(unknowns) 28 | return fmt.Errorf("extra sibling fields: %+v", unknowns) 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /openapi3/external_docs.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "net/url" 9 | ) 10 | 11 | // ExternalDocs is specified by OpenAPI/Swagger standard version 3. 12 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object 13 | type ExternalDocs struct { 14 | Extensions map[string]any `json:"-" yaml:"-"` 15 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 16 | 17 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 18 | URL string `json:"url,omitempty" yaml:"url,omitempty"` 19 | } 20 | 21 | // MarshalJSON returns the JSON encoding of ExternalDocs. 22 | func (e ExternalDocs) MarshalJSON() ([]byte, error) { 23 | x, err := e.MarshalYAML() 24 | if err != nil { 25 | return nil, err 26 | } 27 | return json.Marshal(x) 28 | } 29 | 30 | // MarshalYAML returns the YAML encoding of ExternalDocs. 31 | func (e ExternalDocs) MarshalYAML() (any, error) { 32 | m := make(map[string]any, 2+len(e.Extensions)) 33 | for k, v := range e.Extensions { 34 | m[k] = v 35 | } 36 | if x := e.Description; x != "" { 37 | m["description"] = x 38 | } 39 | if x := e.URL; x != "" { 40 | m["url"] = x 41 | } 42 | return m, nil 43 | } 44 | 45 | // UnmarshalJSON sets ExternalDocs to a copy of data. 46 | func (e *ExternalDocs) UnmarshalJSON(data []byte) error { 47 | type ExternalDocsBis ExternalDocs 48 | var x ExternalDocsBis 49 | if err := json.Unmarshal(data, &x); err != nil { 50 | return unmarshalError(err) 51 | } 52 | _ = json.Unmarshal(data, &x.Extensions) 53 | delete(x.Extensions, originKey) 54 | delete(x.Extensions, "description") 55 | delete(x.Extensions, "url") 56 | if len(x.Extensions) == 0 { 57 | x.Extensions = nil 58 | } 59 | *e = ExternalDocs(x) 60 | return nil 61 | } 62 | 63 | // Validate returns an error if ExternalDocs does not comply with the OpenAPI spec. 64 | func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error { 65 | ctx = WithValidationOptions(ctx, opts...) 66 | 67 | if e.URL == "" { 68 | return errors.New("url is required") 69 | } 70 | if _, err := url.Parse(e.URL); err != nil { 71 | return fmt.Errorf("url is incorrect: %w", err) 72 | } 73 | 74 | return validateExtensions(ctx, e.Extensions) 75 | } 76 | -------------------------------------------------------------------------------- /openapi3/external_docs_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestExternalDocs_Validate(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | extDocs *ExternalDocs 14 | expectedErr string 15 | }{ 16 | { 17 | name: "url is missing", 18 | extDocs: &ExternalDocs{}, 19 | expectedErr: "url is required", 20 | }, 21 | { 22 | name: "url is incorrect", 23 | extDocs: &ExternalDocs{URL: "ht tps://example.com"}, 24 | expectedErr: `url is incorrect: parse "ht tps://example.com": first path segment in URL cannot contain colon`, 25 | }, 26 | { 27 | name: "ok", 28 | extDocs: &ExternalDocs{URL: "https://example.com"}, 29 | }, 30 | } 31 | for i := range tests { 32 | tt := tests[i] 33 | t.Run(tt.name, func(t *testing.T) { 34 | err := tt.extDocs.Validate(context.Background()) 35 | if tt.expectedErr != "" { 36 | require.EqualError(t, err, tt.expectedErr) 37 | } else { 38 | require.NoError(t, err) 39 | } 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /openapi3/internalize_refs_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "regexp" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestInternalizeRefs(t *testing.T) { 13 | ctx := context.Background() 14 | 15 | regexpRef := regexp.MustCompile(`"\$ref":`) 16 | regexpRefInternal := regexp.MustCompile(`"\$ref":"#`) 17 | 18 | tests := []struct { 19 | filename string 20 | }{ 21 | {"testdata/testref.openapi.yml"}, 22 | {"testdata/recursiveRef/openapi.yml"}, 23 | {"testdata/spec.yaml"}, 24 | {"testdata/callbacks.yml"}, 25 | {"testdata/issue831/testref.internalizepath.openapi.yml"}, 26 | {"testdata/issue959/openapi.yml"}, 27 | {"testdata/interalizationNameCollision/api.yml"}, 28 | } 29 | 30 | for _, test := range tests { 31 | t.Run(test.filename, func(t *testing.T) { 32 | // Load in the reference spec from the testdata 33 | sl := NewLoader() 34 | sl.IsExternalRefsAllowed = true 35 | doc, err := sl.LoadFromFile(test.filename) 36 | require.NoError(t, err, "loading test file") 37 | err = doc.Validate(ctx) 38 | require.NoError(t, err, "validating spec") 39 | 40 | // Internalize the references 41 | doc.InternalizeRefs(ctx, nil) 42 | 43 | // Validate the internalized spec 44 | err = doc.Validate(ctx) 45 | require.NoError(t, err, "validating internalized spec") 46 | 47 | actual, err := doc.MarshalJSON() 48 | require.NoError(t, err, "marshaling internalized spec") 49 | 50 | // run a static check over the file, making sure each occurrence of a 51 | // reference is followed by a # 52 | numRefs := len(regexpRef.FindAll(actual, -1)) 53 | numInternalRefs := len(regexpRefInternal.FindAll(actual, -1)) 54 | require.Equal(t, numRefs, numInternalRefs, "checking all references are internal") 55 | 56 | // load from actual, but with the path set to the current directory 57 | doc2, err := sl.LoadFromData(actual) 58 | require.NoError(t, err, "reloading spec") 59 | err = doc2.Validate(ctx) 60 | require.NoError(t, err, "validating reloaded spec") 61 | 62 | // compare with expected 63 | expected, err := os.ReadFile(test.filename + ".internalized.yml") 64 | require.NoError(t, err) 65 | require.JSONEq(t, string(expected), string(actual)) 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /openapi3/issue136_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue136(t *testing.T) { 10 | specf := func(dflt string) string { 11 | return ` 12 | openapi: 3.0.2 13 | info: 14 | title: "Hello World REST APIs" 15 | version: "1.0" 16 | paths: {} 17 | components: 18 | schemas: 19 | SomeSchema: 20 | type: string 21 | default: ` + dflt + ` 22 | ` 23 | } 24 | 25 | for _, testcase := range []struct { 26 | dflt, err string 27 | }{ 28 | { 29 | dflt: `"foo"`, 30 | err: "", 31 | }, 32 | { 33 | dflt: `1`, 34 | err: "invalid components: invalid schema default: value must be a string", 35 | }, 36 | } { 37 | t.Run(testcase.dflt, func(t *testing.T) { 38 | spec := specf(testcase.dflt) 39 | 40 | sl := NewLoader() 41 | 42 | doc, err := sl.LoadFromData([]byte(spec)) 43 | require.NoError(t, err) 44 | 45 | err = doc.Validate(sl.Context) 46 | if testcase.err == "" { 47 | require.NoError(t, err) 48 | } else { 49 | require.Error(t, err, testcase.err) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /openapi3/issue241_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "github.com/oasdiff/yaml3" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/getkin/kin-openapi/openapi3" 12 | ) 13 | 14 | func TestIssue241(t *testing.T) { 15 | data, err := os.ReadFile("testdata/issue241.yml") 16 | require.NoError(t, err) 17 | 18 | loader := openapi3.NewLoader() 19 | loader.IsExternalRefsAllowed = true 20 | spec, err := loader.LoadFromData(data) 21 | require.NoError(t, err) 22 | 23 | var buf bytes.Buffer 24 | enc := yaml.NewEncoder(&buf) 25 | enc.SetIndent(2) 26 | err = enc.Encode(spec) 27 | require.NoError(t, err) 28 | require.Equal(t, string(data), buf.String()) 29 | } 30 | -------------------------------------------------------------------------------- /openapi3/issue301_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue301(t *testing.T) { 10 | sl := NewLoader() 11 | sl.IsExternalRefsAllowed = true 12 | 13 | doc, err := sl.LoadFromFile("testdata/callbacks.yml") 14 | require.NoError(t, err) 15 | 16 | err = doc.Validate(sl.Context) 17 | require.NoError(t, err) 18 | 19 | require.Equal(t, &Types{"object"}, doc. 20 | Paths.Value("/trans"). 21 | Post.Callbacks["transactionCallback"].Value. 22 | Value("http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}"). 23 | Post.RequestBody.Value. 24 | Content["application/json"].Schema.Value. 25 | Type) 26 | 27 | require.Equal(t, &Types{"boolean"}, doc. 28 | Paths.Value("/other"). 29 | Post.Callbacks["myEvent"].Value. 30 | Value("{$request.query.queryUrl}"). 31 | Post.RequestBody.Value. 32 | Content["application/json"].Schema.Value. 33 | Type) 34 | } 35 | -------------------------------------------------------------------------------- /openapi3/issue341_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestIssue341(t *testing.T) { 11 | sl := NewLoader() 12 | sl.IsExternalRefsAllowed = true 13 | doc, err := sl.LoadFromFile("testdata/main.yaml") 14 | require.NoError(t, err) 15 | 16 | err = doc.Validate(sl.Context) 17 | require.NoError(t, err) 18 | 19 | err = sl.ResolveRefsIn(doc, nil) 20 | require.NoError(t, err) 21 | 22 | bs, err := doc.MarshalJSON() 23 | require.NoError(t, err) 24 | require.JSONEq(t, `{ 25 | "info": { 26 | "title": "test file", 27 | "version": "n/a" 28 | }, 29 | "openapi": "3.0.0", 30 | "paths": { 31 | "/testpath": { 32 | "$ref": "testpath.yaml#/paths/~1testpath" 33 | } 34 | } 35 | }`, string(bs)) 36 | 37 | require.Equal(t, &Types{"string"}, doc. 38 | Paths.Value("/testpath"). 39 | Get. 40 | Responses.Value("200").Value. 41 | Content["application/json"]. 42 | Schema.Value. 43 | Type) 44 | 45 | doc.InternalizeRefs(context.Background(), nil) 46 | bs, err = doc.MarshalJSON() 47 | require.NoError(t, err) 48 | require.JSONEq(t, `{ 49 | "components": { 50 | "responses": { 51 | "testpath_testpath_200_response": { 52 | "content": { 53 | "application/json": { 54 | "schema": { 55 | "type": "string" 56 | } 57 | } 58 | }, 59 | "description": "a custom response" 60 | } 61 | } 62 | }, 63 | "info": { 64 | "title": "test file", 65 | "version": "n/a" 66 | }, 67 | "openapi": "3.0.0", 68 | "paths": { 69 | "/testpath": { 70 | "get": { 71 | "responses": { 72 | "200": { 73 | "$ref": "#/components/responses/testpath_testpath_200_response" 74 | } 75 | } 76 | } 77 | } 78 | } 79 | }`, string(bs)) 80 | } 81 | -------------------------------------------------------------------------------- /openapi3/issue344_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue344(t *testing.T) { 10 | sl := NewLoader() 11 | sl.IsExternalRefsAllowed = true 12 | 13 | doc, err := sl.LoadFromFile("testdata/spec.yaml") 14 | require.NoError(t, err) 15 | 16 | err = doc.Validate(sl.Context) 17 | require.NoError(t, err) 18 | 19 | require.Equal(t, &Types{"string"}, doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type) 20 | } 21 | -------------------------------------------------------------------------------- /openapi3/issue382_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestOverridingGlobalParametersValidation(t *testing.T) { 10 | loader := NewLoader() 11 | doc, err := loader.LoadFromFile("testdata/Test_param_override.yml") 12 | require.NoError(t, err) 13 | err = doc.Validate(loader.Context) 14 | require.NoError(t, err) 15 | } 16 | -------------------------------------------------------------------------------- /openapi3/issue499_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue499(t *testing.T) { 10 | loader := NewLoader() 11 | loader.IsExternalRefsAllowed = true 12 | _, err := loader.LoadFromFile("testdata/issue499/main.yml") 13 | require.NoError(t, err) 14 | } 15 | -------------------------------------------------------------------------------- /openapi3/issue542_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue542(t *testing.T) { 10 | spec := []byte(` 11 | openapi: '3.0.0' 12 | info: 13 | version: '1.0.0' 14 | title: Swagger Petstore 15 | license: 16 | name: MIT 17 | servers: 18 | - url: http://petstore.swagger.io/v1 19 | paths: {} 20 | components: 21 | schemas: 22 | Cat: 23 | anyOf: 24 | - $ref: '#/components/schemas/Kitten' 25 | - type: object 26 | Kitten: 27 | type: string 28 | `[1:]) 29 | 30 | sl := NewLoader() 31 | 32 | doc, err := sl.LoadFromData(spec) 33 | require.NoError(t, err) 34 | 35 | err = doc.Validate(sl.Context) 36 | require.NoError(t, err) 37 | } 38 | -------------------------------------------------------------------------------- /openapi3/issue570_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue570(t *testing.T) { 10 | loader := NewLoader() 11 | _, err := loader.LoadFromFile("testdata/issue570.json") 12 | require.NoError(t, err) 13 | } 14 | -------------------------------------------------------------------------------- /openapi3/issue594_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/getkin/kin-openapi/openapi3" 10 | ) 11 | 12 | func TestIssue594(t *testing.T) { 13 | uri, err := url.Parse("https://raw.githubusercontent.com/sendgrid/sendgrid-oai/c3aaa432b769faa47285166aca17c7ed2ea71787/oai_v3_stoplight.json") 14 | require.NoError(t, err) 15 | 16 | sl := openapi3.NewLoader() 17 | var doc *openapi3.T 18 | if false { 19 | doc, err = sl.LoadFromURI(uri) 20 | } else { 21 | doc, err = sl.LoadFromFile("testdata/oai_v3_stoplight.json") 22 | } 23 | require.NoError(t, err) 24 | 25 | doc.Info.Version = "1.2.3" 26 | doc.Paths.Value("/marketing/contacts/search/emails").Post = nil 27 | doc.Components.Schemas["full-segment"].Value.Example = nil 28 | 29 | err = doc.Validate(sl.Context) 30 | require.NoError(t, err) 31 | } 32 | -------------------------------------------------------------------------------- /openapi3/issue601_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue601(t *testing.T) { 10 | // Document is invalid: first validation error returned is because 11 | // schema: 12 | // example: {key: value} 13 | // is not how schema examples are defined (but how components' examples are defined. Components are maps.) 14 | // Correct code should be: 15 | // schema: {example: value} 16 | sl := NewLoader() 17 | doc, err := sl.LoadFromFile("testdata/lxkns.yaml") 18 | require.NoError(t, err) 19 | 20 | err = doc.Validate(sl.Context) 21 | require.ErrorContains(t, err, `invalid components: schema "DiscoveryResult": invalid example: Error at "/type": property "type" is missing`) 22 | require.ErrorContains(t, err, `| Error at "/nsid": property "nsid" is missing`) 23 | 24 | err = doc.Validate(sl.Context, DisableExamplesValidation()) 25 | require.NoError(t, err) 26 | 27 | // Now let's remove all the invalid parts 28 | for _, schema := range doc.Components.Schemas { 29 | schema.Value.Example = nil 30 | } 31 | 32 | err = doc.Validate(sl.Context) 33 | require.NoError(t, err) 34 | } 35 | -------------------------------------------------------------------------------- /openapi3/issue615_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/getkin/kin-openapi/openapi3" 9 | ) 10 | 11 | func TestIssue615(t *testing.T) { 12 | loader := openapi3.NewLoader() 13 | loader.IsExternalRefsAllowed = true 14 | doc, err := loader.LoadFromFile("testdata/recursiveRef/issue615.yml") 15 | require.NoError(t, err) 16 | 17 | err = doc.Validate(loader.Context) 18 | require.NoError(t, err) 19 | } 20 | -------------------------------------------------------------------------------- /openapi3/issue618_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue618(t *testing.T) { 10 | spec := ` 11 | openapi: 3.0.0 12 | info: 13 | title: foo 14 | version: 0.0.0 15 | paths: 16 | /foo: 17 | get: 18 | responses: 19 | '200': 20 | description: Some description value text 21 | content: 22 | application/json: 23 | schema: 24 | $ref: ./testdata/schema618.yml#/components/schemas/JournalEntry 25 | `[1:] 26 | 27 | loader := NewLoader() 28 | loader.IsExternalRefsAllowed = true 29 | ctx := loader.Context 30 | 31 | doc, err := loader.LoadFromData([]byte(spec)) 32 | require.NoError(t, err) 33 | 34 | doc.InternalizeRefs(ctx, nil) 35 | 36 | require.Contains(t, doc.Components.Schemas, "testdata_schema618_JournalEntry") 37 | require.Contains(t, doc.Components.Schemas, "testdata_schema618_Record") 38 | require.Contains(t, doc.Components.Schemas, "testdata_schema618_Account") 39 | } 40 | -------------------------------------------------------------------------------- /openapi3/issue638_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue638(t *testing.T) { 10 | for i := 0; i < 50; i++ { 11 | loader := NewLoader() 12 | loader.IsExternalRefsAllowed = true 13 | // This path affects the occurrence of the issue #638. 14 | // ../openapi3/testdata/issue638/test1.yaml : reproduce 15 | // ./testdata/issue638/test1.yaml : not reproduce 16 | // testdata/issue638/test1.yaml : reproduce 17 | doc, err := loader.LoadFromFile("testdata/issue638/test1.yaml") 18 | require.NoError(t, err) 19 | require.Equal(t, &Types{"int"}, doc.Components.Schemas["test1d"].Value.Type) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /openapi3/issue652_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/getkin/kin-openapi/openapi3" 10 | ) 11 | 12 | func TestIssue652(t *testing.T) { 13 | loader := openapi3.NewLoader() 14 | loader.IsExternalRefsAllowed = true 15 | 16 | // Test checks that no slice bounds out of range error occurs while loading 17 | // from file that contains reference to file in the parent directory. 18 | require.NotPanics(t, func() { 19 | const schemaName = "ReferenceToParentDirectory" 20 | 21 | spec, err := loader.LoadFromFile("testdata/issue652/nested/schema.yml") 22 | require.NoError(t, err) 23 | require.Contains(t, spec.Components.Schemas, schemaName) 24 | 25 | schema := spec.Components.Schemas[schemaName] 26 | assert.Equal(t, "../definitions.yml#/components/schemas/TestSchema", schema.Ref) 27 | assert.Equal(t, &openapi3.Types{"string"}, schema.Value.Type) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /openapi3/issue657_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/getkin/kin-openapi/openapi3" 9 | ) 10 | 11 | func TestOneOf_Warning_Errors(t *testing.T) { 12 | t.Parallel() 13 | 14 | loader := openapi3.NewLoader() 15 | spec := ` 16 | components: 17 | schemas: 18 | Something: 19 | type: object 20 | properties: 21 | field: 22 | title: Some field 23 | oneOf: 24 | - title: First rule 25 | type: string 26 | minLength: 10 27 | maxLength: 10 28 | - title: Second rule 29 | type: string 30 | minLength: 15 31 | maxLength: 15 32 | `[1:] 33 | 34 | doc, err := loader.LoadFromData([]byte(spec)) 35 | require.NoError(t, err) 36 | 37 | tests := [...]struct { 38 | name string 39 | value string 40 | checkErr require.ErrorAssertionFunc 41 | }{ 42 | { 43 | name: "valid value", 44 | value: "ABCDE01234", 45 | checkErr: require.NoError, 46 | }, 47 | { 48 | name: "valid value", 49 | value: "ABCDE0123456789", 50 | checkErr: require.NoError, 51 | }, 52 | { 53 | name: "no valid value", 54 | value: "ABCDE", 55 | checkErr: func(t require.TestingT, err error, i ...any) { 56 | require.ErrorContains(t, err, "doesn't match schema due to: minimum string length is 10") 57 | 58 | wErr := &openapi3.MultiError{} 59 | require.ErrorAs(t, err, wErr) 60 | 61 | require.Len(t, *wErr, 2) 62 | 63 | require.Equal(t, "minimum string length is 10", (*wErr)[0].(*openapi3.SchemaError).Reason) 64 | require.Equal(t, "minimum string length is 15", (*wErr)[1].(*openapi3.SchemaError).Reason) 65 | }, 66 | }, 67 | } 68 | 69 | for _, test := range tests { 70 | test := test 71 | t.Run(test.name, func(t *testing.T) { 72 | t.Parallel() 73 | 74 | err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(test.value) 75 | 76 | test.checkErr(t, err) 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /openapi3/issue697_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue697(t *testing.T) { 10 | loader := NewLoader() 11 | doc, err := loader.LoadFromFile("testdata/issue697.yml") 12 | require.NoError(t, err) 13 | err = doc.Validate(loader.Context) 14 | require.NoError(t, err) 15 | } 16 | -------------------------------------------------------------------------------- /openapi3/issue741_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "sync" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestIssue741(t *testing.T) { 14 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | w.WriteHeader(http.StatusOK) 16 | body := `{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Foo":{"type":"string"}}}}` 17 | _, err := w.Write([]byte(body)) 18 | if err != nil { 19 | panic(err) 20 | } 21 | })) 22 | defer ts.Close() 23 | 24 | rootSpec := []byte(fmt.Sprintf( 25 | `{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Bar1":{"$ref":"%s#/components/schemas/Foo"}}}}`, 26 | ts.URL, 27 | )) 28 | 29 | wg := &sync.WaitGroup{} 30 | n := 10 31 | for i := 0; i < n; i++ { 32 | wg.Add(1) 33 | go func() { 34 | defer wg.Done() 35 | loader := NewLoader() 36 | loader.IsExternalRefsAllowed = true 37 | doc, err := loader.LoadFromData(rootSpec) 38 | require.NoError(t, err) 39 | require.NotNil(t, doc) 40 | }() 41 | } 42 | wg.Wait() 43 | } 44 | -------------------------------------------------------------------------------- /openapi3/issue746_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestIssue746(t *testing.T) { 11 | schema := &Schema{} 12 | err := schema.UnmarshalJSON([]byte(`{"additionalProperties": false}`)) 13 | require.NoError(t, err) 14 | 15 | var value any 16 | err = json.Unmarshal([]byte(`{"foo": "bar"}`), &value) 17 | require.NoError(t, err) 18 | 19 | err = schema.VisitJSON(value) 20 | require.Error(t, err) 21 | 22 | schemaErr := &SchemaError{} 23 | require.ErrorAs(t, err, &schemaErr) 24 | require.Equal(t, "properties", schemaErr.SchemaField) 25 | require.Equal(t, `property "foo" is unsupported`, schemaErr.Reason) 26 | } 27 | -------------------------------------------------------------------------------- /openapi3/issue753_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue753(t *testing.T) { 10 | loader := NewLoader() 11 | 12 | doc, err := loader.LoadFromFile("testdata/issue753.yml") 13 | require.NoError(t, err) 14 | 15 | err = doc.Validate(loader.Context) 16 | require.NoError(t, err) 17 | 18 | require.NotNil(t, doc. 19 | Paths.Value("/test1"). 20 | Post.Callbacks["callback1"].Value. 21 | Value("{$request.body#/callback}"). 22 | Post.RequestBody.Value. 23 | Content["application/json"]. 24 | Schema.Value) 25 | require.NotNil(t, doc. 26 | Paths.Value("/test2"). 27 | Post.Callbacks["callback2"].Value. 28 | Value("{$request.body#/callback}"). 29 | Post.RequestBody.Value. 30 | Content["application/json"]. 31 | Schema.Value) 32 | } 33 | -------------------------------------------------------------------------------- /openapi3/issue759_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue759(t *testing.T) { 10 | spec := []byte(` 11 | openapi: 3.0.0 12 | info: 13 | title: title 14 | description: description 15 | version: 0.0.0 16 | paths: 17 | /slash: 18 | get: 19 | responses: 20 | "200": 21 | # Ref should point to a response, not a schema 22 | $ref: "#/components/schemas/UserStruct" 23 | components: 24 | schemas: 25 | UserStruct: 26 | type: object 27 | `[1:]) 28 | 29 | loader := NewLoader() 30 | 31 | doc, err := loader.LoadFromData(spec) 32 | require.Nil(t, doc) 33 | require.EqualError(t, err, `bad data in "#/components/schemas/UserStruct" (expecting ref to response object)`) 34 | } 35 | -------------------------------------------------------------------------------- /openapi3/issue794_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCrashOnLoad(t *testing.T) { 10 | loader := NewLoader() 11 | doc, err := loader.LoadFromFile("testdata/issue794.yml") 12 | require.NoError(t, err) 13 | err = doc.Validate(loader.Context) 14 | require.NoError(t, err) 15 | } 16 | -------------------------------------------------------------------------------- /openapi3/issue796_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue796(t *testing.T) { 10 | loader := NewLoader() 11 | doc, err := loader.LoadFromFile("testdata/issue796.yml") 12 | require.NoError(t, err) 13 | 14 | err = doc.Validate(loader.Context) 15 | require.NoError(t, err) 16 | } 17 | -------------------------------------------------------------------------------- /openapi3/issue819_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue819ResponsesGetPatternedFields(t *testing.T) { 10 | spec := ` 11 | openapi: "3.0.3" 12 | info: 13 | title: 'My app' 14 | version: 1.0.0 15 | description: 'An API' 16 | 17 | paths: 18 | /v1/operation: 19 | get: 20 | summary: Fetch something 21 | responses: 22 | 2XX: 23 | description: Success 24 | content: 25 | application/json: 26 | schema: 27 | type: object 28 | description: An error response body. 29 | `[1:] 30 | sl := NewLoader() 31 | doc, err := sl.LoadFromData([]byte(spec)) 32 | require.NoError(t, err) 33 | err = doc.Validate(sl.Context) 34 | require.NoError(t, err) 35 | 36 | require.NotNil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(201)) 37 | require.Nil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(404)) 38 | require.Nil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(999)) 39 | } 40 | -------------------------------------------------------------------------------- /openapi3/issue82_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestIssue82(t *testing.T) { 11 | payload := map[string]any{ 12 | "prop1": "val", 13 | "prop3": "val", 14 | } 15 | 16 | schemas := []string{` 17 | { 18 | "type": "object", 19 | "additionalProperties": false, 20 | "required": ["prop1"], 21 | "properties": { 22 | "prop1": { 23 | "type": "string" 24 | } 25 | } 26 | }`, `{ 27 | "anyOf": [ 28 | { 29 | "type": "object", 30 | "additionalProperties": false, 31 | "required": ["prop1"], 32 | "properties": { 33 | "prop1": { 34 | "type": "string" 35 | } 36 | } 37 | }, 38 | { 39 | "type": "object", 40 | "additionalProperties": false, 41 | "properties": { 42 | "prop2": { 43 | "type": "string" 44 | } 45 | } 46 | } 47 | ] 48 | }`, `{ 49 | "oneOf": [ 50 | { 51 | "type": "object", 52 | "additionalProperties": false, 53 | "required": ["prop1"], 54 | "properties": { 55 | "prop1": { 56 | "type": "string" 57 | } 58 | } 59 | }, 60 | { 61 | "type": "object", 62 | "additionalProperties": false, 63 | "properties": { 64 | "prop2": { 65 | "type": "string" 66 | } 67 | } 68 | } 69 | ] 70 | }`, `{ 71 | "allOf": [ 72 | { 73 | "type": "object", 74 | "additionalProperties": false, 75 | "required": ["prop1"], 76 | "properties": { 77 | "prop1": { 78 | "type": "string" 79 | } 80 | } 81 | }, 82 | { 83 | "type": "object", 84 | "additionalProperties": false, 85 | "properties": { 86 | "prop2": { 87 | "type": "string" 88 | } 89 | } 90 | } 91 | ] 92 | } 93 | `} 94 | 95 | for _, jsonSchema := range schemas { 96 | var dataSchema Schema 97 | err := json.Unmarshal([]byte(jsonSchema), &dataSchema) 98 | require.NoError(t, err) 99 | 100 | err = dataSchema.VisitJSON(payload) 101 | require.Error(t, err) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /openapi3/issue961_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue961(t *testing.T) { 10 | loader := NewLoader() 11 | loader.IsExternalRefsAllowed = true 12 | _, err := loader.LoadFromFile("./testdata/issue961/main.yml") 13 | require.NoError(t, err) 14 | } 15 | -------------------------------------------------------------------------------- /openapi3/issue972_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oasdiff/yaml3" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestIssue972(t *testing.T) { 11 | type testcase struct { 12 | spec string 13 | validationErrorContains string 14 | } 15 | 16 | base := ` 17 | openapi: 3.0.2 18 | paths: {} 19 | components: {} 20 | ` 21 | 22 | for _, tc := range []testcase{{ 23 | spec: base, 24 | validationErrorContains: "invalid info: must be an object", 25 | }, { 26 | spec: base + ` 27 | info: 28 | title: "Hello World REST APIs" 29 | version: "1.0" 30 | `, 31 | }, { 32 | spec: base + ` 33 | info: null 34 | `, 35 | validationErrorContains: "invalid info: must be an object", 36 | }, { 37 | spec: base + ` 38 | info: {} 39 | `, 40 | validationErrorContains: "invalid info: value of version must be a non-empty string", 41 | }, { 42 | spec: base + ` 43 | info: 44 | title: "Hello World REST APIs" 45 | `, 46 | validationErrorContains: "invalid info: value of version must be a non-empty string", 47 | }} { 48 | t.Logf("spec: %s", tc.spec) 49 | 50 | loader := &Loader{} 51 | doc, err := loader.LoadFromData([]byte(tc.spec)) 52 | assert.NoError(t, err) 53 | assert.NotNil(t, doc) 54 | 55 | err = doc.Validate(loader.Context) 56 | if e := tc.validationErrorContains; e != "" { 57 | assert.ErrorContains(t, err, e) 58 | } else { 59 | assert.NoError(t, err) 60 | } 61 | 62 | txt, err := yaml.Marshal(doc) 63 | assert.NoError(t, err) 64 | assert.NotEmpty(t, txt) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /openapi3/license.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | // License is specified by OpenAPI/Swagger standard version 3. 10 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object 11 | type License struct { 12 | Extensions map[string]any `json:"-" yaml:"-"` 13 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 14 | 15 | Name string `json:"name" yaml:"name"` // Required 16 | URL string `json:"url,omitempty" yaml:"url,omitempty"` 17 | } 18 | 19 | // MarshalJSON returns the JSON encoding of License. 20 | func (license License) MarshalJSON() ([]byte, error) { 21 | x, err := license.MarshalYAML() 22 | if err != nil { 23 | return nil, err 24 | } 25 | return json.Marshal(x) 26 | } 27 | 28 | // MarshalYAML returns the YAML encoding of License. 29 | func (license License) MarshalYAML() (any, error) { 30 | m := make(map[string]any, 2+len(license.Extensions)) 31 | for k, v := range license.Extensions { 32 | m[k] = v 33 | } 34 | m["name"] = license.Name 35 | if x := license.URL; x != "" { 36 | m["url"] = x 37 | } 38 | return m, nil 39 | } 40 | 41 | // UnmarshalJSON sets License to a copy of data. 42 | func (license *License) UnmarshalJSON(data []byte) error { 43 | type LicenseBis License 44 | var x LicenseBis 45 | if err := json.Unmarshal(data, &x); err != nil { 46 | return unmarshalError(err) 47 | } 48 | _ = json.Unmarshal(data, &x.Extensions) 49 | delete(x.Extensions, originKey) 50 | delete(x.Extensions, "name") 51 | delete(x.Extensions, "url") 52 | if len(x.Extensions) == 0 { 53 | x.Extensions = nil 54 | } 55 | *license = License(x) 56 | return nil 57 | } 58 | 59 | // Validate returns an error if License does not comply with the OpenAPI spec. 60 | func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error { 61 | ctx = WithValidationOptions(ctx, opts...) 62 | 63 | if license.Name == "" { 64 | return errors.New("value of license name must be a non-empty string") 65 | } 66 | 67 | return validateExtensions(ctx, license.Extensions) 68 | } 69 | -------------------------------------------------------------------------------- /openapi3/load_with_go_embed_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.16 2 | // +build go1.16 3 | 4 | package openapi3_test 5 | 6 | import ( 7 | "embed" 8 | "fmt" 9 | "net/url" 10 | 11 | "github.com/getkin/kin-openapi/openapi3" 12 | ) 13 | 14 | //go:embed testdata/recursiveRef/* 15 | var fs embed.FS 16 | 17 | func Example() { 18 | loader := openapi3.NewLoader() 19 | loader.IsExternalRefsAllowed = true 20 | loader.ReadFromURIFunc = func(loader *openapi3.Loader, uri *url.URL) ([]byte, error) { 21 | return fs.ReadFile(uri.Path) 22 | } 23 | 24 | doc, err := loader.LoadFromFile("testdata/recursiveRef/openapi.yml") 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | if err = doc.Validate(loader.Context); err != nil { 30 | panic(err) 31 | } 32 | 33 | fmt.Println(doc. 34 | Paths.Value("/foo"). 35 | Get.Responses.Value("200").Value. 36 | Content["application/json"]. 37 | Schema.Value. 38 | Properties["foo2"].Value. 39 | Properties["foo"].Value. 40 | Properties["bar"].Value. 41 | Type, 42 | ) 43 | // Output: &[string] 44 | } 45 | -------------------------------------------------------------------------------- /openapi3/loader_circular_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestLoadCircular(t *testing.T) { 11 | loader := NewLoader() 12 | loader.IsExternalRefsAllowed = true 13 | doc, err := loader.LoadFromFile("testdata/circularRef2/circular2.yaml") 14 | require.NoError(t, err) 15 | require.NotNil(t, doc) 16 | 17 | ref := "./AwsEnvironmentSettings.yaml" 18 | 19 | arr := NewArraySchema() 20 | obj := NewObjectSchema() 21 | arr.Items = &SchemaRef{ 22 | Ref: ref, 23 | Value: obj, 24 | } 25 | arr.Items.setRefPath(&url.URL{Path: "testdata/circularRef2/AwsEnvironmentSettings.yaml"}) 26 | obj.Description = "test" 27 | obj.Properties = map[string]*SchemaRef{ 28 | "children": { 29 | Value: arr, 30 | }, 31 | } 32 | 33 | expected := &SchemaRef{ 34 | Ref: ref, 35 | Value: obj, 36 | } 37 | 38 | actual := doc.Paths.Map()["/sample"].Put.RequestBody.Value.Content.Get("application/json").Schema 39 | 40 | require.Equal(t, expected.Value, actual.Value) 41 | } 42 | -------------------------------------------------------------------------------- /openapi3/loader_empty_response_description_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestJSONSpecResponseDescriptionEmptiness(t *testing.T) { 12 | const spec = ` 13 | { 14 | "info": { 15 | "description": "A sample API to illustrate OpenAPI concepts", 16 | "title": "Sample API", 17 | "version": "1.0.0" 18 | }, 19 | "openapi": "3.0.0", 20 | "paths": { 21 | "/path1": { 22 | "get": { 23 | "responses": { 24 | "200": { 25 | "description": "" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | ` 33 | 34 | { 35 | spec := []byte(spec) 36 | loader := NewLoader() 37 | doc, err := loader.LoadFromData(spec) 38 | require.NoError(t, err) 39 | require.Equal(t, "", *doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description) 40 | t.Log("Empty description provided: valid spec") 41 | err = doc.Validate(loader.Context) 42 | require.NoError(t, err) 43 | } 44 | 45 | { 46 | spec := []byte(strings.Replace(spec, `"description": ""`, `"description": "My response"`, 1)) 47 | loader := NewLoader() 48 | doc, err := loader.LoadFromData(spec) 49 | require.NoError(t, err) 50 | require.Equal(t, "My response", *doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description) 51 | t.Log("Non-empty description provided: valid spec") 52 | err = doc.Validate(loader.Context) 53 | require.NoError(t, err) 54 | } 55 | 56 | noDescriptionIsInvalid := func(data []byte) *T { 57 | loader := NewLoader() 58 | doc, err := loader.LoadFromData(data) 59 | require.NoError(t, err) 60 | require.Nil(t, doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description) 61 | t.Log("No description provided: invalid spec") 62 | err = doc.Validate(loader.Context) 63 | require.Error(t, err) 64 | return doc 65 | } 66 | 67 | var docWithNoResponseDescription *T 68 | { 69 | spec := []byte(strings.Replace(spec, `"description": ""`, ``, 1)) 70 | docWithNoResponseDescription = noDescriptionIsInvalid(spec) 71 | } 72 | 73 | str, err := json.Marshal(docWithNoResponseDescription) 74 | require.NoError(t, err) 75 | require.NotEmpty(t, str) 76 | t.Log("Reserialization does not set description") 77 | _ = noDescriptionIsInvalid(str) 78 | } 79 | -------------------------------------------------------------------------------- /openapi3/loader_issue220_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestIssue220(t *testing.T) { 11 | for _, specPath := range []string{ 12 | "testdata/my-openapi.json", 13 | filepath.FromSlash("testdata/my-openapi.json"), 14 | } { 15 | t.Logf("specPath: %q", specPath) 16 | 17 | loader := NewLoader() 18 | loader.IsExternalRefsAllowed = true 19 | doc, err := loader.LoadFromFile(specPath) 20 | require.NoError(t, err) 21 | 22 | err = doc.Validate(loader.Context) 23 | require.NoError(t, err) 24 | 25 | require.Equal(t, &Types{"integer"}, doc. 26 | Paths.Value("/foo"). 27 | Get.Responses.Value("200").Value. 28 | Content["application/json"]. 29 | Schema.Value.Properties["bar"].Value. 30 | Type) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /openapi3/loader_issue235_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue235OK(t *testing.T) { 10 | loader := NewLoader() 11 | loader.IsExternalRefsAllowed = true 12 | doc, err := loader.LoadFromFile("testdata/issue235.spec0.yml") 13 | require.NoError(t, err) 14 | err = doc.Validate(loader.Context) 15 | require.NoError(t, err) 16 | } 17 | 18 | func TestIssue235CircularDep(t *testing.T) { 19 | t.Skip("TODO: return an error on circular dependencies between external files of a spec") 20 | loader := NewLoader() 21 | loader.IsExternalRefsAllowed = true 22 | doc, err := loader.LoadFromFile("testdata/issue235.spec0-typo.yml") 23 | require.Nil(t, doc) 24 | require.Error(t, err) 25 | } 26 | -------------------------------------------------------------------------------- /openapi3/loader_outside_refs_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLoadOutsideRefs(t *testing.T) { 10 | loader := NewLoader() 11 | loader.IsExternalRefsAllowed = true 12 | doc, err := loader.LoadFromFile("testdata/303bis/service.yaml") 13 | require.NoError(t, err) 14 | require.NotNil(t, doc) 15 | 16 | err = doc.Validate(loader.Context) 17 | require.NoError(t, err) 18 | 19 | require.Equal(t, &Types{"string"}, doc. 20 | Paths.Value("/service"). 21 | Get. 22 | Responses.Value("200").Value. 23 | Content["application/json"]. 24 | Schema.Value. 25 | Items.Value. 26 | AllOf[0].Value. 27 | Properties["created_at"].Value. 28 | Type) 29 | } 30 | 31 | func TestIssue423(t *testing.T) { 32 | spec := ` 33 | info: 34 | description: test 35 | title: test 36 | version: 0.0.0 37 | openapi: 3.0.1 38 | paths: 39 | /api/bundles: 40 | get: 41 | responses: 42 | "200": 43 | description: OK 44 | content: 45 | application/json: 46 | schema: 47 | $ref: '#/components/schemas/Data' 48 | components: 49 | schemas: 50 | Data: 51 | description: rbac Data 52 | properties: 53 | roles: 54 | $ref: https://raw.githubusercontent.com/kubernetes/kubernetes/132f29769dfecfc808adc58f756be43171054094/api/openapi-spec/swagger.json#/definitions/io.k8s.api.rbac.v1.RoleList 55 | ` 56 | 57 | loader := NewLoader() 58 | loader.IsExternalRefsAllowed = true 59 | loader.LoadFromData([]byte(spec)) 60 | } 61 | -------------------------------------------------------------------------------- /openapi3/loader_paths_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestPathsMustStartWithSlash(t *testing.T) { 11 | spec := ` 12 | openapi: "3.0" 13 | info: 14 | version: "1.0" 15 | title: sample 16 | paths: 17 | PATH: 18 | get: 19 | responses: 20 | 200: 21 | description: description 22 | ` 23 | 24 | for path, expectedErr := range map[string]string{ 25 | "foo/bar": "invalid paths: path \"foo/bar\" does not start with a forward slash (/)", 26 | "/foo/bar": "", 27 | } { 28 | loader := NewLoader() 29 | doc, err := loader.LoadFromData([]byte(strings.Replace(spec, "PATH", path, 1))) 30 | require.NoError(t, err) 31 | err = doc.Validate(loader.Context) 32 | if expectedErr != "" { 33 | require.EqualError(t, err, expectedErr) 34 | } else { 35 | require.NoError(t, err) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /openapi3/loader_recursive_ref_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLoaderSupportsRecursiveReference(t *testing.T) { 10 | loader := NewLoader() 11 | loader.IsExternalRefsAllowed = true 12 | doc, err := loader.LoadFromFile("testdata/recursiveRef/openapi.yml") 13 | require.NoError(t, err) 14 | err = doc.Validate(loader.Context) 15 | require.NoError(t, err) 16 | 17 | require.Equal(t, "bar", doc. 18 | Paths.Value("/foo"). 19 | Get.Responses.Status(200).Value. 20 | Content.Get("application/json"). 21 | Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Example) 22 | 23 | require.Equal(t, "ErrorDetails", doc. 24 | Paths.Value("/foo"). 25 | Get.Responses.Status(400).Value. 26 | Content.Get("application/json"). 27 | Schema.Value.Title) 28 | 29 | require.Equal(t, "ErrorDetails", doc. 30 | Paths.Value("/double-ref-foo"). 31 | Get.Responses.Status(400).Value. 32 | Content.Get("application/json"). 33 | Schema.Value.Title) 34 | } 35 | 36 | func TestIssue447(t *testing.T) { 37 | loader := NewLoader() 38 | doc, err := loader.LoadFromData([]byte(` 39 | openapi: 3.0.1 40 | info: 41 | title: Recursive refs example 42 | version: "1.0" 43 | paths: {} 44 | components: 45 | schemas: 46 | Complex: 47 | type: object 48 | properties: 49 | parent: 50 | $ref: '#/components/schemas/Complex' 51 | `)) 52 | require.NoError(t, err) 53 | err = doc.Validate(loader.Context) 54 | require.NoError(t, err) 55 | require.Equal(t, &Types{"object"}, doc.Components. 56 | Schemas["Complex"]. 57 | Value.Properties["parent"]. 58 | Value.Properties["parent"]. 59 | Value.Properties["parent"]. 60 | Value.Type) 61 | } 62 | -------------------------------------------------------------------------------- /openapi3/mapping_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestMapping(t *testing.T) { 10 | 11 | schema := ` 12 | openapi: 3.0.0 13 | info: 14 | title: ACME 15 | version: 1.0.0 16 | components: 17 | schemas: 18 | Pet: 19 | type: object 20 | required: 21 | - petType 22 | properties: 23 | petType: 24 | type: string 25 | discriminator: 26 | propertyName: petType 27 | mapping: 28 | dog: Dog 29 | Cat: 30 | allOf: 31 | - $ref: '#/components/schemas/Pet' 32 | - type: object 33 | # all other properties specific to a Cat 34 | properties: 35 | name: 36 | type: string 37 | Dog: 38 | allOf: 39 | - $ref: '#/components/schemas/Pet' 40 | - type: object 41 | # all other properties specific to a Dog 42 | properties: 43 | bark: 44 | type: string 45 | Lizard: 46 | allOf: 47 | - $ref: '#/components/schemas/Pet' 48 | - type: object 49 | # all other properties specific to a Lizard 50 | properties: 51 | lovesRocks: 52 | type: boolean 53 | ` 54 | loader := NewLoader() 55 | loader.IsExternalRefsAllowed = true 56 | _, err := loader.LoadFromData([]byte(schema)) 57 | require.NoError(t, err) 58 | } 59 | -------------------------------------------------------------------------------- /openapi3/marsh.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/oasdiff/yaml" 9 | ) 10 | 11 | func unmarshalError(jsonUnmarshalErr error) error { 12 | if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" { 13 | before = strings.ReplaceAll(before, " Go struct ", " ") 14 | return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", "")) 15 | } 16 | return jsonUnmarshalErr 17 | } 18 | 19 | func unmarshal(data []byte, v any, includeOrigin bool) error { 20 | var jsonErr, yamlErr error 21 | 22 | // See https://github.com/getkin/kin-openapi/issues/680 23 | if jsonErr = json.Unmarshal(data, v); jsonErr == nil { 24 | return nil 25 | } 26 | 27 | // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys 28 | if yamlErr = yaml.UnmarshalWithOrigin(data, v, includeOrigin); yamlErr == nil { 29 | return nil 30 | } 31 | 32 | // If both unmarshaling attempts fail, return a new error that includes both errors 33 | return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr) 34 | } 35 | -------------------------------------------------------------------------------- /openapi3/marsh_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestUnmarshalError(t *testing.T) { 10 | { 11 | spec := []byte(` 12 | openapi: 3.0.1 13 | info: 14 | version: v1 15 | title: Products api 16 | components: 17 | schemas: 18 | someSchema: 19 | type: object 20 | schemaArray: 21 | type: array 22 | minItems: 1 23 | items: 24 | $ref: '#/components/schemas/someSchema' 25 | paths: 26 | /categories: 27 | get: 28 | responses: 29 | '200': 30 | description: '' 31 | content: 32 | application/json: 33 | schema: 34 | allOf: 35 | $ref: '#/components/schemas/schemaArray' # <- Should have been a list 36 | `[1:]) 37 | 38 | sl := NewLoader() 39 | 40 | _, err := sl.LoadFromData(spec) 41 | require.ErrorContains(t, err, `json: cannot unmarshal object into field Schema.allOf of type openapi3.SchemaRefs`) 42 | } 43 | 44 | spec := []byte(` 45 | openapi: 3.0.1 46 | info: 47 | version: v1 48 | title: Products api 49 | components: 50 | schemas: 51 | someSchema: 52 | type: object 53 | schemaArray: 54 | type: array 55 | minItems: 1 56 | items: 57 | $ref: '#/components/schemas/someSchema' 58 | paths: 59 | /categories: 60 | get: 61 | responses: 62 | '200': 63 | description: '' 64 | content: 65 | application/json: 66 | schema: 67 | allOf: 68 | - $ref: '#/components/schemas/schemaArray' # <- 69 | `[1:]) 70 | 71 | sl := NewLoader() 72 | 73 | doc, err := sl.LoadFromData(spec) 74 | require.NoError(t, err) 75 | 76 | err = doc.Validate(sl.Context) 77 | require.NoError(t, err) 78 | } 79 | -------------------------------------------------------------------------------- /openapi3/media_type_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestMediaTypeJSON(t *testing.T) { 12 | t.Log("Marshal *openapi3.MediaType to JSON") 13 | data, err := json.Marshal(mediaType()) 14 | require.NoError(t, err) 15 | require.NotEmpty(t, data) 16 | 17 | t.Log("Unmarshal *openapi3.MediaType from JSON") 18 | docA := &MediaType{} 19 | err = json.Unmarshal(mediaTypeJSON, &docA) 20 | require.NoError(t, err) 21 | require.NotEmpty(t, data) 22 | 23 | t.Log("Validate *openapi3.MediaType") 24 | err = docA.Validate(context.Background()) 25 | require.NoError(t, err) 26 | 27 | t.Log("Ensure representations match") 28 | dataA, err := json.Marshal(docA) 29 | require.NoError(t, err) 30 | require.JSONEq(t, string(data), string(mediaTypeJSON)) 31 | require.JSONEq(t, string(data), string(dataA)) 32 | } 33 | 34 | var mediaTypeJSON = []byte(` 35 | { 36 | "schema": { 37 | "description": "Some schema" 38 | }, 39 | "encoding": { 40 | "someEncoding": { 41 | "contentType": "application/xml; charset=utf-8" 42 | } 43 | }, 44 | "examples": { 45 | "someExample": { 46 | "value": { 47 | "name": "Some example" 48 | } 49 | } 50 | } 51 | } 52 | `) 53 | 54 | func mediaType() *MediaType { 55 | example := map[string]string{"name": "Some example"} 56 | return &MediaType{ 57 | Schema: &SchemaRef{ 58 | Value: &Schema{ 59 | Description: "Some schema", 60 | }, 61 | }, 62 | Encoding: map[string]*Encoding{ 63 | "someEncoding": { 64 | ContentType: "application/xml; charset=utf-8", 65 | }, 66 | }, 67 | Examples: map[string]*ExampleRef{ 68 | "someExample": { 69 | Value: NewExample(example), 70 | }, 71 | }, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /openapi3/operation_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func initOperation() *Operation { 12 | operation := NewOperation() 13 | operation.Description = "Some description" 14 | operation.Summary = "Some summary" 15 | operation.Tags = []string{"tag1", "tag2"} 16 | return operation 17 | } 18 | 19 | func TestAddParameter(t *testing.T) { 20 | operation := initOperation() 21 | operation.AddParameter(NewQueryParameter("param1")) 22 | operation.AddParameter(NewCookieParameter("param2")) 23 | require.Equal(t, "param1", operation.Parameters.GetByInAndName("query", "param1").Name) 24 | require.Equal(t, "param2", operation.Parameters.GetByInAndName("cookie", "param2").Name) 25 | } 26 | 27 | func TestAddResponse(t *testing.T) { 28 | operation := initOperation() 29 | operation.AddResponse(200, NewResponse()) 30 | operation.AddResponse(400, NewResponse()) 31 | require.NotNil(t, "status 200", operation.Responses.Status(200).Value) 32 | require.NotNil(t, "status 400", operation.Responses.Status(400).Value) 33 | } 34 | 35 | func operationWithoutResponses() *Operation { 36 | operation := initOperation() 37 | return operation 38 | } 39 | 40 | func operationWithResponses() *Operation { 41 | operation := initOperation() 42 | operation.AddResponse(200, NewResponse().WithDescription("some response")) 43 | return operation 44 | } 45 | 46 | func TestOperationValidation(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | input *Operation 50 | expectedError error 51 | }{ 52 | { 53 | "when no Responses object is provided", 54 | operationWithoutResponses(), 55 | errors.New("value of responses must be an object"), 56 | }, 57 | { 58 | "when a Responses object is provided", 59 | operationWithResponses(), 60 | nil, 61 | }, 62 | } 63 | 64 | for _, test := range tests { 65 | t.Run(test.name, func(t *testing.T) { 66 | c := context.Background() 67 | validationErr := test.input.Validate(c) 68 | 69 | require.Equal(t, test.expectedError, validationErr, "expected errors (or lack of) to match") 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /openapi3/origin.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | const originKey = "__origin__" 4 | 5 | // Origin contains the origin of a collection. 6 | // Key is the location of the collection itself. 7 | // Fields is a map of the location of each field in the collection. 8 | type Origin struct { 9 | Key *Location `json:"key,omitempty" yaml:"key,omitempty"` 10 | Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"` 11 | } 12 | 13 | // Location is a struct that contains the location of a field. 14 | type Location struct { 15 | Line int `json:"line,omitempty" yaml:"line,omitempty"` 16 | Column int `json:"column,omitempty" yaml:"column,omitempty"` 17 | } 18 | -------------------------------------------------------------------------------- /openapi3/paths_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestPathsValidate(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | spec string 14 | wantErr string 15 | }{ 16 | { 17 | name: "ok, empty paths", 18 | spec: ` 19 | openapi: "3.0.0" 20 | info: 21 | version: 1.0.0 22 | title: Swagger Petstore 23 | license: 24 | name: MIT 25 | paths: 26 | /pets: 27 | `, 28 | }, 29 | { 30 | name: "operation ids are not unique, same path", 31 | spec: ` 32 | openapi: "3.0.0" 33 | info: 34 | version: 1.0.0 35 | title: Swagger Petstore 36 | license: 37 | name: MIT 38 | paths: 39 | /pets: 40 | post: 41 | operationId: createPet 42 | responses: 43 | 201: 44 | description: "entity created" 45 | delete: 46 | operationId: createPet 47 | responses: 48 | 204: 49 | description: "entity deleted" 50 | `, 51 | wantErr: `operations "DELETE /pets" and "POST /pets" have the same operation id "createPet"`, 52 | }, 53 | { 54 | name: "operation ids are not unique, different paths", 55 | spec: ` 56 | openapi: "3.0.0" 57 | info: 58 | version: 1.0.0 59 | title: Swagger Petstore 60 | license: 61 | name: MIT 62 | paths: 63 | /pets: 64 | post: 65 | operationId: createPet 66 | responses: 67 | 201: 68 | description: "entity created" 69 | /users: 70 | post: 71 | operationId: createPet 72 | responses: 73 | 201: 74 | description: "entity created" 75 | `, 76 | wantErr: `operations "POST /pets" and "POST /users" have the same operation id "createPet"`, 77 | }, 78 | } 79 | 80 | for i := range tests { 81 | tt := tests[i] 82 | t.Run(tt.name, func(t *testing.T) { 83 | loader := NewLoader() 84 | 85 | doc, err := loader.LoadFromData([]byte(tt.spec[1:])) 86 | require.NoError(t, err) 87 | 88 | err = doc.Paths.Validate(context.Background()) 89 | if tt.wantErr == "" { 90 | require.NoError(t, err) 91 | return 92 | } 93 | require.EqualError(t, err, tt.wantErr) 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /openapi3/race_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/getkin/kin-openapi/openapi3" 10 | ) 11 | 12 | func TestRaceyPatternSchemaValidateHindersIt(t *testing.T) { 13 | schema := openapi3.NewStringSchema().WithPattern("^test|for|race|condition$") 14 | 15 | err := schema.Validate(context.Background()) 16 | require.NoError(t, err) 17 | 18 | visit := func() { 19 | err := schema.VisitJSONString("test") 20 | require.NoError(t, err) 21 | } 22 | 23 | go visit() 24 | visit() 25 | } 26 | 27 | func TestRaceyPatternSchemaForIssue775(t *testing.T) { 28 | schema := openapi3.NewStringSchema().WithPattern("^test|for|race|condition$") 29 | 30 | // err := schema.Validate(context.Background()) 31 | // require.NoError(t, err) 32 | 33 | visit := func() { 34 | err := schema.VisitJSONString("test") 35 | require.NoError(t, err) 36 | } 37 | 38 | go visit() 39 | visit() 40 | } 41 | -------------------------------------------------------------------------------- /openapi3/ref.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | //go:generate go run refsgenerator.go 4 | 5 | // Ref is specified by OpenAPI/Swagger 3.0 standard. 6 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object 7 | type Ref struct { 8 | Ref string `json:"$ref" yaml:"$ref"` 9 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /openapi3/refs_test.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by go generate; DO NOT EDIT. 2 | package {{ .Package }} 3 | 4 | import ( 5 | "context" 6 | "encoding/json" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | {{ range $type := .Types }} 13 | func Test{{ $type.Name }}Ref_Extensions(t *testing.T) { 14 | data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) 15 | 16 | ref := {{ $type.Name }}Ref{} 17 | err := json.Unmarshal(data, &ref) 18 | assert.NoError(t, err) 19 | 20 | // captures extension 21 | assert.Equal(t, "#/components/schemas/Pet", ref.Ref) 22 | assert.Equal(t, float64(1), ref.Extensions["x-order"]) 23 | 24 | // does not capture non-extensions 25 | assert.Nil(t, ref.Extensions["something"]) 26 | 27 | // validation 28 | err = ref.Validate(context.Background()) 29 | require.EqualError(t, err, "extra sibling fields: [something]") 30 | 31 | err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) 32 | require.EqualError(t, err, "extra sibling fields: [something x-order]") 33 | 34 | err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) 35 | assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined 36 | 37 | // non-extension not json lookable 38 | _, err = ref.JSONLookup("something") 39 | assert.Error(t, err) 40 | {{ if ne $type.Name "Header" }} 41 | t.Run("extentions in value", func(t *testing.T) { 42 | ref.Value = &{{ $type.Name }}{Extensions: map[string]any{}} 43 | ref.Value.Extensions["x-order"] = 2.0 44 | 45 | // prefers the value next to the \$ref over the one in the \$ref. 46 | v, err := ref.JSONLookup("x-order") 47 | assert.NoError(t, err) 48 | assert.Equal(t, float64(1), v) 49 | }) 50 | {{ else }} 51 | // Header does not have its own extensions. 52 | {{ end -}} 53 | } 54 | {{ end -}} 55 | -------------------------------------------------------------------------------- /openapi3/refsgenerator.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | // The program generates refs.go, invoke `go generate ./...` to run. 5 | package main 6 | 7 | import ( 8 | _ "embed" 9 | "os" 10 | "text/template" 11 | ) 12 | 13 | //go:embed refs.tmpl 14 | var tmplData string 15 | 16 | //go:embed refs_test.tmpl 17 | var tmplTestData string 18 | 19 | func main() { 20 | generateTemplate("refs", tmplData) 21 | generateTemplate("refs_test", tmplTestData) 22 | } 23 | 24 | func generateTemplate(filename string, tmpl string) { 25 | file, err := os.Create(filename + ".go") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | defer func() { 31 | if err := file.Close(); err != nil { 32 | panic(err) 33 | } 34 | }() 35 | 36 | packageTemplate := template.Must(template.New("openapi3-" + filename).Parse(tmpl)) 37 | 38 | type componentType struct { 39 | Name string 40 | CollectionName string 41 | } 42 | 43 | if err := packageTemplate.Execute(file, struct { 44 | Package string 45 | Types []componentType 46 | }{ 47 | Package: os.Getenv("GOPACKAGE"), // set by the go:generate directive 48 | Types: []componentType{ 49 | {Name: "Callback", CollectionName: "callbacks"}, 50 | {Name: "Example", CollectionName: "examples"}, 51 | {Name: "Header", CollectionName: "headers"}, 52 | {Name: "Link", CollectionName: "links"}, 53 | {Name: "Parameter", CollectionName: "parameters"}, 54 | {Name: "RequestBody", CollectionName: "requestBodies"}, 55 | {Name: "Response", CollectionName: "responses"}, 56 | {Name: "Schema", CollectionName: "schemas"}, 57 | {Name: "SecurityScheme", CollectionName: "securitySchemes"}, 58 | }, 59 | }); err != nil { 60 | panic(err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /openapi3/schema_issue289_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue289(t *testing.T) { 10 | spec := []byte(` 11 | components: 12 | schemas: 13 | Server: 14 | properties: 15 | address: 16 | oneOf: 17 | - $ref: "#/components/schemas/ip-address" 18 | - $ref: "#/components/schemas/domain-name" 19 | name: 20 | type: string 21 | type: object 22 | domain-name: 23 | maxLength: 10 24 | minLength: 5 25 | pattern: "((([a-zA-Z0-9_]([a-zA-Z0-9\\-_]){0,61})?[a-zA-Z0-9]\\.)*([a-zA-Z0-9_]([a-zA-Z0-9\\-_]){0,61})?[a-zA-Z0-9]\\.?)|\\." 26 | type: string 27 | ip-address: 28 | pattern: "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$" 29 | type: string 30 | openapi: "3.0.1" 31 | info: 32 | version: 1.0.0 33 | title: title 34 | paths: {} 35 | `[1:]) 36 | 37 | loader := NewLoader() 38 | doc, err := loader.LoadFromData(spec) 39 | require.NoError(t, err) 40 | 41 | err = doc.Validate(loader.Context) 42 | require.NoError(t, err) 43 | 44 | err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{ 45 | "name": "kin-openapi", 46 | "address": "127.0.0.1", 47 | }) 48 | require.ErrorIs(t, err, ErrOneOfConflict) 49 | } 50 | -------------------------------------------------------------------------------- /openapi3/schema_issue492_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIssue492(t *testing.T) { 10 | spec := []byte(` 11 | components: 12 | schemas: 13 | Server: 14 | properties: 15 | time: 16 | $ref: "#/components/schemas/timestamp" 17 | name: 18 | type: string 19 | type: object 20 | timestamp: 21 | type: string 22 | format: date-time 23 | openapi: "3.0.1" 24 | paths: {} 25 | info: 26 | version: 1.1.1 27 | title: title 28 | `[1:]) 29 | 30 | loader := NewLoader() 31 | doc, err := loader.LoadFromData(spec) 32 | require.NoError(t, err) 33 | 34 | err = doc.Validate(loader.Context) 35 | require.NoError(t, err) 36 | 37 | // verify that the expected format works 38 | err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{ 39 | "name": "kin-openapi", 40 | "time": "2001-02-03T04:05:06.789Z", 41 | }) 42 | require.NoError(t, err) 43 | 44 | // verify that the issue is fixed 45 | err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]any{ 46 | "name": "kin-openapi", 47 | "time": "2001-02-03T04:05:06:789Z", 48 | }) 49 | require.EqualError(t, err, `Error at "/time": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) 50 | } 51 | -------------------------------------------------------------------------------- /openapi3/schema_issue940_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestOneOfErrorPreserved(t *testing.T) { 13 | 14 | SchemaErrorDetailsDisabled = true 15 | defer func() { SchemaErrorDetailsDisabled = false }() 16 | 17 | // language=json 18 | raw := ` 19 | { 20 | "foo": [ "bar" ] 21 | } 22 | ` 23 | 24 | // language=json 25 | schema := ` 26 | { 27 | "type": "object", 28 | "properties": { 29 | "foo": { 30 | "oneOf": [ 31 | { 32 | "type": "number" 33 | }, 34 | { 35 | "type": "string" 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | ` 42 | 43 | s := NewSchema() 44 | err := s.UnmarshalJSON([]byte(schema)) 45 | require.NoError(t, err) 46 | err = s.Validate(context.Background()) 47 | require.NoError(t, err) 48 | 49 | obj := make(map[string]any) 50 | err = json.Unmarshal([]byte(raw), &obj) 51 | require.NoError(t, err) 52 | 53 | err = s.VisitJSON(obj, MultiErrors()) 54 | require.Error(t, err) 55 | 56 | var multiError MultiError 57 | ok := errors.As(err, &multiError) 58 | require.True(t, ok) 59 | var schemaErr *SchemaError 60 | ok = errors.As(multiError[0], &schemaErr) 61 | require.True(t, ok) 62 | 63 | require.Equal(t, "oneOf", schemaErr.SchemaField) 64 | require.Equal(t, `value doesn't match any schema from "oneOf"`, schemaErr.Reason) 65 | require.Equal(t, `Error at "/foo": doesn't match schema due to: value must be a number Or value must be a string`, schemaErr.Error()) 66 | 67 | var me multiErrorForOneOf 68 | ok = errors.As(err, &me) 69 | require.True(t, ok) 70 | require.Equal(t, `Error at "/foo": value must be a number`, me[0].Error()) 71 | require.Equal(t, `Error at "/foo": value must be a string`, me[1].Error()) 72 | } 73 | -------------------------------------------------------------------------------- /openapi3/schema_pattern.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var patRewriteCodepoints = regexp.MustCompile(`(?P\\u)(?P[0-9A-F]{4})`) 9 | 10 | // See https://pkg.go.dev/regexp/syntax 11 | func intoGoRegexp(re string) string { 12 | return patRewriteCodepoints.ReplaceAllString(re, `\x{${code}}`) 13 | } 14 | 15 | // NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns] 16 | func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err error) { 17 | pattern := schema.Pattern 18 | if c != nil { 19 | cp, err = c(pattern) 20 | } else { 21 | cp, err = regexp.Compile(intoGoRegexp(pattern)) 22 | } 23 | if err != nil { 24 | err = &SchemaError{ 25 | Schema: schema, 26 | SchemaField: "pattern", 27 | Origin: err, 28 | Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), 29 | } 30 | return 31 | } 32 | 33 | var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp) 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /openapi3/schema_pattern_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestPattern(t *testing.T) { 11 | _, err := regexp.Compile("^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\\(\\`\\.\\\"\\']+$") 12 | require.EqualError(t, err, "error parsing regexp: invalid escape sequence: `\\u`") 13 | 14 | _, err = regexp.Compile(`^[a-zA-Z\x{0080}-\x{024F}]+$`) 15 | require.NoError(t, err) 16 | 17 | require.Equal(t, `^[a-zA-Z\x{0080}-\x{024F}]+$`, intoGoRegexp(`^[a-zA-Z\u0080-\u024F]+$`)) 18 | require.Equal(t, `^[6789a-zA-Z\x{0080}-\x{024F}]+$`, intoGoRegexp(`^[6789a-zA-Z\u0080-\u024F]+$`)) 19 | } 20 | -------------------------------------------------------------------------------- /openapi3/schema_validation_settings_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/getkin/kin-openapi/openapi3" 7 | ) 8 | 9 | func ExampleSetSchemaErrorMessageCustomizer() { 10 | loader := openapi3.NewLoader() 11 | spc := ` 12 | components: 13 | schemas: 14 | Something: 15 | type: object 16 | properties: 17 | field: 18 | title: Some field 19 | type: string 20 | `[1:] 21 | 22 | doc, err := loader.LoadFromData([]byte(spc)) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | opt := openapi3.SetSchemaErrorMessageCustomizer(func(err *openapi3.SchemaError) string { 28 | return fmt.Sprintf(`field "%s" should be string`, err.Schema.Title) 29 | }) 30 | 31 | err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(123, opt) 32 | 33 | fmt.Println(err.Error()) 34 | 35 | // Output: field "Some field" should be string 36 | } 37 | -------------------------------------------------------------------------------- /openapi3/security_requirements.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type SecurityRequirements []SecurityRequirement 8 | 9 | func NewSecurityRequirements() *SecurityRequirements { 10 | return &SecurityRequirements{} 11 | } 12 | 13 | func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements { 14 | *srs = append(*srs, securityRequirement) 15 | return srs 16 | } 17 | 18 | // Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec. 19 | func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error { 20 | ctx = WithValidationOptions(ctx, opts...) 21 | 22 | for _, security := range srs { 23 | if err := security.Validate(ctx); err != nil { 24 | return err 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | // SecurityRequirement is specified by OpenAPI/Swagger standard version 3. 31 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object 32 | type SecurityRequirement map[string][]string 33 | 34 | func NewSecurityRequirement() SecurityRequirement { 35 | return make(SecurityRequirement) 36 | } 37 | 38 | func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement { 39 | if len(scopes) == 0 { 40 | scopes = []string{} // Forces the variable to be encoded as an array instead of null 41 | } 42 | security[provider] = scopes 43 | return security 44 | } 45 | 46 | // Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec. 47 | func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error { 48 | ctx = WithValidationOptions(ctx, opts...) 49 | 50 | return nil 51 | } 52 | 53 | // UnmarshalJSON sets SecurityRequirement to a copy of data. 54 | func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) { 55 | *security, _, err = unmarshalStringMap[[]string](data) 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /openapi3/security_requirements_test.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestSecurityRequirementsEncoding(t *testing.T) { 11 | 12 | tests := []struct { 13 | requirements *SecurityRequirements 14 | json string 15 | }{ 16 | { 17 | requirements: NewSecurityRequirements(), 18 | json: `[]`, 19 | }, 20 | { 21 | requirements: NewSecurityRequirements().With(NewSecurityRequirement()), 22 | json: `[{}]`, 23 | }, 24 | } 25 | 26 | for _, test := range tests { 27 | 28 | b, err := json.Marshal(test.requirements) 29 | require.NoError(t, err) 30 | require.Equal(t, test.json, string(b), "incorrect requirements encoding") 31 | } 32 | } 33 | 34 | func TestSecurityRequirementEncoding(t *testing.T) { 35 | 36 | tests := []struct { 37 | requirement SecurityRequirement 38 | json string 39 | }{ 40 | { 41 | requirement: NewSecurityRequirement(), 42 | json: `{}`, 43 | }, 44 | { 45 | requirement: NewSecurityRequirement().Authenticate("provider"), 46 | json: `{"provider":[]}`, 47 | }, 48 | { 49 | requirement: NewSecurityRequirement().Authenticate("provider", "scope1"), 50 | json: `{"provider":["scope1"]}`, 51 | }, 52 | { 53 | requirement: NewSecurityRequirement().Authenticate("provider", "scope1", "scope2"), 54 | json: `{"provider":["scope1","scope2"]}`, 55 | }, 56 | } 57 | 58 | for _, test := range tests { 59 | 60 | b, err := json.Marshal(test.requirement) 61 | require.NoError(t, err) 62 | require.Equal(t, test.json, string(b), "incorrect requirements encoding") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /openapi3/serialization_method.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | const ( 4 | SerializationSimple = "simple" 5 | SerializationLabel = "label" 6 | SerializationMatrix = "matrix" 7 | SerializationForm = "form" 8 | SerializationSpaceDelimited = "spaceDelimited" 9 | SerializationPipeDelimited = "pipeDelimited" 10 | SerializationDeepObject = "deepObject" 11 | ) 12 | 13 | // SerializationMethod describes a serialization method of HTTP request's parameters and body. 14 | type SerializationMethod struct { 15 | Style string 16 | Explode bool 17 | } 18 | -------------------------------------------------------------------------------- /openapi3/stringmap.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import "encoding/json" 4 | 5 | // StringMap is a map[string]string that ignores the origin in the underlying json representation. 6 | type StringMap map[string]string 7 | 8 | // UnmarshalJSON sets StringMap to a copy of data. 9 | func (stringMap *StringMap) UnmarshalJSON(data []byte) (err error) { 10 | *stringMap, _, err = unmarshalStringMap[string](data) 11 | return 12 | } 13 | 14 | // unmarshalStringMapP unmarshals given json into a map[string]*V 15 | func unmarshalStringMapP[V any](data []byte) (map[string]*V, *Origin, error) { 16 | var m map[string]any 17 | if err := json.Unmarshal(data, &m); err != nil { 18 | return nil, nil, err 19 | } 20 | 21 | origin, err := popOrigin(m, originKey) 22 | if err != nil { 23 | return nil, nil, err 24 | } 25 | 26 | result := make(map[string]*V, len(m)) 27 | for k, v := range m { 28 | value, err := deepCast[V](v) 29 | if err != nil { 30 | return nil, nil, err 31 | } 32 | result[k] = value 33 | } 34 | 35 | return result, origin, nil 36 | } 37 | 38 | // unmarshalStringMap unmarshals given json into a map[string]V 39 | func unmarshalStringMap[V any](data []byte) (map[string]V, *Origin, error) { 40 | var m map[string]any 41 | if err := json.Unmarshal(data, &m); err != nil { 42 | return nil, nil, err 43 | } 44 | 45 | origin, err := popOrigin(m, originKey) 46 | if err != nil { 47 | return nil, nil, err 48 | } 49 | 50 | result := make(map[string]V, len(m)) 51 | for k, v := range m { 52 | value, err := deepCast[V](v) 53 | if err != nil { 54 | return nil, nil, err 55 | } 56 | result[k] = *value 57 | } 58 | 59 | return result, origin, nil 60 | } 61 | 62 | // deepCast casts any value to a value of type V. 63 | func deepCast[V any](value any) (*V, error) { 64 | data, err := json.Marshal(value) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | var result V 70 | if err = json.Unmarshal(data, &result); err != nil { 71 | return nil, err 72 | } 73 | return &result, nil 74 | } 75 | 76 | // popOrigin removes the origin from the map and returns it. 77 | func popOrigin(m map[string]any, key string) (*Origin, error) { 78 | if !IncludeOrigin { 79 | return nil, nil 80 | } 81 | 82 | origin, err := deepCast[Origin](m[key]) 83 | if err != nil { 84 | return nil, err 85 | } 86 | delete(m, key) 87 | return origin, nil 88 | } 89 | -------------------------------------------------------------------------------- /openapi3/testdata/303bis/common/properties.yaml: -------------------------------------------------------------------------------- 1 | timestamp: 2 | type: string 3 | description: Date and time in ISO 8601 format. 4 | example: "2020-04-09T18:14:30Z" 5 | readOnly: true 6 | nullable: true 7 | 8 | timestamps: 9 | type: object 10 | properties: 11 | created_at: 12 | $ref: "#/timestamp" 13 | deleted_at: 14 | $ref: "#/timestamp" 15 | updated_at: 16 | $ref: "#/timestamp" 17 | -------------------------------------------------------------------------------- /openapi3/testdata/303bis/service.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: 'some service spec' 4 | version: 1.2.3 5 | 6 | paths: 7 | /service: 8 | get: 9 | tags: 10 | - services/service 11 | summary: List services 12 | description: List services. 13 | operationId: list-services 14 | responses: 15 | "200": 16 | description: OK 17 | content: 18 | application/json: 19 | schema: 20 | type: array 21 | items: 22 | $ref: "#/components/schemas/model_service" 23 | 24 | components: 25 | schemas: 26 | model_service: 27 | allOf: 28 | - $ref: "common/properties.yaml#/timestamps" 29 | -------------------------------------------------------------------------------- /openapi3/testdata/Test_param_override.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: customer 4 | version: '1.0' 5 | servers: 6 | - url: 'httpbin.kwaf-demo.test' 7 | paths: 8 | '/customers/{customer_id}': 9 | parameters: 10 | - schema: 11 | type: integer 12 | name: customer_id 13 | in: path 14 | required: true 15 | get: 16 | parameters: 17 | - schema: 18 | type: integer 19 | maximum: 100 20 | name: customer_id 21 | in: path 22 | required: true 23 | summary: customer 24 | tags: [] 25 | responses: 26 | '200': 27 | description: OK 28 | content: 29 | application/json: 30 | schema: 31 | type: object 32 | properties: 33 | customer_id: 34 | type: integer 35 | customer_name: 36 | type: string 37 | operationId: get-customers-customer_id 38 | description: Retrieve a specific customer by ID 39 | components: 40 | schemas: {} 41 | -------------------------------------------------------------------------------- /openapi3/testdata/callback-transactioned.yml: -------------------------------------------------------------------------------- 1 | post: 2 | requestBody: 3 | description: Callback payload 4 | content: 5 | 'application/json': 6 | schema: 7 | $ref: 'callbacks.yml#/components/schemas/SomePayload' 8 | responses: 9 | '200': 10 | description: callback successfully processed 11 | -------------------------------------------------------------------------------- /openapi3/testdata/callbacks.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: Callback refd 4 | version: 1.2.3 5 | paths: 6 | /trans: 7 | post: 8 | description: '' 9 | requestBody: 10 | description: '' 11 | content: 12 | 'application/json': 13 | schema: 14 | properties: 15 | id: {type: string} 16 | email: {format: email} 17 | responses: 18 | '201': 19 | description: subscription successfully created 20 | content: 21 | application/json: 22 | schema: 23 | type: object 24 | callbacks: 25 | transactionCallback: 26 | 'http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}': 27 | $ref: callback-transactioned.yml 28 | 29 | /other: 30 | post: 31 | description: '' 32 | parameters: 33 | - name: queryUrl 34 | in: query 35 | required: true 36 | description: | 37 | bla 38 | bla 39 | bla 40 | schema: 41 | type: string 42 | format: uri 43 | example: https://example.com 44 | responses: 45 | '201': 46 | description: '' 47 | content: 48 | application/json: 49 | schema: 50 | type: object 51 | callbacks: 52 | myEvent: 53 | $ref: '#/components/callbacks/MyCallbackEvent' 54 | 55 | components: 56 | schemas: 57 | SomePayload: {type: object} 58 | SomeOtherPayload: {type: boolean} 59 | callbacks: 60 | MyCallbackEvent: 61 | '{$request.query.queryUrl}': 62 | post: 63 | requestBody: 64 | description: Callback payload 65 | content: 66 | 'application/json': 67 | schema: 68 | $ref: '#/components/schemas/SomeOtherPayload' 69 | responses: 70 | '200': 71 | description: callback successfully processed 72 | -------------------------------------------------------------------------------- /openapi3/testdata/circularRef/base.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.3" 2 | info: 3 | title: Recursive cyclic refs example 4 | version: "1.0" 5 | components: 6 | schemas: 7 | Foo: 8 | properties: 9 | foo2: 10 | $ref: "other.yml#/components/schemas/Foo2" 11 | bar: 12 | $ref: "#/components/schemas/Bar" 13 | Bar: 14 | properties: 15 | foo: 16 | $ref: "#/components/schemas/Foo" 17 | Baz: 18 | $ref: "./baz.yml#/BazNested" 19 | -------------------------------------------------------------------------------- /openapi3/testdata/circularRef/baz.yml: -------------------------------------------------------------------------------- 1 | BazNested: 2 | type: object 3 | properties: 4 | baz: 5 | $ref: "#/BazNested" 6 | bazArray: 7 | type: array 8 | items: 9 | $ref: "#/BazNested" 10 | -------------------------------------------------------------------------------- /openapi3/testdata/circularRef/other.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.3" 2 | info: 3 | title: Recursive cyclic refs example 4 | version: "1.0" 5 | components: 6 | schemas: 7 | Foo2: 8 | properties: 9 | id: 10 | type: string 11 | -------------------------------------------------------------------------------- /openapi3/testdata/circularRef2/AwsEnvironmentSettings.yaml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | children: 4 | type: array 5 | items: 6 | $ref: './AwsEnvironmentSettings.yaml' 7 | description: test -------------------------------------------------------------------------------- /openapi3/testdata/circularRef2/circular2.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Circular Reference Example 4 | version: 1.0.0 5 | paths: 6 | /sample: 7 | put: 8 | requestBody: 9 | required: true 10 | content: 11 | application/json: 12 | schema: 13 | $ref: './AwsEnvironmentSettings.yaml' 14 | responses: 15 | '200': 16 | description: Ok -------------------------------------------------------------------------------- /openapi3/testdata/circularref.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: 'OAI Specification in YAML' 5 | version: 0.0.1 6 | paths: 7 | /test: 8 | get: 9 | responses: 10 | "200": 11 | $ref: '#/components/responses/GetTestOK' 12 | components: 13 | responses: 14 | GetTestOK: 15 | description: OK 16 | content: 17 | application/json: 18 | schema: 19 | $ref: 'pathref.openapi.yml#/components/schemas/TestSchema' 20 | -------------------------------------------------------------------------------- /openapi3/testdata/components.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "", 5 | "version": "1" 6 | }, 7 | "paths": {}, 8 | "components": { 9 | "schemas": { 10 | "Name": { 11 | "type": "string" 12 | }, 13 | "CustomTestSchema": { 14 | "$ref": "#/components/schemas/Name" 15 | } 16 | }, 17 | "responses": { 18 | "Name": { 19 | "description": "description" 20 | }, 21 | "CustomTestResponse": { 22 | "$ref": "#/components/responses/Name" 23 | } 24 | }, 25 | "parameters": { 26 | "Name": { 27 | "name": "id", 28 | "in": "header" 29 | }, 30 | "CustomTestParameter": { 31 | "$ref": "#/components/parameters/Name" 32 | } 33 | }, 34 | "examples": { 35 | "Name": { 36 | "description": "description" 37 | }, 38 | "CustomTestExample": { 39 | "$ref": "#/components/examples/Name" 40 | } 41 | }, 42 | "requestBodies": { 43 | "Name": { 44 | "content": {} 45 | }, 46 | "CustomTestRequestBody": { 47 | "$ref": "#/components/requestBodies/Name" 48 | } 49 | }, 50 | "headers": { 51 | "Name": { 52 | "description": "description" 53 | }, 54 | "CustomTestHeader": { 55 | "$ref": "#/components/headers/Name" 56 | } 57 | }, 58 | "securitySchemes": { 59 | "Name": { 60 | "type": "cookie", 61 | "description": "description" 62 | }, 63 | "CustomTestSecurityScheme": { 64 | "$ref": "#/components/securitySchemes/Name" 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /openapi3/testdata/components.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: '' 5 | version: '1' 6 | paths: {} 7 | components: 8 | schemas: 9 | Name: 10 | type: string 11 | CustomTestSchema: 12 | "$ref": "#/components/schemas/Name" 13 | responses: 14 | Name: 15 | description: description 16 | CustomTestResponse: 17 | "$ref": "#/components/responses/Name" 18 | parameters: 19 | Name: 20 | name: id 21 | in: header 22 | CustomTestParameter: 23 | "$ref": "#/components/parameters/Name" 24 | examples: 25 | Name: 26 | description: description 27 | CustomTestExample: 28 | "$ref": "#/components/examples/Name" 29 | requestBodies: 30 | Name: 31 | content: {} 32 | CustomTestRequestBody: 33 | "$ref": "#/components/requestBodies/Name" 34 | headers: 35 | Name: 36 | description: description 37 | CustomTestHeader: 38 | "$ref": "#/components/headers/Name" 39 | securitySchemes: 40 | Name: 41 | type: cookie 42 | description: description 43 | CustomTestSecurityScheme: 44 | "$ref": "#/components/securitySchemes/Name" 45 | -------------------------------------------------------------------------------- /openapi3/testdata/ext.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "a": { 5 | "type": "string" 6 | }, 7 | "b": { 8 | "type": "object", 9 | "description": "I use a local reference.", 10 | "properties": { 11 | "name": { 12 | "$ref": "#/definitions/a" 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /openapi3/testdata/interalizationNameCollision/api.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | version: 1.0.0 4 | title: Internalise ref name collision. 5 | paths: 6 | /book/record: 7 | get: 8 | operationId: getBookRecord 9 | responses: 10 | 200: 11 | description: A Book record. 12 | content: 13 | application/json: 14 | schema: 15 | $ref: './schemas/book/record.yml' 16 | /cd/record: 17 | get: 18 | operationId: getCDRecord 19 | responses: 20 | 200: 21 | description: A CD record. 22 | content: 23 | application/json: 24 | schema: 25 | $ref: './schemas/cd/record.yml' 26 | -------------------------------------------------------------------------------- /openapi3/testdata/interalizationNameCollision/api.yml.internalized.yml: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Internalise ref name collision." 6 | }, 7 | "paths": { 8 | "/book/record": { 9 | "get": { 10 | "operationId": "getBookRecord", 11 | "responses": { 12 | "200": { 13 | "description": "A Book record.", 14 | "content": { 15 | "application/json": { 16 | "schema": { 17 | "$ref": "#/components/schemas/schemas_book_record" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | }, 25 | "/cd/record": { 26 | "get": { 27 | "operationId": "getCDRecord", 28 | "responses": { 29 | "200": { 30 | "description": "A CD record.", 31 | "content": { 32 | "application/json": { 33 | "schema": { 34 | "$ref": "#/components/schemas/schemas_cd_record" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | "components": { 44 | "schemas": { 45 | "schemas_book_record": { 46 | "type": "object", 47 | "required": [ 48 | "id" 49 | ], 50 | "properties": { 51 | "id": { 52 | "type": "number" 53 | }, 54 | "pages": { 55 | "type": "number" 56 | } 57 | } 58 | }, 59 | "schemas_cd_record": { 60 | "type": "object", 61 | "required": [ 62 | "id" 63 | ], 64 | "properties": { 65 | "id": { 66 | "type": "number" 67 | }, 68 | "tracks": { 69 | "type": "number" 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /openapi3/testdata/interalizationNameCollision/schemas/book/record.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | required: 3 | - id 4 | properties: 5 | id: 6 | type: number 7 | pages: 8 | type: number 9 | -------------------------------------------------------------------------------- /openapi3/testdata/interalizationNameCollision/schemas/cd/record.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | required: 3 | - id 4 | properties: 5 | id: 6 | type: number 7 | tracks: 8 | type: number 9 | -------------------------------------------------------------------------------- /openapi3/testdata/issue235.spec0-typo.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: 'OAI Specification in YAML' 4 | version: 0.0.1 5 | paths: 6 | /test: 7 | get: 8 | responses: 9 | "200": 10 | $ref: '#/components/responses/GetTestOK' 11 | components: 12 | responses: 13 | GetTestOK: 14 | description: OK 15 | content: 16 | application/json: 17 | schema: 18 | $ref: '#/components/schemas/ObjectA' 19 | schemas: 20 | ObjectA: 21 | type: object 22 | properties: 23 | object_b: 24 | $ref: 'issue235.spec0-typo.yml#/components/schemas/ObjectD' 25 | -------------------------------------------------------------------------------- /openapi3/testdata/issue235.spec0.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: 'OAI Specification in YAML' 4 | version: 0.0.1 5 | paths: 6 | /test: 7 | get: 8 | responses: 9 | "200": 10 | $ref: '#/components/responses/GetTestOK' 11 | components: 12 | responses: 13 | GetTestOK: 14 | description: OK 15 | content: 16 | application/json: 17 | schema: 18 | $ref: '#/components/schemas/ObjectA' 19 | schemas: 20 | ObjectA: 21 | type: object 22 | properties: 23 | object_b: 24 | $ref: 'issue235.spec1.yml#/components/schemas/ObjectD' 25 | -------------------------------------------------------------------------------- /openapi3/testdata/issue235.spec1.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | ObjectD: 4 | type: object 5 | properties: 6 | result: 7 | $ref: '#/components/schemas/ObjectE' 8 | 9 | ObjectE: 10 | properties: 11 | name: 12 | $ref: issue235.spec2.yml#/components/schemas/ObjectX 13 | -------------------------------------------------------------------------------- /openapi3/testdata/issue235.spec2.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | ObjectX: 4 | type: object 5 | properties: 6 | name: 7 | type: string 8 | -------------------------------------------------------------------------------- /openapi3/testdata/issue241.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | FooBar: 4 | properties: 5 | type_url: 6 | type: string 7 | value: 8 | format: byte 9 | type: string 10 | type: object 11 | info: 12 | title: sample 13 | version: version not set 14 | openapi: 3.0.3 15 | paths: {} 16 | -------------------------------------------------------------------------------- /openapi3/testdata/issue409.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: Contains Patterns that can't be compiled by the go regexp engine 4 | title: Issue 409 5 | version: 0.0.1 6 | paths: 7 | /v1/apis/{apiID}: 8 | get: 9 | description: Get a list of all Apis and there versions for a given workspace 10 | operationId: getApisV1 11 | parameters: 12 | - description: The ID of the API 13 | in: path 14 | name: apiID 15 | required: true 16 | schema: 17 | type: string 18 | pattern: ^[a-zA-Z0-9]{0,4096}$ 19 | responses: 20 | "200": 21 | description: OK 22 | -------------------------------------------------------------------------------- /openapi3/testdata/issue499/foo.yml: -------------------------------------------------------------------------------- 1 | type: string -------------------------------------------------------------------------------- /openapi3/testdata/issue499/main.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: 'spec' 4 | version: 1.2.3 5 | 6 | paths: 7 | /foo: 8 | get: 9 | summary: get foo 10 | responses: 11 | "200": 12 | description: OK 13 | content: 14 | application/json: 15 | schema: 16 | $ref: "#/components/schemas/Foo" 17 | 18 | components: 19 | schemas: 20 | Foo: 21 | type: object 22 | properties: 23 | id: 24 | $ref: "./foo.yml" -------------------------------------------------------------------------------- /openapi3/testdata/issue638/test1.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | version: 1.0.0 4 | title: reference test part 1 5 | description: reference test part 1 6 | components: 7 | schemas: 8 | test1a: 9 | $ref: "test2.yaml#/components/schemas/test2a" 10 | test1b: 11 | $ref: "#/components/schemas/test1c" 12 | test1c: 13 | type: int 14 | test1d: 15 | $ref: "test2.yaml#/components/schemas/test2b" 16 | -------------------------------------------------------------------------------- /openapi3/testdata/issue638/test2.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | version: 1.0.0 4 | title: reference test part 2 5 | description: reference test part 2 6 | components: 7 | schemas: 8 | test2a: 9 | type: number 10 | test2b: 11 | $ref: "test1.yaml#/components/schemas/test1b" 12 | test1c: 13 | type: string 14 | -------------------------------------------------------------------------------- /openapi3/testdata/issue652/definitions.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | TestSchema: 4 | type: string 5 | -------------------------------------------------------------------------------- /openapi3/testdata/issue652/nested/schema.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | ReferenceToParentDirectory: 4 | $ref: "../definitions.yml#/components/schemas/TestSchema" 5 | -------------------------------------------------------------------------------- /openapi3/testdata/issue697.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | components: 3 | schemas: 4 | API: 5 | properties: 6 | dateExample: 7 | type: string 8 | format: date 9 | example: 2019-09-12 10 | info: 11 | title: sample 12 | version: version not set 13 | paths: {} 14 | 15 | -------------------------------------------------------------------------------- /openapi3/testdata/issue753.yml: -------------------------------------------------------------------------------- 1 | openapi: '3' 2 | info: 3 | version: 0.0.1 4 | title: 'test' 5 | paths: 6 | /test1: 7 | post: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | type: object 13 | responses: 14 | '200': 15 | description: 'test' 16 | callbacks: 17 | callback1: 18 | '{$request.body#/callback}': 19 | post: 20 | requestBody: 21 | content: 22 | application/json: 23 | schema: 24 | $ref: '#/components/schemas/test' 25 | responses: 26 | '200': 27 | description: 'test' 28 | /test2: 29 | post: 30 | requestBody: 31 | content: 32 | application/json: 33 | schema: 34 | type: object 35 | responses: 36 | '200': 37 | description: 'test' 38 | callbacks: 39 | callback2: 40 | '{$request.body#/callback}': 41 | post: 42 | requestBody: 43 | content: 44 | application/json: 45 | schema: 46 | $ref: '#/components/schemas/test' 47 | responses: 48 | '200': 49 | description: 'test' 50 | components: 51 | schemas: 52 | test: 53 | type: string 54 | -------------------------------------------------------------------------------- /openapi3/testdata/issue794.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: Swagger API 4 | version: v1 5 | paths: 6 | /test: 7 | post: 8 | requestBody: 9 | content: 10 | application/json: 11 | responses: 12 | "200": 13 | description: description 14 | -------------------------------------------------------------------------------- /openapi3/testdata/issue831/path.yml: -------------------------------------------------------------------------------- 1 | get: 2 | responses: 3 | "200": 4 | description: OK -------------------------------------------------------------------------------- /openapi3/testdata/issue831/testref.internalizepath.openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.3" 2 | info: 3 | title: Recursive refs example 4 | version: "1.0" 5 | paths: 6 | /bar: 7 | $ref: ./path.yml 8 | /foo: 9 | post: 10 | requestBody: 11 | content: 12 | application/json: 13 | schema: 14 | $ref: "#/components/requestBodies/test/content/application~1json/schema" 15 | responses: 16 | '200': 17 | description: Expected response to a valid request 18 | components: 19 | requestBodies: 20 | test: 21 | content: 22 | application/json: 23 | schema: 24 | type: string -------------------------------------------------------------------------------- /openapi3/testdata/issue831/testref.internalizepath.openapi.yml.internalized.yml: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Recursive refs example", 5 | "version": "1.0" 6 | }, 7 | "paths": { 8 | "/bar": { 9 | "get": { 10 | "responses": { 11 | "200": { 12 | "description": "OK" 13 | } 14 | } 15 | } 16 | }, 17 | "/foo": { 18 | "post": { 19 | "requestBody": { 20 | "content": { 21 | "application/json": { 22 | "schema": { 23 | "$ref": "#/components/requestBodies/test/content/application~1json/schema" 24 | } 25 | } 26 | } 27 | }, 28 | "responses": { 29 | "200": { 30 | "description": "Expected response to a valid request" 31 | } 32 | } 33 | } 34 | } 35 | }, 36 | "components": { 37 | "requestBodies": { 38 | "test": { 39 | "content": { 40 | "application/json": { 41 | "schema": { 42 | "type": "string" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /openapi3/testdata/issue959/components.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | External1: 4 | type: string -------------------------------------------------------------------------------- /openapi3/testdata/issue959/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: foo 4 | version: 0.0.0 5 | paths: 6 | /{external1}: 7 | parameters: 8 | - in: path 9 | name: external1 10 | required: true 11 | schema: 12 | $ref: './components.yml#/components/schemas/External1' 13 | get: 14 | responses: 15 | '204': 16 | description: No content -------------------------------------------------------------------------------- /openapi3/testdata/issue959/openapi.yml.internalized.yml: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "schemas": { 4 | "components_External1": { 5 | "type": "string" 6 | } 7 | } 8 | }, 9 | "info": { 10 | "title": "foo", 11 | "version": "0.0.0" 12 | }, 13 | "openapi": "3.0.0", 14 | "paths": { 15 | "/{external1}": { 16 | "get": { 17 | "responses": { 18 | "204": { 19 | "description": "No content" 20 | } 21 | } 22 | }, 23 | "parameters": [ 24 | { 25 | "in": "path", 26 | "name": "external1", 27 | "required": true, 28 | "schema": { 29 | "$ref": "#/components/schemas/components_External1" 30 | } 31 | } 32 | ] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /openapi3/testdata/issue961/config_param.yml: -------------------------------------------------------------------------------- 1 | oneOf: 2 | - title: "text" 3 | description: | 4 | type "text": **text** is a simple string. 5 | type: object 6 | required: 7 | - type 8 | properties: 9 | default: 10 | type: string 11 | position: 12 | type: number 13 | format: int32 14 | minimum: 0 15 | description: | 16 | Position of the parameter in the output. 17 | name: 18 | type: string 19 | description: "name of the parameter as used in the API" 20 | - title: "table" 21 | description: | 22 | type "table" 23 | type: object 24 | required: 25 | - type 26 | properties: 27 | default: 28 | type: string 29 | position: 30 | type: number 31 | format: int32 32 | minimum: 0 33 | description: | 34 | Position of the parameter in the output. 35 | name: 36 | type: string 37 | description: "name of the parameter as used in the API" 38 | fields: 39 | type: array 40 | items: 41 | $ref: '#' 42 | -------------------------------------------------------------------------------- /openapi3/testdata/issue961/main.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | configParam: 4 | $ref: './config_param.yml' -------------------------------------------------------------------------------- /openapi3/testdata/main.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | title: "test file" 4 | version: "n/a" 5 | paths: 6 | /testpath: 7 | $ref: "testpath.yaml#/paths/~1testpath" 8 | -------------------------------------------------------------------------------- /openapi3/testdata/my-openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.2", 3 | "info": { 4 | "title": "My API", 5 | "version": "0.1.0" 6 | }, 7 | "paths": { 8 | "/foo": { 9 | "get": { 10 | "responses": { 11 | "200": { 12 | "$ref": "my-other-openapi.json#/components/responses/DefaultResponse" 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /openapi3/testdata/my-other-openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.2", 3 | "info": { 4 | "title": "My other API", 5 | "version": "0.1.0" 6 | }, 7 | "components": { 8 | "schemas": { 9 | "DefaultObject": { 10 | "type": "object", 11 | "properties": { 12 | "foo": { 13 | "type": "string" 14 | }, 15 | "bar": { 16 | "type": "integer" 17 | } 18 | } 19 | } 20 | }, 21 | "responses": { 22 | "DefaultResponse": { 23 | "description": "", 24 | "content": { 25 | "application/json": { 26 | "schema": { 27 | "$ref": "#/components/schemas/DefaultObject" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /openapi3/testdata/nesteddir/nestedcomponents.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "", 5 | "version": "1" 6 | }, 7 | "paths": {}, 8 | "components": { 9 | "schemas": { 10 | "CustomTestSchema": { 11 | "$ref": "nestedcomponentsref.openapi.json#/components/schemas/Name" 12 | } 13 | }, 14 | "responses": { 15 | "CustomTestResponse": { 16 | "$ref": "nestedcomponentsref.openapi.json#/components/responses/Name" 17 | } 18 | }, 19 | "parameters": { 20 | "CustomTestParameter": { 21 | "$ref": "nestedcomponentsref.openapi.json#/components/parameters/Name" 22 | } 23 | }, 24 | "examples": { 25 | "CustomTestExample": { 26 | "$ref": "nestedcomponentsref.openapi.json#/components/examples/Name" 27 | } 28 | }, 29 | "requestBodies": { 30 | "CustomTestRequestBody": { 31 | "$ref": "nestedcomponentsref.openapi.json#/components/requestBodies/Name" 32 | } 33 | }, 34 | "headers": { 35 | "CustomTestHeader": { 36 | "$ref": "nestedcomponentsref.openapi.json#/components/headers/Name" 37 | } 38 | }, 39 | "securitySchemes": { 40 | "CustomTestSecurityScheme": { 41 | "$ref": "nestedcomponentsref.openapi.json#/components/securitySchemes/Name" 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /openapi3/testdata/nesteddir/nestedcomponentsref.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "", 5 | "version": "1" 6 | }, 7 | "paths": {}, 8 | "components": { 9 | "schemas": { 10 | "Name": { 11 | "type": "string" 12 | } 13 | }, 14 | "responses": { 15 | "Name": { 16 | "description": "description" 17 | } 18 | }, 19 | "parameters": { 20 | "Name": { 21 | "name": "id", 22 | "in": "header" 23 | } 24 | }, 25 | "examples": { 26 | "Name": { 27 | "description": "description" 28 | } 29 | }, 30 | "requestBodies": { 31 | "Name": { 32 | "content": {} 33 | } 34 | }, 35 | "headers": { 36 | "Name": { 37 | "description": "description" 38 | } 39 | }, 40 | "securitySchemes": { 41 | "Name": { 42 | "type": "cookie", 43 | "description": "description" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /openapi3/testdata/origin/additional_properties.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: Test API 4 | version: v1 5 | paths: 6 | /partner-api/test/some-method: 7 | get: 8 | responses: 9 | "200": 10 | description: Success 11 | content: 12 | application/json: 13 | schema: 14 | additionalProperties: 15 | type: object 16 | properties: 17 | code: 18 | type: integer 19 | text: 20 | type: string -------------------------------------------------------------------------------- /openapi3/testdata/origin/example.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Security Requirement Example 4 | version: 1.0.0 5 | paths: 6 | /subscribe: 7 | post: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | type: object 13 | examples: 14 | bar: 15 | summary: A bar example 16 | value: {"bar": "baz"} 17 | responses: 18 | "200": 19 | description: OK 20 | -------------------------------------------------------------------------------- /openapi3/testdata/origin/external_docs.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: Test API 4 | version: v1 5 | paths: 6 | /partner-api/test/some-method: 7 | get: 8 | tags: 9 | - Test 10 | responses: 11 | "200": 12 | description: Success 13 | externalDocs: 14 | description: API Documentation 15 | url: https://openweathermap.org/api -------------------------------------------------------------------------------- /openapi3/testdata/origin/parameters.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | title: Tufin 3 | version: 1.0.0 4 | openapi: 3.0.3 5 | paths: 6 | /api/test: 7 | get: 8 | parameters: 9 | - name: a 10 | in: query 11 | schema: 12 | type: integer 13 | responses: 14 | 200: 15 | description: OK 16 | post: 17 | responses: 18 | 201: 19 | description: OK 20 | -------------------------------------------------------------------------------- /openapi3/testdata/origin/request_body.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Security Requirement Example 4 | version: 1.0.0 5 | paths: 6 | /subscribe: 7 | post: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | type: object 13 | properties: 14 | inProgressUrl: 15 | type: string 16 | failedUrl: 17 | type: string 18 | successUrl: 19 | type: string 20 | responses: 21 | "200": 22 | description: OK 23 | -------------------------------------------------------------------------------- /openapi3/testdata/origin/security.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Security Requirement Example 4 | version: 1.0.0 5 | paths: 6 | /subscribe: 7 | post: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | type: object 13 | properties: 14 | inProgressUrl: 15 | type: string 16 | failedUrl: 17 | type: string 18 | successUrl: 19 | type: string 20 | responses: 21 | "200": 22 | description: OK 23 | security: 24 | - petstore_auth: 25 | - write:pets 26 | - read:pets 27 | components: 28 | securitySchemes: 29 | petstore_auth: 30 | type: oauth2 31 | flows: 32 | implicit: 33 | authorizationUrl: http://example.org/api/oauth/dialog 34 | scopes: 35 | write:pets: modify pets in your account 36 | read:pets: read your pets -------------------------------------------------------------------------------- /openapi3/testdata/origin/simple.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: Test API 4 | version: v1 5 | paths: 6 | /partner-api/test/some-method: 7 | get: 8 | tags: 9 | - Test 10 | responses: 11 | "200": 12 | description: Success 13 | /partner-api/test/another-method: 14 | get: 15 | tags: 16 | - Test 17 | responses: 18 | "200": 19 | description: Success 20 | -------------------------------------------------------------------------------- /openapi3/testdata/origin/xml.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Security Requirement Example 4 | version: 1.0.0 5 | paths: 6 | /subscribe: 7 | post: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | type: object 13 | properties: 14 | id: 15 | type: integer 16 | format: int32 17 | xml: 18 | attribute: true 19 | name: 20 | type: string 21 | xml: 22 | namespace: http://example.com/schema/sample 23 | prefix: sample 24 | responses: 25 | "200": 26 | description: OK 27 | -------------------------------------------------------------------------------- /openapi3/testdata/pathref.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: 'OAI Specification in YAML' 5 | version: 0.0.1 6 | paths: 7 | /test: 8 | $ref: 'circularref.openapi.yml#/paths/~1test' 9 | components: 10 | schemas: 11 | TestSchema: 12 | type: string 13 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/components/Bar.yml: -------------------------------------------------------------------------------- 1 | type: string 2 | example: bar 3 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/components/Cat.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | cat: 4 | $ref: ../openapi.yml#/components/schemas/Cat 5 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/components/Foo.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | bar: 4 | $ref: ../openapi.yml#/components/schemas/Bar 5 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/components/Foo/Foo2.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | foo: 4 | $ref: ../../openapi.yml#/components/schemas/Foo 5 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/components/models/error.yaml: -------------------------------------------------------------------------------- 1 | type: object 2 | title: ErrorDetails 3 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/issue615.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.3" 2 | info: 3 | title: Deep recursive cyclic refs example 4 | version: "1.0" 5 | paths: 6 | /foo: 7 | $ref: ./paths/foo.yml 8 | components: 9 | schemas: 10 | FilterColumnIncludes: 11 | type: object 12 | properties: 13 | $includes: 14 | $ref: '#/components/schemas/FilterPredicate' 15 | additionalProperties: false 16 | maxProperties: 1 17 | minProperties: 1 18 | FilterPredicate: 19 | oneOf: 20 | - $ref: '#/components/schemas/FilterValue' 21 | - type: array 22 | items: 23 | $ref: '#/components/schemas/FilterPredicate' 24 | minLength: 1 25 | - $ref: '#/components/schemas/FilterPredicateOp' 26 | - $ref: '#/components/schemas/FilterPredicateRangeOp' 27 | FilterPredicateOp: 28 | type: object 29 | properties: 30 | $any: 31 | oneOf: 32 | - type: array 33 | items: 34 | $ref: '#/components/schemas/FilterPredicate' 35 | $none: 36 | oneOf: 37 | - $ref: '#/components/schemas/FilterPredicate' 38 | - type: array 39 | items: 40 | $ref: '#/components/schemas/FilterPredicate' 41 | additionalProperties: false 42 | maxProperties: 1 43 | minProperties: 1 44 | FilterPredicateRangeOp: 45 | type: object 46 | properties: 47 | $lt: 48 | $ref: '#/components/schemas/FilterRangeValue' 49 | additionalProperties: false 50 | maxProperties: 2 51 | minProperties: 2 52 | FilterRangeValue: 53 | oneOf: 54 | - type: number 55 | - type: string 56 | FilterValue: 57 | oneOf: 58 | - type: number 59 | - type: string 60 | - type: boolean -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.3" 2 | info: 3 | title: Recursive refs example 4 | version: "1.0" 5 | paths: 6 | /foo: 7 | $ref: ./paths/foo.yml 8 | /double-ref-foo: 9 | get: 10 | summary: Double ref response 11 | description: Reference response with double reference. 12 | responses: 13 | "400": 14 | $ref: "#/components/responses/400" 15 | components: 16 | schemas: 17 | Foo: 18 | $ref: ./components/Foo.yml 19 | Foo2: 20 | $ref: ./components/Foo/Foo2.yml 21 | Bar: 22 | $ref: ./components/Bar.yml 23 | Cat: 24 | $ref: ./components/Cat.yml 25 | Error: 26 | $ref: ./components/models/error.yaml 27 | responses: 28 | "400": 29 | description: 400 Bad Request 30 | content: 31 | application/json: 32 | schema: 33 | $ref: "#/components/schemas/Error" 34 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/parameters/number.yml: -------------------------------------------------------------------------------- 1 | name: someNumber 2 | in: query 3 | schema: 4 | type: string 5 | -------------------------------------------------------------------------------- /openapi3/testdata/recursiveRef/paths/foo.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - $ref: ../parameters/number.yml 3 | get: 4 | responses: 5 | "200": 6 | description: OK 7 | content: 8 | application/json: 9 | schema: 10 | type: object 11 | properties: 12 | foo2: 13 | $ref: ../openapi.yml#/components/schemas/Foo2 14 | "400": 15 | $ref: "../openapi.yml#/components/responses/400" 16 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRef/messages/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int32" 7 | }, 8 | "ref_prop_part": { 9 | "$ref": "./dataPart.json" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRef/messages/dataPart.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "idPart": { 5 | "type": "integer", 6 | "format": "int64" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRef/messages/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "definition_reference" 5 | ], 6 | "properties": { 7 | "definition_reference": { 8 | "$ref": "./data.json" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRef/messages/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int32" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRef/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Reference in reference example", 5 | "version": "1.0.0" 6 | }, 7 | "paths": { 8 | "/api/test/ref/in/ref": { 9 | "post": { 10 | "requestBody": { 11 | "content": { 12 | "application/json": { 13 | "schema": { 14 | "type": "object", 15 | "properties" : { 16 | "data": { 17 | "$ref": "#/components/schemas/Request" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | }, 24 | "responses": { 25 | "200": { 26 | "description": "Successful response", 27 | "content": { 28 | "application/json": { 29 | "schema": { 30 | "$ref": "messages/response.json" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | }, 39 | "components": { 40 | "schemas": { 41 | "Request": { 42 | "$ref": "messages/request.json" 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRefInParentsSubdir/messages/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int32" 7 | }, 8 | "ref_prop_part": { 9 | "$ref": "./dataPart.json" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRefInParentsSubdir/messages/dataPart.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "idPart": { 5 | "type": "integer", 6 | "format": "int64" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRefInParentsSubdir/messages/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "definition_reference" 5 | ], 6 | "properties": { 7 | "definition_reference": { 8 | "$ref": "./data.json" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRefInParentsSubdir/messages/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int32" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /openapi3/testdata/refInLocalRefInParentsSubdir/spec/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Reference in reference example", 5 | "version": "1.0.0" 6 | }, 7 | "paths": { 8 | "/api/test/ref/in/ref": { 9 | "post": { 10 | "requestBody": { 11 | "content": { 12 | "application/json": { 13 | "schema": { 14 | "$ref": "../messages/request.json" 15 | } 16 | } 17 | } 18 | }, 19 | "responses": { 20 | "200": { 21 | "description": "Successful response", 22 | "content": { 23 | "application/json": { 24 | "schema": { 25 | "type": "object", 26 | "properties": { 27 | "ref_prop": { 28 | "$ref": "#/components/schemas/Data" 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | }, 39 | "components": { 40 | "schemas": { 41 | "Data": { 42 | "$ref": "../messages/data.json" 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /openapi3/testdata/refInRef/messages/definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "External": { 4 | "type": "string" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /openapi3/testdata/refInRef/messages/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "definition_reference" 5 | ], 6 | "properties": { 7 | "definition_reference": { 8 | "$ref": "definitions.json#/definitions/External" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /openapi3/testdata/refInRef/messages/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int32" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /openapi3/testdata/refInRef/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Reference in reference example", 5 | "version": "1.0.0" 6 | }, 7 | "paths": { 8 | "/api/test/ref/in/ref": { 9 | "post": { 10 | "requestBody": { 11 | "content": { 12 | "application/json": { 13 | "schema": { 14 | "$ref": "messages/request.json" 15 | } 16 | } 17 | } 18 | }, 19 | "responses": { 20 | "200": { 21 | "description": "Successful response", 22 | "content": { 23 | "application/json": { 24 | "schema": { 25 | "$ref": "messages/response.json" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /openapi3/testdata/refInRefInProperty/components/errors.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | Error: 4 | type: object 5 | description: Error info in problem-details-0.0.1 format 6 | properties: 7 | error: 8 | $ref: "../common-data-objects/problem-details-0.0.1.schema.json" 9 | 10 | responses: 11 | 400: 12 | description: "" 13 | content: 14 | application/json: 15 | schema: 16 | $ref: "#/components/schemas/Error" 17 | examples: 18 | json: 19 | $ref: "#/components/examples/BadRequest" 20 | 401: 21 | description: "" 22 | content: 23 | application/json: 24 | schema: 25 | $ref: "#/components/schemas/Error" 26 | examples: 27 | json: 28 | $ref: "#/components/examples/Unauthorized" 29 | 500: 30 | description: "" 31 | content: 32 | application/json: 33 | schema: 34 | $ref: "#/components/schemas/Error" 35 | examples: 36 | json: 37 | $ref: "#/components/examples/InternalServerError" 38 | 39 | examples: 40 | BadRequest: 41 | summary: Wrong format 42 | value: 43 | error: 44 | type: validation-error 45 | title: Your request parameters didn't validate. 46 | status: 400 47 | invalid-params: 48 | - name: age 49 | reason: must be a positive integer 50 | - name: color 51 | reason: must be 'green', 'red' or 'blue' 52 | Unauthorized: 53 | summary: Not authenticated 54 | value: 55 | error: 56 | type: unauthorized 57 | title: The request has not been applied because it lacks valid authentication credentials 58 | for the target resource. 59 | status: 401 60 | InternalServerError: 61 | summary: Not handled internal server error 62 | value: 63 | error: 64 | type: internal-server-error 65 | title: Internal server error. 66 | status: 500 67 | -------------------------------------------------------------------------------- /openapi3/testdata/refInRefInProperty/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | 3 | info: 4 | title: "Reference in reference in property example" 5 | version: "1.0.0" 6 | paths: 7 | /api/test/ref/in/ref/in/property: 8 | post: 9 | requestBody: 10 | content: 11 | multipart/form-data: 12 | schema: 13 | type: object 14 | properties: 15 | file: 16 | type: array 17 | items: 18 | type: string 19 | format: binary 20 | required: true 21 | responses: 22 | 200: 23 | description: "Files are saved successfully" 24 | 400: 25 | $ref: "./components/errors.yaml#/components/responses/400" 26 | 401: 27 | $ref: "./components/errors.yaml#/components/responses/401" 28 | 500: 29 | $ref: "./components/errors.yaml#/components/responses/500" 30 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | version: 1.0.0 4 | title: Mode ref resolution Example 5 | paths: 6 | /records: 7 | get: 8 | operationId: getBookRecords 9 | responses: 10 | 200: 11 | content: 12 | application/json: 13 | schema: 14 | $ref: '#/components/schemas/BookRecords' 15 | 500: 16 | $ref: './other/response.yml' 17 | /record: 18 | get: 19 | operationId: getBookRecord 20 | parameters: 21 | - $ref: 'other/parameter.yml' 22 | responses: 23 | 200: 24 | content: 25 | application/json: 26 | schema: 27 | $ref: '#/components/schemas/BookRecord' 28 | examples: 29 | first-example: 30 | $ref: './other/example.yml' 31 | headers: 32 | X-Custom-Header: 33 | $ref: 'schema/book/../../other/header.yml' 34 | X-Custom-Header2: 35 | schema: 36 | type: string 37 | 500: 38 | $ref: './other/response.yml' 39 | components: 40 | schemas: 41 | BookRecord: 42 | $ref: './schemas/book/record.yml' 43 | BookRecords: 44 | $ref: './schemas/book/records.yml' 45 | CdRecord: 46 | $ref: './schemas/cd/record.yml' 47 | CdRecords: 48 | $ref: './schemas/cd/records.yml' 49 | responses: 50 | ErrorResponse: 51 | $ref: './other/response.yml' 52 | parameters: 53 | BookIDParameter: 54 | $ref: './other/parameter.yml' 55 | headers: 56 | CustomHeader: 57 | $ref: './other/header.yml' 58 | examples: 59 | RecordResponseExample: 60 | $ref: './other/example.yml' 61 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/other/example.yml: -------------------------------------------------------------------------------- 1 | description: Example example 2 | id: 42 3 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/other/header.yml: -------------------------------------------------------------------------------- 1 | description: Example 2 | schema: 3 | type: string 4 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/other/parameter.yml: -------------------------------------------------------------------------------- 1 | name: id 2 | in: query 3 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/other/response.yml: -------------------------------------------------------------------------------- 1 | content: 2 | application/json: 3 | schema: 4 | $ref: '../schemas/error.yml' 5 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/schemas/book/record.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | required: 3 | - id 4 | properties: 5 | id: 6 | type: number 7 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/schemas/book/records.yml: -------------------------------------------------------------------------------- 1 | type: array 2 | items: 3 | $ref: './record.yml' 4 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/schemas/cd/record.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | required: 3 | - id 4 | properties: 5 | id: 6 | type: number 7 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/schemas/cd/records.yml: -------------------------------------------------------------------------------- 1 | type: array 2 | items: 3 | $ref: '../../openapi.yml#/components/schemas/CdRecord' 4 | -------------------------------------------------------------------------------- /openapi3/testdata/refsToRoot/schemas/error.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | required: 3 | - msg 4 | properties: 5 | id: 6 | type: string 7 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestExample.yml: -------------------------------------------------------------------------------- 1 | summary: An example 2 | value: | 3 | { 4 | "example": "hello" 5 | } -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestHeader.yml: -------------------------------------------------------------------------------- 1 | description: description -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestParameter.yml: -------------------------------------------------------------------------------- 1 | name: param 2 | in: path 3 | required: true 4 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestPath.yml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: findAllDefinitions 3 | responses: 4 | "200": 5 | content: 6 | application/json: 7 | schema: 8 | properties: 9 | pets: 10 | items: 11 | type: array 12 | properties: 13 | repository: 14 | name: string 15 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestRequestBody.yml: -------------------------------------------------------------------------------- 1 | description: example request -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestResponse.yml: -------------------------------------------------------------------------------- 1 | description: description -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestSchema.yml: -------------------------------------------------------------------------------- 1 | type: string -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/CustomTestSecurityScheme.yml: -------------------------------------------------------------------------------- 1 | type: http 2 | scheme: basic -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/openapi/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: "" 4 | version: "1.0" 5 | paths: {} 6 | components: 7 | responses: 8 | TestResponse: 9 | $ref: responses/nesteddir/CustomTestResponse.yml 10 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocs/openapi/responses/custom/CustomTestResponse.yml: -------------------------------------------------------------------------------- 1 | description: description 2 | content: 3 | application/json: 4 | schema: 5 | $ref: "../../../CustomTestSchema.yml" 6 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestExample.yml: -------------------------------------------------------------------------------- 1 | summary: An example 2 | value: hello -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml: -------------------------------------------------------------------------------- 1 | description: header 2 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1.yml: -------------------------------------------------------------------------------- 1 | description: header1 2 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1bis.yml: -------------------------------------------------------------------------------- 1 | header: 2 | description: header1 3 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2.yml: -------------------------------------------------------------------------------- 1 | description: header2 2 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2bis.yml: -------------------------------------------------------------------------------- 1 | header: 2 | description: header2 3 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestParameter.yml: -------------------------------------------------------------------------------- 1 | name: param 2 | in: path 3 | required: true 4 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestRequestBody.yml: -------------------------------------------------------------------------------- 1 | description: example request -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestResponse.yml: -------------------------------------------------------------------------------- 1 | description: description -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/CustomTestSchema.yml: -------------------------------------------------------------------------------- 1 | type: string -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/openapi/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: "" 4 | version: "1.0" 5 | paths: 6 | /pets/{id}: 7 | $ref: "paths/nesteddir/CustomTestPath.yml" 8 | /pets/{id}/{city}: 9 | $ref: "paths/nesteddir/morenested/CustomTestPath.yml" 10 | components: {} 11 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/CustomTestPath.yml: -------------------------------------------------------------------------------- 1 | patch: 2 | description: "Modify a pet" 3 | parameters: 4 | - $ref: "../../../CustomTestParameter.yml" 5 | requestBody: 6 | $ref: "../../../CustomTestRequestBody.yml" 7 | responses: 8 | 200: 9 | description: "success" 10 | headers: 11 | X-Rate-Limit-Reset: 12 | $ref: "../../../CustomTestHeader.yml" 13 | X-Another: 14 | $ref: ../../../CustomTestHeader1.yml 15 | X-And-Another: 16 | $ref: ../../../CustomTestHeader2.yml 17 | content: 18 | application/json: 19 | schema: 20 | $ref: "../../../CustomTestSchema.yml" 21 | examples: 22 | CustomTestExample: 23 | $ref: "../../../CustomTestExample.yml" 24 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenested/CustomTestPath.yml: -------------------------------------------------------------------------------- 1 | patch: 2 | description: "Modify a pet" 3 | parameters: 4 | - $ref: "../../../../CustomTestParameter.yml" 5 | requestBody: 6 | $ref: "../../../../CustomTestRequestBody.yml" 7 | responses: 8 | 200: 9 | description: "success" 10 | headers: 11 | X-Rate-Limit-Reset: 12 | $ref: "../../../../CustomTestHeader.yml" 13 | X-Another: 14 | $ref: '../../../../CustomTestHeader1bis.yml#/header' 15 | X-And-Another: 16 | $ref: '../../../../CustomTestHeader2bis.yml#/header' 17 | content: 18 | application/json: 19 | schema: 20 | $ref: "../../../../CustomTestSchema.yml" 21 | examples: 22 | CustomTestExample: 23 | $ref: "../../../../CustomTestExample.yml" 24 | -------------------------------------------------------------------------------- /openapi3/testdata/relativeDocsUseDocumentPath/openapi/responses/nesteddir/CustomTestResponse.yml: -------------------------------------------------------------------------------- 1 | description: description 2 | content: 3 | application/json: 4 | schema: 5 | $ref: "../../../CustomTestSchema.yml" 6 | example: 7 | $ref: "../../../CustomTestExample.yml" 8 | headers: 9 | X-Rate-Limit-Reset: 10 | $ref: "../../../CustomTestHeader.yml" 11 | -------------------------------------------------------------------------------- /openapi3/testdata/schema618.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | Account: 4 | required: 5 | - name 6 | - nature 7 | type: object 8 | properties: 9 | id: 10 | type: string 11 | name: 12 | type: string 13 | description: 14 | type: string 15 | type: 16 | type: string 17 | enum: 18 | - assets 19 | - liabilities 20 | nature: 21 | type: string 22 | enum: 23 | - asset 24 | - liability 25 | Record: 26 | required: 27 | - account 28 | - concept 29 | type: object 30 | properties: 31 | account: 32 | $ref: "#/components/schemas/Account" 33 | concept: 34 | type: string 35 | partial: 36 | type: number 37 | credit: 38 | type: number 39 | debit: 40 | type: number 41 | JournalEntry: 42 | required: 43 | - type 44 | - creationDate 45 | - records 46 | type: object 47 | properties: 48 | id: 49 | type: string 50 | type: 51 | type: string 52 | enum: 53 | - daily 54 | - ingress 55 | - egress 56 | creationDate: 57 | type: string 58 | format: date 59 | records: 60 | type: array 61 | items: 62 | $ref: "#/components/schemas/Record" 63 | -------------------------------------------------------------------------------- /openapi3/testdata/singleresponse.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "this is a single response definition" 3 | } 4 | -------------------------------------------------------------------------------- /openapi3/testdata/singleresponse.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | description: this is a single response definition 3 | -------------------------------------------------------------------------------- /openapi3/testdata/spec.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | version: 1.0.0 4 | title: Some Swagger 5 | license: 6 | name: MIT 7 | paths: {} 8 | components: 9 | schemas: 10 | Test: 11 | type: object 12 | properties: 13 | test: 14 | $ref: 'ext.json#/definitions/b' 15 | -------------------------------------------------------------------------------- /openapi3/testdata/spec.yaml.internalized.yml: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "schemas": { 4 | "Test": { 5 | "properties": { 6 | "test": { 7 | "$ref": "#/components/schemas/ext_definitions_b" 8 | } 9 | }, 10 | "type": "object" 11 | }, 12 | "ext_definitions_a": { 13 | "type": "string" 14 | }, 15 | "ext_definitions_b": { 16 | "description": "I use a local reference.", 17 | "properties": { 18 | "name": { 19 | "$ref": "#/components/schemas/ext_definitions_a" 20 | } 21 | }, 22 | "type": "object" 23 | } 24 | } 25 | }, 26 | "info": { 27 | "license": { 28 | "name": "MIT" 29 | }, 30 | "title": "Some Swagger", 31 | "version": "1.0.0" 32 | }, 33 | "openapi": "3.0.1", 34 | "paths": { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /openapi3/testdata/test.openapi.additionalproperties.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: "OAI Specification in YAML" 4 | version: 0.0.1 5 | paths: {} 6 | components: 7 | schemas: 8 | TestSchema: 9 | type: object 10 | additionalProperties: 11 | type: array 12 | items: 13 | type: string 14 | TestSchema2: 15 | type: object 16 | additionalProperties: 17 | type: string 18 | -------------------------------------------------------------------------------- /openapi3/testdata/test.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "", 5 | "version": "0.0.1" 6 | }, 7 | "paths": {}, 8 | "components": { 9 | "schemas": { 10 | "TestSchema": { 11 | "type": "string" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /openapi3/testdata/test.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: 'OAI Specification in YAML' 5 | version: 0.0.1 6 | paths: {} 7 | components: 8 | schemas: 9 | TestSchema: 10 | type: string 11 | -------------------------------------------------------------------------------- /openapi3/testdata/testpath.yaml: -------------------------------------------------------------------------------- 1 | paths: 2 | /testpath: 3 | get: 4 | responses: 5 | "200": 6 | $ref: "#/components/responses/testpath_200_response" 7 | 8 | components: 9 | responses: 10 | testpath_200_response: 11 | description: a custom response 12 | content: 13 | application/json: 14 | schema: 15 | type: string 16 | -------------------------------------------------------------------------------- /openapi3/testdata/testref.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "OAI Specification w/ refs in JSON", 5 | "x-my-extension": {"k": 42}, 6 | "version": "1" 7 | }, 8 | "paths": {}, 9 | "components": { 10 | "schemas": { 11 | "AnotherTestSchema": { 12 | "$ref": "components.openapi.json#/components/schemas/CustomTestSchema" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /openapi3/testdata/testref.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: 'OAI Specification w/ refs in YAML' 5 | # x-my-extension: {k: 42}, 6 | version: '1' 7 | paths: {} 8 | components: 9 | schemas: 10 | AnotherTestSchema: 11 | $ref: 'components.openapi.yml#/components/schemas/CustomTestSchema' 12 | -------------------------------------------------------------------------------- /openapi3/testdata/testref.openapi.yml.internalized.yml: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "schemas": { 4 | "AnotherTestSchema": { 5 | "type": "string" 6 | }, 7 | "components_Name": { 8 | "type": "string" 9 | } 10 | } 11 | }, 12 | "info": { 13 | "title": "OAI Specification w/ refs in YAML", 14 | "version": "1" 15 | }, 16 | "openapi": "3.0.0", 17 | "paths": { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /openapi3/testdata/testrefsinglecomponent.openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "", 5 | "version": "1" 6 | }, 7 | "paths": {}, 8 | "components": { 9 | "responses": { 10 | "SomeResponse": { 11 | "$ref": "singleresponse.openapi.json" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /openapi3/testdata/testrefsinglecomponent.openapi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: 'OAI Specification w/ refs in YAML' 5 | version: '1' 6 | paths: {} 7 | components: 8 | responses: 9 | SomeResponse: 10 | "$ref": singleresponse.openapi.yml 11 | -------------------------------------------------------------------------------- /openapi3/unique_items_checker_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/getkin/kin-openapi/openapi3" 9 | ) 10 | 11 | func TestRegisterArrayUniqueItemsChecker(t *testing.T) { 12 | var ( 13 | schema = openapi3.Schema{ 14 | Type: &openapi3.Types{"array"}, 15 | UniqueItems: true, 16 | Items: openapi3.NewStringSchema().NewRef(), 17 | } 18 | val = []any{"1", "2", "3"} 19 | ) 20 | 21 | // Fist checked by predefined function 22 | err := schema.VisitJSON(val) 23 | require.NoError(t, err) 24 | 25 | // Register a function will always return false when check if a 26 | // slice has unique items, then use a slice indeed has unique 27 | // items to verify that check unique items will failed. 28 | openapi3.RegisterArrayUniqueItemsChecker(func(items []any) bool { 29 | return false 30 | }) 31 | defer openapi3.RegisterArrayUniqueItemsChecker(nil) // Reset for other tests 32 | 33 | err = schema.VisitJSON(val) 34 | require.Error(t, err) 35 | require.ErrorContains(t, err, "duplicate items found") 36 | } 37 | -------------------------------------------------------------------------------- /openapi3/validation_issue409_test.go: -------------------------------------------------------------------------------- 1 | package openapi3_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/getkin/kin-openapi/openapi3" 10 | ) 11 | 12 | func TestIssue409PatternIgnored(t *testing.T) { 13 | l := openapi3.NewLoader() 14 | s, err := l.LoadFromFile("testdata/issue409.yml") 15 | require.NoError(t, err) 16 | 17 | err = s.Validate(l.Context, openapi3.DisableSchemaPatternValidation()) 18 | assert.NoError(t, err) 19 | } 20 | 21 | func TestIssue409PatternNotIgnored(t *testing.T) { 22 | l := openapi3.NewLoader() 23 | s, err := l.LoadFromFile("testdata/issue409.yml") 24 | require.NoError(t, err) 25 | 26 | err = s.Validate(l.Context) 27 | assert.Error(t, err) 28 | } 29 | 30 | func TestIssue409HygienicUseOfCtx(t *testing.T) { 31 | l := openapi3.NewLoader() 32 | doc, err := l.LoadFromFile("testdata/issue409.yml") 33 | require.NoError(t, err) 34 | 35 | err = doc.Validate(l.Context, openapi3.DisableSchemaPatternValidation()) 36 | assert.NoError(t, err) 37 | err = doc.Validate(l.Context) 38 | assert.Error(t, err) 39 | 40 | // and the other way 41 | 42 | l = openapi3.NewLoader() 43 | doc, err = l.LoadFromFile("testdata/issue409.yml") 44 | require.NoError(t, err) 45 | 46 | err = doc.Validate(l.Context) 47 | assert.Error(t, err) 48 | err = doc.Validate(l.Context, openapi3.DisableSchemaPatternValidation()) 49 | assert.NoError(t, err) 50 | } 51 | -------------------------------------------------------------------------------- /openapi3/visited.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | func newVisited() visitedComponent { 4 | return visitedComponent{ 5 | header: make(map[*Header]struct{}), 6 | schema: make(map[*Schema]struct{}), 7 | } 8 | } 9 | 10 | type visitedComponent struct { 11 | header map[*Header]struct{} 12 | schema map[*Schema]struct{} 13 | } 14 | 15 | // resetVisited clears visitedComponent map 16 | // should be called before recursion over doc *T 17 | func (doc *T) resetVisited() { 18 | doc.visited = newVisited() 19 | } 20 | 21 | // isVisitedHeader returns `true` if the *Header pointer was already visited 22 | // otherwise it returns `false` 23 | func (doc *T) isVisitedHeader(h *Header) bool { 24 | if _, ok := doc.visited.header[h]; ok { 25 | return true 26 | } 27 | 28 | doc.visited.header[h] = struct{}{} 29 | return false 30 | } 31 | 32 | // isVisitedHeader returns `true` if the *Schema pointer was already visited 33 | // otherwise it returns `false` 34 | func (doc *T) isVisitedSchema(s *Schema) bool { 35 | if _, ok := doc.visited.schema[s]; ok { 36 | return true 37 | } 38 | 39 | doc.visited.schema[s] = struct{}{} 40 | return false 41 | } 42 | -------------------------------------------------------------------------------- /openapi3/xml.go: -------------------------------------------------------------------------------- 1 | package openapi3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // XML is specified by OpenAPI/Swagger standard version 3. 9 | // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object 10 | type XML struct { 11 | Extensions map[string]any `json:"-" yaml:"-"` 12 | Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 13 | 14 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 15 | Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` 16 | Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` 17 | Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"` 18 | Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"` 19 | } 20 | 21 | // MarshalJSON returns the JSON encoding of XML. 22 | func (xml XML) MarshalJSON() ([]byte, error) { 23 | x, err := xml.MarshalYAML() 24 | if err != nil { 25 | return nil, err 26 | } 27 | return json.Marshal(x) 28 | } 29 | 30 | // MarshalYAML returns the YAML encoding of XML. 31 | func (xml XML) MarshalYAML() (any, error) { 32 | m := make(map[string]any, 5+len(xml.Extensions)) 33 | for k, v := range xml.Extensions { 34 | m[k] = v 35 | } 36 | if x := xml.Name; x != "" { 37 | m["name"] = x 38 | } 39 | if x := xml.Namespace; x != "" { 40 | m["namespace"] = x 41 | } 42 | if x := xml.Prefix; x != "" { 43 | m["prefix"] = x 44 | } 45 | if x := xml.Attribute; x { 46 | m["attribute"] = x 47 | } 48 | if x := xml.Wrapped; x { 49 | m["wrapped"] = x 50 | } 51 | return m, nil 52 | } 53 | 54 | // UnmarshalJSON sets XML to a copy of data. 55 | func (xml *XML) UnmarshalJSON(data []byte) error { 56 | type XMLBis XML 57 | var x XMLBis 58 | if err := json.Unmarshal(data, &x); err != nil { 59 | return unmarshalError(err) 60 | } 61 | _ = json.Unmarshal(data, &x.Extensions) 62 | delete(x.Extensions, originKey) 63 | delete(x.Extensions, "name") 64 | delete(x.Extensions, "namespace") 65 | delete(x.Extensions, "prefix") 66 | delete(x.Extensions, "attribute") 67 | delete(x.Extensions, "wrapped") 68 | if len(x.Extensions) == 0 { 69 | x.Extensions = nil 70 | } 71 | *xml = XML(x) 72 | return nil 73 | } 74 | 75 | // Validate returns an error if XML does not comply with the OpenAPI spec. 76 | func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error { 77 | ctx = WithValidationOptions(ctx, opts...) 78 | 79 | return validateExtensions(ctx, xml.Extensions) 80 | } 81 | -------------------------------------------------------------------------------- /openapi3filter/authentication_input.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/getkin/kin-openapi/openapi3" 7 | ) 8 | 9 | type AuthenticationInput struct { 10 | RequestValidationInput *RequestValidationInput 11 | SecuritySchemeName string 12 | SecurityScheme *openapi3.SecurityScheme 13 | Scopes []string 14 | } 15 | 16 | func (input *AuthenticationInput) NewError(err error) error { 17 | if err == nil { 18 | if len(input.Scopes) == 0 { 19 | err = fmt.Errorf("security requirement %q failed", input.SecuritySchemeName) 20 | } else { 21 | err = fmt.Errorf("security requirement %q (scopes: %+v) failed", input.SecuritySchemeName, input.Scopes) 22 | } 23 | } 24 | return &RequestError{ 25 | Input: input.RequestValidationInput, 26 | Reason: "authorization failed", 27 | Err: err, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /openapi3filter/internal.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | func parseMediaType(contentType string) string { 9 | i := strings.IndexByte(contentType, ';') 10 | if i < 0 { 11 | return contentType 12 | } 13 | return contentType[:i] 14 | } 15 | 16 | func isNilValue(value any) bool { 17 | if value == nil { 18 | return true 19 | } 20 | switch reflect.TypeOf(value).Kind() { 21 | case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: 22 | return reflect.ValueOf(value).IsNil() 23 | } 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /openapi3filter/issue624_test.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/getkin/kin-openapi/openapi3" 10 | "github.com/getkin/kin-openapi/routers/gorillamux" 11 | ) 12 | 13 | func TestIssue624(t *testing.T) { 14 | loader := openapi3.NewLoader() 15 | ctx := loader.Context 16 | spec := ` 17 | openapi: 3.0.0 18 | info: 19 | version: 1.0.0 20 | title: Sample API 21 | paths: 22 | /items: 23 | get: 24 | description: Returns a list of stuff 25 | parameters: 26 | - description: "test non object" 27 | explode: true 28 | style: form 29 | in: query 30 | name: test 31 | required: false 32 | content: 33 | application/json: 34 | schema: 35 | anyOf: 36 | - type: string 37 | - type: integer 38 | responses: 39 | '200': 40 | description: Successful response 41 | `[1:] 42 | 43 | doc, err := loader.LoadFromData([]byte(spec)) 44 | require.NoError(t, err) 45 | 46 | err = doc.Validate(ctx) 47 | require.NoError(t, err) 48 | 49 | router, err := gorillamux.NewRouter(doc) 50 | require.NoError(t, err) 51 | 52 | for _, testcase := range []string{`test1`, `test[1`} { 53 | t.Run(testcase, func(t *testing.T) { 54 | httpReq, err := http.NewRequest(http.MethodGet, `/items?test=`+testcase, nil) 55 | require.NoError(t, err) 56 | 57 | route, pathParams, err := router.FindRoute(httpReq) 58 | require.NoError(t, err) 59 | 60 | requestValidationInput := &RequestValidationInput{ 61 | Request: httpReq, 62 | PathParams: pathParams, 63 | Route: route, 64 | } 65 | err = ValidateRequest(ctx, requestValidationInput) 66 | require.NoError(t, err) 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /openapi3filter/issue707_test.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/getkin/kin-openapi/openapi3" 11 | "github.com/getkin/kin-openapi/routers/gorillamux" 12 | ) 13 | 14 | func TestIssue707(t *testing.T) { 15 | loader := openapi3.NewLoader() 16 | ctx := loader.Context 17 | spec := ` 18 | openapi: 3.0.0 19 | info: 20 | version: 1.0.0 21 | title: Sample API 22 | paths: 23 | /items: 24 | get: 25 | description: Returns a list of stuff 26 | parameters: 27 | - description: parameter with a default value 28 | explode: true 29 | in: query 30 | name: param-with-default 31 | schema: 32 | default: 124 33 | type: integer 34 | required: false 35 | responses: 36 | '200': 37 | description: Successful response 38 | `[1:] 39 | 40 | doc, err := loader.LoadFromData([]byte(spec)) 41 | require.NoError(t, err) 42 | 43 | err = doc.Validate(ctx) 44 | require.NoError(t, err) 45 | 46 | router, err := gorillamux.NewRouter(doc) 47 | require.NoError(t, err) 48 | 49 | tests := []struct { 50 | name string 51 | options *Options 52 | expectedQuery string 53 | }{ 54 | { 55 | name: "no defaults are added to requests parameters", 56 | options: &Options{ 57 | SkipSettingDefaults: true, 58 | }, 59 | expectedQuery: "", 60 | }, 61 | 62 | { 63 | name: "defaults are added to requests", 64 | expectedQuery: "param-with-default=124", 65 | }, 66 | } 67 | 68 | for _, testcase := range tests { 69 | t.Run(testcase.name, func(t *testing.T) { 70 | httpReq, err := http.NewRequest(http.MethodGet, "/items", strings.NewReader("")) 71 | require.NoError(t, err) 72 | 73 | route, pathParams, err := router.FindRoute(httpReq) 74 | require.NoError(t, err) 75 | 76 | requestValidationInput := &RequestValidationInput{ 77 | Request: httpReq, 78 | PathParams: pathParams, 79 | Route: route, 80 | Options: testcase.options, 81 | } 82 | err = ValidateRequest(ctx, requestValidationInput) 83 | require.NoError(t, err) 84 | 85 | require.NoError(t, err) 86 | require.Equal(t, testcase.expectedQuery, 87 | httpReq.URL.RawQuery, "default value must not be included") 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /openapi3filter/options.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import "github.com/getkin/kin-openapi/openapi3" 4 | 5 | // Options used by ValidateRequest and ValidateResponse 6 | type Options struct { 7 | // Set ExcludeRequestBody so ValidateRequest skips request body validation 8 | ExcludeRequestBody bool 9 | 10 | // Set ExcludeRequestQueryParams so ValidateRequest skips request query params validation 11 | ExcludeRequestQueryParams bool 12 | 13 | // Set ExcludeResponseBody so ValidateResponse skips response body validation 14 | ExcludeResponseBody bool 15 | 16 | // Set ExcludeReadOnlyValidations so ValidateRequest skips read-only validations 17 | ExcludeReadOnlyValidations bool 18 | 19 | // Set ExcludeWriteOnlyValidations so ValidateResponse skips write-only validations 20 | ExcludeWriteOnlyValidations bool 21 | 22 | // Set IncludeResponseStatus so ValidateResponse fails on response 23 | // status not defined in OpenAPI spec 24 | IncludeResponseStatus bool 25 | 26 | MultiError bool 27 | 28 | // Set RegexCompiler to override the regex implementation 29 | RegexCompiler openapi3.RegexCompilerFunc 30 | 31 | // A document with security schemes defined will not pass validation 32 | // unless an AuthenticationFunc is defined. 33 | // See NoopAuthenticationFunc 34 | AuthenticationFunc AuthenticationFunc 35 | 36 | // Indicates whether default values are set in the 37 | // request. If true, then they are not set 38 | SkipSettingDefaults bool 39 | 40 | customSchemaErrorFunc CustomSchemaErrorFunc 41 | } 42 | 43 | // CustomSchemaErrorFunc allows for custom the schema error message. 44 | type CustomSchemaErrorFunc func(err *openapi3.SchemaError) string 45 | 46 | // WithCustomSchemaErrorFunc sets a function to override the schema error message. 47 | // If the passed function returns an empty string, it returns to the previous Error() implementation. 48 | func (o *Options) WithCustomSchemaErrorFunc(f CustomSchemaErrorFunc) { 49 | o.customSchemaErrorFunc = f 50 | } 51 | -------------------------------------------------------------------------------- /openapi3filter/options_test.go: -------------------------------------------------------------------------------- 1 | package openapi3filter_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/getkin/kin-openapi/openapi3" 10 | "github.com/getkin/kin-openapi/openapi3filter" 11 | "github.com/getkin/kin-openapi/routers/gorillamux" 12 | ) 13 | 14 | func ExampleOptions_WithCustomSchemaErrorFunc() { 15 | const spec = ` 16 | openapi: 3.0.0 17 | info: 18 | title: 'Validator' 19 | version: 0.0.1 20 | paths: 21 | /some: 22 | post: 23 | requestBody: 24 | required: true 25 | content: 26 | application/json: 27 | schema: 28 | type: object 29 | properties: 30 | field: 31 | title: Some field 32 | type: integer 33 | responses: 34 | '200': 35 | description: Created 36 | ` 37 | 38 | loader := openapi3.NewLoader() 39 | doc, err := loader.LoadFromData([]byte(spec)) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | if err = doc.Validate(loader.Context); err != nil { 45 | panic(err) 46 | } 47 | 48 | router, err := gorillamux.NewRouter(doc) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | opts := &openapi3filter.Options{} 54 | 55 | opts.WithCustomSchemaErrorFunc(func(err *openapi3.SchemaError) string { 56 | return fmt.Sprintf(`field "%s" must be an integer`, err.Schema.Title) 57 | }) 58 | 59 | req, err := http.NewRequest(http.MethodPost, "/some", strings.NewReader(`{"field":"not integer"}`)) 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | req.Header.Add("Content-Type", "application/json") 65 | 66 | route, pathParams, err := router.FindRoute(req) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | validationInput := &openapi3filter.RequestValidationInput{ 72 | Request: req, 73 | PathParams: pathParams, 74 | Route: route, 75 | Options: opts, 76 | } 77 | err = openapi3filter.ValidateRequest(context.Background(), validationInput) 78 | 79 | fmt.Println(err.Error()) 80 | 81 | // Output: request body has an error: doesn't match schema: field "Some field" must be an integer 82 | } 83 | -------------------------------------------------------------------------------- /openapi3filter/req_resp_encoder.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | func encodeBody(body any, mediaType string) ([]byte, error) { 10 | if encoder := RegisteredBodyEncoder(mediaType); encoder != nil { 11 | return encoder(body) 12 | } 13 | return nil, &ParseError{ 14 | Kind: KindUnsupportedFormat, 15 | Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType), 16 | } 17 | } 18 | 19 | // BodyEncoder really is an (encoding/json).Marshaler 20 | type BodyEncoder func(body any) ([]byte, error) 21 | 22 | var bodyEncodersM sync.RWMutex 23 | var bodyEncoders = map[string]BodyEncoder{ 24 | "application/json": json.Marshal, 25 | } 26 | 27 | // RegisterBodyEncoder enables package-wide decoding of contentType values 28 | func RegisterBodyEncoder(contentType string, encoder BodyEncoder) { 29 | if contentType == "" { 30 | panic("contentType is empty") 31 | } 32 | if encoder == nil { 33 | panic("encoder is not defined") 34 | } 35 | bodyEncodersM.Lock() 36 | bodyEncoders[contentType] = encoder 37 | bodyEncodersM.Unlock() 38 | } 39 | 40 | // UnregisterBodyEncoder disables package-wide decoding of contentType values 41 | func UnregisterBodyEncoder(contentType string) { 42 | if contentType == "" { 43 | panic("contentType is empty") 44 | } 45 | bodyEncodersM.Lock() 46 | delete(bodyEncoders, contentType) 47 | bodyEncodersM.Unlock() 48 | } 49 | 50 | // RegisteredBodyEncoder returns the registered body encoder for the given content type. 51 | // 52 | // If no encoder was registered for the given content type, nil is returned. 53 | func RegisteredBodyEncoder(contentType string) BodyEncoder { 54 | bodyEncodersM.RLock() 55 | mayBE := bodyEncoders[contentType] 56 | bodyEncodersM.RUnlock() 57 | return mayBE 58 | } 59 | -------------------------------------------------------------------------------- /openapi3filter/req_resp_encoder_test.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestRegisterAndUnregisterBodyEncoder(t *testing.T) { 12 | var encoder BodyEncoder 13 | encoder = func(body any) (data []byte, err error) { 14 | return []byte(strings.Join(body.([]string), ",")), nil 15 | } 16 | const contentType = "text/csv" 17 | 18 | originalEncoder := RegisteredBodyEncoder(contentType) 19 | require.Nil(t, originalEncoder) 20 | 21 | RegisterBodyEncoder(contentType, encoder) 22 | require.Equal(t, fmt.Sprint(encoder), fmt.Sprint(RegisteredBodyEncoder(contentType))) 23 | 24 | body := []string{"foo", "bar"} 25 | got, err := encodeBody(body, contentType) 26 | 27 | require.NoError(t, err) 28 | require.Equal(t, []byte("foo,bar"), got) 29 | 30 | UnregisterBodyEncoder(contentType) 31 | 32 | originalEncoder = RegisteredBodyEncoder(contentType) 33 | require.Nil(t, originalEncoder) 34 | 35 | _, err = encodeBody(body, contentType) 36 | require.Equal(t, &ParseError{ 37 | Kind: KindUnsupportedFormat, 38 | Reason: prefixUnsupportedCT + ` "text/csv"`, 39 | }, err) 40 | } 41 | -------------------------------------------------------------------------------- /openapi3filter/validate_request_input.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | 7 | "github.com/getkin/kin-openapi/openapi3" 8 | "github.com/getkin/kin-openapi/routers" 9 | ) 10 | 11 | // A ContentParameterDecoder takes a parameter definition from the OpenAPI spec, 12 | // and the value which we received for it. It is expected to return the 13 | // value unmarshaled into an interface which can be traversed for 14 | // validation, it should also return the schema to be used for validating the 15 | // object, since there can be more than one in the content spec. 16 | // 17 | // If a query parameter appears multiple times, values[] will have more 18 | // than one value, but for all other parameter types it should have just 19 | // one. 20 | type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (any, *openapi3.Schema, error) 21 | 22 | type RequestValidationInput struct { 23 | Request *http.Request 24 | PathParams map[string]string 25 | QueryParams url.Values 26 | Route *routers.Route 27 | Options *Options 28 | ParamDecoder ContentParameterDecoder 29 | } 30 | 31 | func (input *RequestValidationInput) GetQueryParams() url.Values { 32 | q := input.QueryParams 33 | if q == nil { 34 | q = input.Request.URL.Query() 35 | input.QueryParams = q 36 | } 37 | return q 38 | } 39 | -------------------------------------------------------------------------------- /openapi3filter/validate_response_input.go: -------------------------------------------------------------------------------- 1 | package openapi3filter 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | type ResponseValidationInput struct { 10 | RequestValidationInput *RequestValidationInput 11 | Status int 12 | Header http.Header 13 | Body io.ReadCloser 14 | Options *Options 15 | } 16 | 17 | func (input *ResponseValidationInput) SetBodyBytes(value []byte) *ResponseValidationInput { 18 | input.Body = io.NopCloser(bytes.NewReader(value)) 19 | return input 20 | } 21 | 22 | var JSONPrefixes = []string{ 23 | ")]}',\n", 24 | } 25 | 26 | // TrimJSONPrefix trims one of the possible prefixes 27 | func TrimJSONPrefix(data []byte) []byte { 28 | search: 29 | for _, prefix := range JSONPrefixes { 30 | if len(data) < len(prefix) { 31 | continue 32 | } 33 | for i, b := range data[:len(prefix)] { 34 | if b != prefix[i] { 35 | continue search 36 | } 37 | } 38 | return data[len(prefix):] 39 | } 40 | return data 41 | } 42 | -------------------------------------------------------------------------------- /openapi3gen/internal/subpkg/sub_type.go: -------------------------------------------------------------------------------- 1 | package subpkg 2 | 3 | type Child struct { 4 | Name string `yaml:"name"` 5 | } 6 | -------------------------------------------------------------------------------- /openapi3gen/type_info.go: -------------------------------------------------------------------------------- 1 | package openapi3gen 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | typeInfos = map[reflect.Type]*theTypeInfo{} 11 | typeInfosMutex sync.RWMutex 12 | ) 13 | 14 | // theTypeInfo contains information about JSON serialization of a type 15 | type theTypeInfo struct { 16 | Type reflect.Type 17 | Fields []theFieldInfo 18 | } 19 | 20 | // getTypeInfo returns theTypeInfo for the given type. 21 | func getTypeInfo(t reflect.Type) *theTypeInfo { 22 | for t.Kind() == reflect.Ptr { 23 | t = t.Elem() 24 | } 25 | typeInfosMutex.RLock() 26 | typeInfo, exists := typeInfos[t] 27 | typeInfosMutex.RUnlock() 28 | if exists { 29 | return typeInfo 30 | } 31 | if t.Kind() != reflect.Struct { 32 | typeInfo = &theTypeInfo{ 33 | Type: t, 34 | } 35 | } else { 36 | // Allocate 37 | typeInfo = &theTypeInfo{ 38 | Type: t, 39 | Fields: make([]theFieldInfo, 0, 16), 40 | } 41 | 42 | // Add fields 43 | typeInfo.Fields = appendFields(nil, nil, t) 44 | 45 | // Sort fields 46 | sort.Sort(sortableFieldInfos(typeInfo.Fields)) 47 | } 48 | 49 | // Publish 50 | typeInfosMutex.Lock() 51 | typeInfos[t] = typeInfo 52 | typeInfosMutex.Unlock() 53 | return typeInfo 54 | } 55 | -------------------------------------------------------------------------------- /routers/gorillamux/example_test.go: -------------------------------------------------------------------------------- 1 | package gorillamux_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/getkin/kin-openapi/openapi3" 9 | "github.com/getkin/kin-openapi/openapi3filter" 10 | "github.com/getkin/kin-openapi/routers/gorillamux" 11 | ) 12 | 13 | func Example() { 14 | ctx := context.Background() 15 | loader := &openapi3.Loader{Context: ctx, IsExternalRefsAllowed: true} 16 | doc, err := loader.LoadFromFile("../../openapi3/testdata/pathref.openapi.yml") 17 | if err != nil { 18 | panic(err) 19 | } 20 | if err = doc.Validate(ctx); err != nil { 21 | panic(err) 22 | } 23 | router, err := gorillamux.NewRouter(doc) 24 | if err != nil { 25 | panic(err) 26 | } 27 | httpReq, err := http.NewRequest(http.MethodGet, "/test", nil) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | route, pathParams, err := router.FindRoute(httpReq) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | requestValidationInput := &openapi3filter.RequestValidationInput{ 38 | Request: httpReq, 39 | PathParams: pathParams, 40 | Route: route, 41 | } 42 | if err := openapi3filter.ValidateRequest(ctx, requestValidationInput); err != nil { 43 | panic(err) 44 | } 45 | 46 | responseValidationInput := &openapi3filter.ResponseValidationInput{ 47 | RequestValidationInput: requestValidationInput, 48 | Status: 200, 49 | Header: http.Header{"Content-Type": []string{"application/json"}}, 50 | } 51 | responseValidationInput.SetBodyBytes([]byte(`{}`)) 52 | 53 | err = openapi3filter.ValidateResponse(ctx, responseValidationInput) 54 | fmt.Println(err) 55 | // Output: 56 | // response body doesn't match schema pathref.openapi.yml#/components/schemas/TestSchema: value must be a string 57 | // Schema: 58 | // { 59 | // "type": "string" 60 | // } 61 | // 62 | // Value: 63 | // {} 64 | } 65 | -------------------------------------------------------------------------------- /routers/legacy/issue444_test.go: -------------------------------------------------------------------------------- 1 | package legacy_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/getkin/kin-openapi/openapi3" 12 | "github.com/getkin/kin-openapi/openapi3filter" 13 | legacyrouter "github.com/getkin/kin-openapi/routers/legacy" 14 | ) 15 | 16 | func TestIssue444(t *testing.T) { 17 | loader := openapi3.NewLoader() 18 | oas, err := loader.LoadFromData([]byte(` 19 | openapi: '3.0.0' 20 | info: 21 | title: API 22 | version: 1.0.0 23 | paths: 24 | '/path': 25 | post: 26 | requestBody: 27 | required: true 28 | content: 29 | application/x-yaml: 30 | schema: 31 | type: object 32 | responses: 33 | '200': 34 | description: x 35 | content: 36 | application/json: 37 | schema: 38 | type: string 39 | `)) 40 | require.NoError(t, err) 41 | router, err := legacyrouter.NewRouter(oas) 42 | require.NoError(t, err) 43 | 44 | r := httptest.NewRequest("POST", "/path", bytes.NewReader([]byte(` 45 | foo: bar 46 | `))) 47 | r.Header.Set("Content-Type", "application/x-yaml") 48 | 49 | openapi3.SchemaErrorDetailsDisabled = true 50 | route, pathParams, err := router.FindRoute(r) 51 | require.NoError(t, err) 52 | reqValidationInput := &openapi3filter.RequestValidationInput{ 53 | Request: r, 54 | PathParams: pathParams, 55 | Route: route, 56 | } 57 | err = openapi3filter.ValidateRequest(context.Background(), reqValidationInput) 58 | require.NoError(t, err) 59 | } 60 | -------------------------------------------------------------------------------- /routers/types.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/getkin/kin-openapi/openapi3" 7 | ) 8 | 9 | // Router helps link http.Request.s and an OpenAPIv3 spec 10 | type Router interface { 11 | // FindRoute matches an HTTP request with the operation it resolves to. 12 | // Hosts are matched from the OpenAPIv3 servers key. 13 | // 14 | // If you experience ErrPathNotFound and have localhost hosts specified as your servers, 15 | // turning these server URLs as relative (leaving only the path) should resolve this. 16 | // 17 | // See openapi3filter for example uses with request and response validation. 18 | FindRoute(req *http.Request) (route *Route, pathParams map[string]string, err error) 19 | } 20 | 21 | // Route describes the operation an http.Request can match 22 | type Route struct { 23 | Spec *openapi3.T 24 | Server *openapi3.Server 25 | Path string 26 | PathItem *openapi3.PathItem 27 | Method string 28 | Operation *openapi3.Operation 29 | } 30 | 31 | // ErrPathNotFound is returned when no route match is found 32 | var ErrPathNotFound error = &RouteError{"no matching operation was found"} 33 | 34 | // ErrMethodNotAllowed is returned when no method of the matched route matches 35 | var ErrMethodNotAllowed error = &RouteError{"method not allowed"} 36 | 37 | // RouteError describes Router errors 38 | type RouteError struct { 39 | Reason string 40 | } 41 | 42 | func (e *RouteError) Error() string { return e.Reason } 43 | --------------------------------------------------------------------------------