From 4c53cd0e0a25913f56ea3f989d4e0d62997cbfde Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Thu, 21 Mar 2024 10:42:59 +0700 Subject: [PATCH] Support env template and basic authentication (#1) --- .github/workflows/test.yaml | 6 +- .hasura-connector/Dockerfile | 2 +- Makefile | 13 + README.md | 17 + go.mod | 17 +- go.sum | 36 +- rest/client.go | 4 +- rest/connector.go | 4 +- rest/connector_test.go | 134 ++- rest/metadata.go | 155 +++ rest/mutation.go | 19 +- rest/query.go | 26 +- rest/schema.go | 113 +- rest/testdata/auth/config.yaml | 3 + rest/testdata/auth/schema.yaml | 115 ++ rest/testdata/petstore3/config.yaml | 2 +- rest/testdata/petstore3/openapi.json | 1321 ---------------------- rest/testdata/petstore3/openapi.yaml | 1548 ++++++++++++++++++++++++++ rest/types.go | 6 + 19 files changed, 2104 insertions(+), 1437 deletions(-) create mode 100644 Makefile create mode 100644 rest/metadata.go create mode 100644 rest/testdata/auth/config.yaml create mode 100644 rest/testdata/auth/schema.yaml delete mode 100644 rest/testdata/petstore3/openapi.json create mode 100644 rest/testdata/petstore3/openapi.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d1e3698..71e3ce4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -39,13 +39,15 @@ jobs: - name: Vet run: go vet ./... - name: Lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: version: latest only-new-issues: true skip-cache: true - name: Run Go unit tests - run: go test -v -coverpkg=./... -race -timeout 3m -coverprofile=coverage.out ./... + run: | + go test -v -coverpkg=./... -race -timeout 3m -coverprofile=coverage.out.tmp ./... + cat coverage.out.tmp | grep -v "main.go" > coverage.out - name: Go coverage format if: ${{ github.event_name == 'pull_request' }} run: | diff --git a/.hasura-connector/Dockerfile b/.hasura-connector/Dockerfile index 2ad4b90..70e3ef6 100644 --- a/.hasura-connector/Dockerfile +++ b/.hasura-connector/Dockerfile @@ -11,7 +11,7 @@ FROM gcr.io/distroless/base-debian12:nonroot # Copy the binary to the production image from the builder stage. COPY --from=builder /app/ndc-cli /ndc-cli -ENV CONFIGURATION /config +ENV HASURA_CONFIGURATION_DIRECTORY /config # Run the web service on container startup. CMD ["/ndc-cli", "serve"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae60339 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: format +format: + gofmt -w -s . + +.PHONY: test +test: + go test -v -race -timeout 3m ./... + +# Install golangci-lint tool to run lint locally +# https://golangci-lint.run/usage/install +.PHONY: lint +lint: + golangci-lint run \ No newline at end of file diff --git a/README.md b/README.md index ed67f5c..d872e42 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,17 @@ files: spec: openapi2 - path: openapi.yaml spec: openapi3 + trimPrefix: /v1 + envPrefix: PET_STORE + methodAlias: + post: create + put: update - path: schema.json spec: ndc ``` +`trimPrefix`, `envPrefix` and `methodAlias` options are used to convert OpenAPI by [ndc-rest-schema](https://github.com/hasura/ndc-rest-schema#openapi). + **Supported specs** - `openapi3`: OpenAPI 3.0/3.1. @@ -45,3 +52,13 @@ ndc-rest convert -f ./rest/testdata/jsonplaceholder/swagger.json -o ./rest/testd ``` > The `convert` command is imported from the [NDC REST Schema](https://github.com/hasura/ndc-rest-schema#quick-start) CLI tool. + +### Environment variable template + +The connector can replaces `{{xxx}}` templates with environment variables. The converter automatically renders variables for API keys and tokens when converting OpenAPI documents. However, you can add your custom variables as well. + +### Authentication + +The current version supports API key and Auth token authentication schemes. The configuration is inspired from `securitySchemes` [with env variables](https://github.com/hasura/ndc-rest-schema#authentication) + +See [this example](rest/testdata/auth/schema.yaml) for more context. diff --git a/go.mod b/go.mod index 9ea4ae8..512f6d7 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.21 toolchain go1.22.0 require ( - github.com/hasura/ndc-rest-schema v0.0.0-20240312024503-345ae7cb6078 - github.com/hasura/ndc-sdk-go v0.1.1-0.20240312092535-8e480ed1490d + github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217 + github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3 github.com/lmittmann/tint v1.0.4 gopkg.in/yaml.v3 v3.0.1 ) @@ -22,15 +22,14 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pb33f/libopenapi v0.15.14 // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.49.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/prometheus/common v0.50.0 // indirect + github.com/prometheus/procfs v0.13.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opentelemetry.io/otel v1.24.0 // indirect @@ -45,13 +44,13 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect google.golang.org/grpc v1.62.1 // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index cacc068..d04e8de 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -54,12 +54,12 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= -github.com/hasura/ndc-rest-schema v0.0.0-20240312024503-345ae7cb6078 h1:5lV4lnOfzP+U+yykgaqWESm6L3fTdSmDEVkzY0NDf8s= -github.com/hasura/ndc-rest-schema v0.0.0-20240312024503-345ae7cb6078/go.mod h1:sa89qdTWWNQUHgHx3FST6oBdTcaW9CBbvW2TFne3oQE= -github.com/hasura/ndc-sdk-go v0.1.1-0.20240312081636-37f5f535293c h1:x9VzFBUG5xKz50CYiOtIZTDy3jByRcolIHGi+CcpJBY= -github.com/hasura/ndc-sdk-go v0.1.1-0.20240312081636-37f5f535293c/go.mod h1:u4+Xp/6icjMUQMGil6tgD05y5ZH1eXZ8gX2OCM/kgcU= -github.com/hasura/ndc-sdk-go v0.1.1-0.20240312092535-8e480ed1490d h1:EhmMj8kWlrxxd0gf4naCsUeX0bIjCjGXF9QdBKndn5w= -github.com/hasura/ndc-sdk-go v0.1.1-0.20240312092535-8e480ed1490d/go.mod h1:u4+Xp/6icjMUQMGil6tgD05y5ZH1eXZ8gX2OCM/kgcU= +github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855 h1:d3bq7jjsK/aPcFih6B+h1uM3CkNY24F8Eo5JXx8QBo8= +github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855/go.mod h1:R1xvYOx/TqgHEiP5Nm8qTRwFfJmxmV1y1De4b1i1CFg= +github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217 h1:HhfRPEHzEpBO2j55oHV7r+uBQcg2KMgWlmEtp3TvWO4= +github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217/go.mod h1:R1xvYOx/TqgHEiP5Nm8qTRwFfJmxmV1y1De4b1i1CFg= +github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3 h1:A1N3ilX1EIxjTA2qaHPXFnIECpYjKhIFmtva8qJrpHk= +github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3/go.mod h1:EeM3hKbhCfBjDDva8mP4D2KeptTqAaxNqNw8rFQAnMs= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -99,10 +99,10 @@ github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7km github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= -github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ= +github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ= +github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= +github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -147,8 +147,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -195,10 +195,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 h1:8eadJkXbwDEMNwcB5O0s5Y5eCfyuCLdvaiOIaGTrWmQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/rest/client.go b/rest/client.go index 532cd07..f867e1f 100644 --- a/rest/client.go +++ b/rest/client.go @@ -155,7 +155,9 @@ func evalHTTPResponse(resp *http.Response, selection schema.NestedField) (any, e return utils.EvalNestedColumnFields(selection, result) default: - return nil, fmt.Errorf("unsupported content type %s", contentType) + return nil, schema.NewConnectorError(http.StatusInternalServerError, "failed to evaluate response", map[string]any{ + "cause": fmt.Sprintf("unsupported content type %s", contentType), + }) } } diff --git a/rest/connector.go b/rest/connector.go index f2763a2..c50ffff 100644 --- a/rest/connector.go +++ b/rest/connector.go @@ -7,7 +7,6 @@ import ( "fmt" "os" - rest "github.com/hasura/ndc-rest-schema/schema" "github.com/hasura/ndc-sdk-go/connector" "github.com/hasura/ndc-sdk-go/schema" "gopkg.in/yaml.v3" @@ -15,10 +14,9 @@ import ( // RESTConnector implements the SDK interface of NDC specification type RESTConnector struct { + metadata RESTMetadataCollection capabilities *schema.RawCapabilitiesResponse schema *schema.RawSchemaResponse - functions map[string]rest.RESTFunctionInfo - procedures map[string]rest.RESTProcedureInfo client *httpClient } diff --git a/rest/connector_test.go b/rest/connector_test.go index d576c2b..a137786 100644 --- a/rest/connector_test.go +++ b/rest/connector_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "net/http/httptest" "os" "path" "reflect" @@ -85,6 +86,137 @@ func TestRESTConnector_configurationFailure(t *testing.T) { assertError(t, err, "the config.{json,yaml,yml} file does not exist at") } +func TestRESTConnector_authentication(t *testing.T) { + apiKey := "random_api_key" + bearerToken := "random_bearer_token" + server := createMockServer(t, apiKey, bearerToken) + defer server.Close() + + t.Setenv("PET_STORE_URL", server.URL) + t.Setenv("PET_STORE_API_KEY", apiKey) + t.Setenv("PET_STORE_BEARER_TOKEN", bearerToken) + connServer, err := connector.NewServer(NewRESTConnector(), &connector.ServerOptions{ + Configuration: "testdata/auth", + }, connector.WithoutRecovery()) + assertNoError(t, err) + testServer := connServer.BuildTestServer() + defer testServer.Close() + + t.Run("auth_default", func(t *testing.T) { + reqBody := []byte(`{ + "collection": "findPets", + "query": { + "fields": { + "__value": { + "type": "column", + "column": "__value" + } + } + }, + "arguments": {}, + "collection_relationships": {} + }`) + + res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(reqBody)) + assertNoError(t, err) + assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{ + { + Rows: []map[string]any{ + {"__value": map[string]any{}}, + }, + }, + }) + }) + + t.Run("auth_api_key", func(t *testing.T) { + reqBody := []byte(`{ + "operations": [ + { + "type": "procedure", + "name": "addPet", + "arguments": {} + } + ], + "collection_relationships": {} + }`) + + res, err := http.Post(fmt.Sprintf("%s/mutation", testServer.URL), "application/json", bytes.NewBuffer(reqBody)) + assertNoError(t, err) + assertHTTPResponse(t, res, http.StatusOK, schema.MutationResponse{ + OperationResults: []schema.MutationOperationResults{ + schema.NewProcedureResult(map[string]any{}).Encode(), + }, + }) + }) + + t.Run("auth_bearer", func(t *testing.T) { + reqBody := []byte(`{ + "collection": "findPetsByStatus", + "query": { + "fields": { + "__value": { + "type": "column", + "column": "__value" + } + } + }, + "arguments": {}, + "collection_relationships": {} + }`) + + res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(reqBody)) + assertNoError(t, err) + assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{ + { + Rows: []map[string]any{ + {"__value": map[string]any{}}, + }, + }, + }) + }) +} + +func createMockServer(t *testing.T, apiKey string, bearerToken string) *httptest.Server { + mux := http.NewServeMux() + + writeResponse := func(w http.ResponseWriter) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{}`)) + } + mux.HandleFunc("/pet", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet, http.MethodPost: + if r.Header.Get("api_key") != apiKey { + t.Errorf("invalid api key, expected %s, got %s", apiKey, r.Header.Get("api_key")) + t.FailNow() + return + } + writeResponse(w) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + }) + + mux.HandleFunc("/pet/findByStatus", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + if r.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", bearerToken) { + t.Errorf("invalid bearer token, expected %s, got %s", bearerToken, r.Header.Get("Authorization")) + t.FailNow() + return + } + writeResponse(w) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + }) + + return httptest.NewServer(mux) +} + func assertNdcOperations(t *testing.T, dir string, targetURL string) { queryFiles, err := os.ReadDir(dir) if err != nil { @@ -117,7 +249,7 @@ func test_createServer(t *testing.T, dir string) *connector.Server[Configuration c := NewRESTConnector() server, err := connector.NewServer(c, &connector.ServerOptions{ Configuration: dir, - }) + }, connector.WithoutRecovery()) if err != nil { t.Errorf("failed to start server: %s", err) t.FailNow() diff --git a/rest/metadata.go b/rest/metadata.go new file mode 100644 index 0000000..011c2e4 --- /dev/null +++ b/rest/metadata.go @@ -0,0 +1,155 @@ +package rest + +import ( + "fmt" + "net/url" + "strings" + + rest "github.com/hasura/ndc-rest-schema/schema" + "github.com/hasura/ndc-rest-schema/utils" + "github.com/hasura/ndc-sdk-go/schema" +) + +// RESTMetadataCollection stores list of REST metadata with helper methods +type RESTMetadataCollection []RESTMetadata + +func (rms RESTMetadataCollection) GetFunction(name string) (*rest.RESTFunctionInfo, error) { + for _, rm := range rms { + fn, err := rm.GetFunction(name) + if err != nil { + return nil, err + } + if fn != nil { + return fn, nil + } + } + return nil, schema.UnprocessableContentError(fmt.Sprintf("unsupported query: %s", name), nil) +} + +func (rms RESTMetadataCollection) GetProcedure(name string) (*rest.RESTProcedureInfo, error) { + for _, rm := range rms { + fn, err := rm.GetProcedure(name) + if err != nil { + return nil, err + } + if fn != nil { + return fn, nil + } + } + return nil, schema.UnprocessableContentError(fmt.Sprintf("unsupported query: %s", name), nil) +} + +// RESTMetadata stores REST schema with handy methods to build requests +type RESTMetadata struct { + settings *rest.NDCRestSettings + functions map[string]rest.RESTFunctionInfo + procedures map[string]rest.RESTProcedureInfo +} + +func (rm RESTMetadata) GetFunction(name string) (*rest.RESTFunctionInfo, error) { + fn, ok := rm.functions[name] + if !ok { + return nil, nil + } + + req, err := rm.buildRequest(fn.Request) + if err != nil { + return nil, err + } + return &rest.RESTFunctionInfo{ + Request: req, + FunctionInfo: fn.FunctionInfo, + }, nil +} + +func (rm RESTMetadata) GetProcedure(name string) (*rest.RESTProcedureInfo, error) { + fn, ok := rm.procedures[name] + if !ok { + return nil, nil + } + + req, err := rm.buildRequest(fn.Request) + if err != nil { + return nil, err + } + return &rest.RESTProcedureInfo{ + Request: req, + ProcedureInfo: fn.ProcedureInfo, + }, nil +} + +func (rm RESTMetadata) buildRequest(rawReq *rest.Request) (*rest.Request, error) { + req := rawReq.Clone() + req.URL = rm.buildURL(req.URL) + + return rm.applySecurity(req) +} + +func (rm RESTMetadata) buildURL(endpoint string) string { + if strings.HasPrefix(endpoint, "http") { + return endpoint + } + var host string + for _, server := range rm.settings.Servers { + host = server.URL + break + } + + return fmt.Sprintf("%s%s", host, endpoint) +} + +func (rm RESTMetadata) applySecurity(req *rest.Request) (*rest.Request, error) { + if req.Security.IsEmpty() { + req.Security = rm.settings.Security + } + if req.Security.IsOptional() || len(rm.settings.SecuritySchemes) == 0 { + return req, nil + } + + security := req.Security.First() + securityScheme, ok := rm.settings.SecuritySchemes[security.Name()] + if !ok { + return req, nil + } + + if req.Headers == nil { + req.Headers = make(map[string]string) + } + + switch securityScheme.Type { + case rest.HTTPAuthScheme: + headerName := securityScheme.Header + if headerName == "" { + headerName = "Authorization" + } + scheme := securityScheme.Scheme + if scheme == "bearer" || scheme == "basic" { + scheme = utils.ToPascalCase(securityScheme.Scheme) + } + req.Headers[headerName] = fmt.Sprintf("%s %s", scheme, securityScheme.Value) + case rest.APIKeyScheme: + switch securityScheme.In { + case rest.APIKeyInHeader: + req.Headers[securityScheme.Name] = securityScheme.Value + case rest.APIKeyInQuery: + endpoint, err := url.Parse(req.URL) + if err != nil { + return nil, err + } + + q := endpoint.Query() + q.Add(securityScheme.Name, securityScheme.Value) + endpoint.RawQuery = q.Encode() + req.URL = endpoint.String() + case rest.APIKeyInCookie: + req.Headers["Cookie"] = fmt.Sprintf("%s=%s", securityScheme.Name, securityScheme.Value) + default: + return nil, fmt.Errorf("unsupported location for apiKey scheme: %s", securityScheme.In) + } + // TODO: support OAuth and OIDC + default: + return nil, fmt.Errorf("unsupported security scheme: %s", securityScheme.Type) + } + + return req, nil +} diff --git a/rest/mutation.go b/rest/mutation.go index 6312161..07a28cd 100644 --- a/rest/mutation.go +++ b/rest/mutation.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - rest "github.com/hasura/ndc-rest-schema/schema" "github.com/hasura/ndc-sdk-go/schema" ) @@ -33,9 +32,9 @@ func (c *RESTConnector) Mutation(ctx context.Context, configuration *Configurati func (c *RESTConnector) execProcedure(ctx context.Context, operation *schema.MutationOperation) (schema.MutationOperationResults, error) { - procedure := c.getProcedure(operation.Name) - if procedure == nil { - return nil, schema.BadRequestError(fmt.Sprintf("unsupported procedure operation: %s", operation.Name), nil) + procedure, err := c.metadata.GetProcedure(operation.Name) + if err != nil { + return nil, err } // 1. resolve arguments, evaluate URL and query parameters @@ -55,18 +54,10 @@ func (c *RESTConnector) execProcedure(ctx context.Context, operation *schema.Mut // 2. create and execute request // 3. evaluate response selection - rawRequest := procedure.Request.Clone() - rawRequest.URL = endpoint - result, err := c.client.Send(ctx, rawRequest, headers, rawArgs["body"], operation.Fields) + procedure.Request.URL = endpoint + result, err := c.client.Send(ctx, procedure.Request, headers, rawArgs["body"], operation.Fields) if err != nil { return nil, err } return schema.NewProcedureResult(result).Encode(), nil } - -func (c *RESTConnector) getProcedure(key string) *rest.RESTProcedureInfo { - if item, ok := c.procedures[key]; ok { - return &item - } - return nil -} diff --git a/rest/query.go b/rest/query.go index aad5db8..64f021a 100644 --- a/rest/query.go +++ b/rest/query.go @@ -18,7 +18,7 @@ import ( func (c *RESTConnector) Query(ctx context.Context, configuration *Configuration, state *State, request *schema.QueryRequest) (schema.QueryResponse, error) { valueField, err := utils.EvalFunctionSelectionFieldValue(request) if err != nil { - return nil, schema.BadRequestError(err.Error(), nil) + return nil, schema.UnprocessableContentError(err.Error(), nil) } requestVars := request.Variables if len(requestVars) == 0 { @@ -46,38 +46,30 @@ func (c *RESTConnector) Query(ctx context.Context, configuration *Configuration, func (c *RESTConnector) execQuery(ctx context.Context, request *schema.QueryRequest, queryFields schema.NestedField, variables map[string]any) (any, error) { - function := c.getFunction(request.Collection) - if function == nil { - return nil, schema.BadRequestError(fmt.Sprintf("unsupported query: %s", request.Collection), nil) + function, err := c.metadata.GetFunction(request.Collection) + if err != nil { + return nil, err } // 1. resolve arguments, evaluate URL and query parameters rawArgs, err := utils.ResolveArgumentVariables(request.Arguments, variables) if err != nil { - return nil, schema.BadRequestError("failed to resolve argument variables", map[string]any{ + return nil, schema.UnprocessableContentError("failed to resolve argument variables", map[string]any{ "cause": err.Error(), }) } endpoint, headers, err := evalURLAndHeaderParameters(function.Request, function.Arguments, rawArgs) if err != nil { - return nil, schema.BadRequestError("failed to evaluate URL and Headers from parameters", map[string]any{ + return nil, schema.UnprocessableContentError("failed to evaluate URL and Headers from parameters", map[string]any{ "cause": err.Error(), }) } // 2. create and execute request // 3. evaluate response selection - rawRequest := function.Request.Clone() - rawRequest.URL = endpoint - - return c.client.Send(ctx, rawRequest, headers, nil, queryFields) -} + function.Request.URL = endpoint -func (c *RESTConnector) getFunction(key string) *rest.RESTFunctionInfo { - if item, ok := c.functions[key]; ok { - return &item - } - return nil + return c.client.Send(ctx, function.Request, headers, nil, queryFields) } func evalURLAndHeaderParameters(request *rest.Request, argumentsSchema map[string]schema.ArgumentInfo, arguments map[string]any) (string, http.Header, error) { @@ -142,7 +134,7 @@ func evalURLAndHeaderParameterBySchema(endpoint *url.URL, header *http.Header, p switch param.In { case rest.InHeader: - // TODO: set header + header.Set(param.Name, valueStr) case rest.InQuery: q := endpoint.Query() q.Add(param.Name, valueStr) diff --git a/rest/schema.go b/rest/schema.go index 0cfef09..e1b0e7a 100644 --- a/rest/schema.go +++ b/rest/schema.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log/slog" + "os" "path" "strings" @@ -21,18 +22,38 @@ func (c *RESTConnector) GetSchema(ctx context.Context, configuration *Configurat return c.schema, nil } +func getEnvVariables() map[string]string { + results := make(map[string]string) + for _, env := range os.Environ() { + if env == "" { + continue + } + keyValues := strings.Split(env, "=") + if len(keyValues) < 2 { + continue + } + value := strings.Trim(strings.Join(keyValues[1:], "="), `"`) + results[keyValues[0]] = value + } + return results +} + // build NDC REST schema from file list -func buildSchemaFiles(configDir string, files []SchemaFile, logger *slog.Logger) (map[string]*rest.NDCRestSchema, map[string][]string) { - schemas := make(map[string]*rest.NDCRestSchema) +func buildSchemaFiles(configDir string, files []SchemaFile, logger *slog.Logger) ([]ndcRestSchemaWithName, map[string][]string) { + envVars := getEnvVariables() + schemas := make([]ndcRestSchemaWithName, len(files)) errors := make(map[string][]string) - for _, file := range files { + for i, file := range files { var errs []string - schemaOutput, err := buildSchemaFile(configDir, &file, logger) + schemaOutput, err := buildSchemaFile(configDir, &file, envVars, logger) if err != nil { errs = append(errs, err.Error()) } if schemaOutput != nil { - schemas[file.Path] = schemaOutput + schemas[i] = ndcRestSchemaWithName{ + name: file.Path, + schema: schemaOutput, + } } if len(errs) > 0 { errors[file.Path] = errs @@ -42,7 +63,7 @@ func buildSchemaFiles(configDir string, files []SchemaFile, logger *slog.Logger) return schemas, errors } -func buildSchemaFile(configDir string, conf *SchemaFile, logger *slog.Logger) (*rest.NDCRestSchema, error) { +func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]string, logger *slog.Logger) (*rest.NDCRestSchema, error) { if conf.Path == "" { return nil, errors.New("file path is empty") } @@ -55,21 +76,28 @@ func buildSchemaFile(configDir string, conf *SchemaFile, logger *slog.Logger) (* if err != nil { return nil, err } + + // replace environment variables + rawString := string(rawBytes) + for key, val := range envVars { + rawString = strings.ReplaceAll(rawString, fmt.Sprintf("{{%s}}", key), val) + } + switch conf.Spec { case rest.NDCSpec: var result rest.NDCRestSchema - fileFormat, err := rest.ParseSchemaFileFormat(conf.Path) + fileFormat, err := rest.ParseSchemaFileFormat(strings.Trim(path.Ext(conf.Path), ".")) if err != nil { return nil, err } switch fileFormat { case rest.SchemaFileJSON: - if err := json.Unmarshal(rawBytes, &result); err != nil { + if err := json.Unmarshal([]byte(rawString), &result); err != nil { return nil, err } return &result, nil case rest.SchemaFileYAML: - if err := yaml.Unmarshal(rawBytes, &result); err != nil { + if err := yaml.Unmarshal([]byte(rawString), &result); err != nil { return nil, err } return &result, nil @@ -105,7 +133,7 @@ func buildSchemaFile(configDir string, conf *SchemaFile, logger *slog.Logger) (* } } -func (c *RESTConnector) applyNDCRestSchemas(schemas map[string]*rest.NDCRestSchema) map[string][]string { +func (c *RESTConnector) applyNDCRestSchemas(schemas []ndcRestSchemaWithName) map[string][]string { ndcSchema := &schema.SchemaResponse{ Collections: []schema.CollectionInfo{}, ScalarTypes: make(schema.SchemaResponseScalarTypes), @@ -115,33 +143,34 @@ func (c *RESTConnector) applyNDCRestSchemas(schemas map[string]*rest.NDCRestSche procedures := map[string]rest.RESTProcedureInfo{} errors := make(map[string][]string) - for key, item := range schemas { - var host string - var errs []string - timeout := defaultTimeout - - if item.Settings != nil { - if item.Settings.Timeout > 0 { - timeout = item.Settings.Timeout + for _, item := range schemas { + settings := item.schema.Settings + if settings == nil { + settings = &rest.NDCRestSettings{} + if settings.Timeout == 0 { + settings.Timeout = defaultTimeout } - host = item.Settings.URL } + meta := RESTMetadata{ + settings: settings, + } + var errs []string - for name, scalar := range item.ScalarTypes { + for name, scalar := range item.schema.ScalarTypes { ndcSchema.ScalarTypes[name] = scalar } - for name, object := range item.ObjectTypes { + for name, object := range item.schema.ObjectTypes { ndcSchema.ObjectTypes[name] = object } - ndcSchema.Collections = append(ndcSchema.Collections, item.Collections...) + ndcSchema.Collections = append(ndcSchema.Collections, item.schema.Collections...) var functionSchemas []schema.FunctionInfo var procedureSchemas []schema.ProcedureInfo - for _, fnItem := range item.Functions { + for _, fnItem := range item.schema.Functions { if fnItem.Request == nil || fnItem.Request.URL == "" { continue } - req, err := validateRequestSchema(fnItem.Request, host, "get", timeout) + req, err := validateRequestSchema(fnItem.Request, "get", settings.Timeout) if err != nil { errs = append(errs, fmt.Sprintf("function %s: %s", fnItem.Name, err)) continue @@ -154,11 +183,11 @@ func (c *RESTConnector) applyNDCRestSchemas(schemas map[string]*rest.NDCRestSche functionSchemas = append(functionSchemas, fn.FunctionInfo) } - for _, procItem := range item.Procedures { + for _, procItem := range item.schema.Procedures { if procItem.Request == nil || procItem.Request.URL == "" { continue } - req, err := validateRequestSchema(procItem.Request, host, "", timeout) + req, err := validateRequestSchema(procItem.Request, "", settings.Timeout) if err != nil { errs = append(errs, fmt.Sprintf("procedure %s: %s", procItem.Name, err)) continue @@ -171,11 +200,15 @@ func (c *RESTConnector) applyNDCRestSchemas(schemas map[string]*rest.NDCRestSche } if len(errs) > 0 { - errors[key] = errs + errors[item.name] = errs continue } ndcSchema.Functions = append(ndcSchema.Functions, functionSchemas...) ndcSchema.Procedures = append(ndcSchema.Procedures, procedureSchemas...) + + meta.functions = functions + meta.procedures = procedures + c.metadata = append(c.metadata, meta) } schemaBytes, err := json.Marshal(ndcSchema) @@ -188,20 +221,10 @@ func (c *RESTConnector) applyNDCRestSchemas(schemas map[string]*rest.NDCRestSche } c.schema = schema.NewRawSchemaResponseUnsafe(schemaBytes) - c.functions = functions - c.procedures = procedures return nil } -func validateRequestSchema(req *rest.Request, host string, defaultMethod string, defTimeout uint) (*rest.Request, error) { - endpoint := req.URL - if !strings.HasPrefix(endpoint, "http") && host != "" { - endpoint = fmt.Sprintf("%s%s", host, req.URL) - } - if !strings.HasPrefix(endpoint, "http") { - return nil, fmt.Errorf("the URL is invalid: %s", endpoint) - } - +func validateRequestSchema(req *rest.Request, defaultMethod string, defTimeout uint) (*rest.Request, error) { if req.Method == "" { if defaultMethod == "" { return nil, fmt.Errorf("the HTTP method is required") @@ -212,19 +235,11 @@ func validateRequestSchema(req *rest.Request, host string, defaultMethod string, if req.Type == "" { req.Type = rest.RequestTypeREST } - timeout := req.Timeout - if timeout == 0 { - timeout = defTimeout + if req.Timeout == 0 { + req.Timeout = defTimeout } - return &rest.Request{ - URL: endpoint, - Method: req.Method, - Type: req.Type, - Headers: req.Headers, - Parameters: req.Parameters, - Timeout: timeout, - }, nil + return req, nil } func printSchemaValidationError(logger *slog.Logger, errors map[string][]string) { diff --git a/rest/testdata/auth/config.yaml b/rest/testdata/auth/config.yaml new file mode 100644 index 0000000..a312d2f --- /dev/null +++ b/rest/testdata/auth/config.yaml @@ -0,0 +1,3 @@ +files: + - path: schema.yaml + spec: ndc diff --git a/rest/testdata/auth/schema.yaml b/rest/testdata/auth/schema.yaml new file mode 100644 index 0000000..1b85650 --- /dev/null +++ b/rest/testdata/auth/schema.yaml @@ -0,0 +1,115 @@ +--- +settings: + servers: + - url: "{{PET_STORE_URL}}" + securitySchemes: + api_key: + type: apiKey + value: "{{PET_STORE_API_KEY}}" + in: header + name: api_key + bearer: + type: http + value: "{{PET_STORE_BEARER_TOKEN}}" + scheme: bearer + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + read:pets: read your pets + write:pets: modify pets in your account + security: + - api_key: [] + version: 1.0.18 +collections: [] +functions: + - request: + url: "/pet" + method: get + parameters: [] + security: [] + arguments: {} + description: Finds Pets + name: findPets + result_type: + element_type: + name: Pet + type: named + type: array + - request: + url: "/pet/findByStatus" + method: get + parameters: + - name: status + in: query + required: false + schema: + type: String + enum: + - available + - pending + - sold + security: + - bearer: [] + arguments: + status: + description: Status values that need to be considered for filter + type: + type: nullable + underlying_type: + name: String + type: named + description: Finds Pets by status + name: findPetsByStatus + result_type: + element_type: + name: Pet + type: named + type: array +procedures: + - request: + url: "/pet" + method: post + headers: + Content-Type: application/json + security: + - api_key: [] + arguments: + body: + description: Request body of /pet + type: + name: Pet + type: named + description: Add a new pet to the store + name: addPet + result_type: + name: Pet + type: named +object_types: + Pet: + fields: + id: + type: + type: nullable + underlying_type: + name: Int + type: named + name: + type: + name: String + type: named +scalar_types: + Boolean: + aggregate_functions: {} + comparison_operators: {} + Int: + aggregate_functions: {} + comparison_operators: {} + JSON: + aggregate_functions: {} + comparison_operators: {} + String: + aggregate_functions: {} + comparison_operators: {} diff --git a/rest/testdata/petstore3/config.yaml b/rest/testdata/petstore3/config.yaml index 6935280..9044c73 100644 --- a/rest/testdata/petstore3/config.yaml +++ b/rest/testdata/petstore3/config.yaml @@ -1,3 +1,3 @@ files: - - path: openapi.json + - path: openapi.yaml spec: openapi3 diff --git a/rest/testdata/petstore3/openapi.json b/rest/testdata/petstore3/openapi.json deleted file mode 100644 index 4d61c5e..0000000 --- a/rest/testdata/petstore3/openapi.json +++ /dev/null @@ -1,1321 +0,0 @@ -{ - "openapi": "3.0.2", - "info": { - "title": "Swagger Petstore - OpenAPI 3.0", - "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", - "termsOfService": "http://swagger.io/terms/", - "contact": { "email": "apiteam@swagger.io" }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0.18" - }, - "externalDocs": { - "description": "Find out more about Swagger", - "url": "http://swagger.io" - }, - "servers": [{ "url": "https://petstore3.swagger.io/api/v3" }], - "tags": [ - { - "name": "pet", - "description": "Everything about your Pets", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } - }, - { - "name": "store", - "description": "Access to Petstore orders", - "externalDocs": { - "description": "Find out more about our store", - "url": "http://swagger.io" - } - }, - { "name": "user", "description": "Operations about user" } - ], - "paths": { - "/pet": { - "put": { - "tags": ["pet"], - "summary": "Update an existing pet", - "description": "Update an existing pet by Id", - "operationId": "updatePet", - "requestBody": { - "description": "Update an existent pet in the store", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/x-www-form-urlencoded": { - "schema": { "$ref": "#/components/schemas/Pet" } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/xml": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/json": { - "schema": { "$ref": "#/components/schemas/Pet" } - } - } - }, - "400": { "description": "Invalid ID supplied" }, - "404": { "description": "Pet not found" }, - "405": { "description": "Validation exception" } - }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - }, - "post": { - "tags": ["pet"], - "summary": "Add a new pet to the store", - "description": "Add a new pet to the store", - "operationId": "addPet", - "requestBody": { - "description": "Create a new pet in the store", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/x-www-form-urlencoded": { - "schema": { "$ref": "#/components/schemas/Pet" } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/xml": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/json": { - "schema": { "$ref": "#/components/schemas/Pet" } - } - } - }, - "405": { "description": "Invalid input" } - }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - } - }, - "/pet/findByStatus": { - "get": { - "tags": ["pet"], - "summary": "Finds Pets by status", - "description": "Multiple status values can be provided with comma separated strings", - "operationId": "findPetsByStatus", - "parameters": [ - { - "name": "status", - "in": "query", - "description": "Status values that need to be considered for filter", - "required": false, - "explode": true, - "schema": { - "type": "string", - "default": "available", - "enum": ["available", "pending", "sold"] - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/xml": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/Pet" } - } - }, - "application/json": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/Pet" } - } - } - } - }, - "400": { "description": "Invalid status value" } - }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - } - }, - "/pet/findByTags": { - "get": { - "tags": ["pet"], - "summary": "Finds Pets by tags", - "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", - "operationId": "findPetsByTags", - "parameters": [ - { - "name": "tags", - "in": "query", - "description": "Tags to filter by", - "required": false, - "explode": true, - "schema": { "type": "array", "items": { "type": "string" } } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/xml": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/Pet" } - } - }, - "application/json": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/Pet" } - } - } - } - }, - "400": { "description": "Invalid tag value" } - }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - } - }, - "/pet/{petId}": { - "get": { - "tags": ["pet"], - "summary": "Find pet by ID", - "description": "Returns a single pet", - "operationId": "getPetById", - "parameters": [ - { - "name": "petId", - "in": "path", - "description": "ID of pet to return", - "required": true, - "schema": { "type": "integer", "format": "int64" } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/xml": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/json": { - "schema": { "$ref": "#/components/schemas/Pet" } - } - } - }, - "400": { "description": "Invalid ID supplied" }, - "404": { "description": "Pet not found" } - }, - "security": [ - { "api_key": [] }, - { "petstore_auth": ["write:pets", "read:pets"] } - ] - }, - "post": { - "tags": ["pet"], - "summary": "Updates a pet in the store with form data", - "description": "", - "operationId": "updatePetWithForm", - "parameters": [ - { - "name": "petId", - "in": "path", - "description": "ID of pet that needs to be updated", - "required": true, - "schema": { "type": "integer", "format": "int64" } - }, - { - "name": "name", - "in": "query", - "description": "Name of pet that needs to be updated", - "schema": { "type": "string" } - }, - { - "name": "status", - "in": "query", - "description": "Status of pet that needs to be updated", - "schema": { "type": "string" } - } - ], - "responses": { "405": { "description": "Invalid input" } }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - }, - "delete": { - "tags": ["pet"], - "summary": "Deletes a pet", - "description": "", - "operationId": "deletePet", - "parameters": [ - { - "name": "api_key", - "in": "header", - "description": "", - "required": false, - "schema": { "type": "string" } - }, - { - "name": "petId", - "in": "path", - "description": "Pet id to delete", - "required": true, - "schema": { "type": "integer", "format": "int64" } - } - ], - "responses": { "400": { "description": "Invalid pet value" } }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - } - }, - "/pet/{petId}/uploadImage": { - "post": { - "tags": ["pet"], - "summary": "uploads an image", - "description": "", - "operationId": "uploadFile", - "parameters": [ - { - "name": "petId", - "in": "path", - "description": "ID of pet to update", - "required": true, - "schema": { "type": "integer", "format": "int64" } - }, - { - "name": "additionalMetadata", - "in": "query", - "description": "Additional Metadata", - "required": false, - "schema": { "type": "string" } - } - ], - "requestBody": { - "content": { - "application/octet-stream": { - "schema": { "type": "string", "format": "binary" } - } - } - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ApiResponse" } - } - } - } - }, - "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] - } - }, - "/store/inventory": { - "get": { - "tags": ["store"], - "summary": "Returns pet inventories by status", - "description": "Returns a map of status codes to quantities", - "operationId": "getInventory", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - } - } - } - } - }, - "security": [{ "api_key": [] }] - } - }, - "/store/order": { - "post": { - "tags": ["store"], - "summary": "Place an order for a pet", - "description": "Place a new order in the store", - "operationId": "placeOrder", - "requestBody": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/Order" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/Order" } - }, - "application/x-www-form-urlencoded": { - "schema": { "$ref": "#/components/schemas/Order" } - } - } - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/Order" } - } - } - }, - "405": { "description": "Invalid input" } - } - } - }, - "/store/order/{orderId}": { - "get": { - "tags": ["store"], - "summary": "Find purchase order by ID", - "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", - "operationId": "getOrderById", - "parameters": [ - { - "name": "orderId", - "in": "path", - "description": "ID of order that needs to be fetched", - "required": true, - "schema": { "type": "integer", "format": "int64" } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/xml": { - "schema": { "$ref": "#/components/schemas/Order" } - }, - "application/json": { - "schema": { "$ref": "#/components/schemas/Order" } - } - } - }, - "400": { "description": "Invalid ID supplied" }, - "404": { "description": "Order not found" } - } - }, - "delete": { - "tags": ["store"], - "summary": "Delete purchase order by ID", - "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", - "operationId": "deleteOrder", - "parameters": [ - { - "name": "orderId", - "in": "path", - "description": "ID of the order that needs to be deleted", - "required": true, - "schema": { "type": "integer", "format": "int64" } - } - ], - "responses": { - "400": { "description": "Invalid ID supplied" }, - "404": { "description": "Order not found" } - } - } - }, - "/user": { - "post": { - "tags": ["user"], - "summary": "Create user", - "description": "This can only be done by the logged in user.", - "operationId": "createUser", - "requestBody": { - "description": "Created user object", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/x-www-form-urlencoded": { - "schema": { "$ref": "#/components/schemas/User" } - } - } - }, - "responses": { - "default": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/User" } - } - } - } - } - } - }, - "/user/createWithList": { - "post": { - "tags": ["user"], - "summary": "Creates list of users with given input array", - "description": "Creates list of users with given input array", - "operationId": "createUsersWithListInput", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/User" } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/xml": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/json": { - "schema": { "$ref": "#/components/schemas/User" } - } - } - }, - "default": { "description": "successful operation" } - } - } - }, - "/user/login": { - "get": { - "tags": ["user"], - "summary": "Logs user into the system", - "description": "", - "operationId": "loginUser", - "parameters": [ - { - "name": "username", - "in": "query", - "description": "The user name for login", - "required": false, - "schema": { "type": "string" } - }, - { - "name": "password", - "in": "query", - "description": "The password for login in clear text", - "required": false, - "schema": { "type": "string" } - } - ], - "responses": { - "200": { - "description": "successful operation", - "headers": { - "X-Rate-Limit": { - "description": "calls per hour allowed by the user", - "schema": { "type": "integer", "format": "int32" } - }, - "X-Expires-After": { - "description": "date in UTC when token expires", - "schema": { "type": "string", "format": "date-time" } - } - }, - "content": { - "application/xml": { "schema": { "type": "string" } }, - "application/json": { "schema": { "type": "string" } } - } - }, - "400": { "description": "Invalid username/password supplied" } - } - } - }, - "/user/logout": { - "get": { - "tags": ["user"], - "summary": "Logs out current logged in user session", - "description": "", - "operationId": "logoutUser", - "parameters": [], - "responses": { "default": { "description": "successful operation" } } - } - }, - "/user/{username}": { - "get": { - "tags": ["user"], - "summary": "Get user by user name", - "description": "", - "operationId": "getUserByName", - "parameters": [ - { - "name": "username", - "in": "path", - "description": "The name that needs to be fetched. Use user1 for testing. ", - "required": true, - "schema": { "type": "string" } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/xml": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/json": { - "schema": { "$ref": "#/components/schemas/User" } - } - } - }, - "400": { "description": "Invalid username supplied" }, - "404": { "description": "User not found" } - } - }, - "put": { - "tags": ["user"], - "summary": "Update user", - "description": "This can only be done by the logged in user.", - "operationId": "updateUser", - "parameters": [ - { - "name": "username", - "in": "path", - "description": "name that needs to be updated", - "required": true, - "schema": { "type": "string" } - } - ], - "requestBody": { - "description": "Update an existent user in the store", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/User" } - }, - "application/x-www-form-urlencoded": { - "schema": { "$ref": "#/components/schemas/User" } - } - } - }, - "responses": { "default": { "description": "successful operation" } } - }, - "delete": { - "tags": ["user"], - "summary": "Delete user", - "description": "This can only be done by the logged in user.", - "operationId": "deleteUser", - "parameters": [ - { - "name": "username", - "in": "path", - "description": "The name that needs to be deleted", - "required": true, - "schema": { "type": "string" } - } - ], - "responses": { - "400": { "description": "Invalid username supplied" }, - "404": { "description": "User not found" } - } - } - } - }, - "components": { - "schemas": { - "Order": { - "type": "object", - "properties": { - "id": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "petId": { - "type": "integer", - "format": "int64", - "example": 198772 - }, - "quantity": { - "type": "integer", - "format": "int32", - "example": 7 - }, - "shipDate": { "type": "string", "format": "date-time" }, - "status": { - "type": "string", - "description": "Order Status", - "example": "approved", - "enum": ["placed", "approved", "delivered"] - }, - "complete": { "type": "boolean" } - }, - "xml": { "name": "order" } - }, - "petId": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "petId": { - "type": "integer", - "format": "int64", - "example": 198772 - }, - "quantity": { - "type": "integer", - "format": "int32", - "example": 7 - }, - "shipDate": { "type": "string", "format": "date-time" }, - "status": { - "type": "string", - "description": "Order Status", - "example": "approved", - "enum": ["placed", "approved", "delivered"] - }, - "complete": { "type": "boolean" } - }, - "xml": { "name": "order" } - }, - "quantity": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "petId": { - "type": "integer", - "format": "int64", - "example": 198772 - }, - "quantity": { - "type": "integer", - "format": "int32", - "example": 7 - }, - "shipDate": { "type": "string", "format": "date-time" }, - "status": { - "type": "string", - "description": "Order Status", - "example": "approved", - "enum": ["placed", "approved", "delivered"] - }, - "complete": { "type": "boolean" } - }, - "xml": { "name": "order" } - }, - "shipDate": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "petId": { - "type": "integer", - "format": "int64", - "example": 198772 - }, - "quantity": { - "type": "integer", - "format": "int32", - "example": 7 - }, - "shipDate": { "type": "string", "format": "date-time" }, - "status": { - "type": "string", - "description": "Order Status", - "example": "approved", - "enum": ["placed", "approved", "delivered"] - }, - "complete": { "type": "boolean" } - }, - "xml": { "name": "order" } - }, - "status": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "petId": { - "type": "integer", - "format": "int64", - "example": 198772 - }, - "quantity": { - "type": "integer", - "format": "int32", - "example": 7 - }, - "shipDate": { "type": "string", "format": "date-time" }, - "status": { - "type": "string", - "description": "Order Status", - "example": "approved", - "enum": ["placed", "approved", "delivered"] - }, - "complete": { "type": "boolean" } - }, - "xml": { "name": "order" } - }, - "complete": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "petId": { - "type": "integer", - "format": "int64", - "example": 198772 - }, - "quantity": { - "type": "integer", - "format": "int32", - "example": 7 - }, - "shipDate": { "type": "string", "format": "date-time" }, - "status": { - "type": "string", - "description": "Order Status", - "example": "approved", - "enum": ["placed", "approved", "delivered"] - }, - "complete": { "type": "boolean" } - }, - "xml": { "name": "order" } - } - }, - "xml": { "name": "order" } - }, - "Customer": { - "type": "object", - "properties": { - "id": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 100000 }, - "username": { "type": "string", "example": "fehguy" }, - "address": { - "type": "array", - "xml": { "name": "addresses", "wrapped": true }, - "items": { "$ref": "#/components/schemas/Address" } - } - }, - "xml": { "name": "customer" } - }, - "username": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 100000 }, - "username": { "type": "string", "example": "fehguy" }, - "address": { - "type": "array", - "xml": { "name": "addresses", "wrapped": true }, - "items": { "$ref": "#/components/schemas/Address" } - } - }, - "xml": { "name": "customer" } - }, - "address": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 100000 }, - "username": { "type": "string", "example": "fehguy" }, - "address": { - "type": "array", - "xml": { "name": "addresses", "wrapped": true }, - "items": { "$ref": "#/components/schemas/Address" } - } - }, - "xml": { "name": "customer" } - } - }, - "xml": { "name": "customer" } - }, - "Address": { - "type": "object", - "properties": { - "street": { - "type": "object", - "properties": { - "street": { "type": "string", "example": "437 Lytton" }, - "city": { "type": "string", "example": "Palo Alto" }, - "state": { "type": "string", "example": "CA" }, - "zip": { "type": "string", "example": "94301" } - }, - "xml": { "name": "address" } - }, - "city": { - "type": "object", - "properties": { - "street": { "type": "string", "example": "437 Lytton" }, - "city": { "type": "string", "example": "Palo Alto" }, - "state": { "type": "string", "example": "CA" }, - "zip": { "type": "string", "example": "94301" } - }, - "xml": { "name": "address" } - }, - "state": { - "type": "object", - "properties": { - "street": { "type": "string", "example": "437 Lytton" }, - "city": { "type": "string", "example": "Palo Alto" }, - "state": { "type": "string", "example": "CA" }, - "zip": { "type": "string", "example": "94301" } - }, - "xml": { "name": "address" } - }, - "zip": { - "type": "object", - "properties": { - "street": { "type": "string", "example": "437 Lytton" }, - "city": { "type": "string", "example": "Palo Alto" }, - "state": { "type": "string", "example": "CA" }, - "zip": { "type": "string", "example": "94301" } - }, - "xml": { "name": "address" } - } - }, - "xml": { "name": "address" } - }, - "Category": { - "type": "object", - "properties": { - "id": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 1 }, - "name": { "type": "string", "example": "Dogs" } - }, - "xml": { "name": "category" } - }, - "name": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 1 }, - "name": { "type": "string", "example": "Dogs" } - }, - "xml": { "name": "category" } - } - }, - "xml": { "name": "category" } - }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "username": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "firstName": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "lastName": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "email": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "password": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "phone": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - }, - "userStatus": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "username": { "type": "string", "example": "theUser" }, - "firstName": { "type": "string", "example": "John" }, - "lastName": { "type": "string", "example": "James" }, - "email": { "type": "string", "example": "john@email.com" }, - "password": { "type": "string", "example": "12345" }, - "phone": { "type": "string", "example": "12345" }, - "userStatus": { - "type": "integer", - "description": "User Status", - "format": "int32", - "example": 1 - } - }, - "xml": { "name": "user" } - } - }, - "xml": { "name": "user" } - }, - "Tag": { - "type": "object", - "properties": { - "id": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64" }, - "name": { "type": "string" } - }, - "xml": { "name": "tag" } - }, - "name": { - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64" }, - "name": { "type": "string" } - }, - "xml": { "name": "tag" } - } - }, - "xml": { "name": "tag" } - }, - "Pet": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "name": { "type": "string", "example": "doggie" }, - "category": { "$ref": "#/components/schemas/Category" }, - "photoUrls": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "type": "string", "xml": { "name": "photoUrl" } } - }, - "tags": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "$ref": "#/components/schemas/Tag" } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": ["available", "pending", "sold"] - } - }, - "xml": { "name": "pet" } - }, - "name": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "name": { "type": "string", "example": "doggie" }, - "category": { "$ref": "#/components/schemas/Category" }, - "photoUrls": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "type": "string", "xml": { "name": "photoUrl" } } - }, - "tags": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "$ref": "#/components/schemas/Tag" } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": ["available", "pending", "sold"] - } - }, - "xml": { "name": "pet" } - }, - "category": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "name": { "type": "string", "example": "doggie" }, - "category": { "$ref": "#/components/schemas/Category" }, - "photoUrls": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "type": "string", "xml": { "name": "photoUrl" } } - }, - "tags": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "$ref": "#/components/schemas/Tag" } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": ["available", "pending", "sold"] - } - }, - "xml": { "name": "pet" } - }, - "photoUrls": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "name": { "type": "string", "example": "doggie" }, - "category": { "$ref": "#/components/schemas/Category" }, - "photoUrls": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "type": "string", "xml": { "name": "photoUrl" } } - }, - "tags": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "$ref": "#/components/schemas/Tag" } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": ["available", "pending", "sold"] - } - }, - "xml": { "name": "pet" } - }, - "tags": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "name": { "type": "string", "example": "doggie" }, - "category": { "$ref": "#/components/schemas/Category" }, - "photoUrls": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "type": "string", "xml": { "name": "photoUrl" } } - }, - "tags": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "$ref": "#/components/schemas/Tag" } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": ["available", "pending", "sold"] - } - }, - "xml": { "name": "pet" } - }, - "status": { - "required": ["name", "photoUrls"], - "type": "object", - "properties": { - "id": { "type": "integer", "format": "int64", "example": 10 }, - "name": { "type": "string", "example": "doggie" }, - "category": { "$ref": "#/components/schemas/Category" }, - "photoUrls": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "type": "string", "xml": { "name": "photoUrl" } } - }, - "tags": { - "type": "array", - "xml": { "wrapped": true }, - "items": { "$ref": "#/components/schemas/Tag" } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": ["available", "pending", "sold"] - } - }, - "xml": { "name": "pet" } - } - }, - "xml": { "name": "pet" } - }, - "ApiResponse": { - "type": "object", - "properties": { - "code": { - "type": "object", - "properties": { - "code": { "type": "integer", "format": "int32" }, - "type": { "type": "string" }, - "message": { "type": "string" } - }, - "xml": { "name": "##default" } - }, - "type": { - "type": "object", - "properties": { - "code": { "type": "integer", "format": "int32" }, - "type": { "type": "string" }, - "message": { "type": "string" } - }, - "xml": { "name": "##default" } - }, - "message": { - "type": "object", - "properties": { - "code": { "type": "integer", "format": "int32" }, - "type": { "type": "string" }, - "message": { "type": "string" } - }, - "xml": { "name": "##default" } - } - }, - "xml": { "name": "##default" } - } - }, - "requestBodies": { - "Pet": { - "description": "Pet object that needs to be added to the store", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/Pet" } - }, - "application/xml": { - "schema": { "$ref": "#/components/schemas/Pet" } - } - } - }, - "UserArray": { - "description": "List of user object", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { "$ref": "#/components/schemas/User" } - } - } - } - } - }, - "securitySchemes": { - "petstore_auth": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", - "scopes": { - "write:pets": "modify pets in your account", - "read:pets": "read your pets" - } - } - } - }, - "api_key": { "type": "apiKey", "name": "api_key", "in": "header" } - } - } -} diff --git a/rest/testdata/petstore3/openapi.yaml b/rest/testdata/petstore3/openapi.yaml new file mode 100644 index 0000000..d6e6006 --- /dev/null +++ b/rest/testdata/petstore3/openapi.yaml @@ -0,0 +1,1548 @@ +openapi: 3.0.2 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.18 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: https://petstore3.swagger.io/api/v3 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io + - name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Pet" + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/json: + schema: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "405": + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Pet" + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/json: + schema: + $ref: "#/components/schemas/Pet" + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: + "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/json: + schema: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid ID supplied + "404": + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: "" + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: "" + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + description: "" + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + application/xml: + schema: + $ref: "#/components/schemas/Order" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Order" + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + "405": + description: Invalid input + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: + For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: "#/components/schemas/Order" + application/json: + schema: + $ref: "#/components/schemas/Order" + "400": + description: Invalid ID supplied + "404": + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: + For valid response try integer IDs with value < 1000. Anything + above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid ID supplied + "404": + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/User" + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: "#/components/schemas/User" + application/json: + schema: + $ref: "#/components/schemas/User" + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: "" + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: "" + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: "" + operationId: getUserByName + parameters: + - name: username + in: path + description: "The name that needs to be fetched. Use user1 for testing. " + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: "#/components/schemas/User" + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid username supplied + "404": + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that needs to be updated + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/User" + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "400": + description: Invalid username supplied + "404": + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + petId: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + quantity: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + shipDate: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + status: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + complete: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + xml: + name: order + Customer: + type: object + properties: + id: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: "#/components/schemas/Address" + xml: + name: customer + username: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: "#/components/schemas/Address" + xml: + name: customer + address: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: "#/components/schemas/Address" + xml: + name: customer + xml: + name: customer + Address: + type: object + properties: + street: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + city: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + state: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + zip: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + xml: + name: address + Category: + type: object + properties: + id: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + name: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + xml: + name: category + User: + type: object + properties: + id: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + username: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + firstName: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + lastName: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + email: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + password: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + phone: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + userStatus: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + xml: + name: user + Tag: + type: object + properties: + id: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + name: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + name: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + category: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + photoUrls: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + tags: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + status: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: "##default" + type: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: "##default" + message: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: "##default" + xml: + name: "##default" + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/rest/types.go b/rest/types.go index 7c4b824..169b02f 100644 --- a/rest/types.go +++ b/rest/types.go @@ -19,6 +19,7 @@ type SchemaFile struct { Spec schema.SchemaSpecType `json:"spec" yaml:"spec"` MethodAlias map[string]string `json:"methodAlias" yaml:"methodAlias"` TrimPrefix string `json:"trimPrefix" yaml:"trimPrefix"` + EnvPrefix string `json:"envPrefix" yaml:"envPrefix"` } // Configuration contains required settings for the connector. @@ -50,3 +51,8 @@ func WithClient(client Doer) Option { opts.client = client } } + +type ndcRestSchemaWithName struct { + name string + schema *schema.NDCRestSchema +}