├── testdata ├── hide_operation_3.yaml ├── hide_operation_31.yaml ├── spec_information_31.yaml ├── spec_information_3.yaml ├── server_variables_31.yaml ├── server_variables_3.yaml ├── custom_type_mapping_3.yaml ├── custom_type_mapping_31.yaml ├── custom_path_parser_3.yaml ├── custom_path_parser_31.yaml ├── all_reflector_options_3.yaml ├── custom_parameter_mapping_3.yaml ├── custom_parameter_mapping_31.yaml ├── all_reflector_options_31.yaml ├── generic_response_3.yaml ├── generic_response_31.yaml ├── basic_data_types_3.yaml ├── basic_data_types_31.yaml ├── mux_route_3.yaml ├── mux_route_31.yaml ├── all_operation_options_3.yaml ├── basic_data_types_pointers_3.yaml ├── all_operation_options_31.yaml ├── basic_data_types_pointers_31.yaml ├── all_methods_3.yaml ├── all_methods_31.yaml ├── group_routes_3.yaml └── group_routes_31.yaml ├── option ├── doc.go ├── server.go ├── content.go ├── group.go ├── security.go ├── content_test.go ├── reflector_test.go ├── operation.go └── group_test.go ├── adapter ├── chiopenapi │ ├── internal │ │ └── constant │ │ │ └── constant.go │ ├── route.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ ├── main.go │ │ │ └── go.sum │ ├── go.mod │ ├── types.go │ └── go.sum ├── ginopenapi │ ├── internal │ │ └── constant │ │ │ └── constant.go │ ├── route.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ └── main.go │ ├── go.mod │ └── types.go ├── muxopenapi │ ├── internal │ │ └── constant │ │ │ └── constant.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ ├── main.go │ │ │ └── go.sum │ ├── go.mod │ ├── route.go │ └── go.sum ├── echoopenapi │ ├── internal │ │ └── constant │ │ │ └── constant.go │ ├── route.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ └── main.go │ ├── go.mod │ └── types.go ├── fiberopenapi │ ├── internal │ │ └── constant │ │ │ └── constant.go │ ├── route.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ └── main.go │ ├── go.mod │ └── types.go ├── httpopenapi │ ├── internal │ │ ├── constant │ │ │ └── constant.go │ │ └── parser │ │ │ ├── parser.go │ │ │ └── parser_test.go │ ├── route.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ ├── main.go │ │ │ └── go.sum │ ├── go.mod │ ├── types.go │ └── go.sum ├── httprouteropenapi │ ├── internal │ │ └── constant │ │ │ └── constant.go │ ├── route.go │ ├── examples │ │ └── basic │ │ │ ├── go.mod │ │ │ ├── main.go │ │ │ └── go.sum │ ├── go.mod │ ├── types.go │ └── go.sum └── README.md ├── examples ├── petstore │ ├── README.md │ ├── go.mod │ ├── router.go │ ├── dto.go │ └── go.sum └── basic │ ├── go.mod │ ├── main.go │ ├── openapi.yaml │ └── go.sum ├── go.work ├── go.mod ├── .gitignore ├── lefthook.yml ├── pkg ├── parser │ ├── colon_param_parser.go │ └── colon_param_parser_test.go ├── testutil │ ├── yaml.go │ └── yaml_test.go ├── util │ └── util.go ├── mapper │ ├── specui.go │ └── specui_test.go └── dto │ └── pet.go ├── LICENSE ├── internal ├── errs │ ├── error.go │ └── error_test.go └── debuglog │ └── debuglog.go ├── reflector.go ├── jsonschema.go ├── .github └── workflows │ └── ci.yml ├── go.sum └── types.go /testdata/hide_operation_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Hide Operation 4 | title: 'API Doc: Hide Operation' 5 | version: 1.0.0 6 | paths: {} 7 | -------------------------------------------------------------------------------- /testdata/hide_operation_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Hide Operation 4 | title: 'API Doc: Hide Operation' 5 | version: 1.0.0 6 | paths: {} 7 | -------------------------------------------------------------------------------- /option/doc.go: -------------------------------------------------------------------------------- 1 | // Package option provides functional options for configuring OpenAPI generation, 2 | // including server setup, group settings, operation options, and reflector behavior. 3 | package option 4 | -------------------------------------------------------------------------------- /adapter/chiopenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "Chi OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for Chi applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/ginopenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "Gin OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for Gin applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/muxopenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "Mux OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for Mux applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/echoopenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "Echo OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for Echo applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/fiberopenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "Fiber OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for Fiber applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/httpopenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "HTTP OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for HTTP applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DefaultTitle = "HTTP Router OpenAPI" 5 | DefaultDescription = "OpenAPI documentation for HTTP Router applications" 6 | DefaultVersion = "1.0.0" 7 | ) 8 | -------------------------------------------------------------------------------- /adapter/chiopenapi/route.go: -------------------------------------------------------------------------------- 1 | package chiopenapi 2 | 3 | import ( 4 | "github.com/oaswrap/spec" 5 | "github.com/oaswrap/spec/option" 6 | ) 7 | 8 | type route struct { 9 | specRoute spec.Route 10 | } 11 | 12 | var _ Route = &route{} 13 | 14 | func (r *route) With(opts ...option.OperationOption) Route { 15 | if r.specRoute == nil { 16 | return nil 17 | } 18 | r.specRoute.With(opts...) 19 | return r 20 | } 21 | -------------------------------------------------------------------------------- /adapter/httpopenapi/route.go: -------------------------------------------------------------------------------- 1 | package httpopenapi 2 | 3 | import ( 4 | "github.com/oaswrap/spec" 5 | "github.com/oaswrap/spec/option" 6 | ) 7 | 8 | type route struct { 9 | specRoute spec.Route 10 | } 11 | 12 | var _ Route = (*route)(nil) 13 | 14 | func (r *route) With(opts ...option.OperationOption) Route { 15 | if r.specRoute == nil { 16 | return r 17 | } 18 | r.specRoute.With(opts...) 19 | return r 20 | } 21 | -------------------------------------------------------------------------------- /examples/petstore/README.md: -------------------------------------------------------------------------------- 1 | ## Petstore API Example 2 | 3 | This example demonstrates how to use the OASWrap library to create an OpenAPI specification for a simple Petstore API. The API includes endpoints for managing pets, including adding, updating, and retrieving pet information. 4 | 5 | **[View the generated spec](https://rest.wiki/?https://raw.githubusercontent.com/oaswrap/spec/main/examples/petstore/openapi.yaml)** on Rest.Wiki -------------------------------------------------------------------------------- /adapter/httprouteropenapi/route.go: -------------------------------------------------------------------------------- 1 | package httprouteropenapi 2 | 3 | import ( 4 | "github.com/oaswrap/spec" 5 | "github.com/oaswrap/spec/option" 6 | ) 7 | 8 | type route struct { 9 | specRoute spec.Route 10 | } 11 | 12 | var _ Route = (*route)(nil) 13 | 14 | func (r *route) With(opts ...option.OperationOption) Route { 15 | if r.specRoute == nil { 16 | return r 17 | } 18 | r.specRoute.With(opts...) 19 | return r 20 | } 21 | -------------------------------------------------------------------------------- /testdata/spec_information_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | contact: 4 | email: support@example.com 5 | name: Support Team 6 | url: https://support.example.com 7 | description: This is the API documentation for Spec Information 8 | license: 9 | name: MIT License 10 | url: https://opensource.org/licenses/MIT 11 | title: 'API Doc: Spec Information' 12 | version: 1.0.0 13 | externalDocs: 14 | description: API Documentation 15 | url: https://docs.example.com 16 | -------------------------------------------------------------------------------- /testdata/spec_information_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | contact: 4 | email: support@example.com 5 | name: Support Team 6 | url: https://support.example.com 7 | description: This is the API documentation for Spec Information 8 | license: 9 | name: MIT License 10 | url: https://opensource.org/licenses/MIT 11 | title: 'API Doc: Spec Information' 12 | version: 1.0.0 13 | externalDocs: 14 | description: API Documentation 15 | url: https://docs.example.com 16 | paths: {} 17 | -------------------------------------------------------------------------------- /examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/examples/basic 2 | 3 | go 1.21 4 | 5 | require github.com/oaswrap/spec v0.3.6 6 | 7 | require ( 8 | github.com/kr/text v0.1.0 // indirect 9 | github.com/oaswrap/spec-ui v0.1.4 // indirect 10 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 11 | github.com/swaggest/openapi-go v0.2.60 // indirect 12 | github.com/swaggest/refl v1.4.0 // indirect 13 | gopkg.in/yaml.v2 v2.4.0 // indirect 14 | ) 15 | 16 | replace github.com/oaswrap/spec => ../.. 17 | -------------------------------------------------------------------------------- /examples/petstore/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/examples/petstore 2 | 3 | go 1.21 4 | 5 | require github.com/oaswrap/spec v0.3.6 6 | 7 | require ( 8 | github.com/kr/text v0.1.0 // indirect 9 | github.com/oaswrap/spec-ui v0.1.4 // indirect 10 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 11 | github.com/swaggest/openapi-go v0.2.60 // indirect 12 | github.com/swaggest/refl v1.4.0 // indirect 13 | gopkg.in/yaml.v2 v2.4.0 // indirect 14 | ) 15 | 16 | replace github.com/oaswrap/spec => ../.. 17 | -------------------------------------------------------------------------------- /adapter/ginopenapi/route.go: -------------------------------------------------------------------------------- 1 | package ginopenapi 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/oaswrap/spec" 6 | "github.com/oaswrap/spec/option" 7 | ) 8 | 9 | type route struct { 10 | ginRoute gin.IRoutes 11 | specRoute spec.Route 12 | } 13 | 14 | var _ Route = &route{} 15 | 16 | // With applies the specified options to the route. 17 | func (r *route) With(opts ...option.OperationOption) Route { 18 | if r.specRoute == nil { 19 | return nil 20 | } 21 | r.specRoute.With(opts...) 22 | return r 23 | } 24 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.23.0 2 | 3 | use ( 4 | . 5 | ./adapter/chiopenapi 6 | ./adapter/chiopenapi/examples/basic 7 | ./adapter/echoopenapi 8 | ./adapter/echoopenapi/examples/basic 9 | ./adapter/fiberopenapi 10 | ./adapter/fiberopenapi/examples/basic 11 | ./adapter/ginopenapi 12 | ./adapter/ginopenapi/examples/basic 13 | ./adapter/httpopenapi 14 | ./adapter/httpopenapi/examples/basic 15 | ./adapter/httprouteropenapi 16 | ./adapter/httprouteropenapi/examples/basic 17 | ./adapter/muxopenapi 18 | ./adapter/muxopenapi/examples/basic 19 | ./examples/basic 20 | ./examples/petstore 21 | ) 22 | -------------------------------------------------------------------------------- /adapter/httpopenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/httpopenapi/examples/basic 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/oaswrap/spec v0.3.6 7 | github.com/oaswrap/spec/adapter/httpopenapi v0.0.0 8 | ) 9 | 10 | require ( 11 | github.com/oaswrap/spec-ui v0.1.4 // indirect 12 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 13 | github.com/swaggest/openapi-go v0.2.60 // indirect 14 | github.com/swaggest/refl v1.4.0 // indirect 15 | gopkg.in/yaml.v2 v2.4.0 // indirect 16 | ) 17 | 18 | replace github.com/oaswrap/spec/adapter/httpopenapi => ../.. 19 | -------------------------------------------------------------------------------- /adapter/muxopenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/muxopenapi/examples/basic 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.1 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec/adapter/muxopenapi v0.3.1 9 | ) 10 | 11 | require ( 12 | github.com/oaswrap/spec-ui v0.1.4 // indirect 13 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 14 | github.com/swaggest/openapi-go v0.2.60 // indirect 15 | github.com/swaggest/refl v1.4.0 // indirect 16 | gopkg.in/yaml.v2 v2.4.0 // indirect 17 | ) 18 | 19 | replace github.com/oaswrap/spec/adapter/muxopenapi => ../.. 20 | -------------------------------------------------------------------------------- /adapter/chiopenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/chiopenapi/examples/basic 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.2.2 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec/adapter/chiopenapi v0.0.0 9 | ) 10 | 11 | require ( 12 | github.com/oaswrap/spec-ui v0.1.4 // indirect 13 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 14 | github.com/swaggest/openapi-go v0.2.60 // indirect 15 | github.com/swaggest/refl v1.4.0 // indirect 16 | gopkg.in/yaml.v2 v2.4.0 // indirect 17 | ) 18 | 19 | replace github.com/oaswrap/spec/adapter/chiopenapi => ../.. 20 | -------------------------------------------------------------------------------- /testdata/server_variables_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Server Variables 4 | title: 'API Doc: Server Variables' 5 | version: 1.0.0 6 | servers: 7 | - description: Production Server 8 | url: https://api.example.com/{version} 9 | variables: 10 | version: 11 | default: v1 12 | description: API version 13 | enum: 14 | - v1 15 | - v2 16 | - description: Development Server 17 | url: https://api.example.dev/{version} 18 | variables: 19 | version: 20 | default: v1 21 | description: API version 22 | enum: 23 | - v1 24 | - v2 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/google/go-cmp v0.7.0 7 | github.com/oaswrap/spec-ui v0.1.4 8 | github.com/stretchr/testify v1.11.1 9 | github.com/swaggest/jsonschema-go v0.3.78 10 | github.com/swaggest/openapi-go v0.2.60 11 | gopkg.in/yaml.v3 v3.0.1 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/kr/pretty v0.1.0 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | github.com/swaggest/refl v1.4.0 // indirect 19 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 20 | gopkg.in/yaml.v2 v2.4.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/httprouteropenapi/examples/basic 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/julienschmidt/httprouter v1.3.0 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec/adapter/httprouteropenapi v0.0.0 9 | ) 10 | 11 | require ( 12 | github.com/oaswrap/spec-ui v0.1.4 // indirect 13 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 14 | github.com/swaggest/openapi-go v0.2.60 // indirect 15 | github.com/swaggest/refl v1.4.0 // indirect 16 | gopkg.in/yaml.v2 v2.4.0 // indirect 17 | ) 18 | 19 | replace github.com/oaswrap/spec/adapter/httprouteropenapi => ../.. 20 | -------------------------------------------------------------------------------- /testdata/server_variables_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Server Variables 4 | title: 'API Doc: Server Variables' 5 | version: 1.0.0 6 | servers: 7 | - description: Production Server 8 | url: https://api.example.com/{version} 9 | variables: 10 | version: 11 | default: v1 12 | description: API version 13 | enum: 14 | - v1 15 | - v2 16 | - description: Development Server 17 | url: https://api.example.dev/{version} 18 | variables: 19 | version: 20 | default: v1 21 | description: API version 22 | enum: 23 | - v1 24 | - v2 25 | paths: {} 26 | -------------------------------------------------------------------------------- /adapter/httpopenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/httpopenapi 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/oaswrap/spec v0.3.6 7 | github.com/oaswrap/spec-ui v0.1.4 8 | github.com/stretchr/testify v1.11.1 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/google/go-cmp v0.7.0 // indirect 14 | github.com/kr/text v0.1.0 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 17 | github.com/swaggest/openapi-go v0.2.60 // indirect 18 | github.com/swaggest/refl v1.4.0 // indirect 19 | gopkg.in/yaml.v2 v2.4.0 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /adapter/echoopenapi/route.go: -------------------------------------------------------------------------------- 1 | package echoopenapi 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/oaswrap/spec" 6 | "github.com/oaswrap/spec/option" 7 | ) 8 | 9 | type route struct { 10 | echoRoute *echo.Route 11 | specRoute spec.Route 12 | } 13 | 14 | var _ Route = (*route)(nil) 15 | 16 | func (r *route) Method() string { 17 | return r.echoRoute.Method 18 | } 19 | 20 | func (r *route) Path() string { 21 | return r.echoRoute.Path 22 | } 23 | 24 | func (r *route) Name() string { 25 | return r.echoRoute.Name 26 | } 27 | 28 | func (r *route) With(opts ...option.OperationOption) Route { 29 | if r.specRoute == nil { 30 | return r 31 | } 32 | r.specRoute.With(opts...) 33 | return r 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Code coverage profiles and other test artifacts 15 | *.out 16 | coverage.* 17 | *.coverprofile 18 | profile.cov 19 | junit.xml 20 | coverage/ 21 | 22 | # Dependency directories (remove the comment below to include it) 23 | # vendor/ 24 | 25 | # Go workspace file 26 | # go.work 27 | # go.work.sum 28 | 29 | # env file 30 | .env 31 | 32 | # Editor/IDE 33 | .idea/ 34 | .vscode/ -------------------------------------------------------------------------------- /adapter/muxopenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/muxopenapi 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.1 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec-ui v0.1.4 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/google/go-cmp v0.7.0 // indirect 15 | github.com/kr/text v0.1.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 18 | github.com/swaggest/openapi-go v0.2.60 // indirect 19 | github.com/swaggest/refl v1.4.0 // indirect 20 | gopkg.in/yaml.v2 v2.4.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /adapter/chiopenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/chiopenapi 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.2.2 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec-ui v0.1.4 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/google/go-cmp v0.7.0 // indirect 15 | github.com/kr/text v0.1.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 18 | github.com/swaggest/openapi-go v0.2.60 // indirect 19 | github.com/swaggest/refl v1.4.0 // indirect 20 | gopkg.in/yaml.v2 v2.4.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/httprouteropenapi 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/julienschmidt/httprouter v1.3.0 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec-ui v0.1.4 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/google/go-cmp v0.7.0 // indirect 15 | github.com/kr/text v0.1.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 18 | github.com/swaggest/openapi-go v0.2.60 // indirect 19 | github.com/swaggest/refl v1.4.0 // indirect 20 | gopkg.in/yaml.v2 v2.4.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # Lefthook configuration for Go project 2 | 3 | pre-commit: 4 | parallel: true 5 | commands: 6 | fmt: 7 | glob: "*.go" 8 | run: gofmt -w {staged_files} && git add {staged_files} 9 | 10 | vet: 11 | glob: "*.go" 12 | run: go vet ./... 13 | 14 | lint: 15 | glob: "*.go" 16 | run: make lint 17 | 18 | mod-tidy: 19 | glob: "go.{mod,sum}" 20 | run: make tidy 21 | 22 | commit-msg: 23 | commands: 24 | check-message: 25 | run: | 26 | if ! grep -qE "^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+" {1}; then 27 | echo "Commit message must follow conventional commits format" 28 | echo "Example: feat: add new feature" 29 | exit 1 30 | fi -------------------------------------------------------------------------------- /pkg/parser/colon_param_parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/oaswrap/spec/openapi" 7 | ) 8 | 9 | // ColonParamParser is a parser that converts paths with colon-prefixed parameters 10 | // (e.g., "/users/:id") to OpenAPI-style parameters (e.g., "/users/{id}"). 11 | type ColonParamParser struct { 12 | re *regexp.Regexp 13 | } 14 | 15 | var _ openapi.PathParser = &ColonParamParser{} 16 | 17 | // NewColonParamParser creates a new ColonParamParser instance. 18 | func NewColonParamParser() *ColonParamParser { 19 | return &ColonParamParser{ 20 | re: regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`), 21 | } 22 | } 23 | 24 | // Parse converts a path with colon-prefixed parameters to OpenAPI-style parameters. 25 | func (p *ColonParamParser) Parse(colonParam string) (string, error) { 26 | return p.re.ReplaceAllString(colonParam, "{$1}"), nil 27 | } 28 | -------------------------------------------------------------------------------- /adapter/fiberopenapi/route.go: -------------------------------------------------------------------------------- 1 | package fiberopenapi 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/oaswrap/spec" 6 | "github.com/oaswrap/spec/option" 7 | ) 8 | 9 | // Route represents a single route in the OpenAPI specification. 10 | type Route interface { 11 | // Name sets the name for the route. 12 | Name(name string) Route 13 | // With applies the given options to the route. 14 | With(opts ...option.OperationOption) Route 15 | } 16 | 17 | type route struct { 18 | fr fiber.Router 19 | sr spec.Route 20 | } 21 | 22 | // Name sets the name for the route. 23 | func (r *route) Name(name string) Route { 24 | r.fr.Name(name) 25 | 26 | return r 27 | } 28 | 29 | // With applies the given options to the route. 30 | func (r *route) With(opts ...option.OperationOption) Route { 31 | if r.sr == nil { 32 | return r 33 | } 34 | r.sr.With(opts...) 35 | 36 | return r 37 | } 38 | -------------------------------------------------------------------------------- /pkg/testutil/yaml.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | "github.com/stretchr/testify/assert" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | // YAMLToInterface parses a YAML blob into an interface{}. 13 | // Use it before comparing. 14 | func YAMLToInterface(t *testing.T, data []byte) interface{} { 15 | t.Helper() 16 | var v interface{} 17 | dec := yaml.NewDecoder(bytes.NewReader(data)) 18 | err := dec.Decode(&v) 19 | assert.NoError(t, err) 20 | return v 21 | } 22 | 23 | // EqualYAML asserts that two YAML documents are semantically equal. 24 | // It returns a cmp.Diff if they are not. 25 | func EqualYAML(t *testing.T, want []byte, got []byte) { 26 | wantObj := YAMLToInterface(t, want) 27 | gotObj := YAMLToInterface(t, got) 28 | 29 | if diff := cmp.Diff(wantObj, gotObj); diff != "" { 30 | t.Errorf("YAML mismatch (-want +got):\n%s", diff) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util //nolint:revive // Utility functions 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | ) 7 | 8 | // Optional returns the first value from the provided values or the default value if no values are provided. 9 | func Optional[T any](defaultValue T, value ...T) T { 10 | if len(value) > 0 { 11 | return value[0] 12 | } 13 | return defaultValue 14 | } 15 | 16 | // PtrOf returns a pointer to the provided value. 17 | func PtrOf[T any](value T) *T { 18 | return &value 19 | } 20 | 21 | // JoinURL joins the base URL with the provided segments, ensuring proper formatting. 22 | func JoinURL(base string, segments ...string) string { 23 | base = strings.TrimRight(base, "/") 24 | if len(segments) == 0 { 25 | return base 26 | } 27 | return base + "/" + strings.TrimLeft(JoinPath(segments...), "/") 28 | } 29 | 30 | // JoinPath joins multiple path segments into a single path, ensuring proper formatting. 31 | func JoinPath(paths ...string) string { 32 | return path.Join(paths...) 33 | } 34 | -------------------------------------------------------------------------------- /adapter/echoopenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/echoopenapi/examples/basic 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/labstack/echo/v4 v4.13.4 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec/adapter/echoopenapi v0.0.0 9 | ) 10 | 11 | require ( 12 | github.com/labstack/gommon v0.4.2 // indirect 13 | github.com/mattn/go-colorable v0.1.14 // indirect 14 | github.com/mattn/go-isatty v0.0.20 // indirect 15 | github.com/oaswrap/spec-ui v0.1.4 // indirect 16 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 17 | github.com/swaggest/openapi-go v0.2.60 // indirect 18 | github.com/swaggest/refl v1.4.0 // indirect 19 | github.com/valyala/bytebufferpool v1.0.0 // indirect 20 | github.com/valyala/fasttemplate v1.2.2 // indirect 21 | golang.org/x/crypto v0.38.0 // indirect 22 | golang.org/x/net v0.40.0 // indirect 23 | golang.org/x/sys v0.33.0 // indirect 24 | golang.org/x/text v0.25.0 // indirect 25 | gopkg.in/yaml.v2 v2.4.0 // indirect 26 | ) 27 | 28 | replace github.com/oaswrap/spec/adapter/echoopenapi => ../.. 29 | -------------------------------------------------------------------------------- /pkg/mapper/specui.go: -------------------------------------------------------------------------------- 1 | package mapper 2 | 3 | import ( 4 | "github.com/oaswrap/spec" 5 | specui "github.com/oaswrap/spec-ui" 6 | "github.com/oaswrap/spec-ui/config" 7 | ) 8 | 9 | func SpecUIOpts(gen spec.Generator) []specui.Option { 10 | cfg := gen.Config() 11 | opts := []specui.Option{ 12 | specui.WithTitle(cfg.Title), 13 | specui.WithDocsPath(cfg.DocsPath), 14 | specui.WithSpecPath(cfg.SpecPath), 15 | specui.WithSpecGenerator(gen), 16 | } 17 | if cfg.CacheAge != nil { 18 | opts = append(opts, specui.WithCacheAge(*cfg.CacheAge)) 19 | } 20 | 21 | switch cfg.UIProvider { 22 | case config.ProviderSwaggerUI: 23 | opts = append(opts, specui.WithSwaggerUI(*cfg.SwaggerUIConfig)) 24 | case config.ProviderStoplightElements: 25 | opts = append(opts, specui.WithStoplightElements(*cfg.StoplightElementsConfig)) 26 | case config.ProviderReDoc: 27 | opts = append(opts, specui.WithReDoc(*cfg.ReDocConfig)) 28 | case config.ProviderScalar: 29 | opts = append(opts, specui.WithScalar(*cfg.ScalarConfig)) 30 | case config.ProviderRapiDoc: 31 | opts = append(opts, specui.WithRapiDoc(*cfg.RapiDocConfig)) 32 | } 33 | 34 | return opts 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ahmad Faiz Kamaludin 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 | -------------------------------------------------------------------------------- /option/server.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import "github.com/oaswrap/spec/openapi" 4 | 5 | // ServerOption applies configuration to an OpenAPI server. 6 | type ServerOption func(*openapi.Server) 7 | 8 | // ServerDescription sets the description for an OpenAPI server. 9 | // 10 | // Example: 11 | // 12 | // option.WithServer("https://api.example.com", 13 | // option.ServerDescription("Production server"), 14 | // ) 15 | func ServerDescription(description string) ServerOption { 16 | return func(s *openapi.Server) { 17 | s.Description = &description 18 | } 19 | } 20 | 21 | // ServerVariables sets one or more variables for an OpenAPI server. 22 | // 23 | // Example: 24 | // 25 | // option.WithServer("https://api.example.com/{version}", 26 | // option.ServerVariables(map[string]openapi.ServerVariable{ 27 | // "version": { 28 | // Default: "v1", 29 | // Description: "API version", 30 | // Enum: []string{"v1", "v2"}, 31 | // }, 32 | // }), 33 | // ) 34 | func ServerVariables(variables map[string]openapi.ServerVariable) ServerOption { 35 | return func(s *openapi.Server) { 36 | s.Variables = variables 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /adapter/echoopenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/echoopenapi 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/labstack/echo/v4 v4.13.4 7 | github.com/oaswrap/spec v0.3.6 8 | github.com/oaswrap/spec-ui v0.1.4 9 | github.com/stretchr/testify v1.11.1 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/google/go-cmp v0.7.0 // indirect 15 | github.com/kr/text v0.1.0 // indirect 16 | github.com/labstack/gommon v0.4.2 // indirect 17 | github.com/mattn/go-colorable v0.1.14 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 21 | github.com/swaggest/openapi-go v0.2.60 // indirect 22 | github.com/swaggest/refl v1.4.0 // indirect 23 | github.com/valyala/bytebufferpool v1.0.0 // indirect 24 | github.com/valyala/fasttemplate v1.2.2 // indirect 25 | golang.org/x/crypto v0.38.0 // indirect 26 | golang.org/x/net v0.40.0 // indirect 27 | golang.org/x/sys v0.33.0 // indirect 28 | golang.org/x/text v0.25.0 // indirect 29 | gopkg.in/yaml.v2 v2.4.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /internal/errs/error.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | ) 7 | 8 | // SpecError is a thread-safe error collector for OpenAPI specification errors. 9 | type SpecError struct { 10 | mu sync.Mutex 11 | errors []error 12 | } 13 | 14 | func (se *SpecError) Add(err error) { 15 | se.mu.Lock() 16 | defer se.mu.Unlock() 17 | if err != nil { 18 | se.errors = append(se.errors, err) 19 | } 20 | } 21 | 22 | // Errors returns a slice of collected errors. 23 | func (se *SpecError) Errors() []error { 24 | se.mu.Lock() 25 | defer se.mu.Unlock() 26 | return se.errors 27 | } 28 | 29 | // Error implements the error interface for SpecError. 30 | func (se *SpecError) Error() string { 31 | se.mu.Lock() 32 | defer se.mu.Unlock() 33 | if len(se.errors) == 0 { 34 | return "" 35 | } 36 | var sb strings.Builder 37 | sb.WriteString("Spec errors:\n") 38 | for _, err := range se.errors { 39 | sb.WriteString("- " + err.Error() + "\n") 40 | } 41 | return sb.String() 42 | } 43 | 44 | // HasErrors checks if there are any collected errors. 45 | func (se *SpecError) HasErrors() bool { 46 | se.mu.Lock() 47 | defer se.mu.Unlock() 48 | return len(se.errors) > 0 49 | } 50 | -------------------------------------------------------------------------------- /option/content.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "github.com/oaswrap/spec/openapi" 5 | "github.com/oaswrap/spec/pkg/util" 6 | ) 7 | 8 | // ContentOption is a function that modifies the ContentUnit. 9 | type ContentOption func(cu *openapi.ContentUnit) 10 | 11 | // ContentType sets the content type for the OpenAPI content. 12 | func ContentType(contentType string) ContentOption { 13 | return func(cu *openapi.ContentUnit) { 14 | cu.ContentType = contentType 15 | } 16 | } 17 | 18 | // ContentDescription sets the description for the OpenAPI content. 19 | func ContentDescription(description string) ContentOption { 20 | return func(cu *openapi.ContentUnit) { 21 | cu.Description = description 22 | } 23 | } 24 | 25 | // ContentDefault sets whether this content unit is the default response. 26 | func ContentDefault(isDefault ...bool) ContentOption { 27 | return func(cu *openapi.ContentUnit) { 28 | cu.IsDefault = util.Optional(true, isDefault...) 29 | } 30 | } 31 | 32 | func ContentEncoding(prop, enc string) ContentOption { 33 | return func(cu *openapi.ContentUnit) { 34 | if cu.Encoding == nil { 35 | cu.Encoding = map[string]string{} 36 | } 37 | cu.Encoding[prop] = enc 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /testdata/custom_type_mapping_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Custom Type Mapping 4 | title: 'API Doc: Custom Type Mapping' 5 | version: 1.0.0 6 | paths: 7 | /auth/me: 8 | get: 9 | description: This operation retrieves the authenticated user's profile. 10 | operationId: getUserProfile 11 | responses: 12 | "200": 13 | content: 14 | application/json: 15 | schema: 16 | $ref: '#/components/schemas/SpecTestUser' 17 | description: OK 18 | security: 19 | - bearerAuth: [] 20 | summary: Get User Profile 21 | components: 22 | schemas: 23 | SpecTestUser: 24 | properties: 25 | age: 26 | nullable: true 27 | type: integer 28 | created_at: 29 | format: date-time 30 | type: string 31 | email: 32 | type: string 33 | id: 34 | type: integer 35 | updated_at: 36 | format: date-time 37 | type: string 38 | username: 39 | type: string 40 | type: object 41 | securitySchemes: 42 | bearerAuth: 43 | scheme: Bearer 44 | type: http 45 | -------------------------------------------------------------------------------- /adapter/fiberopenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/fiberopenapi/examples/basic 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.12 6 | 7 | require ( 8 | github.com/gofiber/fiber/v2 v2.52.9 9 | github.com/oaswrap/spec v0.3.6 10 | github.com/oaswrap/spec/adapter/fiberopenapi v0.0.0 11 | ) 12 | 13 | require ( 14 | github.com/andybalholm/brotli v1.1.0 // indirect 15 | github.com/google/uuid v1.6.0 // indirect 16 | github.com/klauspost/compress v1.17.9 // indirect 17 | github.com/mattn/go-colorable v0.1.14 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/mattn/go-runewidth v0.0.16 // indirect 20 | github.com/oaswrap/spec-ui v0.1.4 // indirect 21 | github.com/rivo/uniseg v0.2.0 // indirect 22 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 23 | github.com/swaggest/openapi-go v0.2.60 // indirect 24 | github.com/swaggest/refl v1.4.0 // indirect 25 | github.com/valyala/bytebufferpool v1.0.0 // indirect 26 | github.com/valyala/fasthttp v1.51.0 // indirect 27 | github.com/valyala/tcplisten v1.0.0 // indirect 28 | golang.org/x/sys v0.33.0 // indirect 29 | gopkg.in/yaml.v2 v2.4.0 // indirect 30 | ) 31 | 32 | replace github.com/oaswrap/spec/adapter/fiberopenapi => ../.. 33 | -------------------------------------------------------------------------------- /testdata/custom_type_mapping_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Custom Type Mapping 4 | title: 'API Doc: Custom Type Mapping' 5 | version: 1.0.0 6 | paths: 7 | /auth/me: 8 | get: 9 | description: This operation retrieves the authenticated user's profile. 10 | operationId: getUserProfile 11 | responses: 12 | "200": 13 | content: 14 | application/json: 15 | schema: 16 | $ref: '#/components/schemas/SpecTestUser' 17 | description: OK 18 | security: 19 | - bearerAuth: [] 20 | summary: Get User Profile 21 | components: 22 | schemas: 23 | SpecTestUser: 24 | properties: 25 | age: 26 | type: 27 | - "null" 28 | - integer 29 | created_at: 30 | format: date-time 31 | type: string 32 | email: 33 | type: string 34 | id: 35 | type: integer 36 | updated_at: 37 | format: date-time 38 | type: string 39 | username: 40 | type: string 41 | type: object 42 | securitySchemes: 43 | bearerAuth: 44 | scheme: Bearer 45 | type: http 46 | -------------------------------------------------------------------------------- /adapter/fiberopenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/fiberopenapi 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.12 6 | 7 | require ( 8 | github.com/gofiber/fiber/v2 v2.52.9 9 | github.com/oaswrap/spec v0.3.6 10 | github.com/oaswrap/spec-ui v0.1.4 11 | github.com/stretchr/testify v1.11.1 12 | ) 13 | 14 | require ( 15 | github.com/andybalholm/brotli v1.1.0 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/google/go-cmp v0.7.0 // indirect 18 | github.com/google/uuid v1.6.0 // indirect 19 | github.com/klauspost/compress v1.17.9 // indirect 20 | github.com/kr/text v0.1.0 // indirect 21 | github.com/mattn/go-colorable v0.1.14 // indirect 22 | github.com/mattn/go-isatty v0.0.20 // indirect 23 | github.com/mattn/go-runewidth v0.0.16 // indirect 24 | github.com/pmezard/go-difflib v1.0.0 // indirect 25 | github.com/rivo/uniseg v0.2.0 // indirect 26 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 27 | github.com/swaggest/openapi-go v0.2.60 // indirect 28 | github.com/swaggest/refl v1.4.0 // indirect 29 | github.com/valyala/bytebufferpool v1.0.0 // indirect 30 | github.com/valyala/fasthttp v1.51.0 // indirect 31 | github.com/valyala/tcplisten v1.0.0 // indirect 32 | golang.org/x/sys v0.33.0 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /testdata/custom_path_parser_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Custom Path Parser 4 | title: 'API Doc: Custom Path Parser' 5 | version: 1.0.0 6 | paths: 7 | /user/{id}: 8 | get: 9 | description: This operation retrieves a user by ID. 10 | operationId: getUserById 11 | parameters: 12 | - in: path 13 | name: id 14 | required: true 15 | schema: 16 | type: integer 17 | responses: 18 | "200": 19 | content: 20 | application/json: 21 | schema: 22 | $ref: '#/components/schemas/SpecTestUser' 23 | description: OK 24 | summary: Get User by ID 25 | components: 26 | schemas: 27 | SpecTestNullString: 28 | type: object 29 | SpecTestNullTime: 30 | type: object 31 | SpecTestUser: 32 | properties: 33 | age: 34 | nullable: true 35 | type: integer 36 | created_at: 37 | format: date-time 38 | type: string 39 | email: 40 | $ref: '#/components/schemas/SpecTestNullString' 41 | id: 42 | type: integer 43 | updated_at: 44 | $ref: '#/components/schemas/SpecTestNullTime' 45 | username: 46 | type: string 47 | type: object 48 | -------------------------------------------------------------------------------- /testdata/custom_path_parser_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Custom Path Parser 4 | title: 'API Doc: Custom Path Parser' 5 | version: 1.0.0 6 | paths: 7 | /user/{id}: 8 | get: 9 | description: This operation retrieves a user by ID. 10 | operationId: getUserById 11 | parameters: 12 | - in: path 13 | name: id 14 | required: true 15 | schema: 16 | type: integer 17 | responses: 18 | "200": 19 | content: 20 | application/json: 21 | schema: 22 | $ref: '#/components/schemas/SpecTestUser' 23 | description: OK 24 | summary: Get User by ID 25 | components: 26 | schemas: 27 | SpecTestNullString: 28 | type: object 29 | SpecTestNullTime: 30 | type: object 31 | SpecTestUser: 32 | properties: 33 | age: 34 | type: 35 | - "null" 36 | - integer 37 | created_at: 38 | format: date-time 39 | type: string 40 | email: 41 | $ref: '#/components/schemas/SpecTestNullString' 42 | id: 43 | type: integer 44 | updated_at: 45 | $ref: '#/components/schemas/SpecTestNullTime' 46 | username: 47 | type: string 48 | type: object 49 | -------------------------------------------------------------------------------- /testdata/all_reflector_options_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for All Reflector Options 4 | title: 'API Doc: All Reflector Options' 5 | version: 1.0.0 6 | paths: 7 | /reflector/options: 8 | get: 9 | description: This operation retrieves the OpenAPI reflector options. 10 | operationId: getReflectorOptions 11 | responses: 12 | "200": 13 | content: 14 | application/json: 15 | schema: 16 | properties: 17 | data: 18 | properties: 19 | age: 20 | nullable: true 21 | type: integer 22 | created_at: 23 | format: date-time 24 | type: string 25 | email: 26 | type: string 27 | id: 28 | type: integer 29 | updated_at: 30 | format: date-time 31 | type: string 32 | username: 33 | type: string 34 | type: object 35 | status: 36 | example: 200 37 | type: integer 38 | type: object 39 | description: OK 40 | summary: Get Reflector Options 41 | -------------------------------------------------------------------------------- /testdata/custom_parameter_mapping_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Custom Parameter Mapping 4 | title: 'API Doc: Custom Parameter Mapping' 5 | version: 1.0.0 6 | paths: 7 | /user/{id}: 8 | get: 9 | description: This operation retrieves a user by ID. 10 | operationId: getUserById 11 | parameters: 12 | - in: query 13 | name: extra_param 14 | required: true 15 | schema: 16 | type: string 17 | - in: path 18 | name: id 19 | required: true 20 | schema: 21 | type: integer 22 | responses: 23 | "200": 24 | content: 25 | application/json: 26 | schema: 27 | $ref: '#/components/schemas/SpecTestUser' 28 | description: OK 29 | summary: Get User by ID 30 | components: 31 | schemas: 32 | SpecTestNullString: 33 | type: object 34 | SpecTestNullTime: 35 | type: object 36 | SpecTestUser: 37 | properties: 38 | age: 39 | nullable: true 40 | type: integer 41 | created_at: 42 | format: date-time 43 | type: string 44 | email: 45 | $ref: '#/components/schemas/SpecTestNullString' 46 | id: 47 | type: integer 48 | updated_at: 49 | $ref: '#/components/schemas/SpecTestNullTime' 50 | username: 51 | type: string 52 | type: object 53 | -------------------------------------------------------------------------------- /testdata/custom_parameter_mapping_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Custom Parameter Mapping 4 | title: 'API Doc: Custom Parameter Mapping' 5 | version: 1.0.0 6 | paths: 7 | /user/{id}: 8 | get: 9 | description: This operation retrieves a user by ID. 10 | operationId: getUserById 11 | parameters: 12 | - in: query 13 | name: extra_param 14 | required: true 15 | schema: 16 | type: string 17 | - in: path 18 | name: id 19 | required: true 20 | schema: 21 | type: integer 22 | responses: 23 | "200": 24 | content: 25 | application/json: 26 | schema: 27 | $ref: '#/components/schemas/SpecTestUser' 28 | description: OK 29 | summary: Get User by ID 30 | components: 31 | schemas: 32 | SpecTestNullString: 33 | type: object 34 | SpecTestNullTime: 35 | type: object 36 | SpecTestUser: 37 | properties: 38 | age: 39 | type: 40 | - "null" 41 | - integer 42 | created_at: 43 | format: date-time 44 | type: string 45 | email: 46 | $ref: '#/components/schemas/SpecTestNullString' 47 | id: 48 | type: integer 49 | updated_at: 50 | $ref: '#/components/schemas/SpecTestNullTime' 51 | username: 52 | type: string 53 | type: object 54 | -------------------------------------------------------------------------------- /examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/oaswrap/spec" 7 | "github.com/oaswrap/spec/option" 8 | ) 9 | 10 | func main() { 11 | // Create a new OpenAPI router 12 | r := spec.NewRouter( 13 | option.WithTitle("My API"), 14 | option.WithVersion("1.0.0"), 15 | option.WithServer("https://api.example.com"), 16 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 17 | ) 18 | 19 | // Add routes 20 | v1 := r.Group("/api/v1") 21 | 22 | v1.Post("/login", 23 | option.Summary("User login"), 24 | option.Request(new(LoginRequest)), 25 | option.Response(200, new(LoginResponse)), 26 | ) 27 | 28 | auth := v1.Group("/", option.GroupSecurity("bearerAuth")) 29 | 30 | auth.Get("/users/{id}", 31 | option.Summary("Get user by ID"), 32 | option.Request(new(GetUserRequest)), 33 | option.Response(200, new(User)), 34 | ) 35 | 36 | // Generate OpenAPI spec 37 | if err := r.WriteSchemaTo("openapi.yaml"); err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | log.Println("✅ OpenAPI spec generated at openapi.yaml") 42 | } 43 | 44 | type LoginRequest struct { 45 | Username string `json:"username" required:"true"` 46 | Password string `json:"password" required:"true"` 47 | } 48 | 49 | type LoginResponse struct { 50 | Token string `json:"token"` 51 | } 52 | 53 | type GetUserRequest struct { 54 | ID string `path:"id" required:"true"` 55 | } 56 | 57 | type User struct { 58 | ID string `json:"id"` 59 | Name string `json:"name"` 60 | } 61 | -------------------------------------------------------------------------------- /testdata/all_reflector_options_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for All Reflector Options 4 | title: 'API Doc: All Reflector Options' 5 | version: 1.0.0 6 | paths: 7 | /reflector/options: 8 | get: 9 | description: This operation retrieves the OpenAPI reflector options. 10 | operationId: getReflectorOptions 11 | responses: 12 | "200": 13 | content: 14 | application/json: 15 | schema: 16 | properties: 17 | data: 18 | properties: 19 | age: 20 | type: 21 | - "null" 22 | - integer 23 | created_at: 24 | format: date-time 25 | type: string 26 | email: 27 | type: string 28 | id: 29 | type: integer 30 | updated_at: 31 | format: date-time 32 | type: string 33 | username: 34 | type: string 35 | type: object 36 | status: 37 | examples: 38 | - 200 39 | type: integer 40 | type: 41 | - object 42 | - "null" 43 | description: OK 44 | summary: Get Reflector Options 45 | -------------------------------------------------------------------------------- /testdata/generic_response_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Generic Response 4 | title: 'API Doc: Generic Response' 5 | version: 1.0.0 6 | tags: 7 | - description: Operations related to user authentication 8 | name: Authentication 9 | paths: 10 | /login: 11 | post: 12 | description: This operation allows users to log in. 13 | operationId: login 14 | requestBody: 15 | content: 16 | application/json: 17 | schema: 18 | $ref: '#/components/schemas/SpecTestLoginRequest' 19 | responses: 20 | "200": 21 | content: 22 | application/json: 23 | schema: 24 | $ref: '#/components/schemas/SpecTestResponseGithubComOaswrapSpecTestToken' 25 | description: OK 26 | summary: User Login 27 | tags: 28 | - Authentication 29 | components: 30 | schemas: 31 | SpecTestLoginRequest: 32 | properties: 33 | password: 34 | example: password123 35 | type: string 36 | username: 37 | example: john_doe 38 | type: string 39 | required: 40 | - username 41 | - password 42 | type: object 43 | SpecTestResponseGithubComOaswrapSpecTestToken: 44 | properties: 45 | data: 46 | $ref: '#/components/schemas/SpecTestToken' 47 | status: 48 | example: 200 49 | type: integer 50 | type: object 51 | SpecTestToken: 52 | properties: 53 | token: 54 | example: abc123 55 | type: string 56 | type: object 57 | -------------------------------------------------------------------------------- /adapter/README.md: -------------------------------------------------------------------------------- 1 | # Framework Adapters 2 | 3 | This directory contains framework-specific adapters for `oaswrap/spec` that provide seamless integration with popular Go web frameworks. Each adapter automatically generates OpenAPI 3.x specifications from your existing routes and handlers. 4 | 5 | ## Available Adapters 6 | 7 | ### Web Frameworks 8 | 9 | | Framework | Adapter | Go Module | Description | 10 | |-----------|---------|-----------|-------------| 11 | | [Chi](https://github.com/go-chi/chi) | [`chiopenapi`](./chiopenapi) | `github.com/oaswrap/spec/adapter/chiopenapi` | Lightweight router with middleware support | 12 | | [Echo](https://github.com/labstack/echo) | [`echoopenapi`](./echoopenapi) | `github.com/oaswrap/spec/adapter/echoopenapi` | High performance, extensible, minimalist framework | 13 | | [Fiber](https://github.com/gofiber/fiber) | [`fiberopenapi`](./fiberopenapi) | `github.com/oaswrap/spec/adapter/fiberopenapi` | Express-inspired framework built on Fasthttp | 14 | | [Gin](https://github.com/gin-gonic/gin) | [`ginopenapi`](./ginopenapi) | `github.com/oaswrap/spec/adapter/ginopenapi` | Fast HTTP web framework with zero allocation | 15 | | [net/http](https://pkg.go.dev/net/http) | [`httpopenapi`](./httpopenapi) | `github.com/oaswrap/spec/adapter/httpopenapi` | Standard library HTTP package | 16 | | [HttpRouter](https://github.com/julienschmidt/httprouter) | [`httprouteropenapi`](./httprouteropenapi) | `github.com/oaswrap/spec/adapter/httprouteropenapi` | High performance HTTP request router | 17 | | [Gorilla Mux](https://github.com/gorilla/mux) | [`muxopenapi`](./muxopenapi) | `github.com/oaswrap/spec/adapter/muxopenapi` | Powerful HTTP router and URL matcher | -------------------------------------------------------------------------------- /testdata/generic_response_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Generic Response 4 | title: 'API Doc: Generic Response' 5 | version: 1.0.0 6 | paths: 7 | /login: 8 | post: 9 | description: This operation allows users to log in. 10 | operationId: login 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestLoginRequest' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestResponseGithubComOaswrapSpecTestToken' 22 | description: OK 23 | summary: User Login 24 | tags: 25 | - Authentication 26 | components: 27 | schemas: 28 | SpecTestLoginRequest: 29 | properties: 30 | password: 31 | examples: 32 | - password123 33 | type: string 34 | username: 35 | examples: 36 | - john_doe 37 | type: string 38 | required: 39 | - username 40 | - password 41 | type: object 42 | SpecTestResponseGithubComOaswrapSpecTestToken: 43 | properties: 44 | data: 45 | $ref: '#/components/schemas/SpecTestToken' 46 | status: 47 | examples: 48 | - 200 49 | type: integer 50 | type: object 51 | SpecTestToken: 52 | properties: 53 | token: 54 | examples: 55 | - abc123 56 | type: string 57 | type: object 58 | tags: 59 | - description: Operations related to user authentication 60 | name: Authentication 61 | -------------------------------------------------------------------------------- /adapter/httpopenapi/internal/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type RoutePattern struct { 11 | Method string 12 | Host string 13 | Path string 14 | } 15 | 16 | var ( 17 | ErrInvalidRoutePattern = errors.New("invalid route pattern") 18 | ) 19 | 20 | const TotalParts = 2 21 | 22 | func ParseRoutePattern(s string) (*RoutePattern, error) { 23 | rp := &RoutePattern{} 24 | 25 | parts := strings.SplitN(s, " ", TotalParts) 26 | 27 | if len(parts) == 2 && isHTTPMethod(parts[0]) { 28 | rp.Method = parts[0] 29 | s = parts[1] 30 | } 31 | // If NOT a valid method, keep full input in s! 32 | 33 | if strings.HasPrefix(s, "/") { 34 | rp.Path = s 35 | return rp, nil 36 | } 37 | 38 | slash := strings.Index(s, "/") 39 | if slash == -1 { 40 | rp.Host = s 41 | rp.Path = "/" 42 | } else { 43 | rp.Host = s[:slash] 44 | rp.Path = s[slash:] 45 | } 46 | 47 | if strings.Contains(rp.Host, " ") { 48 | return nil, ErrInvalidRoutePattern 49 | } 50 | 51 | // Handle host:port 52 | hostPart := rp.Host 53 | if h, _, err := net.SplitHostPort(rp.Host); err == nil { 54 | hostPart = h 55 | } 56 | 57 | // Strict host check 58 | if hostPart != "localhost" && !strings.Contains(hostPart, ".") { 59 | return nil, ErrInvalidRoutePattern 60 | } 61 | 62 | return rp, nil 63 | } 64 | 65 | func isHTTPMethod(s string) bool { 66 | switch s { 67 | case http.MethodGet, 68 | http.MethodPost, 69 | http.MethodPut, 70 | http.MethodDelete, 71 | http.MethodPatch, 72 | http.MethodOptions, 73 | http.MethodHead, 74 | http.MethodTrace, 75 | http.MethodConnect: 76 | return true 77 | default: 78 | return false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /reflector.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/oaswrap/spec/internal/debuglog" 8 | "github.com/oaswrap/spec/internal/errs" 9 | "github.com/oaswrap/spec/openapi" 10 | "github.com/oaswrap/spec/option" 11 | ) 12 | 13 | var ( 14 | re3 = regexp.MustCompile(`^3\.0\.\d(-.+)?$`) 15 | re31 = regexp.MustCompile(`^3\.1\.\d+(-.+)?$`) 16 | ) 17 | 18 | func newReflector(cfg *openapi.Config) reflector { 19 | logger := debuglog.NewLogger("spec", cfg.Logger) 20 | 21 | if re3.MatchString(cfg.OpenAPIVersion) { 22 | return newReflector3(cfg, logger) 23 | } else if re31.MatchString(cfg.OpenAPIVersion) { 24 | return newReflector31(cfg, logger) 25 | } 26 | 27 | logger.Printf("Unsupported OpenAPI version: %s", cfg.OpenAPIVersion) 28 | return newInvalidReflector(fmt.Errorf("unsupported OpenAPI version: %s", cfg.OpenAPIVersion)) 29 | } 30 | 31 | type invalidReflector struct { 32 | spec *noopSpec 33 | errors *errs.SpecError 34 | } 35 | 36 | func newInvalidReflector(err error) reflector { 37 | e := &errs.SpecError{} 38 | e.Add(err) 39 | 40 | return &invalidReflector{ 41 | errors: e, 42 | spec: &noopSpec{}, 43 | } 44 | } 45 | 46 | var _ reflector = (*invalidReflector)(nil) 47 | 48 | func (r *invalidReflector) Spec() spec { 49 | return r.spec 50 | } 51 | 52 | func (r *invalidReflector) Add(_, _ string, _ ...option.OperationOption) {} 53 | 54 | func (r *invalidReflector) Validate() error { 55 | if r.errors.HasErrors() { 56 | return r.errors 57 | } 58 | return nil 59 | } 60 | 61 | type noopSpec struct{} 62 | 63 | func (s *noopSpec) MarshalYAML() ([]byte, error) { 64 | return nil, nil 65 | } 66 | 67 | func (s *noopSpec) MarshalJSON() ([]byte, error) { 68 | return nil, nil 69 | } 70 | -------------------------------------------------------------------------------- /option/group.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import "github.com/oaswrap/spec/pkg/util" 4 | 5 | // GroupConfig defines configuration options for a group of routes in an OpenAPI specification. 6 | type GroupConfig struct { 7 | Tags []string 8 | Security []OperationSecurityConfig 9 | Deprecated bool 10 | Hide bool 11 | } 12 | 13 | // GroupOption applies a configuration option to a GroupConfig. 14 | type GroupOption func(*GroupConfig) 15 | 16 | // GroupTags sets one or more tags for the group. 17 | // 18 | // These tags will be added to all routes in the sub-router. 19 | func GroupTags(tags ...string) GroupOption { 20 | return func(cfg *GroupConfig) { 21 | cfg.Tags = append(cfg.Tags, tags...) 22 | } 23 | } 24 | 25 | // GroupSecurity adds a security scheme to the group. 26 | // 27 | // The security scheme will apply to all routes in the sub-router. 28 | func GroupSecurity(securityName string, scopes ...string) GroupOption { 29 | return func(cfg *GroupConfig) { 30 | cfg.Security = append(cfg.Security, OperationSecurityConfig{ 31 | Name: securityName, 32 | Scopes: scopes, 33 | }) 34 | } 35 | } 36 | 37 | // GroupHidden sets whether the group should be hidden. 38 | // 39 | // If true, the group and its routes will be excluded from the OpenAPI output. 40 | func GroupHidden(hidden ...bool) GroupOption { 41 | return func(cfg *GroupConfig) { 42 | cfg.Hide = util.Optional(true, hidden...) 43 | } 44 | } 45 | 46 | // GroupDeprecated sets whether the group is deprecated. 47 | // 48 | // If true, all routes in the group will be marked as deprecated in the OpenAPI output. 49 | func GroupDeprecated(deprecated ...bool) GroupOption { 50 | return func(cfg *GroupConfig) { 51 | cfg.Deprecated = util.Optional(true, deprecated...) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/basic/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: My API 4 | version: 1.0.0 5 | servers: 6 | - url: https://api.example.com 7 | paths: 8 | /api/v1/login: 9 | post: 10 | description: User login 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/LoginRequest' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/LoginResponse' 22 | description: OK 23 | summary: User login 24 | /api/v1/users/{id}: 25 | get: 26 | description: Get user by ID 27 | parameters: 28 | - in: path 29 | name: id 30 | required: true 31 | schema: 32 | type: string 33 | responses: 34 | "200": 35 | content: 36 | application/json: 37 | schema: 38 | $ref: '#/components/schemas/User' 39 | description: OK 40 | security: 41 | - bearerAuth: [] 42 | summary: Get user by ID 43 | components: 44 | schemas: 45 | LoginRequest: 46 | properties: 47 | password: 48 | type: string 49 | username: 50 | type: string 51 | required: 52 | - username 53 | - password 54 | type: object 55 | LoginResponse: 56 | properties: 57 | token: 58 | type: string 59 | type: object 60 | User: 61 | properties: 62 | id: 63 | type: string 64 | name: 65 | type: string 66 | type: object 67 | securitySchemes: 68 | bearerAuth: 69 | scheme: Bearer 70 | type: http 71 | -------------------------------------------------------------------------------- /testdata/basic_data_types_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Basic Data Types 4 | title: 'API Doc: Basic Data Types' 5 | version: 1.0.0 6 | paths: 7 | /basic-data-types: 8 | post: 9 | description: This operation returns all basic data types. 10 | operationId: getBasicDataTypes 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestAllBasicDataTypes' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestAllBasicDataTypes' 22 | description: OK 23 | summary: Get Basic Data Types 24 | components: 25 | schemas: 26 | SpecTestAllBasicDataTypes: 27 | properties: 28 | bool: 29 | type: boolean 30 | byte: 31 | minimum: 0 32 | type: integer 33 | float32: 34 | format: float 35 | type: number 36 | float64: 37 | format: double 38 | type: number 39 | int: 40 | type: integer 41 | int8: 42 | type: integer 43 | int16: 44 | type: integer 45 | int32: 46 | format: int32 47 | type: integer 48 | int64: 49 | format: int64 50 | type: integer 51 | rune: 52 | format: int32 53 | type: integer 54 | string: 55 | type: string 56 | uint: 57 | minimum: 0 58 | type: integer 59 | uint8: 60 | minimum: 0 61 | type: integer 62 | uint16: 63 | minimum: 0 64 | type: integer 65 | uint32: 66 | minimum: 0 67 | type: integer 68 | uint64: 69 | minimum: 0 70 | type: integer 71 | type: object 72 | -------------------------------------------------------------------------------- /testdata/basic_data_types_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Basic Data Types 4 | title: 'API Doc: Basic Data Types' 5 | version: 1.0.0 6 | paths: 7 | /basic-data-types: 8 | post: 9 | description: This operation returns all basic data types. 10 | operationId: getBasicDataTypes 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestAllBasicDataTypes' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestAllBasicDataTypes' 22 | description: OK 23 | summary: Get Basic Data Types 24 | components: 25 | schemas: 26 | SpecTestAllBasicDataTypes: 27 | properties: 28 | bool: 29 | type: boolean 30 | byte: 31 | minimum: 0 32 | type: integer 33 | float32: 34 | format: float 35 | type: number 36 | float64: 37 | format: double 38 | type: number 39 | int: 40 | type: integer 41 | int8: 42 | type: integer 43 | int16: 44 | type: integer 45 | int32: 46 | format: int32 47 | type: integer 48 | int64: 49 | format: int64 50 | type: integer 51 | rune: 52 | format: int32 53 | type: integer 54 | string: 55 | type: string 56 | uint: 57 | minimum: 0 58 | type: integer 59 | uint8: 60 | minimum: 0 61 | type: integer 62 | uint16: 63 | minimum: 0 64 | type: integer 65 | uint32: 66 | minimum: 0 67 | type: integer 68 | uint64: 69 | minimum: 0 70 | type: integer 71 | type: object 72 | -------------------------------------------------------------------------------- /pkg/parser/colon_param_parser_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oaswrap/spec/pkg/parser" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestNewColonParamParser(t *testing.T) { 12 | parser := parser.NewColonParamParser() 13 | assert.NotNil(t, parser, "NewColonParamParser() returned nil") 14 | } 15 | 16 | func TestColonParamParser_Parse(t *testing.T) { 17 | parser := parser.NewColonParamParser() 18 | 19 | tests := []struct { 20 | name string 21 | input string 22 | expected string 23 | }{ 24 | { 25 | name: "simple parameter", 26 | input: "/users/:id", 27 | expected: "/users/{id}", 28 | }, 29 | { 30 | name: "multiple parameters", 31 | input: "/users/:userId/posts/:postId", 32 | expected: "/users/{userId}/posts/{postId}", 33 | }, 34 | { 35 | name: "no parameters", 36 | input: "/users", 37 | expected: "/users", 38 | }, 39 | { 40 | name: "parameter with underscore", 41 | input: "/users/:user_id", 42 | expected: "/users/{user_id}", 43 | }, 44 | { 45 | name: "parameter with numbers", 46 | input: "/api/v1/:id123", 47 | expected: "/api/v1/{id123}", 48 | }, 49 | { 50 | name: "parameter at root", 51 | input: "/:id", 52 | expected: "/{id}", 53 | }, 54 | { 55 | name: "mixed parameters and static segments", 56 | input: "/api/:version/users/:id/profile", 57 | expected: "/api/{version}/users/{id}/profile", 58 | }, 59 | { 60 | name: "empty path", 61 | input: "", 62 | expected: "", 63 | }, 64 | { 65 | name: "parameter starting with underscore", 66 | input: "/users/:_id", 67 | expected: "/users/{_id}", 68 | }, 69 | } 70 | 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | result, err := parser.Parse(tt.input) 74 | require.NoError(t, err) 75 | assert.Equal(t, tt.expected, result) 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /adapter/httpopenapi/types.go: -------------------------------------------------------------------------------- 1 | package httpopenapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/oaswrap/spec/option" 7 | ) 8 | 9 | // Generator is an interface for generating OpenAPI documentation for HTTP applications. 10 | type Generator interface { 11 | Router 12 | 13 | // Validate checks if the OpenAPI schema is valid. 14 | Validate() error 15 | 16 | // GenerateSchema generates the OpenAPI schema in the specified formats. 17 | // Supported formats include "yaml", "json", etc. 18 | // If no formats are specified, it defaults to "yaml". 19 | GenerateSchema(formats ...string) ([]byte, error) 20 | 21 | // MarshalYAML generates the OpenAPI schema in YAML format. 22 | MarshalYAML() ([]byte, error) 23 | 24 | // MarshalJSON generates the OpenAPI schema in JSON format. 25 | MarshalJSON() ([]byte, error) 26 | 27 | // WriteSchemaTo writes the OpenAPI schema to a file in the specified format. 28 | WriteSchemaTo(filename string) error 29 | } 30 | 31 | // Router is an interface for handling HTTP routes with OpenAPI support. 32 | type Router interface { 33 | http.Handler 34 | 35 | // HandleFunc registers a handler function for the specified pattern. 36 | HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) Route 37 | 38 | // Handle registers a handler for the specified pattern. 39 | Handle(pattern string, handler http.Handler) Route 40 | 41 | // Group creates a new sub-router with the specified options. 42 | Group(prefix string, middlewares ...func(http.Handler) http.Handler) Router 43 | 44 | // Route creates a new route with the specified pattern and handler function. 45 | Route(prefix string, fn func(r Router), middlewares ...func(http.Handler) http.Handler) Router 46 | 47 | // With applies group level options to the router. 48 | With(opts ...option.GroupOption) Router 49 | } 50 | 51 | // Route is an interface for defining a route with OpenAPI options. 52 | type Route interface { 53 | // With applies operation options to the route. 54 | With(opts ...option.OperationOption) Route 55 | } 56 | -------------------------------------------------------------------------------- /adapter/ginopenapi/examples/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/ginopenapi/examples/basic 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.12 6 | 7 | require ( 8 | github.com/gin-gonic/gin v1.10.1 9 | github.com/oaswrap/spec v0.3.6 10 | github.com/oaswrap/spec/adapter/ginopenapi v0.0.0 11 | ) 12 | 13 | require ( 14 | github.com/bytedance/sonic v1.11.6 // indirect 15 | github.com/bytedance/sonic/loader v0.1.1 // indirect 16 | github.com/cloudwego/base64x v0.1.4 // indirect 17 | github.com/cloudwego/iasm v0.2.0 // indirect 18 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 19 | github.com/gin-contrib/sse v0.1.0 // indirect 20 | github.com/go-playground/locales v0.14.1 // indirect 21 | github.com/go-playground/universal-translator v0.18.1 // indirect 22 | github.com/go-playground/validator/v10 v10.20.0 // indirect 23 | github.com/goccy/go-json v0.10.2 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 26 | github.com/leodido/go-urn v1.4.0 // indirect 27 | github.com/mattn/go-isatty v0.0.20 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/oaswrap/spec-ui v0.1.4 // indirect 31 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 32 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 33 | github.com/swaggest/openapi-go v0.2.60 // indirect 34 | github.com/swaggest/refl v1.4.0 // indirect 35 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 36 | github.com/ugorji/go/codec v1.2.12 // indirect 37 | golang.org/x/arch v0.8.0 // indirect 38 | golang.org/x/crypto v0.38.0 // indirect 39 | golang.org/x/net v0.40.0 // indirect 40 | golang.org/x/sys v0.33.0 // indirect 41 | golang.org/x/text v0.25.0 // indirect 42 | google.golang.org/protobuf v1.34.1 // indirect 43 | gopkg.in/yaml.v2 v2.4.0 // indirect 44 | gopkg.in/yaml.v3 v3.0.1 // indirect 45 | ) 46 | 47 | replace github.com/oaswrap/spec/adapter/ginopenapi => ../.. 48 | -------------------------------------------------------------------------------- /pkg/testutil/yaml_test.go: -------------------------------------------------------------------------------- 1 | package testutil_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oaswrap/spec/pkg/testutil" 7 | ) 8 | 9 | func TestEqualYAML(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | want []byte 13 | got []byte 14 | shouldErr bool 15 | }{ 16 | { 17 | name: "identical YAML", 18 | want: []byte("key: value\nother: 123"), 19 | got: []byte("key: value\nother: 123"), 20 | shouldErr: false, 21 | }, 22 | { 23 | name: "semantically equal YAML different formatting", 24 | want: []byte("key: value\nother: 123"), 25 | got: []byte("other: 123\nkey: value"), 26 | shouldErr: false, 27 | }, 28 | { 29 | name: "different values", 30 | want: []byte("key: value1"), 31 | got: []byte("key: value2"), 32 | shouldErr: true, 33 | }, 34 | { 35 | name: "different keys", 36 | want: []byte("key1: value"), 37 | got: []byte("key2: value"), 38 | shouldErr: true, 39 | }, 40 | { 41 | name: "nested objects equal", 42 | want: []byte("parent:\n child: value\n num: 42"), 43 | got: []byte("parent:\n num: 42\n child: value"), 44 | shouldErr: false, 45 | }, 46 | { 47 | name: "arrays equal", 48 | want: []byte("items:\n - one\n - two\n - three"), 49 | got: []byte("items:\n - one\n - two\n - three"), 50 | shouldErr: false, 51 | }, 52 | { 53 | name: "arrays different order", 54 | want: []byte("items:\n - one\n - two"), 55 | got: []byte("items:\n - two\n - one"), 56 | shouldErr: true, 57 | }, 58 | } 59 | 60 | for _, tt := range tests { 61 | t.Run(tt.name, func(t *testing.T) { 62 | mockT := &testing.T{} 63 | testutil.EqualYAML(mockT, tt.want, tt.got) 64 | 65 | if tt.shouldErr && !mockT.Failed() { 66 | t.Error("Expected test to fail but it passed") 67 | } 68 | if !tt.shouldErr && mockT.Failed() { 69 | t.Error("Expected test to pass but it failed") 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/petstore/router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/oaswrap/spec" 5 | "github.com/oaswrap/spec/openapi" 6 | "github.com/oaswrap/spec/option" 7 | ) 8 | 9 | func createRouter() spec.Generator { 10 | r := spec.NewRouter( 11 | option.WithOpenAPIVersion("3.0.3"), 12 | option.WithTitle("Petstore API"), 13 | option.WithDescription("This is a sample Petstore server."), 14 | option.WithVersion("1.0.0"), 15 | option.WithTermsOfService("https://swagger.io/terms/"), 16 | option.WithContact(openapi.Contact{ 17 | Email: "apiteam@swagger.io", 18 | }), 19 | option.WithLicense(openapi.License{ 20 | Name: "Apache 2.0", 21 | URL: "https://www.apache.org/licenses/LICENSE-2.0.html", 22 | }), 23 | option.WithExternalDocs("https://swagger.io", "Find more info here about swagger"), 24 | option.WithServer("https://petstore3.swagger.io/api/v3"), 25 | option.WithTags( 26 | openapi.Tag{ 27 | Name: "pet", 28 | Description: "Everything about your Pets", 29 | ExternalDocs: &openapi.ExternalDocs{ 30 | Description: "Find out more about our Pets", 31 | URL: "https://swagger.io", 32 | }, 33 | }, 34 | openapi.Tag{ 35 | Name: "store", 36 | Description: "Access to Petstore orders", 37 | ExternalDocs: &openapi.ExternalDocs{ 38 | Description: "Find out more about our Store", 39 | URL: "https://swagger.io", 40 | }, 41 | }, 42 | openapi.Tag{ 43 | Name: "user", 44 | Description: "Operations about user", 45 | }, 46 | ), 47 | option.WithSecurity("petstore_auth", option.SecurityOAuth2( 48 | openapi.OAuthFlows{ 49 | Implicit: &openapi.OAuthFlowsImplicit{ 50 | AuthorizationURL: "https://petstore3.swagger.io/oauth/authorize", 51 | Scopes: map[string]string{ 52 | "write:pets": "modify pets in your account", 53 | "read:pets": "read your pets", 54 | }, 55 | }, 56 | }), 57 | ), 58 | option.WithSecurity("apiKey", option.SecurityAPIKey("api_key", openapi.SecuritySchemeAPIKeyInHeader)), 59 | ) 60 | 61 | return r 62 | } 63 | -------------------------------------------------------------------------------- /examples/petstore/dto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "mime/multipart" 5 | "time" 6 | ) 7 | 8 | type Pet struct { 9 | ID int `json:"id"` 10 | Name string `json:"name"` 11 | Type string `json:"type"` 12 | Status string `json:"status" enum:"available,pending,sold"` 13 | Category Category `json:"category"` 14 | Tags []Tag `json:"tags"` 15 | PhotoURLs []string `json:"photoUrls"` 16 | } 17 | 18 | type Tag struct { 19 | ID int `json:"id"` 20 | Name string `json:"name"` 21 | } 22 | 23 | type Category struct { 24 | ID int `json:"id"` 25 | Name string `json:"name"` 26 | } 27 | 28 | type UpdatePetWithFormRequest struct { 29 | ID int `path:"petId" required:"true"` 30 | Name string `formData:"name" required:"true"` 31 | Status string `formData:"status" enum:"available,pending,sold"` 32 | } 33 | 34 | type UploadImageRequest struct { 35 | ID int64 `params:"petId" path:"petId"` 36 | AdditionalMetaData string `query:"additionalMetadata"` 37 | _ *multipart.File `contentType:"application/octet-stream"` 38 | } 39 | 40 | type DeletePetRequest struct { 41 | ID int `path:"petId" required:"true"` 42 | ApiKey string `header:"api_key"` 43 | } 44 | 45 | type Order struct { 46 | ID int `json:"id"` 47 | PetID int `json:"petId"` 48 | Quantity int `json:"quantity"` 49 | ShipDate time.Time `json:"shipDate"` 50 | Status string `json:"status" enum:"placed,approved,delivered"` 51 | Complete bool `json:"complete"` 52 | } 53 | 54 | type User struct { 55 | ID int `json:"id"` 56 | Username string `json:"username"` 57 | FirstName string `json:"firstName"` 58 | LastName string `json:"lastName"` 59 | Email string `json:"email"` 60 | Password string `json:"password"` 61 | Phone string `json:"phone"` 62 | UserStatus int `json:"userStatus" enum:"0,1,2"` 63 | } 64 | 65 | type ApiResponse struct { 66 | Message string `json:"message"` 67 | Type string `json:"type"` 68 | Code int `json:"code"` 69 | } 70 | -------------------------------------------------------------------------------- /option/security.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import "github.com/oaswrap/spec/openapi" 4 | 5 | // securityConfig holds configuration for defining a security scheme. 6 | type securityConfig struct { 7 | Description *string 8 | APIKey *openapi.SecuritySchemeAPIKey 9 | HTTPBearer *openapi.SecuritySchemeHTTPBearer 10 | Oauth2 *openapi.SecuritySchemeOAuth2 11 | } 12 | 13 | // SecurityOption applies configuration to a securityConfig. 14 | type SecurityOption func(*securityConfig) 15 | 16 | // SecurityDescription sets the description for the security scheme. 17 | // 18 | // If the description is empty, it clears any existing description. 19 | func SecurityDescription(description string) SecurityOption { 20 | return func(cfg *securityConfig) { 21 | if description != "" { 22 | cfg.Description = &description 23 | } else { 24 | cfg.Description = nil 25 | } 26 | } 27 | } 28 | 29 | // SecurityAPIKey defines an API key security scheme. 30 | // 31 | // Example: 32 | // 33 | // option.WithSecurity("apiKey", 34 | // option.SecurityAPIKey("x-api-key", openapi.SecuritySchemeAPIKeyInHeader), 35 | // ) 36 | func SecurityAPIKey(name string, in openapi.SecuritySchemeAPIKeyIn) SecurityOption { 37 | return func(cfg *securityConfig) { 38 | cfg.APIKey = &openapi.SecuritySchemeAPIKey{ 39 | Name: name, 40 | In: in, 41 | } 42 | } 43 | } 44 | 45 | // SecurityHTTPBearer defines an HTTP Bearer security scheme. 46 | // 47 | // Optionally, you can provide a bearer format. 48 | func SecurityHTTPBearer(scheme string, bearerFormat ...string) SecurityOption { 49 | return func(cfg *securityConfig) { 50 | httpBearer := &openapi.SecuritySchemeHTTPBearer{ 51 | Scheme: scheme, 52 | } 53 | if len(bearerFormat) > 0 { 54 | httpBearer.BearerFormat = &bearerFormat[0] 55 | } 56 | cfg.HTTPBearer = httpBearer 57 | } 58 | } 59 | 60 | // SecurityOAuth2 defines an OAuth 2.0 security scheme. 61 | func SecurityOAuth2(flows openapi.OAuthFlows) SecurityOption { 62 | return func(cfg *securityConfig) { 63 | cfg.Oauth2 = &openapi.SecuritySchemeOAuth2{ 64 | Flows: flows, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/errs/error_test.go: -------------------------------------------------------------------------------- 1 | package errs_test 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/oaswrap/spec/internal/errs" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSpecError_Add(t *testing.T) { 13 | se := &errs.SpecError{} 14 | 15 | // Test adding a valid error 16 | err := errors.New("test error") 17 | se.Add(err) 18 | 19 | assert.Len(t, se.Errors(), 1) 20 | 21 | // Test adding nil error (should not be added) 22 | se.Add(nil) 23 | 24 | assert.Len(t, se.Errors(), 1) 25 | } 26 | 27 | func TestSpecError_Errors(t *testing.T) { 28 | se := &errs.SpecError{} 29 | 30 | // Test empty errors 31 | errs := se.Errors() 32 | assert.Empty(t, errs) 33 | 34 | // Test with errors 35 | err1 := errors.New("error 1") 36 | err2 := errors.New("error 2") 37 | se.Add(err1) 38 | se.Add(err2) 39 | 40 | errs = se.Errors() 41 | assert.Len(t, errs, 2) 42 | } 43 | 44 | func TestSpecError_Error(t *testing.T) { 45 | se := &errs.SpecError{} 46 | 47 | // Test empty error message 48 | msg := se.Error() 49 | assert.Empty(t, msg) 50 | 51 | // Test with single error 52 | se.Add(errors.New("test error")) 53 | msg = se.Error() 54 | expected := "Spec errors:\n- test error\n" 55 | assert.Equal(t, expected, msg) 56 | 57 | // Test with multiple errors 58 | se.Add(errors.New("second error")) 59 | msg = se.Error() 60 | expected = "Spec errors:\n- test error\n- second error\n" 61 | assert.Equal(t, expected, msg) 62 | } 63 | 64 | func TestSpecError_HasErrors(t *testing.T) { 65 | se := &errs.SpecError{} 66 | 67 | // Test with no errors 68 | assert.False(t, se.HasErrors()) 69 | 70 | // Test with errors 71 | se.Add(errors.New("test error")) 72 | assert.True(t, se.HasErrors()) 73 | } 74 | 75 | func TestSpecError_ConcurrentAccess(t *testing.T) { 76 | se := &errs.SpecError{} 77 | var wg sync.WaitGroup 78 | 79 | // Test concurrent adds 80 | for i := 0; i < 100; i++ { 81 | wg.Add(1) 82 | go func() { 83 | defer wg.Done() 84 | se.Add(errors.New("error")) 85 | }() 86 | } 87 | 88 | wg.Wait() 89 | 90 | assert.Len(t, se.Errors(), 100) 91 | } 92 | -------------------------------------------------------------------------------- /adapter/ginopenapi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oaswrap/spec/adapter/ginopenapi 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.12 6 | 7 | require ( 8 | github.com/gin-gonic/gin v1.10.1 9 | github.com/oaswrap/spec v0.3.6 10 | github.com/oaswrap/spec-ui v0.1.4 11 | github.com/stretchr/testify v1.11.1 12 | ) 13 | 14 | require ( 15 | github.com/bytedance/sonic v1.11.6 // indirect 16 | github.com/bytedance/sonic/loader v0.1.1 // indirect 17 | github.com/cloudwego/base64x v0.1.4 // indirect 18 | github.com/cloudwego/iasm v0.2.0 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 21 | github.com/gin-contrib/sse v0.1.0 // indirect 22 | github.com/go-playground/locales v0.14.1 // indirect 23 | github.com/go-playground/universal-translator v0.18.1 // indirect 24 | github.com/go-playground/validator/v10 v10.20.0 // indirect 25 | github.com/goccy/go-json v0.10.2 // indirect 26 | github.com/google/go-cmp v0.7.0 // indirect 27 | github.com/json-iterator/go v1.1.12 // indirect 28 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 29 | github.com/kr/text v0.1.0 // indirect 30 | github.com/leodido/go-urn v1.4.0 // indirect 31 | github.com/mattn/go-isatty v0.0.20 // indirect 32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 33 | github.com/modern-go/reflect2 v1.0.2 // indirect 34 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 35 | github.com/pmezard/go-difflib v1.0.0 // indirect 36 | github.com/swaggest/jsonschema-go v0.3.78 // indirect 37 | github.com/swaggest/openapi-go v0.2.60 // indirect 38 | github.com/swaggest/refl v1.4.0 // indirect 39 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 40 | github.com/ugorji/go/codec v1.2.12 // indirect 41 | golang.org/x/arch v0.8.0 // indirect 42 | golang.org/x/crypto v0.38.0 // indirect 43 | golang.org/x/net v0.40.0 // indirect 44 | golang.org/x/sys v0.33.0 // indirect 45 | golang.org/x/text v0.25.0 // indirect 46 | google.golang.org/protobuf v1.34.1 // indirect 47 | gopkg.in/yaml.v2 v2.4.0 // indirect 48 | gopkg.in/yaml.v3 v3.0.1 // indirect 49 | ) 50 | -------------------------------------------------------------------------------- /testdata/mux_route_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Mux Route 4 | title: 'API Doc: Mux Route' 5 | version: 1.0.0 6 | paths: 7 | api/v1/login: 8 | post: 9 | description: User Login v1 10 | requestBody: 11 | content: 12 | application/json: 13 | schema: 14 | $ref: '#/components/schemas/SpecTestLoginRequest' 15 | responses: 16 | "200": 17 | content: 18 | application/json: 19 | schema: 20 | $ref: '#/components/schemas/SpecTestToken' 21 | description: OK 22 | summary: User Login v1 23 | api/v1/profile: 24 | get: 25 | description: Get Profile v1 26 | responses: 27 | "200": 28 | content: 29 | application/json: 30 | schema: 31 | $ref: '#/components/schemas/SpecTestUser' 32 | description: OK 33 | security: 34 | - bearerAuth: [] 35 | summary: Get Profile v1 36 | components: 37 | schemas: 38 | SpecTestLoginRequest: 39 | properties: 40 | password: 41 | example: password123 42 | type: string 43 | username: 44 | example: john_doe 45 | type: string 46 | required: 47 | - username 48 | - password 49 | type: object 50 | SpecTestNullString: 51 | type: object 52 | SpecTestNullTime: 53 | type: object 54 | SpecTestToken: 55 | properties: 56 | token: 57 | example: abc123 58 | type: string 59 | type: object 60 | SpecTestUser: 61 | properties: 62 | age: 63 | nullable: true 64 | type: integer 65 | created_at: 66 | format: date-time 67 | type: string 68 | email: 69 | $ref: '#/components/schemas/SpecTestNullString' 70 | id: 71 | type: integer 72 | updated_at: 73 | $ref: '#/components/schemas/SpecTestNullTime' 74 | username: 75 | type: string 76 | type: object 77 | securitySchemes: 78 | bearerAuth: 79 | scheme: Bearer 80 | type: http 81 | -------------------------------------------------------------------------------- /pkg/dto/pet.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "mime/multipart" 5 | "time" 6 | ) 7 | 8 | type Pet struct { 9 | ID int `json:"id"` 10 | Name string `json:"name"` 11 | Type string `json:"type"` 12 | Status string `json:"status" enum:"available,pending,sold"` 13 | Category Category `json:"category"` 14 | Tags []Tag `json:"tags"` 15 | PhotoURLs []string `json:"photoUrls"` 16 | } 17 | 18 | type Tag struct { 19 | ID int `json:"id"` 20 | Name string `json:"name"` 21 | } 22 | 23 | type Category struct { 24 | ID int `json:"id"` 25 | Name string `json:"name"` 26 | } 27 | 28 | type UpdatePetWithFormRequest struct { 29 | ID int `path:"petId" required:"true"` 30 | Name string ` required:"true" formData:"name"` 31 | Status string ` formData:"status" enum:"available,pending,sold"` 32 | } 33 | 34 | type UploadImageRequest struct { 35 | ID int64 `params:"petId" path:"petId"` 36 | AdditionalMetaData string ` query:"additionalMetadata"` 37 | _ *multipart.File ` contentType:"application/octet-stream"` 38 | } 39 | 40 | type DeletePetRequest struct { 41 | ID int `path:"petId" required:"true"` 42 | APIKey string ` header:"api_key"` 43 | } 44 | 45 | type Order struct { 46 | ID int `json:"id"` 47 | PetID int `json:"petId"` 48 | Quantity int `json:"quantity"` 49 | ShipDate time.Time `json:"shipDate"` 50 | Status string `json:"status" enum:"placed,approved,delivered"` 51 | Complete bool `json:"complete"` 52 | } 53 | 54 | type PetUser struct { 55 | ID int `json:"id"` 56 | Username string `json:"username"` 57 | FirstName string `json:"firstName"` 58 | LastName string `json:"lastName"` 59 | Email string `json:"email"` 60 | Password string `json:"password"` 61 | Phone string `json:"phone"` 62 | UserStatus int `json:"userStatus" enum:"0,1,2"` 63 | } 64 | 65 | type APIResponse struct { 66 | Message string `json:"message"` 67 | Type string `json:"type"` 68 | Code int `json:"code"` 69 | } 70 | -------------------------------------------------------------------------------- /option/content_test.go: -------------------------------------------------------------------------------- 1 | package option_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oaswrap/spec/openapi" 7 | "github.com/oaswrap/spec/option" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestContentOption(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | httpStatus int 15 | opts []option.ContentOption 16 | expected openapi.ContentUnit 17 | }{ 18 | { 19 | name: "empty options", 20 | httpStatus: 0, 21 | opts: []option.ContentOption{}, 22 | expected: openapi.ContentUnit{ 23 | HTTPStatus: 0, 24 | }, 25 | }, 26 | { 27 | name: "with content type", 28 | httpStatus: 200, 29 | opts: []option.ContentOption{ 30 | option.ContentType("application/json"), 31 | }, 32 | expected: openapi.ContentUnit{ 33 | HTTPStatus: 200, 34 | ContentType: "application/json", 35 | }, 36 | }, 37 | { 38 | name: "with description", 39 | httpStatus: 200, 40 | opts: []option.ContentOption{ 41 | option.ContentDescription("This is a response"), 42 | }, 43 | expected: openapi.ContentUnit{ 44 | HTTPStatus: 200, 45 | Description: "This is a response", 46 | }, 47 | }, 48 | { 49 | name: "with default flag", 50 | httpStatus: 200, 51 | opts: []option.ContentOption{ 52 | option.ContentDefault(true), 53 | }, 54 | expected: openapi.ContentUnit{ 55 | HTTPStatus: 200, 56 | IsDefault: true, 57 | }, 58 | }, 59 | { 60 | name: "with multiple options", 61 | httpStatus: 200, 62 | opts: []option.ContentOption{ 63 | option.ContentType("application/json"), 64 | option.ContentDescription("This is a response"), 65 | option.ContentDefault(true), 66 | }, 67 | expected: openapi.ContentUnit{ 68 | HTTPStatus: 200, 69 | ContentType: "application/json", 70 | Description: "This is a response", 71 | IsDefault: true, 72 | }, 73 | }, 74 | } 75 | for _, tt := range tests { 76 | t.Run(tt.name, func(t *testing.T) { 77 | config := &openapi.ContentUnit{ 78 | HTTPStatus: tt.httpStatus, 79 | } 80 | for _, opt := range tt.opts { 81 | opt(config) 82 | } 83 | assert.Equal(t, tt.expected, *config) 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/mapper/specui_test.go: -------------------------------------------------------------------------------- 1 | package mapper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oaswrap/spec" 7 | "github.com/oaswrap/spec-ui/config" 8 | "github.com/oaswrap/spec/option" 9 | "github.com/oaswrap/spec/pkg/mapper" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestSpecUIOpts(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | gen spec.Generator 17 | assert func(t *testing.T, cfg *config.SpecUI) 18 | }{ 19 | { 20 | name: "Swagger UI", 21 | gen: spec.NewGenerator( 22 | option.WithSwaggerUI(), 23 | option.WithCacheAge(86400), 24 | ), 25 | assert: func(t *testing.T, cfg *config.SpecUI) { 26 | assert.Equal(t, config.ProviderSwaggerUI, cfg.Provider) 27 | assert.NotNil(t, cfg.SwaggerUI) 28 | assert.Equal(t, 86400, cfg.CacheAge) 29 | }, 30 | }, 31 | { 32 | name: "Stoplight Elements", 33 | gen: spec.NewGenerator( 34 | option.WithStoplightElements(), 35 | ), 36 | assert: func(t *testing.T, cfg *config.SpecUI) { 37 | assert.Equal(t, config.ProviderStoplightElements, cfg.Provider) 38 | assert.NotNil(t, cfg.StoplightElements) 39 | }, 40 | }, 41 | { 42 | name: "ReDoc", 43 | gen: spec.NewGenerator( 44 | option.WithReDoc(), 45 | ), 46 | assert: func(t *testing.T, cfg *config.SpecUI) { 47 | assert.Equal(t, config.ProviderReDoc, cfg.Provider) 48 | assert.NotNil(t, cfg.ReDoc) 49 | }, 50 | }, 51 | { 52 | name: "Scalar", 53 | gen: spec.NewGenerator( 54 | option.WithScalar(), 55 | ), 56 | assert: func(t *testing.T, cfg *config.SpecUI) { 57 | assert.Equal(t, config.ProviderScalar, cfg.Provider) 58 | assert.NotNil(t, cfg.Scalar) 59 | }, 60 | }, 61 | { 62 | name: "RapiDoc", 63 | gen: spec.NewGenerator( 64 | option.WithRapiDoc(), 65 | ), 66 | assert: func(t *testing.T, cfg *config.SpecUI) { 67 | assert.Equal(t, config.ProviderRapiDoc, cfg.Provider) 68 | assert.NotNil(t, cfg.RapiDoc) 69 | }, 70 | }, 71 | } 72 | 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | opts := mapper.SpecUIOpts(tt.gen) 76 | cfg := &config.SpecUI{} 77 | for _, opt := range opts { 78 | opt(cfg) 79 | } 80 | tt.assert(t, cfg) 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /testdata/mux_route_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Mux Route 4 | title: 'API Doc: Mux Route' 5 | version: 1.0.0 6 | paths: 7 | api/v1/login: 8 | post: 9 | description: User Login v1 10 | requestBody: 11 | content: 12 | application/json: 13 | schema: 14 | $ref: '#/components/schemas/SpecTestLoginRequest' 15 | responses: 16 | "200": 17 | content: 18 | application/json: 19 | schema: 20 | $ref: '#/components/schemas/SpecTestToken' 21 | description: OK 22 | summary: User Login v1 23 | api/v1/profile: 24 | get: 25 | description: Get Profile v1 26 | responses: 27 | "200": 28 | content: 29 | application/json: 30 | schema: 31 | $ref: '#/components/schemas/SpecTestUser' 32 | description: OK 33 | security: 34 | - bearerAuth: [] 35 | summary: Get Profile v1 36 | components: 37 | schemas: 38 | SpecTestLoginRequest: 39 | properties: 40 | password: 41 | examples: 42 | - password123 43 | type: string 44 | username: 45 | examples: 46 | - john_doe 47 | type: string 48 | required: 49 | - username 50 | - password 51 | type: object 52 | SpecTestNullString: 53 | type: object 54 | SpecTestNullTime: 55 | type: object 56 | SpecTestToken: 57 | properties: 58 | token: 59 | examples: 60 | - abc123 61 | type: string 62 | type: object 63 | SpecTestUser: 64 | properties: 65 | age: 66 | type: 67 | - "null" 68 | - integer 69 | created_at: 70 | format: date-time 71 | type: string 72 | email: 73 | $ref: '#/components/schemas/SpecTestNullString' 74 | id: 75 | type: integer 76 | updated_at: 77 | $ref: '#/components/schemas/SpecTestNullTime' 78 | username: 79 | type: string 80 | type: object 81 | securitySchemes: 82 | bearerAuth: 83 | scheme: Bearer 84 | type: http 85 | -------------------------------------------------------------------------------- /testdata/all_operation_options_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for All Operation Options 4 | title: 'API Doc: All Operation Options' 5 | version: 1.0.0 6 | paths: 7 | /operation/options: 8 | post: 9 | deprecated: true 10 | description: This operation retrieves all operation options. 11 | operationId: postOperationOptions 12 | requestBody: 13 | content: 14 | application/json: 15 | schema: 16 | $ref: '#/components/schemas/SpecTestLoginRequest' 17 | description: Request body for operation options 18 | responses: 19 | default: 20 | content: 21 | application/json: 22 | schema: 23 | $ref: '#/components/schemas/SpecTestResponseGithubComOaswrapSpecTestUser' 24 | description: Response body for operation options 25 | security: 26 | - apiKey: [] 27 | summary: Post Operation Options 28 | tags: 29 | - Operation Options 30 | components: 31 | schemas: 32 | SpecTestLoginRequest: 33 | properties: 34 | password: 35 | example: password123 36 | type: string 37 | username: 38 | example: john_doe 39 | type: string 40 | required: 41 | - username 42 | - password 43 | type: object 44 | SpecTestNullString: 45 | type: object 46 | SpecTestNullTime: 47 | type: object 48 | SpecTestResponseGithubComOaswrapSpecTestUser: 49 | properties: 50 | data: 51 | $ref: '#/components/schemas/SpecTestUser' 52 | status: 53 | example: 200 54 | type: integer 55 | type: object 56 | SpecTestUser: 57 | properties: 58 | age: 59 | nullable: true 60 | type: integer 61 | created_at: 62 | format: date-time 63 | type: string 64 | email: 65 | $ref: '#/components/schemas/SpecTestNullString' 66 | id: 67 | type: integer 68 | updated_at: 69 | $ref: '#/components/schemas/SpecTestNullTime' 70 | username: 71 | type: string 72 | type: object 73 | securitySchemes: 74 | apiKey: 75 | in: header 76 | name: x-api-key 77 | type: apiKey 78 | -------------------------------------------------------------------------------- /jsonschema.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/oaswrap/spec/internal/debuglog" 7 | "github.com/oaswrap/spec/openapi" 8 | "github.com/swaggest/jsonschema-go" 9 | ) 10 | 11 | func getJSONSchemaOpts(cfg *openapi.ReflectorConfig, logger *debuglog.Logger) []func(*jsonschema.ReflectContext) { 12 | var opts []func(*jsonschema.ReflectContext) 13 | 14 | if cfg.InlineRefs { 15 | opts = append(opts, jsonschema.InlineRefs) 16 | logger.Printf("set inline references to true") 17 | } 18 | if cfg.RootRef { 19 | opts = append(opts, jsonschema.RootRef) 20 | logger.Printf("set root reference to true") 21 | } 22 | if cfg.RootNullable { 23 | opts = append(opts, jsonschema.RootNullable) 24 | logger.Printf("set root nullable to true") 25 | } 26 | if len(cfg.StripDefNamePrefix) > 0 { 27 | opts = append(opts, jsonschema.StripDefinitionNamePrefix(cfg.StripDefNamePrefix...)) 28 | logger.LogAction("set strip definition name prefix", fmt.Sprintf("%v", cfg.StripDefNamePrefix)) 29 | } 30 | if cfg.InterceptDefNameFunc != nil { 31 | opts = append(opts, jsonschema.InterceptDefName(cfg.InterceptDefNameFunc)) 32 | logger.Printf("set custom intercept definition name function") 33 | } 34 | if cfg.InterceptPropFunc != nil { 35 | opts = append(opts, jsonschema.InterceptProp(func(params jsonschema.InterceptPropParams) error { 36 | return cfg.InterceptPropFunc(openapi.InterceptPropParams{ 37 | Context: params.Context, 38 | Path: params.Path, 39 | Name: params.Name, 40 | Field: params.Field, 41 | PropertySchema: params.PropertySchema, 42 | ParentSchema: params.ParentSchema, 43 | Processed: params.Processed, 44 | }) 45 | })) 46 | logger.Printf("set custom intercept property function") 47 | } 48 | if cfg.InterceptSchemaFunc != nil { 49 | opts = append( 50 | opts, 51 | jsonschema.InterceptSchema(func(params jsonschema.InterceptSchemaParams) (bool, error) { 52 | stop, err := cfg.InterceptSchemaFunc(openapi.InterceptSchemaParams{ 53 | Context: params.Context, 54 | Value: params.Value, 55 | Schema: params.Schema, 56 | Processed: params.Processed, 57 | }) 58 | return stop, err 59 | }), 60 | ) 61 | logger.Printf("set custom intercept schema function") 62 | } 63 | 64 | return opts 65 | } 66 | -------------------------------------------------------------------------------- /testdata/basic_data_types_pointers_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Basic Data Types Pointers 4 | title: 'API Doc: Basic Data Types Pointers' 5 | version: 1.0.0 6 | paths: 7 | /basic-data-types-pointers: 8 | put: 9 | description: This operation returns all basic data types as pointers. 10 | operationId: getBasicDataTypesPointers 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestAllBasicDataTypesPointers' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestAllBasicDataTypesPointers' 22 | description: OK 23 | summary: Get Basic Data Types Pointers 24 | components: 25 | schemas: 26 | SpecTestAllBasicDataTypesPointers: 27 | properties: 28 | bool: 29 | nullable: true 30 | type: boolean 31 | byte: 32 | minimum: 0 33 | nullable: true 34 | type: integer 35 | float32: 36 | nullable: true 37 | type: number 38 | float64: 39 | nullable: true 40 | type: number 41 | int: 42 | nullable: true 43 | type: integer 44 | int8: 45 | nullable: true 46 | type: integer 47 | int16: 48 | nullable: true 49 | type: integer 50 | int32: 51 | nullable: true 52 | type: integer 53 | int64: 54 | nullable: true 55 | type: integer 56 | rune: 57 | nullable: true 58 | type: integer 59 | string: 60 | nullable: true 61 | type: string 62 | uint: 63 | minimum: 0 64 | nullable: true 65 | type: integer 66 | uint8: 67 | minimum: 0 68 | nullable: true 69 | type: integer 70 | uint16: 71 | minimum: 0 72 | nullable: true 73 | type: integer 74 | uint32: 75 | minimum: 0 76 | nullable: true 77 | type: integer 78 | uint64: 79 | minimum: 0 80 | nullable: true 81 | type: integer 82 | type: object 83 | -------------------------------------------------------------------------------- /testdata/all_operation_options_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for All Operation Options 4 | title: 'API Doc: All Operation Options' 5 | version: 1.0.0 6 | paths: 7 | /operation/options: 8 | post: 9 | deprecated: true 10 | description: This operation retrieves all operation options. 11 | operationId: postOperationOptions 12 | requestBody: 13 | content: 14 | application/json: 15 | schema: 16 | $ref: '#/components/schemas/SpecTestLoginRequest' 17 | description: Request body for operation options 18 | responses: 19 | default: 20 | content: 21 | application/json: 22 | schema: 23 | $ref: '#/components/schemas/SpecTestResponseGithubComOaswrapSpecTestUser' 24 | description: Response body for operation options 25 | security: 26 | - apiKey: [] 27 | summary: Post Operation Options 28 | tags: 29 | - Operation Options 30 | components: 31 | schemas: 32 | SpecTestLoginRequest: 33 | properties: 34 | password: 35 | examples: 36 | - password123 37 | type: string 38 | username: 39 | examples: 40 | - john_doe 41 | type: string 42 | required: 43 | - username 44 | - password 45 | type: object 46 | SpecTestNullString: 47 | type: object 48 | SpecTestNullTime: 49 | type: object 50 | SpecTestResponseGithubComOaswrapSpecTestUser: 51 | properties: 52 | data: 53 | $ref: '#/components/schemas/SpecTestUser' 54 | status: 55 | examples: 56 | - 200 57 | type: integer 58 | type: object 59 | SpecTestUser: 60 | properties: 61 | age: 62 | type: 63 | - "null" 64 | - integer 65 | created_at: 66 | format: date-time 67 | type: string 68 | email: 69 | $ref: '#/components/schemas/SpecTestNullString' 70 | id: 71 | type: integer 72 | updated_at: 73 | $ref: '#/components/schemas/SpecTestNullTime' 74 | username: 75 | type: string 76 | type: object 77 | securitySchemes: 78 | apiKey: 79 | in: header 80 | name: x-api-key 81 | type: apiKey 82 | -------------------------------------------------------------------------------- /adapter/muxopenapi/route.go: -------------------------------------------------------------------------------- 1 | package muxopenapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | "github.com/oaswrap/spec" 8 | "github.com/oaswrap/spec/option" 9 | ) 10 | 11 | type route struct { 12 | muxRoute *mux.Route 13 | specRoute spec.Route 14 | specRouter spec.Router 15 | 16 | pathPrefix string 17 | } 18 | 19 | var _ Route = (*route)(nil) 20 | 21 | func (r *route) GetError() error { 22 | return r.muxRoute.GetError() 23 | } 24 | 25 | func (r *route) GetHandler() http.Handler { 26 | return r.muxRoute.GetHandler() 27 | } 28 | 29 | func (r *route) GetName() string { 30 | return r.muxRoute.GetName() 31 | } 32 | 33 | func (r *route) Handler(handler http.Handler) Route { 34 | r.muxRoute.Handler(handler) 35 | return r 36 | } 37 | 38 | func (r *route) HandlerFunc(handler func(http.ResponseWriter, *http.Request)) Route { 39 | r.muxRoute.HandlerFunc(handler) 40 | return r 41 | } 42 | 43 | func (r *route) Headers(pairs ...string) Route { 44 | r.muxRoute.Headers(pairs...) 45 | return r 46 | } 47 | 48 | func (r *route) Host(tpl string) Route { 49 | r.muxRoute.Host(tpl) 50 | return r 51 | } 52 | 53 | func (r *route) Methods(methods ...string) Route { 54 | r.muxRoute.Methods(methods...) 55 | if len(methods) > 0 && methods[0] != http.MethodConnect { 56 | r.specRoute.Method(methods[0]) 57 | } 58 | return r 59 | } 60 | 61 | func (r *route) Name(name string) Route { 62 | r.muxRoute.Name(name) 63 | return r 64 | } 65 | 66 | func (r *route) Path(tpl string) Route { 67 | r.muxRoute.Path(tpl) 68 | r.specRoute.Path(tpl) 69 | return r 70 | } 71 | 72 | func (r *route) PathPrefix(tpl string) Route { 73 | r.muxRoute.PathPrefix(tpl) 74 | r.pathPrefix = tpl 75 | return r 76 | } 77 | 78 | func (r *route) Queries(queries ...string) Route { 79 | r.muxRoute.Queries(queries...) 80 | return r 81 | } 82 | 83 | func (r *route) Schemes(schemes ...string) Route { 84 | r.muxRoute.Schemes(schemes...) 85 | return r 86 | } 87 | 88 | func (r *route) SkipClean() bool { 89 | return r.muxRoute.SkipClean() 90 | } 91 | 92 | func (r *route) Subrouter(opts ...option.GroupOption) Router { 93 | return &router{ 94 | muxRouter: r.muxRoute.Subrouter(), 95 | specRouter: r.specRouter.Group(r.pathPrefix, opts...), 96 | } 97 | } 98 | 99 | func (r *route) With(opts ...option.OperationOption) Route { 100 | r.specRoute.With(opts...) 101 | return r 102 | } 103 | -------------------------------------------------------------------------------- /adapter/ginopenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/oaswrap/spec/adapter/ginopenapi" 8 | "github.com/oaswrap/spec/option" 9 | ) 10 | 11 | func main() { 12 | e := gin.Default() 13 | 14 | // Create a new OpenAPI router 15 | r := ginopenapi.NewRouter(e, 16 | option.WithTitle("My API"), 17 | option.WithVersion("1.0.0"), 18 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 19 | ) 20 | // Add routes 21 | v1 := r.Group("/api/v1") 22 | v1.POST("/login", LoginHandler).With( 23 | option.Summary("User login"), 24 | option.Request(new(LoginRequest)), 25 | option.Response(200, new(LoginResponse)), 26 | ) 27 | auth := v1.Group("/", AuthMiddleware).With( 28 | option.GroupSecurity("bearerAuth"), 29 | ) 30 | auth.GET("/users/:id", GetUserHandler).With( 31 | option.Summary("Get user by ID"), 32 | option.Request(new(GetUserRequest)), 33 | option.Response(200, new(User)), 34 | ) 35 | 36 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 37 | 38 | if err := e.Run(":3000"); err != nil { 39 | log.Fatal(err) 40 | } 41 | } 42 | 43 | type LoginRequest struct { 44 | Username string `json:"username" required:"true"` 45 | Password string `json:"password" required:"true"` 46 | } 47 | 48 | type LoginResponse struct { 49 | Token string `json:"token"` 50 | } 51 | 52 | type GetUserRequest struct { 53 | ID string `uri:"id" required:"true"` 54 | } 55 | 56 | type User struct { 57 | ID string `json:"id"` 58 | Name string `json:"name"` 59 | } 60 | 61 | func AuthMiddleware(c *gin.Context) { 62 | authHeader := c.GetHeader("Authorization") 63 | if authHeader != "" && authHeader == "Bearer example-token" { 64 | c.Next() 65 | } else { 66 | c.JSON(401, gin.H{"error": "Unauthorized"}) 67 | c.Abort() 68 | } 69 | } 70 | 71 | func LoginHandler(c *gin.Context) { 72 | var req LoginRequest 73 | if err := c.ShouldBindJSON(&req); err != nil { 74 | c.JSON(400, map[string]string{"error": "Invalid request"}) 75 | return 76 | } 77 | // Simulate login logic 78 | c.JSON(200, LoginResponse{Token: "example-token"}) 79 | } 80 | 81 | func GetUserHandler(c *gin.Context) { 82 | var req GetUserRequest 83 | if err := c.ShouldBindUri(&req); err != nil { 84 | c.JSON(400, map[string]string{"error": "Invalid request"}) 85 | return 86 | } 87 | // Simulate fetching user by ID 88 | user := User{ID: req.ID, Name: "John Doe"} 89 | c.JSON(200, user) 90 | } 91 | -------------------------------------------------------------------------------- /adapter/fiberopenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/oaswrap/spec/adapter/fiberopenapi" 8 | "github.com/oaswrap/spec/option" 9 | ) 10 | 11 | func main() { 12 | app := fiber.New() 13 | 14 | // Create a new OpenAPI router 15 | r := fiberopenapi.NewRouter(app, 16 | option.WithTitle("My API"), 17 | option.WithVersion("1.0.0"), 18 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 19 | ) 20 | // Add routes 21 | v1 := r.Group("/api/v1") 22 | v1.Post("/login", LoginHandler).With( 23 | option.Summary("User login"), 24 | option.Request(new(LoginRequest)), 25 | option.Response(200, new(LoginResponse)), 26 | ) 27 | auth := v1.Group("/", AuthMiddleware).With( 28 | option.GroupSecurity("bearerAuth"), 29 | ) 30 | auth.Get("/users/:id", GetUserHandler).With( 31 | option.Summary("Get user by ID"), 32 | option.Request(new(GetUserRequest)), 33 | option.Response(200, new(User)), 34 | ) 35 | 36 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 37 | 38 | if err := app.Listen(":3000"); err != nil { 39 | log.Fatal(err) 40 | } 41 | } 42 | 43 | type LoginRequest struct { 44 | Username string `json:"username" required:"true"` 45 | Password string `json:"password" required:"true"` 46 | } 47 | 48 | type LoginResponse struct { 49 | Token string `json:"token"` 50 | } 51 | 52 | type GetUserRequest struct { 53 | ID string `params:"id" required:"true"` 54 | } 55 | 56 | type User struct { 57 | ID string `json:"id"` 58 | Name string `json:"name"` 59 | } 60 | 61 | func AuthMiddleware(c *fiber.Ctx) error { 62 | authHeader := c.Get("Authorization") 63 | if authHeader != "" && authHeader == "Bearer example-token" { 64 | return c.Next() 65 | } 66 | return c.Status(401).JSON(map[string]string{"error": "Unauthorized"}) 67 | } 68 | 69 | func LoginHandler(c *fiber.Ctx) error { 70 | var req LoginRequest 71 | if err := c.BodyParser(&req); err != nil { 72 | return c.Status(400).JSON(map[string]string{"error": "Invalid request"}) 73 | } 74 | // Simulate login logic 75 | return c.Status(200).JSON(LoginResponse{Token: "example-token"}) 76 | } 77 | 78 | func GetUserHandler(c *fiber.Ctx) error { 79 | var req GetUserRequest 80 | if err := c.ParamsParser(&req); err != nil { 81 | return c.Status(400).JSON(map[string]string{"error": "Invalid request"}) 82 | } 83 | // Simulate fetching user by ID 84 | user := User{ID: req.ID, Name: "John Doe"} 85 | return c.Status(200).JSON(user) 86 | } 87 | -------------------------------------------------------------------------------- /testdata/basic_data_types_pointers_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Basic Data Types Pointers 4 | title: 'API Doc: Basic Data Types Pointers' 5 | version: 1.0.0 6 | paths: 7 | /basic-data-types-pointers: 8 | put: 9 | description: This operation returns all basic data types as pointers. 10 | operationId: getBasicDataTypesPointers 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestAllBasicDataTypesPointers' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestAllBasicDataTypesPointers' 22 | description: OK 23 | summary: Get Basic Data Types Pointers 24 | components: 25 | schemas: 26 | SpecTestAllBasicDataTypesPointers: 27 | properties: 28 | bool: 29 | type: 30 | - "null" 31 | - boolean 32 | byte: 33 | minimum: 0 34 | type: 35 | - "null" 36 | - integer 37 | float32: 38 | type: 39 | - "null" 40 | - number 41 | float64: 42 | type: 43 | - "null" 44 | - number 45 | int: 46 | type: 47 | - "null" 48 | - integer 49 | int8: 50 | type: 51 | - "null" 52 | - integer 53 | int16: 54 | type: 55 | - "null" 56 | - integer 57 | int32: 58 | type: 59 | - "null" 60 | - integer 61 | int64: 62 | type: 63 | - "null" 64 | - integer 65 | rune: 66 | type: 67 | - "null" 68 | - integer 69 | string: 70 | type: 71 | - "null" 72 | - string 73 | uint: 74 | minimum: 0 75 | type: 76 | - "null" 77 | - integer 78 | uint8: 79 | minimum: 0 80 | type: 81 | - "null" 82 | - integer 83 | uint16: 84 | minimum: 0 85 | type: 86 | - "null" 87 | - integer 88 | uint32: 89 | minimum: 0 90 | type: 91 | - "null" 92 | - integer 93 | uint64: 94 | minimum: 0 95 | type: 96 | - "null" 97 | - integer 98 | type: object 99 | -------------------------------------------------------------------------------- /testdata/all_methods_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for All methods 4 | title: 'API Doc: All methods' 5 | version: 1.0.0 6 | paths: 7 | /user: 8 | get: 9 | description: Get User 10 | operationId: getUser 11 | responses: 12 | "204": 13 | description: No Content 14 | summary: Get User 15 | options: 16 | description: Options User 17 | operationId: optionsUser 18 | responses: 19 | "204": 20 | description: No Content 21 | summary: Options User 22 | post: 23 | description: Create User 24 | operationId: createUser 25 | responses: 26 | "201": 27 | content: 28 | plain/text: 29 | schema: 30 | type: string 31 | description: Created 32 | summary: Create User 33 | /user/{id}: 34 | delete: 35 | description: Delete User 36 | operationId: deleteUser 37 | parameters: 38 | - in: path 39 | name: id 40 | required: true 41 | schema: 42 | type: integer 43 | responses: 44 | "204": 45 | description: No Content 46 | summary: Delete User 47 | head: 48 | description: Head User 49 | operationId: headUser 50 | parameters: 51 | - in: path 52 | name: id 53 | required: true 54 | schema: 55 | type: integer 56 | responses: 57 | "204": 58 | description: No Content 59 | summary: Head User 60 | patch: 61 | description: Patch User 62 | operationId: patchUser 63 | parameters: 64 | - in: path 65 | name: id 66 | required: true 67 | schema: 68 | type: integer 69 | responses: 70 | "204": 71 | description: No Content 72 | summary: Patch User 73 | put: 74 | description: Update User 75 | operationId: updateUser 76 | parameters: 77 | - in: path 78 | name: id 79 | required: true 80 | schema: 81 | type: integer 82 | responses: 83 | "204": 84 | description: No Content 85 | summary: Update User 86 | trace: 87 | description: Trace User 88 | operationId: traceUser 89 | parameters: 90 | - in: path 91 | name: id 92 | required: true 93 | schema: 94 | type: integer 95 | responses: 96 | "204": 97 | description: No Content 98 | summary: Trace User 99 | -------------------------------------------------------------------------------- /adapter/echoopenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/oaswrap/spec/adapter/echoopenapi" 8 | "github.com/oaswrap/spec/option" 9 | ) 10 | 11 | func main() { 12 | e := echo.New() 13 | 14 | // Create a new OpenAPI router 15 | r := echoopenapi.NewRouter(e, 16 | option.WithTitle("My API"), 17 | option.WithVersion("1.0.0"), 18 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 19 | ) 20 | // Add routes 21 | v1 := r.Group("/api/v1") 22 | 23 | v1.POST("/login", LoginHandler).With( 24 | option.Summary("User login"), 25 | option.Request(new(LoginRequest)), 26 | option.Response(200, new(LoginResponse)), 27 | ) 28 | 29 | auth := v1.Group("", AuthMiddleware).With( 30 | option.GroupSecurity("bearerAuth"), 31 | ) 32 | auth.GET("/users/:id", GetUserHandler).With( 33 | option.Summary("Get user by ID"), 34 | option.Request(new(GetUserRequest)), 35 | option.Response(200, new(User)), 36 | ) 37 | 38 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 39 | 40 | if err := e.Start(":3000"); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | type LoginRequest struct { 46 | Username string `json:"username" required:"true"` 47 | Password string `json:"password" required:"true"` 48 | } 49 | 50 | type LoginResponse struct { 51 | Token string `json:"token"` 52 | } 53 | 54 | type GetUserRequest struct { 55 | ID string `param:"id" required:"true"` 56 | } 57 | 58 | type User struct { 59 | ID string `json:"id"` 60 | Name string `json:"name"` 61 | } 62 | 63 | func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 64 | return func(c echo.Context) error { 65 | // Simulate authentication logic 66 | authHeader := c.Request().Header.Get("Authorization") 67 | if authHeader != "" && authHeader == "Bearer example-token" { 68 | return next(c) 69 | } 70 | return c.JSON(401, map[string]string{"error": "Unauthorized"}) 71 | } 72 | } 73 | 74 | func LoginHandler(c echo.Context) error { 75 | var req LoginRequest 76 | if err := c.Bind(&req); err != nil { 77 | return c.JSON(400, map[string]string{"error": "Invalid request"}) 78 | } 79 | // Simulate login logic 80 | return c.JSON(200, LoginResponse{Token: "example-token"}) 81 | } 82 | 83 | func GetUserHandler(c echo.Context) error { 84 | var req GetUserRequest 85 | if err := c.Bind(&req); err != nil { 86 | return c.JSON(400, map[string]string{"error": "Invalid request"}) 87 | } 88 | // Simulate fetching user by ID 89 | user := User{ID: req.ID, Name: "John Doe"} 90 | return c.JSON(200, user) 91 | } 92 | -------------------------------------------------------------------------------- /adapter/fiberopenapi/types.go: -------------------------------------------------------------------------------- 1 | package fiberopenapi 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/oaswrap/spec/option" 6 | ) 7 | 8 | // Generator defines the interface for generating OpenAPI schemas. 9 | type Generator interface { 10 | Router 11 | 12 | // Validate checks for errors at OpenAPI router initialization. 13 | Validate() error 14 | 15 | // GenerateSchema generates the OpenAPI schema in the specified format. 16 | GenerateSchema(format ...string) ([]byte, error) 17 | // MarshalYAML marshals the OpenAPI schema to YAML format. 18 | MarshalYAML() ([]byte, error) 19 | // MarshalJSON marshals the OpenAPI schema to JSON format. 20 | MarshalJSON() ([]byte, error) 21 | 22 | // WriteSchemaTo writes the OpenAPI schema to a file. 23 | WriteSchemaTo(filePath string) error 24 | } 25 | 26 | // Router defines the interface for an OpenAPI router. 27 | type Router interface { 28 | // Use applies middleware to the router. 29 | Use(args ...any) Router 30 | 31 | // Get registers a GET route. 32 | Get(path string, handler ...fiber.Handler) Route 33 | // Head registers a HEAD route. 34 | Head(path string, handler ...fiber.Handler) Route 35 | // Post registers a POST route. 36 | Post(path string, handler ...fiber.Handler) Route 37 | // Put registers a PUT route. 38 | Put(path string, handler ...fiber.Handler) Route 39 | // Patch registers a PATCH route. 40 | Patch(path string, handler ...fiber.Handler) Route 41 | // Delete registers a DELETE route. 42 | Delete(path string, handler ...fiber.Handler) Route 43 | // Connect registers a CONNECT route. 44 | Connect(path string, handler ...fiber.Handler) Route 45 | // Options registers an OPTIONS route. 46 | Options(path string, handler ...fiber.Handler) Route 47 | // Trace registers a TRACE route. 48 | Trace(path string, handler ...fiber.Handler) Route 49 | 50 | // Add registers a route with the specified method and path. 51 | Add(method, path string, handler ...fiber.Handler) Route 52 | // Static serves static files from the specified root directory. 53 | Static(prefix, root string, config ...fiber.Static) Router 54 | 55 | // Group creates a new sub-router with the specified prefix and handlers. 56 | // The prefix is prepended to all routes in the sub-router. 57 | Group(prefix string, handlers ...fiber.Handler) Router 58 | 59 | // Route creates a new sub-router with the specified prefix and applies options. 60 | Route(prefix string, fn func(router Router), opts ...option.GroupOption) Router 61 | 62 | // With applies options to the router. 63 | // This allows you to configure tags, security, and visibility for the routes. 64 | With(opts ...option.GroupOption) Router 65 | } 66 | -------------------------------------------------------------------------------- /testdata/all_methods_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for All methods 4 | title: 'API Doc: All methods' 5 | version: 1.0.0 6 | paths: 7 | /user: 8 | get: 9 | description: Get User 10 | operationId: getUser 11 | responses: 12 | "204": 13 | description: No Content 14 | summary: Get User 15 | options: 16 | description: Options User 17 | operationId: optionsUser 18 | responses: 19 | "204": 20 | description: No Content 21 | summary: Options User 22 | post: 23 | description: Create User 24 | operationId: createUser 25 | responses: 26 | "201": 27 | content: 28 | plain/text: 29 | schema: 30 | type: 31 | - "null" 32 | - string 33 | description: Created 34 | summary: Create User 35 | /user/{id}: 36 | delete: 37 | description: Delete User 38 | operationId: deleteUser 39 | parameters: 40 | - in: path 41 | name: id 42 | required: true 43 | schema: 44 | type: integer 45 | responses: 46 | "204": 47 | description: No Content 48 | summary: Delete User 49 | head: 50 | description: Head User 51 | operationId: headUser 52 | parameters: 53 | - in: path 54 | name: id 55 | required: true 56 | schema: 57 | type: integer 58 | responses: 59 | "204": 60 | description: No Content 61 | summary: Head User 62 | patch: 63 | description: Patch User 64 | operationId: patchUser 65 | parameters: 66 | - in: path 67 | name: id 68 | required: true 69 | schema: 70 | type: integer 71 | responses: 72 | "204": 73 | description: No Content 74 | summary: Patch User 75 | put: 76 | description: Update User 77 | operationId: updateUser 78 | parameters: 79 | - in: path 80 | name: id 81 | required: true 82 | schema: 83 | type: integer 84 | responses: 85 | "204": 86 | description: No Content 87 | summary: Update User 88 | trace: 89 | description: Trace User 90 | operationId: traceUser 91 | parameters: 92 | - in: path 93 | name: id 94 | required: true 95 | schema: 96 | type: integer 97 | responses: 98 | "204": 99 | description: No Content 100 | summary: Trace User 101 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/julienschmidt/httprouter" 10 | "github.com/oaswrap/spec/adapter/httprouteropenapi" 11 | "github.com/oaswrap/spec/option" 12 | ) 13 | 14 | func main() { 15 | httpRouter := httprouter.New() 16 | r := httprouteropenapi.NewRouter(httpRouter, 17 | option.WithTitle("My API"), 18 | option.WithVersion("1.0.0"), 19 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 20 | ) 21 | v1 := r.Group("/api/v1") 22 | v1.POST("/login", LoginHandler).With( 23 | option.Summary("User login"), 24 | option.Request(new(LoginRequest)), 25 | option.Response(200, new(LoginResponse)), 26 | ) 27 | auth := v1.Group("/", AuthMiddleware).With( 28 | option.GroupSecurity("bearerAuth"), 29 | ) 30 | auth.GET("/users/:id", GetUserHandler).With( 31 | option.Summary("Get user by ID"), 32 | option.Request(new(GetUserRequest)), 33 | option.Response(200, new(User)), 34 | ) 35 | 36 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 37 | 38 | server := &http.Server{ 39 | Addr: ":3000", 40 | Handler: httpRouter, 41 | ReadHeaderTimeout: 5 * time.Second, 42 | } 43 | if err := server.ListenAndServe(); err != nil { 44 | log.Fatal(err) 45 | } 46 | } 47 | 48 | type LoginRequest struct { 49 | Username string `json:"username" required:"true"` 50 | Password string `json:"password" required:"true"` 51 | } 52 | 53 | type LoginResponse struct { 54 | Token string `json:"token"` 55 | } 56 | 57 | type GetUserRequest struct { 58 | ID string `path:"id" required:"true"` 59 | } 60 | 61 | type User struct { 62 | ID string `json:"id"` 63 | Name string `json:"name"` 64 | } 65 | 66 | func AuthMiddleware(next http.Handler) http.Handler { 67 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 68 | // Simulate authentication logic 69 | authHeader := r.Header.Get("Authorization") 70 | if authHeader != "" && authHeader == "Bearer example-token" { 71 | next.ServeHTTP(w, r) 72 | } else { 73 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 74 | } 75 | }) 76 | } 77 | 78 | func LoginHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 79 | var req LoginRequest 80 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 81 | http.Error(w, err.Error(), http.StatusBadRequest) 82 | return 83 | } 84 | // Simulate login logic 85 | _ = json.NewEncoder(w).Encode(LoginResponse{Token: "example-token"}) 86 | } 87 | 88 | func GetUserHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 89 | var req GetUserRequest 90 | req.ID = ps.ByName("id") 91 | 92 | // Simulate getting user logic 93 | _ = json.NewEncoder(w).Encode(User{ID: req.ID, Name: "John Doe"}) 94 | } 95 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/types.go: -------------------------------------------------------------------------------- 1 | package httprouteropenapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/julienschmidt/httprouter" 7 | "github.com/oaswrap/spec/option" 8 | ) 9 | 10 | // Generator is an interface for generating OpenAPI specifications. 11 | type Generator interface { 12 | Router 13 | 14 | // GenerateSchema generates the OpenAPI schema for the router. 15 | GenerateSchema(formats ...string) ([]byte, error) 16 | 17 | // MarshalJSON marshals the schema to JSON. 18 | MarshalJSON() ([]byte, error) 19 | 20 | // MarshalYAML marshals the schema to YAML. 21 | MarshalYAML() ([]byte, error) 22 | 23 | // Validate validates the schema. 24 | Validate() error 25 | 26 | // WriteSchemaTo writes the schema to a file. 27 | WriteSchemaTo(path string) error 28 | } 29 | 30 | // Router is an interface for handling HTTP requests. 31 | type Router interface { 32 | http.Handler 33 | 34 | // Handle registers a new route with the given method, path, and handler. 35 | Handle(method, path string, handle httprouter.Handle) Route 36 | // Handler registers a new route with the given method, path, and handler. 37 | Handler(method, path string, handler http.Handler) Route 38 | // HandlerFunc registers a new route with the given method, path, and handler function. 39 | HandlerFunc(method, path string, handler http.HandlerFunc) Route 40 | 41 | // GET registers a new GET route with the given path and handler. 42 | GET(path string, handle httprouter.Handle) Route 43 | // POST registers a new POST route with the given path and handler. 44 | POST(path string, handle httprouter.Handle) Route 45 | // PUT registers a new PUT route with the given path and handler. 46 | PUT(path string, handle httprouter.Handle) Route 47 | // DELETE registers a new DELETE route with the given path and handler. 48 | DELETE(path string, handle httprouter.Handle) Route 49 | // PATCH registers a new PATCH route with the given path and handler. 50 | PATCH(path string, handle httprouter.Handle) Route 51 | // HEAD registers a new HEAD route with the given path and handler. 52 | HEAD(path string, handle httprouter.Handle) Route 53 | // OPTIONS registers a new OPTIONS route with the given path and handler. 54 | OPTIONS(path string, handle httprouter.Handle) Route 55 | 56 | // Group creates a new route group with the given prefix and middlewares. 57 | Group(prefix string, middlewares ...func(http.Handler) http.Handler) Router 58 | 59 | // Lookup retrieves the route for the given method and path. 60 | Lookup(method, path string) (httprouter.Handle, httprouter.Params, bool) 61 | // ServeFiles serves static files from the given root. 62 | ServeFiles(path string, root http.FileSystem) 63 | 64 | // With adds the given options to the group. 65 | With(opts ...option.GroupOption) Router 66 | } 67 | 68 | // Route is an interface for handling route-specific options. 69 | type Route interface { 70 | // With adds the given options to the route. 71 | With(opts ...option.OperationOption) Route 72 | } 73 | -------------------------------------------------------------------------------- /adapter/ginopenapi/types.go: -------------------------------------------------------------------------------- 1 | package ginopenapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/oaswrap/spec/option" 8 | ) 9 | 10 | // Generator defines an Gin-compatible OpenAPI generator. 11 | // 12 | // It combines routing and OpenAPI schema generation. 13 | type Generator interface { 14 | Router 15 | 16 | // Validate checks if the OpenAPI specification is valid. 17 | Validate() error 18 | 19 | // GenerateSchema generates the OpenAPI schema. 20 | // Defaults to YAML. Pass "json" to generate JSON. 21 | GenerateSchema(format ...string) ([]byte, error) 22 | 23 | // MarshalYAML marshals the OpenAPI schema to YAML. 24 | MarshalYAML() ([]byte, error) 25 | 26 | // MarshalJSON marshals the OpenAPI schema to JSON. 27 | MarshalJSON() ([]byte, error) 28 | 29 | // WriteSchemaTo writes the schema to the given file. 30 | // The format is inferred from the file extension. 31 | WriteSchemaTo(filepath string) error 32 | } 33 | 34 | // Router defines an OpenAPI-aware Gin router. 35 | // 36 | // It wraps Gin routes and supports OpenAPI metadata. 37 | type Router interface { 38 | // Handle registers a new route with the given method, path, and handler. 39 | Handle(method string, path string, handlers ...gin.HandlerFunc) Route 40 | 41 | // GET registers a new GET route. 42 | GET(path string, handlers ...gin.HandlerFunc) Route 43 | 44 | // POST registers a new POST route. 45 | POST(path string, handlers ...gin.HandlerFunc) Route 46 | 47 | // DELETE registers a new DELETE route. 48 | DELETE(path string, handlers ...gin.HandlerFunc) Route 49 | 50 | // PATCH registers a new PATCH route. 51 | PATCH(path string, handlers ...gin.HandlerFunc) Route 52 | 53 | // PUT registers a new PUT route. 54 | PUT(path string, handlers ...gin.HandlerFunc) Route 55 | 56 | // OPTIONS registers a new OPTIONS route. 57 | OPTIONS(path string, handlers ...gin.HandlerFunc) Route 58 | 59 | // HEAD registers a new HEAD route. 60 | HEAD(path string, handlers ...gin.HandlerFunc) Route 61 | 62 | // Group creates a new sub-group with the given prefix and middleware. 63 | Group(prefix string, handlers ...gin.HandlerFunc) Router 64 | 65 | // Use adds global middleware. 66 | Use(middlewares ...gin.HandlerFunc) Router 67 | 68 | // StaticFile serves a single static file. 69 | StaticFile(path string, filepath string) Router 70 | 71 | // StaticFileFS serves a static file from the given filesystem. 72 | StaticFileFS(path string, filepath string, fs http.FileSystem) Router 73 | 74 | // Static serves static files from a directory under the given prefix. 75 | Static(path string, root string) Router 76 | 77 | // StaticFS serves static files from the given filesystem. 78 | StaticFS(path string, fs http.FileSystem) Router 79 | 80 | // With applies OpenAPI group options to this router. 81 | With(opts ...option.GroupOption) Router 82 | } 83 | 84 | // Route represents a single Echo route with OpenAPI metadata. 85 | type Route interface { 86 | // With applies OpenAPI operation options to this route. 87 | With(opts ...option.OperationOption) Route 88 | } 89 | -------------------------------------------------------------------------------- /adapter/httpopenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/oaswrap/spec/adapter/httpopenapi" 10 | "github.com/oaswrap/spec/option" 11 | ) 12 | 13 | func main() { 14 | mainMux := http.NewServeMux() 15 | r := httpopenapi.NewGenerator(mainMux, 16 | option.WithTitle("My API"), 17 | option.WithVersion("1.0.0"), 18 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 19 | ) 20 | 21 | r.Route("/api/v1", func(r httpopenapi.Router) { 22 | r.HandleFunc("POST /login", LoginHandler).With( 23 | option.Summary("User login"), 24 | option.Request(new(LoginRequest)), 25 | option.Response(200, new(LoginResponse)), 26 | ) 27 | auth := r.Group("/", AuthMiddleware).With( 28 | option.GroupSecurity("bearerAuth"), 29 | ) 30 | auth.HandleFunc("GET /users/{id}", GetUserHandler).With( 31 | option.Summary("Get user by ID"), 32 | option.Request(new(GetUserRequest)), 33 | option.Response(200, new(User)), 34 | ) 35 | }) 36 | 37 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 38 | 39 | // Start the server 40 | server := &http.Server{ 41 | Handler: mainMux, 42 | Addr: ":3000", 43 | ReadHeaderTimeout: 5 * time.Second, 44 | } 45 | if err := server.ListenAndServe(); err != nil { 46 | log.Fatal(err) 47 | } 48 | } 49 | 50 | type LoginRequest struct { 51 | Username string `json:"username" required:"true"` 52 | Password string `json:"password" required:"true"` 53 | } 54 | 55 | type LoginResponse struct { 56 | Token string `json:"token"` 57 | } 58 | 59 | type GetUserRequest struct { 60 | ID string `path:"id" required:"true"` 61 | } 62 | 63 | type User struct { 64 | ID string `json:"id"` 65 | Name string `json:"name"` 66 | } 67 | 68 | func AuthMiddleware(next http.Handler) http.Handler { 69 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 70 | // Simulate authentication logic 71 | authHeader := r.Header.Get("Authorization") 72 | if authHeader != "" && authHeader == "Bearer example-token" { 73 | next.ServeHTTP(w, r) 74 | } else { 75 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 76 | } 77 | }) 78 | } 79 | 80 | func LoginHandler(w http.ResponseWriter, r *http.Request) { 81 | var req LoginRequest 82 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 83 | http.Error(w, "Invalid request", http.StatusBadRequest) 84 | return 85 | } 86 | // Simulate login logic 87 | _ = json.NewEncoder(w).Encode(LoginResponse{Token: "example-token"}) 88 | } 89 | 90 | func GetUserHandler(w http.ResponseWriter, r *http.Request) { 91 | var req GetUserRequest 92 | id := r.PathValue("id") 93 | if id == "" { 94 | http.Error(w, "User ID is required", http.StatusBadRequest) 95 | return 96 | } 97 | req.ID = id 98 | // Simulate fetching user by ID 99 | user := User{ID: req.ID, Name: "John Doe"} 100 | _ = json.NewEncoder(w).Encode(user) 101 | } 102 | -------------------------------------------------------------------------------- /adapter/chiopenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/go-chi/chi/v5" 10 | "github.com/oaswrap/spec/adapter/chiopenapi" 11 | "github.com/oaswrap/spec/option" 12 | ) 13 | 14 | func main() { 15 | c := chi.NewRouter() 16 | // Create a new OpenAPI router 17 | r := chiopenapi.NewRouter(c, 18 | option.WithTitle("My API"), 19 | option.WithVersion("1.0.0"), 20 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 21 | ) 22 | // Add routes 23 | r.Route("/api/v1", func(r chiopenapi.Router) { 24 | r.Post("/login", LoginHandler).With( 25 | option.Summary("User login"), 26 | option.Request(new(LoginRequest)), 27 | option.Response(200, new(LoginResponse)), 28 | ) 29 | r.Group(func(r chiopenapi.Router) { 30 | r.Use(AuthMiddleware) 31 | r.Get("/users/{id}", GetUserHandler).With( 32 | option.Summary("Get user by ID"), 33 | option.Request(new(GetUserRequest)), 34 | option.Response(200, new(User)), 35 | ) 36 | }, option.GroupSecurity("bearerAuth")) 37 | }) 38 | 39 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 40 | 41 | // Start the server 42 | server := &http.Server{ 43 | Handler: c, 44 | Addr: ":3000", 45 | ReadHeaderTimeout: 5 * time.Second, 46 | } 47 | if err := server.ListenAndServe(); err != nil { 48 | log.Fatal(err) 49 | } 50 | } 51 | 52 | type LoginRequest struct { 53 | Username string `json:"username" required:"true"` 54 | Password string `json:"password" required:"true"` 55 | } 56 | 57 | type LoginResponse struct { 58 | Token string `json:"token"` 59 | } 60 | 61 | type GetUserRequest struct { 62 | ID string `path:"id" required:"true"` 63 | } 64 | 65 | type User struct { 66 | ID string `json:"id"` 67 | Name string `json:"name"` 68 | } 69 | 70 | func AuthMiddleware(next http.Handler) http.Handler { 71 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 72 | // Simulate authentication logic 73 | authHeader := r.Header.Get("Authorization") 74 | if authHeader != "" && authHeader == "Bearer example-token" { 75 | next.ServeHTTP(w, r) 76 | } else { 77 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 78 | } 79 | }) 80 | } 81 | 82 | func LoginHandler(w http.ResponseWriter, r *http.Request) { 83 | var req LoginRequest 84 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 85 | http.Error(w, "Invalid request", http.StatusBadRequest) 86 | return 87 | } 88 | // Simulate login logic 89 | _ = json.NewEncoder(w).Encode(LoginResponse{Token: "example-token"}) 90 | } 91 | 92 | func GetUserHandler(w http.ResponseWriter, r *http.Request) { 93 | var req GetUserRequest 94 | id := chi.URLParam(r, "id") 95 | if id == "" { 96 | http.Error(w, "User ID is required", http.StatusBadRequest) 97 | return 98 | } 99 | req.ID = id 100 | // Simulate fetching user by ID 101 | user := User{ID: req.ID, Name: "John Doe"} 102 | _ = json.NewEncoder(w).Encode(user) 103 | } 104 | -------------------------------------------------------------------------------- /adapter/chiopenapi/types.go: -------------------------------------------------------------------------------- 1 | package chiopenapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/oaswrap/spec/option" 7 | ) 8 | 9 | // Generator is an interface that defines methods for generating OpenAPI schemas. 10 | type Generator interface { 11 | Router 12 | 13 | // Validate checks if the OpenAPI schema is valid. 14 | Validate() error 15 | 16 | // GenerateSchema generates the OpenAPI schema in the specified formats. 17 | // Supported formats include "yaml", "json", etc. 18 | // If no formats are specified, it defaults to "yaml". 19 | GenerateSchema(formats ...string) ([]byte, error) 20 | 21 | // MarshalYAML generates the OpenAPI schema in YAML format. 22 | MarshalYAML() ([]byte, error) 23 | 24 | // MarshalJSON generates the OpenAPI schema in JSON format. 25 | MarshalJSON() ([]byte, error) 26 | 27 | // WriteSchemaTo writes the OpenAPI schema to a file in the specified format. 28 | WriteSchemaTo(filename string) error 29 | } 30 | 31 | // Router is an interface that defines methods for handling HTTP routes with OpenAPI support. 32 | type Router interface { 33 | http.Handler 34 | // Use applies middleware to the router. 35 | Use(middlewares ...func(http.Handler) http.Handler) 36 | 37 | // With applies middleware to the router and returns a new Router instance. 38 | With(middlewares ...func(http.Handler) http.Handler) Router 39 | 40 | // Group creates a new sub-router with the specified options. 41 | Group(fn func(r Router), opts ...option.GroupOption) Router 42 | 43 | // Route creates a new sub-router for the specified pattern. 44 | Route(pattern string, fn func(r Router), opts ...option.GroupOption) Router 45 | 46 | // Mount mounts a sub-router at the specified pattern. 47 | Mount(pattern string, h http.Handler) 48 | 49 | // Handle registers a handler for the specified pattern. 50 | Handle(pattern string, h http.Handler) 51 | HandleFunc(pattern string, h http.HandlerFunc) 52 | 53 | // Method registers a handler for the specified HTTP method and pattern. 54 | Method(method, pattern string, h http.Handler) Route 55 | MethodFunc(method, pattern string, h http.HandlerFunc) Route 56 | 57 | // HTTP methods 58 | Connect(pattern string, h http.HandlerFunc) Route 59 | Delete(pattern string, h http.HandlerFunc) Route 60 | Get(pattern string, h http.HandlerFunc) Route 61 | Head(pattern string, h http.HandlerFunc) Route 62 | Options(pattern string, h http.HandlerFunc) Route 63 | Patch(pattern string, h http.HandlerFunc) Route 64 | Post(pattern string, h http.HandlerFunc) Route 65 | Put(pattern string, h http.HandlerFunc) Route 66 | Trace(pattern string, h http.HandlerFunc) Route 67 | 68 | // NotFound sets the handler for 404 Not Found responses. 69 | NotFound(h http.HandlerFunc) 70 | // MethodNotAllowed sets the handler for 405 Method Not Allowed responses. 71 | MethodNotAllowed(h http.HandlerFunc) 72 | 73 | // WithOptions applies OpenAPI group options to this router. 74 | WithOptions(opts ...option.GroupOption) Router 75 | } 76 | 77 | // Route represents a single Chi route with OpenAPI metadata. 78 | type Route interface { 79 | // With applies OpenAPI operation options to this route. 80 | With(opts ...option.OperationOption) Route 81 | } 82 | -------------------------------------------------------------------------------- /adapter/muxopenapi/examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gorilla/mux" 10 | "github.com/oaswrap/spec/adapter/muxopenapi" 11 | "github.com/oaswrap/spec/option" 12 | ) 13 | 14 | func main() { 15 | mux := mux.NewRouter() 16 | r := muxopenapi.NewRouter(mux, 17 | option.WithTitle("My API"), 18 | option.WithVersion("1.0.0"), 19 | option.WithSecurity("bearerAuth", option.SecurityHTTPBearer("Bearer")), 20 | ) 21 | 22 | api := r.PathPrefix("/api").Subrouter() 23 | v1 := api.PathPrefix("/v1").Subrouter() 24 | 25 | v1.HandleFunc("/login", LoginHandler).Methods("POST").With( 26 | option.Summary("User Login"), 27 | option.Request(new(LoginRequest)), 28 | option.Response(200, new(LoginResponse)), 29 | ) 30 | auth := v1.PathPrefix("/").Subrouter().With( 31 | option.GroupSecurity("bearerAuth"), 32 | ) 33 | auth.Use(AuthMiddleware) 34 | auth.HandleFunc("/users/{id}", GetUserHandler).Methods("GET").With( 35 | option.Summary("Get User by ID"), 36 | option.Request(new(GetUserRequest)), 37 | option.Response(200, new(User)), 38 | ) 39 | 40 | log.Printf("🚀 OpenAPI docs available at: %s", "http://localhost:3000/docs") 41 | 42 | // Start the server 43 | server := &http.Server{ 44 | Handler: mux, 45 | Addr: ":3000", 46 | ReadHeaderTimeout: 5 * time.Second, 47 | } 48 | if err := server.ListenAndServe(); err != nil { 49 | log.Fatal(err) 50 | } 51 | } 52 | 53 | type LoginRequest struct { 54 | Username string `json:"username" required:"true"` 55 | Password string `json:"password" required:"true"` 56 | } 57 | 58 | type LoginResponse struct { 59 | Token string `json:"token"` 60 | } 61 | 62 | type GetUserRequest struct { 63 | ID string `path:"id" required:"true"` 64 | } 65 | 66 | type User struct { 67 | ID string `json:"id"` 68 | Name string `json:"name"` 69 | } 70 | 71 | func AuthMiddleware(next http.Handler) http.Handler { 72 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 73 | // Simulate authentication logic 74 | authHeader := r.Header.Get("Authorization") 75 | if authHeader != "" && authHeader == "Bearer example-token" { 76 | next.ServeHTTP(w, r) 77 | } else { 78 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 79 | } 80 | }) 81 | } 82 | 83 | func LoginHandler(w http.ResponseWriter, r *http.Request) { 84 | var req LoginRequest 85 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 86 | http.Error(w, "Invalid request", http.StatusBadRequest) 87 | return 88 | } 89 | // Simulate login logic 90 | _ = json.NewEncoder(w).Encode(LoginResponse{Token: "example-token"}) 91 | } 92 | 93 | func GetUserHandler(w http.ResponseWriter, r *http.Request) { 94 | var req GetUserRequest 95 | vars := mux.Vars(r) 96 | id := vars["id"] 97 | if id == "" { 98 | http.Error(w, "User ID is required", http.StatusBadRequest) 99 | return 100 | } 101 | req.ID = id 102 | // Simulate fetching user by ID 103 | user := User{ID: req.ID, Name: "John Doe"} 104 | _ = json.NewEncoder(w).Encode(user) 105 | } 106 | -------------------------------------------------------------------------------- /adapter/httpopenapi/examples/basic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 12 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 13 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 14 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 15 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 19 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 20 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 21 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 22 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 23 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 24 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 25 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 26 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 27 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 28 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 29 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 30 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 31 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 34 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 35 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 36 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 37 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | -------------------------------------------------------------------------------- /adapter/muxopenapi/examples/basic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 10 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 11 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 12 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 16 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 17 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 21 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 22 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 23 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 24 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 25 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 26 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 27 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 28 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 29 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 30 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 31 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 32 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 33 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 35 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 36 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 37 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | -------------------------------------------------------------------------------- /adapter/chiopenapi/examples/basic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= 8 | github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 9 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 10 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 11 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 12 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 16 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 17 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 21 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 22 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 23 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 24 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 25 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 26 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 27 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 28 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 29 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 30 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 31 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 32 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 33 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 35 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 36 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 37 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | -------------------------------------------------------------------------------- /option/reflector_test.go: -------------------------------------------------------------------------------- 1 | package option_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/oaswrap/spec/openapi" 8 | "github.com/oaswrap/spec/option" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | "github.com/swaggest/jsonschema-go" 12 | ) 13 | 14 | func TestInlineRefs(t *testing.T) { 15 | config := &openapi.ReflectorConfig{} 16 | opt := option.InlineRefs() 17 | opt(config) 18 | 19 | assert.True(t, config.InlineRefs) 20 | } 21 | 22 | func TestRootRef(t *testing.T) { 23 | config := &openapi.ReflectorConfig{} 24 | opt := option.RootRef() 25 | opt(config) 26 | 27 | assert.True(t, config.RootRef) 28 | } 29 | 30 | func TestRootNullable(t *testing.T) { 31 | config := &openapi.ReflectorConfig{} 32 | opt := option.RootNullable() 33 | opt(config) 34 | 35 | assert.True(t, config.RootNullable) 36 | } 37 | 38 | func TestStripDefNamePrefix(t *testing.T) { 39 | config := &openapi.ReflectorConfig{} 40 | prefixes := []string{"Test", "Mock"} 41 | opt := option.StripDefNamePrefix(prefixes...) 42 | opt(config) 43 | 44 | assert.Equal(t, prefixes, config.StripDefNamePrefix) 45 | } 46 | 47 | func TestInterceptDefNameFunc(t *testing.T) { 48 | config := &openapi.ReflectorConfig{} 49 | mockFunc := func(_ reflect.Type, _ string) string { 50 | return "CustomName" 51 | } 52 | opt := option.InterceptDefNameFunc(mockFunc) 53 | opt(config) 54 | 55 | assert.NotNil(t, config.InterceptDefNameFunc) 56 | } 57 | 58 | func TestInterceptPropFunc(t *testing.T) { 59 | config := &openapi.ReflectorConfig{} 60 | mockFunc := func(_ openapi.InterceptPropParams) error { 61 | return nil 62 | } 63 | opt := option.InterceptPropFunc(mockFunc) 64 | opt(config) 65 | 66 | assert.NotNil(t, config.InterceptPropFunc) 67 | } 68 | 69 | func TestRequiredPropByValidateTag(t *testing.T) { 70 | config := &openapi.ReflectorConfig{} 71 | opt := option.RequiredPropByValidateTag() 72 | opt(config) 73 | 74 | assert.NotNil(t, config.InterceptPropFunc) 75 | 76 | params := openapi.InterceptPropParams{ 77 | Name: "Field1", 78 | Field: reflect.StructField{ 79 | Tag: reflect.StructTag(`validate:"required"`), 80 | }, 81 | ParentSchema: &jsonschema.Schema{ 82 | Required: []string{}, 83 | }, 84 | Processed: true, 85 | } 86 | err := config.InterceptPropFunc(params) 87 | require.NoError(t, err) 88 | assert.Contains(t, params.ParentSchema.Required, "Field1") 89 | } 90 | 91 | func TestInterceptSchemaFunc(t *testing.T) { 92 | config := &openapi.ReflectorConfig{} 93 | mockFunc := func(_ openapi.InterceptSchemaParams) (bool, error) { 94 | return false, nil 95 | } 96 | opt := option.InterceptSchemaFunc(mockFunc) 97 | opt(config) 98 | 99 | assert.NotNil(t, config.InterceptSchemaFunc) 100 | } 101 | 102 | func TestTypeMapping(t *testing.T) { 103 | config := &openapi.ReflectorConfig{} 104 | src := "source" 105 | dst := "destination" 106 | opt := option.TypeMapping(src, dst) 107 | opt(config) 108 | 109 | assert.Len(t, config.TypeMappings, 1) 110 | 111 | mapping := config.TypeMappings[0] 112 | assert.Equal(t, src, mapping.Src) 113 | assert.Equal(t, dst, mapping.Dst) 114 | } 115 | 116 | func TestParameterTagMapping(t *testing.T) { 117 | config := &openapi.ReflectorConfig{} 118 | opt := option.ParameterTagMapping(openapi.ParameterInPath, "param") 119 | opt(config) 120 | 121 | assert.Equal(t, "param", config.ParameterTagMapping[openapi.ParameterInPath]) 122 | } 123 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/examples/basic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 12 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 16 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 17 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 21 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 22 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 23 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 24 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 25 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 26 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 27 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 28 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 29 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 30 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 31 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 32 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 33 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 35 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 36 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 37 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/ci.yml 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: [ main, develop ] 7 | pull_request: 8 | branches: [ main, develop ] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | env: 15 | CACHE_VERSION: v1 # Increment to bust cache when needed 16 | 17 | jobs: 18 | # ======================================== 19 | # Quality Gate 20 | # ======================================== 21 | quality: 22 | name: 🛡️ Quality Gate 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 10 25 | 26 | steps: 27 | - name: 📥 Checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: 🐹 Setup Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '1.23.x' # Force Go 1.23 latest patch 34 | check-latest: true # Always grab the newest patch release 35 | cache: true # Handles ~/.cache/go-build and ~/go/pkg/mod 36 | 37 | - name: 📁 Cache Tools 38 | uses: actions/cache@v4 39 | with: 40 | path: | 41 | ~/.cache/golangci-lint 42 | ~/go/bin 43 | key: ${{ runner.os }}-tools-${{ env.CACHE_VERSION }}-${{ hashFiles('tools/tools.go', 'Makefile') }} 44 | restore-keys: | 45 | ${{ runner.os }}-tools-${{ env.CACHE_VERSION }}- 46 | ${{ runner.os }}-tools- 47 | 48 | - name: 🔧 Install Tools 49 | run: make install-tools 50 | 51 | - name: ✅ Run Quality Checks 52 | run: make check 53 | 54 | # ======================================== 55 | # Test Matrix 56 | # ======================================== 57 | test: 58 | name: 🧪 Test Matrix 59 | runs-on: ${{ matrix.os }} 60 | timeout-minutes: 15 61 | needs: quality 62 | 63 | strategy: 64 | fail-fast: false 65 | matrix: 66 | os: [ubuntu-latest] 67 | go-version: ['1.23', '1.24', '1.25'] 68 | 69 | steps: 70 | - name: 📥 Checkout 71 | uses: actions/checkout@v4 72 | 73 | - name: 🐹 Setup Go 74 | uses: actions/setup-go@v5 75 | with: 76 | go-version: ${{ matrix.go-version }} 77 | check-latest: true 78 | cache: true # Handles module/build cache 79 | 80 | - name: 🔧 Install Tools 81 | run: make install-tools 82 | 83 | - name: 🧪 Run Tests 84 | run: make test 85 | 86 | - name: 📊 Generate Coverage Report 87 | if: matrix.go-version == '1.23' 88 | run: make testcov 89 | 90 | - name: 📈 Upload Coverage Report 91 | if: matrix.go-version == '1.23' 92 | uses: codecov/codecov-action@v5 93 | with: 94 | token: ${{ secrets.CODECOV_TOKEN }} 95 | files: coverage/coverage.out 96 | fail_ci_if_error: false 97 | verbose: true 98 | 99 | # ======================================== 100 | # Final Status Check 101 | # ======================================== 102 | ci-success: 103 | name: ✅ CI Success 104 | runs-on: ubuntu-latest 105 | needs: [quality, test] 106 | if: always() 107 | 108 | steps: 109 | - name: Check all jobs status 110 | run: | 111 | if [[ "${{ needs.quality.result }}" != "success" ]]; then 112 | echo "Quality gate failed" 113 | exit 1 114 | fi 115 | if [[ "${{ needs.test.result }}" != "success" ]]; then 116 | echo "Tests failed" 117 | exit 1 118 | fi 119 | echo "All CI checks passed! 🎉" -------------------------------------------------------------------------------- /adapter/httpopenapi/internal/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oaswrap/spec/adapter/httpopenapi/internal/parser" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestParseRoutePattern(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input string 15 | want *parser.RoutePattern 16 | wantErr bool 17 | }{ 18 | { 19 | name: "path only", 20 | input: "/api/users", 21 | want: &parser.RoutePattern{ 22 | Method: "", 23 | Host: "", 24 | Path: "/api/users", 25 | }, 26 | wantErr: false, 27 | }, 28 | { 29 | name: "method and path", 30 | input: "GET /api/users", 31 | want: &parser.RoutePattern{ 32 | Method: "GET", 33 | Host: "", 34 | Path: "/api/users", 35 | }, 36 | wantErr: false, 37 | }, 38 | { 39 | name: "host and path", 40 | input: "example.com/api/users", 41 | want: &parser.RoutePattern{ 42 | Method: "", 43 | Host: "example.com", 44 | Path: "/api/users", 45 | }, 46 | wantErr: false, 47 | }, 48 | { 49 | name: "method, host and path", 50 | input: "POST api.example.com/users", 51 | want: &parser.RoutePattern{ 52 | Method: "POST", 53 | Host: "api.example.com", 54 | Path: "/users", 55 | }, 56 | wantErr: false, 57 | }, 58 | { 59 | name: "host only", 60 | input: "example.com", 61 | want: &parser.RoutePattern{ 62 | Method: "", 63 | Host: "example.com", 64 | Path: "/", 65 | }, 66 | wantErr: false, 67 | }, 68 | { 69 | name: "localhost", 70 | input: "localhost/api", 71 | want: &parser.RoutePattern{ 72 | Method: "", 73 | Host: "localhost", 74 | Path: "/api", 75 | }, 76 | wantErr: false, 77 | }, 78 | { 79 | name: "host with port", 80 | input: "localhost:8080/api", 81 | want: &parser.RoutePattern{ 82 | Method: "", 83 | Host: "localhost:8080", 84 | Path: "/api", 85 | }, 86 | wantErr: false, 87 | }, 88 | { 89 | name: "invalid host with space", 90 | input: "invalid host/path", 91 | want: nil, 92 | wantErr: true, 93 | }, 94 | { 95 | name: "invalid hostname", 96 | input: "invalidhost/path", 97 | want: nil, 98 | wantErr: true, 99 | }, 100 | { 101 | name: "valid single word hostname", 102 | input: "localhost/path", 103 | want: &parser.RoutePattern{ 104 | Method: "", 105 | Host: "localhost", 106 | Path: "/path", 107 | }, 108 | wantErr: false, 109 | }, 110 | { 111 | name: "all HTTP methods", 112 | input: "DELETE api.example.com/resource", 113 | want: &parser.RoutePattern{ 114 | Method: "DELETE", 115 | Host: "api.example.com", 116 | Path: "/resource", 117 | }, 118 | wantErr: false, 119 | }, 120 | { 121 | name: "non-HTTP method word", 122 | input: "INVALID api.example.com/resource", 123 | want: nil, 124 | wantErr: true, 125 | }, 126 | } 127 | 128 | for _, tt := range tests { 129 | t.Run(tt.name, func(t *testing.T) { 130 | got, err := parser.ParseRoutePattern(tt.input) 131 | if tt.wantErr { 132 | require.Error(t, err) 133 | assert.Nil(t, got) 134 | } else { 135 | require.NoError(t, err) 136 | assert.Equal(t, tt.want.Method, got.Method) 137 | assert.Equal(t, tt.want.Host, got.Host) 138 | assert.Equal(t, tt.want.Path, got.Path) 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /internal/debuglog/debuglog.go: -------------------------------------------------------------------------------- 1 | package debuglog 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/oaswrap/spec/openapi" 7 | ) 8 | 9 | type Logger struct { 10 | prefix string 11 | logger openapi.Logger 12 | } 13 | 14 | func NewLogger(prefix string, logger openapi.Logger) *Logger { 15 | return &Logger{logger: logger, prefix: fmt.Sprintf("[%s]", prefix)} 16 | } 17 | 18 | func (l *Logger) Printf(format string, v ...any) { 19 | l.logger.Printf(l.prefix+" "+format, v...) 20 | } 21 | 22 | func (l *Logger) LogOp(method, path, action, value string) { 23 | l.Printf("%s %s → %s: %s", method, path, action, value) 24 | } 25 | 26 | func (l *Logger) LogAction(action, value string) { 27 | l.Printf("%s: %s", action, value) 28 | } 29 | 30 | func (l *Logger) LogContact(contact *openapi.Contact) { 31 | if contact == nil { 32 | return 33 | } 34 | var contactInfo string 35 | if contact.Name != "" { 36 | contactInfo += "name: " + contact.Name + ", " 37 | } 38 | if contact.Email != "" { 39 | contactInfo += "email: " + contact.Email + ", " 40 | } 41 | if contact.URL != "" { 42 | contactInfo += "url: " + contact.URL 43 | } 44 | if contactInfo != "" { 45 | l.Printf("set contact: %s", contactInfo) 46 | } 47 | } 48 | 49 | func (l *Logger) LogLicense(license *openapi.License) { 50 | var licenseInfo string 51 | if license.Name != "" { 52 | licenseInfo += "name: " + license.Name + ", " 53 | } 54 | if license.URL != "" { 55 | licenseInfo += "url: " + license.URL 56 | } 57 | if licenseInfo != "" { 58 | l.Printf("set license: %s", licenseInfo) 59 | } 60 | } 61 | 62 | func (l *Logger) LogExternalDocs(externalDocs *openapi.ExternalDocs) { 63 | var docsInfo string 64 | if externalDocs.URL != "" { 65 | docsInfo += "url: " + externalDocs.URL 66 | } 67 | if externalDocs.Description != "" { 68 | docsInfo += ", description: " + externalDocs.Description 69 | } 70 | if docsInfo != "" { 71 | l.Printf("set external docs: %s", docsInfo) 72 | } 73 | } 74 | 75 | func (l *Logger) LogServer(server openapi.Server) { 76 | var serverInfo string 77 | serverInfo += "url: " + server.URL 78 | if server.Description != nil { 79 | serverInfo += ", description: " + *server.Description 80 | } 81 | if len(server.Variables) > 0 { 82 | serverInfo += ", variables: " 83 | for name, variable := range server.Variables { 84 | serverInfo += name + ": " + variable.Default + ", " 85 | } 86 | serverInfo = serverInfo[:len(serverInfo)-2] // Remove trailing comma and space 87 | } 88 | l.Printf("set server: %s", serverInfo) 89 | } 90 | 91 | func (l *Logger) LogTag(tag openapi.Tag) { 92 | tagInfo := "name: " + tag.Name 93 | if tag.Description != "" { 94 | tagInfo += ", description: " + tag.Description 95 | } 96 | if tag.ExternalDocs != nil { 97 | tagInfo += ", external docs: " + tag.ExternalDocs.URL 98 | if tag.ExternalDocs.Description != "" { 99 | tagInfo += " (" + tag.ExternalDocs.Description + ")" 100 | } 101 | } 102 | l.Printf("add tag: %s", tagInfo) 103 | } 104 | 105 | func (l *Logger) LogSecurityScheme(name string, scheme *openapi.SecurityScheme) { 106 | var typeInfo string 107 | switch { 108 | case scheme.APIKey != nil: 109 | typeInfo = "APIKey" 110 | case scheme.HTTPBearer != nil: 111 | typeInfo = "HTTPBearer" 112 | case scheme.OAuth2 != nil: 113 | typeInfo = "OAuth2" 114 | default: 115 | typeInfo = "Unknown" 116 | } 117 | schemeInfo := "name: " + name + ", type: " + typeInfo 118 | if scheme.Description != nil { 119 | schemeInfo += ", description: " + *scheme.Description 120 | } 121 | l.Printf("add security scheme: %s", schemeInfo) 122 | } 123 | -------------------------------------------------------------------------------- /adapter/echoopenapi/types.go: -------------------------------------------------------------------------------- 1 | package echoopenapi 2 | 3 | import ( 4 | "io/fs" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/oaswrap/spec/option" 8 | ) 9 | 10 | // Generator defines an Echo-compatible OpenAPI generator. 11 | // 12 | // It combines routing and OpenAPI schema generation. 13 | type Generator interface { 14 | Router 15 | 16 | // Validate checks if the OpenAPI specification is valid. 17 | Validate() error 18 | 19 | // GenerateSchema generates the OpenAPI schema. 20 | // Defaults to YAML. Pass "json" to generate JSON. 21 | GenerateSchema(format ...string) ([]byte, error) 22 | 23 | // MarshalYAML marshals the OpenAPI schema to YAML. 24 | MarshalYAML() ([]byte, error) 25 | 26 | // MarshalJSON marshals the OpenAPI schema to JSON. 27 | MarshalJSON() ([]byte, error) 28 | 29 | // WriteSchemaTo writes the schema to the given file. 30 | // The format is inferred from the file extension. 31 | WriteSchemaTo(filepath string) error 32 | } 33 | 34 | // Router defines an OpenAPI-aware Echo router. 35 | // 36 | // It wraps Echo routes and supports OpenAPI metadata. 37 | type Router interface { 38 | // GET registers a new GET route. 39 | GET(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 40 | 41 | // POST registers a new POST route. 42 | POST(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 43 | 44 | // PUT registers a new PUT route. 45 | PUT(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 46 | 47 | // DELETE registers a new DELETE route. 48 | DELETE(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 49 | 50 | // PATCH registers a new PATCH route. 51 | PATCH(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 52 | 53 | // HEAD registers a new HEAD route. 54 | HEAD(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 55 | 56 | // OPTIONS registers a new OPTIONS route. 57 | OPTIONS(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 58 | 59 | // TRACE registers a new TRACE route. 60 | TRACE(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 61 | 62 | // CONNECT registers a new CONNECT route. 63 | CONNECT(path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 64 | 65 | // Add registers a new route with the given method, path, and handler. 66 | Add(method, path string, handler echo.HandlerFunc, m ...echo.MiddlewareFunc) Route 67 | 68 | // Group creates a new sub-group with the given prefix and middleware. 69 | Group(prefix string, m ...echo.MiddlewareFunc) Router 70 | 71 | // Use adds global middleware. 72 | Use(m ...echo.MiddlewareFunc) Router 73 | 74 | // File serves a single static file. 75 | File(path, file string) 76 | 77 | // FileFS serves a static file from the given filesystem. 78 | FileFS(path, file string, fs fs.FS, m ...echo.MiddlewareFunc) 79 | 80 | // Static serves static files from a directory under the given prefix. 81 | Static(prefix, root string) 82 | 83 | // StaticFS serves static files from the given filesystem. 84 | StaticFS(prefix string, fs fs.FS) 85 | 86 | // With applies OpenAPI group options to this router. 87 | With(opts ...option.GroupOption) Router 88 | } 89 | 90 | // Route represents a single Echo route with OpenAPI metadata. 91 | type Route interface { 92 | // Method returns the HTTP method (GET, POST, etc.). 93 | Method() string 94 | 95 | // Path returns the route path. 96 | Path() string 97 | 98 | // Name returns the route name. 99 | Name() string 100 | 101 | // With applies OpenAPI operation options to this route. 102 | With(opts ...option.OperationOption) Route 103 | } 104 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 17 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 21 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 22 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 23 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 24 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 25 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 26 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 27 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 28 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 29 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 30 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 31 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 32 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 33 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 34 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 35 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 38 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 40 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 41 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 42 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 43 | -------------------------------------------------------------------------------- /examples/basic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 17 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 21 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 22 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 23 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 24 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 25 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 26 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 27 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 28 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 29 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 30 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 31 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 32 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 33 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 34 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 35 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 38 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 40 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 41 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 42 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 43 | -------------------------------------------------------------------------------- /examples/petstore/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 17 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 21 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 22 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 23 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 24 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 25 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 26 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 27 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 28 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 29 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 30 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 31 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 32 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 33 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 34 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 35 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 38 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 40 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 41 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 42 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 43 | -------------------------------------------------------------------------------- /testdata/group_routes_3.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | description: This is the API documentation for Group Routes 4 | title: 'API Doc: Group Routes' 5 | version: 1.0.0 6 | paths: 7 | /api/v1/auth/login: 8 | post: 9 | deprecated: true 10 | description: User Login v1 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestLoginRequest' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestToken' 22 | description: OK 23 | summary: User Login v1 24 | tags: 25 | - Authentication 26 | /api/v1/auth/me: 27 | get: 28 | deprecated: true 29 | description: Get Profile v1 30 | responses: 31 | "200": 32 | content: 33 | application/json: 34 | schema: 35 | $ref: '#/components/schemas/SpecTestUser' 36 | description: OK 37 | security: 38 | - bearerAuth: [] 39 | summary: Get Profile v1 40 | tags: 41 | - Authentication 42 | /api/v2/auth/login: 43 | post: 44 | description: User Login v2 45 | requestBody: 46 | content: 47 | application/json: 48 | schema: 49 | $ref: '#/components/schemas/SpecTestLoginRequest' 50 | responses: 51 | "200": 52 | content: 53 | application/json: 54 | schema: 55 | $ref: '#/components/schemas/SpecTestToken' 56 | description: OK 57 | summary: User Login v2 58 | tags: 59 | - Authentication 60 | /api/v2/auth/me: 61 | get: 62 | description: Get Profile v2 63 | responses: 64 | "200": 65 | content: 66 | application/json: 67 | schema: 68 | $ref: '#/components/schemas/SpecTestUser' 69 | description: OK 70 | security: 71 | - bearerAuth: [] 72 | summary: Get Profile v2 73 | tags: 74 | - Profile 75 | - Authentication 76 | /api/v2/profile: 77 | put: 78 | description: Update Profile v2 79 | requestBody: 80 | content: 81 | application/json: 82 | schema: 83 | $ref: '#/components/schemas/SpecTestUser' 84 | responses: 85 | "200": 86 | content: 87 | application/json: 88 | schema: 89 | $ref: '#/components/schemas/SpecTestUser' 90 | description: OK 91 | security: 92 | - bearerAuth: [] 93 | summary: Update Profile v2 94 | tags: 95 | - Profile 96 | components: 97 | schemas: 98 | SpecTestLoginRequest: 99 | properties: 100 | password: 101 | example: password123 102 | type: string 103 | username: 104 | example: john_doe 105 | type: string 106 | required: 107 | - username 108 | - password 109 | type: object 110 | SpecTestToken: 111 | properties: 112 | token: 113 | example: abc123 114 | type: string 115 | type: object 116 | SpecTestUser: 117 | properties: 118 | age: 119 | nullable: true 120 | type: integer 121 | created_at: 122 | format: date-time 123 | type: string 124 | email: 125 | type: string 126 | id: 127 | type: integer 128 | updated_at: 129 | format: date-time 130 | type: string 131 | username: 132 | type: string 133 | type: object 134 | securitySchemes: 135 | bearerAuth: 136 | scheme: Bearer 137 | type: http 138 | -------------------------------------------------------------------------------- /option/operation.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "github.com/oaswrap/spec/openapi" 5 | "github.com/oaswrap/spec/pkg/util" 6 | ) 7 | 8 | // OperationConfig holds configuration for an OpenAPI operation. 9 | type OperationConfig struct { 10 | Hide bool 11 | OperationID string 12 | Description string 13 | Summary string 14 | Deprecated bool 15 | Tags []string 16 | Security []OperationSecurityConfig 17 | 18 | Requests []*openapi.ContentUnit 19 | Responses []*openapi.ContentUnit 20 | } 21 | 22 | // OperationSecurityConfig defines a security requirement for an operation. 23 | type OperationSecurityConfig struct { 24 | Name string 25 | Scopes []string 26 | } 27 | 28 | // OperationOption applies configuration to an OpenAPI operation. 29 | type OperationOption func(*OperationConfig) 30 | 31 | // Hidden marks the operation as hidden in the OpenAPI documentation. 32 | // 33 | // This is useful for internal or non-public endpoints. 34 | func Hidden(hide ...bool) OperationOption { 35 | return func(cfg *OperationConfig) { 36 | cfg.Hide = util.Optional(true, hide...) 37 | } 38 | } 39 | 40 | // OperationID sets the unique operation ID for the OpenAPI operation. 41 | func OperationID(id string) OperationOption { 42 | return func(cfg *OperationConfig) { 43 | cfg.OperationID = id 44 | } 45 | } 46 | 47 | // Description sets the detailed description for the OpenAPI operation. 48 | func Description(description string) OperationOption { 49 | return func(cfg *OperationConfig) { 50 | cfg.Description = description 51 | } 52 | } 53 | 54 | // Summary sets a short summary for the OpenAPI operation. 55 | // 56 | // If no description is set, the summary is also used as the description. 57 | func Summary(summary string) OperationOption { 58 | return func(cfg *OperationConfig) { 59 | cfg.Summary = summary 60 | if cfg.Description == "" { 61 | cfg.Description = summary 62 | } 63 | } 64 | } 65 | 66 | // Deprecated marks the operation as deprecated. 67 | // 68 | // Deprecated operations should not be used by clients. 69 | func Deprecated(deprecated ...bool) OperationOption { 70 | return func(cfg *OperationConfig) { 71 | cfg.Deprecated = util.Optional(true, deprecated...) 72 | } 73 | } 74 | 75 | // Tags adds tags to the OpenAPI operation. 76 | // 77 | // Tags help organize operations in the generated documentation. 78 | func Tags(tags ...string) OperationOption { 79 | return func(cfg *OperationConfig) { 80 | cfg.Tags = append(cfg.Tags, tags...) 81 | } 82 | } 83 | 84 | // Security adds a security requirement to the OpenAPI operation. 85 | // 86 | // Example: 87 | // 88 | // r.Get("/me", 89 | // option.Security("bearerAuth"), 90 | // ) 91 | func Security(securityName string, scopes ...string) OperationOption { 92 | return func(cfg *OperationConfig) { 93 | cfg.Security = append(cfg.Security, OperationSecurityConfig{ 94 | Name: securityName, 95 | Scopes: scopes, 96 | }) 97 | } 98 | } 99 | 100 | // Request adds a request body or parameter structure to the OpenAPI operation. 101 | func Request(structure any, options ...ContentOption) OperationOption { 102 | return func(cfg *OperationConfig) { 103 | cu := &openapi.ContentUnit{ 104 | Structure: structure, 105 | } 106 | for _, opt := range options { 107 | opt(cu) 108 | } 109 | cfg.Requests = append(cfg.Requests, cu) 110 | } 111 | } 112 | 113 | // Response adds a response for the OpenAPI operation. 114 | // 115 | // The HTTP status code defines which response is described. 116 | func Response(httpStatus int, structure any, options ...ContentOption) OperationOption { 117 | return func(cfg *OperationConfig) { 118 | cu := &openapi.ContentUnit{ 119 | HTTPStatus: httpStatus, 120 | Structure: structure, 121 | } 122 | for _, opt := range options { 123 | opt(cu) 124 | } 125 | cfg.Responses = append(cfg.Responses, cu) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /adapter/httpopenapi/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 17 | github.com/oaswrap/spec v0.3.6/go.mod h1:e6cGQJcVCkQozwsw8T0ydSWEgQPA/dHFmQME4KawOYU= 18 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 19 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 20 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 22 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 23 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 24 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 25 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 26 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 27 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 28 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 29 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 30 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 31 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 32 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 33 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 34 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 35 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 36 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 37 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 40 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 42 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 43 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 44 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 45 | -------------------------------------------------------------------------------- /testdata/group_routes_31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | description: This is the API documentation for Group Routes 4 | title: 'API Doc: Group Routes' 5 | version: 1.0.0 6 | paths: 7 | /api/v1/auth/login: 8 | post: 9 | deprecated: true 10 | description: User Login v1 11 | requestBody: 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/SpecTestLoginRequest' 16 | responses: 17 | "200": 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/SpecTestToken' 22 | description: OK 23 | summary: User Login v1 24 | tags: 25 | - Authentication 26 | /api/v1/auth/me: 27 | get: 28 | deprecated: true 29 | description: Get Profile v1 30 | responses: 31 | "200": 32 | content: 33 | application/json: 34 | schema: 35 | $ref: '#/components/schemas/SpecTestUser' 36 | description: OK 37 | security: 38 | - bearerAuth: [] 39 | summary: Get Profile v1 40 | tags: 41 | - Authentication 42 | /api/v2/auth/login: 43 | post: 44 | description: User Login v2 45 | requestBody: 46 | content: 47 | application/json: 48 | schema: 49 | $ref: '#/components/schemas/SpecTestLoginRequest' 50 | responses: 51 | "200": 52 | content: 53 | application/json: 54 | schema: 55 | $ref: '#/components/schemas/SpecTestToken' 56 | description: OK 57 | summary: User Login v2 58 | tags: 59 | - Authentication 60 | /api/v2/auth/me: 61 | get: 62 | description: Get Profile v2 63 | responses: 64 | "200": 65 | content: 66 | application/json: 67 | schema: 68 | $ref: '#/components/schemas/SpecTestUser' 69 | description: OK 70 | security: 71 | - bearerAuth: [] 72 | summary: Get Profile v2 73 | tags: 74 | - Profile 75 | - Authentication 76 | /api/v2/profile: 77 | put: 78 | description: Update Profile v2 79 | requestBody: 80 | content: 81 | application/json: 82 | schema: 83 | $ref: '#/components/schemas/SpecTestUser' 84 | responses: 85 | "200": 86 | content: 87 | application/json: 88 | schema: 89 | $ref: '#/components/schemas/SpecTestUser' 90 | description: OK 91 | security: 92 | - bearerAuth: [] 93 | summary: Update Profile v2 94 | tags: 95 | - Profile 96 | components: 97 | schemas: 98 | SpecTestLoginRequest: 99 | properties: 100 | password: 101 | examples: 102 | - password123 103 | type: string 104 | username: 105 | examples: 106 | - john_doe 107 | type: string 108 | required: 109 | - username 110 | - password 111 | type: object 112 | SpecTestToken: 113 | properties: 114 | token: 115 | examples: 116 | - abc123 117 | type: string 118 | type: object 119 | SpecTestUser: 120 | properties: 121 | age: 122 | type: 123 | - "null" 124 | - integer 125 | created_at: 126 | format: date-time 127 | type: string 128 | email: 129 | type: string 130 | id: 131 | type: integer 132 | updated_at: 133 | format: date-time 134 | type: string 135 | username: 136 | type: string 137 | type: object 138 | securitySchemes: 139 | bearerAuth: 140 | scheme: Bearer 141 | type: http 142 | -------------------------------------------------------------------------------- /option/group_test.go: -------------------------------------------------------------------------------- 1 | package option_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/oaswrap/spec/option" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGroupTags(t *testing.T) { 11 | t.Run("adds single tag", func(t *testing.T) { 12 | cfg := &option.GroupConfig{} 13 | opt := option.GroupTags("auth") 14 | opt(cfg) 15 | 16 | assert.Equal(t, []string{"auth"}, cfg.Tags) 17 | }) 18 | 19 | t.Run("adds multiple tags", func(t *testing.T) { 20 | cfg := &option.GroupConfig{} 21 | opt := option.GroupTags("auth", "user", "admin") 22 | opt(cfg) 23 | 24 | assert.Equal(t, []string{"auth", "user", "admin"}, cfg.Tags) 25 | }) 26 | 27 | t.Run("appends to existing tags", func(t *testing.T) { 28 | cfg := &option.GroupConfig{Tags: []string{"existing"}} 29 | opt := option.GroupTags("new") 30 | opt(cfg) 31 | 32 | assert.Equal(t, []string{"existing", "new"}, cfg.Tags) 33 | }) 34 | } 35 | 36 | func TestGroupSecurity(t *testing.T) { 37 | t.Run("adds security without scopes", func(t *testing.T) { 38 | cfg := &option.GroupConfig{} 39 | opt := option.GroupSecurity("oauth2") 40 | opt(cfg) 41 | 42 | expected := []option.OperationSecurityConfig{ 43 | {Name: "oauth2"}, 44 | } 45 | assert.Equal(t, expected, cfg.Security) 46 | }) 47 | 48 | t.Run("adds security with scopes", func(t *testing.T) { 49 | cfg := &option.GroupConfig{} 50 | opt := option.GroupSecurity("oauth2", "read", "write") 51 | opt(cfg) 52 | 53 | expected := []option.OperationSecurityConfig{ 54 | {Name: "oauth2", Scopes: []string{"read", "write"}}, 55 | } 56 | assert.Equal(t, expected, cfg.Security) 57 | }) 58 | 59 | t.Run("appends to existing security", func(t *testing.T) { 60 | cfg := &option.GroupConfig{ 61 | Security: []option.OperationSecurityConfig{ 62 | {Name: "existing", Scopes: []string{"scope1"}}, 63 | }, 64 | } 65 | opt := option.GroupSecurity("oauth2", "read") 66 | opt(cfg) 67 | 68 | expected := []option.OperationSecurityConfig{ 69 | {Name: "existing", Scopes: []string{"scope1"}}, 70 | {Name: "oauth2", Scopes: []string{"read"}}, 71 | } 72 | assert.Equal(t, expected, cfg.Security) 73 | }) 74 | } 75 | 76 | func TestGroupHidden(t *testing.T) { 77 | t.Run("hides route by default", func(t *testing.T) { 78 | cfg := &option.GroupConfig{} 79 | opt := option.GroupHidden() 80 | opt(cfg) 81 | 82 | assert.True(t, cfg.Hide) 83 | }) 84 | 85 | t.Run("hides route when true", func(t *testing.T) { 86 | cfg := &option.GroupConfig{} 87 | opt := option.GroupHidden(true) 88 | opt(cfg) 89 | 90 | assert.True(t, cfg.Hide) 91 | }) 92 | 93 | t.Run("shows route when false", func(t *testing.T) { 94 | cfg := &option.GroupConfig{} 95 | opt := option.GroupHidden(false) 96 | opt(cfg) 97 | 98 | assert.False(t, cfg.Hide) 99 | }) 100 | 101 | t.Run("uses first value when multiple provided", func(t *testing.T) { 102 | cfg := &option.GroupConfig{} 103 | opt := option.GroupHidden(false, true, false) 104 | opt(cfg) 105 | 106 | assert.False(t, cfg.Hide) 107 | }) 108 | } 109 | 110 | func TestGroupDeprecated(t *testing.T) { 111 | t.Run("deprecated route by default", func(t *testing.T) { 112 | cfg := &option.GroupConfig{} 113 | opt := option.GroupDeprecated() 114 | opt(cfg) 115 | 116 | assert.True(t, cfg.Deprecated) 117 | }) 118 | 119 | t.Run("deprecated route when true", func(t *testing.T) { 120 | cfg := &option.GroupConfig{} 121 | opt := option.GroupDeprecated(true) 122 | opt(cfg) 123 | 124 | assert.True(t, cfg.Deprecated) 125 | }) 126 | 127 | t.Run("not deprecated route when false", func(t *testing.T) { 128 | cfg := &option.GroupConfig{} 129 | opt := option.GroupDeprecated(false) 130 | opt(cfg) 131 | 132 | assert.False(t, cfg.Deprecated) 133 | }) 134 | 135 | t.Run("uses first value when multiple provided", func(t *testing.T) { 136 | cfg := &option.GroupConfig{} 137 | opt := option.GroupDeprecated(false, true, false) 138 | opt(cfg) 139 | 140 | assert.False(t, cfg.Deprecated) 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | specopenapi "github.com/oaswrap/spec/openapi" 5 | "github.com/oaswrap/spec/option" 6 | "github.com/swaggest/openapi-go" 7 | ) 8 | 9 | // Generator defines an interface for building and exporting OpenAPI specifications. 10 | type Generator interface { 11 | Router 12 | 13 | // Config returns the OpenAPI configuration used by the Generator. 14 | Config() *specopenapi.Config 15 | 16 | // GenerateSchema generates the OpenAPI schema in the specified format. 17 | // By default, it generates YAML. Pass "json" to generate JSON instead. 18 | GenerateSchema(formats ...string) ([]byte, error) 19 | 20 | // MarshalYAML returns the OpenAPI specification marshaled as YAML. 21 | MarshalYAML() ([]byte, error) 22 | 23 | // MarshalJSON returns the OpenAPI specification marshaled as JSON. 24 | MarshalJSON() ([]byte, error) 25 | 26 | // Validate checks whether the OpenAPI specification is valid. 27 | Validate() error 28 | 29 | // WriteSchemaTo writes the OpenAPI schema to a file. 30 | // The format is inferred from the file extension: ".yaml" for YAML, ".json" for JSON. 31 | WriteSchemaTo(path string) error 32 | } 33 | 34 | // Router defines methods for registering API routes and operations 35 | // in an OpenAPI specification. It lets you describe HTTP methods, paths, and options. 36 | type Router interface { 37 | // Get registers a GET operation for the given path and options. 38 | Get(path string, opts ...option.OperationOption) Route 39 | 40 | // Post registers a POST operation for the given path and options. 41 | Post(path string, opts ...option.OperationOption) Route 42 | 43 | // Put registers a PUT operation for the given path and options. 44 | Put(path string, opts ...option.OperationOption) Route 45 | 46 | // Delete registers a DELETE operation for the given path and options. 47 | Delete(path string, opts ...option.OperationOption) Route 48 | 49 | // Patch registers a PATCH operation for the given path and options. 50 | Patch(path string, opts ...option.OperationOption) Route 51 | 52 | // Options registers an OPTIONS operation for the given path and options. 53 | Options(path string, opts ...option.OperationOption) Route 54 | 55 | // Head registers a HEAD operation for the given path and options. 56 | Head(path string, opts ...option.OperationOption) Route 57 | 58 | // Trace registers a TRACE operation for the given path and options. 59 | Trace(path string, opts ...option.OperationOption) Route 60 | 61 | // Add registers an operation for the given HTTP method, path, and options. 62 | Add(method, path string, opts ...option.OperationOption) Route 63 | 64 | // NewRoute creates a new route with the given options. 65 | NewRoute(opts ...option.OperationOption) Route 66 | 67 | // Route registers a nested route under the given pattern. 68 | // The provided function receives a Router to define sub-routes. 69 | Route(pattern string, fn func(router Router), opts ...option.GroupOption) Router 70 | 71 | // Group creates a new sub-router with the given path prefix and group options. 72 | Group(pattern string, opts ...option.GroupOption) Router 73 | 74 | // With applies one or more group options to the router. 75 | With(opts ...option.GroupOption) Router 76 | } 77 | 78 | // Route represents a single API route in the OpenAPI specification. 79 | type Route interface { 80 | // Method sets the HTTP method for the route. 81 | Method(method string) Route 82 | // Path sets the HTTP path for the route. 83 | Path(path string) Route 84 | // With applies additional operation options to the route. 85 | With(opts ...option.OperationOption) Route 86 | } 87 | 88 | type reflector interface { 89 | Add(method, path string, opts ...option.OperationOption) 90 | Spec() spec 91 | Validate() error 92 | } 93 | 94 | type spec interface { 95 | MarshalYAML() ([]byte, error) 96 | MarshalJSON() ([]byte, error) 97 | } 98 | 99 | type operationContext interface { 100 | With(opts ...option.OperationOption) operationContext 101 | build() openapi.OperationContext 102 | } 103 | -------------------------------------------------------------------------------- /adapter/chiopenapi/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= 8 | github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 9 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 10 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 11 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 12 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 19 | github.com/oaswrap/spec v0.3.6/go.mod h1:e6cGQJcVCkQozwsw8T0ydSWEgQPA/dHFmQME4KawOYU= 20 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 21 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 25 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 26 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 27 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 28 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 29 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 30 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 31 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 32 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 33 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 34 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 35 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 36 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 37 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 38 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 39 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 42 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 44 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 45 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /adapter/muxopenapi/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 10 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 11 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 12 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 19 | github.com/oaswrap/spec v0.3.6/go.mod h1:e6cGQJcVCkQozwsw8T0ydSWEgQPA/dHFmQME4KawOYU= 20 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 21 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 25 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 26 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 27 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 28 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 29 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 30 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 31 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 32 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 33 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 34 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 35 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 36 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 37 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 38 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 39 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 42 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 44 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 45 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /adapter/httprouteropenapi/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= 2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 10 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 11 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 12 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/oaswrap/spec v0.3.6 h1:igKJvrrEYP/pK5I4TzEzYVcdbbr8eJ1gfALUXgZ/Oc8= 19 | github.com/oaswrap/spec v0.3.6/go.mod h1:e6cGQJcVCkQozwsw8T0ydSWEgQPA/dHFmQME4KawOYU= 20 | github.com/oaswrap/spec-ui v0.1.4 h1:XM2Z/ZS2Su90EtDSVuOHGr2+DLpVc2933mxkn6F4aeU= 21 | github.com/oaswrap/spec-ui v0.1.4/go.mod h1:D8EnD6zbYJ3q65wdltw6QHXfw+nut5XwSSA1xtlSEQQ= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 25 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 26 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 27 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 28 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 29 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 30 | github.com/swaggest/jsonschema-go v0.3.78 h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw= 31 | github.com/swaggest/jsonschema-go v0.3.78/go.mod h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g= 32 | github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= 33 | github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= 34 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k= 35 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= 36 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 37 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 38 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 39 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 42 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 44 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 45 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | --------------------------------------------------------------------------------