diff --git a/.golangci.yml b/.golangci.yml index 87d91fc..c979181 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,6 +38,7 @@ linters: - noctx - unparam - forbidigo + - tagalign issues: exclude-files: diff --git a/Dockerfile b/Dockerfile index 6431466..d2452b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,8 @@ FROM golang:1.23 AS builder WORKDIR /app -COPY go.mod go.sum ./ +COPY ndc-rest-schema ./ndc-rest-schema +COPY go.mod go.sum go.work ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -v -o ndc-cli ./server diff --git a/rest/cli.go b/connector/cli.go similarity index 100% rename from rest/cli.go rename to connector/cli.go diff --git a/rest/connector.go b/connector/connector.go similarity index 80% rename from rest/connector.go rename to connector/connector.go index bbe2493..47fca1f 100644 --- a/rest/connector.go +++ b/connector/connector.go @@ -6,7 +6,9 @@ import ( "fmt" "os" - "github.com/hasura/ndc-rest/rest/internal" + "github.com/hasura/ndc-rest/connector/internal" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-sdk-go/connector" "github.com/hasura/ndc-sdk-go/schema" "gopkg.in/yaml.v3" @@ -14,10 +16,10 @@ import ( // RESTConnector implements the SDK interface of NDC specification type RESTConnector struct { - metadata RESTMetadataCollection + metadata internal.MetadataCollection capabilities *schema.RawCapabilitiesResponse rawSchema *schema.RawSchemaResponse - schema *schema.SchemaResponse + schema *rest.NDCRestSchema client *internal.HTTPClient } @@ -34,7 +36,7 @@ func NewRESTConnector(opts ...Option) *RESTConnector { // ParseConfiguration validates the configuration files provided by the user, returning a validated 'Configuration', // or throwing an error to prevents Connector startup. -func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir string) (*Configuration, error) { +func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir string) (*configuration.Configuration, error) { restCapabilities := schema.CapabilitiesResponse{ Version: "0.1.6", Capabilities: schema.Capabilities{ @@ -78,7 +80,7 @@ func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir // // In addition, this function should register any // connector-specific metrics with the metrics registry. -func (c *RESTConnector) TryInitState(ctx context.Context, configuration *Configuration, metrics *connector.TelemetryState) (*State, error) { +func (c *RESTConnector) TryInitState(ctx context.Context, configuration *configuration.Configuration, metrics *connector.TelemetryState) (*State, error) { c.client.SetTracer(metrics.Tracer) return &State{}, nil } @@ -89,27 +91,27 @@ func (c *RESTConnector) TryInitState(ctx context.Context, configuration *Configu // is able to reach its data source over the network. // // Should throw if the check fails, else resolve. -func (c *RESTConnector) HealthCheck(ctx context.Context, configuration *Configuration, state *State) error { +func (c *RESTConnector) HealthCheck(ctx context.Context, configuration *configuration.Configuration, state *State) error { return nil } // GetCapabilities get the connector's capabilities. -func (c *RESTConnector) GetCapabilities(configuration *Configuration) schema.CapabilitiesResponseMarshaler { +func (c *RESTConnector) GetCapabilities(configuration *configuration.Configuration) schema.CapabilitiesResponseMarshaler { return c.capabilities } // QueryExplain explains a query by creating an execution plan. -func (c *RESTConnector) QueryExplain(ctx context.Context, configuration *Configuration, state *State, request *schema.QueryRequest) (*schema.ExplainResponse, error) { +func (c *RESTConnector) QueryExplain(ctx context.Context, configuration *configuration.Configuration, state *State, request *schema.QueryRequest) (*schema.ExplainResponse, error) { return nil, schema.NotSupportedError("query explain has not been supported yet", nil) } // MutationExplain explains a mutation by creating an execution plan. -func (c *RESTConnector) MutationExplain(ctx context.Context, configuration *Configuration, state *State, request *schema.MutationRequest) (*schema.ExplainResponse, error) { +func (c *RESTConnector) MutationExplain(ctx context.Context, configuration *configuration.Configuration, state *State, request *schema.MutationRequest) (*schema.ExplainResponse, error) { return nil, schema.NotSupportedError("mutation explain has not been supported yet", nil) } -func parseConfiguration(configurationDir string) (*Configuration, error) { - var config Configuration +func parseConfiguration(configurationDir string) (*configuration.Configuration, error) { + var config configuration.Configuration jsonBytes, err := os.ReadFile(configurationDir + "/config.json") if err == nil { if err = json.Unmarshal(jsonBytes, &config); err != nil { diff --git a/rest/connector_test.go b/connector/connector_test.go similarity index 96% rename from rest/connector_test.go rename to connector/connector_test.go index c4b966d..b7c8dbb 100644 --- a/rest/connector_test.go +++ b/connector/connector_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" "github.com/hasura/ndc-sdk-go/connector" "github.com/hasura/ndc-sdk-go/schema" "gotest.tools/v3/assert" @@ -274,35 +275,35 @@ func TestRESTConnector_distribution(t *testing.T) { }, connector.WithoutRecovery()) assert.NilError(t, err) - timeout, err := rc.metadata[0].settings.Servers[0].Timeout.Value() + timeout, err := rc.metadata[0].Settings.Servers[0].Timeout.Value() assert.NilError(t, err) assert.Equal(t, int64(30), *timeout) - retryTimes, err := rc.metadata[0].settings.Servers[0].Retry.Times.Value() + retryTimes, err := rc.metadata[0].Settings.Servers[0].Retry.Times.Value() assert.NilError(t, err) assert.Equal(t, int64(2), *retryTimes) - retryDelay, err := rc.metadata[0].settings.Servers[0].Retry.Delay.Value() + retryDelay, err := rc.metadata[0].Settings.Servers[0].Retry.Delay.Value() assert.NilError(t, err) assert.Equal(t, int64(1000), *retryDelay) - retryStatus, err := rc.metadata[0].settings.Servers[0].Retry.HTTPStatus.Value() + retryStatus, err := rc.metadata[0].Settings.Servers[0].Retry.HTTPStatus.Value() assert.NilError(t, err) assert.DeepEqual(t, []int64{429, 500}, retryStatus) - timeout1, err := rc.metadata[0].settings.Servers[1].Timeout.Value() + timeout1, err := rc.metadata[0].Settings.Servers[1].Timeout.Value() assert.NilError(t, err) assert.Equal(t, int64(10), *timeout1) - retryTimes1, err := rc.metadata[0].settings.Servers[1].Retry.Times.Value() + retryTimes1, err := rc.metadata[0].Settings.Servers[1].Retry.Times.Value() assert.NilError(t, err) assert.Equal(t, int64(1), *retryTimes1) - retryDelay1, err := rc.metadata[0].settings.Servers[1].Retry.Delay.Value() + retryDelay1, err := rc.metadata[0].Settings.Servers[1].Retry.Delay.Value() assert.NilError(t, err) assert.Equal(t, int64(500), *retryDelay1) - retryStatus1, err := rc.metadata[0].settings.Servers[1].Retry.HTTPStatus.Value() + retryStatus1, err := rc.metadata[0].Settings.Servers[1].Retry.HTTPStatus.Value() assert.NilError(t, err) assert.DeepEqual(t, []int64{429, 500, 501, 502}, retryStatus1) @@ -543,6 +544,7 @@ func TestRESTConnector_multiSchemas(t *testing.T) { } func createMockServer(t *testing.T, apiKey string, bearerToken string) *httptest.Server { + t.Helper() mux := http.NewServeMux() writeResponse := func(w http.ResponseWriter, body string) { @@ -709,6 +711,7 @@ func (mds *mockMultiSchemaServer) createServer() *httptest.Server { } func assertNdcOperations(t *testing.T, dir string, targetURL string) { + t.Helper() queryFiles, err := os.ReadDir(dir) if err != nil { if !os.IsNotExist(err) { @@ -736,7 +739,8 @@ func assertNdcOperations(t *testing.T, dir string, targetURL string) { } } -func test_createServer(t *testing.T, dir string) *connector.Server[Configuration, State] { +func test_createServer(t *testing.T, dir string) *connector.Server[configuration.Configuration, State] { + t.Helper() c := NewRESTConnector() server, err := connector.NewServer(c, &connector.ServerOptions{ Configuration: dir, @@ -750,6 +754,7 @@ func test_createServer(t *testing.T, dir string) *connector.Server[Configuration } func assertHTTPResponse[B any](t *testing.T, res *http.Response, statusCode int, expectedBody B) { + t.Helper() defer res.Body.Close() bodyBytes, err := io.ReadAll(res.Body) diff --git a/rest/internal/client.go b/connector/internal/client.go similarity index 100% rename from rest/internal/client.go rename to connector/internal/client.go diff --git a/rest/internal/decode.go b/connector/internal/decode.go similarity index 100% rename from rest/internal/decode.go rename to connector/internal/decode.go diff --git a/rest/internal/decode_test.go b/connector/internal/decode_test.go similarity index 100% rename from rest/internal/decode_test.go rename to connector/internal/decode_test.go diff --git a/connector/internal/metadata.go b/connector/internal/metadata.go new file mode 100644 index 0000000..49e9426 --- /dev/null +++ b/connector/internal/metadata.go @@ -0,0 +1,31 @@ +package internal + +import ( + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "github.com/hasura/ndc-sdk-go/schema" +) + +// MetadataCollection stores list of REST metadata with helper methods +type MetadataCollection []rest.NDCRestSchema + +// GetFunction gets the NDC function by name +func (rms MetadataCollection) GetFunction(name string) (*rest.OperationInfo, *rest.NDCRestSettings, error) { + for _, rm := range rms { + fn := rm.GetFunction(name) + if fn != nil { + return fn, rm.Settings, nil + } + } + return nil, nil, schema.UnprocessableContentError("unsupported query: "+name, nil) +} + +// GetProcedure gets the NDC procedure by name +func (rms MetadataCollection) GetProcedure(name string) (*rest.OperationInfo, *rest.NDCRestSettings, error) { + for _, rm := range rms { + fn := rm.GetProcedure(name) + if fn != nil { + return fn, rm.Settings, nil + } + } + return nil, nil, schema.UnprocessableContentError("unsupported query: "+name, nil) +} diff --git a/rest/internal/multipart.go b/connector/internal/multipart.go similarity index 100% rename from rest/internal/multipart.go rename to connector/internal/multipart.go diff --git a/rest/internal/parameter.go b/connector/internal/parameter.go similarity index 100% rename from rest/internal/parameter.go rename to connector/internal/parameter.go diff --git a/rest/internal/request.go b/connector/internal/request.go similarity index 100% rename from rest/internal/request.go rename to connector/internal/request.go diff --git a/connector/internal/request_builder.go b/connector/internal/request_builder.go new file mode 100644 index 0000000..55e00ae --- /dev/null +++ b/connector/internal/request_builder.go @@ -0,0 +1,404 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "slices" + "strings" + + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "github.com/hasura/ndc-sdk-go/schema" + "github.com/hasura/ndc-sdk-go/utils" +) + +// RequestBuilder builds requests to the remote service +type RequestBuilder struct { + Schema *rest.NDCRestSchema + Operation *rest.OperationInfo + Arguments map[string]any +} + +// NewRequestBuilder creates a new RequestBuilder instance +func NewRequestBuilder(restSchema *rest.NDCRestSchema, operation *rest.OperationInfo, arguments map[string]any) *RequestBuilder { + return &RequestBuilder{ + Schema: restSchema, + Operation: operation, + Arguments: arguments, + } +} + +// Build evaluates and builds a RetryableRequest +func (c *RequestBuilder) Build() (*RetryableRequest, error) { + endpoint, headers, err := c.evalURLAndHeaderParameters() + if err != nil { + return nil, schema.UnprocessableContentError("failed to evaluate URL and Headers from parameters", map[string]any{ + "cause": err.Error(), + }) + } + + var buffer io.ReadSeeker + + rawRequest := c.Operation.Request + contentType := rest.ContentTypeJSON + + if rawRequest.RequestBody != nil { + contentType = rawRequest.RequestBody.ContentType + bodyInfo, infoOk := c.Operation.Arguments[rest.BodyKey] + bodyData, ok := c.Arguments[rest.BodyKey] + if ok && bodyData != nil { + var err error + binaryBody := c.getRequestUploadBody(c.Operation.Request, &bodyInfo) + if binaryBody != nil { + b64, err := utils.DecodeString(bodyData) + if err != nil { + return nil, err + } + dataURI, err := DecodeDataURI(b64) + if err != nil { + return nil, err + } + buffer = bytes.NewReader([]byte(dataURI.Data)) + } else if strings.HasPrefix(contentType, "text/") { + buffer = bytes.NewReader([]byte(fmt.Sprint(bodyData))) + } else if strings.HasPrefix(contentType, "multipart/") { + buffer, contentType, err = c.createMultipartForm(bodyData) + if err != nil { + return nil, err + } + } else { + switch contentType { + case rest.ContentTypeFormURLEncoded: + buffer, err = c.createFormURLEncoded(&bodyInfo, bodyData) + if err != nil { + return nil, err + } + case rest.ContentTypeJSON, "": + bodyBytes, err := json.Marshal(bodyData) + if err != nil { + return nil, err + } + + buffer = bytes.NewReader(bodyBytes) + default: + return nil, fmt.Errorf("unsupported content type %s", contentType) + } + } + } else if infoOk { + ty, err := bodyInfo.Type.Type() + if err != nil { + return nil, err + } + if ty != schema.TypeNullable { + return nil, errRequestBodyRequired + } + } + } + + request := &RetryableRequest{ + URL: endpoint, + RawRequest: rawRequest, + ContentType: contentType, + Headers: headers, + Body: buffer, + Timeout: rawRequest.Timeout, + Retry: rawRequest.Retry, + } + + return request, nil +} + +func (c *RequestBuilder) createFormURLEncoded(bodyInfo *rest.ArgumentInfo, bodyData any) (io.ReadSeeker, error) { + queryParams, err := c.encodeParameterValues(&rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: bodyInfo.Type, + }, + Rest: bodyInfo.Rest.Schema, + }, reflect.ValueOf(bodyData), []string{"body"}) + if err != nil { + return nil, err + } + + if len(queryParams) == 0 { + return nil, nil + } + q := url.Values{} + for _, qp := range queryParams { + keys := qp.Keys() + evalQueryParameterURL(&q, "", bodyInfo.Rest.EncodingObject, keys, qp.Values()) + } + rawQuery := encodeQueryValues(q, true) + + return bytes.NewReader([]byte(rawQuery)), nil +} + +func (c *RequestBuilder) createMultipartForm(bodyData any) (io.ReadSeeker, string, error) { + bodyInfo, ok := c.Operation.Arguments[rest.BodyKey] + if !ok { + return nil, "", errRequestBodyTypeRequired + } + + buffer := new(bytes.Buffer) + writer := NewMultipartWriter(buffer) + + if err := c.evalMultipartForm(writer, &bodyInfo, reflect.ValueOf(bodyData)); err != nil { + return nil, "", err + } + if err := writer.Close(); err != nil { + return nil, "", err + } + + reader := bytes.NewReader(buffer.Bytes()) + buffer.Reset() + + return reader, writer.FormDataContentType(), nil +} + +func (c *RequestBuilder) evalMultipartForm(w *MultipartWriter, bodyInfo *rest.ArgumentInfo, bodyData reflect.Value) error { + bodyData, ok := utils.UnwrapPointerFromReflectValue(bodyData) + if !ok { + return nil + } + switch bodyType := bodyInfo.Type.Interface().(type) { + case *schema.NullableType: + return c.evalMultipartForm(w, &rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Type: bodyType.UnderlyingType, + }, + Rest: bodyInfo.Rest, + }, bodyData) + case *schema.NamedType: + if !ok { + return fmt.Errorf("%s: %w", rest.BodyKey, errArgumentRequired) + } + bodyObject, ok := c.Schema.ObjectTypes[bodyType.Name] + if !ok { + break + } + kind := bodyData.Kind() + switch kind { + case reflect.Map, reflect.Interface: + bi := bodyData.Interface() + bodyMap, ok := bi.(map[string]any) + if !ok { + return fmt.Errorf("invalid multipart form body, expected object, got %v", bi) + } + + for key, fieldInfo := range bodyObject.Fields { + fieldValue := bodyMap[key] + var enc *rest.EncodingObject + if len(c.Operation.Request.RequestBody.Encoding) > 0 { + en, ok := c.Operation.Request.RequestBody.Encoding[key] + if ok { + enc = &en + } + } + + if err := c.evalMultipartFieldValueRecursive(w, key, reflect.ValueOf(fieldValue), &fieldInfo, enc); err != nil { + return err + } + } + return nil + case reflect.Struct: + reflectType := bodyData.Type() + for fieldIndex := range bodyData.NumField() { + fieldValue := bodyData.Field(fieldIndex) + fieldType := reflectType.Field(fieldIndex) + fieldInfo, ok := bodyObject.Fields[fieldType.Name] + if !ok { + continue + } + + var enc *rest.EncodingObject + if len(c.Operation.Request.RequestBody.Encoding) > 0 { + en, ok := c.Operation.Request.RequestBody.Encoding[fieldType.Name] + if ok { + enc = &en + } + } + + if err := c.evalMultipartFieldValueRecursive(w, fieldType.Name, fieldValue, &fieldInfo, enc); err != nil { + return err + } + } + return nil + } + } + + return fmt.Errorf("invalid multipart form body, expected object, got %v", bodyInfo.Type) +} + +func (c *RequestBuilder) evalMultipartFieldValueRecursive(w *MultipartWriter, name string, value reflect.Value, fieldInfo *rest.ObjectField, enc *rest.EncodingObject) error { + underlyingValue, notNull := utils.UnwrapPointerFromReflectValue(value) + argTypeT, err := fieldInfo.Type.InterfaceT() + switch argType := argTypeT.(type) { + case *schema.ArrayType: + if !notNull { + return fmt.Errorf("%s: %w", name, errArgumentRequired) + } + if enc != nil && slices.Contains(enc.ContentType, rest.ContentTypeJSON) { + var headers http.Header + var err error + if len(enc.Headers) > 0 { + headers, err = c.evalEncodingHeaders(enc.Headers) + if err != nil { + return err + } + } + return w.WriteJSON(name, value.Interface(), headers) + } + if !slices.Contains([]reflect.Kind{reflect.Slice, reflect.Array}, value.Kind()) { + return fmt.Errorf("%s: expected array type, got %v", name, value.Kind()) + } + + for i := range value.Len() { + elem := value.Index(i) + err := c.evalMultipartFieldValueRecursive(w, name+"[]", elem, &rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: argType.ElementType, + }, + Rest: fieldInfo.Rest.Items, + }, enc) + if err != nil { + return err + } + } + return nil + case *schema.NullableType: + if !notNull { + return nil + } + return c.evalMultipartFieldValueRecursive(w, name, underlyingValue, &rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: argType.UnderlyingType, + }, + Rest: fieldInfo.Rest, + }, enc) + case *schema.NamedType: + if !notNull { + return fmt.Errorf("%s: %w", name, errArgumentRequired) + } + var headers http.Header + var err error + if enc != nil && len(enc.Headers) > 0 { + headers, err = c.evalEncodingHeaders(enc.Headers) + if err != nil { + return err + } + } + if iScalar, ok := c.Schema.ScalarTypes[argType.Name]; ok { + switch iScalar.Representation.Interface().(type) { + case *schema.TypeRepresentationBytes: + return w.WriteDataURI(name, value.Interface(), headers) + } + } + + if enc != nil && slices.Contains(enc.ContentType, rest.ContentTypeJSON) { + return w.WriteJSON(name, value, headers) + } + + params, err := c.encodeParameterValues(fieldInfo, value, []string{}) + if err != nil { + return err + } + + if len(params) == 0 { + return nil + } + + for _, p := range params { + keys := p.Keys() + values := p.Values() + fieldName := name + + if len(keys) > 0 { + keys = append([]Key{NewKey(name)}, keys...) + fieldName = keys.String() + } + + if len(values) > 1 { + fieldName += "[]" + for _, v := range values { + if err = w.WriteField(fieldName, v, headers); err != nil { + return err + } + } + } else if len(values) == 1 { + if err = w.WriteField(fieldName, values[0], headers); err != nil { + return err + } + } + } + + return nil + case *schema.PredicateType: + return fmt.Errorf("%s: predicate type is not supported", name) + default: + return fmt.Errorf("%s: %w", name, err) + } +} + +func (c *RequestBuilder) evalEncodingHeaders(encHeaders map[string]rest.RequestParameter) (http.Header, error) { + results := http.Header{} + for key, param := range encHeaders { + argumentName := param.ArgumentName + if argumentName == "" { + argumentName = key + } + argumentInfo, ok := c.Operation.Arguments[argumentName] + if !ok { + continue + } + rawHeaderValue, ok := c.Arguments[argumentName] + if !ok { + continue + } + + headerParams, err := c.encodeParameterValues(&rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: argumentInfo.Type, + }, + Rest: param.Schema, + }, reflect.ValueOf(rawHeaderValue), []string{}) + if err != nil { + return nil, err + } + + param.Name = key + setHeaderParameters(&results, ¶m, headerParams) + } + + return results, nil +} + +func (c *RequestBuilder) getRequestUploadBody(rawRequest *rest.Request, bodyInfo *rest.ArgumentInfo) *rest.RequestBody { + if rawRequest.RequestBody == nil || bodyInfo == nil { + return nil + } + if rawRequest.RequestBody.ContentType == "application/octet-stream" { + return rawRequest.RequestBody + } + + bi, ok, err := UnwrapNullableType(bodyInfo.Type) + if err != nil || !ok { + return nil + } + namedType, ok := bi.(*schema.NamedType) + if !ok { + return nil + } + iScalar, ok := c.Schema.ScalarTypes[namedType.Name] + if !ok { + return nil + } + _, err = iScalar.Representation.AsBytes() + if err != nil { + return nil + } + + return rawRequest.RequestBody +} diff --git a/connector/internal/request_builder_test.go b/connector/internal/request_builder_test.go new file mode 100644 index 0000000..ebebe3d --- /dev/null +++ b/connector/internal/request_builder_test.go @@ -0,0 +1,597 @@ +package internal + +import ( + "encoding/json" + "io" + "mime" + "mime/multipart" + "net/http" + "os" + "slices" + "strings" + "testing" + + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "gotest.tools/v3/assert" +) + +func TestCreateMultipartForm(t *testing.T) { + testCases := []struct { + Name string + RawArguments string + Expected map[string]string + ExpectedHeaders map[string]http.Header + }{ + { + Name: "PostFiles", + RawArguments: `{ + "body": { + "expand": ["foo"], + "expand_json": ["foo","bar"], + "file": "aGVsbG8gd29ybGQ=", + "file_link_data": { + "create": true, + "expires_at": 181320689 + }, + "purpose": "business_icon" + }, + "headerXRateLimitLimit": 10 + }`, + Expected: map[string]string{ + "expand[]": `foo`, + "expand_json": `["foo","bar"]`, + "file": "hello world", + "file_link_data.create": "true", + "file_link_data.expires_at": "181320689", + "purpose": "business_icon", + }, + ExpectedHeaders: map[string]http.Header{ + "expand": { + "Content-Type": []string{"application/json"}, + }, + "file": { + "X-Rate-Limit-Limit": []string{"10"}, + }, + }, + }, + { + Name: "uploadPetMultipart", + RawArguments: `{ + "body": { + "address": { + "street": "street 1", + "location": [0, 1] + } + } + }`, + Expected: map[string]string{ + "address.street": `street 1`, + "address.location[0]": `0`, + "address.location[1]": `1`, + }, + ExpectedHeaders: map[string]http.Header{}, + }, + } + + ndcSchema := createMockSchema(t) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + var info *rest.OperationInfo + for key, f := range ndcSchema.Procedures { + if key == tc.Name { + info = &f + break + } + } + assert.Assert(t, info != nil) + + var arguments map[string]any + assert.NilError(t, json.Unmarshal([]byte(tc.RawArguments), &arguments)) + builder := RequestBuilder{ + Schema: ndcSchema, + Operation: info, + Arguments: arguments, + } + buf, mediaType, err := builder.createMultipartForm(arguments["body"]) + assert.NilError(t, err) + + _, params, err := mime.ParseMediaType(mediaType) + assert.NilError(t, err) + + reader := multipart.NewReader(buf, params["boundary"]) + var count int + results := make(map[string]string) + for { + form, err := reader.NextPart() + if err != nil && strings.Contains(err.Error(), io.EOF.Error()) { + break + } + assert.NilError(t, err) + count++ + name := form.FormName() + + expected, ok := tc.Expected[name] + if !ok { + t.Fatalf("field %s does not exist", name) + } else { + result, err := io.ReadAll(form) + assert.NilError(t, err) + assert.Equal(t, expected, string(result)) + results[name] = string(result) + expectedHeader := tc.ExpectedHeaders[name] + + for key, value := range expectedHeader { + assert.DeepEqual(t, value, form.Header[key]) + } + } + } + if len(tc.Expected) != count { + assert.DeepEqual(t, tc.Expected, results) + } + }) + } +} + +func TestCreateFormURLEncoded(t *testing.T) { + testCases := []struct { + Name string + RawArguments string + Expected string + }{ + { + Name: "PostCheckoutSessions", + RawArguments: `{ + "body": { + "after_expiration": { + "recovery": { + "allow_promotion_codes": true, + "enabled": true + } + }, + "allow_promotion_codes": true, + "automatic_tax": { + "enabled": false, + "liability": { + "account": "gW7D0WhP9C", + "type": "self" + } + }, + "billing_address_collection": "auto", + "cancel_url": "qpmWppPyIv", + "client_reference_id": "ZcJeCf6JAa", + "consent_collection": { + "payment_method_reuse_agreement": { + "position": "auto" + }, + "promotions": "auto", + "terms_of_service": "required" + }, + "currency": "oVljMB8lon", + "custom_fields": [ + { + "dropdown": { + "options": [ + { + "label": "W3oysCi31d", + "value": "hXN8MppU0k" + } + ] + }, + "key": "5ZeyjIHLn8", + "label": { + "custom": "uabTz3xzdn", + "type": "custom" + }, + "numeric": { + "maximum_length": 678468035, + "minimum_length": 2134997439 + }, + "optional": false, + "text": { + "maximum_length": 331815114, + "minimum_length": 1689246767 + }, + "type": "dropdown" + } + ], + "custom_text": { + "after_submit": { + "message": "b7ifuedi9S" + }, + "shipping_address": { + "message": "XeD5TkmC8k" + }, + "submit": { + "message": "vGcSz5eSlo" + }, + "terms_of_service_acceptance": { + "message": "zGLTTZItPl" + } + }, + "customer": "mT4BKOSu9s", + "customer_creation": "always", + "customer_email": "1xiCJ8M7Pr", + "customer_update": { + "address": "never", + "name": "never", + "shipping": "auto" + }, + "discounts": [ + { + "coupon": "tOlEXiZKv9", + "promotion_code": "Xknj8juRnm" + } + ], + "expand": ["ZBxEXz7SN0"], + "expires_at": 1756067225, + "invoice_creation": { + "enabled": true, + "invoice_data": { + "account_tax_ids": ["dev8vFF6xG"], + "custom_fields": [ + { + "name": "LBlZjJ4gEy", + "value": "EWoKgkV3fg" + } + ], + "description": "MiePp9LfkQ", + "footer": "OAELqbYbKV", + "issuer": { + "account": "aqOwDzxnyg", + "type": "account" + }, + "metadata": null, + "rendering_options": { + "amount_tax_display": "exclude_tax" + } + } + }, + "line_items": [ + { + "adjustable_quantity": { + "enabled": false, + "maximum": 1665059759, + "minimum": 905088217 + }, + "dynamic_tax_rates": ["jMMvH8TmQD"], + "price": "fR6vnvprv8", + "price_data": { + "currency": "euIDO8C4A7", + "product": "xilQ2QDVdA", + "product_data": { + "description": "DQECtJEsLI", + "images": ["gE5K8MOzRc"], + "metadata": null, + "name": "ak6UVjXl1B", + "tax_code": "PzbIHvqWJp" + }, + "recurring": { + "interval": "day", + "interval_count": 592739346 + }, + "tax_behavior": "inclusive", + "unit_amount": 945322526, + "unit_amount_decimal": "vkJPCvrn9Q" + }, + "quantity": 968305911, + "tax_rates": ["Ts1bPAoT0T"] + } + ], + "locale": "auto", + "metadata": null, + "mode": "payment", + "payment_intent_data": { + "application_fee_amount": 2033958571, + "capture_method": "manual", + "description": "yoalRHw9ZG", + "metadata": null, + "on_behalf_of": "mpkGzXu3st", + "receipt_email": "LxJLYGjJ4r", + "setup_future_usage": "off_session", + "shipping": { + "address": { + "city": "v6nZI33cUt", + "country": "O8MBVcia7c", + "line1": "3YghEmysVn", + "line2": "CM9x9Jizzu", + "postal_code": "1aAilmcYiq", + "state": "ILODDWP1IP" + }, + "carrier": "P8mCJlEq1J", + "name": "mJYqgRIh3S", + "phone": "CWAbvZM4Kw", + "tracking_number": "XGOZIrLZf0" + }, + "statement_descriptor": "JCOo6lU8Fy", + "statement_descriptor_suffix": "dtPJwyuc4i", + "transfer_data": { + "amount": 94957585, + "destination": "LrcNMrJPkO" + }, + "transfer_group": "XKfPQPVhOT" + }, + "payment_method_collection": "always", + "payment_method_configuration": "uwYSwIZP4V", + "payment_method_options": { + "acss_debit": { + "currency": "usd", + "mandate_options": { + "custom_mandate_url": "FZwPtJKktL", + "default_for": ["invoice"], + "interval_description": "iMgay8S9If", + "payment_schedule": "sporadic", + "transaction_type": "business" + }, + "setup_future_usage": "off_session", + "verification_method": "instant" + }, + "affirm": { + "setup_future_usage": "none" + }, + "afterpay_clearpay": { + "setup_future_usage": "none" + }, + "alipay": { + "setup_future_usage": "none" + }, + "au_becs_debit": { + "setup_future_usage": "none" + }, + "bacs_debit": { + "setup_future_usage": "off_session" + }, + "bancontact": { + "setup_future_usage": "none" + }, + "boleto": { + "expires_after_days": 953467886, + "setup_future_usage": "none" + }, + "card": { + "installments": { + "enabled": true + }, + "request_three_d_secure": "any", + "setup_future_usage": "on_session", + "statement_descriptor_suffix_kana": "ZvJtIONyDK", + "statement_descriptor_suffix_kanji": "Y57zexRcIH" + }, + "cashapp": { + "setup_future_usage": "off_session" + }, + "customer_balance": { + "bank_transfer": { + "eu_bank_transfer": { + "country": "mzrVWAjBTc" + }, + "requested_address_types": ["iban"], + "type": "gb_bank_transfer" + }, + "funding_type": "bank_transfer", + "setup_future_usage": "none" + }, + "eps": { + "setup_future_usage": "none" + }, + "fpx": { + "setup_future_usage": "none" + }, + "giropay": { + "setup_future_usage": "none" + }, + "grabpay": { + "setup_future_usage": "none" + }, + "ideal": { + "setup_future_usage": "none" + }, + "klarna": { + "setup_future_usage": "none" + }, + "konbini": { + "expires_after_days": 664583520, + "setup_future_usage": "none" + }, + "link": { + "setup_future_usage": "none" + }, + "mobilepay": { + "setup_future_usage": "none" + }, + "oxxo": { + "expires_after_days": 1925345768, + "setup_future_usage": "none" + }, + "p24": { + "setup_future_usage": "none", + "tos_shown_and_accepted": true + }, + "paynow": { + "setup_future_usage": "none" + }, + "paypal": { + "capture_method": "manual", + "preferred_locale": "cs-CZ", + "reference": "ulLn2NXA1P", + "risk_correlation_id": "fj1J6Nux6P", + "setup_future_usage": "none" + }, + "pix": { + "expires_after_seconds": 191312234 + }, + "revolut_pay": { + "setup_future_usage": "off_session" + }, + "sepa_debit": { + "setup_future_usage": "none" + }, + "sofort": { + "setup_future_usage": "none" + }, + "swish": { + "reference": "rXJq1EX4rc" + }, + "us_bank_account": { + "financial_connections": { + "permissions": ["ownership"], + "prefetch": ["transactions"] + }, + "setup_future_usage": "none", + "verification_method": "automatic" + }, + "wechat_pay": { + "app_id": "9Pu0d1pZ2r", + "client": "ios", + "setup_future_usage": "none" + } + }, + "payment_method_types": ["acss_debit"], + "phone_number_collection": { + "enabled": true + }, + "redirect_on_completion": "never", + "return_url": "YgIdKykEHC", + "setup_intent_data": { + "description": "U9qFTQnt1W", + "metadata": null, + "on_behalf_of": "165u5Fvodj" + }, + "shipping_address_collection": { + "allowed_countries": ["AC"] + }, + "shipping_options": [ + { + "shipping_rate": "5PAjqTpMjw", + "shipping_rate_data": { + "delivery_estimate": { + "maximum": { + "unit": "week", + "value": 479399576 + }, + "minimum": { + "unit": "day", + "value": 1640284987 + } + }, + "display_name": "PXozGQQnBA", + "fixed_amount": { + "amount": 2040036333, + "currency": "KkRL3jvZMO", + "currency_options": null + }, + "metadata": null, + "tax_behavior": "exclusive", + "tax_code": "NKSQxYdCfO", + "type": "fixed_amount" + } + } + ], + "submit_type": "donate", + "subscription_data": { + "application_fee_percent": 1.7020678102144877, + "billing_cycle_anchor": 1981798554, + "default_tax_rates": ["b3jgFBJq4f"], + "description": "7mpaD2E0jf", + "invoice_settings": { + "issuer": { + "account": "axhiYamJKY", + "type": "account" + } + }, + "metadata": null, + "on_behalf_of": "oGsMnSifXV", + "proration_behavior": "create_prorations", + "transfer_data": { + "amount_percent": 1.5805719275050356, + "destination": "wzJ3U1Tyhd" + }, + "trial_end": 606476058, + "trial_period_days": 1684102049, + "trial_settings": { + "end_behavior": { + "missing_payment_method": "create_invoice" + } + } + }, + "success_url": "hDTwi34TAz", + "tax_id_collection": { + "enabled": true + }, + "ui_mode": "hosted" + } + }`, + Expected: "payment_method_options[bacs_debit][setup_future_usage]=off_session&payment_intent_data[shipping][address][line1]=3YghEmysVn&consent_collection[payment_method_reuse_agreement][position]=auto&payment_method_options[au_becs_debit][setup_future_usage]=none&payment_method_options[customer_balance][bank_transfer][eu_bank_transfer][country]=mzrVWAjBTc&payment_method_options[acss_debit][verification_method]=instant&payment_intent_data[transfer_group]=XKfPQPVhOT&line_items[0][price_data][currency]=euIDO8C4A7&invoice_creation[invoice_data][footer]=OAELqbYbKV&payment_method_options[us_bank_account][verification_method]=automatic&payment_method_options[cashapp][setup_future_usage]=off_session&payment_method_options[konbini][expires_after_days]=664583520&subscription_data[default_tax_rates][]=b3jgFBJq4f&line_items[0][price_data][product_data][tax_code]=PzbIHvqWJp&line_items[0][adjustable_quantity][minimum]=905088217¤cy=oVljMB8lon&invoice_creation[invoice_data][issuer][account]=aqOwDzxnyg&success_url=hDTwi34TAz&payment_method_options[giropay][setup_future_usage]=none&payment_method_options[oxxo][setup_future_usage]=none&payment_method_options[acss_debit][currency]=usd&payment_method_options[acss_debit][mandate_options][payment_schedule]=sporadic&payment_intent_data[shipping][name]=mJYqgRIh3S&subscription_data[transfer_data][amount_percent]=1.5805719275050356&custom_fields[0][label][custom]=uabTz3xzdn&customer_update[name]=never&payment_method_options[acss_debit][mandate_options][interval_description]=iMgay8S9If&automatic_tax[liability][type]=self&payment_intent_data[capture_method]=manual&custom_fields[0][dropdown][options][0][label]=W3oysCi31d&custom_fields[0][text][maximum_length]=331815114&custom_text[terms_of_service_acceptance][message]=zGLTTZItPl&invoice_creation[enabled]=true&shipping_options[0][shipping_rate]=5PAjqTpMjw&shipping_options[0][shipping_rate_data][delivery_estimate][minimum][unit]=day&payment_method_types[]=acss_debit&payment_intent_data[application_fee_amount]=2033958571&custom_text[after_submit][message]=b7ifuedi9S&shipping_options[0][shipping_rate_data][delivery_estimate][minimum][value]=1640284987&payment_method_options[afterpay_clearpay][setup_future_usage]=none&payment_method_options[alipay][setup_future_usage]=none&payment_method_options[us_bank_account][financial_connections][permissions][]=ownership&mode=payment&line_items[0][quantity]=968305911&return_url=YgIdKykEHC&shipping_options[0][shipping_rate_data][delivery_estimate][maximum][value]=479399576&payment_method_options[paypal][reference]=ulLn2NXA1P&subscription_data[transfer_data][destination]=wzJ3U1Tyhd&customer=mT4BKOSu9s&submit_type=donate&payment_method_options[boleto][setup_future_usage]=none&payment_method_options[acss_debit][mandate_options][transaction_type]=business&payment_intent_data[shipping][address][city]=v6nZI33cUt&custom_fields[0][type]=dropdown&invoice_creation[invoice_data][custom_fields][0][name]=LBlZjJ4gEy&shipping_options[0][shipping_rate_data][tax_behavior]=exclusive&customer_email=1xiCJ8M7Pr&invoice_creation[invoice_data][issuer][type]=account&payment_method_options[customer_balance][funding_type]=bank_transfer&payment_intent_data[shipping][address][state]=ILODDWP1IP&subscription_data[proration_behavior]=create_prorations&line_items[0][price_data][product_data][name]=ak6UVjXl1B&invoice_creation[invoice_data][custom_fields][0][value]=EWoKgkV3fg&shipping_options[0][shipping_rate_data][type]=fixed_amount&payment_method_options[link][setup_future_usage]=none&expand[]=ZBxEXz7SN0&subscription_data[invoice_settings][issuer][type]=account&payment_method_collection=always&customer_update[address]=never&payment_method_options[wechat_pay][setup_future_usage]=none&customer_creation=always&payment_method_options[card][statement_descriptor_suffix_kanji]=Y57zexRcIH&payment_method_options[p24][setup_future_usage]=none&locale=auto&line_items[0][price_data][product_data][images][]=gE5K8MOzRc&payment_method_options[us_bank_account][setup_future_usage]=none&payment_intent_data[on_behalf_of]=mpkGzXu3st&custom_fields[0][label][type]=custom&custom_fields[0][optional]=false&line_items[0][price_data][tax_behavior]=inclusive&billing_address_collection=auto&invoice_creation[invoice_data][rendering_options][amount_tax_display]=exclude_tax&shipping_options[0][shipping_rate_data][fixed_amount][currency]=KkRL3jvZMO&payment_method_options[grabpay][setup_future_usage]=none&ui_mode=hosted&payment_intent_data[transfer_data][destination]=LrcNMrJPkO&shipping_options[0][shipping_rate_data][tax_code]=NKSQxYdCfO&payment_method_options[affirm][setup_future_usage]=none&payment_method_options[paypal][setup_future_usage]=none&payment_method_options[acss_debit][mandate_options][custom_mandate_url]=FZwPtJKktL&automatic_tax[liability][account]=gW7D0WhP9C&custom_fields[0][numeric][maximum_length]=678468035&custom_fields[0][text][minimum_length]=1689246767&line_items[0][price_data][recurring][interval_count]=592739346&client_reference_id=ZcJeCf6JAa&line_items[0][price_data][unit_amount]=945322526&line_items[0][adjustable_quantity][maximum]=1665059759&discounts[0][coupon]=tOlEXiZKv9&shipping_address_collection[allowed_countries][]=AC&payment_method_options[paypal][risk_correlation_id]=fj1J6Nux6P&payment_method_options[acss_debit][setup_future_usage]=off_session&payment_method_options[konbini][setup_future_usage]=none&payment_intent_data[statement_descriptor_suffix]=dtPJwyuc4i&payment_intent_data[setup_future_usage]=off_session&subscription_data[on_behalf_of]=oGsMnSifXV&allow_promotion_codes=true&custom_fields[0][key]=5ZeyjIHLn8&custom_text[submit][message]=vGcSz5eSlo&setup_intent_data[on_behalf_of]=165u5Fvodj&discounts[0][promotion_code]=Xknj8juRnm&customer_update[shipping]=auto&shipping_options[0][shipping_rate_data][delivery_estimate][maximum][unit]=week&payment_method_options[oxxo][expires_after_days]=1925345768&payment_intent_data[receipt_email]=LxJLYGjJ4r&subscription_data[trial_settings][end_behavior][missing_payment_method]=create_invoice&after_expiration[recovery][enabled]=true&payment_method_configuration=uwYSwIZP4V&invoice_creation[invoice_data][account_tax_ids][]=dev8vFF6xG&shipping_options[0][shipping_rate_data][fixed_amount][amount]=2040036333&payment_method_options[paypal][capture_method]=manual&payment_method_options[paypal][preferred_locale]=cs-CZ&payment_intent_data[shipping][address][country]=O8MBVcia7c&after_expiration[recovery][allow_promotion_codes]=true&custom_text[shipping_address][message]=XeD5TkmC8k&line_items[0][price_data][recurring][interval]=day&line_items[0][price_data][product]=xilQ2QDVdA&line_items[0][dynamic_tax_rates][]=jMMvH8TmQD&payment_method_options[card][setup_future_usage]=on_session&payment_method_options[customer_balance][bank_transfer][type]=gb_bank_transfer&payment_method_options[sepa_debit][setup_future_usage]=none&automatic_tax[enabled]=false&consent_collection[terms_of_service]=required&payment_method_options[fpx][setup_future_usage]=none&payment_method_options[us_bank_account][financial_connections][prefetch][]=transactions&payment_intent_data[transfer_data][amount]=94957585&payment_method_options[bancontact][setup_future_usage]=none&payment_intent_data[statement_descriptor]=JCOo6lU8Fy&line_items[0][tax_rates][]=Ts1bPAoT0T&line_items[0][price]=fR6vnvprv8&setup_intent_data[description]=U9qFTQnt1W&redirect_on_completion=never&shipping_options[0][shipping_rate_data][display_name]=PXozGQQnBA&payment_method_options[card][installments][enabled]=true&payment_method_options[p24][tos_shown_and_accepted]=true&payment_method_options[wechat_pay][app_id]=9Pu0d1pZ2r&payment_method_options[wechat_pay][client]=ios&payment_method_options[boleto][expires_after_days]=953467886&payment_method_options[eps][setup_future_usage]=none&payment_method_options[acss_debit][mandate_options][default_for][]=invoice&subscription_data[trial_end]=606476058&custom_fields[0][numeric][minimum_length]=2134997439&line_items[0][price_data][product_data][description]=DQECtJEsLI&consent_collection[promotions]=auto&payment_method_options[swish][reference]=rXJq1EX4rc&payment_intent_data[shipping][carrier]=P8mCJlEq1J&payment_intent_data[shipping][tracking_number]=XGOZIrLZf0&payment_method_options[paynow][setup_future_usage]=none&payment_method_options[revolut_pay][setup_future_usage]=off_session&payment_method_options[klarna][setup_future_usage]=none&payment_intent_data[shipping][address][postal_code]=1aAilmcYiq&subscription_data[invoice_settings][issuer][account]=axhiYamJKY&subscription_data[trial_period_days]=1684102049&subscription_data[description]=7mpaD2E0jf&cancel_url=qpmWppPyIv&payment_method_options[card][statement_descriptor_suffix_kana]=ZvJtIONyDK&payment_method_options[pix][expires_after_seconds]=191312234&custom_fields[0][dropdown][options][0][value]=hXN8MppU0k&tax_id_collection[enabled]=true&payment_method_options[sofort][setup_future_usage]=none&payment_method_options[customer_balance][setup_future_usage]=none&payment_method_options[ideal][setup_future_usage]=none&payment_intent_data[description]=yoalRHw9ZG&payment_intent_data[shipping][phone]=CWAbvZM4Kw&expires_at=1756067225&line_items[0][adjustable_quantity][enabled]=false&invoice_creation[invoice_data][description]=MiePp9LfkQ&payment_method_options[card][request_three_d_secure]=any&payment_method_options[customer_balance][bank_transfer][requested_address_types][]=iban&line_items[0][price_data][unit_amount_decimal]=vkJPCvrn9Q&phone_number_collection[enabled]=true&payment_intent_data[shipping][address][line2]=CM9x9Jizzu&subscription_data[billing_cycle_anchor]=1981798554&subscription_data[application_fee_percent]=1.7020678102144877", + }, + { + Name: "PostCheckoutSessions", + RawArguments: `{ + "body": { + "automatic_tax": { + "enabled": false, + "liability": { + "type": "self", + "country": "DE" + } + }, + "subscription_data": { + "description": "nyxWwjZ0JY", + "invoice_settings": { + "issuer": { + "type": "self" + } + }, + "metadata": null, + "trial_period_days": 27623, + "trial_settings": { + "end_behavior": { + "missing_payment_method": "cancel" + } + } + } + } + }`, + Expected: "automatic_tax[enabled]=false&automatic_tax[liability][type]=self&subscription_data[description]=nyxWwjZ0JY&subscription_data[invoice_settings][issuer][type]=self&subscription_data[trial_period_days]=27623&subscription_data[trial_settings][end_behavior][missing_payment_method]=cancel", + }, + } + + ndcSchema := createMockSchema(t) + parseQueryAndSort := func(input string) []string { + items := strings.Split(input, "&") + slices.Sort(items) + return items + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + var info *rest.OperationInfo + for key, f := range ndcSchema.Procedures { + if key == tc.Name { + info = &f + break + } + } + assert.Assert(t, info != nil) + var arguments map[string]any + assert.NilError(t, json.Unmarshal([]byte(tc.RawArguments), &arguments)) + argumentInfo := info.Arguments["body"] + builder := RequestBuilder{ + Schema: ndcSchema, + Operation: info, + Arguments: arguments, + } + buf, err := builder.createFormURLEncoded(&argumentInfo, arguments["body"]) + assert.NilError(t, err) + result, err := io.ReadAll(buf) + assert.NilError(t, err) + assert.DeepEqual(t, parseQueryAndSort(tc.Expected), parseQueryAndSort(string(result))) + }) + } +} + +func createMockSchema(t *testing.T) *rest.NDCRestSchema { + var ndcSchema rest.NDCRestSchema + rawSchemaBytes, err := os.ReadFile("../../ndc-rest-schema/openapi/testdata/petstore3/expected.json") + assert.NilError(t, err) + assert.NilError(t, json.Unmarshal(rawSchemaBytes, &ndcSchema)) + return &ndcSchema +} diff --git a/connector/internal/request_parameter.go b/connector/internal/request_parameter.go new file mode 100644 index 0000000..3b35c7e --- /dev/null +++ b/connector/internal/request_parameter.go @@ -0,0 +1,466 @@ +package internal + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "reflect" + "slices" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "github.com/hasura/ndc-sdk-go/schema" + "github.com/hasura/ndc-sdk-go/utils" + sdkUtils "github.com/hasura/ndc-sdk-go/utils" +) + +var urlAndHeaderLocations = []rest.ParameterLocation{rest.InPath, rest.InQuery, rest.InHeader} + +// evaluate URL and header parameters +func (c *RequestBuilder) evalURLAndHeaderParameters() (string, http.Header, error) { + endpoint, err := url.Parse(c.Operation.Request.URL) + if err != nil { + return "", nil, err + } + headers := http.Header{} + for k, h := range c.Operation.Request.Headers { + v := h.Value() + if v != nil && *v != "" { + headers.Add(k, *v) + } + } + + for argumentKey, argumentInfo := range c.Operation.Arguments { + if argumentInfo.Rest == nil || !slices.Contains(urlAndHeaderLocations, argumentInfo.Rest.In) { + continue + } + if err := c.evalURLAndHeaderParameterBySchema(endpoint, &headers, argumentKey, &argumentInfo, c.Arguments[argumentKey]); err != nil { + return "", nil, fmt.Errorf("%s: %w", argumentKey, err) + } + } + return endpoint.String(), headers, nil +} + +// the query parameters serialization follows [OAS 3.1 spec] +// +// [OAS 3.1 spec]: https://swagger.io/docs/specification/serialization/ +func (c *RequestBuilder) evalURLAndHeaderParameterBySchema(endpoint *url.URL, header *http.Header, argumentKey string, argumentInfo *rest.ArgumentInfo, value any) error { + if argumentInfo.Rest.Name != "" { + argumentKey = argumentInfo.Rest.Name + } + queryParams, err := c.encodeParameterValues(&rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: argumentInfo.Type, + }, + Rest: argumentInfo.Rest.Schema, + }, reflect.ValueOf(value), []string{argumentKey}) + if err != nil { + return err + } + + if len(queryParams) == 0 { + return nil + } + + // following the OAS spec to serialize parameters + // https://swagger.io/docs/specification/serialization/ + // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object + switch argumentInfo.Rest.In { + case rest.InHeader: + setHeaderParameters(header, argumentInfo.Rest, queryParams) + case rest.InQuery: + q := endpoint.Query() + for _, qp := range queryParams { + evalQueryParameterURL(&q, argumentKey, argumentInfo.Rest.EncodingObject, qp.Keys(), qp.Values()) + } + endpoint.RawQuery = encodeQueryValues(q, argumentInfo.Rest.AllowReserved) + case rest.InPath: + defaultParam := queryParams.FindDefault() + if defaultParam != nil { + endpoint.Path = strings.ReplaceAll(endpoint.Path, "{"+argumentKey+"}", strings.Join(defaultParam.Values(), ",")) + } + } + return nil +} + +func (c *RequestBuilder) encodeParameterValues(objectField *rest.ObjectField, reflectValue reflect.Value, fieldPaths []string) (ParameterItems, error) { + results := ParameterItems{} + + typeSchema := objectField.Rest + reflectValue, nonNull := sdkUtils.UnwrapPointerFromReflectValue(reflectValue) + + switch ty := objectField.Type.Interface().(type) { + case *schema.NullableType: + if !nonNull { + return results, nil + } + return c.encodeParameterValues(&rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: ty.UnderlyingType, + }, + Rest: typeSchema, + }, reflectValue, fieldPaths) + case *schema.ArrayType: + if !nonNull { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), errArgumentRequired) + } + elements, ok := reflectValue.Interface().([]any) + if !ok { + return nil, fmt.Errorf("%s: expected array, got <%s> %v", strings.Join(fieldPaths, ""), reflectValue.Kind(), reflectValue.Interface()) + } + for i, elem := range elements { + propPaths := append(fieldPaths, "["+strconv.Itoa(i)+"]") + + outputs, err := c.encodeParameterValues(&rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: ty.ElementType, + }, + Rest: typeSchema.Items, + }, reflect.ValueOf(elem), propPaths) + if err != nil { + return nil, err + } + + for _, output := range outputs { + results.Add(append([]Key{NewIndexKey(i)}, output.Keys()...), output.Values()) + } + } + + return results, nil + case *schema.NamedType: + if !nonNull { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), errArgumentRequired) + } + iScalar, ok := c.Schema.ScalarTypes[ty.Name] + if ok { + return encodeScalarParameterReflectionValues(reflectValue, &iScalar, fieldPaths) + } + kind := reflectValue.Kind() + objectInfo, ok := c.Schema.ObjectTypes[ty.Name] + if !ok { + return nil, fmt.Errorf("%s: invalid type %s", strings.Join(fieldPaths, ""), ty.Name) + } + + switch kind { + case reflect.Map, reflect.Interface: + anyValue := reflectValue.Interface() + object, ok := anyValue.(map[string]any) + if !ok { + return nil, fmt.Errorf("%s: failed to evaluate object, got <%s> %v", strings.Join(fieldPaths, ""), kind, anyValue) + } + for key, fieldInfo := range objectInfo.Fields { + propPaths := append(fieldPaths, "."+key) + fieldVal := object[key] + output, err := c.encodeParameterValues(&fieldInfo, reflect.ValueOf(fieldVal), propPaths) + if err != nil { + return nil, err + } + + for _, pair := range output { + results.Add(append([]Key{NewKey(key)}, pair.Keys()...), pair.Values()) + } + } + case reflect.Struct: + reflectType := reflectValue.Type() + for fieldIndex := range reflectValue.NumField() { + fieldVal := reflectValue.Field(fieldIndex) + fieldType := reflectType.Field(fieldIndex) + fieldInfo, ok := objectInfo.Fields[fieldType.Name] + if !ok { + continue + } + propPaths := append(fieldPaths, "."+fieldType.Name) + output, err := c.encodeParameterValues(&fieldInfo, fieldVal, propPaths) + if err != nil { + return nil, err + } + + for _, pair := range output { + results.Add(append([]Key{NewKey(fieldType.Name)}, pair.Keys()...), pair.Values()) + } + } + default: + return nil, fmt.Errorf("%s: failed to evaluate object, got %s", strings.Join(fieldPaths, ""), kind) + } + return results, nil + } + return nil, fmt.Errorf("%s: invalid type %v", strings.Join(fieldPaths, ""), objectField.Type) +} + +func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *schema.ScalarType, fieldPaths []string) (ParameterItems, error) { + switch sl := scalar.Representation.Interface().(type) { + case *schema.TypeRepresentationBoolean: + value, err := utils.DecodeBooleanReflection(reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{ + NewParameterItem([]Key{}, []string{strconv.FormatBool(value)}), + }, nil + case *schema.TypeRepresentationString, *schema.TypeRepresentationBytes: + value, err := utils.DecodeStringReflection(reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{NewParameterItem([]Key{}, []string{value})}, nil + case *schema.TypeRepresentationInteger, *schema.TypeRepresentationInt8, *schema.TypeRepresentationInt16, *schema.TypeRepresentationInt32, *schema.TypeRepresentationInt64, *schema.TypeRepresentationBigInteger: //nolint:all + value, err := utils.DecodeIntReflection[int64](reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{ + NewParameterItem([]Key{}, []string{strconv.FormatInt(value, 10)}), + }, nil + case *schema.TypeRepresentationNumber, *schema.TypeRepresentationFloat32, *schema.TypeRepresentationFloat64, *schema.TypeRepresentationBigDecimal: //nolint:all + value, err := utils.DecodeFloatReflection[float64](reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{ + NewParameterItem([]Key{}, []string{fmt.Sprint(value)}), + }, nil + case *schema.TypeRepresentationEnum: + value, err := utils.DecodeStringReflection(reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + if !slices.Contains(sl.OneOf, value) { + return nil, fmt.Errorf("%s: the value must be one of %v, got %s", strings.Join(fieldPaths, ""), sl.OneOf, value) + } + return []ParameterItem{NewParameterItem([]Key{}, []string{value})}, nil + case *schema.TypeRepresentationDate: + value, err := utils.DecodeDateReflection(reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{ + NewParameterItem([]Key{}, []string{value.Format(time.DateOnly)}), + }, nil + case *schema.TypeRepresentationTimestamp, *schema.TypeRepresentationTimestampTZ: + value, err := utils.DecodeDateTimeReflection(reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{ + NewParameterItem([]Key{}, []string{value.Format(time.RFC3339)}), + }, nil + case *schema.TypeRepresentationUUID: + rawValue, err := utils.DecodeStringReflection(reflectValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + _, err = uuid.Parse(rawValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + return []ParameterItem{NewParameterItem([]Key{}, []string{rawValue})}, nil + default: + return encodeParameterReflectionValues(reflectValue, fieldPaths) + } +} + +func encodeParameterReflectionValues(reflectValue reflect.Value, fieldPaths []string) (ParameterItems, error) { + results := ParameterItems{} + reflectValue, ok := sdkUtils.UnwrapPointerFromReflectValue(reflectValue) + if !ok { + return results, nil + } + + kind := reflectValue.Kind() + switch kind { + case reflect.Bool: + return []ParameterItem{ + NewParameterItem([]Key{}, []string{strconv.FormatBool(reflectValue.Bool())}), + }, nil + case reflect.String: + return []ParameterItem{NewParameterItem([]Key{}, []string{reflectValue.String()})}, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return []ParameterItem{ + NewParameterItem([]Key{}, []string{strconv.FormatInt(reflectValue.Int(), 10)}), + }, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return []ParameterItem{ + NewParameterItem([]Key{}, []string{strconv.FormatUint(reflectValue.Uint(), 10)}), + }, nil + case reflect.Float32, reflect.Float64: + return []ParameterItem{ + NewParameterItem([]Key{}, []string{fmt.Sprintf("%f", reflectValue.Float())}), + }, nil + case reflect.Slice: + valueLen := reflectValue.Len() + for i := range valueLen { + propPaths := append(fieldPaths, fmt.Sprintf("[%d]", i)) + elem := reflectValue.Index(i) + + outputs, err := encodeParameterReflectionValues(elem, propPaths) + if err != nil { + return nil, err + } + + for _, output := range outputs { + results.Add(append([]Key{NewIndexKey(i)}, output.Keys()...), output.Values()) + } + } + return results, nil + case reflect.Map, reflect.Interface: + anyValue := reflectValue.Interface() + object, ok := anyValue.(map[string]any) + if !ok { + b, err := json.Marshal(anyValue) + if err != nil { + return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) + } + values := []string{strings.Trim(string(b), `"`)} + return []ParameterItem{NewParameterItem([]Key{}, values)}, nil + } + + for key, fieldValue := range object { + propPaths := append(fieldPaths, "."+key) + + output, err := encodeParameterReflectionValues(reflect.ValueOf(fieldValue), propPaths) + if err != nil { + return nil, err + } + + for _, pair := range output { + results.Add(append([]Key{NewKey(key)}, pair.Keys()...), pair.Values()) + } + } + + return results, nil + case reflect.Struct: + reflectType := reflectValue.Type() + for fieldIndex := range reflectValue.NumField() { + fieldVal := reflectValue.Field(fieldIndex) + fieldType := reflectType.Field(fieldIndex) + propPaths := append(fieldPaths, "."+fieldType.Name) + output, err := encodeParameterReflectionValues(fieldVal, propPaths) + if err != nil { + return nil, err + } + + for _, pair := range output { + results.Add(append([]Key{NewKey(fieldType.Name)}, pair.Keys()...), pair.Values()) + } + } + return results, nil + default: + return nil, fmt.Errorf("%s: failed to encode parameter, got %s", strings.Join(fieldPaths, ""), kind) + } +} + +func buildParamQueryKey(name string, encObject rest.EncodingObject, keys Keys, values []string) string { + resultKeys := []string{} + if name != "" { + resultKeys = append(resultKeys, name) + } + keysLength := len(keys) + // non-explode or explode form object does not require param name + // /users?role=admin&firstName=Alex + if (encObject.Explode != nil && !*encObject.Explode) || + (len(values) == 1 && encObject.Style == rest.EncodingStyleForm && (keysLength > 1 || (keysLength == 1 && !keys[0].IsEmpty()))) { + resultKeys = []string{} + } + + if keysLength > 0 { + if encObject.Style != rest.EncodingStyleDeepObject && keys[keysLength-1].IsEmpty() { + keys = keys[:keysLength-1] + } + + for i, key := range keys { + if len(resultKeys) == 0 { + resultKeys = append(resultKeys, key.String()) + continue + } + if i == len(keys)-1 && key.Index() != nil { + // the last element of array in the deepObject style doesn't have index + resultKeys = append(resultKeys, "[]") + continue + } + + resultKeys = append(resultKeys, "["+key.String()+"]") + } + } + + return strings.Join(resultKeys, "") +} + +func evalQueryParameterURL(q *url.Values, name string, encObject rest.EncodingObject, keys Keys, values []string) { + if len(values) == 0 { + return + } + paramKey := buildParamQueryKey(name, encObject, keys, values) + // encode explode queries, e.g /users?id=3&id=4&id=5 + if encObject.Explode == nil || *encObject.Explode { + for _, value := range values { + q.Add(paramKey, value) + } + return + } + + switch encObject.Style { + case rest.EncodingStyleSpaceDelimited: + q.Add(name, strings.Join(values, " ")) + case rest.EncodingStylePipeDelimited: + q.Add(name, strings.Join(values, "|")) + // default style is form + default: + paramValues := values + if paramKey != "" { + paramValues = append([]string{paramKey}, paramValues...) + } + q.Add(name, strings.Join(paramValues, ",")) + } +} + +func encodeQueryValues(qValues url.Values, allowReserved bool) string { + if !allowReserved { + return qValues.Encode() + } + + var builder strings.Builder + index := 0 + for key, values := range qValues { + for i, value := range values { + if index > 0 || i > 0 { + builder.WriteRune('&') + } + builder.WriteString(key) + builder.WriteRune('=') + builder.WriteString(value) + } + index++ + } + return builder.String() +} + +func setHeaderParameters(header *http.Header, param *rest.RequestParameter, queryParams ParameterItems) { + defaultParam := queryParams.FindDefault() + // the param is an array + if defaultParam != nil { + header.Set(param.Name, strings.Join(defaultParam.Values(), ",")) + return + } + + if param.Explode != nil && *param.Explode { + var headerValues []string + for _, pair := range queryParams { + headerValues = append(headerValues, fmt.Sprintf("%s=%s", pair.Keys().String(), strings.Join(pair.Values(), ","))) + } + header.Set(param.Name, strings.Join(headerValues, ",")) + return + } + + var headerValues []string + for _, pair := range queryParams { + pairKey := pair.Keys().String() + for _, v := range pair.Values() { + headerValues = append(headerValues, pairKey, v) + } + } + header.Set(param.Name, strings.Join(headerValues, ",")) +} diff --git a/connector/internal/request_parameter_test.go b/connector/internal/request_parameter_test.go new file mode 100644 index 0000000..0e5c070 --- /dev/null +++ b/connector/internal/request_parameter_test.go @@ -0,0 +1,313 @@ +package internal + +import ( + "encoding/json" + "net/url" + "testing" + + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "github.com/hasura/ndc-sdk-go/utils" + "gotest.tools/v3/assert" +) + +func TestEvalQueryParameterURL(t *testing.T) { + testCases := []struct { + name string + param *rest.RequestParameter + keys []Key + values []string + expected string + }{ + { + name: "empty", + param: &rest.RequestParameter{}, + keys: []Key{NewKey("")}, + values: []string{}, + expected: "", + }, + { + name: "form_explode_single", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{}, + values: []string{"3"}, + expected: "id=3", + }, + { + name: "form_single", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(false), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3"}, + expected: "id=3", + }, + { + name: "form_explode_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3", "4", "5"}, + expected: "id=3&id=4&id=5", + }, + { + name: "spaceDelimited_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(false), + Style: rest.EncodingStyleSpaceDelimited, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3", "4", "5"}, + expected: "id=3 4 5", + }, + { + name: "spaceDelimited_explode_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleSpaceDelimited, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3", "4", "5"}, + expected: "id=3&id=4&id=5", + }, + + { + name: "pipeDelimited_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(false), + Style: rest.EncodingStylePipeDelimited, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3", "4", "5"}, + expected: "id=3|4|5", + }, + { + name: "pipeDelimited_explode_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStylePipeDelimited, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3", "4", "5"}, + expected: "id=3&id=4&id=5", + }, + { + name: "deepObject_explode_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleDeepObject, + }, + }, + keys: []Key{NewKey("")}, + values: []string{"3", "4", "5"}, + expected: "id[]=3&id[]=4&id[]=5", + }, + { + name: "form_object", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(false), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("role")}, + values: []string{"admin"}, + expected: "id=role,admin", + }, + { + name: "form_explode_object", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("role")}, + values: []string{"admin"}, + expected: "role=admin", + }, + { + name: "deepObject_explode_object", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleDeepObject, + }, + }, + keys: []Key{NewKey("role")}, + values: []string{"admin"}, + expected: "id[role]=admin", + }, + { + name: "form_array_object", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(false), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("role"), NewKey(""), NewKey("user"), NewKey("")}, + values: []string{"admin"}, + expected: "id=role[][user],admin", + }, + { + name: "form_explode_array_object", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("role"), NewKey(""), NewKey("user"), NewKey("")}, + values: []string{"admin"}, + expected: "role[][user]=admin", + }, + { + name: "form_explode_array_object_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleForm, + }, + }, + keys: []Key{NewKey("role"), NewKey(""), NewKey("user"), NewKey("")}, + values: []string{"admin", "anonymous"}, + expected: "id[role][][user]=admin&id[role][][user]=anonymous", + }, + { + name: "deepObject_explode_array_object", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleDeepObject, + }, + }, + keys: []Key{NewKey("role"), NewKey(""), NewKey("user"), NewKey("")}, + values: []string{"admin"}, + expected: "id[role][][user][]=admin", + }, + { + name: "deepObject_explode_array_object_multiple", + param: &rest.RequestParameter{ + Name: "id", + EncodingObject: rest.EncodingObject{ + Explode: utils.ToPtr(true), + Style: rest.EncodingStyleDeepObject, + }, + }, + keys: []Key{NewKey("role"), NewKey(""), NewKey("user"), NewKey("")}, + values: []string{"admin", "anonymous"}, + expected: "id[role][][user][]=admin&id[role][][user][]=anonymous", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + qValues := make(url.Values) + evalQueryParameterURL(&qValues, tc.param.Name, tc.param.EncodingObject, tc.keys, tc.values) + assert.Equal(t, tc.expected, encodeQueryValues(qValues, true)) + }) + } +} + +func TestEvalURLAndHeaderParameters(t *testing.T) { + testCases := []struct { + name string + rawArguments string + expectedURL string + errorMsg string + headers map[string]string + }{ + { + name: "findPetsByStatus", + rawArguments: `{ + "status": "available" + }`, + expectedURL: "/pet/findByStatus?status=available", + }, + { + name: "GetInvoices", + rawArguments: `{ + "collection_method": "charge_automatically", + "created": null, + "customer": "UFGkQ6qKPc", + "due_date": null, + "ending_before": "bAOW2sHpAG", + "expand": ["HbZr0T5gf8"], + "limit": 19522, + "starting_after": "McghIoX8E7", + "status": "draft", + "subscription": "UpqQmfokoF" + }`, + expectedURL: "/v1/invoices?collection_method=charge_automatically&customer=UFGkQ6qKPc&ending_before=bAOW2sHpAG&expand[]=HbZr0T5gf8&limit=19522&starting_after=McghIoX8E7&status=draft&subscription=UpqQmfokoF", + }, + } + + ndcSchema := createMockSchema(t) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var info *rest.OperationInfo + for key, f := range ndcSchema.Functions { + if key == tc.name { + info = &f + break + } + } + var arguments map[string]any + assert.NilError(t, json.Unmarshal([]byte(tc.rawArguments), &arguments)) + + builder := RequestBuilder{ + Schema: ndcSchema, + Operation: info, + Arguments: arguments, + } + result, headers, err := builder.evalURLAndHeaderParameters() + if tc.errorMsg != "" { + assert.ErrorContains(t, err, tc.errorMsg) + } else { + assert.NilError(t, err) + decodedValue, err := url.QueryUnescape(result) + assert.NilError(t, err) + assert.Equal(t, tc.expectedURL, decodedValue) + for k, v := range tc.headers { + assert.Equal(t, v, headers.Get(k)) + } + } + }) + } +} diff --git a/rest/internal/types.go b/connector/internal/types.go similarity index 73% rename from rest/internal/types.go rename to connector/internal/types.go index 8417aeb..40583db 100644 --- a/rest/internal/types.go +++ b/connector/internal/types.go @@ -2,6 +2,7 @@ package internal import ( "encoding/json" + "errors" "fmt" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" @@ -15,6 +16,12 @@ const ( defaultRetryDelays uint = 1000 ) +var ( + errArgumentRequired = errors.New("argument is required") + errRequestBodyRequired = errors.New("request body is required") + errRequestBodyTypeRequired = errors.New("failed to decode request body, empty body type") +) + var defaultRetryHTTPStatus = []int64{429, 500, 502, 503} const ( @@ -26,27 +33,33 @@ const ( ) // SingleObjectType represents the object type of REST execution options for single server -var SingleObjectType schema.ObjectType = schema.ObjectType{ +var SingleObjectType rest.ObjectType = rest.ObjectType{ Description: utils.ToPtr("Execution options for REST requests to a single server"), - Fields: schema.ObjectTypeFields{ - "servers": schema.ObjectField{ - Description: utils.ToPtr("Specify remote servers to receive the request. If there are many server IDs the server is selected randomly"), - Type: schema.NewNullableType(schema.NewArrayType(schema.NewNamedType(RESTServerIDScalarName))).Encode(), + Fields: map[string]rest.ObjectField{ + "servers": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Specify remote servers to receive the request. If there are many server IDs the server is selected randomly"), + Type: schema.NewNullableType(schema.NewArrayType(schema.NewNamedType(RESTServerIDScalarName))).Encode(), + }, }, }, } // DistributedObjectType represents the object type of REST execution options for distributed servers -var DistributedObjectType schema.ObjectType = schema.ObjectType{ +var DistributedObjectType rest.ObjectType = rest.ObjectType{ Description: utils.ToPtr("Distributed execution options for REST requests to multiple servers"), - Fields: schema.ObjectTypeFields{ - "servers": schema.ObjectField{ - Description: utils.ToPtr("Specify remote servers to receive the request"), - Type: schema.NewNullableType(schema.NewArrayType(schema.NewNamedType(RESTServerIDScalarName))).Encode(), + Fields: map[string]rest.ObjectField{ + "servers": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Specify remote servers to receive the request"), + Type: schema.NewNullableType(schema.NewArrayType(schema.NewNamedType(RESTServerIDScalarName))).Encode(), + }, }, - "parallel": schema.ObjectField{ - Description: utils.ToPtr("Execute requests to remote servers in parallel"), - Type: schema.NewNullableNamedType(string(rest.ScalarBoolean)).Encode(), + "parallel": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Execute requests to remote servers in parallel"), + Type: schema.NewNullableNamedType(string(rest.ScalarBoolean)).Encode(), + }, }, }, } diff --git a/connector/internal/utils.go b/connector/internal/utils.go new file mode 100644 index 0000000..5e1e8c8 --- /dev/null +++ b/connector/internal/utils.go @@ -0,0 +1,23 @@ +package internal + +import ( + "fmt" + + "github.com/hasura/ndc-sdk-go/schema" +) + +// UnwrapNullableType unwraps the underlying type of the nullable type +func UnwrapNullableType(input schema.Type) (schema.TypeEncoder, bool, error) { + switch ty := input.Interface().(type) { + case *schema.NullableType: + childType, _, err := UnwrapNullableType(ty.UnderlyingType) + if err != nil { + return nil, false, err + } + return childType, true, nil + case *schema.NamedType, *schema.ArrayType: + return ty, false, nil + default: + return nil, false, fmt.Errorf("invalid type %v", input) + } +} diff --git a/rest/mutation.go b/connector/mutation.go similarity index 75% rename from rest/mutation.go rename to connector/mutation.go index a187566..47dd972 100644 --- a/rest/mutation.go +++ b/connector/mutation.go @@ -5,12 +5,13 @@ import ( "encoding/json" "fmt" - "github.com/hasura/ndc-rest/rest/internal" + "github.com/hasura/ndc-rest/connector/internal" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" "github.com/hasura/ndc-sdk-go/schema" ) // Mutation executes a mutation. -func (c *RESTConnector) Mutation(ctx context.Context, configuration *Configuration, state *State, request *schema.MutationRequest) (*schema.MutationResponse, error) { +func (c *RESTConnector) Mutation(ctx context.Context, configuration *configuration.Configuration, state *State, request *schema.MutationRequest) (*schema.MutationResponse, error) { operationResults := make([]schema.MutationOperationResults, len(request.Operations)) for i, operation := range request.Operations { @@ -45,11 +46,11 @@ func (c *RESTConnector) execProcedure(ctx context.Context, operation *schema.Mut }) } - endpoint, headers, err := c.evalURLAndHeaderParameters(procedure.Request, procedure.Arguments, rawArgs) + // 2. build the request + builder := internal.NewRequestBuilder(c.schema, procedure, rawArgs) + httpRequest, err := builder.Build() if err != nil { - return nil, schema.BadRequestError("failed to evaluate URL and Headers from parameters", map[string]any{ - "cause": err.Error(), - }) + return nil, err } restOptions, err := parseRESTOptionsFromArguments(procedure.Arguments, rawArgs[internal.RESTOptionsArgumentName]) @@ -59,14 +60,9 @@ func (c *RESTConnector) execProcedure(ctx context.Context, operation *schema.Mut }) } - // 2. create and execute request - // 3. evaluate response selection restOptions.Settings = settings - httpRequest, err := c.createRequest(procedure.Request, endpoint, headers, rawArgs) - if err != nil { - return nil, err - } + // 3. execute the request and evaluate response selection result, err := c.client.Send(ctx, httpRequest, operation.Fields, procedure.ResultType, restOptions) if err != nil { return nil, err diff --git a/rest/query.go b/connector/query.go similarity index 77% rename from rest/query.go rename to connector/query.go index cb109dd..44aaafd 100644 --- a/rest/query.go +++ b/connector/query.go @@ -3,13 +3,14 @@ package rest import ( "context" - "github.com/hasura/ndc-rest/rest/internal" + "github.com/hasura/ndc-rest/connector/internal" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" "github.com/hasura/ndc-sdk-go/schema" "github.com/hasura/ndc-sdk-go/utils" ) // Query executes a query. -func (c *RESTConnector) Query(ctx context.Context, configuration *Configuration, state *State, request *schema.QueryRequest) (schema.QueryResponse, error) { +func (c *RESTConnector) Query(ctx context.Context, configuration *configuration.Configuration, state *State, request *schema.QueryRequest) (schema.QueryResponse, error) { valueField, err := utils.EvalFunctionSelectionFieldValue(request) if err != nil { return nil, schema.UnprocessableContentError(err.Error(), nil) @@ -52,11 +53,11 @@ func (c *RESTConnector) execQuery(ctx context.Context, request *schema.QueryRequ }) } - endpoint, headers, err := c.evalURLAndHeaderParameters(function.Request, function.Arguments, rawArgs) + // 2. build the request + builder := internal.NewRequestBuilder(c.schema, function, rawArgs) + httpRequest, err := builder.Build() if err != nil { - return nil, schema.UnprocessableContentError("failed to evaluate URL and Headers from parameters", map[string]any{ - "cause": err.Error(), - }) + return nil, err } restOptions, err := parseRESTOptionsFromArguments(function.Arguments, rawArgs[internal.RESTOptionsArgumentName]) @@ -66,13 +67,8 @@ func (c *RESTConnector) execQuery(ctx context.Context, request *schema.QueryRequ }) } - // 2. create and execute request - // 3. evaluate response selection restOptions.Settings = settings - httpRequest, err := c.createRequest(function.Request, endpoint, headers, nil) - if err != nil { - return nil, err - } + // 3. execute request and evaluate response selection return c.client.Send(ctx, httpRequest, queryFields, function.ResultType, restOptions) } diff --git a/rest/schema.go b/connector/schema.go similarity index 60% rename from rest/schema.go rename to connector/schema.go index a6603ef..dbd733b 100644 --- a/rest/schema.go +++ b/connector/schema.go @@ -3,27 +3,27 @@ package rest import ( "context" "encoding/json" - "errors" "fmt" "log/slog" "reflect" "strconv" + "github.com/hasura/ndc-rest/connector/internal" "github.com/hasura/ndc-rest/ndc-rest-schema/command" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" restUtils "github.com/hasura/ndc-rest/ndc-rest-schema/utils" - "github.com/hasura/ndc-rest/rest/internal" "github.com/hasura/ndc-sdk-go/schema" "github.com/hasura/ndc-sdk-go/utils" ) // GetSchema gets the connector's schema. -func (c *RESTConnector) GetSchema(ctx context.Context, configuration *Configuration, _ *State) (schema.SchemaResponseMarshaler, error) { +func (c *RESTConnector) GetSchema(ctx context.Context, configuration *configuration.Configuration, _ *State) (schema.SchemaResponseMarshaler, error) { return c.rawSchema, nil } // BuildSchemaFiles build NDC REST schema from file list -func BuildSchemaFiles(configDir string, files []ConfigItem, logger *slog.Logger) ([]NDCRestSchemaWithName, map[string][]string) { +func BuildSchemaFiles(configDir string, files []configuration.ConfigItem, logger *slog.Logger) ([]NDCRestSchemaWithName, map[string][]string) { schemas := make([]NDCRestSchemaWithName, len(files)) errors := make(map[string][]string) for i, file := range files { @@ -46,9 +46,9 @@ func BuildSchemaFiles(configDir string, files []ConfigItem, logger *slog.Logger) return schemas, errors } -func buildSchemaFile(configDir string, conf *ConfigItem, logger *slog.Logger) (*rest.NDCRestSchema, error) { +func buildSchemaFile(configDir string, conf *configuration.ConfigItem, logger *slog.Logger) (*rest.NDCRestSchema, error) { if conf.ConvertConfig.File == "" { - return nil, errors.New("file path is empty") + return nil, errFilePathRequired } command.ResolveConvertConfigArguments(&conf.ConvertConfig, configDir, nil) ndcSchema, err := command.ConvertToNDCSchema(&conf.ConvertConfig, logger) @@ -67,10 +67,11 @@ func buildSchemaFile(configDir string, conf *ConfigItem, logger *slog.Logger) (* // ApplyNDCRestSchemas applies slice of raw NDC REST schemas to the connector func (c *RESTConnector) ApplyNDCRestSchemas(schemas []NDCRestSchemaWithName) map[string][]string { - ndcSchema := &schema.SchemaResponse{ - Collections: []schema.CollectionInfo{}, + ndcSchema := &rest.NDCRestSchema{ ScalarTypes: make(schema.SchemaResponseScalarTypes), - ObjectTypes: make(schema.SchemaResponseObjectTypes), + ObjectTypes: make(map[string]rest.ObjectType), + Functions: make(map[string]rest.OperationInfo), + Procedures: make(map[string]rest.OperationInfo), } errors := make(map[string][]string) @@ -151,10 +152,10 @@ func (c *RESTConnector) ApplyNDCRestSchemas(schemas []NDCRestSchemaWithName) map settings.Servers[i] = server } } - meta := RESTMetadata{ - settings: settings, - functions: map[string]rest.RESTFunctionInfo{}, - procedures: map[string]rest.RESTProcedureInfo{}, + meta := rest.NDCRestSchema{ + Settings: settings, + Functions: map[string]rest.OperationInfo{}, + Procedures: map[string]rest.OperationInfo{}, } var errs []string @@ -172,54 +173,54 @@ func (c *RESTConnector) ApplyNDCRestSchemas(schemas []NDCRestSchemaWithName) map slog.Warn(fmt.Sprintf("Object type %s is conflicted", name)) } } - ndcSchema.Collections = append(ndcSchema.Collections, item.schema.Collections...) - var functionSchemas []schema.FunctionInfo - var procedureSchemas []schema.ProcedureInfo - for _, fnItem := range item.schema.Functions { + for fnName, fnItem := range item.schema.Functions { if fnItem.Request == nil || fnItem.Request.URL == "" { continue } req, err := validateRequestSchema(fnItem.Request, "get") if err != nil { - errs = append(errs, fmt.Sprintf("function %s: %s", fnItem.Name, err)) + errs = append(errs, fmt.Sprintf("function %s: %s", fnName, err)) continue } - fn := rest.RESTFunctionInfo{ - Request: req, - FunctionInfo: fnItem.FunctionInfo, + fn := rest.OperationInfo{ + Request: req, + Arguments: fnItem.Arguments, + Description: fnItem.Description, + ResultType: fnItem.ResultType, } - meta.functions[fnItem.Name] = fn - functionSchemas = append(functionSchemas, fn.FunctionInfo) + meta.Functions[fnName] = fn + ndcSchema.Functions[fnName] = fn } - for _, procItem := range item.schema.Procedures { + for procName, procItem := range item.schema.Procedures { if procItem.Request == nil || procItem.Request.URL == "" { continue } req, err := validateRequestSchema(procItem.Request, "") if err != nil { - errs = append(errs, fmt.Sprintf("procedure %s: %s", procItem.Name, err)) + errs = append(errs, fmt.Sprintf("procedure %s: %s", procName, err)) continue } - meta.procedures[procItem.Name] = rest.RESTProcedureInfo{ - Request: req, - ProcedureInfo: procItem.ProcedureInfo, + + proc := rest.OperationInfo{ + Request: req, + Arguments: procItem.Arguments, + Description: procItem.Description, + ResultType: procItem.ResultType, } - procedureSchemas = append(procedureSchemas, procItem.ProcedureInfo) + meta.Procedures[procName] = proc + ndcSchema.Procedures[procName] = proc } if len(errs) > 0 { errors[item.name] = errs continue } - ndcSchema.Functions = append(ndcSchema.Functions, functionSchemas...) - ndcSchema.Procedures = append(ndcSchema.Procedures, procedureSchemas...) - c.metadata = append(c.metadata, meta) } - schemaBytes, err := json.Marshal(ndcSchema) + schemaBytes, err := json.Marshal(ndcSchema.ToSchemaResponse()) if err != nil { errors["schema"] = []string{err.Error()} } @@ -228,7 +229,7 @@ func (c *RESTConnector) ApplyNDCRestSchemas(schemas []NDCRestSchemaWithName) map return errors } - c.schema = &schema.SchemaResponse{ + c.schema = &rest.NDCRestSchema{ ScalarTypes: ndcSchema.ScalarTypes, ObjectTypes: ndcSchema.ObjectTypes, } @@ -251,7 +252,7 @@ func validateRequestSchema(req *rest.Request, defaultMethod string) (*rest.Reque return req, nil } -func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *ConfigItem) { +func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *configuration.ConfigItem) { if restSchema.Settings == nil || len(restSchema.Settings.Servers) < 2 { return } @@ -271,20 +272,21 @@ func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *ConfigItem) { serverScalar.Representation = schema.NewTypeRepresentationEnum(serverIDs).Encode() restSchema.ScalarTypes[internal.RESTServerIDScalarName] = *serverScalar - restSchema.ObjectTypes[internal.RESTSingleOptionsObjectName] = internal.SingleObjectType - restSingleOptionsArgument := schema.ArgumentInfo{ - Description: internal.SingleObjectType.Description, - Type: schema.NewNullableNamedType(internal.RESTSingleOptionsObjectName).Encode(), + restSingleOptionsArgument := rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Description: internal.SingleObjectType.Description, + Type: schema.NewNullableNamedType(internal.RESTSingleOptionsObjectName).Encode(), + }, } for _, fn := range restSchema.Functions { - fn.FunctionInfo.Arguments[internal.RESTOptionsArgumentName] = restSingleOptionsArgument + fn.Arguments[internal.RESTOptionsArgumentName] = restSingleOptionsArgument } for _, proc := range restSchema.Procedures { - proc.ProcedureInfo.Arguments[internal.RESTOptionsArgumentName] = restSingleOptionsArgument + proc.Arguments[internal.RESTOptionsArgumentName] = restSingleOptionsArgument } if !conf.Distributed { @@ -292,70 +294,70 @@ func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *ConfigItem) { } restSchema.ObjectTypes[internal.RESTDistributedOptionsObjectName] = internal.DistributedObjectType - restSchema.ObjectTypes[internal.DistributedErrorObjectName] = schema.ObjectType{ + restSchema.ObjectTypes[internal.DistributedErrorObjectName] = rest.ObjectType{ Description: utils.ToPtr("The error response of the remote request"), - Fields: schema.ObjectTypeFields{ - "server": schema.ObjectField{ - Description: utils.ToPtr("Identity of the remote server"), - Type: schema.NewNamedType(internal.RESTServerIDScalarName).Encode(), + Fields: map[string]rest.ObjectField{ + "server": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Identity of the remote server"), + Type: schema.NewNamedType(internal.RESTServerIDScalarName).Encode(), + }, }, - "message": schema.ObjectField{ - Description: utils.ToPtr("An optional human-readable summary of the error"), - Type: schema.NewNullableType(schema.NewNamedType(string(rest.ScalarString))).Encode(), + "message": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("An optional human-readable summary of the error"), + Type: schema.NewNullableType(schema.NewNamedType(string(rest.ScalarString))).Encode(), + }, }, - "details": schema.ObjectField{ - Description: utils.ToPtr("Any additional structured information about the error"), - Type: schema.NewNullableType(schema.NewNamedType(string(rest.ScalarJSON))).Encode(), + "details": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Any additional structured information about the error"), + Type: schema.NewNullableType(schema.NewNamedType(string(rest.ScalarJSON))).Encode(), + }, }, }, } - functionsLen := len(restSchema.Functions) - for i := range functionsLen { - fn := restSchema.Functions[i] - funcName := buildDistributedName(fn.Name) - info := schema.FunctionInfo{ - Arguments: cloneDistributedArguments(fn.FunctionInfo.Arguments), - Description: fn.FunctionInfo.Description, - Name: funcName, + functionKeys := utils.GetKeys(restSchema.Functions) + for _, key := range functionKeys { + fn := restSchema.Functions[key] + funcName := buildDistributedName(key) + distributedFn := rest.OperationInfo{ + Request: fn.Request, + Arguments: cloneDistributedArguments(fn.Arguments), + Description: fn.Description, ResultType: schema.NewNamedType(buildDistributedResultObjectType(restSchema, funcName, fn.ResultType)).Encode(), } - distributedFn := &rest.RESTFunctionInfo{ - Request: fn.Request, - FunctionInfo: info, - } - restSchema.Functions = append(restSchema.Functions, distributedFn) + restSchema.Functions[funcName] = distributedFn } - proceduresLen := len(restSchema.Procedures) - for i := range proceduresLen { - proc := restSchema.Procedures[i] - procName := buildDistributedName(proc.Name) - info := schema.ProcedureInfo{ - Arguments: cloneDistributedArguments(proc.ProcedureInfo.Arguments), - Description: proc.ProcedureInfo.Description, - Name: procName, - ResultType: schema.NewNamedType(buildDistributedResultObjectType(restSchema, procName, proc.ResultType)).Encode(), - } + procedureKeys := utils.GetKeys(restSchema.Procedures) + for _, key := range procedureKeys { + proc := restSchema.Procedures[key] + procName := buildDistributedName(key) - distributedProc := &rest.RESTProcedureInfo{ - Request: proc.Request, - ProcedureInfo: info, + distributedProc := rest.OperationInfo{ + Request: proc.Request, + Arguments: cloneDistributedArguments(proc.Arguments), + Description: proc.Description, + ResultType: schema.NewNamedType(buildDistributedResultObjectType(restSchema, procName, proc.ResultType)).Encode(), } - restSchema.Procedures = append(restSchema.Procedures, distributedProc) + restSchema.Procedures[procName] = distributedProc } } -func cloneDistributedArguments(arguments map[string]schema.ArgumentInfo) map[string]schema.ArgumentInfo { - result := map[string]schema.ArgumentInfo{} +func cloneDistributedArguments(arguments map[string]rest.ArgumentInfo) map[string]rest.ArgumentInfo { + result := map[string]rest.ArgumentInfo{} for k, v := range arguments { if k != internal.RESTOptionsArgumentName { result[k] = v } } - result[internal.RESTOptionsArgumentName] = schema.ArgumentInfo{ - Description: internal.DistributedObjectType.Description, - Type: schema.NewNullableNamedType(internal.RESTDistributedOptionsObjectName).Encode(), + result[internal.RESTOptionsArgumentName] = rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Description: internal.DistributedObjectType.Description, + Type: schema.NewNullableNamedType(internal.RESTDistributedOptionsObjectName).Encode(), + }, } return result } @@ -364,30 +366,38 @@ func buildDistributedResultObjectType(restSchema *rest.NDCRestSchema, operationN distResultType := restUtils.StringSliceToPascalCase([]string{operationName, "Result"}) distResultDataType := distResultType + "Data" - restSchema.ObjectTypes[distResultDataType] = schema.ObjectType{ + restSchema.ObjectTypes[distResultDataType] = rest.ObjectType{ Description: utils.ToPtr("Distributed response data of " + operationName), - Fields: schema.ObjectTypeFields{ - "server": schema.ObjectField{ - Description: utils.ToPtr("Identity of the remote server"), - Type: schema.NewNamedType(internal.RESTServerIDScalarName).Encode(), + Fields: map[string]rest.ObjectField{ + "server": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Identity of the remote server"), + Type: schema.NewNamedType(internal.RESTServerIDScalarName).Encode(), + }, }, - "data": schema.ObjectField{ - Description: utils.ToPtr("A result of " + operationName), - Type: underlyingType, + "data": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("A result of " + operationName), + Type: underlyingType, + }, }, }, } - restSchema.ObjectTypes[distResultType] = schema.ObjectType{ + restSchema.ObjectTypes[distResultType] = rest.ObjectType{ Description: utils.ToPtr("Distributed responses of " + operationName), - Fields: schema.ObjectTypeFields{ - "results": schema.ObjectField{ - Description: utils.ToPtr("Results of " + operationName), - Type: schema.NewArrayType(schema.NewNamedType(distResultDataType)).Encode(), + Fields: map[string]rest.ObjectField{ + "results": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Results of " + operationName), + Type: schema.NewArrayType(schema.NewNamedType(distResultDataType)).Encode(), + }, }, - "errors": schema.ObjectField{ - Description: utils.ToPtr("Error responses of " + operationName), - Type: schema.NewArrayType(schema.NewNamedType(internal.DistributedErrorObjectName)).Encode(), + "errors": { + ObjectField: schema.ObjectField{ + Description: utils.ToPtr("Error responses of " + operationName), + Type: schema.NewArrayType(schema.NewNamedType(internal.DistributedErrorObjectName)).Encode(), + }, }, }, } @@ -403,7 +413,7 @@ func printSchemaValidationError(logger *slog.Logger, errors map[string][]string) logger.Error("errors happen when validating NDC REST schemas", slog.Any("errors", errors)) } -func parseRESTOptionsFromArguments(arguments map[string]schema.ArgumentInfo, rawRestOptions any) (*internal.RESTOptions, error) { +func parseRESTOptionsFromArguments(arguments map[string]rest.ArgumentInfo, rawRestOptions any) (*internal.RESTOptions, error) { var result internal.RESTOptions if err := result.FromValue(rawRestOptions); err != nil { return nil, err diff --git a/rest/testdata/auth/config.yaml b/connector/testdata/auth/config.yaml similarity index 100% rename from rest/testdata/auth/config.yaml rename to connector/testdata/auth/config.yaml diff --git a/rest/testdata/auth/schema.yaml b/connector/testdata/auth/schema.yaml similarity index 95% rename from rest/testdata/auth/schema.yaml rename to connector/testdata/auth/schema.yaml index 3cd2538..cba438d 100644 --- a/rest/testdata/auth/schema.yaml +++ b/connector/testdata/auth/schema.yaml @@ -30,7 +30,8 @@ settings: version: 1.0.18 collections: [] functions: - - request: + findPets: + request: url: "/pet" method: get parameters: [] @@ -43,19 +44,10 @@ functions: name: Pet type: named type: array - - request: + findPetsByStatus: + request: url: "/pet/findByStatus" method: get - parameters: - - name: status - in: query - schema: - type: String - nullable: true - enum: - - available - - pending - - sold security: - bearer: [] arguments: @@ -66,6 +58,14 @@ functions: underlying_type: name: String type: named + rest: + in: query + schema: + type: [string] + enum: + - available + - pending + - sold description: Finds Pets by status name: findPetsByStatus result_type: @@ -73,7 +73,8 @@ functions: name: Pet type: named type: array - - request: + petRetry: + request: url: "/pet/retry" method: get parameters: [] @@ -86,7 +87,8 @@ functions: type: named type: array procedures: - - request: + addPet: + request: url: "/pet" method: post headers: @@ -108,7 +110,8 @@ procedures: result_type: name: Pet type: named - - request: + createModel: + request: url: /model method: post requestBody: diff --git a/rest/testdata/jsonplaceholder/config.yaml b/connector/testdata/jsonplaceholder/config.yaml similarity index 100% rename from rest/testdata/jsonplaceholder/config.yaml rename to connector/testdata/jsonplaceholder/config.yaml diff --git a/rest/testdata/jsonplaceholder/snapshots/mutation/create_post/expected.json b/connector/testdata/jsonplaceholder/snapshots/mutation/create_post/expected.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/mutation/create_post/expected.json rename to connector/testdata/jsonplaceholder/snapshots/mutation/create_post/expected.json diff --git a/rest/testdata/jsonplaceholder/snapshots/mutation/create_post/request.json b/connector/testdata/jsonplaceholder/snapshots/mutation/create_post/request.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/mutation/create_post/request.json rename to connector/testdata/jsonplaceholder/snapshots/mutation/create_post/request.json diff --git a/rest/testdata/jsonplaceholder/snapshots/mutation/delete_post/expected.json b/connector/testdata/jsonplaceholder/snapshots/mutation/delete_post/expected.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/mutation/delete_post/expected.json rename to connector/testdata/jsonplaceholder/snapshots/mutation/delete_post/expected.json diff --git a/rest/testdata/jsonplaceholder/snapshots/mutation/delete_post/request.json b/connector/testdata/jsonplaceholder/snapshots/mutation/delete_post/request.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/mutation/delete_post/request.json rename to connector/testdata/jsonplaceholder/snapshots/mutation/delete_post/request.json diff --git a/rest/testdata/jsonplaceholder/snapshots/query/get_albums/expected.json b/connector/testdata/jsonplaceholder/snapshots/query/get_albums/expected.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/query/get_albums/expected.json rename to connector/testdata/jsonplaceholder/snapshots/query/get_albums/expected.json diff --git a/rest/testdata/jsonplaceholder/snapshots/query/get_albums/request.json b/connector/testdata/jsonplaceholder/snapshots/query/get_albums/request.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/query/get_albums/request.json rename to connector/testdata/jsonplaceholder/snapshots/query/get_albums/request.json diff --git a/rest/testdata/jsonplaceholder/snapshots/query/get_user/expected.json b/connector/testdata/jsonplaceholder/snapshots/query/get_user/expected.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/query/get_user/expected.json rename to connector/testdata/jsonplaceholder/snapshots/query/get_user/expected.json diff --git a/rest/testdata/jsonplaceholder/snapshots/query/get_user/request.json b/connector/testdata/jsonplaceholder/snapshots/query/get_user/request.json similarity index 100% rename from rest/testdata/jsonplaceholder/snapshots/query/get_user/request.json rename to connector/testdata/jsonplaceholder/snapshots/query/get_user/request.json diff --git a/rest/testdata/jsonplaceholder/swagger.json b/connector/testdata/jsonplaceholder/swagger.json similarity index 100% rename from rest/testdata/jsonplaceholder/swagger.json rename to connector/testdata/jsonplaceholder/swagger.json diff --git a/rest/testdata/multi-schemas/cat.yaml b/connector/testdata/multi-schemas/cat.yaml similarity index 96% rename from rest/testdata/multi-schemas/cat.yaml rename to connector/testdata/multi-schemas/cat.yaml index 58599c2..138b51b 100644 --- a/rest/testdata/multi-schemas/cat.yaml +++ b/connector/testdata/multi-schemas/cat.yaml @@ -5,7 +5,8 @@ settings: pet: cat collections: [] functions: - - request: + findCats: + request: url: "/cat" method: get arguments: {} diff --git a/rest/testdata/multi-schemas/config.yaml b/connector/testdata/multi-schemas/config.yaml similarity index 100% rename from rest/testdata/multi-schemas/config.yaml rename to connector/testdata/multi-schemas/config.yaml diff --git a/rest/testdata/multi-schemas/dog.yaml b/connector/testdata/multi-schemas/dog.yaml similarity index 96% rename from rest/testdata/multi-schemas/dog.yaml rename to connector/testdata/multi-schemas/dog.yaml index 4544028..ca28b14 100644 --- a/rest/testdata/multi-schemas/dog.yaml +++ b/connector/testdata/multi-schemas/dog.yaml @@ -5,7 +5,8 @@ settings: pet: dog collections: [] functions: - - request: + findDogs: + request: url: "/dog" method: get parameters: [] diff --git a/rest/testdata/patch/config.yaml b/connector/testdata/patch/config.yaml similarity index 100% rename from rest/testdata/patch/config.yaml rename to connector/testdata/patch/config.yaml diff --git a/rest/testdata/patch/patch-after.yaml b/connector/testdata/patch/patch-after.yaml similarity index 100% rename from rest/testdata/patch/patch-after.yaml rename to connector/testdata/patch/patch-after.yaml diff --git a/rest/testdata/patch/patch-before.yaml b/connector/testdata/patch/patch-before.yaml similarity index 100% rename from rest/testdata/patch/patch-before.yaml rename to connector/testdata/patch/patch-before.yaml diff --git a/rest/testdata/petstore3/config.yaml b/connector/testdata/petstore3/config.yaml similarity index 100% rename from rest/testdata/petstore3/config.yaml rename to connector/testdata/petstore3/config.yaml diff --git a/rest/testdata/petstore3/openapi.yaml b/connector/testdata/petstore3/openapi.yaml similarity index 100% rename from rest/testdata/petstore3/openapi.yaml rename to connector/testdata/petstore3/openapi.yaml diff --git a/rest/testdata/petstore3/snapshots/mutation/addPet/expected.json b/connector/testdata/petstore3/snapshots/mutation/addPet/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/addPet/expected.json rename to connector/testdata/petstore3/snapshots/mutation/addPet/expected.json diff --git a/rest/testdata/petstore3/snapshots/mutation/addPet/request.json b/connector/testdata/petstore3/snapshots/mutation/addPet/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/addPet/request.json rename to connector/testdata/petstore3/snapshots/mutation/addPet/request.json diff --git a/rest/testdata/petstore3/snapshots/mutation/deleteOrder/expected.json b/connector/testdata/petstore3/snapshots/mutation/deleteOrder/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/deleteOrder/expected.json rename to connector/testdata/petstore3/snapshots/mutation/deleteOrder/expected.json diff --git a/rest/testdata/petstore3/snapshots/mutation/deleteOrder/request.json b/connector/testdata/petstore3/snapshots/mutation/deleteOrder/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/deleteOrder/request.json rename to connector/testdata/petstore3/snapshots/mutation/deleteOrder/request.json diff --git a/rest/testdata/petstore3/snapshots/mutation/placeOrder/expected.json b/connector/testdata/petstore3/snapshots/mutation/placeOrder/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/placeOrder/expected.json rename to connector/testdata/petstore3/snapshots/mutation/placeOrder/expected.json diff --git a/rest/testdata/petstore3/snapshots/mutation/placeOrder/request.json b/connector/testdata/petstore3/snapshots/mutation/placeOrder/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/placeOrder/request.json rename to connector/testdata/petstore3/snapshots/mutation/placeOrder/request.json diff --git a/rest/testdata/petstore3/snapshots/mutation/updatePet/expected.json b/connector/testdata/petstore3/snapshots/mutation/updatePet/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/updatePet/expected.json rename to connector/testdata/petstore3/snapshots/mutation/updatePet/expected.json diff --git a/rest/testdata/petstore3/snapshots/mutation/updatePet/request.json b/connector/testdata/petstore3/snapshots/mutation/updatePet/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/updatePet/request.json rename to connector/testdata/petstore3/snapshots/mutation/updatePet/request.json diff --git a/rest/testdata/petstore3/snapshots/mutation/updatePetWithForm/expected.json b/connector/testdata/petstore3/snapshots/mutation/updatePetWithForm/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/updatePetWithForm/expected.json rename to connector/testdata/petstore3/snapshots/mutation/updatePetWithForm/expected.json diff --git a/rest/testdata/petstore3/snapshots/mutation/updatePetWithForm/request.json b/connector/testdata/petstore3/snapshots/mutation/updatePetWithForm/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/updatePetWithForm/request.json rename to connector/testdata/petstore3/snapshots/mutation/updatePetWithForm/request.json diff --git a/rest/testdata/petstore3/snapshots/mutation/uploadFile/expected.json b/connector/testdata/petstore3/snapshots/mutation/uploadFile/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/uploadFile/expected.json rename to connector/testdata/petstore3/snapshots/mutation/uploadFile/expected.json diff --git a/rest/testdata/petstore3/snapshots/mutation/uploadFile/request.json b/connector/testdata/petstore3/snapshots/mutation/uploadFile/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/mutation/uploadFile/request.json rename to connector/testdata/petstore3/snapshots/mutation/uploadFile/request.json diff --git a/rest/testdata/petstore3/snapshots/query/findPetsByTags/expected.json b/connector/testdata/petstore3/snapshots/query/findPetsByTags/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/findPetsByTags/expected.json rename to connector/testdata/petstore3/snapshots/query/findPetsByTags/expected.json diff --git a/rest/testdata/petstore3/snapshots/query/findPetsByTags/request.json b/connector/testdata/petstore3/snapshots/query/findPetsByTags/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/findPetsByTags/request.json rename to connector/testdata/petstore3/snapshots/query/findPetsByTags/request.json diff --git a/rest/testdata/petstore3/snapshots/query/getOrderById/expected.json b/connector/testdata/petstore3/snapshots/query/getOrderById/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/getOrderById/expected.json rename to connector/testdata/petstore3/snapshots/query/getOrderById/expected.json diff --git a/rest/testdata/petstore3/snapshots/query/getOrderById/request.json b/connector/testdata/petstore3/snapshots/query/getOrderById/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/getOrderById/request.json rename to connector/testdata/petstore3/snapshots/query/getOrderById/request.json diff --git a/rest/testdata/petstore3/snapshots/query/getPetById/expected.json b/connector/testdata/petstore3/snapshots/query/getPetById/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/getPetById/expected.json rename to connector/testdata/petstore3/snapshots/query/getPetById/expected.json diff --git a/rest/testdata/petstore3/snapshots/query/getPetById/request.json b/connector/testdata/petstore3/snapshots/query/getPetById/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/getPetById/request.json rename to connector/testdata/petstore3/snapshots/query/getPetById/request.json diff --git a/rest/testdata/petstore3/snapshots/query/getUserByName/expected.json b/connector/testdata/petstore3/snapshots/query/getUserByName/expected.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/getUserByName/expected.json rename to connector/testdata/petstore3/snapshots/query/getUserByName/expected.json diff --git a/rest/testdata/petstore3/snapshots/query/getUserByName/request.json b/connector/testdata/petstore3/snapshots/query/getUserByName/request.json similarity index 100% rename from rest/testdata/petstore3/snapshots/query/getUserByName/request.json rename to connector/testdata/petstore3/snapshots/query/getUserByName/request.json diff --git a/rest/testdata/server-empty/config.yaml b/connector/testdata/server-empty/config.yaml similarity index 100% rename from rest/testdata/server-empty/config.yaml rename to connector/testdata/server-empty/config.yaml diff --git a/rest/testdata/server-empty/schema.yaml b/connector/testdata/server-empty/schema.yaml similarity index 96% rename from rest/testdata/server-empty/schema.yaml rename to connector/testdata/server-empty/schema.yaml index 1bd7843..3cba6be 100644 --- a/rest/testdata/server-empty/schema.yaml +++ b/connector/testdata/server-empty/schema.yaml @@ -7,7 +7,8 @@ settings: httpStatus: [429, 500, 501, 502] collections: [] functions: - - request: + findPets: + request: url: "/pet" method: get parameters: [] diff --git a/connector/testdata/stripe/config.yaml b/connector/testdata/stripe/config.yaml new file mode 100755 index 0000000..03e8c3e --- /dev/null +++ b/connector/testdata/stripe/config.yaml @@ -0,0 +1,5 @@ +files: + - file: https://raw.githubusercontent.com/stripe/openapi/v1296/openapi/spec3.json + spec: oas3 + trimPrefix: /v1 + envPrefix: STRIPE diff --git a/rest/types.go b/connector/types.go similarity index 66% rename from rest/types.go rename to connector/types.go index 2482193..1b2523f 100644 --- a/rest/types.go +++ b/connector/types.go @@ -4,34 +4,17 @@ import ( "errors" "net/http" - "github.com/hasura/ndc-rest/ndc-rest-schema/command" + "github.com/hasura/ndc-rest/connector/internal" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" - "github.com/hasura/ndc-rest/rest/internal" ) var ( - errBuildSchemaFailed = errors.New("failed to build NDC REST schema") errInvalidSchema = errors.New("failed to validate NDC REST schema") + errBuildSchemaFailed = errors.New("failed to build NDC REST schema") errHTTPMethodRequired = errors.New("the HTTP method is required") + errFilePathRequired = errors.New("file path is empty") ) -const ( - contentTypeJSON = "application/json" -) - -// ConfigItem extends the ConvertConfig with advanced options -type ConfigItem struct { - command.ConvertConfig `yaml:",inline"` - - // Distributed enables distributed schema - Distributed bool `json:"distributed" yaml:"distributed"` -} - -// Configuration contains required settings for the connector. -type Configuration struct { - Files []ConfigItem `json:"files" yaml:"files"` -} - // State is the global state which is shared for every connector request. type State struct { Schema *rest.NDCRestSchema diff --git a/go.mod b/go.mod index d1f4ddd..babbb64 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.23.0 toolchain go1.23.1 require ( + github.com/google/uuid v1.6.0 github.com/hasura/ndc-rest/ndc-rest-schema v0.2.5 - github.com/hasura/ndc-sdk-go v1.5.1 + github.com/hasura/ndc-sdk-go v1.5.2-0.20241020093415-b752942bd505 github.com/lmittmann/tint v1.0.5 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 @@ -27,7 +28,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect github.com/klauspost/compress v1.17.11 // indirect @@ -35,13 +35,13 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pb33f/libopenapi v0.18.3 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.20.4 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect @@ -55,8 +55,8 @@ require ( golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go.sum b/go.sum index 8907b22..20081ac 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hasura/ndc-sdk-go v1.5.1 h1:RYcT/PpKcfeuugG575tFv1wogEMI040LoC5Q/8ANY9o= -github.com/hasura/ndc-sdk-go v1.5.1/go.mod h1:oik0JrwuN5iZwZjZJzIRMw9uO2xDJbCXwhS1GgaRejk= +github.com/hasura/ndc-sdk-go v1.5.2-0.20241020093415-b752942bd505 h1:6ATW3s+x5aJ+hGzdlNr1f5nu9IV8SY/lakVuOCMvBjM= +github.com/hasura/ndc-sdk-go v1.5.2-0.20241020093415-b752942bd505/go.mod h1:oik0JrwuN5iZwZjZJzIRMw9uO2xDJbCXwhS1GgaRejk= 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= @@ -104,8 +104,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= @@ -127,8 +127,8 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew= github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= +go.opentelemetry.io/contrib/propagators/b3 v1.31.0 h1:PQPXYscmwbCp76QDvO4hMngF2j8Bx/OTV86laEl8uqo= +go.opentelemetry.io/contrib/propagators/b3 v1.31.0/go.mod h1:jbqfV8wDdqSDrAYxVpXQnpM0XFMq2FtDesblJ7blOwQ= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= @@ -202,10 +202,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-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/go.work b/go.work index 25b97fa..c45ceb7 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.23.1 +go 1.23.0 use ( . diff --git a/ndc-rest-schema/README.md b/ndc-rest-schema/README.md index d93e3cf..0f41f3e 100644 --- a/ndc-rest-schema/README.md +++ b/ndc-rest-schema/README.md @@ -20,7 +20,7 @@ This module includes libraries and tools to convert other API schemas to Native **Install** ```go -go install github.com/hasura/ndc-rest/ndc-rest-schema +go install github.com/hasura/ndc-rest/ndc-rest-schema@latest ``` ## Quick start @@ -83,12 +83,6 @@ The NDC REST configuration adds `request` information into `functions` and `proc headers: Foo: bar timeout: 30 # seconds, default 30s - parameters: - - name: petId - in: path - schema: - type: string - nullable: false security: - api_key: [] ``` @@ -153,19 +147,10 @@ settings: version: 1.0.18 collections: [] functions: - - request: + findPetsByStatus: + request: url: "/pet/findByStatus" method: get - parameters: - - name: status - in: query - schema: - type: String - nullable: true - enum: - - available - - pending - - sold security: - petstore_auth: - write:pets @@ -178,15 +163,25 @@ functions: underlying_type: name: String type: named + rest: + name: status + in: query + schema: + type: string + nullable: true + enum: + - available + - pending + - sold description: Finds Pets by status - name: findPetsByStatus result_type: element_type: name: Pet type: named type: array procedures: - - request: + addPet: + request: url: "/pet" method: post headers: @@ -201,8 +196,11 @@ procedures: type: name: Pet type: named + rest: + in: body + schema: + type: object description: Add a new pet to the store - name: addPet result_type: name: Pet type: named diff --git a/ndc-rest-schema/command/convert.go b/ndc-rest-schema/command/convert.go index c42fdeb..846d1ea 100644 --- a/ndc-rest-schema/command/convert.go +++ b/ndc-rest-schema/command/convert.go @@ -7,7 +7,9 @@ import ( "log/slog" "os" "path/filepath" + "time" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" "github.com/hasura/ndc-rest/ndc-rest-schema/openapi" "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-rest/ndc-rest-schema/utils" @@ -34,6 +36,7 @@ type ConvertCommandArguments struct { // ConvertToNDCSchema converts to NDC REST schema from file func CommandConvertToNDCSchema(args *ConvertCommandArguments, logger *slog.Logger) error { + start := time.Now() logger.Debug( "converting the document to NDC REST schema", slog.String("file", args.File), @@ -63,7 +66,7 @@ func CommandConvertToNDCSchema(args *ConvertCommandArguments, logger *slog.Logge return err } - var config ConvertConfig + var config configuration.ConvertConfig if args.Config != "" { rawConfig, err := utils.ReadFileFromPath(args.Config) if err != nil { @@ -96,7 +99,7 @@ func CommandConvertToNDCSchema(args *ConvertCommandArguments, logger *slog.Logge return err } - logger.Info("generated successfully") + logger.Info("generated successfully", slog.Duration("execution_time", time.Since(start))) return nil } @@ -125,36 +128,8 @@ func CommandConvertToNDCSchema(args *ConvertCommandArguments, logger *slog.Logge return nil } -// ConvertConfig represents the content of convert config file -type ConvertConfig struct { - // File path needs to be converted - File string `json:"file" jsonschema:"required" yaml:"file"` - // The API specification of the file, is one of oas3 (openapi3), oas2 (openapi2) - Spec schema.SchemaSpecType `json:"spec,omitempty" jsonschema:"default=oas3" yaml:"spec"` - // Alias names for HTTP method. Used for prefix renaming, e.g. getUsers, postUser - MethodAlias map[string]string `json:"methodAlias,omitempty" yaml:"methodAlias"` - // Add a prefix to the function and procedure names - Prefix string `json:"prefix,omitempty" yaml:"prefix"` - // Trim the prefix in URL, e.g. /v1 - TrimPrefix string `json:"trimPrefix,omitempty" yaml:"trimPrefix"` - // The environment variable prefix for security values, e.g. PET_STORE - EnvPrefix string `json:"envPrefix,omitempty" yaml:"envPrefix"` - // Return the pure NDC schema only - Pure bool `json:"pure,omitempty" yaml:"pure"` - // Require strict validation - Strict bool `json:"strict,omitempty" yaml:"strict"` - // Patch files to be applied into the input file before converting - PatchBefore []utils.PatchConfig `json:"patchBefore,omitempty" yaml:"patchBefore"` - // Patch files to be applied into the input file after converting - PatchAfter []utils.PatchConfig `json:"patchAfter,omitempty" yaml:"patchAfter"` - // Allowed content types. All content types are allowed by default - AllowedContentTypes []string `json:"allowedContentTypes,omitempty" yaml:"allowedContentTypes"` - // The location where the ndc schema file will be generated. Print to stdout if not set - Output string `json:"output,omitempty" yaml:"output"` -} - // ConvertToNDCSchema converts to NDC REST schema from config -func ConvertToNDCSchema(config *ConvertConfig, logger *slog.Logger) (*schema.NDCRestSchema, error) { +func ConvertToNDCSchema(config *configuration.ConvertConfig, logger *slog.Logger) (*schema.NDCRestSchema, error) { rawContent, err := utils.ReadFileFromPath(config.File) if err != nil { return nil, err @@ -199,7 +174,7 @@ func ConvertToNDCSchema(config *ConvertConfig, logger *slog.Logger) (*schema.NDC } // ResolveConvertConfigArguments resolves convert config arguments -func ResolveConvertConfigArguments(config *ConvertConfig, configDir string, args *ConvertCommandArguments) { +func ResolveConvertConfigArguments(config *configuration.ConvertConfig, configDir string, args *ConvertCommandArguments) { if args != nil { if args.Spec != "" { config.Spec = schema.SchemaSpecType(args.Spec) diff --git a/ndc-rest-schema/command/convert_test.go b/ndc-rest-schema/command/convert_test.go index 2732678..7677602 100644 --- a/ndc-rest-schema/command/convert_test.go +++ b/ndc-rest-schema/command/convert_test.go @@ -6,11 +6,10 @@ import ( "io" "log/slog" "os" - "reflect" - "strings" "testing" "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "gotest.tools/v3/assert" ) var nopLogger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) @@ -118,11 +117,11 @@ func TestConvertToNDCSchema(t *testing.T) { err := CommandConvertToNDCSchema(args, nopLogger) if tc.errorMsg != "" { - assertError(t, err, tc.errorMsg) + assert.ErrorContains(t, err, tc.errorMsg) return } - assertNoError(t, err) + assert.NilError(t, err) if tc.noOutput { return } @@ -150,46 +149,11 @@ func TestConvertToNDCSchema(t *testing.T) { t.Errorf("cannot decode the output file json at %s", tc.expected) t.FailNow() } - assertDeepEqual(t, expectedSchema.Settings, output.Settings) - assertDeepEqual(t, expectedSchema.Functions, output.Functions) - assertDeepEqual(t, expectedSchema.Procedures, output.Procedures) - assertDeepEqual(t, expectedSchema.ScalarTypes, output.ScalarTypes) - assertDeepEqual(t, expectedSchema.ObjectTypes, output.ObjectTypes) + assert.DeepEqual(t, expectedSchema.Settings, output.Settings) + assert.DeepEqual(t, expectedSchema.Functions, output.Functions) + assert.DeepEqual(t, expectedSchema.Procedures, output.Procedures) + assert.DeepEqual(t, expectedSchema.ScalarTypes, output.ScalarTypes) + assert.DeepEqual(t, expectedSchema.ObjectTypes, output.ObjectTypes) }) } } - -func assertNoError(t *testing.T, err error) { - if err != nil { - t.Errorf("expected no error, got: %s", err) - panic(err) - } -} - -func assertError(t *testing.T, err error, message string) { - if err == nil { - t.Error("expected error, got nil") - t.FailNow() - } else if !strings.Contains(err.Error(), message) { - t.Errorf("expected error with content: %s, got: %s", err.Error(), message) - t.FailNow() - } -} - -func assertDeepEqual(t *testing.T, expected any, reality any, msgs ...string) { - if reflect.DeepEqual(expected, reality) { - return - } - - expectedJson, _ := json.Marshal(expected) - realityJson, _ := json.Marshal(reality) - - var expected1, reality1 any - assertNoError(t, json.Unmarshal(expectedJson, &expected1)) - assertNoError(t, json.Unmarshal(realityJson, &reality1)) - - if !reflect.DeepEqual(expected1, reality1) { - t.Errorf("%s: not equal.\nexpected: %s\ngot : %s", strings.Join(msgs, " "), string(expectedJson), string(realityJson)) - t.FailNow() - } -} diff --git a/ndc-rest-schema/command/json2yaml_test.go b/ndc-rest-schema/command/json2yaml_test.go index 2e13b3d..199784a 100644 --- a/ndc-rest-schema/command/json2yaml_test.go +++ b/ndc-rest-schema/command/json2yaml_test.go @@ -6,6 +6,7 @@ import ( "testing" "gopkg.in/yaml.v3" + "gotest.tools/v3/assert" ) func TestJson2Yaml(t *testing.T) { @@ -46,11 +47,11 @@ func TestJson2Yaml(t *testing.T) { }, nopLogger) if tc.errorMsg != "" { - assertError(t, err, tc.errorMsg) + assert.ErrorContains(t, err, tc.errorMsg) return } - assertNoError(t, err) + assert.NilError(t, err) if tc.noOutput { return diff --git a/ndc-rest-schema/configuration/types.go b/ndc-rest-schema/configuration/types.go new file mode 100644 index 0000000..5d2d26e --- /dev/null +++ b/ndc-rest-schema/configuration/types.go @@ -0,0 +1,48 @@ +package configuration + +import ( + "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "github.com/hasura/ndc-rest/ndc-rest-schema/utils" +) + +// ConfigItem extends the ConvertConfig with advanced options +type ConfigItem struct { + ConvertConfig `yaml:",inline"` + + // Distributed enables distributed schema + Distributed bool `json:"distributed" yaml:"distributed"` +} + +// Configuration contains required settings for the connector. +type Configuration struct { + Output string `json:"output,omitempty" yaml:"output,omitempty"` + Files []ConfigItem `json:"files" yaml:"files"` +} + +// ConvertConfig represents the content of convert config file +type ConvertConfig struct { + // File path needs to be converted + File string `json:"file" jsonschema:"required" yaml:"file"` + // The API specification of the file, is one of oas3 (openapi3), oas2 (openapi2) + Spec schema.SchemaSpecType `json:"spec,omitempty" jsonschema:"default=oas3" yaml:"spec"` + // Alias names for HTTP method. Used for prefix renaming, e.g. getUsers, postUser + MethodAlias map[string]string `json:"methodAlias,omitempty" yaml:"methodAlias"` + // Add a prefix to the function and procedure names + Prefix string `json:"prefix,omitempty" yaml:"prefix"` + // Trim the prefix in URL, e.g. /v1 + TrimPrefix string `json:"trimPrefix,omitempty" yaml:"trimPrefix"` + // The environment variable prefix for security values, e.g. PET_STORE + EnvPrefix string `json:"envPrefix,omitempty" yaml:"envPrefix"` + // Return the pure NDC schema only + Pure bool `json:"pure,omitempty" yaml:"pure"` + // Require strict validation + Strict bool `json:"strict,omitempty" yaml:"strict"` + // Patch files to be applied into the input file before converting + PatchBefore []utils.PatchConfig `json:"patchBefore,omitempty" yaml:"patchBefore"` + // Patch files to be applied into the input file after converting + PatchAfter []utils.PatchConfig `json:"patchAfter,omitempty" yaml:"patchAfter"` + // Allowed content types. All content types are allowed by default + AllowedContentTypes []string `json:"allowedContentTypes,omitempty" yaml:"allowedContentTypes"` + // The location where the ndc schema file will be generated. Print to stdout if not set + Output string `json:"output,omitempty" yaml:"output"` +} diff --git a/ndc-rest-schema/go.mod b/ndc-rest-schema/go.mod index 79d4230..bd9833f 100644 --- a/ndc-rest-schema/go.mod +++ b/ndc-rest-schema/go.mod @@ -7,21 +7,26 @@ toolchain go1.23.1 require ( github.com/alecthomas/kong v1.2.1 github.com/evanphx/json-patch v0.5.2 - github.com/hasura/ndc-sdk-go v1.5.1 + github.com/hasura/ndc-sdk-go v1.5.2-0.20241020093415-b752942bd505 github.com/invopop/jsonschema v0.12.0 github.com/lmittmann/tint v1.0.5 github.com/pb33f/libopenapi v0.18.3 github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd gopkg.in/yaml.v3 v3.0.1 + gotest.tools/v3 v3.5.1 ) require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect golang.org/x/net v0.30.0 // indirect ) diff --git a/ndc-rest-schema/go.sum b/ndc-rest-schema/go.sum index f64965f..79ed1c2 100644 --- a/ndc-rest-schema/go.sum +++ b/ndc-rest-schema/go.sum @@ -23,6 +23,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -39,8 +41,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/hasura/ndc-sdk-go v1.5.1 h1:RYcT/PpKcfeuugG575tFv1wogEMI040LoC5Q/8ANY9o= -github.com/hasura/ndc-sdk-go v1.5.1/go.mod h1:oik0JrwuN5iZwZjZJzIRMw9uO2xDJbCXwhS1GgaRejk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hasura/ndc-sdk-go v1.5.2-0.20241020093415-b752942bd505 h1:6ATW3s+x5aJ+hGzdlNr1f5nu9IV8SY/lakVuOCMvBjM= +github.com/hasura/ndc-sdk-go v1.5.2-0.20241020093415-b752942bd505/go.mod h1:oik0JrwuN5iZwZjZJzIRMw9uO2xDJbCXwhS1GgaRejk= 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= @@ -92,6 +96,8 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew= github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= 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= diff --git a/ndc-rest-schema/jsonschema/configuration.schema.json b/ndc-rest-schema/jsonschema/configuration.schema.json new file mode 100644 index 0000000..7686885 --- /dev/null +++ b/ndc-rest-schema/jsonschema/configuration.schema.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/hasura/ndc-rest/ndc-rest-schema/configuration/configuration", + "$ref": "#/$defs/Configuration", + "$defs": { + "ConfigItem": { + "properties": { + "file": { + "type": "string", + "description": "File path needs to be converted" + }, + "spec": { + "$ref": "#/$defs/SchemaSpecType", + "description": "The API specification of the file, is one of oas3 (openapi3), oas2 (openapi2)" + }, + "methodAlias": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Alias names for HTTP method. Used for prefix renaming, e.g. getUsers, postUser" + }, + "prefix": { + "type": "string", + "description": "Add a prefix to the function and procedure names" + }, + "trimPrefix": { + "type": "string", + "description": "Trim the prefix in URL, e.g. /v1" + }, + "envPrefix": { + "type": "string", + "description": "The environment variable prefix for security values, e.g. PET_STORE" + }, + "pure": { + "type": "boolean", + "description": "Return the pure NDC schema only" + }, + "strict": { + "type": "boolean", + "description": "Require strict validation" + }, + "patchBefore": { + "items": { + "$ref": "#/$defs/PatchConfig" + }, + "type": "array", + "description": "Patch files to be applied into the input file before converting" + }, + "patchAfter": { + "items": { + "$ref": "#/$defs/PatchConfig" + }, + "type": "array", + "description": "Patch files to be applied into the input file after converting" + }, + "allowedContentTypes": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Allowed content types. All content types are allowed by default" + }, + "output": { + "type": "string", + "description": "The location where the ndc schema file will be generated. Print to stdout if not set" + }, + "distributed": { + "type": "boolean", + "description": "Distributed enables distributed schema" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "file", + "distributed" + ], + "description": "ConfigItem extends the ConvertConfig with advanced options" + }, + "Configuration": { + "properties": { + "output": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ConfigItem" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "files" + ], + "description": "Configuration contains required settings for the connector." + }, + "PatchConfig": { + "properties": { + "path": { + "type": "string" + }, + "strategy": { + "type": "string", + "enum": [ + "merge", + "json6902" + ] + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "path", + "strategy" + ] + }, + "SchemaSpecType": { + "type": "string", + "enum": [ + "oas3", + "oas2", + "openapi3", + "openapi2", + "ndc" + ] + } + } +} \ No newline at end of file diff --git a/ndc-rest-schema/jsonschema/convert-config.jsonschema b/ndc-rest-schema/jsonschema/convert-config.schema.json similarity index 96% rename from ndc-rest-schema/jsonschema/convert-config.jsonschema rename to ndc-rest-schema/jsonschema/convert-config.schema.json index 9f93c94..e751b73 100644 --- a/ndc-rest-schema/jsonschema/convert-config.jsonschema +++ b/ndc-rest-schema/jsonschema/convert-config.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/hasura/ndc-rest/ndc-rest-schema/command/convert-config", + "$id": "https://github.com/hasura/ndc-rest/ndc-rest-schema/configuration/convert-config", "$ref": "#/$defs/ConvertConfig", "$defs": { "ConvertConfig": { diff --git a/ndc-rest-schema/jsonschema/generator.go b/ndc-rest-schema/jsonschema/generator.go index 2cf8190..888dbb2 100644 --- a/ndc-rest-schema/jsonschema/generator.go +++ b/ndc-rest-schema/jsonschema/generator.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/hasura/ndc-rest/ndc-rest-schema/command" + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/invopop/jsonschema" ) @@ -17,14 +17,17 @@ func main() { if err := jsonSchemaNdcRESTSchema(); err != nil { panic(fmt.Errorf("failed to write jsonschema for NDCRestSchema: %w", err)) } + if err := jsonSchemaConnectorConfiguration(); err != nil { + panic(fmt.Errorf("failed to write jsonschema for Configuration: %w", err)) + } } func jsonSchemaConvertConfig() error { r := new(jsonschema.Reflector) - if err := r.AddGoComments("github.com/hasura/ndc-rest/ndc-rest-schema/command", "../command"); err != nil { + if err := r.AddGoComments("github.com/hasura/ndc-rest/ndc-rest-schema/configuration", "../configuration"); err != nil { return err } - reflectSchema := r.Reflect(&command.ConvertConfig{}) + reflectSchema := r.Reflect(&configuration.ConvertConfig{}) schemaBytes, err := json.MarshalIndent(reflectSchema, "", " ") if err != nil { @@ -34,6 +37,21 @@ func jsonSchemaConvertConfig() error { return os.WriteFile("convert-config.schema.json", schemaBytes, 0644) } +func jsonSchemaConnectorConfiguration() error { + r := new(jsonschema.Reflector) + if err := r.AddGoComments("github.com/hasura/ndc-rest/ndc-rest-schema/configuration", "../configuration"); err != nil { + return err + } + reflectSchema := r.Reflect(&configuration.Configuration{}) + + schemaBytes, err := json.MarshalIndent(reflectSchema, "", " ") + if err != nil { + return err + } + + return os.WriteFile("configuration.schema.json", schemaBytes, 0644) +} + func jsonSchemaNdcRESTSchema() error { r := new(jsonschema.Reflector) if err := r.AddGoComments("github.com/hasura/ndc-rest/ndc-rest-schema/schema", "../schema"); err != nil { diff --git a/ndc-rest-schema/jsonschema/ndc-rest-schema.jsonschema b/ndc-rest-schema/jsonschema/ndc-rest-schema.schema.json similarity index 83% rename from ndc-rest-schema/jsonschema/ndc-rest-schema.jsonschema rename to ndc-rest-schema/jsonschema/ndc-rest-schema.schema.json index 61b0620..cbfa0e5 100644 --- a/ndc-rest-schema/jsonschema/ndc-rest-schema.jsonschema +++ b/ndc-rest-schema/jsonschema/ndc-rest-schema.schema.json @@ -22,13 +22,18 @@ }, "type": { "$ref": "#/$defs/Type" + }, + "rest": { + "$ref": "#/$defs/RequestParameter", + "description": "The request parameter information of the REST request" } }, "additionalProperties": false, "type": "object", "required": [ "type" - ] + ], + "description": "ArgumentInfo the information of REST request argument" }, "AuthSecurities": { "items": { @@ -47,55 +52,6 @@ "type": "object", "description": "AuthSecurity wraps the raw security requirement with helpers" }, - "CollectionInfo": { - "properties": { - "arguments": { - "$ref": "#/$defs/CollectionInfoArguments" - }, - "description": { - "type": "string" - }, - "foreign_keys": { - "$ref": "#/$defs/CollectionInfoForeignKeys" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "uniqueness_constraints": { - "$ref": "#/$defs/CollectionInfoUniquenessConstraints" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "arguments", - "foreign_keys", - "name", - "type", - "uniqueness_constraints" - ] - }, - "CollectionInfoArguments": { - "additionalProperties": { - "$ref": "#/$defs/ArgumentInfo" - }, - "type": "object" - }, - "CollectionInfoForeignKeys": { - "additionalProperties": { - "$ref": "#/$defs/ForeignKeyConstraint" - }, - "type": "object" - }, - "CollectionInfoUniquenessConstraints": { - "additionalProperties": { - "$ref": "#/$defs/UniquenessConstraint" - }, - "type": "object" - }, "ComparisonOperatorDefinition": { "type": "object" }, @@ -181,34 +137,6 @@ } ] }, - "ForeignKeyConstraint": { - "properties": { - "column_mapping": { - "$ref": "#/$defs/ForeignKeyConstraintColumnMapping" - }, - "foreign_collection": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "column_mapping", - "foreign_collection" - ] - }, - "ForeignKeyConstraintColumnMapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "FunctionInfoArguments": { - "additionalProperties": { - "$ref": "#/$defs/ArgumentInfo" - }, - "type": "object" - }, "NDCRestSchema": { "properties": { "$schema": { @@ -217,29 +145,25 @@ "settings": { "$ref": "#/$defs/NDCRestSettings" }, - "collections": { - "items": { - "$ref": "#/$defs/CollectionInfo" - }, - "type": "array", - "description": "Collections which are available for queries" - }, "functions": { - "items": { - "$ref": "#/$defs/RESTFunctionInfo" + "additionalProperties": { + "$ref": "#/$defs/OperationInfo" }, - "type": "array", + "type": "object", "description": "Functions (i.e. collections which return a single column and row)" }, "object_types": { - "$ref": "#/$defs/SchemaResponseObjectTypes", + "additionalProperties": { + "$ref": "#/$defs/ObjectType" + }, + "type": "object", "description": "A list of object types which can be used as the types of arguments, or return\ntypes of procedures. Names should not overlap with scalar type names." }, "procedures": { - "items": { - "$ref": "#/$defs/RESTProcedureInfo" + "additionalProperties": { + "$ref": "#/$defs/OperationInfo" }, - "type": "array", + "type": "object", "description": "Procedures which are available for execution as part of mutations" }, "scalar_types": { @@ -250,7 +174,6 @@ "additionalProperties": false, "type": "object", "required": [ - "collections", "functions", "object_types", "procedures", @@ -309,13 +232,18 @@ }, "type": { "$ref": "#/$defs/Type" + }, + "rest": { + "$ref": "#/$defs/TypeSchema", + "description": "The field schema information of the REST request" } }, "additionalProperties": false, "type": "object", "required": [ "type" - ] + ], + "description": "ObjectField defined on this object type" }, "ObjectFieldArguments": { "additionalProperties": { @@ -326,23 +254,58 @@ "ObjectType": { "properties": { "description": { - "type": "string" + "type": "string", + "description": "Description of this type" }, "fields": { - "$ref": "#/$defs/ObjectTypeFields" + "additionalProperties": { + "$ref": "#/$defs/ObjectField" + }, + "type": "object", + "description": "Fields defined on this object type" } }, "additionalProperties": false, "type": "object", "required": [ "fields" - ] + ], + "description": "ObjectType represents the object type of rest schema" }, - "ObjectTypeFields": { - "additionalProperties": { - "$ref": "#/$defs/ObjectField" + "OperationInfo": { + "properties": { + "request": { + "$ref": "#/$defs/Request" + }, + "arguments": { + "additionalProperties": { + "$ref": "#/$defs/ArgumentInfo" + }, + "type": "object", + "description": "Any arguments that this collection requires" + }, + "description": { + "type": "string", + "description": "Column description" + }, + "name": { + "type": "string", + "description": "The name of the procedure" + }, + "result_type": { + "$ref": "#/$defs/Type", + "description": "The name of the result type" + } }, - "type": "object" + "additionalProperties": false, + "type": "object", + "required": [ + "request", + "arguments", + "name", + "result_type" + ], + "description": "OperationInfo extends connector command operation with OpenAPI REST information" }, "ParameterEncodingStyle": { "type": "string", @@ -367,68 +330,6 @@ "formData" ] }, - "ProcedureInfoArguments": { - "additionalProperties": { - "$ref": "#/$defs/ArgumentInfo" - }, - "type": "object" - }, - "RESTFunctionInfo": { - "properties": { - "request": { - "$ref": "#/$defs/Request" - }, - "arguments": { - "$ref": "#/$defs/FunctionInfoArguments" - }, - "description": { - "type": "string" - }, - "name": { - "type": "string" - }, - "result_type": { - "$ref": "#/$defs/Type" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "request", - "arguments", - "name", - "result_type" - ], - "description": "RESTFunctionInfo extends NDC query function with OpenAPI REST information" - }, - "RESTProcedureInfo": { - "properties": { - "request": { - "$ref": "#/$defs/Request" - }, - "arguments": { - "$ref": "#/$defs/ProcedureInfoArguments" - }, - "description": { - "type": "string" - }, - "name": { - "type": "string" - }, - "result_type": { - "$ref": "#/$defs/Type" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "request", - "arguments", - "name", - "result_type" - ], - "description": "RESTProcedureInfo extends NDC mutation procedure with OpenAPI REST information" - }, "Request": { "properties": { "url": { @@ -453,12 +354,6 @@ }, "type": "object" }, - "parameters": { - "items": { - "$ref": "#/$defs/RequestParameter" - }, - "type": "array" - }, "security": { "$ref": "#/$defs/AuthSecurities" }, @@ -494,9 +389,6 @@ "contentType": { "type": "string" }, - "schema": { - "$ref": "#/$defs/TypeSchema" - }, "encoding": { "additionalProperties": { "$ref": "#/$defs/EncodingObject" @@ -634,12 +526,6 @@ }, "type": "object" }, - "SchemaResponseObjectTypes": { - "additionalProperties": { - "$ref": "#/$defs/ObjectType" - }, - "type": "object" - }, "SchemaResponseScalarTypes": { "additionalProperties": { "$ref": "#/$defs/ScalarType" @@ -849,7 +735,10 @@ "TypeSchema": { "properties": { "type": { - "type": "string" + "items": { + "type": "string" + }, + "type": "array" }, "format": { "type": "string" @@ -857,9 +746,6 @@ "pattern": { "type": "string" }, - "nullable": { - "type": "boolean" - }, "maximum": { "type": "number" }, @@ -872,20 +758,8 @@ "minLength": { "type": "integer" }, - "enum": { - "items": { - "type": "string" - }, - "type": "array" - }, "items": { "$ref": "#/$defs/TypeSchema" - }, - "properties": { - "additionalProperties": { - "$ref": "#/$defs/TypeSchema" - }, - "type": "object" } }, "additionalProperties": false, @@ -894,21 +768,6 @@ "type" ], "description": "TypeSchema represents a serializable object of OpenAPI schema that is used for validation" - }, - "UniquenessConstraint": { - "properties": { - "unique_columns": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "unique_columns" - ] } } } \ No newline at end of file diff --git a/ndc-rest-schema/openapi/internal/oas2.go b/ndc-rest-schema/openapi/internal/oas2.go index 49b2da4..7875769 100644 --- a/ndc-rest-schema/openapi/internal/oas2.go +++ b/ndc-rest-schema/openapi/internal/oas2.go @@ -165,44 +165,44 @@ func (oc *OAS2Builder) pathToNDCOperations(pathItem orderedmap.Pair[string, *v2. pathKey := pathItem.Key() pathValue := pathItem.Value() - funcGet, err := newOAS2OperationBuilder(oc).BuildFunction(pathKey, pathValue.Get) + funcGet, funcName, err := newOAS2OperationBuilder(oc).BuildFunction(pathKey, pathValue.Get) if err != nil { return err } if funcGet != nil { - oc.schema.Functions = append(oc.schema.Functions, funcGet) + oc.schema.Functions[funcName] = *funcGet } - procPost, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "post", pathValue.Post) + procPost, procPostName, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "post", pathValue.Post) if err != nil { return err } if procPost != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procPost) + oc.schema.Procedures[procPostName] = *procPost } - procPut, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "put", pathValue.Put) + procPut, procPutName, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "put", pathValue.Put) if err != nil { return err } if procPut != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procPut) + oc.schema.Procedures[procPutName] = *procPut } - procPatch, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "patch", pathValue.Patch) + procPatch, procPatchName, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "patch", pathValue.Patch) if err != nil { return err } if procPatch != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procPatch) + oc.schema.Procedures[procPatchName] = *procPatch } - procDelete, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "delete", pathValue.Delete) + procDelete, procDeleteName, err := newOAS2OperationBuilder(oc).BuildProcedure(pathKey, "delete", pathValue.Delete) if err != nil { return err } if procDelete != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procDelete) + oc.schema.Procedures[procDeleteName] = *procDelete } return nil } @@ -226,7 +226,7 @@ func (oc *OAS2Builder) getSchemaTypeFromProxy(schemaProxy *base.SchemaProxy, nul if refName != "" && len(innerSchema.Type) > 0 && innerSchema.Type[0] == "object" { refName = utils.ToPascalCase(refName) ndcType = schema.NewNamedType(refName) - typeSchema = &rest.TypeSchema{Type: refName} + typeSchema = createSchemaFromOpenAPISchema(innerSchema) } else { if innerSchema.Title != "" && !strings.Contains(innerSchema.Title, " ") { fieldPaths = []string{utils.ToPascalCase(innerSchema.Title)} @@ -237,7 +237,6 @@ func (oc *OAS2Builder) getSchemaTypeFromProxy(schemaProxy *base.SchemaProxy, nul } } if nullable { - typeSchema.Nullable = true if !isNullableType(ndcType) { ndcType = schema.NewNullableType(ndcType) } @@ -253,7 +252,7 @@ func (oc *OAS2Builder) getSchemaTypeFromParameter(param *v2.Parameter, apiPath s return nil, errParameterSchemaEmpty(fieldPaths) } result = oc.buildScalarJSON() - } else if isPrimitiveScalar(param.Type) { + } else if isPrimitiveScalar([]string{param.Type}) { scalarName := getScalarFromType(oc.schema, []string{param.Type}, param.Format, param.Enum, oc.trimPathPrefix(apiPath), fieldPaths) result = schema.NewNamedType(scalarName) } else { @@ -293,7 +292,7 @@ func (oc *OAS2Builder) getSchemaType(typeSchema *base.Schema, apiPath string, fi if _, ok := oc.schema.ScalarTypes[scalarName]; !ok { oc.schema.ScalarTypes[scalarName] = *defaultScalarTypes[rest.ScalarJSON] } - typeResult = createSchemaFromOpenAPISchema(typeSchema, scalarName) + typeResult = createSchemaFromOpenAPISchema(typeSchema) return schema.NewNamedType(scalarName), typeResult, nil } @@ -303,16 +302,15 @@ func (oc *OAS2Builder) getSchemaType(typeSchema *base.Schema, apiPath string, fi return nil, nil, errParameterSchemaEmpty(fieldPaths) } result = oc.buildScalarJSON() - typeResult = createSchemaFromOpenAPISchema(typeSchema, string(rest.ScalarJSON)) + typeResult = createSchemaFromOpenAPISchema(typeSchema) } else { typeName := typeSchema.Type[0] - if isPrimitiveScalar(typeName) { + if isPrimitiveScalar(typeSchema.Type) { scalarName := getScalarFromType(oc.schema, typeSchema.Type, typeSchema.Format, typeSchema.Enum, oc.trimPathPrefix(apiPath), fieldPaths) result = schema.NewNamedType(scalarName) - typeResult = createSchemaFromOpenAPISchema(typeSchema, scalarName) + typeResult = createSchemaFromOpenAPISchema(typeSchema) } else { - typeResult = createSchemaFromOpenAPISchema(typeSchema, "") - typeResult.Type = typeName + typeResult = createSchemaFromOpenAPISchema(typeSchema) switch typeName { case "object": refName := utils.StringSliceToPascalCase(fieldPaths) @@ -321,14 +319,13 @@ func (oc *OAS2Builder) getSchemaType(typeSchema *base.Schema, apiPath string, fi // treat no-property objects as a JSON scalar oc.schema.ScalarTypes[refName] = *defaultScalarTypes[rest.ScalarJSON] } else { - object := schema.ObjectType{ - Fields: make(schema.ObjectTypeFields), + object := rest.ObjectType{ + Fields: make(map[string]rest.ObjectField), } if typeSchema.Description != "" { object.Description = &typeSchema.Description } - typeResult.Properties = make(map[string]rest.TypeSchema) for prop := typeSchema.Properties.First(); prop != nil; prop = prop.Next() { propName := prop.Key() nullable := !slices.Contains(typeSchema.Required, propName) @@ -336,14 +333,15 @@ func (oc *OAS2Builder) getSchemaType(typeSchema *base.Schema, apiPath string, fi if err != nil { return nil, nil, err } - objField := schema.ObjectField{ - Type: propType.Encode(), + objField := rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: propType.Encode(), + }, + Rest: propApiSchema, } if propApiSchema.Description != "" { objField.Description = &propApiSchema.Description } - propApiSchema.Nullable = nullable - typeResult.Properties[propName] = *propApiSchema object.Fields[propName] = objField oc.typeUsageCounter.Add(getNamedType(propType, true, ""), 1) diff --git a/ndc-rest-schema/openapi/internal/oas2_operation.go b/ndc-rest-schema/openapi/internal/oas2_operation.go index ea0323d..a42bbd9 100644 --- a/ndc-rest-schema/openapi/internal/oas2_operation.go +++ b/ndc-rest-schema/openapi/internal/oas2_operation.go @@ -1,11 +1,11 @@ package internal import ( - "errors" "fmt" "log/slog" "slices" "strconv" + "strings" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-rest/ndc-rest-schema/utils" @@ -14,22 +14,21 @@ import ( ) type oas2OperationBuilder struct { - builder *OAS2Builder - Arguments map[string]schema.ArgumentInfo - RequestParams []rest.RequestParameter + builder *OAS2Builder + Arguments map[string]rest.ArgumentInfo } func newOAS2OperationBuilder(builder *OAS2Builder) *oas2OperationBuilder { return &oas2OperationBuilder{ builder: builder, - Arguments: make(map[string]schema.ArgumentInfo), + Arguments: make(map[string]rest.ArgumentInfo), } } // BuildFunction build a REST NDC function information from OpenAPI v2 operation -func (oc *oas2OperationBuilder) BuildFunction(pathKey string, operation *v2.Operation) (*rest.RESTFunctionInfo, error) { +func (oc *oas2OperationBuilder) BuildFunction(pathKey string, operation *v2.Operation) (*rest.OperationInfo, string, error) { if operation == nil { - return nil, nil + return nil, "", nil } funcName := operation.OperationId if funcName == "" { @@ -52,50 +51,44 @@ func (oc *oas2OperationBuilder) BuildFunction(pathKey string, operation *v2.Oper slog.Any("produces", operation.Produces), slog.Any("consumes", operation.Consumes), ) - return nil, nil + return nil, "", nil } resultType, err := oc.convertResponse(operation.Responses, pathKey, []string{funcName, "Result"}) if err != nil { - return nil, fmt.Errorf("%s: %w", pathKey, err) + return nil, "", fmt.Errorf("%s: %w", pathKey, err) } if resultType == nil { - return nil, nil + return nil, "", nil } reqBody, err := oc.convertParameters(operation, pathKey, []string{funcName}) if err != nil { - return nil, fmt.Errorf("%s: %w", funcName, err) + return nil, "", fmt.Errorf("%s: %w", funcName, err) } - function := rest.RESTFunctionInfo{ + description := oc.getOperationDescription(pathKey, "get", operation) + function := rest.OperationInfo{ Request: &rest.Request{ URL: pathKey, Method: "get", - Parameters: oc.RequestParams, RequestBody: reqBody, Response: rest.Response{ ContentType: responseContentType, }, Security: convertSecurities(operation.Security), }, - FunctionInfo: schema.FunctionInfo{ - Name: funcName, - Arguments: oc.Arguments, - ResultType: resultType.Encode(), - }, - } - - if operation.Summary != "" { - function.Description = &operation.Summary + Description: &description, + Arguments: oc.Arguments, + ResultType: resultType.Encode(), } - return &function, nil + return &function, funcName, nil } // BuildProcedure build a REST NDC function information from OpenAPI v2 operation -func (oc *oas2OperationBuilder) BuildProcedure(pathKey string, method string, operation *v2.Operation) (*rest.RESTProcedureInfo, error) { +func (oc *oas2OperationBuilder) BuildProcedure(pathKey string, method string, operation *v2.Operation) (*rest.OperationInfo, string, error) { if operation == nil { - return nil, nil + return nil, "", nil } procName := operation.OperationId @@ -121,46 +114,40 @@ func (oc *oas2OperationBuilder) BuildProcedure(pathKey string, method string, op slog.Any("produces", operation.Produces), slog.Any("consumes", operation.Consumes), ) - return nil, nil + return nil, "", nil } resultType, err := oc.convertResponse(operation.Responses, pathKey, []string{procName, "Result"}) if err != nil { - return nil, fmt.Errorf("%s: %w", pathKey, err) + return nil, "", fmt.Errorf("%s: %w", pathKey, err) } if resultType == nil { - return nil, nil + return nil, "", nil } reqBody, err := oc.convertParameters(operation, pathKey, []string{procName}) if err != nil { - return nil, fmt.Errorf("%s: %w", pathKey, err) + return nil, "", fmt.Errorf("%s: %w", pathKey, err) } - procedure := rest.RESTProcedureInfo{ + description := oc.getOperationDescription(pathKey, method, operation) + procedure := rest.OperationInfo{ Request: &rest.Request{ URL: pathKey, Method: method, - Parameters: oc.RequestParams, RequestBody: reqBody, Security: convertSecurities(operation.Security), Response: rest.Response{ ContentType: responseContentType, }, }, - ProcedureInfo: schema.ProcedureInfo{ - Name: procName, - Arguments: oc.Arguments, - ResultType: resultType.Encode(), - }, - } - - if operation.Summary != "" { - procedure.Description = &operation.Summary + Description: &description, + Arguments: oc.Arguments, + ResultType: resultType.Encode(), } - return &procedure, nil + return &procedure, procName, nil } func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPath string, fieldPaths []string) (*rest.RequestBody, error) { @@ -175,11 +162,10 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa var requestBody *rest.RequestBody formData := rest.TypeSchema{ - Type: "object", - Properties: make(map[string]rest.TypeSchema), + Type: []string{"object"}, } - formDataObject := schema.ObjectType{ - Fields: schema.ObjectTypeFields{}, + formDataObject := rest.ObjectType{ + Fields: map[string]rest.ObjectField{}, } for _, param := range operation.Parameters { if param == nil { @@ -187,7 +173,7 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa } paramName := param.Name if paramName == "" { - return nil, errors.New("parameter name is empty") + return nil, errParameterNameRequired } var typeEncoder schema.TypeEncoder @@ -204,11 +190,9 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa if err != nil { return nil, err } - nullable := !paramRequired typeSchema = &rest.TypeSchema{ - Type: getNamedType(typeEncoder, false, param.Type), - Pattern: param.Pattern, - Nullable: nullable, + Type: []string{param.Type}, + Pattern: param.Pattern, } if param.Maximum != nil { maximum := float64(*param.Maximum) @@ -240,8 +224,10 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa oc.builder.typeUsageCounter.Add(getNamedType(typeEncoder, true, ""), 1) schemaType := typeEncoder.Encode() - argument := schema.ArgumentInfo{ - Type: schemaType, + argument := rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Type: schemaType, + }, } if param.Description != "" { argument.Description = ¶m.Description @@ -249,42 +235,52 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa switch paramLocation { case rest.InBody: - oc.Arguments["body"] = argument + argument.Rest = &rest.RequestParameter{ + In: rest.InBody, + Schema: typeSchema, + } + oc.Arguments[rest.BodyKey] = argument requestBody = &rest.RequestBody{ ContentType: contentType, - Schema: typeSchema, } case rest.InFormData: if typeSchema != nil { - formDataObject.Fields[paramName] = schema.ObjectField{ - Type: argument.Type, - Description: argument.Description, + formDataObject.Fields[paramName] = rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: argument.Type, + Description: argument.Description, + }, + Rest: typeSchema, } - formData.Properties[paramName] = *typeSchema } default: - oc.Arguments[paramName] = argument - oc.RequestParams = append(oc.RequestParams, rest.RequestParameter{ + argument.Rest = &rest.RequestParameter{ Name: paramName, In: paramLocation, Schema: typeSchema, - }) + } + oc.Arguments[paramName] = argument } } - if len(formData.Properties) > 0 { + if len(formDataObject.Fields) > 0 { bodyName := utils.StringSliceToPascalCase(fieldPaths) + "Body" oc.builder.schema.ObjectTypes[bodyName] = formDataObject oc.builder.typeUsageCounter.Add(bodyName, 1) desc := "Form data of " + apiPath - oc.Arguments["body"] = schema.ArgumentInfo{ - Type: schema.NewNamedType(bodyName).Encode(), - Description: &desc, + oc.Arguments["body"] = rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Type: schema.NewNamedType(bodyName).Encode(), + Description: &desc, + }, + Rest: &rest.RequestParameter{ + In: rest.InFormData, + Schema: &formData, + }, } requestBody = &rest.RequestBody{ ContentType: contentType, - Schema: &formData, } } @@ -350,3 +346,13 @@ func (oc *oas2OperationBuilder) getResponseContentTypeV2(contentTypes []string) } return "" } + +func (oc *oas2OperationBuilder) getOperationDescription(pathKey string, method string, operation *v2.Operation) string { + if operation.Summary != "" { + return utils.StripHTMLTags(operation.Summary) + } + if operation.Description != "" { + return utils.StripHTMLTags(operation.Description) + } + return strings.ToUpper(method) + " " + pathKey +} diff --git a/ndc-rest-schema/openapi/internal/oas3.go b/ndc-rest-schema/openapi/internal/oas3.go index d36dc01..5ee044e 100644 --- a/ndc-rest-schema/openapi/internal/oas3.go +++ b/ndc-rest-schema/openapi/internal/oas3.go @@ -29,8 +29,9 @@ type OAS3Builder struct { // SchemaInfoCache stores prebuilt information of component schema types. type SchemaInfoCache struct { - Name string - Schema schema.TypeEncoder + Name string + Schema schema.TypeEncoder + TypeSchema *rest.TypeSchema } // NewOAS3Builder creates an OAS3Builder instance @@ -199,45 +200,45 @@ func (oc *OAS3Builder) pathToNDCOperations(pathItem orderedmap.Pair[string, *v3. pathValue := pathItem.Value() if pathValue.Get != nil { - funcGet, err := newOAS3OperationBuilder(oc, pathKey, "get").BuildFunction(pathValue.Get) + funcGet, funcName, err := newOAS3OperationBuilder(oc, pathKey, "get").BuildFunction(pathValue.Get) if err != nil { return err } if funcGet != nil { - oc.schema.Functions = append(oc.schema.Functions, funcGet) + oc.schema.Functions[funcName] = *funcGet } } - procPost, err := newOAS3OperationBuilder(oc, pathKey, "post").BuildProcedure(pathValue.Post) + procPost, procPostName, err := newOAS3OperationBuilder(oc, pathKey, "post").BuildProcedure(pathValue.Post) if err != nil { return err } if procPost != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procPost) + oc.schema.Procedures[procPostName] = *procPost } - procPut, err := newOAS3OperationBuilder(oc, pathKey, "put").BuildProcedure(pathValue.Put) + procPut, procPutName, err := newOAS3OperationBuilder(oc, pathKey, "put").BuildProcedure(pathValue.Put) if err != nil { return err } if procPut != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procPut) + oc.schema.Procedures[procPutName] = *procPut } - procPatch, err := newOAS3OperationBuilder(oc, pathKey, "patch").BuildProcedure(pathValue.Patch) + procPatch, procPutName, err := newOAS3OperationBuilder(oc, pathKey, "patch").BuildProcedure(pathValue.Patch) if err != nil { return err } if procPatch != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procPatch) + oc.schema.Procedures[procPutName] = *procPatch } - procDelete, err := newOAS3OperationBuilder(oc, pathKey, "delete").BuildProcedure(pathValue.Delete) + procDelete, procDeleteName, err := newOAS3OperationBuilder(oc, pathKey, "delete").BuildProcedure(pathValue.Delete) if err != nil { return err } if procDelete != nil { - oc.schema.Procedures = append(oc.schema.Procedures, procDelete) + oc.schema.Procedures[procDeleteName] = *procDelete } return nil } @@ -255,21 +256,33 @@ func (oc *OAS3Builder) convertComponentSchemas(schemaItem orderedmap.Pair[string if _, ok := oc.schema.ObjectTypes[typeKey]; ok { return nil } - typeEncoder, _, _, err := newOAS3SchemaBuilder(oc, "", rest.InBody, false). + typeEncoder, schemaResult, _, err := newOAS3SchemaBuilder(oc, "", rest.InBody, false). getSchemaType(typeSchema, []string{typeKey}) if err != nil { return err } + var typeName string + if typeEncoder != nil { + typeName = getNamedType(typeEncoder, true, "") + } + cacheKey := "#/components/schemas/" + typeKey // treat no-property objects as a Arbitrary JSON scalar - if typeEncoder == nil || getNamedType(typeEncoder, true, "") == string(rest.ScalarJSON) { + if typeEncoder == nil || typeName == string(rest.ScalarJSON) { refName := utils.ToPascalCase(typeKey) scalar := schema.NewScalarType() scalar.Representation = schema.NewTypeRepresentationJSON().Encode() oc.schema.ScalarTypes[refName] = *scalar - oc.schemaCache["#/components/schemas/"+typeKey] = SchemaInfoCache{ - Name: refName, - Schema: schema.NewNamedType(refName), + oc.schemaCache[cacheKey] = SchemaInfoCache{ + Name: refName, + Schema: schema.NewNamedType(refName), + TypeSchema: schemaResult, + } + } else { + oc.schemaCache[cacheKey] = SchemaInfoCache{ + Name: typeName, + Schema: typeEncoder, + TypeSchema: schemaResult, } } @@ -304,7 +317,6 @@ func (oc *OAS3Builder) transformWriteSchema() { } } for _, proc := range oc.schema.Procedures { - var bodyName string for key, arg := range proc.Arguments { ty, name, _ := oc.populateWriteSchemaType(arg.Type) if name == "" { @@ -312,13 +324,6 @@ func (oc *OAS3Builder) transformWriteSchema() { } arg.Type = ty proc.Arguments[key] = arg - if key == "body" { - bodyName = name - } - } - - if bodyName != "" && proc.Request.RequestBody != nil && proc.Request.RequestBody.Schema != nil && !isOASType(proc.Request.RequestBody.Schema.Type) { - proc.Request.RequestBody.Schema.Type = bodyName } } } @@ -337,6 +342,9 @@ func (oc *OAS3Builder) populateWriteSchemaType(schemaType schema.Type) (schema.T oc.schemaCache[ty.Name] = SchemaInfoCache{ Name: ty.Name, Schema: schema.NewNamedType(ty.Name), + TypeSchema: &rest.TypeSchema{ + Type: []string{"object"}, + }, } } @@ -353,9 +361,9 @@ func (oc *OAS3Builder) populateWriteSchemaType(schemaType schema.Type) (schema.T if !ok { return schemaType, ty.Name, false } - writeObject := schema.ObjectType{ + writeObject := rest.ObjectType{ Description: objectType.Description, - Fields: make(schema.ObjectTypeFields), + Fields: make(map[string]rest.ObjectField), } var hasWriteField bool for key, field := range objectType.Fields { @@ -363,9 +371,12 @@ func (oc *OAS3Builder) populateWriteSchemaType(schemaType schema.Type) (schema.T if name == "" { continue } - writeObject.Fields[key] = schema.ObjectField{ - Description: field.Description, - Type: ut, + writeObject.Fields[key] = rest.ObjectField{ + ObjectField: schema.ObjectField{ + Description: field.Description, + Type: ut, + }, + Rest: field.Rest, } if isInput { hasWriteField = true diff --git a/ndc-rest-schema/openapi/internal/oas3_operation.go b/ndc-rest-schema/openapi/internal/oas3_operation.go index 6b8294f..b7453e1 100644 --- a/ndc-rest-schema/openapi/internal/oas3_operation.go +++ b/ndc-rest-schema/openapi/internal/oas3_operation.go @@ -1,11 +1,11 @@ package internal import ( - "errors" "fmt" "log/slog" "strconv" "strings" + "time" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-rest/ndc-rest-schema/utils" @@ -14,11 +14,10 @@ import ( ) type oas3OperationBuilder struct { - builder *OAS3Builder - pathKey string - method string - Arguments map[string]schema.ArgumentInfo - RequestParams []rest.RequestParameter + builder *OAS3Builder + pathKey string + method string + Arguments map[string]rest.ArgumentInfo } func newOAS3OperationBuilder(builder *OAS3Builder, pathKey string, method string) *oas3OperationBuilder { @@ -26,12 +25,13 @@ func newOAS3OperationBuilder(builder *OAS3Builder, pathKey string, method string builder: builder, pathKey: pathKey, method: method, - Arguments: make(map[string]schema.ArgumentInfo), + Arguments: make(map[string]rest.ArgumentInfo), } } // BuildFunction build a REST NDC function information from OpenAPI v3 operation -func (oc *oas3OperationBuilder) BuildFunction(itemGet *v3.Operation) (*rest.RESTFunctionInfo, error) { +func (oc *oas3OperationBuilder) BuildFunction(itemGet *v3.Operation) (*rest.OperationInfo, string, error) { + start := time.Now() funcName := itemGet.OperationId if funcName == "" { funcName = buildPathMethodName(oc.pathKey, "get", oc.builder.ConvertOptions) @@ -40,52 +40,50 @@ func (oc *oas3OperationBuilder) BuildFunction(itemGet *v3.Operation) (*rest.REST funcName = utils.StringSliceToCamelCase([]string{oc.builder.Prefix, funcName}) } - oc.builder.Logger.Info("function", - slog.String("name", funcName), - slog.String("path", oc.pathKey), - slog.String("method", oc.method), - ) + defer func() { + oc.builder.Logger.Info("function", + slog.String("name", funcName), + slog.String("path", oc.pathKey), + slog.String("method", oc.method), + slog.Duration("duration", time.Since(start)), + ) + }() + resultType, schemaResponse, err := oc.convertResponse(itemGet.Responses, oc.pathKey, []string{funcName, "Result"}) if err != nil { - return nil, fmt.Errorf("%s: %w", oc.pathKey, err) + return nil, "", fmt.Errorf("%s: %w", oc.pathKey, err) } if resultType == nil { - return nil, nil + return nil, "", nil } err = oc.convertParameters(itemGet.Parameters, oc.pathKey, []string{funcName}) if err != nil { - return nil, fmt.Errorf("%s: %w", funcName, err) + return nil, "", fmt.Errorf("%s: %w", funcName, err) } - function := rest.RESTFunctionInfo{ + description := oc.getOperationDescription(itemGet) + function := rest.OperationInfo{ Request: &rest.Request{ - URL: oc.pathKey, - Method: "get", - Parameters: sortRequestParameters(oc.RequestParams), - Security: convertSecurities(itemGet.Security), - Servers: oc.builder.convertServers(itemGet.Servers), - Response: *schemaResponse, - }, - FunctionInfo: schema.FunctionInfo{ - Name: funcName, - Arguments: oc.Arguments, - ResultType: resultType.Encode(), + URL: oc.pathKey, + Method: "get", + Security: convertSecurities(itemGet.Security), + Servers: oc.builder.convertServers(itemGet.Servers), + Response: *schemaResponse, }, + Description: &description, + Arguments: oc.Arguments, + ResultType: resultType.Encode(), } - if itemGet.Summary != "" { - function.Description = &itemGet.Summary - } - - return &function, nil + return &function, funcName, nil } -func (oc *oas3OperationBuilder) BuildProcedure(operation *v3.Operation) (*rest.RESTProcedureInfo, error) { +func (oc *oas3OperationBuilder) BuildProcedure(operation *v3.Operation) (*rest.OperationInfo, string, error) { if operation == nil { - return nil, nil + return nil, "", nil } - + start := time.Now() procName := operation.OperationId if procName == "" { procName = buildPathMethodName(oc.pathKey, oc.method, oc.builder.ConvertOptions) @@ -94,64 +92,68 @@ func (oc *oas3OperationBuilder) BuildProcedure(operation *v3.Operation) (*rest.R if oc.builder.Prefix != "" { procName = utils.StringSliceToCamelCase([]string{oc.builder.Prefix, procName}) } - oc.builder.Logger.Info("procedure", - slog.String("name", procName), - slog.String("path", oc.pathKey), - slog.String("method", oc.method), - ) + + defer func() { + oc.builder.Logger.Info("procedure", + slog.String("name", procName), + slog.String("path", oc.pathKey), + slog.String("method", oc.method), + slog.Duration("duration", time.Since(start)), + ) + }() + resultType, schemaResponse, err := oc.convertResponse(operation.Responses, oc.pathKey, []string{procName, "Result"}) if err != nil { - return nil, fmt.Errorf("%s: %w", oc.pathKey, err) + return nil, "", fmt.Errorf("%s: %w", oc.pathKey, err) } if resultType == nil { - return nil, nil + return nil, "", nil } err = oc.convertParameters(operation.Parameters, oc.pathKey, []string{procName}) if err != nil { - return nil, fmt.Errorf("%s: %w", oc.pathKey, err) + return nil, "", fmt.Errorf("%s: %w", oc.pathKey, err) } reqBody, schemaType, err := oc.convertRequestBody(operation.RequestBody, oc.pathKey, []string{procName, "Body"}) if err != nil { - return nil, fmt.Errorf("%s: %w", oc.pathKey, err) + return nil, "", fmt.Errorf("%s: %w", oc.pathKey, err) } if reqBody != nil { description := fmt.Sprintf("Request body of %s %s", strings.ToUpper(oc.method), oc.pathKey) // renaming query parameter name `body` if exist to avoid conflicts - if paramData, ok := oc.Arguments["body"]; ok { + if paramData, ok := oc.Arguments[rest.BodyKey]; ok { oc.Arguments["paramBody"] = paramData } - oc.Arguments["body"] = schema.ArgumentInfo{ - Description: &description, - Type: schemaType.Encode(), + oc.Arguments[rest.BodyKey] = rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Description: &description, + Type: schemaType.Encode(), + }, + Rest: &rest.RequestParameter{ + In: rest.InBody, + }, } } - procedure := rest.RESTProcedureInfo{ + description := oc.getOperationDescription(operation) + procedure := rest.OperationInfo{ Request: &rest.Request{ URL: oc.pathKey, Method: oc.method, - Parameters: sortRequestParameters(oc.RequestParams), Security: convertSecurities(operation.Security), Servers: oc.builder.convertServers(operation.Servers), RequestBody: reqBody, Response: *schemaResponse, }, - ProcedureInfo: schema.ProcedureInfo{ - Name: procName, - Arguments: oc.Arguments, - ResultType: resultType.Encode(), - }, + Description: &description, + Arguments: oc.Arguments, + ResultType: resultType.Encode(), } - if operation.Summary != "" { - procedure.Description = &operation.Summary - } - - return &procedure, nil + return &procedure, procName, nil } func (oc *oas3OperationBuilder) convertParameters(params []*v3.Parameter, apiPath string, fieldPaths []string) error { @@ -165,7 +167,7 @@ func (oc *oas3OperationBuilder) convertParameters(params []*v3.Parameter, apiPat } paramName := param.Name if paramName == "" { - return errors.New("parameter name is empty") + return errParameterNameRequired } paramRequired := false if param.Required != nil && *param.Required { @@ -194,16 +196,18 @@ func (oc *oas3OperationBuilder) convertParameters(params []*v3.Parameter, apiPat } encoding.Style = style } - oc.RequestParams = append(oc.RequestParams, rest.RequestParameter{ - Name: paramName, - In: paramLocation, - Schema: apiSchema, - EncodingObject: encoding, - }) oc.builder.typeUsageCounter.Add(getNamedType(schemaType, true, ""), 1) - argument := schema.ArgumentInfo{ - Type: schemaType.Encode(), + argument := rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Type: schemaType.Encode(), + }, + Rest: &rest.RequestParameter{ + Name: paramName, + In: paramLocation, + Schema: apiSchema, + EncodingObject: encoding, + }, } if param.Description != "" { argument.Description = ¶m.Description @@ -249,11 +253,10 @@ func (oc *oas3OperationBuilder) convertRequestBody(reqBody *v3.RequestBody, apiP oc.builder.typeUsageCounter.Add(getNamedType(schemaType, true, ""), 1) bodyResult := &rest.RequestBody{ ContentType: contentType, - Schema: typeSchema, } - if content.Encoding != nil { - encoding := make(map[string]rest.EncodingObject) + if content.Encoding != nil && content.Encoding.Len() > 0 { + bodyResult.Encoding = make(map[string]rest.EncodingObject) for iter := content.Encoding.First(); iter != nil; iter = iter.Next() { encodingValue := iter.Value() if encodingValue == nil { @@ -274,7 +277,7 @@ func (oc *oas3OperationBuilder) convertRequestBody(reqBody *v3.RequestBody, apiP item.Style = style } - if encodingValue.Headers != nil { + if encodingValue.Headers != nil && encodingValue.Headers.Len() > 0 { item.Headers = make(map[string]rest.RequestParameter) for encodingHeader := encodingValue.Headers.First(); encodingHeader != nil; encodingHeader = encodingHeader.Next() { key := strings.TrimSpace(encodingHeader.Key()) @@ -292,7 +295,6 @@ func (oc *oas3OperationBuilder) convertRequestBody(reqBody *v3.RequestBody, apiP headerEncoding := rest.EncodingObject{ AllowReserved: header.AllowReserved, Explode: &header.Explode, - Headers: map[string]rest.RequestParameter{}, } if header.Style != "" { @@ -304,7 +306,7 @@ func (oc *oas3OperationBuilder) convertRequestBody(reqBody *v3.RequestBody, apiP } argumentName := encodeHeaderArgumentName(key) - item.Headers[key] = rest.RequestParameter{ + headerParam := rest.RequestParameter{ ArgumentName: argumentName, Schema: typeSchema, EncodingObject: headerEncoding, @@ -317,14 +319,15 @@ func (oc *oas3OperationBuilder) convertRequestBody(reqBody *v3.RequestBody, apiP if header.Description != "" { argument.Description = &header.Description } - - oc.Arguments[argumentName] = argument + item.Headers[key] = headerParam + oc.Arguments[argumentName] = rest.ArgumentInfo{ + ArgumentInfo: argument, + Rest: &headerParam, + } } } - - encoding[iter.Key()] = item + bodyResult.Encoding[iter.Key()] = item } - bodyResult.Encoding = encoding } return bodyResult, schemaType, nil } @@ -405,3 +408,13 @@ func (oc *oas3OperationBuilder) convertResponse(responses *v3.Responses, apiPath return schemaType, schemaResponse, nil } } + +func (oc *oas3OperationBuilder) getOperationDescription(operation *v3.Operation) string { + if operation.Summary != "" { + return utils.StripHTMLTags(operation.Summary) + } + if operation.Description != "" { + return utils.StripHTMLTags(operation.Description) + } + return strings.ToUpper(oc.method) + " " + oc.pathKey +} diff --git a/ndc-rest-schema/openapi/internal/oas3_schema.go b/ndc-rest-schema/openapi/internal/oas3_schema.go index c19d5cf..4ba4319 100644 --- a/ndc-rest-schema/openapi/internal/oas3_schema.go +++ b/ndc-rest-schema/openapi/internal/oas3_schema.go @@ -54,9 +54,9 @@ func (oc *oas3SchemaBuilder) getSchemaTypeFromProxy(schemaProxy *base.SchemaProx } else if typeCache, ok := oc.builder.schemaCache[rawRefName]; ok { isRef = true ndcType = typeCache.Schema - typeSchema = &rest.TypeSchema{ - Type: typeCache.Name, - Description: innerSchema.Description, + typeSchema = createSchemaFromOpenAPISchema(innerSchema) + if typeCache.TypeSchema != nil { + typeSchema.Type = typeCache.TypeSchema.Type } } else { // return early object from ref @@ -74,17 +74,14 @@ func (oc *oas3SchemaBuilder) getSchemaTypeFromProxy(schemaProxy *base.SchemaProx if err != nil { return nil, nil, false, err } - typeSchema.Description = innerSchema.Description oc.builder.schemaCache[rawRefName] = SchemaInfoCache{ - Name: schemaName, - Schema: ndcType, + Name: schemaName, + Schema: ndcType, + TypeSchema: typeSchema, } } else { ndcType = schema.NewNamedType(schemaName) - typeSchema = &rest.TypeSchema{ - Type: schemaName, - Description: innerSchema.Description, - } + typeSchema = createSchemaFromOpenAPISchema(innerSchema) } } @@ -93,7 +90,6 @@ func (oc *oas3SchemaBuilder) getSchemaTypeFromProxy(schemaProxy *base.SchemaProx } if nullable { - typeSchema.Nullable = true if !isNullableType(ndcType) { ndcType = schema.NewNullableType(ndcType) } @@ -142,10 +138,9 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return enc, ty, isRef, nil } - var typeResult *rest.TypeSchema + typeResult := createSchemaFromOpenAPISchema(typeSchema) var isRef bool if oneOfLength > 0 || (typeSchema.AdditionalProperties != nil && (typeSchema.AdditionalProperties.B || typeSchema.AdditionalProperties.A != nil)) { - typeResult = createSchemaFromOpenAPISchema(typeSchema, string(rest.ScalarJSON)) return oc.builder.buildScalarJSON(), typeResult, false, nil } @@ -155,34 +150,30 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return nil, nil, false, errParameterSchemaEmpty(fieldPaths) } result = oc.builder.buildScalarJSON() - typeResult = createSchemaFromOpenAPISchema(typeSchema, string(rest.ScalarJSON)) - } else if len(typeSchema.Type) > 1 || isPrimitiveScalar(typeSchema.Type[0]) { + } else if len(typeSchema.Type) > 1 || isPrimitiveScalar(typeSchema.Type) { scalarName := getScalarFromType(oc.builder.schema, typeSchema.Type, typeSchema.Format, typeSchema.Enum, oc.builder.trimPathPrefix(oc.apiPath), fieldPaths) result = schema.NewNamedType(scalarName) - typeResult = createSchemaFromOpenAPISchema(typeSchema, scalarName) } else { typeName := typeSchema.Type[0] - typeResult = createSchemaFromOpenAPISchema(typeSchema, typeName) switch typeName { case "object": refName := utils.StringSliceToPascalCase(fieldPaths) - if typeSchema.Properties == nil || typeSchema.Properties.IsZero() { if typeSchema.AdditionalProperties != nil && (typeSchema.AdditionalProperties.A == nil || !typeSchema.AdditionalProperties.B) { return nil, nil, false, nil } // treat no-property objects as a JSON scalar - return oc.builder.buildScalarJSON(), &rest.TypeSchema{Type: string(rest.ScalarJSON)}, false, nil + return oc.builder.buildScalarJSON(), typeResult, false, nil } - object := schema.ObjectType{ - Fields: make(schema.ObjectTypeFields), + object := rest.ObjectType{ + Fields: make(map[string]rest.ObjectField), } - readObject := schema.ObjectType{ - Fields: make(schema.ObjectTypeFields), + readObject := rest.ObjectType{ + Fields: make(map[string]rest.ObjectField), } - writeObject := schema.ObjectType{ - Fields: make(schema.ObjectTypeFields), + writeObject := rest.ObjectType{ + Fields: make(map[string]rest.ObjectField), } if typeSchema.Description != "" { object.Description = &typeSchema.Description @@ -190,7 +181,6 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ writeObject.Description = &typeSchema.Description } - typeResult.Properties = make(map[string]rest.TypeSchema) for prop := typeSchema.Properties.First(); prop != nil; prop = prop.Next() { propName := prop.Key() oc.builder.Logger.Debug( @@ -206,17 +196,19 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ continue } oc.builder.typeUsageCounter.Add(getNamedType(propType, true, ""), 1) - objField := schema.ObjectField{ - Type: propType.Encode(), + objField := rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: propType.Encode(), + }, + Rest: propApiSchema, + } + if propApiSchema == nil { + propApiSchema = &rest.TypeSchema{} } if propApiSchema.Description != "" { objField.Description = &propApiSchema.Description } - if (!propApiSchema.ReadOnly && !propApiSchema.WriteOnly) || (!oc.writeMode && propApiSchema.ReadOnly) || (oc.writeMode || propApiSchema.WriteOnly) { - propApiSchema.Nullable = nullable - typeResult.Properties[propName] = *propApiSchema - } if !propApiSchema.ReadOnly && !propApiSchema.WriteOnly { object.Fields[propName] = objField } else if !oc.writeMode && propApiSchema.ReadOnly { @@ -290,15 +282,14 @@ func (oc *oas3SchemaBuilder) buildAllOfAnyOfSchemaType(schemaProxies []*base.Sch if len(proxies) == 1 { return oc.getSchemaTypeFromProxy(proxies[0], nullable, fieldPaths) } - readObject := schema.ObjectType{ - Fields: schema.ObjectTypeFields{}, + readObject := rest.ObjectType{ + Fields: map[string]rest.ObjectField{}, } - writeObject := schema.ObjectType{ - Fields: schema.ObjectTypeFields{}, + writeObject := rest.ObjectType{ + Fields: map[string]rest.ObjectField{}, } typeSchema := &rest.TypeSchema{ - Type: "object", - Properties: map[string]rest.TypeSchema{}, + Type: []string{"object"}, } for i, item := range proxies { @@ -310,7 +301,7 @@ func (oc *oas3SchemaBuilder) buildAllOfAnyOfSchemaType(schemaProxies []*base.Sch name := getNamedType(enc, true, "") writeName := formatWriteObjectName(name) - isObject := !isPrimitiveScalar(ty.Type) && ty.Type != "array" + isObject := !isPrimitiveScalar(ty.Type) && !slices.Contains(ty.Type, "array") if isObject { if _, ok := oc.builder.schema.ScalarTypes[name]; ok { isObject = false @@ -324,8 +315,6 @@ func (oc *oas3SchemaBuilder) buildAllOfAnyOfSchemaType(schemaProxies []*base.Sch } // TODO: should we keep the original anyOf or allOf type schema ty = &rest.TypeSchema{ - Type: string(rest.ScalarJSON), - Nullable: ty.Nullable, Description: ty.Description, } return oc.builder.buildScalarJSON(), ty, false, nil @@ -377,10 +366,5 @@ func (oc *oas3SchemaBuilder) buildAllOfAnyOfSchemaType(schemaProxies []*base.Sch refName = writeRefName } - if len(typeSchema.Properties) == 0 { - typeSchema = &rest.TypeSchema{ - Type: refName, - } - } return schema.NewNamedType(refName), typeSchema, false, nil } diff --git a/ndc-rest-schema/openapi/internal/types.go b/ndc-rest-schema/openapi/internal/types.go index 75ebd11..83014dd 100644 --- a/ndc-rest-schema/openapi/internal/types.go +++ b/ndc-rest-schema/openapi/internal/types.go @@ -1,6 +1,7 @@ package internal import ( + "errors" "log/slog" "regexp" @@ -14,6 +15,10 @@ var ( schemaRefNameV3Regexp = regexp.MustCompile(`^#/components/schemas/([a-zA-Z0-9\.\-_]+)$`) ) +var ( + errParameterNameRequired = errors.New("parameter name is empty") +) + var defaultScalarTypes = map[rest.ScalarName]*schema.ScalarType{ rest.ScalarBoolean: { AggregateFunctions: schema.ScalarTypeAggregateFunctions{}, diff --git a/ndc-rest-schema/openapi/internal/utils.go b/ndc-rest-schema/openapi/internal/utils.go index d08fef2..f3fd0cd 100644 --- a/ndc-rest-schema/openapi/internal/utils.go +++ b/ndc-rest-schema/openapi/internal/utils.go @@ -188,21 +188,13 @@ func canSetEnumToSchema(sm *rest.NDCRestSchema, scalarName string, enums []strin return false } -func createSchemaFromOpenAPISchema(input *base.Schema, typeName string) *rest.TypeSchema { +func createSchemaFromOpenAPISchema(input *base.Schema) *rest.TypeSchema { ps := &rest.TypeSchema{} if input == nil { return ps } - if typeName != "" { - ps.Type = typeName - } else { - if len(input.Type) > 1 { - ps.Type = string(rest.ScalarJSON) - } else if len(input.Type) > 0 { - ps.Type = input.Type[0] - } - ps.Format = input.Format - } + ps.Type = input.Type + ps.Format = input.Format ps.Pattern = input.Pattern ps.Maximum = input.Maximum ps.Minimum = input.Minimum @@ -259,24 +251,13 @@ func convertSecurity(security *base.SecurityRequirement) rest.AuthSecurity { } // check if the OAS type is a scalar -func isPrimitiveScalar(name string) bool { - return slices.Contains([]string{"boolean", "integer", "number", "string", "file", "long"}, name) -} - -func isOASType(name string) bool { - return slices.Contains([]string{"boolean", "integer", "number", "string", "file", "long", "array", "object"}, name) -} - -// sort request parameters in order: in -> name -func sortRequestParameters(input []rest.RequestParameter) []rest.RequestParameter { - slices.SortFunc(input, func(a rest.RequestParameter, b rest.RequestParameter) int { - if result := strings.Compare(string(a.In), string(b.In)); result != 0 { - return result +func isPrimitiveScalar(names []string) bool { + for _, name := range names { + if !slices.Contains([]string{"boolean", "integer", "number", "string", "file", "long"}, name) { + return false } - return strings.Compare(a.Name, b.Name) - }) - - return input + } + return true } // get the inner named type of the type encoder diff --git a/ndc-rest-schema/openapi/oas2_test.go b/ndc-rest-schema/openapi/oas2_test.go index 3246d73..8e19331 100644 --- a/ndc-rest-schema/openapi/oas2_test.go +++ b/ndc-rest-schema/openapi/oas2_test.go @@ -6,7 +6,9 @@ import ( "os" "testing" - "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "github.com/hasura/ndc-sdk-go/schema" + "gotest.tools/v3/assert" ) func TestOpenAPIv2ToRESTSchema(t *testing.T) { @@ -16,36 +18,45 @@ func TestOpenAPIv2ToRESTSchema(t *testing.T) { Source string Options ConvertOptions Expected string + Schema string }{ - // go run . convert -f ./openapi/testdata/jsonplaceholder/swagger.json -o ./openapi/testdata/jsonplaceholder/expected.json --spec oas2 --trim-prefix /v1 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/jsonplaceholder/swagger.json -o ./ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json --spec oas2 --trim-prefix /v1 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/jsonplaceholder/swagger.json -o ./ndc-rest-schema/openapi/testdata/jsonplaceholder/schema.json --pure --spec oas2 --trim-prefix /v1 { Name: "jsonplaceholder", Source: "testdata/jsonplaceholder/swagger.json", Expected: "testdata/jsonplaceholder/expected.json", + Schema: "testdata/jsonplaceholder/schema.json", Options: ConvertOptions{ TrimPrefix: "/v1", }, }, - // go run . convert -f ./openapi/testdata/petstore2/swagger.json -o ./openapi/testdata/petstore2/expected.json --spec oas2 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/petstore2/swagger.json -o ./ndc-rest-schema/openapi/testdata/petstore2/expected.json --spec oas2 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/petstore2/swagger.json -o ./ndc-rest-schema/openapi/testdata/petstore2/schema.json --pure --spec oas2 { Name: "petstore2", Source: "testdata/petstore2/swagger.json", Expected: "testdata/petstore2/expected.json", + Schema: "testdata/petstore2/schema.json", }, - // go run . convert -f ./openapi/testdata/prefix2/source.json -o ./openapi/testdata/prefix2/expected_single_word.json --spec oas2 --prefix hasura + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix2/source.json -o ./ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json --spec oas2 --prefix hasura + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix2/source.json -o ./ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.schema.json --pure --spec oas2 --prefix hasura { Name: "prefix2_single_word", Source: "testdata/prefix2/source.json", Expected: "testdata/prefix2/expected_single_word.json", + Schema: "testdata/prefix2/expected_single_word.schema.json", Options: ConvertOptions{ Prefix: "hasura", }, }, - // go run . convert -f ./openapi/testdata/prefix2/source.json -o ./openapi/testdata/prefix2/expected_multi_words.json --spec oas2 --prefix hasura_mock_json + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix2/source.json -o ./ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json --spec oas2 --prefix hasura_mock_json + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix2/source.json -o ./ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.schema.json --pure --spec oas2 --prefix hasura_mock_json { Name: "prefix2_single_word", Source: "testdata/prefix2/source.json", Expected: "testdata/prefix2/expected_multi_words.json", + Schema: "testdata/prefix2/expected_multi_words.schema.json", Options: ConvertOptions{ Prefix: "hasura_mock_json", }, @@ -55,12 +66,12 @@ func TestOpenAPIv2ToRESTSchema(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { sourceBytes, err := os.ReadFile(tc.Source) - assertNoError(t, err) + assert.NilError(t, err) expectedBytes, err := os.ReadFile(tc.Expected) - assertNoError(t, err) - var expected schema.NDCRestSchema - assertNoError(t, json.Unmarshal(expectedBytes, &expected)) + assert.NilError(t, err) + var expected rest.NDCRestSchema + assert.NilError(t, json.Unmarshal(expectedBytes, &expected)) output, errs := OpenAPIv2ToNDCSchema(sourceBytes, tc.Options) if output == nil { @@ -69,11 +80,25 @@ func TestOpenAPIv2ToRESTSchema(t *testing.T) { } assertRESTSchemaEqual(t, &expected, output) + assertConnectorSchema(t, tc.Schema, output) }) } t.Run("failure_empty", func(t *testing.T) { _, err := OpenAPIv2ToNDCSchema([]byte(""), ConvertOptions{}) - assertError(t, errors.Join(err...), "there is nothing in the spec, it's empty") + assert.ErrorContains(t, errors.Join(err...), "there is nothing in the spec, it's empty") }) } + +func assertConnectorSchema(t *testing.T, schemaPath string, output *rest.NDCRestSchema) { + t.Helper() + if schemaPath == "" { + return + } + schemaBytes, err := os.ReadFile(schemaPath) + assert.NilError(t, err) + var expectedSchema schema.SchemaResponse + assert.NilError(t, json.Unmarshal(schemaBytes, &expectedSchema)) + outputSchema := output.ToSchemaResponse() + assert.DeepEqual(t, expectedSchema, *outputSchema) +} diff --git a/ndc-rest-schema/openapi/oas3_test.go b/ndc-rest-schema/openapi/oas3_test.go index 8074304..2037b19 100644 --- a/ndc-rest-schema/openapi/oas3_test.go +++ b/ndc-rest-schema/openapi/oas3_test.go @@ -3,13 +3,11 @@ package openapi import ( "encoding/json" "errors" - "fmt" "os" - "reflect" - "strings" "testing" "github.com/hasura/ndc-rest/ndc-rest-schema/schema" + "gotest.tools/v3/assert" ) func TestOpenAPIv3ToRESTSchema(t *testing.T) { @@ -18,46 +16,57 @@ func TestOpenAPIv3ToRESTSchema(t *testing.T) { Name string Source string Expected string + Schema string Options ConvertOptions }{ - // go run . convert -f ./openapi/testdata/petstore3/source.json -o ./openapi/testdata/petstore3/expected.json --trim-prefix /v1 --spec openapi3 --env-prefix PET_STORE + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/petstore3/source.json -o ./ndc-rest-schema/openapi/testdata/petstore3/expected.json --trim-prefix /v1 --spec openapi3 --env-prefix PET_STORE + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/petstore3/source.json -o ./ndc-rest-schema/openapi/testdata/petstore3/schema.json --pure --trim-prefix /v1 --spec openapi3 --env-prefix PET_STORE { Name: "petstore3", Source: "testdata/petstore3/source.json", Expected: "testdata/petstore3/expected.json", + Schema: "testdata/petstore3/schema.json", Options: ConvertOptions{ TrimPrefix: "/v1", EnvPrefix: "PET_STORE", }, }, - // go run . convert -f ./openapi/testdata/onesignal/source.json -o ./openapi/testdata/onesignal/expected.json --spec openapi3 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/onesignal/source.json -o ./ndc-rest-schema/openapi/testdata/onesignal/expected.json --spec openapi3 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/onesignal/source.json -o ./ndc-rest-schema/openapi/testdata/onesignal/schema.json --pure --spec openapi3 { Name: "onesignal", Source: "testdata/onesignal/source.json", Expected: "testdata/onesignal/expected.json", + Schema: "testdata/onesignal/schema.json", Options: ConvertOptions{}, }, - // go run . convert -f ./openapi/testdata/openai/source.json -o ./openapi/testdata/openai/expected.json --spec openapi3 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/openai/source.json -o ./ndc-rest-schema/openapi/testdata/openai/expected.json --spec openapi3 + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/openai/source.json -o ./ndc-rest-schema/openapi/testdata/openai/schema.json --pure --spec openapi3 { Name: "openai", Source: "testdata/openai/source.json", Expected: "testdata/openai/expected.json", + Schema: "testdata/openai/schema.json", Options: ConvertOptions{}, }, - // go run . convert -f ./openapi/testdata/prefix3/source.json -o ./openapi/testdata/prefix3/expected_single_word.json --spec openapi3 --prefix hasura + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix3/source.json -o ./ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json --spec openapi3 --prefix hasura + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix3/source.json -o ./ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.schema.json --pure --spec openapi3 --prefix hasura { Name: "prefix3_single_word", Source: "testdata/prefix3/source.json", Expected: "testdata/prefix3/expected_single_word.json", + Schema: "testdata/prefix3/expected_single_word.schema.json", Options: ConvertOptions{ Prefix: "hasura", }, }, - // go run . convert -f ./openapi/testdata/prefix3/source.json -o ./openapi/testdata/prefix3/expected_multi_words.json --spec openapi3 --prefix hasura_one_signal + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix3/source.json -o ./ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json --spec openapi3 --prefix hasura_one_signal + // go run ./ndc-rest-schema convert -f ./ndc-rest-schema/openapi/testdata/prefix3/source.json -o ./ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.schema.json --pure --spec openapi3 --prefix hasura_one_signal { Name: "prefix3_multi_words", Source: "testdata/prefix3/source.json", Expected: "testdata/prefix3/expected_multi_words.json", + Schema: "testdata/prefix3/expected_multi_words.schema.json", Options: ConvertOptions{ Prefix: "hasura_one_signal", }, @@ -67,12 +76,12 @@ func TestOpenAPIv3ToRESTSchema(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { sourceBytes, err := os.ReadFile(tc.Source) - assertNoError(t, err) + assert.NilError(t, err) expectedBytes, err := os.ReadFile(tc.Expected) - assertNoError(t, err) + assert.NilError(t, err) var expected schema.NDCRestSchema - assertNoError(t, json.Unmarshal(expectedBytes, &expected)) + assert.NilError(t, json.Unmarshal(expectedBytes, &expected)) output, errs := OpenAPIv3ToNDCSchema(sourceBytes, tc.Options) if output == nil { @@ -80,77 +89,24 @@ func TestOpenAPIv3ToRESTSchema(t *testing.T) { } assertRESTSchemaEqual(t, &expected, output) + assertConnectorSchema(t, tc.Schema, output) }) } t.Run("failure_empty", func(t *testing.T) { _, err := OpenAPIv3ToNDCSchema([]byte(""), ConvertOptions{}) - assertError(t, errors.Join(err...), "there is nothing in the spec, it's empty") + assert.ErrorContains(t, errors.Join(err...), "there is nothing in the spec, it's empty") }) } -func assertNoError(t *testing.T, err error) { - if err != nil { - t.Errorf("expected no error, got: %s", err) - panic(err) - } -} - -func assertError(t *testing.T, err error, message string) { - if err == nil { - t.Error("expected error, got nil") - t.FailNow() - } else if !strings.Contains(err.Error(), message) { - t.Errorf("expected error with content: %s, got: %s", err.Error(), message) - t.FailNow() - } -} - -func assertDeepEqual(t *testing.T, expected any, reality any, msgs ...string) { - if reflect.DeepEqual(expected, reality) { - return - } - - expectedJson, _ := json.Marshal(expected) - realityJson, _ := json.Marshal(reality) - - var expected1, reality1 any - assertNoError(t, json.Unmarshal(expectedJson, &expected1)) - assertNoError(t, json.Unmarshal(realityJson, &reality1)) - - if !reflect.DeepEqual(expected1, reality1) { - t.Errorf("%s: not equal.\nexpected: %s\ngot : %s", strings.Join(msgs, " "), string(expectedJson), string(realityJson)) - t.FailNow() - } -} - func assertRESTSchemaEqual(t *testing.T, expected *schema.NDCRestSchema, output *schema.NDCRestSchema) { - assertDeepEqual(t, expected.Collections, output.Collections, "Collections") - assertDeepEqual(t, expected.Settings, output.Settings, "Settings") - assertDeepEqual(t, len(expected.ScalarTypes), len(output.ScalarTypes), "ScalarTypes") - for key, item := range expected.ScalarTypes { - assertDeepEqual(t, item, output.ScalarTypes[key], fmt.Sprintf("ScalarTypes[%s]", key)) - } - assertDeepEqual(t, len(expected.ObjectTypes), len(output.ObjectTypes), "ObjectTypes") - for key, item := range expected.ObjectTypes { - assertDeepEqual(t, item, output.ObjectTypes[key], fmt.Sprintf("ObjectTypes[%s]", key)) - } - assertDeepEqual(t, len(expected.Procedures), len(output.Procedures), "Procedures") - for i, item := range expected.Procedures { - assertDeepEqual(t, item.Arguments, output.Procedures[i].Arguments, fmt.Sprintf("Procedures[%d].Arguments", i)) - assertDeepEqual(t, item.Description, output.Procedures[i].Description, fmt.Sprintf("Procedures[%d].Description", i)) - assertDeepEqual(t, item.Name, output.Procedures[i].Name, fmt.Sprintf("Procedures[%d].Name", i)) - assertDeepEqual(t, item.ProcedureInfo, output.Procedures[i].ProcedureInfo, fmt.Sprintf("Procedures[%d].ProcedureInfo", i)) - assertDeepEqual(t, item.Request, output.Procedures[i].Request, fmt.Sprintf("Procedures[%d].Request", i)) - assertDeepEqual(t, item.ResultType, output.Procedures[i].ResultType, fmt.Sprintf("Procedures[%d].ResultType", i)) - } - assertDeepEqual(t, len(expected.Functions), len(output.Functions), "Functions") - for i, item := range expected.Functions { - assertDeepEqual(t, item.Arguments, output.Functions[i].Arguments, fmt.Sprintf("Functions[%d].Arguments", i)) - assertDeepEqual(t, item.Description, output.Functions[i].Description, fmt.Sprintf("Functions[%d].Description", i)) - assertDeepEqual(t, item.Name, output.Functions[i].Name, fmt.Sprintf("Functions[%d].Name", i)) - assertDeepEqual(t, item.FunctionInfo, output.Functions[i].FunctionInfo, fmt.Sprintf("Functions[%d].ProcedureInfo", i)) - assertDeepEqual(t, item.Request, output.Functions[i].Request, fmt.Sprintf("Functions[%d].Request", i)) - assertDeepEqual(t, item.ResultType, output.Functions[i].ResultType, fmt.Sprintf("Functions[%d].ResultType", i)) - } + t.Helper() + assert.DeepEqual(t, expected.Settings, output.Settings) + assert.DeepEqual(t, expected.ScalarTypes, output.ScalarTypes) + objectBs, _ := json.Marshal(output.ObjectTypes) + var objectTypes map[string]schema.ObjectType + assert.NilError(t, json.Unmarshal(objectBs, &objectTypes)) + assert.DeepEqual(t, expected.ObjectTypes, objectTypes) + assert.DeepEqual(t, expected.Procedures, output.Procedures) + assert.DeepEqual(t, expected.Functions, output.Functions) } diff --git a/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json b/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json index 22e5593..dd616a5 100644 --- a/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json +++ b/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json @@ -14,43 +14,33 @@ }, "version": "1.0.0" }, - "collections": [], - "functions": [ - { + "functions": { + "getAlbums": { "request": { - "url": "/posts", + "url": "/albums", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "userId", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "Filter by post ID", + "description": "Filter by album ID", "type": { "type": "nullable", "underlying_type": { "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "userId": { @@ -61,65 +51,65 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "userId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get all available posts", - "name": "getPosts", + "description": "Get all available albums", + "name": "getAlbums", "result_type": { "element_type": { - "name": "Post", + "name": "Album", "type": "named" }, "type": "array" } }, - { + "getAlbumsId": { "request": { - "url": "/posts/{id}", + "url": "/albums/{id}", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "The ID of the post to retrieve", + "description": "The ID of the album to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get specific post", - "name": "getPostById", + "description": "Get specific album", + "name": "getAlbumsId", "result_type": { - "name": "Post", + "name": "Album", "type": "named" } }, - { + "getAlbumsIdPhotos": { "request": { - "url": "/posts/{id}/comments", + "url": "/albums/{id}/photos", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } @@ -130,41 +120,65 @@ "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get comments for a specific post", - "name": "getPostsIdComments", + "description": "Get photos for a specific album", + "name": "getAlbumsIdPhotos", "result_type": { "element_type": { - "name": "Comment", + "name": "Photo", "type": "named" }, "type": "array" } }, - { + "getComment": { "request": { - "url": "/comments", + "url": "/comments/{id}", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "id": { + "description": "The ID of the comment to retrieve", + "type": { + "name": "Int32", + "type": "named" }, - { - "name": "postId", - "in": "query", + "rest": { + "name": "id", + "in": "path", "schema": { - "type": "Int32", - "nullable": true + "type": [ + "integer" + ] } } - ], + } + }, + "description": "Get specific comment", + "name": "getComment", + "result_type": { + "name": "Comment", + "type": "named" + } + }, + "getComments": { + "request": { + "url": "/comments", + "method": "get", "response": { "contentType": "application/json" } @@ -178,6 +192,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "postId": { @@ -188,6 +211,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "postId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -201,67 +233,49 @@ "type": "array" } }, - { + "getPhoto": { "request": { - "url": "/comments/{id}", + "url": "/photos/{id}", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "The ID of the comment to retrieve", + "description": "The ID of the photo to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get specific comment", - "name": "getComment", + "description": "Get specific photo", + "name": "getPhoto", "result_type": { - "name": "Comment", + "name": "Photo", "type": "named" } }, - { + "getPhotos": { "request": { - "url": "/albums", + "url": "/photos", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "userId", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { - "id": { + "albumId": { "description": "Filter by album ID", "type": { "type": "nullable", @@ -269,211 +283,227 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "albumId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, - "userId": { - "description": "Filter by user ID", + "id": { + "description": "Filter by photo ID", "type": { "type": "nullable", "underlying_type": { "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get all available albums", - "name": "getAlbums", + "description": "Get all available photos", + "name": "getPhotos", "result_type": { "element_type": { - "name": "Album", + "name": "Photo", "type": "named" }, "type": "array" } }, - { + "getPostById": { "request": { - "url": "/albums/{id}", + "url": "/posts/{id}", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "The ID of the album to retrieve", + "description": "The ID of the post to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get specific album", - "name": "getAlbumsId", + "description": "Get specific post", + "name": "getPostById", "result_type": { - "name": "Album", + "name": "Post", "type": "named" } }, - { + "getPosts": { "request": { - "url": "/albums/{id}/photos", + "url": "/posts", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "post id", + "description": "Filter by post ID", "type": { - "name": "Int32", - "type": "named" + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + }, + "rest": { + "name": "userId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get photos for a specific album", - "name": "getAlbumsIdPhotos", + "description": "Get all available posts", + "name": "getPosts", "result_type": { "element_type": { - "name": "Photo", + "name": "Post", "type": "named" }, "type": "array" } }, - { + "getPostsIdComments": { "request": { - "url": "/photos", + "url": "/posts/{id}/comments", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "albumId", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { - "albumId": { - "description": "Filter by album ID", - "type": { - "type": "nullable", - "underlying_type": { - "name": "Int32", - "type": "named" - } - } - }, "id": { - "description": "Filter by photo ID", + "description": "post id", "type": { - "type": "nullable", - "underlying_type": { - "name": "Int32", - "type": "named" + "name": "Int32", + "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] } } } }, - "description": "Get all available photos", - "name": "getPhotos", + "description": "Get comments for a specific post", + "name": "getPostsIdComments", "result_type": { "element_type": { - "name": "Photo", + "name": "Comment", "type": "named" }, "type": "array" } }, - { + "getTest": { + "request": { + "url": "/v1/test", + "method": "get", + "response": { + "contentType": "application/json" + } + }, + "arguments": {}, + "description": "Get test", + "name": "getTest", + "result_type": { + "name": "User", + "type": "named" + } + }, + "getTodo": { "request": { - "url": "/photos/{id}", + "url": "/todos/{id}", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "The ID of the photo to retrieve", + "description": "The ID of the todo to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get specific photo", - "name": "getPhoto", + "description": "Get specific todo", + "name": "getTodo", "result_type": { - "name": "Photo", + "name": "Todo", "type": "named" } }, - { + "getTodos": { "request": { "url": "/todos", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "userId", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } @@ -487,6 +517,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "userId": { @@ -497,6 +536,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "userId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -510,61 +558,43 @@ "type": "array" } }, - { + "getUser": { "request": { - "url": "/todos/{id}", + "url": "/users/{id}", "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { "id": { - "description": "The ID of the todo to retrieve", + "description": "The ID of the user to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Get specific todo", - "name": "getTodo", + "description": "Get specific user", + "name": "getUser", "result_type": { - "name": "Todo", + "name": "User", "type": "named" } }, - { + "getUsers": { "request": { "url": "/users", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "email", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } @@ -578,6 +608,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "email", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "id": { @@ -588,6 +627,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -600,57 +648,8 @@ }, "type": "array" } - }, - { - "request": { - "url": "/users/{id}", - "method": "get", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], - "response": { - "contentType": "application/json" - } - }, - "arguments": { - "id": { - "description": "The ID of the user to retrieve", - "type": { - "name": "Int32", - "type": "named" - } - } - }, - "description": "Get specific user", - "name": "getUser", - "result_type": { - "name": "User", - "type": "named" - } - }, - { - "request": { - "url": "/v1/test", - "method": "get", - "response": { - "contentType": "application/json" - } - }, - "arguments": {}, - "description": "Get test", - "name": "getTest", - "result_type": { - "name": "User", - "type": "named" - } } - ], + }, "object_types": { "Album": { "fields": { @@ -661,6 +660,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "title": { @@ -670,6 +675,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userId": { @@ -679,6 +689,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } } } @@ -692,6 +708,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "email": { @@ -701,6 +722,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "email" } }, "id": { @@ -710,6 +737,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { @@ -719,6 +752,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "postId": { @@ -728,6 +766,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } } } @@ -741,6 +785,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "id": { @@ -750,6 +800,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "thumbnailUrl": { @@ -759,6 +815,12 @@ "name": "URI", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "uri" } }, "title": { @@ -768,6 +830,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "url": { @@ -777,6 +844,12 @@ "name": "URI", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "uri" } } } @@ -790,6 +863,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -799,6 +877,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "title": { @@ -808,6 +892,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userId": { @@ -817,6 +906,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } } } @@ -830,6 +925,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "id": { @@ -839,6 +939,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "title": { @@ -848,6 +954,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userId": { @@ -857,6 +968,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } } } @@ -870,6 +987,11 @@ "name": "UserAddress", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "company": { @@ -879,6 +1001,11 @@ "name": "UserCompany", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "email": { @@ -888,6 +1015,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "email" } }, "id": { @@ -897,6 +1030,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { @@ -906,6 +1045,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "phone": { @@ -915,6 +1059,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "username": { @@ -924,6 +1073,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "website": { @@ -933,6 +1087,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -946,6 +1105,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "geo": { @@ -955,6 +1119,11 @@ "name": "UserAddressGeo", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "street": { @@ -964,6 +1133,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "suite": { @@ -973,6 +1147,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "zipcode": { @@ -982,6 +1161,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -995,6 +1179,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "lng": { @@ -1004,6 +1193,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1017,6 +1211,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "catchPhrase": { @@ -1026,6 +1225,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "name": { @@ -1035,21 +1239,23 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } } }, - "procedures": [ - { + "procedures": { + "createPost": { "request": { "url": "/posts", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Post" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -1061,6 +1267,14 @@ "type": { "name": "Post", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } } }, @@ -1071,70 +1285,48 @@ "type": "named" } }, - { + "deletePostById": { "request": { "url": "/posts/{id}", - "method": "put", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], - "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Post" - } - }, + "method": "delete", "response": { "contentType": "application/json" } }, "arguments": { - "body": { - "description": "Post object that needs to be updated", - "type": { - "name": "Post", - "type": "named" - } - }, "id": { "description": "The ID of the post to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Update specific post", - "name": "updatePostById", + "description": "Delete specific post", + "name": "deletePostById", "result_type": { - "name": "Post", - "type": "named" + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } } }, - { + "patchPostById": { "request": { "url": "/posts/{id}", "method": "patch", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Post" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -1146,6 +1338,14 @@ "type": { "name": "Post", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } }, "id": { @@ -1153,6 +1353,15 @@ "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -1163,43 +1372,58 @@ "type": "named" } }, - { + "updatePostById": { "request": { "url": "/posts/{id}", - "method": "delete", - "parameters": [ - { - "name": "id", - "in": "path", - "schema": { - "type": "Int32" - } - } - ], + "method": "put", + "requestBody": { + "contentType": "application/json" + }, "response": { "contentType": "application/json" } }, "arguments": { + "body": { + "description": "Post object that needs to be updated", + "type": { + "name": "Post", + "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } + } + }, "id": { "description": "The ID of the post to retrieve", "type": { "name": "Int32", "type": "named" + }, + "rest": { + "name": "id", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Delete specific post", - "name": "deletePostById", + "description": "Update specific post", + "name": "updatePostById", "result_type": { - "type": "nullable", - "underlying_type": { - "name": "Boolean", - "type": "named" - } + "name": "Post", + "type": "named" } } - ], + }, "scalar_types": { "Boolean": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/jsonplaceholder/schema.json b/ndc-rest-schema/openapi/testdata/jsonplaceholder/schema.json new file mode 100644 index 0000000..0248da1 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/jsonplaceholder/schema.json @@ -0,0 +1,867 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "id": { + "description": "Filter by album ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available albums", + "name": "getAlbums", + "result_type": { + "element_type": { + "name": "Album", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the album to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get specific album", + "name": "getAlbumsId", + "result_type": { + "name": "Album", + "type": "named" + } + }, + { + "arguments": { + "id": { + "description": "post id", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get photos for a specific album", + "name": "getAlbumsIdPhotos", + "result_type": { + "element_type": { + "name": "Photo", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the comment to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get specific comment", + "name": "getComment", + "result_type": { + "name": "Comment", + "type": "named" + } + }, + { + "arguments": { + "id": { + "description": "Filter by comment ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "postId": { + "description": "Filter by post ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available comments", + "name": "getComments", + "result_type": { + "element_type": { + "name": "Comment", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the photo to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get specific photo", + "name": "getPhoto", + "result_type": { + "name": "Photo", + "type": "named" + } + }, + { + "arguments": { + "albumId": { + "description": "Filter by album ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "id": { + "description": "Filter by photo ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available photos", + "name": "getPhotos", + "result_type": { + "element_type": { + "name": "Photo", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the post to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get specific post", + "name": "getPostById", + "result_type": { + "name": "Post", + "type": "named" + } + }, + { + "arguments": { + "id": { + "description": "Filter by post ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available posts", + "name": "getPosts", + "result_type": { + "element_type": { + "name": "Post", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "id": { + "description": "post id", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get comments for a specific post", + "name": "getPostsIdComments", + "result_type": { + "element_type": { + "name": "Comment", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": {}, + "description": "Get test", + "name": "getTest", + "result_type": { + "name": "User", + "type": "named" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the todo to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get specific todo", + "name": "getTodo", + "result_type": { + "name": "Todo", + "type": "named" + } + }, + { + "arguments": { + "id": { + "description": "Filter by todo ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available todos", + "name": "getTodos", + "result_type": { + "element_type": { + "name": "Todo", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the user to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Get specific user", + "name": "getUser", + "result_type": { + "name": "User", + "type": "named" + } + }, + { + "arguments": { + "email": { + "description": "Filter by user email address", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "id": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available users", + "name": "getUsers", + "result_type": { + "element_type": { + "name": "User", + "type": "named" + }, + "type": "array" + } + } + ], + "object_types": { + "Album": { + "fields": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + }, + "Comment": { + "fields": { + "body": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "postId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + }, + "Photo": { + "fields": { + "albumId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "thumbnailUrl": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "URI", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "url": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "URI", + "type": "named" + } + } + } + } + }, + "Post": { + "fields": { + "body": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + }, + "Todo": { + "fields": { + "completed": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + }, + "User": { + "fields": { + "address": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UserAddress", + "type": "named" + } + } + }, + "company": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UserCompany", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "phone": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "username": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "website": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "UserAddress": { + "fields": { + "city": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "geo": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UserAddressGeo", + "type": "named" + } + } + }, + "street": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "suite": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "zipcode": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "UserAddressGeo": { + "fields": { + "lat": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "lng": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "UserCompany": { + "fields": { + "bs": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "catchPhrase": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Post object that needs to be added", + "type": { + "name": "Post", + "type": "named" + } + } + }, + "description": "Create a post", + "name": "createPost", + "result_type": { + "name": "Post", + "type": "named" + } + }, + { + "arguments": { + "id": { + "description": "The ID of the post to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Delete specific post", + "name": "deletePostById", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "body": { + "description": "Post object that needs to be updated", + "type": { + "name": "Post", + "type": "named" + } + }, + "id": { + "description": "The ID of the post to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "patch specific post", + "name": "patchPostById", + "result_type": { + "name": "Post", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Post object that needs to be updated", + "type": { + "name": "Post", + "type": "named" + } + }, + "id": { + "description": "The ID of the post to retrieve", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "Update specific post", + "name": "updatePostById", + "result_type": { + "name": "Post", + "type": "named" + } + } + ], + "scalar_types": { + "Boolean": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "boolean" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "URI": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/onesignal/expected.json b/ndc-rest-schema/openapi/testdata/onesignal/expected.json index 6586555..ee2476d 100644 --- a/ndc-rest-schema/openapi/testdata/onesignal/expected.json +++ b/ndc-rest-schema/openapi/testdata/onesignal/expected.json @@ -28,45 +28,63 @@ }, "version": "1.2.2" }, - "collections": [], - "functions": [ - { + "functions": { + "get_notification": { "request": { - "url": "/notifications", + "url": "/notifications/{notification_id}", "method": "get", - "parameters": [ - { - "name": "app_id", - "in": "query", - "schema": { - "type": "String" - } - }, + "security": [ { - "name": "kind", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } + "app_key": [] + } + ], + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "app_id": { + "type": { + "name": "String", + "type": "named" }, - { - "name": "limit", + "rest": { + "name": "app_id", "in": "query", "schema": { - "type": "Int32", - "nullable": true + "type": [ + "string" + ] } + } + }, + "notification_id": { + "type": { + "name": "String", + "type": "named" }, - { - "name": "offset", - "in": "query", + "rest": { + "name": "notification_id", + "in": "path", "schema": { - "type": "Int32", - "nullable": true + "type": [ + "string" + ] } } - ], + } + }, + "description": "View notification", + "name": "get_notification", + "result_type": { + "name": "NotificationWithMeta", + "type": "named" + } + }, + "get_notifications": { + "request": { + "url": "/notifications", + "method": "get", "security": [ { "app_key": [] @@ -82,6 +100,15 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "app_id", + "in": "query", + "schema": { + "type": [ + "string" + ] + } } }, "kind": { @@ -92,6 +119,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "kind", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "limit": { @@ -102,6 +138,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "limit", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "offset": { @@ -112,67 +157,26 @@ "name": "Int32", "type": "named" } - } - } - }, - "description": "View notifications", - "name": "get_notifications", - "result_type": { - "name": "NotificationSlice", - "type": "named" - } - }, - { - "request": { - "url": "/notifications/{notification_id}", - "method": "get", - "parameters": [ - { - "name": "notification_id", - "in": "path", - "schema": { - "type": "String" - } }, - { - "name": "app_id", + "rest": { + "name": "offset", "in": "query", "schema": { - "type": "String" + "type": [ + "integer" + ] } } - ], - "security": [ - { - "app_key": [] - } - ], - "response": { - "contentType": "application/json" } }, - "arguments": { - "app_id": { - "type": { - "name": "String", - "type": "named" - } - }, - "notification_id": { - "type": { - "name": "String", - "type": "named" - } - } - }, - "description": "View notification", - "name": "get_notification", + "description": "View notifications", + "name": "get_notifications", "result_type": { - "name": "NotificationWithMeta", + "name": "NotificationSlice", "type": "named" } } - ], + }, "object_types": { "CancelNotificationSuccessResponse": { "fields": { @@ -183,6 +187,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } } } @@ -196,6 +205,9 @@ "name": "Notification200Errors", "type": "named" } + }, + "rest": { + "type": null } }, "external_id": { @@ -205,6 +217,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -214,6 +231,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "recipients": { @@ -223,6 +245,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -237,6 +264,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "errored": { @@ -247,6 +279,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "failed": { @@ -257,6 +294,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "received": { @@ -267,6 +309,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "successful": { @@ -277,6 +324,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -288,6 +340,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "key": { @@ -298,6 +355,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "relation": { @@ -305,6 +367,11 @@ "type": { "name": "FilterRelation", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { @@ -315,6 +382,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -328,6 +400,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "email": { @@ -338,6 +415,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "events": { @@ -348,6 +430,11 @@ "name": "NotificationsEvents", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -361,6 +448,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "success": { @@ -370,12 +462,105 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } } } }, "NotificationInput": { "fields": { + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "data": { + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + }, + "rest": { + "type": [ + "array" + ] + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "type": [ + "string" + ] + } + }, "send_after": { "type": { "type": "nullable", @@ -383,6 +568,26 @@ "name": "TimestampTZ", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "date-time" + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -396,6 +601,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "notifications": { @@ -408,6 +618,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "offset": { @@ -417,6 +632,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "total_count": { @@ -426,6 +646,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -440,6 +665,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "contents": { @@ -449,6 +680,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "converted": { @@ -459,24 +695,11 @@ "name": "Int32", "type": "named" } - } - }, - "custom_data": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "JSON", - "type": "named" - } - } - }, - "data": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "JSON", - "type": "named" - } + }, + "rest": { + "type": [ + "integer" + ] } }, "errored": { @@ -487,6 +710,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "excluded_segments": { @@ -499,6 +727,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "failed": { @@ -509,6 +747,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "filters": { @@ -521,6 +764,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "headings": { @@ -530,6 +778,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -539,6 +792,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "include_player_ids": { @@ -551,6 +809,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "included_segments": { @@ -563,6 +831,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "outcomes": { @@ -575,6 +853,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "platform_delivery_stats": { @@ -585,6 +868,11 @@ "name": "PlatformDeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "queued_at": { @@ -595,6 +883,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "received": { @@ -605,6 +899,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "remaining": { @@ -615,6 +914,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "send_after": { @@ -625,6 +929,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "subtitle": { @@ -634,6 +944,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "successful": { @@ -644,6 +959,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "target_channel": { @@ -653,6 +973,11 @@ "name": "PlayerNotificationTargetTargetChannel", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "throttle_rate_per_minute": { @@ -663,6 +988,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -673,18 +1003,33 @@ "type": { "name": "OutcomeDataAggregation", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "id": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -699,6 +1044,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "chrome_web_push": { @@ -708,6 +1058,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "edge_web_push": { @@ -717,6 +1072,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "email": { @@ -726,6 +1086,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "firefox_web_push": { @@ -735,6 +1100,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "ios": { @@ -744,6 +1114,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "safari_web_push": { @@ -753,6 +1128,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "sms": { @@ -762,6 +1142,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -776,134 +1161,115 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } } }, - "procedures": [ - { + "procedures": { + "cancel_notification": { "request": { - "url": "/notifications", - "method": "post", + "url": "/notifications/{notification_id}", + "method": "delete", "security": [ { "app_key": [] } ], - "requestBody": { - "contentType": "application/json", - "schema": { - "type": "NotificationInput" - } - }, "response": { "contentType": "application/json" } }, "arguments": { - "body": { - "description": "Request body of POST /notifications", + "app_id": { "type": { - "name": "NotificationInput", + "name": "String", "type": "named" + }, + "rest": { + "name": "app_id", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + }, + "notification_id": { + "type": { + "name": "String", + "type": "named" + }, + "rest": { + "name": "notification_id", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, - "description": "Create notification", - "name": "create_notification", + "description": "Stop a scheduled or currently outgoing notification", + "name": "cancel_notification", "result_type": { - "name": "CreateNotificationSuccessResponse", + "name": "CancelNotificationSuccessResponse", "type": "named" } }, - { + "create_notification": { "request": { - "url": "/notifications/{notification_id}", - "method": "delete", - "parameters": [ - { - "name": "notification_id", - "in": "path", - "schema": { - "type": "String" - } - }, - { - "name": "app_id", - "in": "query", - "schema": { - "type": "String" - } - } - ], + "url": "/notifications", + "method": "post", "security": [ { "app_key": [] } ], + "requestBody": { + "contentType": "application/json" + }, "response": { "contentType": "application/json" } }, "arguments": { - "app_id": { - "type": { - "name": "String", - "type": "named" - } - }, - "notification_id": { + "body": { + "description": "Request body of POST /notifications", "type": { - "name": "String", + "name": "NotificationInput", "type": "named" + }, + "rest": { + "in": "body" } } }, - "description": "Stop a scheduled or currently outgoing notification", - "name": "cancel_notification", + "description": "Create notification", + "name": "create_notification", "result_type": { - "name": "CancelNotificationSuccessResponse", + "name": "CreateNotificationSuccessResponse", "type": "named" } }, - { + "get_notification_history": { "request": { "url": "/notifications/{notification_id}/history", "method": "post", - "parameters": [ - { - "name": "notification_id", - "in": "path", - "schema": { - "type": "String" - } - } - ], "security": [ { "app_key": [] } ], "requestBody": { - "contentType": "application/json", - "schema": { - "type": "object", - "properties": { - "app_id": { - "type": "String", - "nullable": true - }, - "email": { - "type": "String", - "nullable": true - }, - "events": { - "type": "NotificationsEvents", - "nullable": true - } - } - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -915,6 +1281,9 @@ "type": { "name": "GetNotificationHistoryBody", "type": "named" + }, + "rest": { + "in": "body" } }, "notification_id": { @@ -922,6 +1291,15 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "notification_id", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, @@ -932,7 +1310,7 @@ "type": "named" } } - ], + }, "scalar_types": { "Boolean": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/onesignal/schema.json b/ndc-rest-schema/openapi/testdata/onesignal/schema.json new file mode 100644 index 0000000..1533c04 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/onesignal/schema.json @@ -0,0 +1,898 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "app_id": { + "type": { + "name": "String", + "type": "named" + } + }, + "notification_id": { + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "View notification", + "name": "get_notification", + "result_type": { + "name": "NotificationWithMeta", + "type": "named" + } + }, + { + "arguments": { + "app_id": { + "description": "The app ID that you want to view notifications from", + "type": { + "name": "String", + "type": "named" + } + }, + "kind": { + "description": "Kind of notifications returned:\n * unset - All notification types (default)\n * `0` - Dashboard only\n * `1` - API only\n * `3` - Automated only\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "limit": { + "description": "How many notifications to return. Max is 50. Default is 50.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "offset": { + "description": "Page offset. Default is 0. Results are sorted by queued_at in descending order. queued_at is a representation of the time that the notification was queued at.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "View notifications", + "name": "get_notifications", + "result_type": { + "name": "NotificationSlice", + "type": "named" + } + } + ], + "object_types": { + "CancelNotificationSuccessResponse": { + "fields": { + "success": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + } + } + }, + "CreateNotificationSuccessResponse": { + "fields": { + "errors": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Notification200Errors", + "type": "named" + } + } + }, + "external_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "recipients": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "DeliveryData": { + "fields": { + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "Filter": { + "fields": { + "field": { + "description": "Name of the field to use as the first operand in the filter expression.", + "type": { + "name": "String", + "type": "named" + } + }, + "key": { + "description": "If `field` is `tag`, this field is *required* to specify `key` inside the tags.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "relation": { + "description": "Operator of a filter expression.", + "type": { + "name": "FilterRelation", + "type": "named" + } + }, + "value": { + "description": "Constant value to use as the second operand in the filter expression. This value is *required* when the relation operator is a binary operator.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "GetNotificationHistoryBody": { + "fields": { + "app_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "email": { + "description": "The email address you would like the report sent.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "events": { + "description": "-\u003e \"sent\" - All the devices by player_id that were sent the specified notification_id. Notifications targeting under 1000 recipients will not have \"sent\" events recorded, but will show \"clicked\" events. \"clicked\" - All the devices by `player_id` that clicked the specified notification_id.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "NotificationsEvents", + "type": "named" + } + } + } + } + }, + "NotificationHistorySuccessResponse": { + "fields": { + "destination_url": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "success": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + } + } + }, + "NotificationInput": { + "fields": { + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "data": { + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "send_after": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + } + } + }, + "NotificationSlice": { + "fields": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "notifications": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "NotificationWithMeta", + "type": "named" + }, + "type": "array" + } + } + }, + "offset": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "total_count": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "NotificationWithMeta": { + "fields": { + "completed_at": { + "description": "Unix timestamp indicating when notification delivery completed. The delivery duration from start to finish can be calculated with completed_at - send_after.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "excluded_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "include_player_ids": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "included_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "outcomes": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "OutcomeData", + "type": "named" + }, + "type": "array" + } + } + }, + "platform_delivery_stats": { + "description": "Hash of delivery statistics broken out by target device platform.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlatformDeliveryData", + "type": "named" + } + } + }, + "queued_at": { + "description": "Unix timestamp indicating when the notification was created.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "remaining": { + "description": "Number of notifications that have not been sent out yet. This can mean either our system is still processing the notification or you have delayed options set.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "send_after": { + "description": "Unix timestamp indicating when notification delivery should begin.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "target_channel": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlayerNotificationTargetTargetChannel", + "type": "named" + } + } + }, + "throttle_rate_per_minute": { + "description": "number of push notifications sent per minute. Paid Feature Only. If throttling is not enabled for the app or the notification, and for free accounts, null is returned. Refer to Throttling for more details.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "OutcomeData": { + "fields": { + "aggregation": { + "type": { + "name": "OutcomeDataAggregation", + "type": "named" + } + }, + "id": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PlatformDeliveryData": { + "description": "Hash of delivery statistics broken out by target device platform.", + "fields": { + "android": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "chrome_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "edge_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "firefox_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "ios": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "safari_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "sms": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + } + } + }, + "StringMap": { + "fields": { + "en": { + "description": "Text in English. Will be used as a fallback", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "app_id": { + "type": { + "name": "String", + "type": "named" + } + }, + "notification_id": { + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Stop a scheduled or currently outgoing notification", + "name": "cancel_notification", + "result_type": { + "name": "CancelNotificationSuccessResponse", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /notifications", + "type": { + "name": "NotificationInput", + "type": "named" + } + } + }, + "description": "Create notification", + "name": "create_notification", + "result_type": { + "name": "CreateNotificationSuccessResponse", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /notifications/{notification_id}/history", + "type": { + "name": "GetNotificationHistoryBody", + "type": "named" + } + }, + "notification_id": { + "description": "The \"id\" of the message found in the Notification object", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Notification History", + "name": "get_notification_history", + "result_type": { + "name": "NotificationHistorySuccessResponse", + "type": "named" + } + } + ], + "scalar_types": { + "Boolean": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "boolean" + } + }, + "FilterRelation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "\u003e", + "\u003c", + "=", + "!=", + "exists", + "not_exists", + "time_elapsed_gt", + "time_elapsed_lt" + ], + "type": "enum" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "Notification200Errors": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "NotificationsEvents": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "sent", + "clicked" + ], + "type": "enum" + } + }, + "OutcomeDataAggregation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "sum", + "count" + ], + "type": "enum" + } + }, + "PlayerNotificationTargetTargetChannel": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/openai/expected.json b/ndc-rest-schema/openapi/testdata/openai/expected.json index 30df2ad..1e751f7 100644 --- a/ndc-rest-schema/openapi/testdata/openai/expected.json +++ b/ndc-rest-schema/openapi/testdata/openai/expected.json @@ -30,8 +30,7 @@ ], "version": "2.1.0" }, - "collections": [], - "functions": [], + "functions": {}, "object_types": { "ChatCompletionFunctions": { "fields": { @@ -43,6 +42,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "name": { @@ -50,6 +54,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "parameters": { @@ -60,6 +69,11 @@ "name": "FunctionParameters", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -71,6 +85,11 @@ "type": { "name": "ChatCompletionMessageToolCallFunction", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -78,6 +97,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "type": { @@ -85,6 +109,11 @@ "type": { "name": "ChatCompletionMessageToolCallType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -97,6 +126,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "name": { @@ -104,6 +138,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -116,6 +155,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "function_call": { @@ -126,6 +170,11 @@ "name": "ChatCompletionResponseMessageFunctionCall", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "role": { @@ -133,6 +182,11 @@ "type": { "name": "ChatCompletionResponseMessageRole", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "tool_calls": { @@ -146,6 +200,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } } } @@ -158,6 +217,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "name": { @@ -165,6 +229,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -180,6 +249,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } } } @@ -194,6 +268,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "integer" + ] + } } }, "logprob": { @@ -201,6 +285,11 @@ "type": { "name": "Float64", "type": "named" + }, + "rest": { + "type": [ + "number" + ] } }, "token": { @@ -208,6 +297,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "top_logprobs": { @@ -218,6 +312,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } } } @@ -232,6 +336,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "integer" + ] + } } }, "logprob": { @@ -239,6 +353,11 @@ "type": { "name": "Float64", "type": "named" + }, + "rest": { + "type": [ + "number" + ] } }, "token": { @@ -246,6 +365,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -256,6 +380,11 @@ "type": { "name": "FunctionObject", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "type": { @@ -263,6 +392,11 @@ "type": { "name": "ChatCompletionToolType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -277,6 +411,13 @@ "name": "Float64", "type": "named" } + }, + "rest": { + "type": [ + "number" + ], + "maximum": 2, + "minimum": -2 } }, "function_call": { @@ -287,6 +428,9 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": null } }, "functions": { @@ -300,6 +444,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "logit_bias": { @@ -310,6 +459,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "logprobs": { @@ -320,6 +474,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "max_tokens": { @@ -330,6 +489,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "messages": { @@ -340,6 +504,11 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ] } }, "model": { @@ -347,6 +516,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "n": { @@ -357,6 +531,13 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "maximum": 128, + "minimum": 1 } }, "parallel_tool_calls": { @@ -367,6 +548,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "presence_penalty": { @@ -377,6 +563,13 @@ "name": "Float64", "type": "named" } + }, + "rest": { + "type": [ + "number" + ], + "maximum": 2, + "minimum": -2 } }, "response_format": { @@ -387,6 +580,11 @@ "name": "CreateChatCompletionRequestResponseFormat", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "seed": { @@ -397,6 +595,13 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "maximum": 9223372036854776000, + "minimum": -9223372036854776000 } }, "service_tier": { @@ -407,6 +612,11 @@ "name": "CreateChatCompletionRequestServiceTier", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "stop": { @@ -417,6 +627,9 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": null } }, "stream": { @@ -427,6 +640,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "stream_options": { @@ -437,6 +655,11 @@ "name": "ChatCompletionStreamOptions", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "temperature": { @@ -447,6 +670,13 @@ "name": "Float64", "type": "named" } + }, + "rest": { + "type": [ + "number" + ], + "maximum": 2, + "minimum": 0 } }, "tool_choice": { @@ -457,6 +687,9 @@ "name": "ChatCompletionToolChoiceOption", "type": "named" } + }, + "rest": { + "type": null } }, "tools": { @@ -470,6 +703,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "top_logprobs": { @@ -480,6 +718,13 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "maximum": 20, + "minimum": 0 } }, "top_p": { @@ -490,6 +735,13 @@ "name": "Float64", "type": "named" } + }, + "rest": { + "type": [ + "number" + ], + "maximum": 1, + "minimum": 0 } }, "user": { @@ -500,6 +752,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -515,6 +772,11 @@ "name": "CreateChatCompletionRequestResponseFormatType", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -530,6 +792,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } }, "created": { @@ -537,6 +809,11 @@ "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } }, "id": { @@ -544,6 +821,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "model": { @@ -551,6 +833,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "object": { @@ -558,6 +845,11 @@ "type": { "name": "CreateChatCompletionResponseObject", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "service_tier": { @@ -568,6 +860,11 @@ "name": "CreateChatCompletionResponseServiceTier", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "system_fingerprint": { @@ -578,6 +875,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -589,6 +891,11 @@ "type": { "name": "CreateChatCompletionResponseChoicesFinishReason", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "index": { @@ -596,6 +903,11 @@ "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } }, "logprobs": { @@ -603,6 +915,11 @@ "type": { "name": "CreateChatCompletionResponseChoicesLogprobs", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "message": { @@ -610,6 +927,11 @@ "type": { "name": "ChatCompletionResponseMessage", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } } } @@ -625,6 +947,11 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ] } } } @@ -639,6 +966,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -653,6 +985,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "name": { @@ -660,6 +997,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "parameters": { @@ -670,6 +1012,11 @@ "name": "FunctionParameters", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -684,6 +1031,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "digest": { @@ -694,6 +1046,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "status": { @@ -704,6 +1061,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "total": { @@ -714,75 +1076,81 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } } }, - "procedures": [ - { + "procedures": { + "createChatCompletion": { "request": { - "url": "/api/create", + "url": "/chat/completions", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "CreateModelRequest" - } + "contentType": "application/json" }, "response": { - "contentType": "application/x-ndjson" + "contentType": "application/json" } }, "arguments": { "body": { - "description": "Request body of POST /api/create", + "description": "Request body of POST /chat/completions", "type": { - "name": "CreateModelRequest", + "name": "CreateChatCompletionRequest", "type": "named" + }, + "rest": { + "in": "body" } } }, - "name": "createModel", + "description": "Creates a model response for the given chat conversation.", + "name": "createChatCompletion", "result_type": { - "element_type": { - "name": "ProgressResponse", - "type": "named" - }, - "type": "array" + "name": "CreateChatCompletionResponse", + "type": "named" } }, - { + "createModel": { "request": { - "url": "/chat/completions", + "url": "/api/create", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "CreateChatCompletionRequest" - } + "contentType": "application/json" }, "response": { - "contentType": "application/json" + "contentType": "application/x-ndjson" } }, "arguments": { "body": { - "description": "Request body of POST /chat/completions", + "description": "Request body of POST /api/create", "type": { - "name": "CreateChatCompletionRequest", + "name": "CreateModelRequest", "type": "named" + }, + "rest": { + "in": "body" } } }, - "description": "Creates a model response for the given chat conversation.", - "name": "createChatCompletion", + "description": "POST /api/create", + "name": "createModel", "result_type": { - "name": "CreateChatCompletionResponse", - "type": "named" + "element_type": { + "name": "ProgressResponse", + "type": "named" + }, + "type": "array" } } - ], + }, "scalar_types": { "Boolean": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/openai/schema.json b/ndc-rest-schema/openapi/testdata/openai/schema.json new file mode 100644 index 0000000..a4dd0de --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/openai/schema.json @@ -0,0 +1,875 @@ +{ + "collections": [], + "functions": [], + "object_types": { + "ChatCompletionFunctions": { + "fields": { + "description": { + "description": "A description of what the function does, used by the model to choose when and how to call the function.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "name": { + "description": "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.", + "type": { + "name": "String", + "type": "named" + } + }, + "parameters": { + "description": "The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. \n\nOmitting `parameters` defines a function with an empty parameter list.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "FunctionParameters", + "type": "named" + } + } + } + } + }, + "ChatCompletionMessageToolCall": { + "fields": { + "function": { + "description": "The function that the model called.", + "type": { + "name": "ChatCompletionMessageToolCallFunction", + "type": "named" + } + }, + "id": { + "description": "The ID of the tool call.", + "type": { + "name": "String", + "type": "named" + } + }, + "type": { + "description": "The type of the tool. Currently, only `function` is supported.", + "type": { + "name": "ChatCompletionMessageToolCallType", + "type": "named" + } + } + } + }, + "ChatCompletionMessageToolCallFunction": { + "description": "The function that the model called.", + "fields": { + "arguments": { + "description": "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function.", + "type": { + "name": "String", + "type": "named" + } + }, + "name": { + "description": "The name of the function to call.", + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "ChatCompletionResponseMessage": { + "description": "A chat completion message generated by the model.", + "fields": { + "content": { + "description": "The contents of the message.", + "type": { + "name": "String", + "type": "named" + } + }, + "function_call": { + "description": "Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called, as generated by the model.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "ChatCompletionResponseMessageFunctionCall", + "type": "named" + } + } + }, + "role": { + "description": "The role of the author of this message.", + "type": { + "name": "ChatCompletionResponseMessageRole", + "type": "named" + } + }, + "tool_calls": { + "description": "The tool calls generated by the model, such as function calls.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "ChatCompletionMessageToolCall", + "type": "named" + }, + "type": "array" + } + } + } + } + }, + "ChatCompletionResponseMessageFunctionCall": { + "description": "Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called, as generated by the model.", + "fields": { + "arguments": { + "description": "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function.", + "type": { + "name": "String", + "type": "named" + } + }, + "name": { + "description": "The name of the function to call.", + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "ChatCompletionStreamOptions": { + "description": "Options for streaming response. Only set this when you set `stream: true`.\n", + "fields": { + "include_usage": { + "description": "If set, an additional chunk will be streamed before the `data: [DONE]` message. The `usage` field on this chunk shows the token usage statistics for the entire request, and the `choices` field will always be an empty array. All other chunks will also include a `usage` field, but with a null value.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + } + } + }, + "ChatCompletionTokenLogprob": { + "fields": { + "bytes": { + "description": "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token.", + "type": { + "element_type": { + "name": "Int32", + "type": "named" + }, + "type": "array" + } + }, + "logprob": { + "description": "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely.", + "type": { + "name": "Float64", + "type": "named" + } + }, + "token": { + "description": "The token.", + "type": { + "name": "String", + "type": "named" + } + }, + "top_logprobs": { + "description": "List of the most likely tokens and their log probability, at this token position. In rare cases, there may be fewer than the number of requested `top_logprobs` returned.", + "type": { + "element_type": { + "name": "ChatCompletionTokenLogprobTopLogprobs", + "type": "named" + }, + "type": "array" + } + } + } + }, + "ChatCompletionTokenLogprobTopLogprobs": { + "fields": { + "bytes": { + "description": "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token.", + "type": { + "element_type": { + "name": "Int32", + "type": "named" + }, + "type": "array" + } + }, + "logprob": { + "description": "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely.", + "type": { + "name": "Float64", + "type": "named" + } + }, + "token": { + "description": "The token.", + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "ChatCompletionTool": { + "fields": { + "function": { + "type": { + "name": "FunctionObject", + "type": "named" + } + }, + "type": { + "description": "The type of the tool. Currently, only `function` is supported.", + "type": { + "name": "ChatCompletionToolType", + "type": "named" + } + } + } + }, + "CreateChatCompletionRequest": { + "fields": { + "frequency_penalty": { + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details)\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Float64", + "type": "named" + } + } + }, + "function_call": { + "description": "Deprecated in favor of `tool_choice`.\n\nControls which (if any) function is called by the model.\n`none` means the model will not call a function and instead generates a message.\n`auto` means the model can pick between generating a message or calling a function.\nSpecifying a particular function via `{\"name\": \"my_function\"}` forces the model to call that function.\n\n`none` is the default when no functions are present. `auto` is the default if functions are present.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "functions": { + "description": "Deprecated in favor of `tools`.\n\nA list of functions the model may generate JSON inputs for.\n", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "ChatCompletionFunctions", + "type": "named" + }, + "type": "array" + } + } + }, + "logit_bias": { + "description": "Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "logprobs": { + "description": "Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each output token returned in the `content` of `message`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "max_tokens": { + "description": "The maximum number of [tokens](/tokenizer) that can be generated in the chat completion.\n\nThe total length of input tokens and generated tokens is limited by the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "messages": { + "description": "A list of messages comprising the conversation so far. [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models).", + "type": { + "element_type": { + "name": "ChatCompletionRequestMessage", + "type": "named" + }, + "type": "array" + } + }, + "model": { + "description": "ID of the model to use. See the [model endpoint compatibility](/docs/models/model-endpoint-compatibility) table for details on which models work with the Chat API.", + "type": { + "name": "String", + "type": "named" + } + }, + "n": { + "description": "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "parallel_tool_calls": { + "description": "Whether to enable [parallel function calling](/docs/guides/function-calling/parallel-function-calling) during tool use.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "presence_penalty": { + "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details)\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Float64", + "type": "named" + } + } + }, + "response_format": { + "description": "An object specifying the format that the model must output. Compatible with [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CreateChatCompletionRequestResponseFormat", + "type": "named" + } + } + }, + "seed": { + "description": "This feature is in Beta.\nIf specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "service_tier": { + "description": "Specifies the latency tier to use for processing the request. This parameter is relevant for customers subscribed to the scale tier service:\n - If set to 'auto', the system will utilize scale tier credits until they are exhausted.\n - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee.\n - When not set, the default behavior is 'auto'.\n\n When this parameter is set, the response body will include the `service_tier` utilized.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CreateChatCompletionRequestServiceTier", + "type": "named" + } + } + }, + "stop": { + "description": "Up to 4 sequences where the API will stop generating further tokens.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "stream": { + "description": "If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "stream_options": { + "description": "Options for streaming response. Only set this when you set `stream: true`.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "ChatCompletionStreamOptions", + "type": "named" + } + } + }, + "temperature": { + "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n\nWe generally recommend altering this or `top_p` but not both.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Float64", + "type": "named" + } + } + }, + "tool_choice": { + "description": "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tool and instead generates a message.\n`auto` means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools.\nSpecifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n\n`none` is the default when no tools are present. `auto` is the default if tools are present.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "ChatCompletionToolChoiceOption", + "type": "named" + } + } + }, + "tools": { + "description": "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.\n", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "ChatCompletionTool", + "type": "named" + }, + "type": "array" + } + } + }, + "top_logprobs": { + "description": "An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. `logprobs` must be set to `true` if this parameter is used.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "top_p": { + "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or `temperature` but not both.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Float64", + "type": "named" + } + } + }, + "user": { + "description": "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices/end-user-ids).\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "CreateChatCompletionRequestResponseFormat": { + "description": "An object specifying the format that the model must output. Compatible with [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n", + "fields": { + "type": { + "description": "Must be one of `text` or `json_object`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CreateChatCompletionRequestResponseFormatType", + "type": "named" + } + } + } + } + }, + "CreateChatCompletionResponse": { + "description": "Represents a chat completion response returned by model, based on the provided input.", + "fields": { + "choices": { + "description": "A list of chat completion choices. Can be more than one if `n` is greater than 1.", + "type": { + "element_type": { + "name": "CreateChatCompletionResponseChoices", + "type": "named" + }, + "type": "array" + } + }, + "created": { + "description": "The Unix timestamp (in seconds) of when the chat completion was created.", + "type": { + "name": "Int32", + "type": "named" + } + }, + "id": { + "description": "A unique identifier for the chat completion.", + "type": { + "name": "String", + "type": "named" + } + }, + "model": { + "description": "The model used for the chat completion.", + "type": { + "name": "String", + "type": "named" + } + }, + "object": { + "description": "The object type, which is always `chat.completion`.", + "type": { + "name": "CreateChatCompletionResponseObject", + "type": "named" + } + }, + "service_tier": { + "description": "The service tier used for processing the request. This field is only included if the `service_tier` parameter is specified in the request.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CreateChatCompletionResponseServiceTier", + "type": "named" + } + } + }, + "system_fingerprint": { + "description": "This fingerprint represents the backend configuration that the model runs with.\n\nCan be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism.\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "CreateChatCompletionResponseChoices": { + "fields": { + "finish_reason": { + "description": "The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence,\n`length` if the maximum number of tokens specified in the request was reached,\n`content_filter` if content was omitted due to a flag from our content filters,\n`tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function.\n", + "type": { + "name": "CreateChatCompletionResponseChoicesFinishReason", + "type": "named" + } + }, + "index": { + "description": "The index of the choice in the list of choices.", + "type": { + "name": "Int32", + "type": "named" + } + }, + "logprobs": { + "description": "Log probability information for the choice.", + "type": { + "name": "CreateChatCompletionResponseChoicesLogprobs", + "type": "named" + } + }, + "message": { + "description": "A chat completion message generated by the model.", + "type": { + "name": "ChatCompletionResponseMessage", + "type": "named" + } + } + } + }, + "CreateChatCompletionResponseChoicesLogprobs": { + "description": "Log probability information for the choice.", + "fields": { + "content": { + "description": "A list of message content tokens with log probability information.", + "type": { + "element_type": { + "name": "ChatCompletionTokenLogprob", + "type": "named" + }, + "type": "array" + } + } + } + }, + "CreateModelRequest": { + "fields": { + "model": { + "description": "The name of the model to create", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "FunctionObject": { + "fields": { + "description": { + "description": "A description of what the function does, used by the model to choose when and how to call the function.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "name": { + "description": "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.", + "type": { + "name": "String", + "type": "named" + } + }, + "parameters": { + "description": "The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. \n\nOmitting `parameters` defines a function with an empty parameter list.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "FunctionParameters", + "type": "named" + } + } + } + } + }, + "ProgressResponse": { + "fields": { + "completed": { + "description": "The completed size of the task", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "digest": { + "description": "The SHA256 digest of the blob", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "status": { + "description": "The status of the request", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "total": { + "description": "The total size of the task", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Request body of POST /chat/completions", + "type": { + "name": "CreateChatCompletionRequest", + "type": "named" + } + } + }, + "description": "Creates a model response for the given chat conversation.", + "name": "createChatCompletion", + "result_type": { + "name": "CreateChatCompletionResponse", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /api/create", + "type": { + "name": "CreateModelRequest", + "type": "named" + } + } + }, + "description": "POST /api/create", + "name": "createModel", + "result_type": { + "element_type": { + "name": "ProgressResponse", + "type": "named" + }, + "type": "array" + } + } + ], + "scalar_types": { + "Boolean": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "boolean" + } + }, + "ChatCompletionMessageToolCallType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "function" + ], + "type": "enum" + } + }, + "ChatCompletionRequestMessage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "ChatCompletionResponseMessageRole": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "assistant" + ], + "type": "enum" + } + }, + "ChatCompletionToolChoiceOption": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "ChatCompletionToolType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "function" + ], + "type": "enum" + } + }, + "CreateChatCompletionRequestResponseFormatType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "text", + "json_object" + ], + "type": "enum" + } + }, + "CreateChatCompletionRequestServiceTier": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "default" + ], + "type": "enum" + } + }, + "CreateChatCompletionResponseChoicesFinishReason": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "stop", + "length", + "tool_calls", + "content_filter", + "function_call" + ], + "type": "enum" + } + }, + "CreateChatCompletionResponseObject": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "chat.completion" + ], + "type": "enum" + } + }, + "CreateChatCompletionResponseServiceTier": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "scale", + "default" + ], + "type": "enum" + } + }, + "Float64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "float64" + } + }, + "FunctionParameters": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/petstore2/expected.json b/ndc-rest-schema/openapi/testdata/petstore2/expected.json index 13f2ce2..2d57c01 100644 --- a/ndc-rest-schema/openapi/testdata/petstore2/expected.json +++ b/ndc-rest-schema/openapi/testdata/petstore2/expected.json @@ -40,21 +40,11 @@ }, "version": "1.0.6" }, - "collections": [], - "functions": [ - { + "functions": { + "findPetsByStatus": { "request": { "url": "/pet/findByStatus", "method": "get", - "parameters": [ - { - "name": "status", - "in": "query", - "schema": { - "type": "array" - } - } - ], "security": [ { "petstore_auth": [ @@ -76,6 +66,15 @@ "type": "named" }, "type": "array" + }, + "rest": { + "name": "status", + "in": "query", + "schema": { + "type": [ + "array" + ] + } } } }, @@ -89,19 +88,10 @@ "type": "array" } }, - { + "findPetsByTags": { "request": { "url": "/pet/findByTags", "method": "get", - "parameters": [ - { - "name": "tags", - "in": "query", - "schema": { - "type": "array" - } - } - ], "security": [ { "petstore_auth": [ @@ -123,6 +113,15 @@ "type": "named" }, "type": "array" + }, + "rest": { + "name": "tags", + "in": "query", + "schema": { + "type": [ + "array" + ] + } } } }, @@ -136,19 +135,10 @@ "type": "array" } }, - { + "getInventory": { "request": { - "url": "/pet/{petId}", + "url": "/store/inventory", "method": "get", - "parameters": [ - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], "security": [ { "api_key": [] @@ -158,37 +148,18 @@ "contentType": "application/json" } }, - "arguments": { - "petId": { - "description": "ID of pet to return", - "type": { - "name": "Int64", - "type": "named" - } - } - }, - "description": "Find pet by ID", - "name": "getPetById", + "arguments": {}, + "description": "Returns pet inventories by status", + "name": "getInventory", "result_type": { - "name": "Pet", + "name": "JSON", "type": "named" } }, - { + "getOrderById": { "request": { "url": "/store/order/{orderId}", "method": "get", - "parameters": [ - { - "name": "orderId", - "in": "path", - "schema": { - "type": "Int64", - "maximum": 10, - "minimum": 1 - } - } - ], "response": { "contentType": "application/json" } @@ -199,6 +170,17 @@ "type": { "name": "Int64", "type": "named" + }, + "rest": { + "name": "orderId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "maximum": 10, + "minimum": 1 + } } } }, @@ -209,9 +191,9 @@ "type": "named" } }, - { + "getPetById": { "request": { - "url": "/store/inventory", + "url": "/pet/{petId}", "method": "get", "security": [ { @@ -222,148 +204,84 @@ "contentType": "application/json" } }, - "arguments": {}, - "description": "Returns pet inventories by status", - "name": "getInventory", + "arguments": { + "petId": { + "description": "ID of pet to return", + "type": { + "name": "Int64", + "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } + } + } + }, + "description": "Find pet by ID", + "name": "getPetById", "result_type": { - "name": "JSON", + "name": "Pet", "type": "named" } }, - { + "getSnake": { "request": { - "url": "/user/{username}", + "url": "/snake", "method": "get", - "parameters": [ - { - "name": "username", - "in": "path", - "schema": { - "type": "String" - } - } - ], "response": { "contentType": "application/json" } }, - "arguments": { - "username": { - "description": "The name that needs to be fetched. Use user1 for testing. ", - "type": { - "name": "String", - "type": "named" - } - } - }, - "description": "Get user by user name", - "name": "getUserByName", + "arguments": {}, + "description": "Get snake", + "name": "getSnake", "result_type": { - "name": "User", + "name": "SnakeObject", "type": "named" } }, - { + "getUserByName": { "request": { - "url": "/user/login", + "url": "/user/{username}", "method": "get", - "parameters": [ - { - "name": "username", - "in": "query", - "schema": { - "type": "String" - } - }, - { - "name": "password", - "in": "query", - "schema": { - "type": "String" - } - } - ], "response": { "contentType": "application/json" } }, "arguments": { - "password": { - "description": "The password for login in clear text", - "type": { - "name": "String", - "type": "named" - } - }, "username": { - "description": "The user name for login", + "description": "The name that needs to be fetched. Use user1 for testing. ", "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "username", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, - "description": "Logs user into the system", - "name": "loginUser", - "result_type": { - "name": "String", - "type": "named" - } - }, - { - "request": { - "url": "/snake", - "method": "get", - "response": { - "contentType": "application/json" - } - }, - "arguments": {}, - "description": "Get snake", - "name": "getSnake", + "description": "Get user by user name", + "name": "getUserByName", "result_type": { - "name": "SnakeObject", + "name": "User", "type": "named" } }, - { + "listOAuth2Clients": { "request": { "url": "/clients", "method": "get", - "parameters": [ - { - "name": "limit", - "in": "query", - "schema": { - "type": "Int64", - "nullable": true - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "Int64", - "nullable": true - } - }, - { - "name": "client_name", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } - }, - { - "name": "owner", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } @@ -377,6 +295,15 @@ "name": "String", "type": "named" } + }, + "rest": { + "name": "client_name", + "in": "query", + "schema": { + "type": [ + "string" + ] + } } }, "limit": { @@ -387,6 +314,15 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "name": "limit", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "offset": { @@ -397,6 +333,15 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "name": "offset", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "owner": { @@ -407,6 +352,15 @@ "name": "String", "type": "named" } + }, + "rest": { + "name": "owner", + "in": "query", + "schema": { + "type": [ + "string" + ] + } } } }, @@ -419,8 +373,57 @@ }, "type": "array" } + }, + "loginUser": { + "request": { + "url": "/user/login", + "method": "get", + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "password": { + "description": "The password for login in clear text", + "type": { + "name": "String", + "type": "named" + }, + "rest": { + "name": "password", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + }, + "username": { + "description": "The user name for login", + "type": { + "name": "String", + "type": "named" + }, + "rest": { + "name": "username", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + } + }, + "description": "Logs user into the system", + "name": "loginUser", + "result_type": { + "name": "String", + "type": "named" + } } - ], + }, "object_types": { "ApiResponse": { "fields": { @@ -431,6 +434,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "message": { @@ -440,6 +449,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "type": { @@ -449,6 +463,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -462,6 +481,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { @@ -471,6 +496,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -485,6 +515,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "client_name": { @@ -495,6 +530,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "client_secret": { @@ -505,6 +545,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "client_secret_expires_at": { @@ -515,6 +560,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "client_uri": { @@ -525,6 +576,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -538,6 +594,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "id": { @@ -547,6 +608,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "petId": { @@ -556,6 +623,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "quantity": { @@ -565,6 +638,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "shipDate": { @@ -574,6 +653,12 @@ "name": "TimestampTZ", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "date-time" } }, "status": { @@ -584,6 +669,11 @@ "name": "OrderStatus", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -597,6 +687,11 @@ "name": "Category", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -606,12 +701,23 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "photoUrls": { @@ -621,6 +727,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "status": { @@ -631,6 +747,11 @@ "name": "PetStatus", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "tags": { @@ -643,6 +764,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } } } @@ -656,6 +782,9 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": null } }, "id": { @@ -665,6 +794,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "username": { @@ -674,6 +809,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -687,6 +827,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { @@ -696,6 +842,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -710,6 +861,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "status": { @@ -720,6 +876,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -734,6 +895,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "file": { @@ -744,6 +910,11 @@ "name": "Binary", "type": "named" } + }, + "rest": { + "type": [ + "file" + ] } } } @@ -757,6 +928,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "firstName": { @@ -766,6 +942,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -775,6 +956,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "lastName": { @@ -784,6 +971,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "password": { @@ -793,6 +985,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "phone": { @@ -802,6 +999,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userStatus": { @@ -812,6 +1014,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "username": { @@ -821,77 +1029,18 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } } }, - "procedures": [ - { - "request": { - "url": "/pet/{petId}/uploadImage", - "method": "post", - "parameters": [ - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], - "requestBody": { - "contentType": "multipart/form-data", - "schema": { - "type": "object", - "properties": { - "additionalMetadata": { - "type": "String", - "nullable": true - }, - "file": { - "type": "Binary", - "nullable": true - } - } - } - }, - "response": { - "contentType": "application/json" - } - }, - "arguments": { - "body": { - "description": "Form data of /pet/{petId}/uploadImage", - "type": { - "name": "UploadFileBody", - "type": "named" - } - }, - "petId": { - "description": "ID of pet to update", - "type": { - "name": "Int64", - "type": "named" - } - } - }, - "description": "uploads an image", - "name": "uploadFile", - "result_type": { - "name": "ApiResponse", - "type": "named" - } - }, - { + "procedures": { + "addPet": { "request": { "url": "/pet", "method": "post", @@ -904,10 +1053,7 @@ } ], "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Pet" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -919,6 +1065,14 @@ "type": { "name": "Pet", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } } }, @@ -932,39 +1086,51 @@ } } }, - { + "addSnake": { "request": { - "url": "/pet", - "method": "put", - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], - "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Pet" - } - }, + "url": "/snake", + "method": "post", + "response": { + "contentType": "application/json" + } + }, + "arguments": {}, + "description": "Create snake", + "name": "addSnake", + "result_type": { + "name": "SnakeObject", + "type": "named" + } + }, + "deleteOrder": { + "request": { + "url": "/store/order/{orderId}", + "method": "delete", "response": { "contentType": "application/json" } }, "arguments": { - "body": { - "description": "Pet object that needs to be added to the store", + "orderId": { + "description": "ID of the order that needs to be deleted", "type": { - "name": "Pet", + "name": "Int64", "type": "named" + }, + "rest": { + "name": "orderId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "minimum": 1 + } } } }, - "description": "Update an existing pet", - "name": "updatePet", + "description": "Delete purchase order by ID", + "name": "deleteOrder", "result_type": { "type": "nullable", "underlying_type": { @@ -973,19 +1139,10 @@ } } }, - { + "deletePet": { "request": { "url": "/pet/{petId}", - "method": "post", - "parameters": [ - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], + "method": "delete", "security": [ { "petstore_auth": [ @@ -994,44 +1151,48 @@ ] } ], - "requestBody": { - "contentType": "application/x-www-form-urlencoded", - "schema": { - "type": "object", - "properties": { - "name": { - "type": "String", - "nullable": true - }, - "status": { - "type": "String", - "nullable": true - } - } - } - }, "response": { "contentType": "application/json" } }, "arguments": { - "body": { - "description": "Form data of /pet/{petId}", + "api_key": { "type": { - "name": "UpdatePetWithFormBody", - "type": "named" + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "name": "api_key", + "in": "header", + "schema": { + "type": [ + "string" + ] + } } }, "petId": { - "description": "ID of pet that needs to be updated", + "description": "Pet id to delete", "type": { "name": "Int64", "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Updates a pet in the store with form data", - "name": "updatePetWithForm", + "description": "Deletes a pet", + "name": "deletePet", "result_type": { "type": "nullable", "underlying_type": { @@ -1040,59 +1201,34 @@ } } }, - { + "deleteUser": { "request": { - "url": "/pet/{petId}", + "url": "/user/{username}", "method": "delete", - "parameters": [ - { - "name": "api_key", - "in": "header", - "schema": { - "type": "String", - "nullable": true - } - }, - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], "response": { "contentType": "application/json" } }, "arguments": { - "api_key": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "String", - "type": "named" - } - } - }, - "petId": { - "description": "Pet id to delete", + "username": { + "description": "The name that needs to be deleted", "type": { - "name": "Int64", + "name": "String", "type": "named" + }, + "rest": { + "name": "username", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, - "description": "Deletes a pet", - "name": "deletePet", + "description": "Delete user", + "name": "deleteUser", "result_type": { "type": "nullable", "underlying_type": { @@ -1101,15 +1237,46 @@ } } }, - { + "dynamicClientRegistrationCreateOAuth2Client": { "request": { - "url": "/store/order", + "url": "/oauth2/register", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Order" + "contentType": "application/json" + }, + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "body": { + "type": { + "name": "OAuth2Client", + "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } + } + }, + "description": "POST /oauth2/register", + "name": "dynamicClientRegistrationCreateOAuth2Client", + "result_type": { + "name": "OAuth2Client", + "type": "named" + } + }, + "placeOrder": { + "request": { + "url": "/store/order", + "method": "post", + "requestBody": { + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -1121,6 +1288,14 @@ "type": { "name": "Order", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } } }, @@ -1131,35 +1306,44 @@ "type": "named" } }, - { + "updatePet": { "request": { - "url": "/store/order/{orderId}", - "method": "delete", - "parameters": [ + "url": "/pet", + "method": "put", + "security": [ { - "name": "orderId", - "in": "path", - "schema": { - "type": "Int64", - "minimum": 1 - } + "petstore_auth": [ + "write:pets", + "read:pets" + ] } ], + "requestBody": { + "contentType": "application/json" + }, "response": { "contentType": "application/json" } }, "arguments": { - "orderId": { - "description": "ID of the order that needs to be deleted", + "body": { + "description": "Pet object that needs to be added to the store", "type": { - "name": "Int64", + "name": "Pet", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } } }, - "description": "Delete purchase order by ID", - "name": "deleteOrder", + "description": "Update an existing pet", + "name": "updatePet", "result_type": { "type": "nullable", "underlying_type": { @@ -1168,24 +1352,20 @@ } } }, - { + "updatePetWithForm": { "request": { - "url": "/user/{username}", - "method": "put", - "parameters": [ + "url": "/pet/{petId}", + "method": "post", + "security": [ { - "name": "username", - "in": "path", - "schema": { - "type": "String" - } + "petstore_auth": [ + "write:pets", + "read:pets" + ] } ], "requestBody": { - "contentType": "application/json", - "schema": { - "type": "User" - } + "contentType": "application/x-www-form-urlencoded" }, "response": { "contentType": "application/json" @@ -1193,22 +1373,39 @@ }, "arguments": { "body": { - "description": "Updated user object", + "description": "Form data of /pet/{petId}", "type": { - "name": "User", + "name": "UpdatePetWithFormBody", "type": "named" + }, + "rest": { + "in": "formData", + "schema": { + "type": [ + "object" + ] + } } }, - "username": { - "description": "name that need to be updated", + "petId": { + "description": "ID of pet that needs to be updated", "type": { - "name": "String", + "name": "Int64", "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "description": "Updated user", - "name": "updateUser", + "description": "Updates a pet in the store with form data", + "name": "updatePetWithForm", "result_type": { "type": "nullable", "underlying_type": { @@ -1217,34 +1414,52 @@ } } }, - { + "updateUser": { "request": { "url": "/user/{username}", - "method": "delete", - "parameters": [ - { - "name": "username", - "in": "path", - "schema": { - "type": "String" - } - } - ], + "method": "put", + "requestBody": { + "contentType": "application/json" + }, "response": { "contentType": "application/json" } }, "arguments": { + "body": { + "description": "Updated user object", + "type": { + "name": "User", + "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } + } + }, "username": { - "description": "The name that needs to be deleted", + "description": "name that need to be updated", "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "username", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, - "description": "Delete user", - "name": "deleteUser", + "description": "Updated user", + "name": "updateUser", "result_type": { "type": "nullable", "underlying_type": { @@ -1253,31 +1468,20 @@ } } }, - { + "uploadFile": { "request": { - "url": "/snake", - "method": "post", - "response": { - "contentType": "application/json" - } - }, - "arguments": {}, - "description": "Create snake", - "name": "addSnake", - "result_type": { - "name": "SnakeObject", - "type": "named" - } - }, - { - "request": { - "url": "/oauth2/register", + "url": "/pet/{petId}/uploadImage", "method": "post", - "requestBody": { - "contentType": "application/json", - "schema": { - "type": "OAuth2Client" + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] } + ], + "requestBody": { + "contentType": "multipart/form-data" }, "response": { "contentType": "application/json" @@ -1285,19 +1489,45 @@ }, "arguments": { "body": { + "description": "Form data of /pet/{petId}/uploadImage", "type": { - "name": "OAuth2Client", + "name": "UploadFileBody", + "type": "named" + }, + "rest": { + "in": "formData", + "schema": { + "type": [ + "object" + ] + } + } + }, + "petId": { + "description": "ID of pet to update", + "type": { + "name": "Int64", "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ] + } } } }, - "name": "dynamicClientRegistrationCreateOAuth2Client", + "description": "uploads an image", + "name": "uploadFile", "result_type": { - "name": "OAuth2Client", + "name": "ApiResponse", "type": "named" } } - ], + }, "scalar_types": { "Binary": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/petstore2/schema.json b/ndc-rest-schema/openapi/testdata/petstore2/schema.json new file mode 100644 index 0000000..dae7ff9 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/petstore2/schema.json @@ -0,0 +1,908 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "status": { + "description": "Status values that need to be considered for filter", + "type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "description": "Finds Pets by status", + "name": "findPetsByStatus", + "result_type": { + "element_type": { + "name": "Pet", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "tags": { + "description": "Tags to filter by", + "type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "description": "Finds Pets by tags", + "name": "findPetsByTags", + "result_type": { + "element_type": { + "name": "Pet", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": {}, + "description": "Returns pet inventories by status", + "name": "getInventory", + "result_type": { + "name": "JSON", + "type": "named" + } + }, + { + "arguments": { + "orderId": { + "description": "ID of pet that needs to be fetched", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Find purchase order by ID", + "name": "getOrderById", + "result_type": { + "name": "Order", + "type": "named" + } + }, + { + "arguments": { + "petId": { + "description": "ID of pet to return", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Find pet by ID", + "name": "getPetById", + "result_type": { + "name": "Pet", + "type": "named" + } + }, + { + "arguments": {}, + "description": "Get snake", + "name": "getSnake", + "result_type": { + "name": "SnakeObject", + "type": "named" + } + }, + { + "arguments": { + "username": { + "description": "The name that needs to be fetched. Use user1 for testing. ", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Get user by user name", + "name": "getUserByName", + "result_type": { + "name": "User", + "type": "named" + } + }, + { + "arguments": { + "client_name": { + "description": "The name of the clients to filter by.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "limit": { + "description": "The maximum amount of clients to returned, upper bound is 500 clients.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "offset": { + "description": "The offset from where to start looking.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "owner": { + "description": "The owner of the clients to filter by.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + }, + "description": "List OAuth 2.0 Clients", + "name": "listOAuth2Clients", + "result_type": { + "element_type": { + "name": "OAuth2Client", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "password": { + "description": "The password for login in clear text", + "type": { + "name": "String", + "type": "named" + } + }, + "username": { + "description": "The user name for login", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Logs user into the system", + "name": "loginUser", + "result_type": { + "name": "String", + "type": "named" + } + } + ], + "object_types": { + "ApiResponse": { + "fields": { + "code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "message": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "type": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "Category": { + "fields": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "OAuth2Client": { + "fields": { + "client_id": { + "description": "ID is the id for this client.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "client_name": { + "description": "Name is the human-readable string name of the client to be presented to the\nend-user during authorization.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "client_secret": { + "description": "Secret is the client's secret. The secret will be included in the create request as cleartext, and then\nnever again. The secret is stored using BCrypt so it is impossible to recover it. Tell your users\nthat they need to write the secret down as it will not be made available again.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "client_secret_expires_at": { + "description": "SecretExpiresAt is an integer holding the time at which the client\nsecret will expire or 0 if it will not expire. The time is\nrepresented as the number of seconds from 1970-01-01T00:00:00Z as\nmeasured in UTC until the date/time of expiration.\n\nThis feature is currently not supported and it's value will always\nbe set to 0.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "client_uri": { + "description": "ClientURI is an URL string of a web page providing information about the client.\nIf present, the server SHOULD display this URL to the end-user in\na clickable fashion.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "Order": { + "fields": { + "complete": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "petId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "quantity": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "shipDate": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + }, + "status": { + "description": "Order Status", + "type": { + "type": "nullable", + "underlying_type": { + "name": "OrderStatus", + "type": "named" + } + } + } + } + }, + "Pet": { + "fields": { + "category": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Category", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "name": "String", + "type": "named" + } + }, + "photoUrls": { + "type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + }, + "status": { + "description": "pet status in the store", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PetStatus", + "type": "named" + } + } + }, + "tags": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Tag", + "type": "named" + }, + "type": "array" + } + } + } + } + }, + "SnakeObject": { + "fields": { + "context": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "username": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "Tag": { + "fields": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "UpdatePetWithFormBody": { + "fields": { + "name": { + "description": "Updated name of the pet", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "status": { + "description": "Updated status of the pet", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "UploadFileBody": { + "fields": { + "additionalMetadata": { + "description": "Additional data to pass to server", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "file": { + "description": "file to upload", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Binary", + "type": "named" + } + } + } + } + }, + "User": { + "fields": { + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "firstName": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "lastName": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "password": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "phone": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userStatus": { + "description": "User Status", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "username": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Pet object that needs to be added to the store", + "type": { + "name": "Pet", + "type": "named" + } + } + }, + "description": "Add a new pet to the store", + "name": "addPet", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": {}, + "description": "Create snake", + "name": "addSnake", + "result_type": { + "name": "SnakeObject", + "type": "named" + } + }, + { + "arguments": { + "orderId": { + "description": "ID of the order that needs to be deleted", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Delete purchase order by ID", + "name": "deleteOrder", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "api_key": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "petId": { + "description": "Pet id to delete", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Deletes a pet", + "name": "deletePet", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "username": { + "description": "The name that needs to be deleted", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Delete user", + "name": "deleteUser", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "body": { + "type": { + "name": "OAuth2Client", + "type": "named" + } + } + }, + "description": "POST /oauth2/register", + "name": "dynamicClientRegistrationCreateOAuth2Client", + "result_type": { + "name": "OAuth2Client", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "order placed for purchasing the pet", + "type": { + "name": "Order", + "type": "named" + } + } + }, + "description": "Place an order for a pet", + "name": "placeOrder", + "result_type": { + "name": "Order", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Pet object that needs to be added to the store", + "type": { + "name": "Pet", + "type": "named" + } + } + }, + "description": "Update an existing pet", + "name": "updatePet", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "body": { + "description": "Form data of /pet/{petId}", + "type": { + "name": "UpdatePetWithFormBody", + "type": "named" + } + }, + "petId": { + "description": "ID of pet that needs to be updated", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Updates a pet in the store with form data", + "name": "updatePetWithForm", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "body": { + "description": "Updated user object", + "type": { + "name": "User", + "type": "named" + } + }, + "username": { + "description": "name that need to be updated", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Updated user", + "name": "updateUser", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "body": { + "description": "Form data of /pet/{petId}/uploadImage", + "type": { + "name": "UploadFileBody", + "type": "named" + } + }, + "petId": { + "description": "ID of pet to update", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "uploads an image", + "name": "uploadFile", + "result_type": { + "name": "ApiResponse", + "type": "named" + } + } + ], + "scalar_types": { + "Binary": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "bytes" + } + }, + "Boolean": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "boolean" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "OrderStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "placed", + "approved", + "delivered" + ], + "type": "enum" + } + }, + "PetStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "available", + "pending", + "sold" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/petstore3/expected.json b/ndc-rest-schema/openapi/testdata/petstore3/expected.json index 08698da..12801ec 100644 --- a/ndc-rest-schema/openapi/testdata/petstore3/expected.json +++ b/ndc-rest-schema/openapi/testdata/petstore3/expected.json @@ -52,23 +52,239 @@ ], "version": "1.0.19" }, - "collections": [], - "functions": [ - { + "functions": { + "GetInvoices": { "request": { - "url": "/pet/findByStatus", + "url": "/v1/invoices", "method": "get", - "parameters": [ - { + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "collection_method": { + "description": "The collection method of the invoice to retrieve. Either `charge_automatically` or `send_invoice`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "InvoicesCollectionMethod", + "type": "named" + } + }, + "rest": { + "style": "form", + "name": "collection_method", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + }, + "created": { + "description": "Only return invoices that were created during the given date interval.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "style": "deepObject", + "explode": true, + "name": "created", + "in": "query", + "schema": { + "type": null + } + } + }, + "customer": { + "description": "Only return invoices for the customer specified by this customer ID.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "style": "form", + "name": "customer", + "in": "query", + "schema": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + }, + "due_date": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "style": "deepObject", + "explode": true, + "name": "due_date", + "in": "query", + "schema": { + "type": null + } + } + }, + "ending_before": { + "description": "A cursor for use in pagination. `ending_before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with `obj_bar`, your subsequent call can include `ending_before=obj_bar` in order to fetch the previous page of the list.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "style": "form", + "name": "ending_before", + "in": "query", + "schema": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + }, + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + }, + "rest": { + "style": "deepObject", "explode": true, + "name": "expand", + "in": "query", + "schema": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + } + }, + "limit": { + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + }, + "rest": { + "style": "form", + "name": "limit", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } + } + }, + "starting_after": { + "description": "A cursor for use in pagination. `starting_after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with `obj_foo`, your subsequent call can include `starting_after=obj_foo` in order to fetch the next page of the list.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "style": "form", + "name": "starting_after", + "in": "query", + "schema": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + }, + "status": { + "description": "The status of the invoice, one of `draft`, `open`, `paid`, `uncollectible`, or `void`. [Learn more](https://stripe.com/docs/billing/invoices/workflow#workflow-overview)", + "type": { + "type": "nullable", + "underlying_type": { + "name": "InvoicesStatus", + "type": "named" + } + }, + "rest": { + "style": "form", "name": "status", "in": "query", "schema": { - "type": "PetStatus", - "nullable": true + "type": [ + "string" + ] } } - ], + }, + "subscription": { + "description": "Only return invoices for the subscription specified by this subscription ID.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "style": "form", + "name": "subscription", + "in": "query", + "schema": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + } + }, + "description": "You can list all invoices, or list the invoices for a specific customer. The invoices are returned sorted by creation date, with the most recently created invoices appearing first.", + "name": "GetInvoices", + "result_type": { + "name": "GetInvoicesResult", + "type": "named" + } + }, + "findPetsByStatus": { + "request": { + "url": "/pet/findByStatus", + "method": "get", "security": [ { "petstore_auth": [ @@ -90,6 +306,16 @@ "name": "PetStatus", "type": "named" } + }, + "rest": { + "explode": true, + "name": "status", + "in": "query", + "schema": { + "type": [ + "string" + ] + } } } }, @@ -103,24 +329,10 @@ "type": "array" } }, - { + "findPetsByTags": { "request": { "url": "/pet/findByTags", "method": "get", - "parameters": [ - { - "explode": true, - "name": "tags", - "in": "query", - "schema": { - "type": "array", - "nullable": true, - "items": { - "type": "String" - } - } - } - ], "security": [ { "petstore_auth": [ @@ -145,6 +357,21 @@ }, "type": "array" } + }, + "rest": { + "explode": true, + "name": "tags", + "in": "query", + "schema": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } + } } } }, @@ -158,51 +385,7 @@ "type": "array" } }, - { - "request": { - "url": "/pet/{petId}", - "method": "get", - "parameters": [ - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], - "security": [ - { - "api_key": [] - }, - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], - "response": { - "contentType": "application/json" - } - }, - "arguments": { - "petId": { - "description": "ID of pet to return", - "type": { - "name": "Int64", - "type": "named" - } - } - }, - "description": "Find pet by ID", - "name": "getPetById", - "result_type": { - "name": "Pet", - "type": "named" - } - }, - { + "getInventory": { "request": { "url": "/store/inventory", "method": "get", @@ -223,19 +406,10 @@ "type": "named" } }, - { + "getOrderById": { "request": { "url": "/store/order/{orderId}", "method": "get", - "parameters": [ - { - "name": "orderId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], "response": { "contentType": "application/json" } @@ -246,6 +420,16 @@ "type": { "name": "Int64", "type": "named" + }, + "rest": { + "name": "orderId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "format": "int64" + } } } }, @@ -256,26 +440,19 @@ "type": "named" } }, - { + "getPetById": { "request": { - "url": "/user/login", + "url": "/pet/{petId}", "method": "get", - "parameters": [ + "security": [ { - "name": "password", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } + "api_key": [] }, { - "name": "username", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } + "petstore_auth": [ + "write:pets", + "read:pets" + ] } ], "response": { @@ -283,47 +460,51 @@ } }, "arguments": { - "password": { - "description": "The password for login in clear text", + "petId": { + "description": "ID of pet to return", "type": { - "type": "nullable", - "underlying_type": { - "name": "String", - "type": "named" - } - } - }, - "username": { - "description": "The user name for login", - "type": { - "type": "nullable", - "underlying_type": { - "name": "String", - "type": "named" + "name": "Int64", + "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "format": "int64" } } } }, - "description": "Logs user into the system", - "name": "loginUser", + "description": "Find pet by ID", + "name": "getPetById", "result_type": { - "name": "String", + "name": "Pet", + "type": "named" + } + }, + "getSnake": { + "request": { + "url": "/snake", + "method": "get", + "response": { + "contentType": "application/json" + } + }, + "arguments": {}, + "description": "Get snake object", + "name": "getSnake", + "result_type": { + "name": "SnakeObject", "type": "named" } }, - { + "getUserByName": { "request": { "url": "/user/{username}", "method": "get", - "parameters": [ - { - "name": "username", - "in": "path", - "schema": { - "type": "String" - } - } - ], "response": { "contentType": "application/json" } @@ -334,6 +515,15 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "username", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, @@ -344,23 +534,62 @@ "type": "named" } }, - { + "loginUser": { "request": { - "url": "/snake", + "url": "/user/login", "method": "get", "response": { "contentType": "application/json" } }, - "arguments": {}, - "description": "Get snake object", - "name": "getSnake", + "arguments": { + "password": { + "description": "The password for login in clear text", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "name": "password", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + }, + "username": { + "description": "The user name for login", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "name": "username", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + } + }, + "description": "Logs user into the system", + "name": "loginUser", "result_type": { - "name": "SnakeObject", + "name": "String", "type": "named" } } - ], + }, "object_types": { "Address": { "fields": { @@ -371,6 +600,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "state": { @@ -380,6 +614,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "street": { @@ -389,6 +628,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "zip": { @@ -398,6 +642,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -411,6 +660,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "message": { @@ -420,6 +675,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "type": { @@ -429,6 +689,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -442,6 +707,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { @@ -451,6 +722,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -464,6 +740,53 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] + } + } + } + }, + "GetInvoicesResult": { + "fields": { + "has_more": { + "description": "True if this list has another page of items after this one that can be fetched.", + "type": { + "name": "Boolean", + "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] + } + }, + "object": { + "description": "String representing the object's type. Objects of the same type share the same value. Always has the value `list`.", + "type": { + "name": "InvoicesObject", + "type": "named" + }, + "rest": { + "type": [ + "string" + ] + } + }, + "url": { + "description": "The URL where this list can be accessed.", + "type": { + "name": "String", + "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "pattern": "^/v1/invoices", + "maxLength": 5000 } } } @@ -477,6 +800,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "id": { @@ -486,6 +814,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "petId": { @@ -495,6 +829,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "quantity": { @@ -504,6 +844,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "shipDate": { @@ -513,6 +859,12 @@ "name": "TimestampTZ", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "date-time" } }, "status": { @@ -523,6 +875,11 @@ "name": "OrderStatus", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -536,6 +893,11 @@ "name": "Category", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -545,12 +907,23 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "photoUrls": { @@ -560,6 +933,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "status": { @@ -570,6 +953,11 @@ "name": "PetStatus", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "tags": { @@ -582,6 +970,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } } } @@ -596,6 +989,11 @@ "name": "PostCheckoutSessionsBodyAfterExpiration", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "allow_promotion_codes": { @@ -606,6 +1004,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "automatic_tax": { @@ -616,6 +1019,11 @@ "name": "PostCheckoutSessionsBodyAutomaticTax", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "billing_address_collection": { @@ -626,6 +1034,11 @@ "name": "CheckoutBillingAddressCollection", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "cancel_url": { @@ -636,6 +1049,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "client_reference_id": { @@ -646,6 +1065,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 200 } }, "consent_collection": { @@ -656,6 +1081,11 @@ "name": "PostCheckoutSessionsBodyConsentCollection", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "currency": { @@ -666,6 +1096,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "custom_fields": { @@ -679,6 +1114,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } }, "custom_text": { @@ -689,6 +1134,11 @@ "name": "PostCheckoutSessionsBodyCustomText", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "customer": { @@ -699,6 +1149,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "customer_creation": { @@ -709,6 +1165,11 @@ "name": "CheckoutCustomerCreation", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "customer_email": { @@ -719,6 +1180,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "customer_update": { @@ -729,6 +1195,11 @@ "name": "PostCheckoutSessionsBodyCustomerUpdate", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "discounts": { @@ -742,6 +1213,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } }, "expand": { @@ -755,6 +1236,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } }, "expires_at": { @@ -765,6 +1257,12 @@ "name": "UnixTime", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } }, "invoice_creation": { @@ -775,6 +1273,11 @@ "name": "PostCheckoutSessionsBodyInvoiceCreation", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "line_items": { @@ -788,6 +1291,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } }, "locale": { @@ -798,6 +1311,11 @@ "name": "CheckoutLocale", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "metadata": { @@ -808,6 +1326,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "mode": { @@ -818,6 +1341,11 @@ "name": "CheckoutMode", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "payment_intent_data": { @@ -828,6 +1356,11 @@ "name": "PostCheckoutSessionsBodyPaymentIntentData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "payment_method_collection": { @@ -838,6 +1371,11 @@ "name": "CheckoutPaymentMethodCollection", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "payment_method_configuration": { @@ -848,6 +1386,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 100 } }, "payment_method_options": { @@ -858,6 +1402,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptions", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "payment_method_types": { @@ -871,6 +1420,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "phone_number_collection": { @@ -881,6 +1440,11 @@ "name": "PostCheckoutSessionsBodyPhoneNumberCollection", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "redirect_on_completion": { @@ -891,6 +1455,11 @@ "name": "CheckoutRedirectOnCompletion", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "return_url": { @@ -901,6 +1470,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "setup_intent_data": { @@ -911,6 +1486,11 @@ "name": "PostCheckoutSessionsBodySetupIntentData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "shipping_address_collection": { @@ -921,6 +1501,11 @@ "name": "PostCheckoutSessionsBodyShippingAddressCollection", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "shipping_options": { @@ -934,6 +1519,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } }, "submit_type": { @@ -944,6 +1539,11 @@ "name": "CheckoutSubmitType", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "subscription_data": { @@ -954,6 +1554,11 @@ "name": "PostCheckoutSessionsBodySubscriptionData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "success_url": { @@ -964,6 +1569,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "tax_id_collection": { @@ -974,6 +1585,11 @@ "name": "PostCheckoutSessionsBodyTaxIdCollection", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "ui_mode": { @@ -984,6 +1600,11 @@ "name": "CheckoutUiMode", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -998,6 +1619,11 @@ "name": "PostCheckoutSessionsBodyAfterExpirationRecovery", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -1011,12 +1637,22 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "enabled": { "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } } } @@ -1028,6 +1664,11 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } }, "liability": { @@ -1037,6 +1678,11 @@ "name": "PostCheckoutSessionsBodyAutomaticTaxLiability", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -1050,12 +1696,22 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "type": { "type": { "name": "CheckoutType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1070,6 +1726,11 @@ "name": "PostCheckoutSessionsBodyConsentCollectionPaymentMethodReuseAgreement", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "promotions": { @@ -1079,6 +1740,11 @@ "name": "CheckoutPromotions", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "terms_of_service": { @@ -1088,6 +1754,11 @@ "name": "CheckoutTermsOfService", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1098,6 +1769,11 @@ "type": { "name": "CheckoutPosition", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1111,18 +1787,34 @@ "name": "PostCheckoutSessionsBodyCustomFieldsDropdown", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "key": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 200 } }, "label": { "type": { "name": "PostCheckoutSessionsBodyCustomFieldsLabel", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "numeric": { @@ -1132,6 +1824,11 @@ "name": "PostCheckoutSessionsBodyCustomFieldsNumeric", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "optional": { @@ -1141,6 +1838,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "text": { @@ -1150,13 +1852,23 @@ "name": "PostCheckoutSessionsBodyCustomFieldsText", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "type": { "type": { "name": "PostCheckoutSessionsBodyCustomFieldsType", "type": "named" - } + }, + "rest": { + "type": [ + "string" + ] + } } } }, @@ -1169,6 +1881,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } } } @@ -1179,12 +1901,24 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 100 } }, "value": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 100 } } } @@ -1195,12 +1929,23 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 50 } }, "type": { "type": { "name": "PostCheckoutSessionsBodyCustomFieldsLabelType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1214,6 +1959,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "minimum_length": { @@ -1223,6 +1973,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -1236,6 +1991,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "minimum_length": { @@ -1245,6 +2005,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -1259,6 +2024,11 @@ "name": "PostCheckoutSessionsBodyCustomTextAfterSubmit", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "shipping_address": { @@ -1268,6 +2038,11 @@ "name": "PostCheckoutSessionsBodyCustomTextShippingAddress", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "submit": { @@ -1277,6 +2052,11 @@ "name": "PostCheckoutSessionsBodyCustomTextSubmit", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "terms_of_service_acceptance": { @@ -1286,6 +2066,11 @@ "name": "PostCheckoutSessionsBodyCustomTextTermsOfServiceAcceptance", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -1296,6 +2081,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1200 } } } @@ -1306,6 +2097,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1200 } } } @@ -1316,6 +2113,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1200 } } } @@ -1326,6 +2129,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1200 } } } @@ -1340,6 +2149,11 @@ "name": "CheckoutAddress", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "name": { @@ -1349,6 +2163,11 @@ "name": "CheckoutName", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "shipping": { @@ -1358,6 +2177,11 @@ "name": "CheckoutShipping", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1371,6 +2195,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "promotion_code": { @@ -1380,6 +2210,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } } } @@ -1391,6 +2227,11 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } }, "invoice_data": { @@ -1400,6 +2241,11 @@ "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -1416,6 +2262,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } }, "custom_fields": { @@ -1428,6 +2285,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "object" + ] + } } }, "description": { @@ -1437,6 +2304,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1500 } }, "footer": { @@ -1446,6 +2319,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "issuer": { @@ -1455,6 +2334,11 @@ "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataIssuer", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "metadata": { @@ -1464,6 +2348,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "rendering_options": { @@ -1473,6 +2362,11 @@ "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataRenderingOptions", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -1483,12 +2377,24 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 40 } }, "value": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 140 } } } @@ -1502,12 +2408,22 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "type": { "type": { "name": "CheckoutType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1521,6 +2437,11 @@ "name": "CheckoutAmountTaxDisplay", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1534,6 +2455,11 @@ "name": "PostCheckoutSessionsBodyLineItemsAdjustableQuantity", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "dynamic_tax_rates": { @@ -1546,6 +2472,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } }, "price": { @@ -1555,6 +2492,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "price_data": { @@ -1564,6 +2507,11 @@ "name": "PostCheckoutSessionsBodyLineItemsPriceData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "quantity": { @@ -1573,6 +2521,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "tax_rates": { @@ -1585,6 +2538,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } } } @@ -1595,6 +2559,11 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } }, "maximum": { @@ -1604,6 +2573,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "minimum": { @@ -1613,6 +2587,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -1623,6 +2602,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "product": { @@ -1632,6 +2616,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "product_data": { @@ -1641,6 +2631,11 @@ "name": "PostCheckoutSessionsBodyLineItemsPriceDataProductData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "recurring": { @@ -1650,6 +2645,11 @@ "name": "PostCheckoutSessionsBodyLineItemsPriceDataRecurring", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "tax_behavior": { @@ -1659,6 +2659,11 @@ "name": "CheckoutTaxBehavior", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "unit_amount": { @@ -1668,6 +2673,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "unit_amount_decimal": { @@ -1677,6 +2687,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "decimal" } } } @@ -1690,6 +2706,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 40000 } }, "images": { @@ -1702,6 +2724,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "metadata": { @@ -1711,12 +2743,23 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "name": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "tax_code": { @@ -1726,6 +2769,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } } } @@ -1736,6 +2785,11 @@ "type": { "name": "CheckoutInterval", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "interval_count": { @@ -1745,6 +2799,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -1759,6 +2818,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "capture_method": { @@ -1768,6 +2832,11 @@ "name": "CheckoutCaptureMethod", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "description": { @@ -1777,6 +2846,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1000 } }, "metadata": { @@ -1786,6 +2861,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "on_behalf_of": { @@ -1795,6 +2875,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "receipt_email": { @@ -1804,6 +2889,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "setup_future_usage": { @@ -1813,6 +2903,11 @@ "name": "CheckoutSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "shipping": { @@ -1822,6 +2917,11 @@ "name": "PostCheckoutSessionsBodyPaymentIntentDataShipping", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "statement_descriptor": { @@ -1831,6 +2931,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 22 } }, "statement_descriptor_suffix": { @@ -1840,6 +2946,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 22 } }, "transfer_data": { @@ -1849,6 +2961,11 @@ "name": "PostCheckoutSessionsBodyPaymentIntentDataTransferData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "transfer_group": { @@ -1858,6 +2975,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1868,6 +2990,11 @@ "type": { "name": "PostCheckoutSessionsBodyPaymentIntentDataShippingAddress", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "carrier": { @@ -1877,12 +3004,24 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "name": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "phone": { @@ -1892,6 +3031,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "tracking_number": { @@ -1901,6 +3046,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } } } @@ -1914,6 +3065,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "country": { @@ -1923,12 +3080,24 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "line1": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "line2": { @@ -1938,6 +3107,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "postal_code": { @@ -1947,6 +3122,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "state": { @@ -1956,6 +3137,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } } } @@ -1969,12 +3156,22 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "destination": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -1989,6 +3186,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebit", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "affirm": { @@ -1998,6 +3200,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAffirm", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "afterpay_clearpay": { @@ -2007,6 +3214,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpay", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "alipay": { @@ -2016,6 +3228,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAlipay", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "au_becs_debit": { @@ -2025,6 +3242,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebit", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "bacs_debit": { @@ -2034,6 +3256,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebit", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "bancontact": { @@ -2043,6 +3270,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBancontact", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "boleto": { @@ -2052,6 +3284,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBoleto", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "card": { @@ -2061,6 +3298,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCard", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "cashapp": { @@ -2070,6 +3312,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCashapp", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "customer_balance": { @@ -2079,6 +3326,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalance", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "eps": { @@ -2088,6 +3340,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsEps", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "fpx": { @@ -2097,6 +3354,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsFpx", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "giropay": { @@ -2106,6 +3368,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGiropay", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "grabpay": { @@ -2115,6 +3382,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpay", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "ideal": { @@ -2124,6 +3396,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsIdeal", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "klarna": { @@ -2133,6 +3410,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKlarna", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "konbini": { @@ -2142,6 +3424,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKonbini", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "link": { @@ -2151,6 +3438,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsLink", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "oxxo": { @@ -2160,6 +3452,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsOxxo", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "p24": { @@ -2169,6 +3466,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsP24", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "paynow": { @@ -2178,6 +3480,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaynow", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "paypal": { @@ -2187,6 +3494,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypal", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "pix": { @@ -2196,6 +3508,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPix", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "revolut_pay": { @@ -2205,6 +3522,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPay", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "sepa_debit": { @@ -2214,6 +3536,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebit", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "sofort": { @@ -2223,6 +3550,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSofort", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "swish": { @@ -2232,6 +3564,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSwish", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "us_bank_account": { @@ -2241,6 +3578,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccount", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "wechat_pay": { @@ -2250,6 +3592,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPay", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -2263,6 +3610,11 @@ "name": "PaymentMethodAffirm", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "currency": { @@ -2272,6 +3624,11 @@ "name": "CheckoutCurrency", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "mandate_options": { @@ -2281,6 +3638,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitMandateOptions", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "setup_future_usage": { @@ -2290,6 +3652,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "verification_method": { @@ -2299,6 +3666,11 @@ "name": "CheckoutVerificationMethod", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2312,6 +3684,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "default_for": { @@ -2324,6 +3701,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "interval_description": { @@ -2333,6 +3720,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 500 } }, "payment_schedule": { @@ -2342,6 +3735,11 @@ "name": "CheckoutPaymentSchedule", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "transaction_type": { @@ -2351,6 +3749,11 @@ "name": "CheckoutTransactionType", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2364,6 +3767,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAffirmSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2377,6 +3785,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpaySetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2390,6 +3803,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAlipaySetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2403,6 +3821,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebitSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2416,6 +3839,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebitSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2429,6 +3857,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBancontactSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2442,6 +3875,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "setup_future_usage": { @@ -2451,6 +3889,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBoletoSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2464,6 +3907,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCardInstallments", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "request_three_d_secure": { @@ -2473,6 +3921,11 @@ "name": "CheckoutRequestThreeDSecure", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "setup_future_usage": { @@ -2482,6 +3935,11 @@ "name": "CheckoutSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "statement_descriptor_suffix_kana": { @@ -2491,6 +3949,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 22 } }, "statement_descriptor_suffix_kanji": { @@ -2500,6 +3964,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 17 } } } @@ -2513,6 +3983,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } } } @@ -2526,6 +4001,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCashappSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2539,6 +4019,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransfer", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "funding_type": { @@ -2548,6 +4033,11 @@ "name": "CheckoutFundingType", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "setup_future_usage": { @@ -2557,6 +4047,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2570,6 +4065,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferEuBankTransfer", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "requested_address_types": { @@ -2582,12 +4082,27 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "type": { "type": { "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2598,6 +4113,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } } } @@ -2611,6 +4132,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsEpsSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2624,6 +4150,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsFpxSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2637,6 +4168,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGiropaySetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2650,6 +4186,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpaySetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2663,6 +4204,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsIdealSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2676,6 +4222,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKlarnaSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2689,6 +4240,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "setup_future_usage": { @@ -2698,6 +4254,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKonbiniSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2711,6 +4272,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsLinkSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2724,6 +4290,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "setup_future_usage": { @@ -2733,6 +4304,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsOxxoSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2746,6 +4322,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsP24SetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "tos_shown_and_accepted": { @@ -2755,6 +4336,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } } } @@ -2768,6 +4354,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaynowSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2781,6 +4372,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalCaptureMethod", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "preferred_locale": { @@ -2790,6 +4386,11 @@ "name": "CheckoutPreferredLocale", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "reference": { @@ -2799,6 +4400,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 127 } }, "risk_correlation_id": { @@ -2808,6 +4415,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 32 } }, "setup_future_usage": { @@ -2817,6 +4430,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2830,6 +4448,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -2843,6 +4466,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPaySetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2856,6 +4484,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebitSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2869,6 +4502,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSofortSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2882,6 +4520,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } } } @@ -2895,6 +4539,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountFinancialConnections", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "setup_future_usage": { @@ -2904,6 +4553,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountSetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "verification_method": { @@ -2913,6 +4567,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountVerificationMethod", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2929,6 +4588,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } }, "prefetch": { @@ -2941,6 +4611,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } } } @@ -2954,12 +4634,23 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "client": { "type": { "name": "CheckoutClient", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "setup_future_usage": { @@ -2969,6 +4660,11 @@ "name": "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPaySetupFutureUsage", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -2980,6 +4676,11 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } } } @@ -2994,6 +4695,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 1000 } }, "metadata": { @@ -3003,6 +4710,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "on_behalf_of": { @@ -3012,6 +4724,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3026,6 +4743,16 @@ "type": "named" }, "type": "array" + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } } } @@ -3039,6 +4766,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "shipping_rate_data": { @@ -3048,6 +4781,11 @@ "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3061,12 +4799,23 @@ "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimate", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "display_name": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 100 } }, "fixed_amount": { @@ -3076,6 +4825,11 @@ "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataFixedAmount", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "metadata": { @@ -3085,6 +4839,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "tax_behavior": { @@ -3094,6 +4853,11 @@ "name": "CheckoutTaxBehavior", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "tax_code": { @@ -3103,6 +4867,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "type": { @@ -3112,6 +4881,11 @@ "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataType", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3125,6 +4899,11 @@ "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimateMaximum", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "minimum": { @@ -3134,6 +4913,11 @@ "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimateMinimum", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3144,12 +4928,22 @@ "type": { "name": "CheckoutUnit", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -3160,12 +4954,22 @@ "type": { "name": "CheckoutUnit", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -3176,12 +4980,22 @@ "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } }, "currency": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "currency_options": { @@ -3191,6 +5005,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3205,6 +5024,11 @@ "name": "Float64", "type": "named" } + }, + "rest": { + "type": [ + "number" + ] } }, "billing_cycle_anchor": { @@ -3214,6 +5038,12 @@ "name": "UnixTime", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } }, "default_tax_rates": { @@ -3226,6 +5056,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } }, "description": { @@ -3235,6 +5076,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 500 } }, "invoice_settings": { @@ -3244,6 +5091,11 @@ "name": "PostCheckoutSessionsBodySubscriptionDataInvoiceSettings", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "metadata": { @@ -3253,6 +5105,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "on_behalf_of": { @@ -3262,6 +5119,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "proration_behavior": { @@ -3271,6 +5133,11 @@ "name": "CheckoutProrationBehavior", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "transfer_data": { @@ -3280,6 +5147,11 @@ "name": "PostCheckoutSessionsBodySubscriptionDataTransferData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "trial_end": { @@ -3289,6 +5161,12 @@ "name": "UnixTime", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } }, "trial_period_days": { @@ -3298,6 +5176,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "trial_settings": { @@ -3307,6 +5190,11 @@ "name": "PostCheckoutSessionsBodySubscriptionDataTrialSettings", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3320,6 +5208,11 @@ "name": "PostCheckoutSessionsBodySubscriptionDataInvoiceSettingsIssuer", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3333,12 +5226,22 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "type": { "type": { "name": "CheckoutType", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3352,12 +5255,22 @@ "name": "Float64", "type": "named" } + }, + "rest": { + "type": [ + "number" + ] } }, "destination": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3368,6 +5281,11 @@ "type": { "name": "PostCheckoutSessionsBodySubscriptionDataTrialSettingsEndBehavior", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3378,6 +5296,11 @@ "type": { "name": "CheckoutMissingPaymentMethod", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3389,6 +5312,145 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] + } + } + } + }, + "PostFilesBody": { + "fields": { + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + }, + "expand_json": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } + } + }, + "file": { + "description": "A file to upload. Make sure that the specifications follow RFC 2388, which defines file transfers for the `multipart/form-data` protocol.", + "type": { + "name": "Binary", + "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "format": "binary" + } + }, + "file_link_data": { + "description": "Optional parameters that automatically create a [file link](https://stripe.com/docs/api#file_links) for the newly created file.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostFilesBodyFileLinkData", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "purpose": { + "description": "The [purpose](https://stripe.com/docs/file-upload#uploading-a-file) of the uploaded file.", + "type": { + "name": "FilesPurpose", + "type": "named" + }, + "rest": { + "type": [ + "string" + ] + } + } + } + }, + "PostFilesBodyFileLinkData": { + "description": "Optional parameters that automatically create a [file link](https://stripe.com/docs/api#file_links) for the newly created file.", + "fields": { + "create": { + "type": { + "name": "Boolean", + "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] + } + }, + "expires_at": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": null } } } @@ -3406,6 +5468,17 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ], + "maxLength": 5000 + } } }, "failure_details": { @@ -3416,6 +5489,11 @@ "name": "PostTestHelpersTreasuryInboundTransfersIdFailBodyFailureDetails", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3430,6 +5508,11 @@ "name": "TestHelpersCode", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3443,6 +5526,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -3452,6 +5540,11 @@ "name": "SnakeObjectId", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -3465,6 +5558,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "id": { @@ -3474,6 +5572,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "petId": { @@ -3483,6 +5587,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "quantity": { @@ -3492,6 +5602,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "shipDate": { @@ -3501,6 +5617,12 @@ "name": "TimestampTZ", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "date-time" } }, "status": { @@ -3511,6 +5633,11 @@ "name": "SnakeObjectIdStatus", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3524,6 +5651,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "name": { @@ -3533,6 +5666,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3545,6 +5683,11 @@ "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } }, "cancelable": { @@ -3552,6 +5695,11 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } }, "context": { @@ -3561,6 +5709,9 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": null } }, "created": { @@ -3568,6 +5719,12 @@ "type": { "name": "UnixTime", "type": "named" + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } }, "currency": { @@ -3575,6 +5732,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "description": { @@ -3585,6 +5747,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "failure_details": { @@ -3595,6 +5763,11 @@ "name": "TreasuryInboundTransfersResourceFailureDetails", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "financial_account": { @@ -3602,6 +5775,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "hosted_regulatory_receipt_url": { @@ -3612,6 +5791,12 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "id": { @@ -3619,6 +5804,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "livemode": { @@ -3626,6 +5817,11 @@ "type": { "name": "Boolean", "type": "named" + }, + "rest": { + "type": [ + "boolean" + ] } }, "metadata": { @@ -3633,6 +5829,11 @@ "type": { "name": "JSON", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "object": { @@ -3640,6 +5841,11 @@ "type": { "name": "TreasuryInboundTransferObject", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "origin_payment_method": { @@ -3647,6 +5853,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "returned": { @@ -3657,6 +5869,11 @@ "name": "Boolean", "type": "named" } + }, + "rest": { + "type": [ + "boolean" + ] } }, "statement_descriptor": { @@ -3664,6 +5881,12 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ], + "maxLength": 5000 } }, "status": { @@ -3671,12 +5894,22 @@ "type": { "name": "TreasuryInboundTransferStatus", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "status_transitions": { "type": { "name": "TreasuryInboundTransfersResourceInboundTransferResourceStatusTransitions", "type": "named" + }, + "rest": { + "type": [ + "object" + ] } }, "transaction": { @@ -3687,6 +5920,9 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": null } } } @@ -3698,6 +5934,11 @@ "type": { "name": "TreasuryInboundTransfersResourceFailureDetailsCode", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } } } @@ -3712,6 +5953,12 @@ "name": "UnixTime", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } }, "failed_at": { @@ -3722,6 +5969,12 @@ "name": "UnixTime", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } }, "succeeded_at": { @@ -3732,6 +5985,12 @@ "name": "UnixTime", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "unix-time" } } } @@ -3745,6 +6004,11 @@ "name": "JSON", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "addresses": { @@ -3757,6 +6021,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "children": { @@ -3769,6 +6038,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "id": { @@ -3778,6 +6057,12 @@ "name": "UUID", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "uuid" } }, "profileImage": { @@ -3787,6 +6072,12 @@ "name": "Binary", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "binary" } } } @@ -3800,6 +6091,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "firstName": { @@ -3809,6 +6105,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -3818,6 +6119,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "lastName": { @@ -3827,6 +6134,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "password": { @@ -3836,6 +6148,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "phone": { @@ -3845,6 +6162,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userStatus": { @@ -3855,6 +6177,12 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int32" } }, "username": { @@ -3864,66 +6192,138 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } } }, - "procedures": [ - { + "procedures": { + "DeleteAccountsAccount": { "request": { - "url": "/pet", - "method": "post", - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], - "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Pet" - } - }, + "url": "/v1/accounts/{account}", + "method": "delete", "response": { "contentType": "application/json" } }, "arguments": { - "body": { - "description": "Request body of POST /pet", + "account": { "type": { - "name": "Pet", + "name": "String", "type": "named" + }, + "rest": { + "style": "simple", + "name": "account", + "in": "path", + "schema": { + "type": [ + "string" + ], + "maxLength": 5000 + } } } }, - "description": "Add a new pet to the store", - "name": "addPet", + "description": "DELETE /v1/accounts/{account}", + "name": "DeleteAccountsAccount", "result_type": { - "name": "Pet", + "name": "TreasuryInboundTransfer", "type": "named" } }, - { + "PostCheckoutSessions": { "request": { - "url": "/pet", - "method": "put", - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], + "url": "/v1/checkout/sessions", + "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Pet" + "contentType": "application/x-www-form-urlencoded", + "encoding": { + "after_expiration": { + "style": "deepObject", + "explode": true + }, + "automatic_tax": { + "style": "deepObject", + "explode": true + }, + "consent_collection": { + "style": "deepObject", + "explode": true + }, + "custom_fields": { + "style": "deepObject", + "explode": true + }, + "custom_text": { + "style": "deepObject", + "explode": true + }, + "customer_update": { + "style": "deepObject", + "explode": true + }, + "discounts": { + "style": "deepObject", + "explode": true + }, + "expand": { + "style": "deepObject", + "explode": true + }, + "invoice_creation": { + "style": "deepObject", + "explode": true + }, + "line_items": { + "style": "deepObject", + "explode": true + }, + "metadata": { + "style": "deepObject", + "explode": true + }, + "payment_intent_data": { + "style": "deepObject", + "explode": true + }, + "payment_method_options": { + "style": "deepObject", + "explode": true + }, + "payment_method_types": { + "style": "deepObject", + "explode": true + }, + "phone_number_collection": { + "style": "deepObject", + "explode": true + }, + "setup_intent_data": { + "style": "deepObject", + "explode": true + }, + "shipping_address_collection": { + "style": "deepObject", + "explode": true + }, + "shipping_options": { + "style": "deepObject", + "explode": true + }, + "subscription_data": { + "style": "deepObject", + "explode": true + }, + "tax_id_collection": { + "style": "deepObject", + "explode": true + } } }, "response": { @@ -3932,182 +6332,168 @@ }, "arguments": { "body": { - "description": "Request body of PUT /pet", + "description": "Request body of POST /v1/checkout/sessions", "type": { - "name": "Pet", - "type": "named" + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBody", + "type": "named" + } + }, + "rest": { + "in": "body" } } }, - "description": "Update an existing pet", - "name": "updatePet", + "description": "Creates a Session object.", + "name": "PostCheckoutSessions", "result_type": { - "name": "Pet", + "name": "Order", "type": "named" } }, - { + "PostFiles": { "request": { - "url": "/pet/{petId}", + "url": "/v1/files", "method": "post", - "parameters": [ - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - }, - { - "name": "name", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } - }, + "servers": [ { - "name": "status", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } + "url": "{{PET_STORE_SERVER_URL:-https://files.stripe.com/}}" } ], - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "requestBody": { + "contentType": "multipart/form-data", + "encoding": { + "expand": { + "style": "deepObject", + "explode": true + }, + "expand_json": { + "contentType": [ + "application/json" + ] + }, + "file": { + "headers": { + "X-Rate-Limit-Limit": { + "explode": false, + "argumentName": "headerXRateLimitLimit", + "schema": { + "type": [ + "integer" + ] + } + } + } + }, + "file_link_data": { + "style": "deepObject", + "explode": true + } } - ], + }, "response": { "contentType": "application/json" } }, "arguments": { - "name": { - "description": "Name of pet that needs to be updated", + "body": { + "description": "Request body of POST /v1/files", "type": { - "type": "nullable", - "underlying_type": { - "name": "String", - "type": "named" - } - } - }, - "petId": { - "description": "ID of pet that needs to be updated", - "type": { - "name": "Int64", + "name": "PostFilesBody", "type": "named" + }, + "rest": { + "in": "body" } }, - "status": { - "description": "Status of pet that needs to be updated", + "headerXRateLimitLimit": { "type": { - "type": "nullable", - "underlying_type": { - "name": "String", - "type": "named" + "name": "Int32", + "type": "named" + }, + "rest": { + "explode": false, + "argumentName": "headerXRateLimitLimit", + "schema": { + "type": [ + "integer" + ] } } } }, - "description": "Updates a pet in the store with form data", - "name": "updatePetWithForm", + "description": "POST /v1/files", + "name": "PostFiles", "result_type": { - "type": "nullable", - "underlying_type": { - "name": "Boolean", - "type": "named" - } + "name": "ApiResponse", + "type": "named" } }, - { + "PostTestHelpersTreasuryInboundTransfersIdFail": { "request": { - "url": "/pet/{petId}", - "method": "delete", - "parameters": [ - { - "name": "api_key", - "in": "header", - "schema": { - "type": "String", - "nullable": true - } - }, - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" + "url": "/test_helpers/treasury/inbound_transfers/{id}/fail", + "method": "post", + "requestBody": { + "contentType": "application/x-www-form-urlencoded", + "encoding": { + "expand": { + "style": "deepObject", + "explode": true + }, + "failure_details": { + "style": "deepObject", + "explode": true } } - ], - "security": [ - { - "petstore_auth": [ - "write:pets", - "read:pets" - ] - } - ], + }, "response": { "contentType": "application/json" } }, "arguments": { - "api_key": { + "body": { + "description": "Request body of POST /test_helpers/treasury/inbound_transfers/{id}/fail", "type": { "type": "nullable", "underlying_type": { - "name": "String", + "name": "PostTestHelpersTreasuryInboundTransfersIdFailBody", "type": "named" } + }, + "rest": { + "in": "body" } }, - "petId": { - "description": "Pet id to delete", + "id": { "type": { - "name": "Int64", + "name": "String", "type": "named" + }, + "rest": { + "style": "simple", + "name": "id", + "in": "path", + "schema": { + "type": [ + "string" + ], + "maxLength": 5000 + } } } }, - "description": "Deletes a pet", - "name": "deletePet", + "description": "Transitions a test mode created InboundTransfer to the failed status. The InboundTransfer must already be in the processing state.", + "name": "PostTestHelpersTreasuryInboundTransfersIdFail", "result_type": { - "type": "nullable", - "underlying_type": { - "name": "Boolean", - "type": "named" - } + "name": "TreasuryInboundTransfer", + "type": "named" } }, - { + "addPet": { "request": { - "url": "/pet/{petId}/uploadImage", + "url": "/pet", "method": "post", - "parameters": [ - { - "name": "petId", - "in": "path", - "schema": { - "type": "Int64" - } - }, - { - "name": "additionalMetadata", - "in": "query", - "schema": { - "type": "String", - "nullable": true - } - } - ], "security": [ { "petstore_auth": [ @@ -4117,104 +6503,53 @@ } ], "requestBody": { - "contentType": "application/octet-stream", - "schema": { - "type": "Binary", - "nullable": true - } + "contentType": "application/json" }, "response": { "contentType": "application/json" } }, "arguments": { - "additionalMetadata": { - "description": "Additional Metadata", - "type": { - "type": "nullable", - "underlying_type": { - "name": "String", - "type": "named" - } - } - }, "body": { - "description": "Request body of POST /pet/{petId}/uploadImage", - "type": { - "type": "nullable", - "underlying_type": { - "name": "Binary", - "type": "named" - } - } - }, - "petId": { - "description": "ID of pet to update", + "description": "Request body of POST /pet", "type": { - "name": "Int64", + "name": "Pet", "type": "named" + }, + "rest": { + "in": "body" } } }, - "description": "uploads an image", - "name": "uploadFile", + "description": "Add a new pet to the store", + "name": "addPet", "result_type": { - "name": "ApiResponse", + "name": "Pet", "type": "named" } }, - { + "addSnake": { "request": { - "url": "/pet/multipart", + "url": "/snake", + "method": "post", + "response": { + "contentType": "application/json" + } + }, + "arguments": {}, + "description": "Add snake object", + "name": "addSnake", + "result_type": { + "name": "SnakeObject", + "type": "named" + } + }, + "createFineTuningJob": { + "request": { + "url": "/fine_tuning/jobs", "method": "post", "requestBody": { - "contentType": "multipart/form-data", - "schema": { - "type": "object", - "nullable": true, - "properties": { - "address": { - "type": "JSON", - "nullable": true - }, - "addresses": { - "type": "array", - "nullable": true - }, - "children": { - "type": "array", - "nullable": true, - "items": { - "type": "String" - } - }, - "id": { - "type": "UUID", - "nullable": true - }, - "profileImage": { - "type": "Binary", - "nullable": true - } - } - }, - "encoding": { - "profileImage": { - "contentType": [ - "image/png", - "image/jpeg" - ], - "headers": { - "X-Rate-Limit-Limit": { - "explode": false, - "argumentName": "headerXRateLimitLimit", - "schema": { - "type": "Int32" - } - } - } - } - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -4222,39 +6557,29 @@ }, "arguments": { "body": { - "description": "Request body of POST /pet/multipart", - "type": { - "type": "nullable", - "underlying_type": { - "name": "UploadPetMultipartBody", - "type": "named" - } - } - }, - "headerXRateLimitLimit": { - "description": "The number of allowed requests in the current period", + "description": "Request body of POST /fine_tuning/jobs", "type": { - "name": "Int32", + "name": "CreateFineTuningJobRequest", "type": "named" + }, + "rest": { + "in": "body" } } }, - "name": "uploadPetMultipart", + "description": "POST /fine_tuning/jobs", + "name": "createFineTuningJob", "result_type": { - "name": "ApiResponse", + "name": "CreateFineTuningJobRequest", "type": "named" } }, - { + "createUsersWithListInput": { "request": { - "url": "/store/order", + "url": "/user/createWithList", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Order", - "nullable": true - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -4262,36 +6587,33 @@ }, "arguments": { "body": { - "description": "Request body of POST /store/order", + "description": "Request body of POST /user/createWithList", "type": { "type": "nullable", "underlying_type": { - "name": "Order", - "type": "named" + "element_type": { + "name": "User", + "type": "named" + }, + "type": "array" } + }, + "rest": { + "in": "body" } } }, - "description": "Place an order for a pet", - "name": "placeOrder", + "description": "Creates list of users with given input array", + "name": "createUsersWithListInput", "result_type": { - "name": "Order", + "name": "User", "type": "named" } }, - { + "deleteOrder": { "request": { "url": "/store/order/{orderId}", "method": "delete", - "parameters": [ - { - "name": "orderId", - "in": "path", - "schema": { - "type": "Int64" - } - } - ], "response": { "contentType": "application/json" } @@ -4302,6 +6624,16 @@ "type": { "name": "Int64", "type": "named" + }, + "rest": { + "name": "orderId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "format": "int64" + } } } }, @@ -4315,56 +6647,73 @@ } } }, - { + "deletePet": { "request": { - "url": "/user/createWithList", - "method": "post", - "requestBody": { - "contentType": "application/json", - "schema": { - "type": "array", - "nullable": true + "url": "/pet/{petId}", + "method": "delete", + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] } - }, + ], "response": { "contentType": "application/json" } }, "arguments": { - "body": { - "description": "Request body of POST /user/createWithList", + "api_key": { "type": { "type": "nullable", "underlying_type": { - "element_type": { - "name": "User", - "type": "named" - }, - "type": "array" + "name": "String", + "type": "named" + } + }, + "rest": { + "name": "api_key", + "in": "header", + "schema": { + "type": [ + "string" + ] + } + } + }, + "petId": { + "description": "Pet id to delete", + "type": { + "name": "Int64", + "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "format": "int64" } } } }, - "description": "Creates list of users with given input array", - "name": "createUsersWithListInput", + "description": "Deletes a pet", + "name": "deletePet", "result_type": { - "name": "User", - "type": "named" + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } } }, - { + "deleteUser": { "request": { "url": "/user/{username}", "method": "delete", - "parameters": [ - { - "name": "username", - "in": "path", - "schema": { - "type": "String" - } - } - ], "response": { "contentType": "application/json" } @@ -4375,6 +6724,15 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "username", + "in": "path", + "schema": { + "type": [ + "string" + ] + } } } }, @@ -4388,73 +6746,12 @@ } } }, - { - "request": { - "url": "/snake", - "method": "post", - "response": { - "contentType": "application/json" - } - }, - "arguments": {}, - "description": "Add snake object", - "name": "addSnake", - "result_type": { - "name": "SnakeObject", - "type": "named" - } - }, - { + "placeOrder": { "request": { - "url": "/test_helpers/treasury/inbound_transfers/{id}/fail", + "url": "/store/order", "method": "post", - "parameters": [ - { - "style": "simple", - "name": "id", - "in": "path", - "schema": { - "type": "String", - "maxLength": 5000 - } - } - ], "requestBody": { - "contentType": "application/x-www-form-urlencoded", - "schema": { - "type": "object", - "nullable": true, - "properties": { - "expand": { - "type": "array", - "nullable": true, - "items": { - "type": "String", - "maxLength": 5000 - } - }, - "failure_details": { - "type": "object", - "nullable": true, - "properties": { - "code": { - "type": "TestHelpersCode", - "nullable": true - } - } - } - } - }, - "encoding": { - "expand": { - "style": "deepObject", - "explode": true - }, - "failure_details": { - "style": "deepObject", - "explode": true - } - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -4462,1529 +6759,248 @@ }, "arguments": { "body": { - "description": "Request body of POST /test_helpers/treasury/inbound_transfers/{id}/fail", + "description": "Request body of POST /store/order", "type": { "type": "nullable", "underlying_type": { - "name": "PostTestHelpersTreasuryInboundTransfersIdFailBody", + "name": "Order", "type": "named" } - } - }, - "id": { - "type": { - "name": "String", - "type": "named" + }, + "rest": { + "in": "body" } } }, - "name": "PostTestHelpersTreasuryInboundTransfersIdFail", + "description": "Place an order for a pet", + "name": "placeOrder", "result_type": { - "name": "TreasuryInboundTransfer", + "name": "Order", "type": "named" } }, - { + "updatePet": { "request": { - "url": "/v1/accounts/{account}", - "method": "delete", - "parameters": [ + "url": "/pet", + "method": "put", + "security": [ { - "style": "simple", - "name": "account", - "in": "path", - "schema": { - "type": "String", - "maxLength": 5000 - } + "petstore_auth": [ + "write:pets", + "read:pets" + ] } ], + "requestBody": { + "contentType": "application/json" + }, "response": { "contentType": "application/json" } }, "arguments": { - "account": { + "body": { + "description": "Request body of PUT /pet", "type": { - "name": "String", + "name": "Pet", "type": "named" + }, + "rest": { + "in": "body" } } }, - "name": "DeleteAccountsAccount", + "description": "Update an existing pet", + "name": "updatePet", "result_type": { - "name": "TreasuryInboundTransfer", + "name": "Pet", "type": "named" } }, - { + "updatePetWithForm": { "request": { - "url": "/v1/checkout/sessions", + "url": "/pet/{petId}", "method": "post", - "requestBody": { - "contentType": "application/x-www-form-urlencoded", - "schema": { - "type": "object", - "nullable": true, - "properties": { - "after_expiration": { - "type": "object", - "nullable": true, - "properties": { - "recovery": { - "type": "object", - "nullable": true, - "properties": { - "allow_promotion_codes": { - "type": "Boolean", - "nullable": true - }, - "enabled": { - "type": "Boolean" - } - } - } - } - }, - "allow_promotion_codes": { - "type": "Boolean", - "nullable": true - }, - "automatic_tax": { - "type": "object", - "nullable": true, - "properties": { - "enabled": { - "type": "Boolean" - }, - "liability": { - "type": "object", - "nullable": true, - "properties": { - "account": { - "type": "String", - "nullable": true - }, - "type": { - "type": "CheckoutType" - } - } - } - } - }, - "billing_address_collection": { - "type": "CheckoutBillingAddressCollection", - "nullable": true - }, - "cancel_url": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "client_reference_id": { - "type": "String", - "nullable": true, - "maxLength": 200 - }, - "consent_collection": { - "type": "object", - "nullable": true, - "properties": { - "payment_method_reuse_agreement": { - "type": "object", - "nullable": true, - "properties": { - "position": { - "type": "CheckoutPosition" - } - } - }, - "promotions": { - "type": "CheckoutPromotions", - "nullable": true - }, - "terms_of_service": { - "type": "CheckoutTermsOfService", - "nullable": true - } - } - }, - "currency": { - "type": "String", - "nullable": true - }, - "custom_fields": { - "type": "array", - "nullable": true, - "items": { - "type": "object", - "properties": { - "dropdown": { - "type": "object", - "nullable": true, - "properties": { - "options": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "String", - "maxLength": 100 - }, - "value": { - "type": "String", - "maxLength": 100 - } - } - } - } - } - }, - "key": { - "type": "String", - "maxLength": 200 - }, - "label": { - "type": "object", - "properties": { - "custom": { - "type": "String", - "maxLength": 50 - }, - "type": { - "type": "PostCheckoutSessionsBodyCustomFieldsLabelType" - } - } - }, - "numeric": { - "type": "object", - "nullable": true, - "properties": { - "maximum_length": { - "type": "Int32", - "nullable": true - }, - "minimum_length": { - "type": "Int32", - "nullable": true - } - } - }, - "optional": { - "type": "Boolean", - "nullable": true - }, - "text": { - "type": "object", - "nullable": true, - "properties": { - "maximum_length": { - "type": "Int32", - "nullable": true - }, - "minimum_length": { - "type": "Int32", - "nullable": true - } - } - }, - "type": { - "type": "PostCheckoutSessionsBodyCustomFieldsType" - } - } - } - }, - "custom_text": { - "type": "object", - "nullable": true, - "properties": { - "after_submit": { - "type": "object", - "nullable": true, - "properties": { - "message": { - "type": "String", - "maxLength": 1200 - } - } - }, - "shipping_address": { - "type": "object", - "nullable": true, - "properties": { - "message": { - "type": "String", - "maxLength": 1200 - } - } - }, - "submit": { - "type": "object", - "nullable": true, - "properties": { - "message": { - "type": "String", - "maxLength": 1200 - } - } - }, - "terms_of_service_acceptance": { - "type": "object", - "nullable": true, - "properties": { - "message": { - "type": "String", - "maxLength": 1200 - } - } - } - } - }, - "customer": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "customer_creation": { - "type": "CheckoutCustomerCreation", - "nullable": true - }, - "customer_email": { - "type": "String", - "nullable": true - }, - "customer_update": { - "type": "object", - "nullable": true, - "properties": { - "address": { - "type": "CheckoutAddress", - "nullable": true - }, - "name": { - "type": "CheckoutName", - "nullable": true - }, - "shipping": { - "type": "CheckoutShipping", - "nullable": true - } - } - }, - "discounts": { - "type": "array", - "nullable": true, - "items": { - "type": "object", - "properties": { - "coupon": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "promotion_code": { - "type": "String", - "nullable": true, - "maxLength": 5000 - } - } - } - }, - "expand": { - "type": "array", - "nullable": true, - "items": { - "type": "String", - "maxLength": 5000 - } - }, - "expires_at": { - "type": "UnixTime", - "nullable": true - }, - "invoice_creation": { - "type": "object", - "nullable": true, - "properties": { - "enabled": { - "type": "Boolean" - }, - "invoice_data": { - "type": "object", - "nullable": true, - "properties": { - "account_tax_ids": { - "type": "array", - "nullable": true, - "items": { - "type": "String", - "maxLength": 5000 - } - }, - "custom_fields": { - "type": "array", - "nullable": true, - "items": { - "type": "object", - "properties": { - "name": { - "type": "String", - "maxLength": 40 - }, - "value": { - "type": "String", - "maxLength": 140 - } - } - } - }, - "description": { - "type": "String", - "nullable": true, - "maxLength": 1500 - }, - "footer": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "issuer": { - "type": "object", - "nullable": true, - "properties": { - "account": { - "type": "String", - "nullable": true - }, - "type": { - "type": "CheckoutType" - } - } - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "rendering_options": { - "type": "object", - "nullable": true, - "properties": { - "amount_tax_display": { - "type": "CheckoutAmountTaxDisplay", - "nullable": true - } - } - } - } - } - } - }, - "line_items": { - "type": "array", - "nullable": true, - "items": { - "type": "object", - "properties": { - "adjustable_quantity": { - "type": "object", - "nullable": true, - "properties": { - "enabled": { - "type": "Boolean" - }, - "maximum": { - "type": "Int32", - "nullable": true - }, - "minimum": { - "type": "Int32", - "nullable": true - } - } - }, - "dynamic_tax_rates": { - "type": "array", - "nullable": true, - "items": { - "type": "String", - "maxLength": 5000 - } - }, - "price": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "price_data": { - "type": "object", - "nullable": true, - "properties": { - "currency": { - "type": "String" - }, - "product": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "product_data": { - "type": "object", - "nullable": true, - "properties": { - "description": { - "type": "String", - "nullable": true, - "maxLength": 40000 - }, - "images": { - "type": "array", - "nullable": true, - "items": { - "type": "String" - } - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "name": { - "type": "String", - "maxLength": 5000 - }, - "tax_code": { - "type": "String", - "nullable": true, - "maxLength": 5000 - } - } - }, - "recurring": { - "type": "object", - "nullable": true, - "properties": { - "interval": { - "type": "CheckoutInterval" - }, - "interval_count": { - "type": "Int32", - "nullable": true - } - } - }, - "tax_behavior": { - "type": "CheckoutTaxBehavior", - "nullable": true - }, - "unit_amount": { - "type": "Int32", - "nullable": true - }, - "unit_amount_decimal": { - "type": "String", - "nullable": true - } - } - }, - "quantity": { - "type": "Int32", - "nullable": true - }, - "tax_rates": { - "type": "array", - "nullable": true, - "items": { - "type": "String", - "maxLength": 5000 - } - } - } - } - }, - "locale": { - "type": "CheckoutLocale", - "nullable": true - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "mode": { - "type": "CheckoutMode", - "nullable": true - }, - "payment_intent_data": { - "type": "object", - "nullable": true, - "properties": { - "application_fee_amount": { - "type": "Int32", - "nullable": true - }, - "capture_method": { - "type": "CheckoutCaptureMethod", - "nullable": true - }, - "description": { - "type": "String", - "nullable": true, - "maxLength": 1000 - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "on_behalf_of": { - "type": "String", - "nullable": true - }, - "receipt_email": { - "type": "String", - "nullable": true - }, - "setup_future_usage": { - "type": "CheckoutSetupFutureUsage", - "nullable": true - }, - "shipping": { - "type": "object", - "nullable": true, - "properties": { - "address": { - "type": "object", - "properties": { - "city": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "country": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "line1": { - "type": "String", - "maxLength": 5000 - }, - "line2": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "postal_code": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "state": { - "type": "String", - "nullable": true, - "maxLength": 5000 - } - } - }, - "carrier": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "name": { - "type": "String", - "maxLength": 5000 - }, - "phone": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "tracking_number": { - "type": "String", - "nullable": true, - "maxLength": 5000 - } - } - }, - "statement_descriptor": { - "type": "String", - "nullable": true, - "maxLength": 22 - }, - "statement_descriptor_suffix": { - "type": "String", - "nullable": true, - "maxLength": 22 - }, - "transfer_data": { - "type": "object", - "nullable": true, - "properties": { - "amount": { - "type": "Int32", - "nullable": true - }, - "destination": { - "type": "String" - } - } - }, - "transfer_group": { - "type": "String", - "nullable": true - } - } - }, - "payment_method_collection": { - "type": "CheckoutPaymentMethodCollection", - "nullable": true - }, - "payment_method_configuration": { - "type": "String", - "nullable": true, - "maxLength": 100 - }, - "payment_method_options": { - "type": "object", - "nullable": true, - "properties": { - "acss_debit": { - "type": "object", - "nullable": true, - "properties": { - "affirm": { - "type": "PaymentMethodAffirm", - "nullable": true - }, - "currency": { - "type": "CheckoutCurrency", - "nullable": true - }, - "mandate_options": { - "type": "object", - "nullable": true, - "properties": { - "custom_mandate_url": { - "type": "String", - "nullable": true - }, - "default_for": { - "type": "array", - "nullable": true, - "items": { - "type": "CheckoutDefaultFor" - } - }, - "interval_description": { - "type": "String", - "nullable": true, - "maxLength": 500 - }, - "payment_schedule": { - "type": "CheckoutPaymentSchedule", - "nullable": true - }, - "transaction_type": { - "type": "CheckoutTransactionType", - "nullable": true - } - } - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitSetupFutureUsage", - "nullable": true - }, - "verification_method": { - "type": "CheckoutVerificationMethod", - "nullable": true - } - } - }, - "affirm": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsAffirmSetupFutureUsage", - "nullable": true - } - } - }, - "afterpay_clearpay": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpaySetupFutureUsage", - "nullable": true - } - } - }, - "alipay": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsAlipaySetupFutureUsage", - "nullable": true - } - } - }, - "au_becs_debit": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebitSetupFutureUsage", - "nullable": true - } - } - }, - "bacs_debit": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebitSetupFutureUsage", - "nullable": true - } - } - }, - "bancontact": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsBancontactSetupFutureUsage", - "nullable": true - } - } - }, - "boleto": { - "type": "object", - "nullable": true, - "properties": { - "expires_after_days": { - "type": "Int32", - "nullable": true - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsBoletoSetupFutureUsage", - "nullable": true - } - } - }, - "card": { - "type": "object", - "nullable": true, - "properties": { - "installments": { - "type": "object", - "nullable": true, - "properties": { - "enabled": { - "type": "Boolean", - "nullable": true - } - } - }, - "request_three_d_secure": { - "type": "CheckoutRequestThreeDSecure", - "nullable": true - }, - "setup_future_usage": { - "type": "CheckoutSetupFutureUsage", - "nullable": true - }, - "statement_descriptor_suffix_kana": { - "type": "String", - "nullable": true, - "maxLength": 22 - }, - "statement_descriptor_suffix_kanji": { - "type": "String", - "nullable": true, - "maxLength": 17 - } - } - }, - "cashapp": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsCashappSetupFutureUsage", - "nullable": true - } - } - }, - "customer_balance": { - "type": "object", - "nullable": true, - "properties": { - "bank_transfer": { - "type": "object", - "nullable": true, - "properties": { - "eu_bank_transfer": { - "type": "object", - "nullable": true, - "properties": { - "country": { - "type": "String", - "maxLength": 5000 - } - } - }, - "requested_address_types": { - "type": "array", - "nullable": true, - "items": { - "type": "CheckoutRequestedAddressTypes" - } - }, - "type": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferType" - } - } - }, - "funding_type": { - "type": "CheckoutFundingType", - "nullable": true - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceSetupFutureUsage", - "nullable": true - } - } - }, - "eps": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsEpsSetupFutureUsage", - "nullable": true - } - } - }, - "fpx": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsFpxSetupFutureUsage", - "nullable": true - } - } - }, - "giropay": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsGiropaySetupFutureUsage", - "nullable": true - } - } - }, - "grabpay": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpaySetupFutureUsage", - "nullable": true - } - } - }, - "ideal": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsIdealSetupFutureUsage", - "nullable": true - } - } - }, - "klarna": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsKlarnaSetupFutureUsage", - "nullable": true - } - } - }, - "konbini": { - "type": "object", - "nullable": true, - "properties": { - "expires_after_days": { - "type": "Int32", - "nullable": true - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsKonbiniSetupFutureUsage", - "nullable": true - } - } - }, - "link": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsLinkSetupFutureUsage", - "nullable": true - } - } - }, - "oxxo": { - "type": "object", - "nullable": true, - "properties": { - "expires_after_days": { - "type": "Int32", - "nullable": true - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsOxxoSetupFutureUsage", - "nullable": true - } - } - }, - "p24": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsP24SetupFutureUsage", - "nullable": true - }, - "tos_shown_and_accepted": { - "type": "Boolean", - "nullable": true - } - } - }, - "paynow": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsPaynowSetupFutureUsage", - "nullable": true - } - } - }, - "paypal": { - "type": "object", - "nullable": true, - "properties": { - "capture_method": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalCaptureMethod", - "nullable": true - }, - "preferred_locale": { - "type": "CheckoutPreferredLocale", - "nullable": true - }, - "reference": { - "type": "String", - "nullable": true, - "maxLength": 127 - }, - "risk_correlation_id": { - "type": "String", - "nullable": true, - "maxLength": 32 - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalSetupFutureUsage", - "nullable": true - } - } - }, - "pix": { - "type": "object", - "nullable": true, - "properties": { - "expires_after_seconds": { - "type": "Int32", - "nullable": true - } - } - }, - "revolut_pay": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPaySetupFutureUsage", - "nullable": true - } - } - }, - "sepa_debit": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebitSetupFutureUsage", - "nullable": true - } - } - }, - "sofort": { - "type": "object", - "nullable": true, - "properties": { - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsSofortSetupFutureUsage", - "nullable": true - } - } - }, - "swish": { - "type": "object", - "nullable": true, - "properties": { - "reference": { - "type": "String", - "nullable": true, - "maxLength": 5000 - } - } - }, - "us_bank_account": { - "type": "object", - "nullable": true, - "properties": { - "financial_connections": { - "type": "object", - "nullable": true, - "properties": { - "permissions": { - "type": "array", - "nullable": true, - "items": { - "type": "CheckoutPermissions", - "maxLength": 5000 - } - }, - "prefetch": { - "type": "array", - "nullable": true, - "items": { - "type": "CheckoutPrefetch" - } - } - } - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountSetupFutureUsage", - "nullable": true - }, - "verification_method": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountVerificationMethod", - "nullable": true - } - } - }, - "wechat_pay": { - "type": "object", - "nullable": true, - "properties": { - "app_id": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "client": { - "type": "CheckoutClient" - }, - "setup_future_usage": { - "type": "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPaySetupFutureUsage", - "nullable": true - } - } - } - } - }, - "payment_method_types": { - "type": "array", - "nullable": true, - "items": { - "type": "CheckoutPaymentMethodTypes" - } - }, - "phone_number_collection": { - "type": "object", - "nullable": true, - "properties": { - "enabled": { - "type": "Boolean" - } - } - }, - "redirect_on_completion": { - "type": "CheckoutRedirectOnCompletion", - "nullable": true - }, - "return_url": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "setup_intent_data": { - "type": "object", - "nullable": true, - "properties": { - "description": { - "type": "String", - "nullable": true, - "maxLength": 1000 - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "on_behalf_of": { - "type": "String", - "nullable": true - } - } - }, - "shipping_address_collection": { - "type": "object", - "nullable": true, - "properties": { - "allowed_countries": { - "type": "array", - "items": { - "type": "CheckoutAllowedCountries" - } - } - } - }, - "shipping_options": { - "type": "array", - "nullable": true, - "items": { - "type": "object", - "properties": { - "shipping_rate": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "shipping_rate_data": { - "type": "object", - "nullable": true, - "properties": { - "delivery_estimate": { - "type": "object", - "nullable": true, - "properties": { - "maximum": { - "type": "object", - "nullable": true, - "properties": { - "unit": { - "type": "CheckoutUnit" - }, - "value": { - "type": "Int32" - } - } - }, - "minimum": { - "type": "object", - "nullable": true, - "properties": { - "unit": { - "type": "CheckoutUnit" - }, - "value": { - "type": "Int32" - } - } - } - } - }, - "display_name": { - "type": "String", - "maxLength": 100 - }, - "fixed_amount": { - "type": "object", - "nullable": true, - "properties": { - "amount": { - "type": "Int32" - }, - "currency": { - "type": "String" - }, - "currency_options": { - "type": "JSON", - "nullable": true - } - } - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "tax_behavior": { - "type": "CheckoutTaxBehavior", - "nullable": true - }, - "tax_code": { - "type": "String", - "nullable": true - }, - "type": { - "type": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataType", - "nullable": true - } - } - } - } - } - }, - "submit_type": { - "type": "CheckoutSubmitType", - "nullable": true - }, - "subscription_data": { - "type": "object", - "nullable": true, - "properties": { - "application_fee_percent": { - "type": "Float64", - "nullable": true - }, - "billing_cycle_anchor": { - "type": "UnixTime", - "nullable": true - }, - "default_tax_rates": { - "type": "array", - "nullable": true, - "items": { - "type": "String", - "maxLength": 5000 - } - }, - "description": { - "type": "String", - "nullable": true, - "maxLength": 500 - }, - "invoice_settings": { - "type": "object", - "nullable": true, - "properties": { - "issuer": { - "type": "object", - "nullable": true, - "properties": { - "account": { - "type": "String", - "nullable": true - }, - "type": { - "type": "CheckoutType" - } - } - } - } - }, - "metadata": { - "type": "JSON", - "nullable": true - }, - "on_behalf_of": { - "type": "String", - "nullable": true - }, - "proration_behavior": { - "type": "CheckoutProrationBehavior", - "nullable": true - }, - "transfer_data": { - "type": "object", - "nullable": true, - "properties": { - "amount_percent": { - "type": "Float64", - "nullable": true - }, - "destination": { - "type": "String" - } - } - }, - "trial_end": { - "type": "UnixTime", - "nullable": true - }, - "trial_period_days": { - "type": "Int32", - "nullable": true - }, - "trial_settings": { - "type": "object", - "nullable": true, - "properties": { - "end_behavior": { - "type": "object", - "properties": { - "missing_payment_method": { - "type": "CheckoutMissingPaymentMethod" - } - } - } - } - } - } - }, - "success_url": { - "type": "String", - "nullable": true, - "maxLength": 5000 - }, - "tax_id_collection": { - "type": "object", - "nullable": true, - "properties": { - "enabled": { - "type": "Boolean" - } - } - }, - "ui_mode": { - "type": "CheckoutUiMode", - "nullable": true - } + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "name": { + "description": "Name of pet that needs to be updated", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" } }, - "encoding": { - "after_expiration": { - "style": "deepObject", - "explode": true - }, - "automatic_tax": { - "style": "deepObject", - "explode": true - }, - "consent_collection": { - "style": "deepObject", - "explode": true - }, - "custom_fields": { - "style": "deepObject", - "explode": true - }, - "custom_text": { - "style": "deepObject", - "explode": true - }, - "customer_update": { - "style": "deepObject", - "explode": true - }, - "discounts": { - "style": "deepObject", - "explode": true - }, - "expand": { - "style": "deepObject", - "explode": true - }, - "invoice_creation": { - "style": "deepObject", - "explode": true - }, - "line_items": { - "style": "deepObject", - "explode": true - }, - "metadata": { - "style": "deepObject", - "explode": true - }, - "payment_intent_data": { - "style": "deepObject", - "explode": true - }, - "payment_method_options": { - "style": "deepObject", - "explode": true - }, - "payment_method_types": { - "style": "deepObject", - "explode": true - }, - "phone_number_collection": { - "style": "deepObject", - "explode": true - }, - "setup_intent_data": { - "style": "deepObject", - "explode": true - }, - "shipping_address_collection": { - "style": "deepObject", - "explode": true - }, - "shipping_options": { - "style": "deepObject", - "explode": true - }, - "subscription_data": { - "style": "deepObject", - "explode": true - }, - "tax_id_collection": { - "style": "deepObject", - "explode": true + "rest": { + "name": "name", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + }, + "petId": { + "description": "ID of pet that needs to be updated", + "type": { + "name": "Int64", + "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "format": "int64" + } + } + }, + "status": { + "description": "Status of pet that needs to be updated", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "name": "status", + "in": "query", + "schema": { + "type": [ + "string" + ] } } + } + }, + "description": "Updates a pet in the store with form data", + "name": "updatePetWithForm", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "uploadFile": { + "request": { + "url": "/pet/{petId}/uploadImage", + "method": "post", + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "requestBody": { + "contentType": "application/octet-stream" }, "response": { "contentType": "application/json" } }, "arguments": { + "additionalMetadata": { + "description": "Additional Metadata", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "name": "additionalMetadata", + "in": "query", + "schema": { + "type": [ + "string" + ] + } + } + }, "body": { - "description": "Request body of POST /v1/checkout/sessions", + "description": "Request body of POST /pet/{petId}/uploadImage", "type": { "type": "nullable", "underlying_type": { - "name": "PostCheckoutSessionsBody", + "name": "Binary", "type": "named" } + }, + "rest": { + "in": "body" + } + }, + "petId": { + "description": "ID of pet to update", + "type": { + "name": "Int64", + "type": "named" + }, + "rest": { + "name": "petId", + "in": "path", + "schema": { + "type": [ + "integer" + ], + "format": "int64" + } } } }, - "name": "PostCheckoutSessions", + "description": "uploads an image", + "name": "uploadFile", "result_type": { - "name": "Order", + "name": "ApiResponse", "type": "named" } }, - { + "uploadPetMultipart": { "request": { - "url": "/fine_tuning/jobs", + "url": "/pet/multipart", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "CreateFineTuningJobRequest" + "contentType": "multipart/form-data", + "encoding": { + "profileImage": { + "contentType": [ + "image/png", + "image/jpeg" + ], + "headers": { + "X-Rate-Limit-Limit": { + "explode": false, + "argumentName": "headerXRateLimitLimit", + "schema": { + "type": [ + "integer" + ] + } + } + } + } } }, "response": { @@ -5993,20 +7009,43 @@ }, "arguments": { "body": { - "description": "Request body of POST /fine_tuning/jobs", + "description": "Request body of POST /pet/multipart", "type": { - "name": "CreateFineTuningJobRequest", + "type": "nullable", + "underlying_type": { + "name": "UploadPetMultipartBody", + "type": "named" + } + }, + "rest": { + "in": "body" + } + }, + "headerXRateLimitLimit": { + "description": "The number of allowed requests in the current period", + "type": { + "name": "Int32", "type": "named" + }, + "rest": { + "explode": false, + "argumentName": "headerXRateLimitLimit", + "schema": { + "type": [ + "integer" + ] + } } } }, - "name": "createFineTuningJob", + "description": "POST /pet/multipart", + "name": "uploadPetMultipart", "result_type": { - "name": "CreateFineTuningJobRequest", + "name": "ApiResponse", "type": "named" } } - ], + }, "scalar_types": { "Binary": { "aggregate_functions": {}, @@ -6448,6 +7487,26 @@ "type": "enum" } }, + "FilesPurpose": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "account_requirement", + "additional_verification", + "business_icon", + "business_logo", + "customer_signature", + "dispute_evidence", + "identity_document", + "issuing_regulatory_reporting", + "pci_document", + "tax_document_user_upload", + "terminal_reader_splashscreen" + ], + "type": "enum" + } + }, "Float64": { "aggregate_functions": {}, "comparison_operators": {}, @@ -6469,6 +7528,41 @@ "type": "int64" } }, + "InvoicesCollectionMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "charge_automatically", + "send_invoice" + ], + "type": "enum" + } + }, + "InvoicesObject": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "list" + ], + "type": "enum" + } + }, + "InvoicesStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "draft", + "open", + "paid", + "uncollectible", + "void" + ], + "type": "enum" + } + }, "JSON": { "aggregate_functions": {}, "comparison_operators": {}, diff --git a/ndc-rest-schema/openapi/testdata/petstore3/schema.json b/ndc-rest-schema/openapi/testdata/petstore3/schema.json new file mode 100644 index 0000000..c59ad77 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/petstore3/schema.json @@ -0,0 +1,5257 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "collection_method": { + "description": "The collection method of the invoice to retrieve. Either `charge_automatically` or `send_invoice`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "InvoicesCollectionMethod", + "type": "named" + } + } + }, + "created": { + "description": "Only return invoices that were created during the given date interval.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "customer": { + "description": "Only return invoices for the customer specified by this customer ID.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "due_date": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "ending_before": { + "description": "A cursor for use in pagination. `ending_before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with `obj_bar`, your subsequent call can include `ending_before=obj_bar` in order to fetch the previous page of the list.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "limit": { + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "starting_after": { + "description": "A cursor for use in pagination. `starting_after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with `obj_foo`, your subsequent call can include `starting_after=obj_foo` in order to fetch the next page of the list.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "status": { + "description": "The status of the invoice, one of `draft`, `open`, `paid`, `uncollectible`, or `void`. [Learn more](https://stripe.com/docs/billing/invoices/workflow#workflow-overview)", + "type": { + "type": "nullable", + "underlying_type": { + "name": "InvoicesStatus", + "type": "named" + } + } + }, + "subscription": { + "description": "Only return invoices for the subscription specified by this subscription ID.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + }, + "description": "You can list all invoices, or list the invoices for a specific customer. The invoices are returned sorted by creation date, with the most recently created invoices appearing first.", + "name": "GetInvoices", + "result_type": { + "name": "GetInvoicesResult", + "type": "named" + } + }, + { + "arguments": { + "status": { + "description": "Status values that need to be considered for filter", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PetStatus", + "type": "named" + } + } + } + }, + "description": "Finds Pets by status", + "name": "findPetsByStatus", + "result_type": { + "element_type": { + "name": "Pet", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": { + "tags": { + "description": "Tags to filter by", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + } + }, + "description": "Finds Pets by tags", + "name": "findPetsByTags", + "result_type": { + "element_type": { + "name": "Pet", + "type": "named" + }, + "type": "array" + } + }, + { + "arguments": {}, + "description": "Returns pet inventories by status", + "name": "getInventory", + "result_type": { + "name": "JSON", + "type": "named" + } + }, + { + "arguments": { + "orderId": { + "description": "ID of order that needs to be fetched", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Find purchase order by ID", + "name": "getOrderById", + "result_type": { + "name": "Order", + "type": "named" + } + }, + { + "arguments": { + "petId": { + "description": "ID of pet to return", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Find pet by ID", + "name": "getPetById", + "result_type": { + "name": "Pet", + "type": "named" + } + }, + { + "arguments": {}, + "description": "Get snake object", + "name": "getSnake", + "result_type": { + "name": "SnakeObject", + "type": "named" + } + }, + { + "arguments": { + "username": { + "description": "The name that needs to be fetched. Use user1 for testing. ", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Get user by user name", + "name": "getUserByName", + "result_type": { + "name": "User", + "type": "named" + } + }, + { + "arguments": { + "password": { + "description": "The password for login in clear text", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "username": { + "description": "The user name for login", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + }, + "description": "Logs user into the system", + "name": "loginUser", + "result_type": { + "name": "String", + "type": "named" + } + } + ], + "object_types": { + "Address": { + "fields": { + "city": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "state": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "street": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "zip": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "ApiResponse": { + "fields": { + "code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "message": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "type": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "Category": { + "fields": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "CreateFineTuningJobRequest": { + "fields": { + "model": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "GetInvoicesResult": { + "fields": { + "has_more": { + "description": "True if this list has another page of items after this one that can be fetched.", + "type": { + "name": "Boolean", + "type": "named" + } + }, + "object": { + "description": "String representing the object's type. Objects of the same type share the same value. Always has the value `list`.", + "type": { + "name": "InvoicesObject", + "type": "named" + } + }, + "url": { + "description": "The URL where this list can be accessed.", + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "Order": { + "fields": { + "complete": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "petId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "quantity": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "shipDate": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + }, + "status": { + "description": "Order Status", + "type": { + "type": "nullable", + "underlying_type": { + "name": "OrderStatus", + "type": "named" + } + } + } + } + }, + "Pet": { + "fields": { + "category": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Category", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "name": "String", + "type": "named" + } + }, + "photoUrls": { + "type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + }, + "status": { + "description": "pet status in the store", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PetStatus", + "type": "named" + } + } + }, + "tags": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Tag", + "type": "named" + }, + "type": "array" + } + } + } + } + }, + "PostCheckoutSessionsBody": { + "fields": { + "after_expiration": { + "description": "Configure actions after a Checkout Session has expired.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyAfterExpiration", + "type": "named" + } + } + }, + "allow_promotion_codes": { + "description": "Enables user redeemable promotion codes.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "automatic_tax": { + "description": "Settings for automatic tax lookup for this session and resulting payments, invoices, and subscriptions.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyAutomaticTax", + "type": "named" + } + } + }, + "billing_address_collection": { + "description": "Specify whether Checkout should collect the customer's billing address. Defaults to `auto`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutBillingAddressCollection", + "type": "named" + } + } + }, + "cancel_url": { + "description": "If set, Checkout displays a back button and customers will be directed to this URL if they decide to cancel payment and return to your website.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "client_reference_id": { + "description": "A unique string to reference the Checkout Session. This can be a\ncustomer ID, a cart ID, or similar, and can be used to reconcile the\nsession with your internal systems.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "consent_collection": { + "description": "Configure fields for the Checkout Session to gather active consent from customers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyConsentCollection", + "type": "named" + } + } + }, + "currency": { + "description": "Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies). Required in `setup` mode when `payment_method_types` is not set.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "custom_fields": { + "description": "Collect additional information from your customer using custom fields. Up to 3 fields are supported.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "PostCheckoutSessionsBodyCustomFields", + "type": "named" + }, + "type": "array" + } + } + }, + "custom_text": { + "description": "Display additional text for your customers using custom text.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomText", + "type": "named" + } + } + }, + "customer": { + "description": "ID of an existing Customer, if one exists. In `payment` mode, the customer’s most recently saved card\npayment method will be used to prefill the email, name, card details, and billing address\non the Checkout page. In `subscription` mode, the customer’s [default payment method](https://stripe.com/docs/api/customers/update#update_customer-invoice_settings-default_payment_method)\nwill be used if it’s a card, otherwise the most recently saved card will be used. A valid billing address, billing name and billing email are required on the payment method for Checkout to prefill the customer's card details.\n\nIf the Customer already has a valid [email](https://stripe.com/docs/api/customers/object#customer_object-email) set, the email will be prefilled and not editable in Checkout.\nIf the Customer does not have a valid `email`, Checkout will set the email entered during the session on the Customer.\n\nIf blank for Checkout Sessions in `subscription` mode or with `customer_creation` set as `always` in `payment` mode, Checkout will create a new Customer object based on information provided during the payment flow.\n\nYou can set [`payment_intent_data.setup_future_usage`](https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-payment_intent_data-setup_future_usage) to have Checkout automatically attach the payment method to the Customer you pass in for future reuse.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "customer_creation": { + "description": "Configure whether a Checkout Session creates a [Customer](https://stripe.com/docs/api/customers) during Session confirmation.\n\nWhen a Customer is not created, you can still retrieve email, address, and other customer data entered in Checkout\nwith [customer_details](https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-customer_details).\n\nSessions that don't create Customers instead are grouped by [guest customers](https://stripe.com/docs/payments/checkout/guest-customers)\nin the Dashboard. Promotion codes limited to first time customers will return invalid for these Sessions.\n\nCan only be set in `payment` and `setup` mode.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutCustomerCreation", + "type": "named" + } + } + }, + "customer_email": { + "description": "If provided, this value will be used when the Customer object is created.\nIf not provided, customers will be asked to enter their email address.\nUse this parameter to prefill customer data if you already have an email\non file. To access information about the customer once a session is\ncomplete, use the `customer` field.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "customer_update": { + "description": "Controls what fields on Customer can be updated by the Checkout Session. Can only be provided when `customer` is provided.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomerUpdate", + "type": "named" + } + } + }, + "discounts": { + "description": "The coupon or promotion code to apply to this Session. Currently, only up to one may be specified.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "PostCheckoutSessionsBodyDiscounts", + "type": "named" + }, + "type": "array" + } + } + }, + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "expires_at": { + "description": "The Epoch time in seconds at which the Checkout Session will expire. It can be anywhere from 30 minutes to 24 hours after Checkout Session creation. By default, this value is 24 hours from creation.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + }, + "invoice_creation": { + "description": "Generate a post-purchase Invoice for one-time payments.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyInvoiceCreation", + "type": "named" + } + } + }, + "line_items": { + "description": "A list of items the customer is purchasing. Use this parameter to pass one-time or recurring [Prices](https://stripe.com/docs/api/prices).\n\nFor `payment` mode, there is a maximum of 100 line items, however it is recommended to consolidate line items if there are more than a few dozen.\n\nFor `subscription` mode, there is a maximum of 20 line items with recurring Prices and 20 line items with one-time Prices. Line items with one-time Prices will be on the initial invoice only.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "PostCheckoutSessionsBodyLineItems", + "type": "named" + }, + "type": "array" + } + } + }, + "locale": { + "description": "The IETF language tag of the locale Checkout is displayed in. If blank or `auto`, the browser's locale is used.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutLocale", + "type": "named" + } + } + }, + "metadata": { + "description": "Set of [key-value pairs](https://stripe.com/docs/api/metadata) that you can attach to an object. This can be useful for storing additional information about the object in a structured format. Individual keys can be unset by posting an empty value to them. All keys can be unset by posting an empty value to `metadata`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "mode": { + "description": "The mode of the Checkout Session. Pass `subscription` if the Checkout Session includes at least one recurring item.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutMode", + "type": "named" + } + } + }, + "payment_intent_data": { + "description": "A subset of parameters to be passed to PaymentIntent creation for Checkout Sessions in `payment` mode.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentIntentData", + "type": "named" + } + } + }, + "payment_method_collection": { + "description": "Specify whether Checkout should collect a payment method. When set to `if_required`, Checkout will not collect a payment method when the total due for the session is 0.\nThis may occur if the Checkout Session includes a free trial or a discount.\n\nCan only be set in `subscription` mode. Defaults to `always`.\n\nIf you'd like information on how to collect a payment method outside of Checkout, read the guide on configuring [subscriptions with a free trial](https://stripe.com/docs/payments/checkout/free-trials).", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutPaymentMethodCollection", + "type": "named" + } + } + }, + "payment_method_configuration": { + "description": "The ID of the payment method configuration to use with this Checkout session.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "payment_method_options": { + "description": "Payment-method-specific configuration.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptions", + "type": "named" + } + } + }, + "payment_method_types": { + "description": "A list of the types of payment methods (e.g., `card`) this Checkout Session can accept.\n\nYou can omit this attribute to manage your payment methods from the [Stripe Dashboard](https://dashboard.stripe.com/settings/payment_methods).\nSee [Dynamic Payment Methods](https://stripe.com/docs/payments/payment-methods/integration-options#using-dynamic-payment-methods) for more details.\n\nRead more about the supported payment methods and their requirements in our [payment\nmethod details guide](/docs/payments/checkout/payment-methods).\n\nIf multiple payment methods are passed, Checkout will dynamically reorder them to\nprioritize the most relevant payment methods based on the customer's location and\nother characteristics.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "CheckoutPaymentMethodTypes", + "type": "named" + }, + "type": "array" + } + } + }, + "phone_number_collection": { + "description": "Controls phone number collection settings for the session.\n\nWe recommend that you review your privacy policy and check with your legal contacts\nbefore using this feature. Learn more about [collecting phone numbers with Checkout](https://stripe.com/docs/payments/checkout/phone-numbers).", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPhoneNumberCollection", + "type": "named" + } + } + }, + "redirect_on_completion": { + "description": "This parameter applies to `ui_mode: embedded`. Learn more about the [redirect behavior](https://stripe.com/docs/payments/checkout/custom-redirect-behavior) of embedded sessions. Defaults to `always`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutRedirectOnCompletion", + "type": "named" + } + } + }, + "return_url": { + "description": "The URL to redirect your customer back to after they authenticate or cancel their payment on the\npayment method's app or site. This parameter is required if ui_mode is `embedded`\nand redirect-based payment methods are enabled on the session.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "setup_intent_data": { + "description": "A subset of parameters to be passed to SetupIntent creation for Checkout Sessions in `setup` mode.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodySetupIntentData", + "type": "named" + } + } + }, + "shipping_address_collection": { + "description": "When set, provides configuration for Checkout to collect a shipping address from a customer.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingAddressCollection", + "type": "named" + } + } + }, + "shipping_options": { + "description": "The shipping rate options to apply to this Session. Up to a maximum of 5.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "PostCheckoutSessionsBodyShippingOptions", + "type": "named" + }, + "type": "array" + } + } + }, + "submit_type": { + "description": "Describes the type of transaction being performed by Checkout in order to customize\nrelevant text on the page, such as the submit button. `submit_type` can only be\nspecified on Checkout Sessions in `payment` mode. If blank or `auto`, `pay` is used.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutSubmitType", + "type": "named" + } + } + }, + "subscription_data": { + "description": "A subset of parameters to be passed to subscription creation for Checkout Sessions in `subscription` mode.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodySubscriptionData", + "type": "named" + } + } + }, + "success_url": { + "description": "The URL to which Stripe should send customers when payment or setup\nis complete.\nThis parameter is not allowed if ui_mode is `embedded`. If you’d like to use\ninformation from the successful Checkout Session on your page, read the\nguide on [customizing your success page](https://stripe.com/docs/payments/checkout/custom-success-page).", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "tax_id_collection": { + "description": "Controls tax ID collection settings for the session.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyTaxIdCollection", + "type": "named" + } + } + }, + "ui_mode": { + "description": "The UI mode of the Session. Defaults to `hosted`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutUiMode", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyAfterExpiration": { + "description": "Configure actions after a Checkout Session has expired.", + "fields": { + "recovery": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyAfterExpirationRecovery", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyAfterExpirationRecovery": { + "fields": { + "allow_promotion_codes": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "enabled": { + "type": { + "name": "Boolean", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyAutomaticTax": { + "description": "Settings for automatic tax lookup for this session and resulting payments, invoices, and subscriptions.", + "fields": { + "enabled": { + "type": { + "name": "Boolean", + "type": "named" + } + }, + "liability": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyAutomaticTaxLiability", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyAutomaticTaxLiability": { + "fields": { + "account": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "type": { + "type": { + "name": "CheckoutType", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyConsentCollection": { + "description": "Configure fields for the Checkout Session to gather active consent from customers.", + "fields": { + "payment_method_reuse_agreement": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyConsentCollectionPaymentMethodReuseAgreement", + "type": "named" + } + } + }, + "promotions": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutPromotions", + "type": "named" + } + } + }, + "terms_of_service": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutTermsOfService", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyConsentCollectionPaymentMethodReuseAgreement": { + "fields": { + "position": { + "type": { + "name": "CheckoutPosition", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomFields": { + "fields": { + "dropdown": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomFieldsDropdown", + "type": "named" + } + } + }, + "key": { + "type": { + "name": "String", + "type": "named" + } + }, + "label": { + "type": { + "name": "PostCheckoutSessionsBodyCustomFieldsLabel", + "type": "named" + } + }, + "numeric": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomFieldsNumeric", + "type": "named" + } + } + }, + "optional": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "text": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomFieldsText", + "type": "named" + } + } + }, + "type": { + "type": { + "name": "PostCheckoutSessionsBodyCustomFieldsType", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomFieldsDropdown": { + "fields": { + "options": { + "type": { + "element_type": { + "name": "PostCheckoutSessionsBodyCustomFieldsDropdownOptions", + "type": "named" + }, + "type": "array" + } + } + } + }, + "PostCheckoutSessionsBodyCustomFieldsDropdownOptions": { + "fields": { + "label": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomFieldsLabel": { + "fields": { + "custom": { + "type": { + "name": "String", + "type": "named" + } + }, + "type": { + "type": { + "name": "PostCheckoutSessionsBodyCustomFieldsLabelType", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomFieldsNumeric": { + "fields": { + "maximum_length": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "minimum_length": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyCustomFieldsText": { + "fields": { + "maximum_length": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "minimum_length": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyCustomText": { + "description": "Display additional text for your customers using custom text.", + "fields": { + "after_submit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomTextAfterSubmit", + "type": "named" + } + } + }, + "shipping_address": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomTextShippingAddress", + "type": "named" + } + } + }, + "submit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomTextSubmit", + "type": "named" + } + } + }, + "terms_of_service_acceptance": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyCustomTextTermsOfServiceAcceptance", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyCustomTextAfterSubmit": { + "fields": { + "message": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomTextShippingAddress": { + "fields": { + "message": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomTextSubmit": { + "fields": { + "message": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomTextTermsOfServiceAcceptance": { + "fields": { + "message": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyCustomerUpdate": { + "description": "Controls what fields on Customer can be updated by the Checkout Session. Can only be provided when `customer` is provided.", + "fields": { + "address": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutAddress", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutName", + "type": "named" + } + } + }, + "shipping": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutShipping", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyDiscounts": { + "fields": { + "coupon": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "promotion_code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyInvoiceCreation": { + "description": "Generate a post-purchase Invoice for one-time payments.", + "fields": { + "enabled": { + "type": { + "name": "Boolean", + "type": "named" + } + }, + "invoice_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceData", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyInvoiceCreationInvoiceData": { + "fields": { + "account_tax_ids": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "custom_fields": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataCustomFields", + "type": "named" + }, + "type": "array" + } + } + }, + "description": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "footer": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "issuer": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataIssuer", + "type": "named" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "rendering_options": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataRenderingOptions", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataCustomFields": { + "fields": { + "name": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataIssuer": { + "fields": { + "account": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "type": { + "type": { + "name": "CheckoutType", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyInvoiceCreationInvoiceDataRenderingOptions": { + "fields": { + "amount_tax_display": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutAmountTaxDisplay", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyLineItems": { + "fields": { + "adjustable_quantity": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyLineItemsAdjustableQuantity", + "type": "named" + } + } + }, + "dynamic_tax_rates": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "price": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "price_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyLineItemsPriceData", + "type": "named" + } + } + }, + "quantity": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "tax_rates": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + } + } + }, + "PostCheckoutSessionsBodyLineItemsAdjustableQuantity": { + "fields": { + "enabled": { + "type": { + "name": "Boolean", + "type": "named" + } + }, + "maximum": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "minimum": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyLineItemsPriceData": { + "fields": { + "currency": { + "type": { + "name": "String", + "type": "named" + } + }, + "product": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "product_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyLineItemsPriceDataProductData", + "type": "named" + } + } + }, + "recurring": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyLineItemsPriceDataRecurring", + "type": "named" + } + } + }, + "tax_behavior": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutTaxBehavior", + "type": "named" + } + } + }, + "unit_amount": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "unit_amount_decimal": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyLineItemsPriceDataProductData": { + "fields": { + "description": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "images": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "name": { + "type": { + "name": "String", + "type": "named" + } + }, + "tax_code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyLineItemsPriceDataRecurring": { + "fields": { + "interval": { + "type": { + "name": "CheckoutInterval", + "type": "named" + } + }, + "interval_count": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentIntentData": { + "description": "A subset of parameters to be passed to PaymentIntent creation for Checkout Sessions in `payment` mode.", + "fields": { + "application_fee_amount": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "capture_method": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutCaptureMethod", + "type": "named" + } + } + }, + "description": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "on_behalf_of": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "receipt_email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutSetupFutureUsage", + "type": "named" + } + } + }, + "shipping": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentIntentDataShipping", + "type": "named" + } + } + }, + "statement_descriptor": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "statement_descriptor_suffix": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "transfer_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentIntentDataTransferData", + "type": "named" + } + } + }, + "transfer_group": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentIntentDataShipping": { + "fields": { + "address": { + "type": { + "name": "PostCheckoutSessionsBodyPaymentIntentDataShippingAddress", + "type": "named" + } + }, + "carrier": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "name": { + "type": { + "name": "String", + "type": "named" + } + }, + "phone": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "tracking_number": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentIntentDataShippingAddress": { + "fields": { + "city": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "country": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "line1": { + "type": { + "name": "String", + "type": "named" + } + }, + "line2": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "postal_code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "state": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentIntentDataTransferData": { + "fields": { + "amount": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "destination": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptions": { + "description": "Payment-method-specific configuration.", + "fields": { + "acss_debit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebit", + "type": "named" + } + } + }, + "affirm": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAffirm", + "type": "named" + } + } + }, + "afterpay_clearpay": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpay", + "type": "named" + } + } + }, + "alipay": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAlipay", + "type": "named" + } + } + }, + "au_becs_debit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebit", + "type": "named" + } + } + }, + "bacs_debit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebit", + "type": "named" + } + } + }, + "bancontact": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBancontact", + "type": "named" + } + } + }, + "boleto": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBoleto", + "type": "named" + } + } + }, + "card": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCard", + "type": "named" + } + } + }, + "cashapp": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCashapp", + "type": "named" + } + } + }, + "customer_balance": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalance", + "type": "named" + } + } + }, + "eps": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsEps", + "type": "named" + } + } + }, + "fpx": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsFpx", + "type": "named" + } + } + }, + "giropay": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGiropay", + "type": "named" + } + } + }, + "grabpay": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpay", + "type": "named" + } + } + }, + "ideal": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsIdeal", + "type": "named" + } + } + }, + "klarna": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKlarna", + "type": "named" + } + } + }, + "konbini": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKonbini", + "type": "named" + } + } + }, + "link": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsLink", + "type": "named" + } + } + }, + "oxxo": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsOxxo", + "type": "named" + } + } + }, + "p24": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsP24", + "type": "named" + } + } + }, + "paynow": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaynow", + "type": "named" + } + } + }, + "paypal": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypal", + "type": "named" + } + } + }, + "pix": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPix", + "type": "named" + } + } + }, + "revolut_pay": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPay", + "type": "named" + } + } + }, + "sepa_debit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebit", + "type": "named" + } + } + }, + "sofort": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSofort", + "type": "named" + } + } + }, + "swish": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSwish", + "type": "named" + } + } + }, + "us_bank_account": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccount", + "type": "named" + } + } + }, + "wechat_pay": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPay", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebit": { + "fields": { + "affirm": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PaymentMethodAffirm", + "type": "named" + } + } + }, + "currency": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutCurrency", + "type": "named" + } + } + }, + "mandate_options": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitMandateOptions", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitSetupFutureUsage", + "type": "named" + } + } + }, + "verification_method": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutVerificationMethod", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitMandateOptions": { + "fields": { + "custom_mandate_url": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "default_for": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "CheckoutDefaultFor", + "type": "named" + }, + "type": "array" + } + } + }, + "interval_description": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "payment_schedule": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutPaymentSchedule", + "type": "named" + } + } + }, + "transaction_type": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutTransactionType", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAffirm": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAffirmSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpay": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpaySetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAlipay": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAlipaySetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebit": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebitSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebit": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebitSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsBancontact": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBancontactSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsBoleto": { + "fields": { + "expires_after_days": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsBoletoSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCard": { + "fields": { + "installments": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCardInstallments", + "type": "named" + } + } + }, + "request_three_d_secure": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutRequestThreeDSecure", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutSetupFutureUsage", + "type": "named" + } + } + }, + "statement_descriptor_suffix_kana": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "statement_descriptor_suffix_kanji": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCardInstallments": { + "fields": { + "enabled": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCashapp": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCashappSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalance": { + "fields": { + "bank_transfer": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransfer", + "type": "named" + } + } + }, + "funding_type": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutFundingType", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransfer": { + "fields": { + "eu_bank_transfer": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferEuBankTransfer", + "type": "named" + } + } + }, + "requested_address_types": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "CheckoutRequestedAddressTypes", + "type": "named" + }, + "type": "array" + } + } + }, + "type": { + "type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferType", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferEuBankTransfer": { + "fields": { + "country": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsEps": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsEpsSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsFpx": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsFpxSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsGiropay": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGiropaySetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpay": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpaySetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsIdeal": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsIdealSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsKlarna": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKlarnaSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsKonbini": { + "fields": { + "expires_after_days": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsKonbiniSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsLink": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsLinkSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsOxxo": { + "fields": { + "expires_after_days": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsOxxoSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsP24": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsP24SetupFutureUsage", + "type": "named" + } + } + }, + "tos_shown_and_accepted": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsPaynow": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaynowSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsPaypal": { + "fields": { + "capture_method": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalCaptureMethod", + "type": "named" + } + } + }, + "preferred_locale": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutPreferredLocale", + "type": "named" + } + } + }, + "reference": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "risk_correlation_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsPix": { + "fields": { + "expires_after_seconds": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPay": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPaySetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebit": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebitSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsSofort": { + "fields": { + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsSofortSetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsSwish": { + "fields": { + "reference": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccount": { + "fields": { + "financial_connections": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountFinancialConnections", + "type": "named" + } + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountSetupFutureUsage", + "type": "named" + } + } + }, + "verification_method": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountVerificationMethod", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountFinancialConnections": { + "fields": { + "permissions": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "CheckoutPermissions", + "type": "named" + }, + "type": "array" + } + } + }, + "prefetch": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "CheckoutPrefetch", + "type": "named" + }, + "type": "array" + } + } + } + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPay": { + "fields": { + "app_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "client": { + "type": { + "name": "CheckoutClient", + "type": "named" + } + }, + "setup_future_usage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPaySetupFutureUsage", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyPhoneNumberCollection": { + "description": "Controls phone number collection settings for the session.\n\nWe recommend that you review your privacy policy and check with your legal contacts\nbefore using this feature. Learn more about [collecting phone numbers with Checkout](https://stripe.com/docs/payments/checkout/phone-numbers).", + "fields": { + "enabled": { + "type": { + "name": "Boolean", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodySetupIntentData": { + "description": "A subset of parameters to be passed to SetupIntent creation for Checkout Sessions in `setup` mode.", + "fields": { + "description": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "on_behalf_of": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyShippingAddressCollection": { + "description": "When set, provides configuration for Checkout to collect a shipping address from a customer.", + "fields": { + "allowed_countries": { + "type": { + "element_type": { + "name": "CheckoutAllowedCountries", + "type": "named" + }, + "type": "array" + } + } + } + }, + "PostCheckoutSessionsBodyShippingOptions": { + "fields": { + "shipping_rate": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "shipping_rate_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateData", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyShippingOptionsShippingRateData": { + "fields": { + "delivery_estimate": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimate", + "type": "named" + } + } + }, + "display_name": { + "type": { + "name": "String", + "type": "named" + } + }, + "fixed_amount": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataFixedAmount", + "type": "named" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "tax_behavior": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutTaxBehavior", + "type": "named" + } + } + }, + "tax_code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "type": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataType", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimate": { + "fields": { + "maximum": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimateMaximum", + "type": "named" + } + } + }, + "minimum": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimateMinimum", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimateMaximum": { + "fields": { + "unit": { + "type": { + "name": "CheckoutUnit", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyShippingOptionsShippingRateDataDeliveryEstimateMinimum": { + "fields": { + "unit": { + "type": { + "name": "CheckoutUnit", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyShippingOptionsShippingRateDataFixedAmount": { + "fields": { + "amount": { + "type": { + "name": "Int32", + "type": "named" + } + }, + "currency": { + "type": { + "name": "String", + "type": "named" + } + }, + "currency_options": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodySubscriptionData": { + "description": "A subset of parameters to be passed to subscription creation for Checkout Sessions in `subscription` mode.", + "fields": { + "application_fee_percent": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Float64", + "type": "named" + } + } + }, + "billing_cycle_anchor": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + }, + "default_tax_rates": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "description": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "invoice_settings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodySubscriptionDataInvoiceSettings", + "type": "named" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "on_behalf_of": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "proration_behavior": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "CheckoutProrationBehavior", + "type": "named" + } + } + }, + "transfer_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodySubscriptionDataTransferData", + "type": "named" + } + } + }, + "trial_end": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + }, + "trial_period_days": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "trial_settings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodySubscriptionDataTrialSettings", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodySubscriptionDataInvoiceSettings": { + "fields": { + "issuer": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBodySubscriptionDataInvoiceSettingsIssuer", + "type": "named" + } + } + } + } + }, + "PostCheckoutSessionsBodySubscriptionDataInvoiceSettingsIssuer": { + "fields": { + "account": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "type": { + "type": { + "name": "CheckoutType", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodySubscriptionDataTransferData": { + "fields": { + "amount_percent": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Float64", + "type": "named" + } + } + }, + "destination": { + "type": { + "name": "String", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodySubscriptionDataTrialSettings": { + "fields": { + "end_behavior": { + "type": { + "name": "PostCheckoutSessionsBodySubscriptionDataTrialSettingsEndBehavior", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodySubscriptionDataTrialSettingsEndBehavior": { + "fields": { + "missing_payment_method": { + "type": { + "name": "CheckoutMissingPaymentMethod", + "type": "named" + } + } + } + }, + "PostCheckoutSessionsBodyTaxIdCollection": { + "description": "Controls tax ID collection settings for the session.", + "fields": { + "enabled": { + "type": { + "name": "Boolean", + "type": "named" + } + } + } + }, + "PostFilesBody": { + "fields": { + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "expand_json": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "file": { + "description": "A file to upload. Make sure that the specifications follow RFC 2388, which defines file transfers for the `multipart/form-data` protocol.", + "type": { + "name": "Binary", + "type": "named" + } + }, + "file_link_data": { + "description": "Optional parameters that automatically create a [file link](https://stripe.com/docs/api#file_links) for the newly created file.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostFilesBodyFileLinkData", + "type": "named" + } + } + }, + "purpose": { + "description": "The [purpose](https://stripe.com/docs/file-upload#uploading-a-file) of the uploaded file.", + "type": { + "name": "FilesPurpose", + "type": "named" + } + } + } + }, + "PostFilesBodyFileLinkData": { + "description": "Optional parameters that automatically create a [file link](https://stripe.com/docs/api#file_links) for the newly created file.", + "fields": { + "create": { + "type": { + "name": "Boolean", + "type": "named" + } + }, + "expires_at": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + }, + "metadata": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + } + } + }, + "PostTestHelpersTreasuryInboundTransfersIdFailBody": { + "fields": { + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "failure_details": { + "description": "Details about a failed InboundTransfer.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostTestHelpersTreasuryInboundTransfersIdFailBodyFailureDetails", + "type": "named" + } + } + } + } + }, + "PostTestHelpersTreasuryInboundTransfersIdFailBodyFailureDetails": { + "description": "Details about a failed InboundTransfer.", + "fields": { + "code": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TestHelpersCode", + "type": "named" + } + } + } + } + }, + "SnakeObject": { + "fields": { + "features": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "SnakeObjectId", + "type": "named" + } + } + } + } + }, + "SnakeObjectId": { + "fields": { + "complete": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "petId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "quantity": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "shipDate": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + }, + "status": { + "description": "Order Status", + "type": { + "type": "nullable", + "underlying_type": { + "name": "SnakeObjectIdStatus", + "type": "named" + } + } + } + } + }, + "Tag": { + "fields": { + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "TreasuryInboundTransfer": { + "description": "Use [InboundTransfers](https://stripe.com/docs/treasury/moving-money/financial-accounts/into/inbound-transfers) to add funds to your [FinancialAccount](https://stripe.com/docs/api#financial_accounts) via a PaymentMethod that is owned by you. The funds will be transferred via an ACH debit.", + "fields": { + "amount": { + "description": "Amount (in cents) transferred.", + "type": { + "name": "Int32", + "type": "named" + } + }, + "cancelable": { + "description": "Returns `true` if the InboundTransfer is able to be canceled.", + "type": { + "name": "Boolean", + "type": "named" + } + }, + "context": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "created": { + "description": "Time at which the object was created. Measured in seconds since the Unix epoch.", + "type": { + "name": "UnixTime", + "type": "named" + } + }, + "currency": { + "description": "Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies).", + "type": { + "name": "String", + "type": "named" + } + }, + "description": { + "description": "An arbitrary string attached to the object. Often useful for displaying to users.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "failure_details": { + "description": "Details about this InboundTransfer's failure. Only set when status is `failed`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "TreasuryInboundTransfersResourceFailureDetails", + "type": "named" + } + } + }, + "financial_account": { + "description": "The FinancialAccount that received the funds.", + "type": { + "name": "String", + "type": "named" + } + }, + "hosted_regulatory_receipt_url": { + "description": "A [hosted transaction receipt](https://stripe.com/docs/treasury/moving-money/regulatory-receipts) URL that is provided when money movement is considered regulated under Stripe's money transmission licenses.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "description": "Unique identifier for the object.", + "type": { + "name": "String", + "type": "named" + } + }, + "livemode": { + "description": "Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode.", + "type": { + "name": "Boolean", + "type": "named" + } + }, + "metadata": { + "description": "Set of [key-value pairs](https://stripe.com/docs/api/metadata) that you can attach to an object. This can be useful for storing additional information about the object in a structured format.", + "type": { + "name": "JSON", + "type": "named" + } + }, + "object": { + "description": "String representing the object's type. Objects of the same type share the same value.", + "type": { + "name": "TreasuryInboundTransferObject", + "type": "named" + } + }, + "origin_payment_method": { + "description": "The origin payment method to be debited for an InboundTransfer.", + "type": { + "name": "String", + "type": "named" + } + }, + "returned": { + "description": "Returns `true` if the funds for an InboundTransfer were returned after the InboundTransfer went to the `succeeded` state.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + "statement_descriptor": { + "description": "Statement descriptor shown when funds are debited from the source. Not all payment networks support `statement_descriptor`.", + "type": { + "name": "String", + "type": "named" + } + }, + "status": { + "description": "Status of the InboundTransfer: `processing`, `succeeded`, `failed`, and `canceled`. An InboundTransfer is `processing` if it is created and pending. The status changes to `succeeded` once the funds have been \"confirmed\" and a `transaction` is created and posted. The status changes to `failed` if the transfer fails.", + "type": { + "name": "TreasuryInboundTransferStatus", + "type": "named" + } + }, + "status_transitions": { + "type": { + "name": "TreasuryInboundTransfersResourceInboundTransferResourceStatusTransitions", + "type": "named" + } + }, + "transaction": { + "description": "The Transaction associated with this object.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + } + } + }, + "TreasuryInboundTransfersResourceFailureDetails": { + "fields": { + "code": { + "description": "Reason for the failure.", + "type": { + "name": "TreasuryInboundTransfersResourceFailureDetailsCode", + "type": "named" + } + } + } + }, + "TreasuryInboundTransfersResourceInboundTransferResourceStatusTransitions": { + "fields": { + "canceled_at": { + "description": "Timestamp describing when an InboundTransfer changed status to `canceled`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + }, + "failed_at": { + "description": "Timestamp describing when an InboundTransfer changed status to `failed`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + }, + "succeeded_at": { + "description": "Timestamp describing when an InboundTransfer changed status to `succeeded`.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "UnixTime", + "type": "named" + } + } + } + } + }, + "UploadPetMultipartBody": { + "fields": { + "address": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "addresses": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Address", + "type": "named" + }, + "type": "array" + } + } + }, + "children": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "UUID", + "type": "named" + } + } + }, + "profileImage": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Binary", + "type": "named" + } + } + } + } + }, + "User": { + "fields": { + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "firstName": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "lastName": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "password": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "phone": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userStatus": { + "description": "User Status", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "username": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "account": { + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "DELETE /v1/accounts/{account}", + "name": "DeleteAccountsAccount", + "result_type": { + "name": "TreasuryInboundTransfer", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /v1/checkout/sessions", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostCheckoutSessionsBody", + "type": "named" + } + } + } + }, + "description": "Creates a Session object.", + "name": "PostCheckoutSessions", + "result_type": { + "name": "Order", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /v1/files", + "type": { + "name": "PostFilesBody", + "type": "named" + } + }, + "headerXRateLimitLimit": { + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "POST /v1/files", + "name": "PostFiles", + "result_type": { + "name": "ApiResponse", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /test_helpers/treasury/inbound_transfers/{id}/fail", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PostTestHelpersTreasuryInboundTransfersIdFailBody", + "type": "named" + } + } + }, + "id": { + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Transitions a test mode created InboundTransfer to the failed status. The InboundTransfer must already be in the processing state.", + "name": "PostTestHelpersTreasuryInboundTransfersIdFail", + "result_type": { + "name": "TreasuryInboundTransfer", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /pet", + "type": { + "name": "Pet", + "type": "named" + } + } + }, + "description": "Add a new pet to the store", + "name": "addPet", + "result_type": { + "name": "Pet", + "type": "named" + } + }, + { + "arguments": {}, + "description": "Add snake object", + "name": "addSnake", + "result_type": { + "name": "SnakeObject", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /fine_tuning/jobs", + "type": { + "name": "CreateFineTuningJobRequest", + "type": "named" + } + } + }, + "description": "POST /fine_tuning/jobs", + "name": "createFineTuningJob", + "result_type": { + "name": "CreateFineTuningJobRequest", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /user/createWithList", + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "User", + "type": "named" + }, + "type": "array" + } + } + } + }, + "description": "Creates list of users with given input array", + "name": "createUsersWithListInput", + "result_type": { + "name": "User", + "type": "named" + } + }, + { + "arguments": { + "orderId": { + "description": "ID of the order that needs to be deleted", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Delete purchase order by ID", + "name": "deleteOrder", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "api_key": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "petId": { + "description": "Pet id to delete", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "Deletes a pet", + "name": "deletePet", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "username": { + "description": "The name that needs to be deleted", + "type": { + "name": "String", + "type": "named" + } + } + }, + "description": "Delete user", + "name": "deleteUser", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /store/order", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Order", + "type": "named" + } + } + } + }, + "description": "Place an order for a pet", + "name": "placeOrder", + "result_type": { + "name": "Order", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of PUT /pet", + "type": { + "name": "Pet", + "type": "named" + } + } + }, + "description": "Update an existing pet", + "name": "updatePet", + "result_type": { + "name": "Pet", + "type": "named" + } + }, + { + "arguments": { + "name": { + "description": "Name of pet that needs to be updated", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "petId": { + "description": "ID of pet that needs to be updated", + "type": { + "name": "Int64", + "type": "named" + } + }, + "status": { + "description": "Status of pet that needs to be updated", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + }, + "description": "Updates a pet in the store with form data", + "name": "updatePetWithForm", + "result_type": { + "type": "nullable", + "underlying_type": { + "name": "Boolean", + "type": "named" + } + } + }, + { + "arguments": { + "additionalMetadata": { + "description": "Additional Metadata", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "body": { + "description": "Request body of POST /pet/{petId}/uploadImage", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Binary", + "type": "named" + } + } + }, + "petId": { + "description": "ID of pet to update", + "type": { + "name": "Int64", + "type": "named" + } + } + }, + "description": "uploads an image", + "name": "uploadFile", + "result_type": { + "name": "ApiResponse", + "type": "named" + } + }, + { + "arguments": { + "body": { + "description": "Request body of POST /pet/multipart", + "type": { + "type": "nullable", + "underlying_type": { + "name": "UploadPetMultipartBody", + "type": "named" + } + } + }, + "headerXRateLimitLimit": { + "description": "The number of allowed requests in the current period", + "type": { + "name": "Int32", + "type": "named" + } + } + }, + "description": "POST /pet/multipart", + "name": "uploadPetMultipart", + "result_type": { + "name": "ApiResponse", + "type": "named" + } + } + ], + "scalar_types": { + "Binary": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "bytes" + } + }, + "Boolean": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "boolean" + } + }, + "CheckoutAddress": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "never" + ], + "type": "enum" + } + }, + "CheckoutAllowedCountries": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "AC", + "AD" + ], + "type": "enum" + } + }, + "CheckoutAmountTaxDisplay": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "", + "exclude_tax", + "include_inclusive_tax" + ], + "type": "enum" + } + }, + "CheckoutBillingAddressCollection": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "required" + ], + "type": "enum" + } + }, + "CheckoutCaptureMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "automatic", + "automatic_async", + "manual" + ], + "type": "enum" + } + }, + "CheckoutClient": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "android", + "ios", + "web" + ], + "type": "enum" + } + }, + "CheckoutCurrency": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "cad", + "usd" + ], + "type": "enum" + } + }, + "CheckoutCustomerCreation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "always", + "if_required" + ], + "type": "enum" + } + }, + "CheckoutDefaultFor": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "invoice", + "subscription" + ], + "type": "enum" + } + }, + "CheckoutFundingType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "bank_transfer" + ], + "type": "enum" + } + }, + "CheckoutInterval": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "day", + "month", + "week", + "year" + ], + "type": "enum" + } + }, + "CheckoutLocale": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "bg", + "cs" + ], + "type": "enum" + } + }, + "CheckoutMissingPaymentMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "cancel", + "create_invoice", + "pause" + ], + "type": "enum" + } + }, + "CheckoutMode": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "payment", + "setup", + "subscription" + ], + "type": "enum" + } + }, + "CheckoutName": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "never" + ], + "type": "enum" + } + }, + "CheckoutPaymentMethodCollection": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "always", + "if_required" + ], + "type": "enum" + } + }, + "CheckoutPaymentMethodTypes": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "acss_debit", + "affirm" + ], + "type": "enum" + } + }, + "CheckoutPaymentSchedule": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "combined", + "interval", + "sporadic" + ], + "type": "enum" + } + }, + "CheckoutPermissions": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "balances", + "ownership", + "payment_method", + "transactions" + ], + "type": "enum" + } + }, + "CheckoutPosition": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "hidden" + ], + "type": "enum" + } + }, + "CheckoutPreferredLocale": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "cs-CZ", + "da-DK" + ], + "type": "enum" + } + }, + "CheckoutPrefetch": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "balances", + "transactions" + ], + "type": "enum" + } + }, + "CheckoutPromotions": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "none" + ], + "type": "enum" + } + }, + "CheckoutProrationBehavior": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "create_prorations", + "none" + ], + "type": "enum" + } + }, + "CheckoutRedirectOnCompletion": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "always", + "if_required", + "never" + ], + "type": "enum" + } + }, + "CheckoutRequestThreeDSecure": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "any", + "automatic", + "challenge" + ], + "type": "enum" + } + }, + "CheckoutRequestedAddressTypes": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "aba", + "iban" + ], + "type": "enum" + } + }, + "CheckoutSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "CheckoutShipping": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "never" + ], + "type": "enum" + } + }, + "CheckoutSubmitType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "auto", + "book", + "donate", + "pay" + ], + "type": "enum" + } + }, + "CheckoutTaxBehavior": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "exclusive", + "inclusive", + "unspecified" + ], + "type": "enum" + } + }, + "CheckoutTermsOfService": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "required" + ], + "type": "enum" + } + }, + "CheckoutTransactionType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "business", + "personal" + ], + "type": "enum" + } + }, + "CheckoutType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "account", + "self" + ], + "type": "enum" + } + }, + "CheckoutUiMode": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "embedded", + "hosted" + ], + "type": "enum" + } + }, + "CheckoutUnit": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "business_day", + "day", + "hour", + "month", + "week" + ], + "type": "enum" + } + }, + "CheckoutVerificationMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "automatic", + "instant", + "microdeposits" + ], + "type": "enum" + } + }, + "FilesPurpose": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "account_requirement", + "additional_verification", + "business_icon", + "business_logo", + "customer_signature", + "dispute_evidence", + "identity_document", + "issuing_regulatory_reporting", + "pci_document", + "tax_document_user_upload", + "terminal_reader_splashscreen" + ], + "type": "enum" + } + }, + "Float64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "float64" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "InvoicesCollectionMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "charge_automatically", + "send_invoice" + ], + "type": "enum" + } + }, + "InvoicesObject": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "list" + ], + "type": "enum" + } + }, + "InvoicesStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "draft", + "open", + "paid", + "uncollectible", + "void" + ], + "type": "enum" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "OrderStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "placed", + "approved", + "delivered" + ], + "type": "enum" + } + }, + "PaymentMethodAffirm": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "PetStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "available", + "pending", + "sold" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyCustomFieldsLabelType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "custom" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyCustomFieldsType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "dropdown", + "numeric", + "text" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAcssDebitSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAffirmSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAfterpayClearpaySetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAlipaySetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsAuBecsDebitSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsBacsDebitSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsBancontactSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsBoletoSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCashappSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceBankTransferType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "eu_bank_transfer", + "gb_bank_transfer" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsCustomerBalanceSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsEpsSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsFpxSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsGiropaySetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsGrabpaySetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsIdealSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsKlarnaSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsKonbiniSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsLinkSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsOxxoSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsP24SetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsPaynowSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalCaptureMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "", + "manual" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsPaypalSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "", + "none", + "off_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsRevolutPaySetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsSepaDebitSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsSofortSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountSetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none", + "off_session", + "on_session" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsUsBankAccountVerificationMethod": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "automatic", + "instant" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyPaymentMethodOptionsWechatPaySetupFutureUsage": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "none" + ], + "type": "enum" + } + }, + "PostCheckoutSessionsBodyShippingOptionsShippingRateDataType": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "fixed_amount" + ], + "type": "enum" + } + }, + "SnakeObjectIdStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "placed", + "approved", + "delivered" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TestHelpersCode": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "account_closed", + "account_frozen", + "bank_account_restricted", + "bank_ownership_changed", + "debit_not_authorized", + "incorrect_account_holder_address", + "incorrect_account_holder_name", + "incorrect_account_holder_tax_id", + "insufficient_funds", + "invalid_account_number", + "invalid_currency", + "no_account", + "other" + ], + "type": "enum" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + }, + "TreasuryInboundTransferObject": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "treasury.inbound_transfer" + ], + "type": "enum" + } + }, + "TreasuryInboundTransferStatus": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "canceled", + "failed", + "processing", + "succeeded" + ], + "type": "enum" + } + }, + "TreasuryInboundTransfersResourceFailureDetailsCode": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "account_closed", + "account_frozen", + "bank_account_restricted", + "bank_ownership_changed", + "debit_not_authorized", + "incorrect_account_holder_address", + "incorrect_account_holder_name", + "incorrect_account_holder_tax_id", + "insufficient_funds", + "invalid_account_number", + "invalid_currency", + "no_account", + "other" + ], + "type": "enum" + } + }, + "UUID": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "uuid" + } + }, + "UnixTime": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/petstore3/source.json b/ndc-rest-schema/openapi/testdata/petstore3/source.json index 41280c3..5eb400d 100644 --- a/ndc-rest-schema/openapi/testdata/petstore3/source.json +++ b/ndc-rest-schema/openapi/testdata/petstore3/source.json @@ -2730,6 +2730,364 @@ } } }, + "/v1/invoices": { + "get": { + "description": "
You can list all invoices, or list the invoices for a specific customer. The invoices are returned sorted by creation date, with the most recently created invoices appearing first.
", + "operationId": "GetInvoices", + "parameters": [ + { + "description": "The collection method of the invoice to retrieve. Either `charge_automatically` or `send_invoice`.", + "in": "query", + "name": "collection_method", + "required": false, + "schema": { + "enum": ["charge_automatically", "send_invoice"], + "type": "string" + }, + "style": "form" + }, + { + "description": "Only return invoices that were created during the given date interval.", + "explode": true, + "in": "query", + "name": "created", + "required": false, + "schema": { + "anyOf": [ + { + "properties": { + "gt": { + "type": "integer" + }, + "gte": { + "type": "integer" + }, + "lt": { + "type": "integer" + }, + "lte": { + "type": "integer" + } + }, + "title": "range_query_specs", + "type": "object" + }, + { + "type": "integer" + } + ] + }, + "style": "deepObject" + }, + { + "description": "Only return invoices for the customer specified by this customer ID.", + "in": "query", + "name": "customer", + "required": false, + "schema": { + "maxLength": 5000, + "type": "string" + }, + "style": "form" + }, + { + "explode": true, + "in": "query", + "name": "due_date", + "required": false, + "schema": { + "anyOf": [ + { + "properties": { + "gt": { + "type": "integer" + }, + "gte": { + "type": "integer" + }, + "lt": { + "type": "integer" + }, + "lte": { + "type": "integer" + } + }, + "title": "range_query_specs", + "type": "object" + }, + { + "type": "integer" + } + ] + }, + "style": "deepObject" + }, + { + "description": "A cursor for use in pagination. `ending_before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with `obj_bar`, your subsequent call can include `ending_before=obj_bar` in order to fetch the previous page of the list.", + "in": "query", + "name": "ending_before", + "required": false, + "schema": { + "maxLength": 5000, + "type": "string" + }, + "style": "form" + }, + { + "description": "Specifies which fields in the response should be expanded.", + "explode": true, + "in": "query", + "name": "expand", + "required": false, + "schema": { + "items": { + "maxLength": 5000, + "type": "string" + }, + "type": "array" + }, + "style": "deepObject" + }, + { + "description": "A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + }, + "style": "form" + }, + { + "description": "A cursor for use in pagination. `starting_after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with `obj_foo`, your subsequent call can include `starting_after=obj_foo` in order to fetch the next page of the list.", + "in": "query", + "name": "starting_after", + "required": false, + "schema": { + "maxLength": 5000, + "type": "string" + }, + "style": "form" + }, + { + "description": "The status of the invoice, one of `draft`, `open`, `paid`, `uncollectible`, or `void`. [Learn more](https://stripe.com/docs/billing/invoices/workflow#workflow-overview)", + "in": "query", + "name": "status", + "required": false, + "schema": { + "enum": ["draft", "open", "paid", "uncollectible", "void"], + "type": "string", + "x-stripeBypassValidation": true + }, + "style": "form" + }, + { + "description": "Only return invoices for the subscription specified by this subscription ID.", + "in": "query", + "name": "subscription", + "required": false, + "schema": { + "maxLength": 5000, + "type": "string" + }, + "style": "form" + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "encoding": {}, + "schema": { + "additionalProperties": false, + "properties": {}, + "type": "object" + } + } + }, + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "", + "properties": { + "has_more": { + "description": "True if this list has another page of items after this one that can be fetched.", + "type": "boolean" + }, + "object": { + "description": "String representing the object's type. Objects of the same type share the same value. Always has the value `list`.", + "enum": ["list"], + "type": "string" + }, + "url": { + "description": "The URL where this list can be accessed.", + "maxLength": 5000, + "pattern": "^/v1/invoices", + "type": "string" + } + }, + "required": ["has_more", "object", "url"], + "title": "InvoicesResourceList", + "type": "object", + "x-expandableFields": ["data"] + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "Error response." + } + } + } + }, + "/v1/files": { + "post": { + "operationId": "PostFiles", + "requestBody": { + "content": { + "multipart/form-data": { + "encoding": { + "expand": { + "explode": true, + "style": "deepObject" + }, + "expand_json": { + "contentType": "application/json" + }, + "file": { + "headers": { + "X-Rate-Limit-Limit": { + "explode": false, + "argumentName": "headerXRateLimitLimit", + "schema": { + "type": ["integer"] + } + } + } + }, + "file_link_data": { + "explode": true, + "style": "deepObject" + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "expand": { + "description": "Specifies which fields in the response should be expanded.", + "items": { + "maxLength": 5000, + "type": "string" + }, + "type": "array" + }, + "expand_json": { + "items": { + "maxLength": 5000, + "type": "string" + }, + "type": "array" + }, + "file": { + "description": "A file to upload. Make sure that the specifications follow RFC 2388, which defines file transfers for the `multipart/form-data` protocol.", + "format": "binary", + "type": "string" + }, + "file_link_data": { + "description": "Optional parameters that automatically create a [file link](https://stripe.com/docs/api#file_links) for the newly created file.", + "properties": { + "create": { + "type": "boolean" + }, + "expires_at": { + "format": "unix-time", + "type": "integer" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "enum": [""], + "type": "string" + } + ] + } + }, + "required": ["create"], + "title": "file_link_creation_params", + "type": "object" + }, + "purpose": { + "description": "The [purpose](https://stripe.com/docs/file-upload#uploading-a-file) of the uploaded file.", + "enum": [ + "account_requirement", + "additional_verification", + "business_icon", + "business_logo", + "customer_signature", + "dispute_evidence", + "identity_document", + "issuing_regulatory_reporting", + "pci_document", + "tax_document_user_upload", + "terminal_reader_splashscreen" + ], + "type": "string", + "x-stripeBypassValidation": true + } + }, + "required": ["file", "purpose"], + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + }, + "description": "Error response." + } + }, + "servers": [ + { + "url": "https://files.stripe.com/" + } + ] + } + }, "/fine_tuning/jobs": { "post": { "operationId": "createFineTuningJob", diff --git a/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json b/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json index 19d8168..69c1b92 100644 --- a/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json +++ b/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json @@ -14,30 +14,11 @@ }, "version": "1.0.0" }, - "collections": [], - "functions": [ - { + "functions": { + "hasuraMockJsonGetPosts": { "request": { "url": "/posts", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "userId", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } @@ -51,6 +32,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "userId": { @@ -61,6 +51,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "userId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -74,7 +73,7 @@ "type": "array" } } - ], + }, "object_types": { "Post": { "fields": { @@ -85,6 +84,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -94,6 +98,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "title": { @@ -103,6 +113,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userId": { @@ -112,21 +127,24 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } } } } }, - "procedures": [ - { + "procedures": { + "hasuraMockJsonCreatePost": { "request": { "url": "/posts", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Post" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -138,6 +156,14 @@ "type": { "name": "Post", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } } }, @@ -148,7 +174,7 @@ "type": "named" } } - ], + }, "scalar_types": { "Int32": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.schema.json b/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.schema.json new file mode 100644 index 0000000..ee901f9 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.schema.json @@ -0,0 +1,122 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "id": { + "description": "Filter by post ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available posts", + "name": "hasuraMockJsonGetPosts", + "result_type": { + "element_type": { + "name": "Post", + "type": "named" + }, + "type": "array" + } + } + ], + "object_types": { + "Post": { + "fields": { + "body": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Post object that needs to be added", + "type": { + "name": "Post", + "type": "named" + } + } + }, + "description": "Create a post", + "name": "hasuraMockJsonCreatePost", + "result_type": { + "name": "Post", + "type": "named" + } + } + ], + "scalar_types": { + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json b/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json index a3ef003..576d18f 100644 --- a/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json +++ b/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json @@ -14,30 +14,11 @@ }, "version": "1.0.0" }, - "collections": [], - "functions": [ - { + "functions": { + "hasuraGetPosts": { "request": { "url": "/posts", "method": "get", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "userId", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "response": { "contentType": "application/json" } @@ -51,6 +32,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "id", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "userId": { @@ -61,6 +51,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "userId", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -74,7 +73,7 @@ "type": "array" } } - ], + }, "object_types": { "Post": { "fields": { @@ -85,6 +84,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -94,6 +98,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "title": { @@ -103,6 +113,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "userId": { @@ -112,21 +127,24 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } } } } }, - "procedures": [ - { + "procedures": { + "hasuraCreatePost": { "request": { "url": "/posts", "method": "post", "requestBody": { - "contentType": "application/json", - "schema": { - "type": "Post" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -138,6 +156,14 @@ "type": { "name": "Post", "type": "named" + }, + "rest": { + "in": "body", + "schema": { + "type": [ + "object" + ] + } } } }, @@ -148,7 +174,7 @@ "type": "named" } } - ], + }, "scalar_types": { "Int32": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.schema.json b/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.schema.json new file mode 100644 index 0000000..83455d9 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.schema.json @@ -0,0 +1,122 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "id": { + "description": "Filter by post ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available posts", + "name": "hasuraGetPosts", + "result_type": { + "element_type": { + "name": "Post", + "type": "named" + }, + "type": "array" + } + } + ], + "object_types": { + "Post": { + "fields": { + "body": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Post object that needs to be added", + "type": { + "name": "Post", + "type": "named" + } + } + }, + "description": "Create a post", + "name": "hasuraCreatePost", + "result_type": { + "name": "Post", + "type": "named" + } + } + ], + "scalar_types": { + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json b/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json index 434cc73..ac0162c 100644 --- a/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json +++ b/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json @@ -28,45 +28,11 @@ }, "version": "1.2.2" }, - "collections": [], - "functions": [ - { + "functions": { + "hasuraOneSignalGetNotifications": { "request": { "url": "/notifications", "method": "get", - "parameters": [ - { - "name": "app_id", - "in": "query", - "schema": { - "type": "String" - } - }, - { - "name": "kind", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "security": [ { "app_key": [] @@ -82,6 +48,15 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "app_id", + "in": "query", + "schema": { + "type": [ + "string" + ] + } } }, "kind": { @@ -92,6 +67,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "kind", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "limit": { @@ -102,6 +86,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "limit", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "offset": { @@ -112,6 +105,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "offset", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -122,7 +124,7 @@ "type": "named" } } - ], + }, "object_types": { "CreateNotificationSuccessResponse": { "fields": { @@ -133,6 +135,9 @@ "name": "Notification200Errors", "type": "named" } + }, + "rest": { + "type": null } }, "external_id": { @@ -142,6 +147,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -151,6 +161,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "recipients": { @@ -160,6 +175,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -174,6 +194,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "errored": { @@ -184,6 +209,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "failed": { @@ -194,6 +224,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "received": { @@ -204,6 +239,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "successful": { @@ -214,6 +254,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -225,6 +270,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "key": { @@ -235,6 +285,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "relation": { @@ -242,6 +297,11 @@ "type": { "name": "FilterRelation", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { @@ -252,12 +312,105 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } }, "NotificationInput": { "fields": { + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "data": { + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + }, + "rest": { + "type": [ + "array" + ] + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "type": [ + "string" + ] + } + }, "send_after": { "type": { "type": "nullable", @@ -265,6 +418,26 @@ "name": "TimestampTZ", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "date-time" + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -278,6 +451,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "notifications": { @@ -290,6 +468,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "offset": { @@ -299,6 +482,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "total_count": { @@ -308,6 +496,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -322,6 +515,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "contents": { @@ -331,6 +530,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "converted": { @@ -341,24 +545,11 @@ "name": "Int32", "type": "named" } - } - }, - "custom_data": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "JSON", - "type": "named" - } - } - }, - "data": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "JSON", - "type": "named" - } + }, + "rest": { + "type": [ + "integer" + ] } }, "errored": { @@ -369,6 +560,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "excluded_segments": { @@ -381,6 +577,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "failed": { @@ -391,6 +597,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "filters": { @@ -403,6 +614,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "headings": { @@ -412,6 +628,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -421,6 +642,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "include_player_ids": { @@ -433,6 +659,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "included_segments": { @@ -445,6 +681,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "outcomes": { @@ -457,6 +703,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "platform_delivery_stats": { @@ -467,6 +718,11 @@ "name": "PlatformDeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "queued_at": { @@ -477,6 +733,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "received": { @@ -487,6 +749,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "remaining": { @@ -497,6 +764,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "send_after": { @@ -507,6 +779,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "subtitle": { @@ -516,6 +794,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "successful": { @@ -526,6 +809,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "target_channel": { @@ -535,6 +823,11 @@ "name": "PlayerNotificationTargetTargetChannel", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "throttle_rate_per_minute": { @@ -545,6 +838,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -555,18 +853,33 @@ "type": { "name": "OutcomeDataAggregation", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "id": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -581,6 +894,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "chrome_web_push": { @@ -590,6 +908,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "edge_web_push": { @@ -599,6 +922,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "email": { @@ -608,6 +936,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "firefox_web_push": { @@ -617,6 +950,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "ios": { @@ -626,6 +964,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "safari_web_push": { @@ -635,6 +978,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "sms": { @@ -644,6 +992,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -658,13 +1011,18 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } } }, - "procedures": [ - { + "procedures": { + "hasuraOneSignalCreateNotification": { "request": { "url": "/notifications", "method": "post", @@ -674,10 +1032,7 @@ } ], "requestBody": { - "contentType": "application/json", - "schema": { - "type": "NotificationInput" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -689,6 +1044,9 @@ "type": { "name": "NotificationInput", "type": "named" + }, + "rest": { + "in": "body" } } }, @@ -699,7 +1057,7 @@ "type": "named" } } - ], + }, "scalar_types": { "FilterRelation": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.schema.json b/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.schema.json new file mode 100644 index 0000000..039649d --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.schema.json @@ -0,0 +1,744 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "app_id": { + "description": "The app ID that you want to view notifications from", + "type": { + "name": "String", + "type": "named" + } + }, + "kind": { + "description": "Kind of notifications returned:\n * unset - All notification types (default)\n * `0` - Dashboard only\n * `1` - API only\n * `3` - Automated only\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "limit": { + "description": "How many notifications to return. Max is 50. Default is 50.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "offset": { + "description": "Page offset. Default is 0. Results are sorted by queued_at in descending order. queued_at is a representation of the time that the notification was queued at.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "View notifications", + "name": "hasuraOneSignalGetNotifications", + "result_type": { + "name": "NotificationSlice", + "type": "named" + } + } + ], + "object_types": { + "CreateNotificationSuccessResponse": { + "fields": { + "errors": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Notification200Errors", + "type": "named" + } + } + }, + "external_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "recipients": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "DeliveryData": { + "fields": { + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "Filter": { + "fields": { + "field": { + "description": "Name of the field to use as the first operand in the filter expression.", + "type": { + "name": "String", + "type": "named" + } + }, + "key": { + "description": "If `field` is `tag`, this field is *required* to specify `key` inside the tags.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "relation": { + "description": "Operator of a filter expression.", + "type": { + "name": "FilterRelation", + "type": "named" + } + }, + "value": { + "description": "Constant value to use as the second operand in the filter expression. This value is *required* when the relation operator is a binary operator.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "NotificationInput": { + "fields": { + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "data": { + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "send_after": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + } + } + }, + "NotificationSlice": { + "fields": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "notifications": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "NotificationWithMeta", + "type": "named" + }, + "type": "array" + } + } + }, + "offset": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "total_count": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "NotificationWithMeta": { + "fields": { + "completed_at": { + "description": "Unix timestamp indicating when notification delivery completed. The delivery duration from start to finish can be calculated with completed_at - send_after.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "excluded_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "include_player_ids": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "included_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "outcomes": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "OutcomeData", + "type": "named" + }, + "type": "array" + } + } + }, + "platform_delivery_stats": { + "description": "Hash of delivery statistics broken out by target device platform.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlatformDeliveryData", + "type": "named" + } + } + }, + "queued_at": { + "description": "Unix timestamp indicating when the notification was created.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "remaining": { + "description": "Number of notifications that have not been sent out yet. This can mean either our system is still processing the notification or you have delayed options set.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "send_after": { + "description": "Unix timestamp indicating when notification delivery should begin.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "target_channel": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlayerNotificationTargetTargetChannel", + "type": "named" + } + } + }, + "throttle_rate_per_minute": { + "description": "number of push notifications sent per minute. Paid Feature Only. If throttling is not enabled for the app or the notification, and for free accounts, null is returned. Refer to Throttling for more details.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "OutcomeData": { + "fields": { + "aggregation": { + "type": { + "name": "OutcomeDataAggregation", + "type": "named" + } + }, + "id": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PlatformDeliveryData": { + "description": "Hash of delivery statistics broken out by target device platform.", + "fields": { + "android": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "chrome_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "edge_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "firefox_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "ios": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "safari_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "sms": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + } + } + }, + "StringMap": { + "fields": { + "en": { + "description": "Text in English. Will be used as a fallback", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Request body of POST /notifications", + "type": { + "name": "NotificationInput", + "type": "named" + } + } + }, + "description": "Create notification", + "name": "hasuraOneSignalCreateNotification", + "result_type": { + "name": "CreateNotificationSuccessResponse", + "type": "named" + } + } + ], + "scalar_types": { + "FilterRelation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "\u003e", + "\u003c", + "=", + "!=", + "exists", + "not_exists", + "time_elapsed_gt", + "time_elapsed_lt" + ], + "type": "enum" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "Notification200Errors": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "OutcomeDataAggregation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "sum", + "count" + ], + "type": "enum" + } + }, + "PlayerNotificationTargetTargetChannel": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + } + } +} diff --git a/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json b/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json index c3be9d5..e5965a4 100644 --- a/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json +++ b/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json @@ -28,45 +28,11 @@ }, "version": "1.2.2" }, - "collections": [], - "functions": [ - { + "functions": { + "hasuraGetNotifications": { "request": { "url": "/notifications", "method": "get", - "parameters": [ - { - "name": "app_id", - "in": "query", - "schema": { - "type": "String" - } - }, - { - "name": "kind", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "Int32", - "nullable": true - } - } - ], "security": [ { "app_key": [] @@ -82,6 +48,15 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "name": "app_id", + "in": "query", + "schema": { + "type": [ + "string" + ] + } } }, "kind": { @@ -92,6 +67,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "kind", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "limit": { @@ -102,6 +86,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "limit", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } }, "offset": { @@ -112,6 +105,15 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "name": "offset", + "in": "query", + "schema": { + "type": [ + "integer" + ] + } } } }, @@ -122,7 +124,7 @@ "type": "named" } } - ], + }, "object_types": { "CreateNotificationSuccessResponse": { "fields": { @@ -133,6 +135,9 @@ "name": "Notification200Errors", "type": "named" } + }, + "rest": { + "type": null } }, "external_id": { @@ -142,6 +147,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "id": { @@ -151,6 +161,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "recipients": { @@ -160,6 +175,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -174,6 +194,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "errored": { @@ -184,6 +209,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "failed": { @@ -194,6 +224,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "received": { @@ -204,6 +239,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "successful": { @@ -214,6 +254,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -225,6 +270,11 @@ "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "key": { @@ -235,6 +285,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "relation": { @@ -242,6 +297,11 @@ "type": { "name": "FilterRelation", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { @@ -252,12 +312,105 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } }, "NotificationInput": { "fields": { + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "data": { + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + }, + "rest": { + "type": [ + "array" + ] + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + }, + "rest": { + "type": [ + "string" + ] + } + }, "send_after": { "type": { "type": "nullable", @@ -265,6 +418,26 @@ "name": "TimestampTZ", "type": "named" } + }, + "rest": { + "type": [ + "string" + ], + "format": "date-time" + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -278,6 +451,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "notifications": { @@ -290,6 +468,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "offset": { @@ -299,6 +482,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "total_count": { @@ -308,6 +496,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -322,6 +515,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "contents": { @@ -331,6 +530,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "converted": { @@ -341,24 +545,11 @@ "name": "Int32", "type": "named" } - } - }, - "custom_data": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "JSON", - "type": "named" - } - } - }, - "data": { - "type": { - "type": "nullable", - "underlying_type": { - "name": "JSON", - "type": "named" - } + }, + "rest": { + "type": [ + "integer" + ] } }, "errored": { @@ -369,6 +560,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "excluded_segments": { @@ -381,6 +577,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "failed": { @@ -391,6 +597,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "filters": { @@ -403,6 +614,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "headings": { @@ -412,6 +628,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "id": { @@ -421,6 +642,11 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "include_player_ids": { @@ -433,6 +659,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "included_segments": { @@ -445,6 +681,16 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ], + "items": { + "type": [ + "string" + ] + } } }, "outcomes": { @@ -457,6 +703,11 @@ }, "type": "array" } + }, + "rest": { + "type": [ + "array" + ] } }, "platform_delivery_stats": { @@ -467,6 +718,11 @@ "name": "PlatformDeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "queued_at": { @@ -477,6 +733,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "received": { @@ -487,6 +749,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "remaining": { @@ -497,6 +764,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "send_after": { @@ -507,6 +779,12 @@ "name": "Int64", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ], + "format": "int64" } }, "subtitle": { @@ -516,6 +794,11 @@ "name": "StringMap", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "successful": { @@ -526,6 +809,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } }, "target_channel": { @@ -535,6 +823,11 @@ "name": "PlayerNotificationTargetTargetChannel", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } }, "throttle_rate_per_minute": { @@ -545,6 +838,11 @@ "name": "Int32", "type": "named" } + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -555,18 +853,33 @@ "type": { "name": "OutcomeDataAggregation", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "id": { "type": { "name": "String", "type": "named" + }, + "rest": { + "type": [ + "string" + ] } }, "value": { "type": { "name": "Int32", "type": "named" + }, + "rest": { + "type": [ + "integer" + ] } } } @@ -581,6 +894,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "chrome_web_push": { @@ -590,6 +908,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "edge_web_push": { @@ -599,6 +922,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "email": { @@ -608,6 +936,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "firefox_web_push": { @@ -617,6 +950,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "ios": { @@ -626,6 +964,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "safari_web_push": { @@ -635,6 +978,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } }, "sms": { @@ -644,6 +992,11 @@ "name": "DeliveryData", "type": "named" } + }, + "rest": { + "type": [ + "object" + ] } } } @@ -658,13 +1011,18 @@ "name": "String", "type": "named" } + }, + "rest": { + "type": [ + "string" + ] } } } } }, - "procedures": [ - { + "procedures": { + "hasuraCreateNotification": { "request": { "url": "/notifications", "method": "post", @@ -674,10 +1032,7 @@ } ], "requestBody": { - "contentType": "application/json", - "schema": { - "type": "NotificationInput" - } + "contentType": "application/json" }, "response": { "contentType": "application/json" @@ -689,6 +1044,9 @@ "type": { "name": "NotificationInput", "type": "named" + }, + "rest": { + "in": "body" } } }, @@ -699,7 +1057,7 @@ "type": "named" } } - ], + }, "scalar_types": { "FilterRelation": { "aggregate_functions": {}, diff --git a/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.schema.json b/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.schema.json new file mode 100644 index 0000000..776ee46 --- /dev/null +++ b/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.schema.json @@ -0,0 +1,744 @@ +{ + "collections": [], + "functions": [ + { + "arguments": { + "app_id": { + "description": "The app ID that you want to view notifications from", + "type": { + "name": "String", + "type": "named" + } + }, + "kind": { + "description": "Kind of notifications returned:\n * unset - All notification types (default)\n * `0` - Dashboard only\n * `1` - API only\n * `3` - Automated only\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "limit": { + "description": "How many notifications to return. Max is 50. Default is 50.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "offset": { + "description": "Page offset. Default is 0. Results are sorted by queued_at in descending order. queued_at is a representation of the time that the notification was queued at.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "View notifications", + "name": "hasuraGetNotifications", + "result_type": { + "name": "NotificationSlice", + "type": "named" + } + } + ], + "object_types": { + "CreateNotificationSuccessResponse": { + "fields": { + "errors": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Notification200Errors", + "type": "named" + } + } + }, + "external_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "recipients": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "DeliveryData": { + "fields": { + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "Filter": { + "fields": { + "field": { + "description": "Name of the field to use as the first operand in the filter expression.", + "type": { + "name": "String", + "type": "named" + } + }, + "key": { + "description": "If `field` is `tag`, this field is *required* to specify `key` inside the tags.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "relation": { + "description": "Operator of a filter expression.", + "type": { + "name": "FilterRelation", + "type": "named" + } + }, + "value": { + "description": "Constant value to use as the second operand in the filter expression. This value is *required* when the relation operator is a binary operator.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "NotificationInput": { + "fields": { + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "data": { + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "send_after": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + } + } + }, + "NotificationSlice": { + "fields": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "notifications": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "NotificationWithMeta", + "type": "named" + }, + "type": "array" + } + } + }, + "offset": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "total_count": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "NotificationWithMeta": { + "fields": { + "completed_at": { + "description": "Unix timestamp indicating when notification delivery completed. The delivery duration from start to finish can be calculated with completed_at - send_after.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "excluded_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "include_player_ids": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "included_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "outcomes": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "OutcomeData", + "type": "named" + }, + "type": "array" + } + } + }, + "platform_delivery_stats": { + "description": "Hash of delivery statistics broken out by target device platform.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlatformDeliveryData", + "type": "named" + } + } + }, + "queued_at": { + "description": "Unix timestamp indicating when the notification was created.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "remaining": { + "description": "Number of notifications that have not been sent out yet. This can mean either our system is still processing the notification or you have delayed options set.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "send_after": { + "description": "Unix timestamp indicating when notification delivery should begin.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "target_channel": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlayerNotificationTargetTargetChannel", + "type": "named" + } + } + }, + "throttle_rate_per_minute": { + "description": "number of push notifications sent per minute. Paid Feature Only. If throttling is not enabled for the app or the notification, and for free accounts, null is returned. Refer to Throttling for more details.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "OutcomeData": { + "fields": { + "aggregation": { + "type": { + "name": "OutcomeDataAggregation", + "type": "named" + } + }, + "id": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PlatformDeliveryData": { + "description": "Hash of delivery statistics broken out by target device platform.", + "fields": { + "android": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "chrome_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "edge_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "firefox_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "ios": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "safari_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "sms": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + } + } + }, + "StringMap": { + "fields": { + "en": { + "description": "Text in English. Will be used as a fallback", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "arguments": { + "body": { + "description": "Request body of POST /notifications", + "type": { + "name": "NotificationInput", + "type": "named" + } + } + }, + "description": "Create notification", + "name": "hasuraCreateNotification", + "result_type": { + "name": "CreateNotificationSuccessResponse", + "type": "named" + } + } + ], + "scalar_types": { + "FilterRelation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "\u003e", + "\u003c", + "=", + "!=", + "exists", + "not_exists", + "time_elapsed_gt", + "time_elapsed_lt" + ], + "type": "enum" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "Notification200Errors": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "OutcomeDataAggregation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "sum", + "count" + ], + "type": "enum" + } + }, + "PlayerNotificationTargetTargetChannel": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + } + } +} diff --git a/ndc-rest-schema/schema/enum.go b/ndc-rest-schema/schema/enum.go index c2b1d5a..c47770b 100644 --- a/ndc-rest-schema/schema/enum.go +++ b/ndc-rest-schema/schema/enum.go @@ -8,6 +8,8 @@ import ( "github.com/invopop/jsonschema" ) +const BodyKey = "body" + // SchemaSpecType represents the spec enum of schema type SchemaSpecType string diff --git a/ndc-rest-schema/schema/env.go b/ndc-rest-schema/schema/env.go index eb9e2bf..6b8480f 100644 --- a/ndc-rest-schema/schema/env.go +++ b/ndc-rest-schema/schema/env.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "regexp" + "slices" "strconv" "strings" @@ -192,6 +193,15 @@ func (et *EnvString) Value() *string { return ©Val } +// Equal checks if the current value equals the target +func (et EnvString) Equal(target EnvString) bool { + srcValue := et.Value() + targetValue := target.Value() + + return (srcValue == nil && targetValue == nil) || + (srcValue != nil && targetValue != nil && *srcValue == *targetValue) +} + // String implements the Stringer interface func (et EnvString) String() string { if et.IsEmpty() { @@ -303,6 +313,21 @@ func (j EnvInt) WithValue(value int64) *EnvInt { return &j } +// Equal checks if the current value equals the target +func (et EnvInt) Equal(target EnvInt) bool { + srcValue, err := et.Value() + if err != nil { + return false + } + targetValue, err := target.Value() + if err != nil { + return false + } + + return (srcValue == nil && targetValue == nil) || + (srcValue != nil && targetValue != nil && *srcValue == *targetValue) +} + // String implements the Stringer interface func (et EnvInt) String() string { if et.IsEmpty() { @@ -439,6 +464,20 @@ func (j EnvInts) WithValue(value []int64) *EnvInts { return &j } +// Equal checks if the current value equals the target +func (et EnvInts) Equal(target EnvInts) bool { + srcValue, err := et.Value() + if err != nil { + return false + } + targetValue, err := target.Value() + if err != nil { + return false + } + + return slices.Equal(srcValue, targetValue) +} + // String implements the Stringer interface func (et EnvInts) String() string { if et.IsEmpty() { @@ -584,6 +623,21 @@ func (j EnvBoolean) WithValue(value bool) *EnvBoolean { return &j } +// Equal checks if the current value equals the target +func (et EnvBoolean) Equal(target EnvBoolean) bool { + srcValue, err := et.Value() + if err != nil { + return false + } + targetValue, err := target.Value() + if err != nil { + return false + } + + return (srcValue == nil && targetValue == nil) || + (srcValue != nil && targetValue != nil && *srcValue == *targetValue) +} + // String implements the Stringer interface func (et EnvBoolean) String() string { if et.IsEmpty() { @@ -720,6 +774,20 @@ func (j EnvStrings) WithValue(value []string) *EnvStrings { return &j } +// Equal checks if the current value equals the target +func (et EnvStrings) Equal(target EnvStrings) bool { + srcValue, err := et.Value() + if err != nil { + return false + } + targetValue, err := target.Value() + if err != nil { + return false + } + + return slices.Equal(srcValue, targetValue) +} + // String implements the Stringer interface func (et EnvStrings) String() string { if et.IsEmpty() { diff --git a/ndc-rest-schema/schema/env_test.go b/ndc-rest-schema/schema/env_test.go index d1f0ff6..8873d0a 100644 --- a/ndc-rest-schema/schema/env_test.go +++ b/ndc-rest-schema/schema/env_test.go @@ -6,7 +6,9 @@ import ( "strings" "testing" + "github.com/hasura/ndc-sdk-go/utils" "gopkg.in/yaml.v3" + "gotest.tools/v3/assert" ) func TestEnvTemplate(t *testing.T) { @@ -42,7 +44,7 @@ func TestEnvTemplate(t *testing.T) { templates: []EnvTemplate{ { Name: "SERVER_URL", - DefaultValue: toPtr(""), + DefaultValue: utils.ToPtr(""), }, }, templateStr: "{{SERVER_URL:-}}", @@ -53,7 +55,7 @@ func TestEnvTemplate(t *testing.T) { templates: []EnvTemplate{ { Name: "SERVER_URL", - DefaultValue: toPtr("http://localhost:8080"), + DefaultValue: utils.ToPtr("http://localhost:8080"), }, { Name: "SERVER_URL", @@ -72,44 +74,44 @@ func TestEnvTemplate(t *testing.T) { t.Errorf("failed to find env template, expected nil, got %s", tmpl) } } else { - assertDeepEqual(t, tc.templates[0].String(), tmpl.String()) + assert.DeepEqual(t, tc.templates[0].String(), tmpl.String()) var jTemplate EnvTemplate if err := json.Unmarshal([]byte(fmt.Sprintf(`"%s"`, tc.input)), &jTemplate); err != nil { t.Errorf("failed to unmarshal template from json: %s", err) t.FailNow() } - assertDeepEqual(t, jTemplate, *tmpl) + assert.DeepEqual(t, jTemplate, *tmpl) bs, err := json.Marshal(jTemplate) if err != nil { t.Errorf("failed to marshal template from json: %s", err) t.FailNow() } - assertDeepEqual(t, tmpl.String(), strings.Trim(string(bs), `"`)) + assert.DeepEqual(t, tmpl.String(), strings.Trim(string(bs), `"`)) if err := yaml.Unmarshal([]byte(fmt.Sprintf(`"%s"`, tc.input)), &jTemplate); err != nil { t.Errorf("failed to unmarshal template from yaml: %s", err) t.FailNow() } - assertDeepEqual(t, jTemplate, *tmpl) + assert.DeepEqual(t, jTemplate, *tmpl) bs, err = yaml.Marshal(jTemplate) if err != nil { t.Errorf("failed to marshal template from yaml: %s", err) t.FailNow() } - assertDeepEqual(t, tmpl.String(), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + assert.DeepEqual(t, tmpl.String(), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) } templates := FindAllEnvTemplates(tc.input) - assertDeepEqual(t, tc.templates, templates) + assert.DeepEqual(t, tc.templates, templates) templateStrings := []string{} for i, item := range templates { - assertDeepEqual(t, tc.templates[i].String(), item.String()) + assert.DeepEqual(t, tc.templates[i].String(), item.String()) templateStrings = append(templateStrings, item.String()) } - assertDeepEqual(t, tc.expected, ReplaceEnvTemplates(tc.input, templates)) + assert.DeepEqual(t, tc.expected, ReplaceEnvTemplates(tc.input, templates)) if len(templateStrings) > 0 { - assertDeepEqual(t, tc.templateStr, strings.Join(templateStrings, ",")) + assert.DeepEqual(t, tc.templateStr, strings.Join(templateStrings, ",")) } }) } @@ -124,7 +126,7 @@ func TestEnvString(t *testing.T) { input: `"{{FOO:-bar}}"`, expected: *NewEnvStringTemplate(EnvTemplate{ Name: "FOO", - DefaultValue: toPtr("bar"), + DefaultValue: utils.ToPtr("bar"), }), }, { @@ -140,13 +142,13 @@ func TestEnvString(t *testing.T) { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) bs, err := yaml.Marshal(result) if err != nil { t.Fatal(t, err) } - assertDeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + assert.DeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) result.JSONSchema() }) } @@ -159,7 +161,7 @@ func TestEnvInt(t *testing.T) { }{ { input: `400`, - expected: EnvInt{value: toPtr[int64](400)}, + expected: EnvInt{value: utils.ToPtr[int64](400)}, }, { input: `"400"`, @@ -168,10 +170,10 @@ func TestEnvInt(t *testing.T) { { input: `"{{FOO:-401}}"`, expected: EnvInt{ - value: toPtr(int64(401)), + value: utils.ToPtr(int64(401)), EnvTemplate: EnvTemplate{ Name: "FOO", - DefaultValue: toPtr("401"), + DefaultValue: utils.ToPtr("401"), }, }, }, @@ -184,21 +186,21 @@ func TestEnvInt(t *testing.T) { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, tc.expected.value, result.value) if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, tc.expected.value, result.value) - assertDeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) bs, err := yaml.Marshal(result) if err != nil { t.Fatal(t, err) } - assertDeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + assert.DeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) result.JSONSchema() }) } @@ -230,7 +232,7 @@ func TestEnvInts(t *testing.T) { value: []int64{400, 401, 403}, EnvTemplate: EnvTemplate{ Name: "FOO", - DefaultValue: toPtr("400, 401, 403"), + DefaultValue: utils.ToPtr("400, 401, 403"), }, }, expectedYaml: `{{FOO:-400, 401, 403}}`, @@ -244,20 +246,20 @@ func TestEnvInts(t *testing.T) { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, tc.expected.value, result.value) if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.String(), result.String()) - assertDeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, tc.expected.String(), result.String()) + assert.DeepEqual(t, tc.expected.value, result.value) bs, err := yaml.Marshal(result) if err != nil { t.Fatal(t, err) } - assertDeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + assert.DeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) result.JSONSchema() }) } @@ -280,10 +282,10 @@ func TestEnvBoolean(t *testing.T) { { input: `"{{FOO:-true}}"`, expected: EnvBoolean{ - value: toPtr(true), + value: utils.ToPtr(true), EnvTemplate: EnvTemplate{ Name: "FOO", - DefaultValue: toPtr("true"), + DefaultValue: utils.ToPtr("true"), }, }, }, @@ -292,7 +294,7 @@ func TestEnvBoolean(t *testing.T) { Name: "TEST_BOOL", }).String()), expected: EnvBoolean{ - value: toPtr(false), + value: utils.ToPtr(false), EnvTemplate: EnvTemplate{ Name: "TEST_BOOL", }, @@ -307,21 +309,21 @@ func TestEnvBoolean(t *testing.T) { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, tc.expected.value, result.value) if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, tc.expected.value, result.value) - assertDeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) bs, err := yaml.Marshal(result) if err != nil { t.Fatal(t, err) } - assertDeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + assert.DeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) result.JSONSchema() if err = (&EnvBoolean{}).UnmarshalText([]byte(strings.Trim(tc.input, `"`))); err != nil { t.Error(t, err) @@ -366,7 +368,7 @@ func TestEnvStrings(t *testing.T) { value: []string{"foo", "bar"}, EnvTemplate: EnvTemplate{ Name: "FOO", - DefaultValue: toPtr("foo, bar"), + DefaultValue: utils.ToPtr("foo, bar"), }, }, expectedYaml: `{{FOO:-foo, bar}}`, @@ -380,20 +382,20 @@ func TestEnvStrings(t *testing.T) { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assertDeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assert.DeepEqual(t, tc.expected.value, result.value) if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { t.Error(t, err) t.FailNow() } - assertDeepEqual(t, tc.expected.String(), result.String()) - assertDeepEqual(t, tc.expected.value, result.value) + assert.DeepEqual(t, tc.expected.String(), result.String()) + assert.DeepEqual(t, tc.expected.value, result.value) bs, err := yaml.Marshal(result) if err != nil { t.Fatal(t, err) } - assertDeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + assert.DeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) result.JSONSchema() }) } diff --git a/ndc-rest-schema/schema/schema.go b/ndc-rest-schema/schema/schema.go index 1a642fd..801509a 100644 --- a/ndc-rest-schema/schema/schema.go +++ b/ndc-rest-schema/schema/schema.go @@ -2,8 +2,11 @@ package schema import ( "encoding/json" + "errors" + "fmt" "github.com/hasura/ndc-sdk-go/schema" + "github.com/hasura/ndc-sdk-go/utils" ) // NDCRestSchema extends the [NDC SchemaResponse] with OpenAPI REST information @@ -13,18 +16,15 @@ type NDCRestSchema struct { SchemaRef string `json:"$schema,omitempty" mapstructure:"$schema" yaml:"$schema,omitempty"` Settings *NDCRestSettings `json:"settings,omitempty" mapstructure:"settings" yaml:"settings,omitempty"` - // Collections which are available for queries - Collections []schema.CollectionInfo `json:"collections" mapstructure:"collections" yaml:"collections"` - // Functions (i.e. collections which return a single column and row) - Functions []*RESTFunctionInfo `json:"functions" mapstructure:"functions" yaml:"functions"` + Functions map[string]OperationInfo `json:"functions" mapstructure:"functions" yaml:"functions"` // A list of object types which can be used as the types of arguments, or return // types of procedures. Names should not overlap with scalar type names. - ObjectTypes schema.SchemaResponseObjectTypes `json:"object_types" mapstructure:"object_types" yaml:"object_types"` + ObjectTypes map[string]ObjectType `json:"object_types" mapstructure:"object_types" yaml:"object_types"` // Procedures which are available for execution as part of mutations - Procedures []*RESTProcedureInfo `json:"procedures" mapstructure:"procedures" yaml:"procedures"` + Procedures map[string]OperationInfo `json:"procedures" mapstructure:"procedures" yaml:"procedures"` // A list of scalar types which will be used as the types of collection columns ScalarTypes schema.SchemaResponseScalarTypes `json:"scalar_types" mapstructure:"scalar_types" yaml:"scalar_types"` @@ -35,46 +35,72 @@ func NewNDCRestSchema() *NDCRestSchema { return &NDCRestSchema{ SchemaRef: "https://raw.githubusercontent.com/hasura/ndc-rest-schema/main/jsonschema/ndc-rest-schema.jsonschema", Settings: &NDCRestSettings{}, - Collections: []schema.CollectionInfo{}, - Functions: []*RESTFunctionInfo{}, - Procedures: []*RESTProcedureInfo{}, - ObjectTypes: make(schema.SchemaResponseObjectTypes), + Functions: map[string]OperationInfo{}, + Procedures: map[string]OperationInfo{}, + ObjectTypes: make(map[string]ObjectType), ScalarTypes: make(schema.SchemaResponseScalarTypes), } } // ToSchemaResponse converts the instance to NDC schema.SchemaResponse func (ndc NDCRestSchema) ToSchemaResponse() *schema.SchemaResponse { - functions := make([]schema.FunctionInfo, len(ndc.Functions)) - for i, fn := range ndc.Functions { - functions[i] = fn.FunctionInfo - } - procedures := make([]schema.ProcedureInfo, len(ndc.Procedures)) - for i, proc := range ndc.Procedures { - procedures[i] = proc.ProcedureInfo + functionKeys := utils.GetSortedKeys(ndc.Functions) + functions := make([]schema.FunctionInfo, len(functionKeys)) + for i, key := range functionKeys { + fn := ndc.Functions[key] + functions[i] = fn.FunctionSchema(key) } + procedureKeys := utils.GetSortedKeys(ndc.Procedures) + procedures := make([]schema.ProcedureInfo, len(procedureKeys)) + for i, key := range procedureKeys { + proc := ndc.Procedures[key] + procedures[i] = proc.ProcedureSchema(key) + } + objectTypes := make(schema.SchemaResponseObjectTypes) + for key, object := range ndc.ObjectTypes { + objectTypes[key] = object.Schema() + } return &schema.SchemaResponse{ - Collections: ndc.Collections, - ObjectTypes: ndc.ObjectTypes, + Collections: []schema.CollectionInfo{}, ScalarTypes: ndc.ScalarTypes, + ObjectTypes: objectTypes, Functions: functions, Procedures: procedures, } } +// GetFunction gets the NDC function by name +func (rm NDCRestSchema) GetFunction(name string) *OperationInfo { + fn, ok := rm.Functions[name] + if !ok { + return nil + } + + return &fn +} + +// GetProcedure gets the NDC procedure by name +func (rm NDCRestSchema) GetProcedure(name string) *OperationInfo { + fn, ok := rm.Procedures[name] + if !ok { + return nil + } + + return &fn +} + type Response struct { ContentType string `json:"contentType" mapstructure:"contentType" yaml:"contentType"` } // Request represents the HTTP request information of the webhook type Request struct { - URL string `json:"url,omitempty" mapstructure:"url" yaml:"url,omitempty"` - Method string `json:"method,omitempty" jsonschema:"enum=get,enum=post,enum=put,enum=patch,enum=delete" mapstructure:"method" yaml:"method,omitempty"` - Type RequestType `json:"type,omitempty" mapstructure:"type" yaml:"type,omitempty"` - Headers map[string]EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` - Parameters []RequestParameter `json:"parameters,omitempty" mapstructure:"parameters" yaml:"parameters,omitempty"` - Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` + URL string `json:"url,omitempty" mapstructure:"url" yaml:"url,omitempty"` + Method string `json:"method,omitempty" jsonschema:"enum=get,enum=post,enum=put,enum=patch,enum=delete" mapstructure:"method" yaml:"method,omitempty"` + Type RequestType `json:"type,omitempty" mapstructure:"type" yaml:"type,omitempty"` + Headers map[string]EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` + Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` // configure the request timeout in seconds, default 30s Timeout uint `json:"timeout,omitempty" mapstructure:"timeout" yaml:"timeout,omitempty"` Servers []ServerConfig `json:"servers,omitempty" mapstructure:"servers" yaml:"servers,omitempty"` @@ -90,7 +116,6 @@ func (r Request) Clone() *Request { Method: r.Method, Type: r.Type, Headers: r.Headers, - Parameters: r.Parameters, Timeout: r.Timeout, Retry: r.Retry, Security: r.Security, @@ -103,30 +128,26 @@ func (r Request) Clone() *Request { // RequestParameter represents an HTTP request parameter type RequestParameter struct { EncodingObject `yaml:",inline"` - - Name string `json:"name,omitempty" mapstructure:"name" yaml:"name,omitempty"` - ArgumentName string `json:"argumentName,omitempty" mapstructure:"argumentName,omitempty" yaml:"argumentName,omitempty"` - In ParameterLocation `json:"in,omitempty" mapstructure:"in" yaml:"in,omitempty"` - Schema *TypeSchema `json:"schema,omitempty" mapstructure:"schema" yaml:"schema,omitempty"` + Name string `json:"name,omitempty" mapstructure:"name" yaml:"name,omitempty"` + ArgumentName string `json:"argumentName,omitempty" mapstructure:"argumentName,omitempty" yaml:"argumentName,omitempty"` + In ParameterLocation `json:"in,omitempty" mapstructure:"in" yaml:"in,omitempty"` + Schema *TypeSchema `json:"schema,omitempty" mapstructure:"schema" yaml:"schema,omitempty"` } // TypeSchema represents a serializable object of OpenAPI schema // that is used for validation type TypeSchema struct { - Type string `json:"type" mapstructure:"type" yaml:"type"` - Format string `json:"format,omitempty" mapstructure:"format" yaml:"format,omitempty"` - Pattern string `json:"pattern,omitempty" mapstructure:"pattern" yaml:"pattern,omitempty"` - Nullable bool `json:"nullable,omitempty" mapstructure:"nullable" yaml:"nullable,omitempty"` - Maximum *float64 `json:"maximum,omitempty" mapstructure:"maximum" yaml:"maximum,omitempty"` - Minimum *float64 `json:"minimum,omitempty," mapstructure:"minimum" yaml:"minimum,omitempty"` - MaxLength *int64 `json:"maxLength,omitempty" mapstructure:"maxLength" yaml:"maxLength,omitempty"` - MinLength *int64 `json:"minLength,omitempty" mapstructure:"minLength" yaml:"minLength,omitempty"` - Enum []string `json:"enum,omitempty" mapstructure:"enum" yaml:"enum,omitempty"` - Items *TypeSchema `json:"items,omitempty" mapstructure:"items" yaml:"items,omitempty"` - Properties map[string]TypeSchema `json:"properties,omitempty" mapstructure:"properties" yaml:"properties,omitempty"` - Description string `json:"-" yaml:"-"` - ReadOnly bool `json:"-" yaml:"-"` - WriteOnly bool `json:"-" yaml:"-"` + Type []string `json:"type" mapstructure:"type" yaml:"type"` + Format string `json:"format,omitempty" mapstructure:"format" yaml:"format,omitempty"` + Pattern string `json:"pattern,omitempty" mapstructure:"pattern" yaml:"pattern,omitempty"` + Maximum *float64 `json:"maximum,omitempty" mapstructure:"maximum" yaml:"maximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty," mapstructure:"minimum" yaml:"minimum,omitempty"` + MaxLength *int64 `json:"maxLength,omitempty" mapstructure:"maxLength" yaml:"maxLength,omitempty"` + MinLength *int64 `json:"minLength,omitempty" mapstructure:"minLength" yaml:"minLength,omitempty"` + Items *TypeSchema `json:"items,omitempty" mapstructure:"items" yaml:"items,omitempty"` + Description string `json:"-" yaml:"-"` + ReadOnly bool `json:"-" yaml:"-"` + WriteOnly bool `json:"-" yaml:"-"` } // RetryPolicy represents the retry policy of request @@ -165,21 +186,45 @@ type EncodingObject struct { Headers map[string]RequestParameter `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` } +// SetHeader sets the encoding header +func (eo *EncodingObject) SetHeader(key string, param RequestParameter) { + if eo.Headers == nil { + eo.Headers = make(map[string]RequestParameter) + } + eo.Headers[key] = param +} + +// GetHeader gets the encoding header by key +func (eo *EncodingObject) GetHeader(key string) *RequestParameter { + if len(eo.Headers) == 0 { + return nil + } + result, ok := eo.Headers[key] + if !ok { + return nil + } + return &result +} + // RequestBody defines flexible request body with content types type RequestBody struct { ContentType string `json:"contentType,omitempty" mapstructure:"contentType" yaml:"contentType,omitempty"` - Schema *TypeSchema `json:"schema,omitempty" mapstructure:"schema" yaml:"schema,omitempty"` Encoding map[string]EncodingObject `json:"encoding,omitempty" mapstructure:"encoding" yaml:"encoding,omitempty"` } -// RESTFunctionInfo extends NDC query function with OpenAPI REST information -type RESTFunctionInfo struct { - Request *Request `json:"request" mapstructure:"request" yaml:"request"` - schema.FunctionInfo `yaml:",inline"` +// OperationInfo extends connector command operation with OpenAPI REST information +type OperationInfo struct { + Request *Request `json:"request" mapstructure:"request" yaml:"request"` + // Any arguments that this collection requires + Arguments map[string]ArgumentInfo `json:"arguments" mapstructure:"arguments" yaml:"arguments"` + // Column description + Description *string `json:"description,omitempty" mapstructure:"description,omitempty" yaml:"description,omitempty"` + // The name of the result type + ResultType schema.Type `json:"result_type" mapstructure:"result_type" yaml:"result_type"` } // UnmarshalJSON implements json.Unmarshaler. -func (j *RESTFunctionInfo) UnmarshalJSON(b []byte) error { +func (j *OperationInfo) UnmarshalJSON(b []byte) error { var raw map[string]json.RawMessage if err := json.Unmarshal(b, &raw); err != nil { return err @@ -194,48 +239,179 @@ func (j *RESTFunctionInfo) UnmarshalJSON(b []byte) error { j.Request = &request } - var function schema.FunctionInfo - if err := function.UnmarshalJSONMap(raw); err != nil { - return err + rawArguments, ok := raw["arguments"] + if ok { + var arguments map[string]ArgumentInfo + if err := json.Unmarshal(rawArguments, &arguments); err != nil { + return err + } + j.Arguments = arguments + } + + rawResultType, ok := raw["result_type"] + if !ok { + return errors.New("field result_type in ProcedureInfo: required") + } + var resultType schema.Type + if err := json.Unmarshal(rawResultType, &resultType); err != nil { + return fmt.Errorf("field result_type in ProcedureInfo: %w", err) + } + j.ResultType = resultType + + if rawDescription, ok := raw["description"]; ok { + var description string + if err := json.Unmarshal(rawDescription, &description); err != nil { + return fmt.Errorf("field description in ProcedureInfo: %w", err) + } + j.Description = &description } - j.FunctionInfo = function return nil } -// RESTProcedureInfo extends NDC mutation procedure with OpenAPI REST information -type RESTProcedureInfo struct { - Request *Request `json:"request" mapstructure:"request" yaml:"request"` - schema.ProcedureInfo `yaml:",inline"` +// Schema returns the connector schema of the function +func (j OperationInfo) FunctionSchema(name string) schema.FunctionInfo { + arguments := make(schema.FunctionInfoArguments) + for key, argument := range j.Arguments { + arguments[key] = argument.ArgumentInfo + } + return schema.FunctionInfo{ + Name: name, + Arguments: arguments, + Description: j.Description, + ResultType: j.ResultType, + } +} + +// Schema returns the connector schema of the function +func (j OperationInfo) ProcedureSchema(name string) schema.ProcedureInfo { + arguments := make(schema.ProcedureInfoArguments) + for key, argument := range j.Arguments { + arguments[key] = argument.ArgumentInfo + } + return schema.ProcedureInfo{ + Name: name, + Arguments: arguments, + Description: j.Description, + ResultType: j.ResultType, + } +} + +// ObjectType represents the object type of rest schema +type ObjectType struct { + // Description of this type + Description *string `json:"description,omitempty" mapstructure:"description,omitempty" yaml:"description,omitempty"` + // Fields defined on this object type + Fields map[string]ObjectField `json:"fields" mapstructure:"fields" yaml:"fields"` +} + +// Schema returns schema the object field +func (of ObjectType) Schema() schema.ObjectType { + result := schema.ObjectType{ + Description: of.Description, + Fields: schema.ObjectTypeFields{}, + } + + for key, field := range of.Fields { + result.Fields[key] = field.Schema() + } + return result +} + +// ObjectField defined on this object type +type ObjectField struct { + schema.ObjectField `yaml:",inline"` + + // The field schema information of the REST request + Rest *TypeSchema `json:"rest,omitempty" mapstructure:"rest" yaml:"rest,omitempty"` +} + +// Schema returns schema the object field +func (of ObjectField) Schema() schema.ObjectField { + return of.ObjectField } // UnmarshalJSON implements json.Unmarshaler. -func (j *RESTProcedureInfo) UnmarshalJSON(b []byte) error { +func (j *ObjectField) UnmarshalJSON(b []byte) error { var raw map[string]json.RawMessage if err := json.Unmarshal(b, &raw); err != nil { return err } + rawType, ok := raw["type"] + if !ok || len(rawType) == 0 { + return errors.New("field type in ObjectField: required") + } - rawReq, ok := raw["request"] - if ok { - var request Request - if err := json.Unmarshal(rawReq, &request); err != nil { - return err + if err := json.Unmarshal(rawType, &j.Type); err != nil { + return fmt.Errorf("field type in ObjectField: %w", err) + } + + if rawDesc, ok := raw["description"]; ok { + var desc string + if err := json.Unmarshal(rawDesc, &desc); err != nil { + return fmt.Errorf("field description in ObjectField: %w", err) } - j.Request = &request + j.Description = &desc + } + if rawArguments, ok := raw["arguments"]; ok { + var arguments schema.ObjectFieldArguments + if err := json.Unmarshal(rawArguments, &arguments); err != nil { + return fmt.Errorf("field arguments in ObjectField: %w", err) + } + j.Arguments = arguments } - var procedure schema.ProcedureInfo - if err := procedure.UnmarshalJSONMap(raw); err != nil { - return err + if rawType, ok := raw["rest"]; ok { + var ty TypeSchema + if err := json.Unmarshal(rawType, &ty); err != nil { + return fmt.Errorf("field rest in ObjectField: %w", err) + } + j.Rest = &ty } - j.ProcedureInfo = procedure return nil } -func toPtr[V any](value V) *V { - return &value +// ArgumentInfo the information of REST request argument +type ArgumentInfo struct { + schema.ArgumentInfo `yaml:",inline"` + + // The request parameter information of the REST request + Rest *RequestParameter `json:"rest,omitempty" mapstructure:"rest" yaml:"rest,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ArgumentInfo) UnmarshalJSON(b []byte) error { + var raw map[string]json.RawMessage + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + rawType, ok := raw["type"] + if !ok || len(rawType) == 0 { + return errors.New("field type in ArgumentInfo: required") + } + + if err := json.Unmarshal(rawType, &j.Type); err != nil { + return fmt.Errorf("field type in ArgumentInfo: %w", err) + } + + if rawDesc, ok := raw["description"]; ok { + var desc string + if err := json.Unmarshal(rawDesc, &desc); err != nil { + return fmt.Errorf("field description in ArgumentInfo: %w", err) + } + j.Description = &desc + } + + if rawParameter, ok := raw["rest"]; ok { + var param RequestParameter + if err := json.Unmarshal(rawParameter, ¶m); err != nil { + return fmt.Errorf("field rest in ArgumentInfo: %w", err) + } + j.Rest = ¶m + } + + return nil } func toAnySlice[T any](values []T) []any { diff --git a/ndc-rest-schema/schema/schema_test.go b/ndc-rest-schema/schema/schema_test.go index e4cf1b7..c3d9ce6 100644 --- a/ndc-rest-schema/schema/schema_test.go +++ b/ndc-rest-schema/schema/schema_test.go @@ -2,25 +2,18 @@ package schema import ( "encoding/json" - "fmt" - "reflect" - "strings" "testing" "github.com/hasura/ndc-sdk-go/schema" + "github.com/hasura/ndc-sdk-go/utils" + "gotest.tools/v3/assert" ) -func assertDeepEqual(_ *testing.T, expected any, reality any, msgs ...string) { - if !reflect.DeepEqual(expected, reality) { - panic(fmt.Errorf("%s: not equal\nexpected: %+v\ngot : %+v", strings.Join(msgs, " "), expected, reality)) - } -} - func TestDecodeRESTProcedureInfo(t *testing.T) { testCases := []struct { name string raw string - expected RESTProcedureInfo + expected OperationInfo }{ { name: "success", @@ -34,30 +27,27 @@ func TestDecodeRESTProcedureInfo(t *testing.T) { "underlying_type": { "name": "Boolean", "type": "named" } } }`, - expected: RESTProcedureInfo{ + expected: OperationInfo{ Request: &Request{ URL: "/pets", Method: "post", }, - ProcedureInfo: schema.ProcedureInfo{ - Arguments: make(schema.ProcedureInfoArguments), - Description: toPtr("Create a pet"), - Name: "createPets", - ResultType: schema.NewNullableNamedType("Boolean").Encode(), - }, + Arguments: map[string]ArgumentInfo{}, + Description: utils.ToPtr("Create a pet"), + ResultType: schema.NewNullableNamedType("Boolean").Encode(), }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - var procedure RESTProcedureInfo + var procedure OperationInfo if err := json.Unmarshal([]byte(tc.raw), &procedure); err != nil { t.Errorf("failed to unmarshal: %s", err) t.FailNow() } - assertDeepEqual(t, tc.expected, procedure) - assertDeepEqual(t, tc.expected.Request.Clone(), procedure.Request.Clone()) + assert.DeepEqual(t, tc.expected, procedure) + assert.DeepEqual(t, tc.expected.Request.Clone(), procedure.Request.Clone()) }) } } @@ -66,21 +56,14 @@ func TestDecodeRESTFunctionInfo(t *testing.T) { testCases := []struct { name string raw string - expected RESTFunctionInfo + expected OperationInfo }{ { name: "success", - raw: ` { + raw: `{ "request": { "url": "/pets", - "method": "get", - "parameters": [ - { - "name": "limit", - "in": "query", - "schema": { "type": "integer", "maximum": 100, "format": "int32", "nullable": true } - } - ] + "method": "get" }, "arguments": { "limit": { @@ -88,6 +71,11 @@ func TestDecodeRESTFunctionInfo(t *testing.T) { "type": { "type": "nullable", "underlying_type": { "name": "Int", "type": "named" } + }, + "rest": { + "name": "limit", + "in": "query", + "schema": { "type": ["integer"], "maximum": 100, "format": "int32", "nullable": true } } } }, @@ -98,48 +86,43 @@ func TestDecodeRESTFunctionInfo(t *testing.T) { "type": "array" } }`, - expected: RESTFunctionInfo{ + expected: OperationInfo{ Request: &Request{ URL: "/pets", Method: "get", - Parameters: []RequestParameter{ - { + }, + Arguments: map[string]ArgumentInfo{ + "limit": { + ArgumentInfo: schema.ArgumentInfo{ + Description: utils.ToPtr("How many items to return at one time (max 100)"), + Type: schema.NewNullableNamedType("Int").Encode(), + }, + Rest: &RequestParameter{ Name: "limit", In: "query", Schema: &TypeSchema{ - Type: "integer", - Maximum: toPtr(float64(100)), - Format: "int32", - Nullable: true, + Type: []string{"integer"}, + Maximum: utils.ToPtr(float64(100)), + Format: "int32", }, }, }, }, - FunctionInfo: schema.FunctionInfo{ - Arguments: schema.FunctionInfoArguments{ - "limit": schema.ArgumentInfo{ - Description: toPtr("How many items to return at one time (max 100)"), - Type: schema.NewNullableNamedType("Int").Encode(), - }, - }, - Description: toPtr("List all pets"), - Name: "listPets", - ResultType: schema.NewArrayType(schema.NewNamedType("Pet")).Encode(), - }, + Description: utils.ToPtr("List all pets"), + ResultType: schema.NewArrayType(schema.NewNamedType("Pet")).Encode(), }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - var fn RESTFunctionInfo + var fn OperationInfo if err := json.Unmarshal([]byte(tc.raw), &fn); err != nil { t.Errorf("failed to unmarshal: %s", err) t.FailNow() } - assertDeepEqual(t, tc.expected, fn) - assertDeepEqual(t, tc.expected.Request.Clone(), fn.Request.Clone()) - + assert.DeepEqual(t, tc.expected, fn) + assert.DeepEqual(t, tc.expected.Request.Clone(), fn.Request.Clone()) }) } } diff --git a/ndc-rest-schema/schema/setting_test.go b/ndc-rest-schema/schema/setting_test.go index e7ed51b..545a903 100644 --- a/ndc-rest-schema/schema/setting_test.go +++ b/ndc-rest-schema/schema/setting_test.go @@ -3,6 +3,8 @@ package schema import ( "encoding/json" "testing" + + "gotest.tools/v3/assert" ) func TestNDCRestSettings(t *testing.T) { @@ -112,14 +114,16 @@ func TestNDCRestSettings(t *testing.T) { t.FailNow() } for i, s := range tc.expected.Servers { - assertDeepEqual(t, s.URL.String(), result.Servers[i].URL.String()) + assert.DeepEqual(t, s.URL.String(), result.Servers[i].URL.String()) } - assertDeepEqual(t, tc.expected.Headers, result.Headers) - assertDeepEqual(t, tc.expected.Retry, result.Retry) - assertDeepEqual(t, tc.expected.Security, result.Security) - assertDeepEqual(t, tc.expected.SecuritySchemes, result.SecuritySchemes) - assertDeepEqual(t, tc.expected.Timeout, result.Timeout) - assertDeepEqual(t, tc.expected.Version, result.Version) + assert.DeepEqual(t, tc.expected.Headers, result.Headers) + assert.DeepEqual(t, tc.expected.Retry.Delay.String(), result.Retry.Delay.String()) + assert.DeepEqual(t, tc.expected.Retry.Times.String(), result.Retry.Times.String()) + assert.DeepEqual(t, tc.expected.Retry.HTTPStatus.String(), result.Retry.HTTPStatus.String()) + assert.DeepEqual(t, tc.expected.Security, result.Security) + assert.DeepEqual(t, tc.expected.SecuritySchemes, result.SecuritySchemes) + assert.DeepEqual(t, tc.expected.Timeout, result.Timeout) + assert.DeepEqual(t, tc.expected.Version, result.Version) _, err := json.Marshal(tc.expected) if err != nil { diff --git a/ndc-rest-schema/utils/string.go b/ndc-rest-schema/utils/string.go index b69e536..fa031b6 100644 --- a/ndc-rest-schema/utils/string.go +++ b/ndc-rest-schema/utils/string.go @@ -5,12 +5,18 @@ import ( "regexp" "strings" "unicode" + "unicode/utf8" ) var ( nonAlphaDigitRegex = regexp.MustCompile(`[^\w]+`) ) +const ( + htmlTagStart = 60 // Unicode `<` + htmlTagEnd = 62 // Unicode `>` +) + // ToCamelCase convert a string to camelCase func ToCamelCase(input string) string { pascalCase := ToPascalCase(input) @@ -133,3 +139,43 @@ func SplitStringsAndTrimSpaces(input string, sep string) []string { return results } + +// StripHTMLTags aggressively strips HTML tags from a string. It will only keep anything between `>` and `<`. +func StripHTMLTags(str string) string { + // Setup a string builder and allocate enough memory for the new string. + var builder strings.Builder + builder.Grow(len(str) + utf8.UTFMax) + + in := false // True if we are inside an HTML tag. + start := 0 // The index of the previous start tag character `<` + end := 0 // The index of the previous end tag character `>` + + for i, c := range str { + // If this is the last character and we are not in an HTML tag, save it. + if (i+1) == len(str) && end >= start { + builder.WriteString(str[end:]) + } + + // Keep going if the character is not `<` or `>` + if c != htmlTagStart && c != htmlTagEnd { + continue + } + + if c == htmlTagStart { + // Only update the start if we are not in a tag. + // This make sure we strip out `<