diff --git a/.golangci.yml b/.golangci.yml index 363032b..604c8c5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,9 +5,7 @@ linters: - err113 - lll - gocognit - - execinquery - exportloopref - - gomnd - funlen - godot - gofumpt @@ -20,7 +18,6 @@ linters: - wsl - wrapcheck - varnamelen - - nlreturn - exhaustive - exhaustruct - gocyclo diff --git a/connector/connector.go b/connector/connector.go index 33eba81..3f73b28 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -71,6 +71,7 @@ func (c *HTTPConnector) ParseConfiguration(ctx context.Context, configurationDir schemas, errs = configuration.BuildSchemaFromConfig(config, configurationDir, logger) if len(errs) > 0 { printSchemaValidationError(logger, errs) + return nil, errBuildSchemaFailed } } diff --git a/connector/connector_test.go b/connector/connector_test.go index 3e5046d..66fa22a 100644 --- a/connector/connector_test.go +++ b/connector/connector_test.go @@ -654,8 +654,9 @@ func createMockServer(t *testing.T, apiKey string, bearerToken string) *httptest mux.HandleFunc("/model", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: - if r.Header.Get("api_key") != apiKey { - t.Errorf("invalid api key, expected %s, got %s", apiKey, r.Header.Get("api_key")) + user, password, ok := r.BasicAuth() + if !ok || user != "user" || password != "password" { + t.Errorf("invalid basic auth, expected user:password, got %s:%s", user, password) t.FailNow() return } diff --git a/connector/internal/client.go b/connector/internal/client.go index e259b71..6aca371 100644 --- a/connector/internal/client.go +++ b/connector/internal/client.go @@ -10,6 +10,7 @@ import ( "log/slog" "math" "net/http" + "net/url" "slices" "strconv" "strings" @@ -250,12 +251,21 @@ func (client *HTTPClient) doRequest(ctx context.Context, request *RetryableReque ctx, span := client.tracer.Start(ctx, fmt.Sprintf("%s %s", method, request.RawRequest.URL), trace.WithSpanKind(trace.SpanKindClient)) defer span.End() - requestURL := request.URL.String() + urlAttr := cloneURL(&request.URL) + password, hasPassword := urlAttr.User.Password() + if urlAttr.User.String() != "" || hasPassword { + maskedUser := MaskString(urlAttr.User.Username()) + if hasPassword { + urlAttr.User = url.UserPassword(maskedUser, MaskString(password)) + } else { + urlAttr.User = url.User(maskedUser) + } + } span.SetAttributes( attribute.String("db.system", "http"), attribute.String("http.request.method", method), - attribute.String("url.full", requestURL), + attribute.String("url.full", urlAttr.String()), attribute.String("server.address", request.URL.Hostname()), attribute.Int("server.port", port), attribute.String("network.protocol.name", "http"), @@ -355,12 +365,14 @@ func evalHTTPResponse(ctx context.Context, span trace.Span, resp *http.Response, if len(respBody) == 0 { return nil, resp.Header, nil } + return string(respBody), resp.Header, nil case "text/plain", "text/html": respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, schema.NewConnectorError(http.StatusInternalServerError, err.Error(), nil) } + return string(respBody), resp.Header, nil case rest.ContentTypeJSON: if len(resultType) > 0 { @@ -378,6 +390,7 @@ func evalHTTPResponse(ctx context.Context, span trace.Span, resp *http.Response, // fallback to raw string response if the result type is String return string(respBytes), resp.Header, nil } + return strResult, resp.Header, nil } } @@ -395,6 +408,7 @@ func evalHTTPResponse(ctx context.Context, span trace.Span, resp *http.Response, if err != nil { return nil, nil, schema.InternalServerError(err.Error(), nil) } + return result, resp.Header, nil case rest.ContentTypeNdJSON: var results []any @@ -415,6 +429,7 @@ func evalHTTPResponse(ctx context.Context, span trace.Span, resp *http.Response, if err != nil { return nil, nil, schema.InternalServerError(err.Error(), nil) } + return result, resp.Header, nil default: return nil, nil, schema.NewConnectorError(http.StatusInternalServerError, "failed to evaluate response", map[string]any{ @@ -428,5 +443,6 @@ func parseContentType(input string) string { return "" } parts := strings.Split(input, ";") + return strings.TrimSpace(parts[0]) } diff --git a/connector/internal/decode.go b/connector/internal/decode.go index 5c3edf0..77e3656 100644 --- a/connector/internal/decode.go +++ b/connector/internal/decode.go @@ -34,6 +34,7 @@ func DecodeDataURI(input string) (*DataURI, error) { if err != nil { return nil, err } + return &DataURI{ Data: string(rawDecodedBytes), }, nil diff --git a/connector/internal/metadata.go b/connector/internal/metadata.go index 51c7acc..15f5fb5 100644 --- a/connector/internal/metadata.go +++ b/connector/internal/metadata.go @@ -17,6 +17,7 @@ func (rms MetadataCollection) GetFunction(name string) (*rest.OperationInfo, con return fn, rm, nil } } + return nil, configuration.NDCHttpRuntimeSchema{}, schema.UnprocessableContentError("unsupported query: "+name, nil) } @@ -28,5 +29,6 @@ func (rms MetadataCollection) GetProcedure(name string) (*rest.OperationInfo, co return fn, rm, nil } } + return nil, configuration.NDCHttpRuntimeSchema{}, schema.UnprocessableContentError("unsupported mutation: "+name, nil) } diff --git a/connector/internal/multipart.go b/connector/internal/multipart.go index 23451e3..72144f0 100644 --- a/connector/internal/multipart.go +++ b/connector/internal/multipart.go @@ -79,6 +79,7 @@ func (w *MultipartWriter) WriteJSON(fieldName string, value any, headers http.He } _, err = p.Write(bs) + return err } @@ -90,6 +91,7 @@ func (w *MultipartWriter) WriteField(fieldName, value string, headers http.Heade return err } _, err = p.Write([]byte(value)) + return err } @@ -100,5 +102,6 @@ func createFieldMIMEHeader(fieldName string, headers http.Header) textproto.MIME } h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldName))) + return h } diff --git a/connector/internal/parameter.go b/connector/internal/parameter.go index 94010c9..af7f9f8 100644 --- a/connector/internal/parameter.go +++ b/connector/internal/parameter.go @@ -23,6 +23,7 @@ func (ssp ParameterItems) String() string { str := item.String() results = append(results, str) } + return strings.Join(results, "&") } @@ -30,6 +31,7 @@ func (ssp *ParameterItems) Add(keys []Key, values []string) { index := ssp.FindIndex(keys) if index == -1 { *ssp = append(*ssp, NewParameterItem(keys, values)) + return } (*ssp)[index].AddValues(values) @@ -41,16 +43,19 @@ func (ssp ParameterItems) FindDefault() *ParameterItem { return item } item, _ = ssp.find([]Key{}) + return item } func (ssp ParameterItems) Find(keys []Key) *ParameterItem { item, _ := ssp.find(keys) + return item } func (ssp ParameterItems) FindIndex(keys []Key) int { _, i := ssp.find(keys) + return i } @@ -73,6 +78,7 @@ func (ssp ParameterItems) find(keys []Key) (*ParameterItem, int) { return &item, i } } + return nil, -1 } @@ -89,6 +95,7 @@ func (ks Keys) String() string { for i, k := range ks { if k.index != nil { sb.WriteString(fmt.Sprintf("[%d]", *k.index)) + continue } if k.key != "" { @@ -98,6 +105,7 @@ func (ks Keys) String() string { sb.WriteString(k.key) } } + return sb.String() } @@ -137,6 +145,7 @@ func (k Key) String() string { if k.index != nil { return strconv.Itoa(*k.index) } + return k.key } @@ -161,6 +170,7 @@ func (ssp ParameterItem) String() string { if key == "" { return value } + return fmt.Sprintf("%s=%s", key, value) } diff --git a/connector/internal/request.go b/connector/internal/request.go index 423df9e..ca541f0 100644 --- a/connector/internal/request.go +++ b/connector/internal/request.go @@ -3,6 +3,7 @@ package internal import ( "bytes" "context" + "encoding/base64" "fmt" "io" "math/rand/v2" @@ -13,7 +14,6 @@ import ( "time" rest "github.com/hasura/ndc-http/ndc-http-schema/schema" - "github.com/hasura/ndc-http/ndc-http-schema/utils" ) // RetryableRequest wraps the raw request with retryable @@ -76,10 +76,12 @@ func getBaseURLFromServers(servers []rest.ServerConfig, serverIDs []string) (*ur return nil, "" case 1: result := results[0] + return &result, selectedServerIDs[0] default: index := rand.IntN(len(results) - 1) host := results[index] + return &host, selectedServerIDs[index] } } @@ -99,6 +101,7 @@ func BuildDistributedRequestsWithOptions(request *RetryableRequest, httpOptions if err := request.applySettings(httpOptions.Settings, httpOptions.Explain); err != nil { return nil, err } + return []RetryableRequest{*request}, nil } @@ -142,6 +145,7 @@ func BuildDistributedRequestsWithOptions(request *RetryableRequest, httpOptions } requests = append(requests, req) } + return requests, nil } @@ -176,77 +180,96 @@ func (req *RetryableRequest) applySecurity(serverConfig *rest.ServerConfig, isEx return nil } - var securityScheme *rest.SecurityScheme for _, security := range securities { sc, ok := securitySchemes[security.Name()] if !ok { continue } - securityScheme = &sc - if slices.Contains([]rest.SecuritySchemeType{rest.HTTPAuthScheme, rest.APIKeyScheme}, sc.Type) && - sc.Value != nil && sc.GetValue() != "" { - break + hasAuth, err := req.applySecurityScheme(sc, isExplain) + if hasAuth || err != nil { + return err } } - return req.applySecurityScheme(securityScheme, isExplain) + return nil } -func (req *RetryableRequest) applySecurityScheme(securityScheme *rest.SecurityScheme, isExplain bool) error { - if securityScheme == nil { - return nil +func (req *RetryableRequest) applySecurityScheme(securityScheme rest.SecurityScheme, isExplain bool) (bool, error) { + if securityScheme.SecuritySchemer == nil { + return false, nil } if req.Headers == nil { req.Headers = http.Header{} } - switch securityScheme.Type { - case rest.HTTPAuthScheme: - headerName := securityScheme.Header + switch config := securityScheme.SecuritySchemer.(type) { + case *rest.BasicAuthConfig: + username := config.GetUsername() + password := config.GetPassword() + if config.Header != "" { + b64Value := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + req.Headers.Set(rest.AuthorizationHeader, "Basic "+b64Value) + } else { + req.URL.User = url.UserPassword(username, password) + } + + return true, nil + case *rest.HTTPAuthConfig: + headerName := config.Header if headerName == "" { - headerName = "Authorization" + headerName = rest.AuthorizationHeader } - scheme := securityScheme.Scheme - if scheme == "bearer" || scheme == "basic" { - scheme = utils.ToPascalCase(securityScheme.Scheme) + scheme := config.Scheme + if scheme == "bearer" { + scheme = "Bearer" } - v := securityScheme.GetValue() + v := config.GetValue() if v != "" { req.Headers.Set(headerName, fmt.Sprintf("%s %s", scheme, eitherMaskSecret(v, isExplain))) + + return true, nil } - case rest.APIKeyScheme: - switch securityScheme.In { + case *rest.APIKeyAuthConfig: + switch config.In { case rest.APIKeyInHeader: - if securityScheme.Value != nil { - value := securityScheme.GetValue() - if value != "" { - req.Headers.Set(securityScheme.Name, eitherMaskSecret(value, isExplain)) - } + value := config.GetValue() + if value != "" { + req.Headers.Set(config.Name, eitherMaskSecret(value, isExplain)) + + return true, nil } case rest.APIKeyInQuery: - value := securityScheme.GetValue() + value := config.GetValue() if value != "" { endpoint := req.URL q := endpoint.Query() - q.Add(securityScheme.Name, eitherMaskSecret(value, isExplain)) + q.Add(config.Name, eitherMaskSecret(value, isExplain)) endpoint.RawQuery = q.Encode() req.URL = endpoint + + return true, nil } case rest.APIKeyInCookie: // Cookie header should be forwarded from Hasura engine + return true, nil default: - return fmt.Errorf("unsupported location for apiKey scheme: %s", securityScheme.In) + return false, fmt.Errorf("unsupported location for apiKey scheme: %s", config.In) } // TODO: support OAuth and OIDC // Authentication headers can be forwarded from Hasura engine - case rest.OAuth2Scheme, rest.OpenIDConnectScheme: + case *rest.OAuth2Config, *rest.OpenIDConnectConfig: + case *rest.CookieAuthConfig: + return true, nil + case *rest.MutualTLSAuthConfig: + // the server may require not only mutualTLS authentication + return false, nil default: - return fmt.Errorf("unsupported security scheme: %s", securityScheme.Type) + return false, fmt.Errorf("unsupported security scheme: %s", securityScheme.GetType()) } - return nil + return false, nil } func (req *RetryableRequest) applySettings(settings *rest.NDCHttpSettings, isExplain bool) error { diff --git a/connector/internal/request_builder.go b/connector/internal/request_builder.go index e3ab1e1..808bc9c 100644 --- a/connector/internal/request_builder.go +++ b/connector/internal/request_builder.go @@ -249,6 +249,7 @@ func (c *RequestBuilder) evalMultipartForm(w *MultipartWriter, bodyInfo *rest.Ar return err } } + return nil case reflect.Struct: reflectType := bodyData.Type() @@ -272,6 +273,7 @@ func (c *RequestBuilder) evalMultipartForm(w *MultipartWriter, bodyInfo *rest.Ar return err } } + return nil } } @@ -296,8 +298,10 @@ func (c *RequestBuilder) evalMultipartFieldValueRecursive(w *MultipartWriter, na 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()) } @@ -314,6 +318,7 @@ func (c *RequestBuilder) evalMultipartFieldValueRecursive(w *MultipartWriter, na return err } } + return nil case *schema.NullableType: if !notNull { diff --git a/connector/internal/request_builder_test.go b/connector/internal/request_builder_test.go index 0885a01..ab25f69 100644 --- a/connector/internal/request_builder_test.go +++ b/connector/internal/request_builder_test.go @@ -558,6 +558,7 @@ func TestCreateFormURLEncoded(t *testing.T) { parseQueryAndSort := func(input string) []string { items := strings.Split(input, "&") slices.Sort(items) + return items } @@ -593,5 +594,6 @@ func createMockSchema(t *testing.T) *rest.NDCHttpSchema { rawSchemaBytes, err := os.ReadFile("../../ndc-http-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 index d978be8..b4df690 100644 --- a/connector/internal/request_parameter.go +++ b/connector/internal/request_parameter.go @@ -45,6 +45,7 @@ func (c *RequestBuilder) evalURLAndHeaderParameters() (*url.URL, http.Header, er return nil, nil, fmt.Errorf("%s: %w", argumentKey, err) } } + return endpoint, headers, nil } @@ -87,6 +88,7 @@ func (c *RequestBuilder) evalURLAndHeaderParameterBySchema(endpoint *url.URL, he endpoint.Path = strings.ReplaceAll(endpoint.Path, "{"+argumentKey+"}", strings.Join(defaultParam.Values(), ",")) } } + return nil } @@ -101,6 +103,7 @@ func (c *RequestBuilder) encodeParameterValues(objectField *rest.ObjectField, re if !nonNull { return results, nil } + return c.encodeParameterValues(&rest.ObjectField{ ObjectField: schema.ObjectField{ Type: ty.UnderlyingType, @@ -188,8 +191,10 @@ func (c *RequestBuilder) encodeParameterValues(objectField *rest.ObjectField, re 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) } @@ -200,6 +205,7 @@ func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *s if err != nil { return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) } + return []ParameterItem{ NewParameterItem([]Key{}, []string{strconv.FormatBool(value)}), }, nil @@ -208,12 +214,14 @@ func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *s 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 @@ -222,6 +230,7 @@ func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *s if err != nil { return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) } + return []ParameterItem{ NewParameterItem([]Key{}, []string{fmt.Sprint(value)}), }, nil @@ -230,15 +239,18 @@ func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *s 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.DecodeDateTimeReflection(reflectValue) if err != nil { return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) } + return []ParameterItem{ NewParameterItem([]Key{}, []string{value.Format(time.DateOnly)}), }, nil @@ -247,6 +259,7 @@ func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *s if err != nil { return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, ""), err) } + return []ParameterItem{ NewParameterItem([]Key{}, []string{value.Format(time.RFC3339)}), }, nil @@ -255,10 +268,12 @@ func encodeScalarParameterReflectionValues(reflectValue reflect.Value, scalar *s 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) @@ -388,11 +403,13 @@ func buildParamQueryKey(name string, encObject rest.EncodingObject, keys Keys, v 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 } @@ -413,6 +430,7 @@ func evalQueryParameterURL(q *url.Values, name string, encObject rest.EncodingOb for _, value := range values { q.Add(paramKey, value) } + return } @@ -449,6 +467,7 @@ func encodeQueryValues(qValues url.Values, allowReserved bool) string { } index++ } + return builder.String() } @@ -457,6 +476,7 @@ func setHeaderParameters(header *http.Header, param *rest.RequestParameter, quer // the param is an array if defaultParam != nil { header.Set(param.Name, strings.Join(defaultParam.Values(), ",")) + return } @@ -466,6 +486,7 @@ func setHeaderParameters(header *http.Header, param *rest.RequestParameter, quer headerValues = append(headerValues, fmt.Sprintf("%s=%s", pair.Keys().String(), strings.Join(pair.Values(), ","))) } header.Set(param.Name, strings.Join(headerValues, ",")) + return } diff --git a/connector/internal/types.go b/connector/internal/types.go index 49cfd8a..e5ccf85 100644 --- a/connector/internal/types.go +++ b/connector/internal/types.go @@ -80,6 +80,7 @@ func (de DistributedError) Error() string { if err != nil { bs = []byte("") } + return fmt.Sprintf("%s: %s", de.Server, string(bs)) } diff --git a/connector/internal/utils.go b/connector/internal/utils.go index 2a33f47..ca6039d 100644 --- a/connector/internal/utils.go +++ b/connector/internal/utils.go @@ -3,6 +3,7 @@ package internal import ( "fmt" "net/http" + "net/url" "strings" "github.com/hasura/ndc-sdk-go/schema" @@ -18,6 +19,7 @@ func UnwrapNullableType(input schema.Type) (schema.TypeEncoder, bool, error) { if err != nil { return nil, false, err } + return childType, true, nil case *schema.NamedType, *schema.ArrayType: return ty, false, nil @@ -31,6 +33,7 @@ func eitherMaskSecret(input string, shouldMask bool) string { if !shouldMask { return input } + return MaskString(input) } @@ -62,3 +65,19 @@ func setHeaderAttributes(span trace.Span, prefix string, httpHeaders http.Header span.SetAttributes(attribute.StringSlice(prefix+strings.ToLower(key), values)) } } + +func cloneURL(input *url.URL) *url.URL { + return &url.URL{ + Scheme: input.Scheme, + Opaque: input.Opaque, + User: input.User, + Host: input.Host, + Path: input.Path, + RawPath: input.RawPath, + OmitHost: input.OmitHost, + ForceQuery: input.ForceQuery, + RawQuery: input.RawQuery, + Fragment: input.Fragment, + RawFragment: input.RawFragment, + } +} diff --git a/connector/mutation.go b/connector/mutation.go index c6ccf55..0fe8f94 100644 --- a/connector/mutation.go +++ b/connector/mutation.go @@ -78,6 +78,7 @@ func (c *HTTPConnector) explainProcedure(operation *schema.MutationOperation) (* } httpOptions.Settings = metadata.Settings + return httpRequest, procedure, httpOptions, nil } diff --git a/connector/testdata/auth/schema.yaml b/connector/testdata/auth/schema.yaml index 330691d..099301a 100644 --- a/connector/testdata/auth/schema.yaml +++ b/connector/testdata/auth/schema.yaml @@ -15,6 +15,14 @@ settings: value: env: PET_STORE_BEARER_TOKEN scheme: bearer + basic: + type: basic + header: Authorization + username: + value: user + password: + value: password + scheme: bearer petstore_auth: type: oauth2 flows: @@ -113,6 +121,8 @@ procedures: request: url: /model method: post + security: + - basic: [] requestBody: contentType: application/json response: diff --git a/ndc-http-schema/command/convert.go b/ndc-http-schema/command/convert.go index c588875..a2126d8 100644 --- a/ndc-http-schema/command/convert.go +++ b/ndc-http-schema/command/convert.go @@ -37,12 +37,14 @@ func CommandConvertToNDCSchema(args *configuration.ConvertCommandArguments, logg if args.File == "" && args.Config == "" { err := errors.New("--config or --file argument is required") logger.Error(err.Error()) + return err } configDir, err := os.Getwd() if err != nil { logger.Error("failed to get work dir: " + err.Error()) + return err } @@ -51,10 +53,12 @@ func CommandConvertToNDCSchema(args *configuration.ConvertCommandArguments, logg rawConfig, err := utils.ReadFileFromPath(args.Config) if err != nil { logger.Error(err.Error()) + return err } if err := yaml.Unmarshal(rawConfig, &config); err != nil { logger.Error(err.Error()) + return err } configDir = filepath.Dir(args.Config) @@ -65,6 +69,7 @@ func CommandConvertToNDCSchema(args *configuration.ConvertCommandArguments, logg if err != nil { logger.Error(err.Error()) + return err } @@ -76,10 +81,12 @@ func CommandConvertToNDCSchema(args *configuration.ConvertCommandArguments, logg } if err != nil { logger.Error("failed to write schema file", slog.String("error", err.Error())) + return err } logger.Info("generated successfully", slog.Duration("execution_time", time.Since(start))) + return nil } diff --git a/ndc-http-schema/command/json2yaml.go b/ndc-http-schema/command/json2yaml.go index ac7a23e..e0642c3 100644 --- a/ndc-http-schema/command/json2yaml.go +++ b/ndc-http-schema/command/json2yaml.go @@ -22,12 +22,14 @@ func Json2Yaml(args *Json2YamlCommandArguments, logger *slog.Logger) error { rawContent, err := utils.ReadFileFromPath(args.File) if err != nil { slog.Error(err.Error()) + return err } var jsonContent any if err := json.Unmarshal(rawContent, &jsonContent); err != nil { slog.Error(err.Error()) + return err } @@ -36,15 +38,18 @@ func Json2Yaml(args *Json2YamlCommandArguments, logger *slog.Logger) error { encoder.SetIndent(2) if err := encoder.Encode(jsonContent); err != nil { slog.Error(err.Error()) + return err } if args.Output != "" { if err := os.WriteFile(args.Output, buf.Bytes(), 0664); err != nil { slog.Error(err.Error()) + return err } logger.Info("generated successfully to " + args.Output) + return nil } diff --git a/ndc-http-schema/configuration/schema.go b/ndc-http-schema/configuration/schema.go index 0e7e0ff..32933f7 100644 --- a/ndc-http-schema/configuration/schema.go +++ b/ndc-http-schema/configuration/schema.go @@ -85,6 +85,7 @@ func MergeNDCHttpSchemas(config *Configuration, schemas []NDCHttpRuntimeSchema) for i, item := range schemas { if item.NDCHttpSchema == nil { errors[item.Name] = []string{fmt.Sprintf("schema of the item %d (%s) is empty", i, item.Name)} + return nil, nil, errors } settings := item.Settings @@ -153,6 +154,7 @@ func MergeNDCHttpSchemas(config *Configuration, schemas []NDCHttpRuntimeSchema) req, err := validateRequestSchema(fnItem.Request, "get") if err != nil { errs = append(errs, fmt.Sprintf("function %s: %s", fnName, err)) + continue } @@ -173,6 +175,7 @@ func MergeNDCHttpSchemas(config *Configuration, schemas []NDCHttpRuntimeSchema) req, err := validateRequestSchema(procItem.Request, "") if err != nil { errs = append(errs, fmt.Sprintf("procedure %s: %s", procName, err)) + continue } @@ -188,6 +191,7 @@ func MergeNDCHttpSchemas(config *Configuration, schemas []NDCHttpRuntimeSchema) if len(errs) > 0 { errors[item.Name] = errs + continue } appliedSchemas[i] = meta diff --git a/ndc-http-schema/configuration/types.go b/ndc-http-schema/configuration/types.go index 3e90b84..6c6f36c 100644 --- a/ndc-http-schema/configuration/types.go +++ b/ndc-http-schema/configuration/types.go @@ -74,6 +74,7 @@ func (j *ForwardHeadersSettings) UnmarshalJSON(b []byte) error { } *j = ForwardHeadersSettings(rawResult) + return nil } @@ -130,6 +131,7 @@ func (rs RetryPolicySetting) Validate() (*rest.RetryPolicy, error) { for _, status := range rs.HTTPStatus { if status < 400 || status >= 600 { errs = append(errs, errors.New("retry http status must be in between 400 and 599")) + break } } @@ -143,6 +145,7 @@ func (rs RetryPolicySetting) Validate() (*rest.RetryPolicy, error) { if len(errs) > 0 { return result, errors.Join(errs...) } + return result, nil } diff --git a/ndc-http-schema/configuration/update.go b/ndc-http-schema/configuration/update.go index 471b48c..6f12c9b 100644 --- a/ndc-http-schema/configuration/update.go +++ b/ndc-http-schema/configuration/update.go @@ -55,6 +55,7 @@ func ReadConfigurationFile(configurationDir string) (*Configuration, error) { if err = json.Unmarshal(jsonBytes, &config); err != nil { return nil, err } + return &config, nil } diff --git a/ndc-http-schema/jsonschema/ndc-http-schema.schema.json b/ndc-http-schema/jsonschema/ndc-http-schema.schema.json index 1cdce69..598b32f 100644 --- a/ndc-http-schema/jsonschema/ndc-http-schema.schema.json +++ b/ndc-http-schema/jsonschema/ndc-http-schema.schema.json @@ -537,7 +537,7 @@ ] }, "value": { - "type": "string" + "$ref": "#/$defs/EnvString" }, "in": { "type": "string", @@ -559,6 +559,28 @@ "name" ] }, + { + "properties": { + "type": { + "type": "string", + "enum": [ + "basic" + ] + }, + "username": { + "$ref": "#/$defs/EnvString" + }, + "password": { + "$ref": "#/$defs/EnvString" + } + }, + "type": "object", + "required": [ + "type", + "username", + "password" + ] + }, { "properties": { "type": { @@ -568,10 +590,18 @@ ] }, "value": { - "type": "string" + "$ref": "#/$defs/EnvString" }, "header": { - "type": "string" + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Request contains a header field in the form of Authorization: Basic \u003ccredentials\u003e" }, "scheme": { "type": "string" @@ -621,6 +651,34 @@ "type", "openIdConnectUrl" ] + }, + { + "properties": { + "type": { + "type": "string", + "enum": [ + "cookie" + ] + } + }, + "type": "object", + "required": [ + "type" + ] + }, + { + "properties": { + "type": { + "type": "string", + "enum": [ + "mutualTLS" + ] + } + }, + "type": "object", + "required": [ + "type" + ] } ] }, diff --git a/ndc-http-schema/jsonschema/ndc-rest-schema.schema.json b/ndc-http-schema/jsonschema/ndc-rest-schema.schema.json deleted file mode 100644 index 1256ef2..0000000 --- a/ndc-http-schema/jsonschema/ndc-rest-schema.schema.json +++ /dev/null @@ -1,683 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/hasura/ndc-http/ndc-http-schema/schema/ndc-http-schema", - "$ref": "#/$defs/NDCHttpSchema", - "$defs": { - "AggregateFunctionDefinition": { - "properties": { - "result_type": { - "$ref": "#/$defs/Type" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["result_type"] - }, - "ArgumentInfo": { - "properties": { - "description": { - "type": "string" - }, - "type": { - "$ref": "#/$defs/Type" - }, - "http": { - "$ref": "#/$defs/RequestParameter", - "description": "The request parameter information of the HTTP request" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["type"], - "description": "ArgumentInfo the information of HTTP request argument" - }, - "AuthSecurities": { - "items": { - "$ref": "#/$defs/AuthSecurity" - }, - "type": "array", - "description": "AuthSecurities wraps list of security requirements with helpers" - }, - "AuthSecurity": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": "object", - "description": "AuthSecurity wraps the raw security requirement with helpers" - }, - "ComparisonOperatorDefinition": { - "type": "object" - }, - "EncodingObject": { - "properties": { - "style": { - "$ref": "#/$defs/ParameterEncodingStyle", - "description": "Describes how a specific property value will be serialized depending on its type.\nSee Parameter Object for details on the style property.\nThe behavior follows the same values as query parameters, including default values.\nThis property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data.\nIf a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored" - }, - "explode": { - "type": "boolean", - "description": "When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map.\nFor other types of properties this property has no effect. When style is form, the default value is true. For all other styles, the default value is false.\nThis property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data.\nIf a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored" - }, - "allowReserved": { - "type": "boolean", - "description": "By default, reserved characters :/?#[]@!$\u0026'()*+,;= in form field values within application/x-www-form-urlencoded bodies are percent-encoded when sent.\nAllowReserved allows these characters to be sent as is:" - }, - "contentType": { - "items": { - "type": "string" - }, - "type": "array", - "description": "For more complex scenarios, such as nested arrays or JSON in form data, use the contentType keyword to specify the media type for encoding the value of a complex field." - }, - "headers": { - "additionalProperties": { - "$ref": "#/$defs/RequestParameter" - }, - "type": "object", - "description": "A map allowing additional information to be provided as headers, for example Content-Disposition.\nContent-Type is described separately and SHALL be ignored in this section.\nThis property SHALL be ignored if the request body media type is not a multipart." - } - }, - "additionalProperties": false, - "type": "object", - "description": "EncodingObject represents the Encoding Object that contains serialization strategy for application/x-www-form-urlencoded\n\n[Encoding Object]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#encoding-object" - }, - "EnvBool": { - "anyOf": [ - { - "required": ["value"], - "title": "value" - }, - { - "required": ["env"], - "title": "env" - } - ], - "properties": { - "value": { - "type": "boolean" - }, - "env": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "EnvInt": { - "anyOf": [ - { - "required": ["value"], - "title": "value" - }, - { - "required": ["env"], - "title": "env" - } - ], - "properties": { - "value": { - "type": "integer" - }, - "env": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "EnvString": { - "anyOf": [ - { - "required": ["value"], - "title": "value" - }, - { - "required": ["env"], - "title": "env" - } - ], - "properties": { - "value": { - "type": "string" - }, - "env": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "NDCHttpSchema": { - "properties": { - "$schema": { - "type": "string" - }, - "settings": { - "$ref": "#/$defs/NDCHttpSettings" - }, - "functions": { - "additionalProperties": { - "$ref": "#/$defs/OperationInfo" - }, - "type": "object", - "description": "Functions (i.e. collections which return a single column and row)" - }, - "object_types": { - "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": { - "additionalProperties": { - "$ref": "#/$defs/OperationInfo" - }, - "type": "object", - "description": "Procedures which are available for execution as part of mutations" - }, - "scalar_types": { - "$ref": "#/$defs/SchemaResponseScalarTypes", - "description": "A list of scalar types which will be used as the types of collection columns" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["functions", "object_types", "procedures", "scalar_types"], - "description": "NDCHttpSchema extends the [NDC SchemaResponse] with OpenAPI HTTP information" - }, - "NDCHttpSettings": { - "properties": { - "servers": { - "items": { - "$ref": "#/$defs/ServerConfig" - }, - "type": "array" - }, - "headers": { - "additionalProperties": { - "$ref": "#/$defs/EnvString" - }, - "type": "object" - }, - "securitySchemes": { - "additionalProperties": { - "$ref": "#/$defs/SecurityScheme" - }, - "type": "object" - }, - "security": { - "$ref": "#/$defs/AuthSecurities" - }, - "version": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["servers"], - "description": "NDCHttpSettings represent global settings of the HTTP API, including base URL, headers, etc..." - }, - "ObjectField": { - "properties": { - "arguments": { - "$ref": "#/$defs/ObjectFieldArguments" - }, - "description": { - "type": "string" - }, - "type": { - "$ref": "#/$defs/Type" - }, - "http": { - "$ref": "#/$defs/TypeSchema", - "description": "The field schema information of the HTTP request" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["type"], - "description": "ObjectField defined on this object type" - }, - "ObjectFieldArguments": { - "additionalProperties": { - "$ref": "#/$defs/ArgumentInfo" - }, - "type": "object" - }, - "ObjectType": { - "properties": { - "description": { - "type": "string", - "description": "Description of this type" - }, - "fields": { - "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 http schema" - }, - "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" - }, - "result_type": { - "$ref": "#/$defs/Type", - "description": "The name of the result type" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["request", "arguments", "result_type"], - "description": "OperationInfo extends connector command operation with OpenAPI HTTP information" - }, - "ParameterEncodingStyle": { - "type": "string", - "enum": [ - "simple", - "label", - "matrix", - "form", - "spaceDelimited", - "pipeDelimited", - "deepObject" - ] - }, - "ParameterLocation": { - "type": "string", - "enum": ["query", "header", "path", "cookie", "body", "formData"] - }, - "Request": { - "properties": { - "url": { - "type": "string" - }, - "method": { - "type": "string", - "enum": ["get", "post", "put", "patch", "delete"] - }, - "type": { - "type": "string" - }, - "headers": { - "additionalProperties": { - "$ref": "#/$defs/EnvString" - }, - "type": "object" - }, - "security": { - "$ref": "#/$defs/AuthSecurities" - }, - "servers": { - "items": { - "$ref": "#/$defs/ServerConfig" - }, - "type": "array" - }, - "requestBody": { - "$ref": "#/$defs/RequestBody" - }, - "response": { - "$ref": "#/$defs/Response" - }, - "timeout": { - "type": "integer" - }, - "retry": { - "$ref": "#/$defs/RetryPolicy" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["response"], - "description": "Request represents the HTTP request information of the webhook" - }, - "RequestBody": { - "properties": { - "contentType": { - "type": "string" - }, - "encoding": { - "additionalProperties": { - "$ref": "#/$defs/EncodingObject" - }, - "type": "object" - } - }, - "additionalProperties": false, - "type": "object", - "description": "RequestBody defines flexible request body with content types" - }, - "RequestParameter": { - "properties": { - "style": { - "$ref": "#/$defs/ParameterEncodingStyle", - "description": "Describes how a specific property value will be serialized depending on its type.\nSee Parameter Object for details on the style property.\nThe behavior follows the same values as query parameters, including default values.\nThis property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data.\nIf a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored" - }, - "explode": { - "type": "boolean", - "description": "When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map.\nFor other types of properties this property has no effect. When style is form, the default value is true. For all other styles, the default value is false.\nThis property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data.\nIf a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored" - }, - "allowReserved": { - "type": "boolean", - "description": "By default, reserved characters :/?#[]@!$\u0026'()*+,;= in form field values within application/x-www-form-urlencoded bodies are percent-encoded when sent.\nAllowReserved allows these characters to be sent as is:" - }, - "contentType": { - "items": { - "type": "string" - }, - "type": "array", - "description": "For more complex scenarios, such as nested arrays or JSON in form data, use the contentType keyword to specify the media type for encoding the value of a complex field." - }, - "headers": { - "additionalProperties": { - "$ref": "#/$defs/RequestParameter" - }, - "type": "object", - "description": "A map allowing additional information to be provided as headers, for example Content-Disposition.\nContent-Type is described separately and SHALL be ignored in this section.\nThis property SHALL be ignored if the request body media type is not a multipart." - }, - "name": { - "type": "string" - }, - "argumentName": { - "type": "string" - }, - "in": { - "$ref": "#/$defs/ParameterLocation" - }, - "schema": { - "$ref": "#/$defs/TypeSchema" - } - }, - "additionalProperties": false, - "type": "object", - "description": "RequestParameter represents an HTTP request parameter" - }, - "Response": { - "properties": { - "contentType": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["contentType"] - }, - "RetryPolicy": { - "properties": { - "times": { - "type": "integer", - "description": "Number of retry times" - }, - "delay": { - "type": "integer", - "description": "Delay retry delay in milliseconds" - }, - "httpStatus": { - "items": { - "type": "integer" - }, - "type": "array", - "description": "HTTPStatus retries if the remote service returns one of these http status" - } - }, - "additionalProperties": false, - "type": "object", - "description": "RetryPolicy represents the retry policy of request" - }, - "ScalarType": { - "properties": { - "aggregate_functions": { - "$ref": "#/$defs/ScalarTypeAggregateFunctions" - }, - "comparison_operators": { - "additionalProperties": { - "$ref": "#/$defs/ComparisonOperatorDefinition" - }, - "type": "object" - }, - "representation": { - "$ref": "#/$defs/TypeRepresentation" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["aggregate_functions", "comparison_operators"] - }, - "ScalarTypeAggregateFunctions": { - "additionalProperties": { - "$ref": "#/$defs/AggregateFunctionDefinition" - }, - "type": "object" - }, - "SchemaResponseScalarTypes": { - "additionalProperties": { - "$ref": "#/$defs/ScalarType" - }, - "type": "object" - }, - "SecurityScheme": { - "oneOf": [ - { - "properties": { - "type": { - "type": "string", - "enum": ["apiKey"] - }, - "value": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["header", "query", "cookie"] - }, - "name": { - "type": "string" - } - }, - "type": "object", - "required": ["type", "value", "in", "name"] - }, - { - "properties": { - "type": { - "type": "string", - "enum": ["http"] - }, - "value": { - "type": "string" - }, - "header": { - "type": "string" - }, - "scheme": { - "type": "string" - } - }, - "type": "object", - "required": ["type", "value", "header", "scheme"] - }, - { - "properties": { - "type": { - "type": "string", - "enum": ["oauth2"] - }, - "flows": { - "additionalProperties": true, - "type": "object" - } - }, - "type": "object", - "required": ["type", "flows"] - }, - { - "properties": { - "type": { - "type": "string", - "enum": ["openIdConnect"] - }, - "openIdConnectUrl": { - "type": "string" - } - }, - "type": "object", - "required": ["type", "openIdConnectUrl"] - } - ] - }, - "ServerConfig": { - "properties": { - "url": { - "$ref": "#/$defs/EnvString" - }, - "id": { - "type": "string" - }, - "headers": { - "additionalProperties": { - "$ref": "#/$defs/EnvString" - }, - "type": "object" - }, - "securitySchemes": { - "additionalProperties": { - "$ref": "#/$defs/SecurityScheme" - }, - "type": "object" - }, - "security": { - "$ref": "#/$defs/AuthSecurities" - }, - "tls": { - "$ref": "#/$defs/TLSConfig" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["url"], - "description": "ServerConfig contains server configurations" - }, - "TLSConfig": { - "properties": { - "certFile": { - "$ref": "#/$defs/EnvString", - "description": "Path to the TLS cert to use for TLS required connections." - }, - "certPem": { - "$ref": "#/$defs/EnvString", - "description": "Alternative to cert_file. Provide the certificate contents as a string instead of a filepath." - }, - "keyFile": { - "$ref": "#/$defs/EnvString", - "description": "Path to the TLS key to use for TLS required connections." - }, - "keyPem": { - "$ref": "#/$defs/EnvString", - "description": "Alternative to key_file. Provide the key contents as a string instead of a filepath." - }, - "caFile": { - "$ref": "#/$defs/EnvString", - "description": "Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates.\nIf empty uses system root CA." - }, - "caPem": { - "$ref": "#/$defs/EnvString", - "description": "Alternative to ca_file. Provide the CA cert contents as a string instead of a filepath." - }, - "insecureSkipVerify": { - "$ref": "#/$defs/EnvBool", - "description": "Additionally you can configure TLS to be enabled but skip verifying the server's certificate chain." - }, - "includeSystemCACertsPool": { - "$ref": "#/$defs/EnvBool", - "description": "Whether to load the system certificate authorities pool alongside the certificate authority." - }, - "minVersion": { - "$ref": "#/$defs/EnvString", - "description": "Minimum acceptable TLS version." - }, - "maxVersion": { - "$ref": "#/$defs/EnvString", - "description": "Maximum acceptable TLS version." - }, - "cipherSuites": { - "items": { - "type": "string" - }, - "type": "array", - "description": "Explicit cipher suites can be set. If left blank, a safe default list is used.\nSee https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites." - }, - "reloadInterval": { - "$ref": "#/$defs/EnvInt", - "description": "Specifies the duration after which the certificate will be reloaded. If not set, it will never be reloaded.\nThe interval unit is minute" - } - }, - "additionalProperties": false, - "type": "object", - "description": "TLSConfig represents the transport layer security (LTS) configuration for the mutualTLS authentication" - }, - "Type": { - "type": "object" - }, - "TypeRepresentation": { - "type": "object" - }, - "TypeSchema": { - "properties": { - "type": { - "items": { - "type": "string" - }, - "type": "array" - }, - "format": { - "type": "string" - }, - "pattern": { - "type": "string" - }, - "maximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "maxLength": { - "type": "integer" - }, - "minLength": { - "type": "integer" - }, - "items": { - "$ref": "#/$defs/TypeSchema" - } - }, - "additionalProperties": false, - "type": "object", - "required": ["type"], - "description": "TypeSchema represents a serializable object of OpenAPI schema that is used for validation" - } - } -} diff --git a/ndc-http-schema/main.go b/ndc-http-schema/main.go index 789ccae..17b7351 100644 --- a/ndc-http-schema/main.go +++ b/ndc-http-schema/main.go @@ -27,6 +27,7 @@ func main() { if err != nil { slog.Error(err.Error()) os.Exit(1) + return } diff --git a/ndc-http-schema/openapi/internal/cleanup.go b/ndc-http-schema/openapi/internal/cleanup.go deleted file mode 100644 index 0e8a17e..0000000 --- a/ndc-http-schema/openapi/internal/cleanup.go +++ /dev/null @@ -1,107 +0,0 @@ -package internal - -import ( - "errors" - "fmt" - - rest "github.com/hasura/ndc-http/ndc-http-schema/schema" - "github.com/hasura/ndc-sdk-go/schema" -) - -type ndcSchemaCleaner struct { - UsedTypes map[string]bool - Schema *rest.NDCHttpSchema -} - -func cleanUnusedSchemaTypes(httpSchema *rest.NDCHttpSchema) error { - cleaner := &ndcSchemaCleaner{ - UsedTypes: make(map[string]bool), - Schema: httpSchema, - } - - if err := cleaner.Validate(); err != nil { - return err - } - - cleaner.cleanupUnusedTypes() - - return nil -} - -// Validate checks if the schema is valid -func (nsc *ndcSchemaCleaner) Validate() error { - for key, operation := range nsc.Schema.Functions { - if err := nsc.validateOperation(key, operation); err != nil { - return err - } - } - - for key, operation := range nsc.Schema.Procedures { - if err := nsc.validateOperation(key, operation); err != nil { - return err - } - } - - return nil -} - -func (nsc *ndcSchemaCleaner) cleanupUnusedTypes() { - for objectName := range nsc.Schema.ObjectTypes { - if _, ok := nsc.UsedTypes[objectName]; !ok { - delete(nsc.Schema.ObjectTypes, objectName) - } - } - - for scalarName := range nsc.Schema.ScalarTypes { - if _, ok := nsc.UsedTypes[scalarName]; !ok { - delete(nsc.Schema.ScalarTypes, scalarName) - } - } -} - -// recursively validate and clean unused objects as well as their inner properties -func (nsc *ndcSchemaCleaner) validateOperation(operationName string, operation rest.OperationInfo) error { - for key, field := range operation.Arguments { - if err := nsc.validateType(field.Type); err != nil { - return fmt.Errorf("%s: arguments.%s: %w", operationName, key, err) - } - } - - if err := nsc.validateType(operation.ResultType); err != nil { - return fmt.Errorf("%s: result_type: %w", operationName, err) - } - - return nil -} - -// recursively validate used types as well as their inner properties -func (nsc *ndcSchemaCleaner) validateType(schemaType schema.Type) error { - typeName := getNamedType(schemaType.Interface(), true, "") - if typeName == "" { - return errors.New("named type is empty") - } - - if _, ok := nsc.UsedTypes[typeName]; ok { - return nil - } - - if _, ok := nsc.Schema.ScalarTypes[typeName]; ok { - nsc.UsedTypes[typeName] = true - - return nil - } - - objectType, ok := nsc.Schema.ObjectTypes[typeName] - if !ok { - return errors.New(typeName + ": named type does not exist") - } - - nsc.UsedTypes[typeName] = true - for key, field := range objectType.Fields { - if err := nsc.validateType(field.Type); err != nil { - return fmt.Errorf("%s.%s: %w", typeName, key, err) - } - } - - return nil -} diff --git a/ndc-http-schema/openapi/internal/ndc.go b/ndc-http-schema/openapi/internal/ndc.go new file mode 100644 index 0000000..df1e221 --- /dev/null +++ b/ndc-http-schema/openapi/internal/ndc.go @@ -0,0 +1,190 @@ +package internal + +import ( + "errors" + "fmt" + + rest "github.com/hasura/ndc-http/ndc-http-schema/schema" + "github.com/hasura/ndc-http/ndc-http-schema/utils" + "github.com/hasura/ndc-sdk-go/schema" +) + +// NDCBuilder the NDC schema builder to validate REST connector schema. +type NDCBuilder struct { + *ConvertOptions + + schema *rest.NDCHttpSchema + newSchema *rest.NDCHttpSchema + usedTypes map[string]string +} + +// NewNDCBuilder creates a new NDCBuilder instance. +func NewNDCBuilder(httpSchema *rest.NDCHttpSchema, options ConvertOptions) *NDCBuilder { + newSchema := rest.NewNDCHttpSchema() + newSchema.Settings = httpSchema.Settings + + return &NDCBuilder{ + ConvertOptions: &options, + usedTypes: make(map[string]string), + schema: httpSchema, + newSchema: newSchema, + } +} + +// Build validates and build the REST connector schema. +func (ndc *NDCBuilder) Build() (*rest.NDCHttpSchema, error) { + if err := ndc.validate(); err != nil { + return nil, err + } + + return ndc.newSchema, nil +} + +// Validate checks if the schema is valid +func (nsc *NDCBuilder) validate() error { + for key, operation := range nsc.schema.Functions { + op, err := nsc.validateOperation(key, operation) + if err != nil { + return err + } + + newName := nsc.formatOperationName(key) + nsc.newSchema.Functions[newName] = *op + } + + for key, operation := range nsc.schema.Procedures { + op, err := nsc.validateOperation(key, operation) + if err != nil { + return err + } + + newName := nsc.formatOperationName(key) + nsc.newSchema.Procedures[newName] = *op + } + + return nil +} + +// recursively validate and clean unused objects as well as their inner properties +func (nsc *NDCBuilder) validateOperation(operationName string, operation rest.OperationInfo) (*rest.OperationInfo, error) { + result := &rest.OperationInfo{ + Request: operation.Request, + Description: operation.Description, + Arguments: make(map[string]rest.ArgumentInfo), + } + for key, field := range operation.Arguments { + fieldType, err := nsc.validateType(field.Type) + if err != nil { + return nil, fmt.Errorf("%s: arguments.%s: %w", operationName, key, err) + } + result.Arguments[key] = rest.ArgumentInfo{ + HTTP: field.HTTP, + ArgumentInfo: schema.ArgumentInfo{ + Description: field.ArgumentInfo.Description, + Type: fieldType.Encode(), + }, + } + } + + resultType, err := nsc.validateType(operation.ResultType) + if err != nil { + return nil, fmt.Errorf("%s: result_type: %w", operationName, err) + } + result.ResultType = resultType.Encode() + + return result, nil +} + +// recursively validate used types as well as their inner properties +func (nsc *NDCBuilder) validateType(schemaType schema.Type) (schema.TypeEncoder, error) { + rawType, err := schemaType.InterfaceT() + + switch t := rawType.(type) { + case *schema.NullableType: + underlyingType, err := nsc.validateType(t.UnderlyingType) + if err != nil { + return nil, err + } + + return schema.NewNullableType(underlyingType), nil + case *schema.ArrayType: + elementType, err := nsc.validateType(t.ElementType) + if err != nil { + return nil, err + } + + return schema.NewArrayType(elementType), nil + case *schema.NamedType: + if t.Name == "" { + return nil, errors.New("named type is empty") + } + + if newName, ok := nsc.usedTypes[t.Name]; ok { + return schema.NewNamedType(newName), nil + } + + if st, ok := nsc.schema.ScalarTypes[t.Name]; ok { + newName := t.Name + if !rest.IsDefaultScalar(t.Name) { + newName = nsc.formatTypeName(t.Name) + } + newNameType := schema.NewNamedType(newName) + nsc.usedTypes[t.Name] = newName + if _, ok := nsc.newSchema.ScalarTypes[newName]; !ok { + nsc.newSchema.ScalarTypes[newName] = st + } + + return newNameType, nil + } + + objectType, ok := nsc.schema.ObjectTypes[t.Name] + if !ok { + return nil, errors.New(t.Name + ": named type does not exist") + } + + newName := nsc.formatTypeName(t.Name) + newNameType := schema.NewNamedType(newName) + nsc.usedTypes[t.Name] = newName + + newObjectType := rest.ObjectType{ + Description: objectType.Description, + Fields: make(map[string]rest.ObjectField), + } + + for key, field := range objectType.Fields { + fieldType, err := nsc.validateType(field.Type) + if err != nil { + return nil, fmt.Errorf("%s.%s: %w", t.Name, key, err) + } + newObjectType.Fields[key] = rest.ObjectField{ + ObjectField: schema.ObjectField{ + Type: fieldType.Encode(), + Description: field.Description, + Arguments: field.Arguments, + }, + HTTP: field.HTTP, + } + } + nsc.newSchema.ObjectTypes[newName] = newObjectType + + return newNameType, nil + default: + return nil, err + } +} + +func (nsc *NDCBuilder) formatTypeName(name string) string { + if nsc.Prefix == "" { + return name + } + + return utils.StringSliceToPascalCase([]string{nsc.Prefix, name}) +} + +func (nsc *NDCBuilder) formatOperationName(name string) string { + if nsc.Prefix == "" { + return name + } + + return utils.StringSliceToCamelCase([]string{nsc.Prefix, name}) +} diff --git a/ndc-http-schema/openapi/internal/oas2.go b/ndc-http-schema/openapi/internal/oas2.go index 22331d1..805a4b7 100644 --- a/ndc-http-schema/openapi/internal/oas2.go +++ b/ndc-http-schema/openapi/internal/oas2.go @@ -28,9 +28,9 @@ type OAS2Builder struct { } // NewOAS2Builder creates an OAS3Builder instance -func NewOAS2Builder(schema *rest.NDCHttpSchema, options ConvertOptions) *OAS2Builder { +func NewOAS2Builder(options ConvertOptions) *OAS2Builder { builder := &OAS2Builder{ - schema: schema, + schema: rest.NewNDCHttpSchema(), schemaCache: make(map[string]SchemaInfoCache), ConvertOptions: applyConvertOptions(options), } @@ -38,12 +38,7 @@ func NewOAS2Builder(schema *rest.NDCHttpSchema, options ConvertOptions) *OAS2Bui return builder } -// Schema returns the inner NDC HTTP schema -func (oc *OAS2Builder) Schema() *rest.NDCHttpSchema { - return oc.schema -} - -func (oc *OAS2Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v2.Swagger]) error { +func (oc *OAS2Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v2.Swagger]) (*rest.NDCHttpSchema, error) { if docModel.Model.Info != nil { oc.schema.Settings.Version = docModel.Model.Info.Version } @@ -53,6 +48,7 @@ func (oc *OAS2Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v2. for _, s := range docModel.Model.Schemes { if strings.HasPrefix(s, "http") { scheme = s + break } } @@ -65,14 +61,14 @@ func (oc *OAS2Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v2. for iterPath := docModel.Model.Paths.PathItems.First(); iterPath != nil; iterPath = iterPath.Next() { if err := oc.pathToNDCOperations(iterPath); err != nil { - return err + return nil, err } } if docModel.Model.Definitions != nil { for cSchema := docModel.Model.Definitions.Definitions.First(); cSchema != nil; cSchema = cSchema.Next() { if err := oc.convertComponentSchemas(cSchema); err != nil { - return err + return nil, err } } } @@ -82,17 +78,14 @@ func (oc *OAS2Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v2. for scheme := docModel.Model.SecurityDefinitions.Definitions.First(); scheme != nil; scheme = scheme.Next() { err := oc.convertSecuritySchemes(scheme) if err != nil { - return err + return nil, err } } } oc.schema.Settings.Security = convertSecurities(docModel.Model.Security) - if err := cleanUnusedSchemaTypes(oc.schema); err != nil { - return err - } - return nil + return NewNDCBuilder(oc.schema, *oc.ConvertOptions).Build() } func (oc *OAS2Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v2.SecurityScheme]) error { @@ -101,30 +94,20 @@ func (oc *OAS2Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v2 if security == nil { return nil } + result := rest.SecurityScheme{} switch security.Type { - case "apiKey": - result.Type = rest.APIKeyScheme + case string(rest.APIKeyScheme): inLocation, err := rest.ParseAPIKeyLocation(security.In) if err != nil { return err } - apiConfig := rest.APIKeyAuthConfig{ - In: inLocation, - Name: security.Name, - } valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key})) - result.Value = &valueEnv - result.APIKeyAuthConfig = &apiConfig - case "basic": - result.Type = rest.HTTPAuthScheme - httpConfig := rest.HTTPAuthConfig{ - Scheme: "Basic", - Header: "Authorization", - } - valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"})) - result.Value = &valueEnv - result.HTTPAuthConfig = &httpConfig + result.SecuritySchemer = rest.NewAPIKeyAuthConfig(security.Name, inLocation, valueEnv) + case string(rest.BasicAuthScheme): + user := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "USERNAME"})) + password := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "PASSWORD"})) + result.SecuritySchemer = rest.NewBasicAuthConfig(user, password) case "oauth2": var flowType rest.OAuthFlowType switch security.Flow { @@ -149,17 +132,16 @@ func (oc *OAS2Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v2 } flow.Scopes = scopes } - result.Type = rest.OAuth2Scheme - result.OAuth2Config = &rest.OAuth2Config{ - Flows: map[rest.OAuthFlowType]rest.OAuthFlow{ - flowType: flow, - }, - } + + result.SecuritySchemer = rest.NewOAuth2Config(map[rest.OAuthFlowType]rest.OAuthFlow{ + flowType: flow, + }) default: return fmt.Errorf("invalid security scheme: %s", security.Type) } oc.schema.Settings.SecuritySchemes[key] = result + return nil } @@ -206,6 +188,7 @@ func (oc *OAS2Builder) pathToNDCOperations(pathItem orderedmap.Pair[string, *v2. if procDelete != nil { oc.schema.Procedures[procDeleteName] = *procDelete } + return nil } @@ -253,5 +236,6 @@ func (oc *OAS2Builder) buildScalarJSON() *schema.NamedType { if _, ok := oc.schema.ScalarTypes[scalarName]; !ok { oc.schema.ScalarTypes[scalarName] = *defaultScalarTypes[rest.ScalarJSON] } + return schema.NewNamedType(scalarName) } diff --git a/ndc-http-schema/openapi/internal/oas2_operation.go b/ndc-http-schema/openapi/internal/oas2_operation.go index e06dc94..9492226 100644 --- a/ndc-http-schema/openapi/internal/oas2_operation.go +++ b/ndc-http-schema/openapi/internal/oas2_operation.go @@ -2,7 +2,6 @@ package internal import ( "fmt" - "log" "log/slog" "slices" "strconv" @@ -35,9 +34,6 @@ func (oc *oas2OperationBuilder) BuildFunction(pathKey string, operation *v2.Oper if funcName == "" { funcName = buildPathMethodName(pathKey, "get", oc.builder.ConvertOptions) } - if oc.builder.Prefix != "" { - funcName = utils.StringSliceToCamelCase([]string{oc.builder.Prefix, funcName}) - } oc.builder.Logger.Info("function", slog.String("name", funcName), slog.String("path", pathKey), @@ -52,6 +48,7 @@ func (oc *oas2OperationBuilder) BuildFunction(pathKey string, operation *v2.Oper slog.Any("produces", operation.Produces), slog.Any("consumes", operation.Consumes), ) + return nil, "", nil } @@ -97,9 +94,6 @@ func (oc *oas2OperationBuilder) BuildProcedure(pathKey string, method string, op procName = buildPathMethodName(pathKey, method, oc.builder.ConvertOptions) } - if oc.builder.Prefix != "" { - procName = utils.StringSliceToCamelCase([]string{oc.builder.Prefix, procName}) - } oc.builder.Logger.Info("procedure", slog.String("name", procName), slog.String("path", pathKey), @@ -115,6 +109,7 @@ func (oc *oas2OperationBuilder) BuildProcedure(pathKey string, method string, op slog.Any("produces", operation.Produces), slog.Any("consumes", operation.Consumes), ) + return nil, "", nil } @@ -230,7 +225,6 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa return nil, err } - log.Println("type != ''", apiPath, fieldPaths, typeEncoder) schemaType := typeEncoder.Encode() argument := rest.ArgumentInfo{ ArgumentInfo: schema.ArgumentInfo{ @@ -238,7 +232,10 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa }, } if param.Description != "" { - argument.Description = ¶m.Description + description := utils.StripHTMLTags(param.Description) + if description != "" { + argument.Description = &description + } } switch paramLocation { @@ -253,13 +250,20 @@ func (oc *oas2OperationBuilder) convertParameters(operation *v2.Operation, apiPa } case rest.InFormData: if typeSchema != nil { - formDataObject.Fields[paramName] = rest.ObjectField{ + param := rest.ObjectField{ ObjectField: schema.ObjectField{ - Type: argument.Type, - Description: argument.Description, + Type: argument.Type, }, HTTP: typeSchema, } + + if argument.Description != nil { + desc := utils.StripHTMLTags(*argument.Description) + if desc != "" { + param.ObjectField.Description = &desc + } + } + formDataObject.Fields[paramName] = param } default: argument.HTTP = &rest.RequestParameter{ @@ -317,6 +321,7 @@ func (oc *oas2OperationBuilder) convertResponse(responses *v2.Responses, apiPath return nil, nil } else if code >= 200 && code < 300 { resp = r.Value() + break } } diff --git a/ndc-http-schema/openapi/internal/oas2_schema.go b/ndc-http-schema/openapi/internal/oas2_schema.go index 4a4376f..5bb57a0 100644 --- a/ndc-http-schema/openapi/internal/oas2_schema.go +++ b/ndc-http-schema/openapi/internal/oas2_schema.go @@ -71,14 +71,15 @@ func (oc *oas2SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return nil, nil, errParameterSchemaEmpty(fieldPaths) } + description := utils.StripHTMLTags(typeSchema.Description) nullable := typeSchema.Nullable != nil && *typeSchema.Nullable if len(typeSchema.AllOf) > 0 { enc, ty, err := oc.buildAllOfAnyOfSchemaType(typeSchema.AllOf, nullable, fieldPaths) if err != nil { return nil, nil, err } - if ty != nil { - ty.Description = typeSchema.Description + if ty != nil && description != "" { + ty.Description = description } return enc, ty, nil @@ -89,8 +90,8 @@ func (oc *oas2SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ if err != nil { return nil, nil, err } - if ty != nil { - ty.Description = typeSchema.Description + if ty != nil && description != "" { + ty.Description = description } return enc, ty, nil @@ -102,8 +103,8 @@ func (oc *oas2SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ if err != nil { return nil, nil, err } - if ty != nil { - ty.Description = typeSchema.Description + if ty != nil && description != "" { + ty.Description = description } return enc, ty, nil @@ -138,8 +139,8 @@ func (oc *oas2SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ object := rest.ObjectType{ Fields: make(map[string]rest.ObjectField), } - if typeSchema.Description != "" { - object.Description = &typeSchema.Description + if description != "" { + object.Description = &description } for prop := typeSchema.Properties.First(); prop != nil; prop = prop.Next() { @@ -203,6 +204,7 @@ func (oc *oas2SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ if nullable { return schema.NewNullableType(result), typeResult, nil } + return result, typeResult, nil } @@ -314,6 +316,7 @@ func (oc *oas2SchemaBuilder) buildAllOfAnyOfSchemaType(schemaProxies []*base.Sch Description: ty.Description, Type: []string{}, } + return oc.builder.buildScalarJSON(), ty, nil } @@ -364,5 +367,6 @@ func (oc *oas2SchemaBuilder) trimPathPrefix(input string) string { if oc.builder.ConvertOptions.TrimPrefix == "" { return input } + return strings.TrimPrefix(input, oc.builder.ConvertOptions.TrimPrefix) } diff --git a/ndc-http-schema/openapi/internal/oas3.go b/ndc-http-schema/openapi/internal/oas3.go index 3377e37..429b531 100644 --- a/ndc-http-schema/openapi/internal/oas3.go +++ b/ndc-http-schema/openapi/internal/oas3.go @@ -35,9 +35,9 @@ type SchemaInfoCache struct { } // NewOAS3Builder creates an OAS3Builder instance -func NewOAS3Builder(schema *rest.NDCHttpSchema, options ConvertOptions) *OAS3Builder { +func NewOAS3Builder(options ConvertOptions) *OAS3Builder { builder := &OAS3Builder{ - schema: schema, + schema: rest.NewNDCHttpSchema(), schemaCache: make(map[string]SchemaInfoCache), ConvertOptions: applyConvertOptions(options), } @@ -45,12 +45,7 @@ func NewOAS3Builder(schema *rest.NDCHttpSchema, options ConvertOptions) *OAS3Bui return builder } -// Schema returns the inner NDC HTTP schema -func (oc *OAS3Builder) Schema() *rest.NDCHttpSchema { - return oc.schema -} - -func (oc *OAS3Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v3.Document]) error { +func (oc *OAS3Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v3.Document]) (*rest.NDCHttpSchema, error) { if docModel.Model.Info != nil { oc.schema.Settings.Version = docModel.Model.Info.Version } @@ -60,13 +55,13 @@ func (oc *OAS3Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v3. if docModel.Model.Components != nil && docModel.Model.Components.Schemas != nil { for cSchema := docModel.Model.Components.Schemas.First(); cSchema != nil; cSchema = cSchema.Next() { if err := oc.convertComponentSchemas(cSchema); err != nil { - return err + return nil, err } } } for iterPath := docModel.Model.Paths.PathItems.First(); iterPath != nil; iterPath = iterPath.Next() { if err := oc.pathToNDCOperations(iterPath); err != nil { - return err + return nil, err } } @@ -75,7 +70,7 @@ func (oc *OAS3Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v3. for scheme := docModel.Model.Components.SecuritySchemes.First(); scheme != nil; scheme = scheme.Next() { err := oc.convertSecuritySchemes(scheme) if err != nil { - return err + return nil, err } } } @@ -85,11 +80,7 @@ func (oc *OAS3Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v3. oc.schemaCache = make(map[string]SchemaInfoCache) oc.transformWriteSchema() - if err := cleanUnusedSchemaTypes(oc.schema); err != nil { - return err - } - - return nil + return NewNDCBuilder(oc.schema, *oc.ConvertOptions).Build() } func (oc *OAS3Builder) convertServers(servers []*v3.Server) []rest.ServerConfig { @@ -142,60 +133,60 @@ func (oc *OAS3Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v3 if err != nil { return err } - result := rest.SecurityScheme{ - Type: securityType, - } + result := rest.SecurityScheme{} switch securityType { case rest.APIKeyScheme: inLocation, err := rest.ParseAPIKeyLocation(security.In) if err != nil { return err } - apiConfig := rest.APIKeyAuthConfig{ - In: inLocation, - Name: security.Name, + + if inLocation == rest.APIKeyInCookie { + result.SecuritySchemer = rest.NewCookieAuthConfig() + } else { + valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key})) + result.SecuritySchemer = rest.NewAPIKeyAuthConfig(security.Name, inLocation, valueEnv) } - valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key})) - result.Value = &valueEnv - result.APIKeyAuthConfig = &apiConfig case rest.HTTPAuthScheme: - httpConfig := rest.HTTPAuthConfig{ - Scheme: security.Scheme, - Header: "Authorization", + switch security.Scheme { + case string(rest.BasicAuthScheme): + user := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "USERNAME"})) + password := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "PASSWORD"})) + result.SecuritySchemer = rest.NewBasicAuthConfig(user, password) + default: + valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"})) + result.SecuritySchemer = rest.NewHTTPAuthConfig(security.Scheme, rest.AuthorizationHeader, valueEnv) } - - valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"})) - result.Value = &valueEnv - result.HTTPAuthConfig = &httpConfig case rest.OAuth2Scheme: if security.Flows == nil { return fmt.Errorf("flows of security scheme %s is required", key) } - oauthConfig := rest.OAuth2Config{ - Flows: make(map[rest.OAuthFlowType]rest.OAuthFlow), - } + + flows := make(map[rest.OAuthFlowType]rest.OAuthFlow) if security.Flows.Implicit != nil { - oauthConfig.Flows[rest.ImplicitFlow] = *convertV3OAuthFLow(security.Flows.Implicit) + flows[rest.ImplicitFlow] = *convertV3OAuthFLow(security.Flows.Implicit) } if security.Flows.AuthorizationCode != nil { - oauthConfig.Flows[rest.AuthorizationCodeFlow] = *convertV3OAuthFLow(security.Flows.AuthorizationCode) + flows[rest.AuthorizationCodeFlow] = *convertV3OAuthFLow(security.Flows.AuthorizationCode) } if security.Flows.ClientCredentials != nil { - oauthConfig.Flows[rest.ClientCredentialsFlow] = *convertV3OAuthFLow(security.Flows.ClientCredentials) + flows[rest.ClientCredentialsFlow] = *convertV3OAuthFLow(security.Flows.ClientCredentials) } if security.Flows.Password != nil { - oauthConfig.Flows[rest.PasswordFlow] = *convertV3OAuthFLow(security.Flows.Password) + flows[rest.PasswordFlow] = *convertV3OAuthFLow(security.Flows.Password) } - result.OAuth2Config = &oauthConfig + + result.SecuritySchemer = rest.NewOAuth2Config(flows) case rest.OpenIDConnectScheme: - result.OpenIDConfig = &rest.OpenIDConfig{ - OpenIDConnectURL: security.OpenIdConnectUrl, - } + result.SecuritySchemer = rest.NewOpenIDConnectConfig(security.OpenIdConnectUrl) + case rest.MutualTLSScheme: + result.SecuritySchemer = rest.NewMutualTLSAuthConfig() default: return fmt.Errorf("invalid security scheme: %s", security.Type) } oc.schema.Settings.SecuritySchemes[key] = result + return nil } @@ -244,6 +235,7 @@ func (oc *OAS3Builder) pathToNDCOperations(pathItem orderedmap.Pair[string, *v3. if procDelete != nil { oc.schema.Procedures[procDeleteName] = *procDelete } + return nil } @@ -297,6 +289,7 @@ func (oc *OAS3Builder) trimPathPrefix(input string) string { if oc.ConvertOptions.TrimPrefix == "" { return input } + return strings.TrimPrefix(input, oc.ConvertOptions.TrimPrefix) } @@ -306,6 +299,7 @@ func (oc *OAS3Builder) buildScalarJSON() *schema.NamedType { if _, ok := oc.schema.ScalarTypes[scalarName]; !ok { oc.schema.ScalarTypes[scalarName] = *defaultScalarTypes[rest.ScalarJSON] } + return schema.NewNamedType(scalarName) } @@ -336,9 +330,11 @@ func (oc *OAS3Builder) populateWriteSchemaType(schemaType schema.Type) (schema.T switch ty := schemaType.Interface().(type) { case *schema.NullableType: ut, name, isInput := oc.populateWriteSchemaType(ty.UnderlyingType) + return schema.NewNullableType(ut.Interface()).Encode(), name, isInput case *schema.ArrayType: ut, name, isInput := oc.populateWriteSchemaType(ty.ElementType) + return schema.NewArrayType(ut.Interface()).Encode(), name, isInput case *schema.NamedType: _, evaluated := oc.schemaCache[ty.Name] @@ -386,8 +382,10 @@ func (oc *OAS3Builder) populateWriteSchemaType(schemaType schema.Type) (schema.T } if hasWriteField { oc.schema.ObjectTypes[writeName] = writeObject + return schema.NewNamedType(writeName).Encode(), writeName, true } + return schemaType, ty.Name, false default: return schemaType, getNamedType(schemaType.Interface(), true, ""), false diff --git a/ndc-http-schema/openapi/internal/oas3_operation.go b/ndc-http-schema/openapi/internal/oas3_operation.go index 7ea1992..360c391 100644 --- a/ndc-http-schema/openapi/internal/oas3_operation.go +++ b/ndc-http-schema/openapi/internal/oas3_operation.go @@ -39,9 +39,6 @@ func (oc *oas3OperationBuilder) BuildFunction(itemGet *v3.Operation) (*rest.Oper if funcName == "" { funcName = buildPathMethodName(oc.pathKey, "get", oc.builder.ConvertOptions) } - if oc.builder.Prefix != "" { - funcName = utils.StringSliceToCamelCase([]string{oc.builder.Prefix, funcName}) - } defer func() { oc.builder.Logger.Info("function", @@ -92,10 +89,6 @@ func (oc *oas3OperationBuilder) BuildProcedure(operation *v3.Operation) (*rest.O procName = buildPathMethodName(oc.pathKey, oc.method, oc.builder.ConvertOptions) } - if oc.builder.Prefix != "" { - procName = utils.StringSliceToCamelCase([]string{oc.builder.Prefix, procName}) - } - defer func() { oc.builder.Logger.Info("procedure", slog.String("name", procName), @@ -210,8 +203,9 @@ func (oc *oas3OperationBuilder) convertParameters(params []*v3.Parameter, apiPat EncodingObject: encoding, }, } - if param.Description != "" { - argument.Description = ¶m.Description + paramDescription := utils.StripHTMLTags(param.Description) + if paramDescription != "" { + argument.Description = ¶mDescription } oc.Arguments[paramName] = argument @@ -318,8 +312,9 @@ func (oc *oas3OperationBuilder) convertRequestBody(reqBody *v3.RequestBody, apiP argument := schema.ArgumentInfo{ Type: ndcType.Encode(), } - if header.Description != "" { - argument.Description = &header.Description + headerDesc := utils.StripHTMLTags(header.Description) + if headerDesc != "" { + argument.Description = &headerDesc } item.Headers[key] = headerParam oc.Arguments[argumentName] = rest.ArgumentInfo{ @@ -354,6 +349,7 @@ func (oc *oas3OperationBuilder) convertResponse(responses *v3.Responses, apiPath return nil, nil, nil } else if code >= 200 && code < 300 { resp = r.Value() + break } } @@ -384,6 +380,7 @@ func (oc *oas3OperationBuilder) convertResponse(responses *v3.Responses, apiPath bodyContent, present = resp.Content.Get(ct) if present { contentType = ct + break } } @@ -420,5 +417,6 @@ func (oc *oas3OperationBuilder) getOperationDescription(operation *v3.Operation) if operation.Description != "" { return utils.StripHTMLTags(operation.Description) } + return strings.ToUpper(oc.method) + " " + oc.pathKey } diff --git a/ndc-http-schema/openapi/internal/oas3_schema.go b/ndc-http-schema/openapi/internal/oas3_schema.go index 421c3b8..562dcb4 100644 --- a/ndc-http-schema/openapi/internal/oas3_schema.go +++ b/ndc-http-schema/openapi/internal/oas3_schema.go @@ -91,6 +91,7 @@ func (oc *oas3SchemaBuilder) getSchemaTypeFromProxy(schemaProxy *base.SchemaProx ndcType = schema.NewNullableType(ndcType) } } + return ndcType, typeSchema, nil } @@ -100,6 +101,7 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return nil, nil, errParameterSchemaEmpty(fieldPaths) } + description := utils.StripHTMLTags(typeSchema.Description) nullable := typeSchema.Nullable != nil && *typeSchema.Nullable if len(typeSchema.AllOf) > 0 { enc, ty, err := oc.buildAllOfAnyOfSchemaType(typeSchema.AllOf, nullable, fieldPaths) @@ -107,7 +109,7 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return nil, nil, err } if ty != nil { - ty.Description = typeSchema.Description + ty.Description = description } return enc, ty, nil @@ -119,7 +121,7 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return nil, nil, err } if ty != nil { - ty.Description = typeSchema.Description + ty.Description = description } return enc, ty, nil @@ -132,7 +134,7 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ return nil, nil, err } if ty != nil { - ty.Description = typeSchema.Description + ty.Description = description } return enc, ty, nil @@ -184,10 +186,11 @@ func (oc *oas3SchemaBuilder) getSchemaType(typeSchema *base.Schema, fieldPaths [ writeObject := rest.ObjectType{ Fields: make(map[string]rest.ObjectField), } - if typeSchema.Description != "" { - object.Description = &typeSchema.Description - readObject.Description = &typeSchema.Description - writeObject.Description = &typeSchema.Description + + if description != "" { + object.Description = &description + readObject.Description = &description + writeObject.Description = &description } for prop := typeSchema.Properties.First(); prop != nil; prop = prop.Next() { @@ -326,6 +329,7 @@ func (oc *oas3SchemaBuilder) buildAllOfAnyOfSchemaType(schemaProxies []*base.Sch Description: ty.Description, Type: []string{}, } + return oc.builder.buildScalarJSON(), ty, nil } diff --git a/ndc-http-schema/openapi/internal/utils.go b/ndc-http-schema/openapi/internal/utils.go index 99b2609..bd49861 100644 --- a/ndc-http-schema/openapi/internal/utils.go +++ b/ndc-http-schema/openapi/internal/utils.go @@ -19,6 +19,7 @@ func applyConvertOptions(opts ConvertOptions) *ConvertOptions { opts.Logger = slog.Default() } opts.MethodAlias = getMethodAlias(opts.MethodAlias) + return &opts } @@ -30,6 +31,7 @@ func buildPathMethodName(apiPath string, method string, options *ConvertOptions) if alias, ok := options.MethodAlias[method]; ok { method = alias } + return utils.ToCamelCase(method + encodedPath) } @@ -38,6 +40,7 @@ func getSchemaRefTypeNameV2(name string) string { if len(result) < 2 { return "" } + return result[1] } @@ -46,6 +49,7 @@ func getSchemaRefTypeNameV3(name string) string { if len(result) < 2 { return "" } + return result[1] } @@ -231,7 +235,7 @@ func createSchemaFromOpenAPISchema(input *base.Schema) *rest.TypeSchema { ps.Minimum = input.Minimum ps.MaxLength = input.MaxLength ps.MinLength = input.MinLength - ps.Description = input.Description + ps.Description = utils.StripHTMLTags(input.Description) ps.ReadOnly = input.ReadOnly != nil && *input.ReadOnly ps.WriteOnly = input.WriteOnly != nil && *input.WriteOnly @@ -342,6 +346,7 @@ func evalSchemaProxiesSlice(schemaProxies []*base.SchemaProxy, location rest.Par // however, it's redundant and prevents the tool converting correct types if location == rest.InQuery && (sc.Type[0] == "string" && len(sc.Enum) == 1 && (sc.Enum[0] == nil || sc.Enum[0].Value == "")) { nullable = true + continue } @@ -387,11 +392,13 @@ func formatOperationName(input string) string { for i, c := range input { if unicode.IsLetter(c) { sb.WriteRune(c) + continue } if unicode.IsNumber(c) && i > 0 { sb.WriteRune(c) + continue } diff --git a/ndc-http-schema/openapi/oas2.go b/ndc-http-schema/openapi/oas2.go index 5006606..ba7e02c 100644 --- a/ndc-http-schema/openapi/oas2.go +++ b/ndc-http-schema/openapi/oas2.go @@ -27,9 +27,11 @@ func OpenAPIv2ToNDCSchema(input []byte, options ConvertOptions) (*rest.NDCHttpSc return nil, append(errs, errors.New("there is no API to be converted")) } - converter := internal.NewOAS2Builder(rest.NewNDCHttpSchema(), internal.ConvertOptions(options)) - if err := converter.BuildDocumentModel(docModel); err != nil { + converter := internal.NewOAS2Builder(internal.ConvertOptions(options)) + result, err := converter.BuildDocumentModel(docModel) + if err != nil { return nil, append(errs, err) } - return converter.Schema(), nil + + return result, nil } diff --git a/ndc-http-schema/openapi/oas2_test.go b/ndc-http-schema/openapi/oas2_test.go index ea4eb02..13461b9 100644 --- a/ndc-http-schema/openapi/oas2_test.go +++ b/ndc-http-schema/openapi/oas2_test.go @@ -52,7 +52,7 @@ func TestOpenAPIv2ToRESTSchema(t *testing.T) { // go run ./ndc-http-schema convert -f ./ndc-http-schema/openapi/testdata/prefix2/source.json -o ./ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.json --spec oas2 --prefix hasura_mock_json // go run ./ndc-http-schema convert -f ./ndc-http-schema/openapi/testdata/prefix2/source.json -o ./ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.schema.json --pure --spec oas2 --prefix hasura_mock_json { - Name: "prefix2_single_word", + Name: "prefix2_multi_word", Source: "testdata/prefix2/source.json", Expected: "testdata/prefix2/expected_multi_words.json", Schema: "testdata/prefix2/expected_multi_words.schema.json", diff --git a/ndc-http-schema/openapi/oas3.go b/ndc-http-schema/openapi/oas3.go index f3f5467..a48a5a9 100644 --- a/ndc-http-schema/openapi/oas3.go +++ b/ndc-http-schema/openapi/oas3.go @@ -29,10 +29,11 @@ func OpenAPIv3ToNDCSchema(input []byte, options ConvertOptions) (*rest.NDCHttpSc return nil, append(errs, errors.New("there is no API to be converted")) } - converter := internal.NewOAS3Builder(rest.NewNDCHttpSchema(), internal.ConvertOptions(options)) - if err := converter.BuildDocumentModel(docModel); err != nil { + converter := internal.NewOAS3Builder(internal.ConvertOptions(options)) + result, err := converter.BuildDocumentModel(docModel) + if err != nil { return nil, append(errs, err) } - return converter.Schema(), nil + return result, nil } diff --git a/ndc-http-schema/openapi/testdata/onesignal/expected.json b/ndc-http-schema/openapi/testdata/onesignal/expected.json index f2a5173..be9e4a4 100644 --- a/ndc-http-schema/openapi/testdata/onesignal/expected.json +++ b/ndc-http-schema/openapi/testdata/onesignal/expected.json @@ -12,19 +12,19 @@ "securitySchemes": { "app_key": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "APP_KEY_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } }, "user_key": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "USER_KEY_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } } }, "version": "1.2.2" @@ -112,7 +112,7 @@ } }, "kind": { - "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only ", + "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only", "type": { "type": "nullable", "underlying_type": { @@ -422,7 +422,7 @@ } }, "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.", + "description": "\"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": { @@ -501,7 +501,7 @@ } }, "data": { - "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1} ", + "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}", "type": { "type": "nullable", "underlying_type": { diff --git a/ndc-http-schema/openapi/testdata/onesignal/schema.json b/ndc-http-schema/openapi/testdata/onesignal/schema.json index fff4b0a..7e98fc7 100644 --- a/ndc-http-schema/openapi/testdata/onesignal/schema.json +++ b/ndc-http-schema/openapi/testdata/onesignal/schema.json @@ -33,7 +33,7 @@ } }, "kind": { - "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only ", + "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only", "type": { "type": "nullable", "underlying_type": { @@ -239,7 +239,7 @@ } }, "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.", + "description": "\"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": { @@ -293,7 +293,7 @@ } }, "data": { - "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1} ", + "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}", "type": { "type": "nullable", "underlying_type": { diff --git a/ndc-http-schema/openapi/testdata/openai/expected.json b/ndc-http-schema/openapi/testdata/openai/expected.json index aaf792e..afe40d9 100644 --- a/ndc-http-schema/openapi/testdata/openai/expected.json +++ b/ndc-http-schema/openapi/testdata/openai/expected.json @@ -18,11 +18,11 @@ "securitySchemes": { "ApiKeyAuth": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "API_KEY_AUTH_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } } }, "security": [ @@ -241,10 +241,10 @@ } }, "ChatCompletionStreamOptions": { - "description": "Options for streaming response. Only set this when you set `stream: true`. ", + "description": "Options for streaming response. Only set this when you set `stream: true`.", "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. ", + "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.", "type": { "type": "nullable", "underlying_type": { @@ -406,7 +406,7 @@ "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details) ", + "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details)", "type": { "type": "nullable", "underlying_type": { @@ -423,7 +423,7 @@ } }, "function_call": { - "description": "Deprecated in favor of `tool_choice`. Controls which (if any) function is called by the model. `none` means the model will not call a function and instead generates a message. `auto` means the model can pick between generating a message or calling a function. Specifying a particular function via `{\"name\": \"my_function\"}` forces the model to call that function. `none` is the default when no functions are present. `auto` is the default if functions are present. ", + "description": "Deprecated in favor of `tool_choice`. Controls which (if any) function is called by the model. `none` means the model will not call a function and instead generates a message. `auto` means the model can pick between generating a message or calling a function. Specifying a particular function via `{\"name\": \"my_function\"}` forces the model to call that function. `none` is the default when no functions are present. `auto` is the default if functions are present.", "type": { "type": "nullable", "underlying_type": { @@ -436,7 +436,7 @@ } }, "functions": { - "description": "Deprecated in favor of `tools`. A list of functions the model may generate JSON inputs for. ", + "description": "Deprecated in favor of `tools`. A list of functions the model may generate JSON inputs for.", "type": { "type": "nullable", "underlying_type": { @@ -454,7 +454,7 @@ } }, "logit_bias": { - "description": "Modify the likelihood of specified tokens appearing in the completion. Accepts 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. ", + "description": "Modify the likelihood of specified tokens appearing in the completion. Accepts 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.", "type": { "type": "nullable", "underlying_type": { @@ -484,7 +484,7 @@ } }, "max_tokens": { - "description": "The maximum number of [tokens](/tokenizer) that can be generated in the chat completion. The 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. ", + "description": "The maximum number of [tokens](/tokenizer) that can be generated in the chat completion. The 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.", "type": { "type": "nullable", "underlying_type": { @@ -558,7 +558,7 @@ } }, "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details) ", + "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details)", "type": { "type": "nullable", "underlying_type": { @@ -575,7 +575,7 @@ } }, "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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. ", + "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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.", "type": { "type": "nullable", "underlying_type": { @@ -590,7 +590,7 @@ } }, "seed": { - "description": "This feature is in Beta. If 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. Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. ", + "description": "This feature is in Beta. If 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. Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.", "type": { "type": "nullable", "underlying_type": { @@ -607,7 +607,7 @@ } }, "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: - If set to 'auto', the system will utilize scale tier credits until they are exhausted. - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee. - When not set, the default behavior is 'auto'. When this parameter is set, the response body will include the `service_tier` utilized. ", + "description": "Specifies the latency tier to use for processing the request. This parameter is relevant for customers subscribed to the scale tier service: - If set to 'auto', the system will utilize scale tier credits until they are exhausted. - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee. - When not set, the default behavior is 'auto'. When this parameter is set, the response body will include the `service_tier` utilized.", "type": { "type": "nullable", "underlying_type": { @@ -622,7 +622,7 @@ } }, "stop": { - "description": "Up to 4 sequences where the API will stop generating further tokens. ", + "description": "Up to 4 sequences where the API will stop generating further tokens.", "type": { "type": "nullable", "underlying_type": { @@ -635,7 +635,7 @@ } }, "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). ", + "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).", "type": { "type": "nullable", "underlying_type": { @@ -650,7 +650,7 @@ } }, "stream_options": { - "description": "Options for streaming response. Only set this when you set `stream: true`. ", + "description": "Options for streaming response. Only set this when you set `stream: true`.", "type": { "type": "nullable", "underlying_type": { @@ -665,7 +665,7 @@ } }, "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. We generally recommend altering this or `top_p` but not both. ", + "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. We generally recommend altering this or `top_p` but not both.", "type": { "type": "nullable", "underlying_type": { @@ -682,7 +682,7 @@ } }, "tool_choice": { - "description": "Controls which (if any) tool is called by the model. `none` means the model will not call any tool and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools. Specifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool. `none` is the default when no tools are present. `auto` is the default if tools are present. ", + "description": "Controls which (if any) tool is called by the model. `none` means the model will not call any tool and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools. Specifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool. `none` is the default when no tools are present. `auto` is the default if tools are present.", "type": { "type": "nullable", "underlying_type": { @@ -695,7 +695,7 @@ } }, "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. ", + "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.", "type": { "type": "nullable", "underlying_type": { @@ -730,7 +730,7 @@ } }, "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. We generally recommend altering this or `temperature` but not both. ", + "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. We generally recommend altering this or `temperature` but not both.", "type": { "type": "nullable", "underlying_type": { @@ -747,7 +747,7 @@ } }, "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). ", + "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).", "type": { "type": "nullable", "underlying_type": { @@ -764,7 +764,7 @@ } }, "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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. ", + "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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.", "fields": { "type": { "description": "Must be one of `text` or `json_object`.", @@ -870,7 +870,7 @@ } }, "system_fingerprint": { - "description": "This fingerprint represents the backend configuration that the model runs with. Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism. ", + "description": "This fingerprint represents the backend configuration that the model runs with. Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism.", "type": { "type": "nullable", "underlying_type": { @@ -889,7 +889,7 @@ "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, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, `tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function. ", + "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, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, `tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function.", "type": { "name": "CreateChatCompletionResponseChoicesFinishReason", "type": "named" diff --git a/ndc-http-schema/openapi/testdata/openai/schema.json b/ndc-http-schema/openapi/testdata/openai/schema.json index d357c11..cf0d999 100644 --- a/ndc-http-schema/openapi/testdata/openai/schema.json +++ b/ndc-http-schema/openapi/testdata/openai/schema.json @@ -139,10 +139,10 @@ } }, "ChatCompletionStreamOptions": { - "description": "Options for streaming response. Only set this when you set `stream: true`. ", + "description": "Options for streaming response. Only set this when you set `stream: true`.", "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. ", + "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.", "type": { "type": "nullable", "underlying_type": { @@ -239,7 +239,7 @@ "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details) ", + "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details)", "type": { "type": "nullable", "underlying_type": { @@ -249,7 +249,7 @@ } }, "function_call": { - "description": "Deprecated in favor of `tool_choice`. Controls which (if any) function is called by the model. `none` means the model will not call a function and instead generates a message. `auto` means the model can pick between generating a message or calling a function. Specifying a particular function via `{\"name\": \"my_function\"}` forces the model to call that function. `none` is the default when no functions are present. `auto` is the default if functions are present. ", + "description": "Deprecated in favor of `tool_choice`. Controls which (if any) function is called by the model. `none` means the model will not call a function and instead generates a message. `auto` means the model can pick between generating a message or calling a function. Specifying a particular function via `{\"name\": \"my_function\"}` forces the model to call that function. `none` is the default when no functions are present. `auto` is the default if functions are present.", "type": { "type": "nullable", "underlying_type": { @@ -259,7 +259,7 @@ } }, "functions": { - "description": "Deprecated in favor of `tools`. A list of functions the model may generate JSON inputs for. ", + "description": "Deprecated in favor of `tools`. A list of functions the model may generate JSON inputs for.", "type": { "type": "nullable", "underlying_type": { @@ -272,7 +272,7 @@ } }, "logit_bias": { - "description": "Modify the likelihood of specified tokens appearing in the completion. Accepts 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. ", + "description": "Modify the likelihood of specified tokens appearing in the completion. Accepts 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.", "type": { "type": "nullable", "underlying_type": { @@ -292,7 +292,7 @@ } }, "max_tokens": { - "description": "The maximum number of [tokens](/tokenizer) that can be generated in the chat completion. The 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. ", + "description": "The maximum number of [tokens](/tokenizer) that can be generated in the chat completion. The 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.", "type": { "type": "nullable", "underlying_type": { @@ -339,7 +339,7 @@ } }, "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details) ", + "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. [See more information about frequency and presence penalties.](/docs/guides/text-generation/parameter-details)", "type": { "type": "nullable", "underlying_type": { @@ -349,7 +349,7 @@ } }, "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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. ", + "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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.", "type": { "type": "nullable", "underlying_type": { @@ -359,7 +359,7 @@ } }, "seed": { - "description": "This feature is in Beta. If 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. Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. ", + "description": "This feature is in Beta. If 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. Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.", "type": { "type": "nullable", "underlying_type": { @@ -369,7 +369,7 @@ } }, "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: - If set to 'auto', the system will utilize scale tier credits until they are exhausted. - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee. - When not set, the default behavior is 'auto'. When this parameter is set, the response body will include the `service_tier` utilized. ", + "description": "Specifies the latency tier to use for processing the request. This parameter is relevant for customers subscribed to the scale tier service: - If set to 'auto', the system will utilize scale tier credits until they are exhausted. - If set to 'default', the request will be processed using the default service tier with a lower uptime SLA and no latency guarentee. - When not set, the default behavior is 'auto'. When this parameter is set, the response body will include the `service_tier` utilized.", "type": { "type": "nullable", "underlying_type": { @@ -379,7 +379,7 @@ } }, "stop": { - "description": "Up to 4 sequences where the API will stop generating further tokens. ", + "description": "Up to 4 sequences where the API will stop generating further tokens.", "type": { "type": "nullable", "underlying_type": { @@ -389,7 +389,7 @@ } }, "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). ", + "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).", "type": { "type": "nullable", "underlying_type": { @@ -399,7 +399,7 @@ } }, "stream_options": { - "description": "Options for streaming response. Only set this when you set `stream: true`. ", + "description": "Options for streaming response. Only set this when you set `stream: true`.", "type": { "type": "nullable", "underlying_type": { @@ -409,7 +409,7 @@ } }, "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. We generally recommend altering this or `top_p` but not both. ", + "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. We generally recommend altering this or `top_p` but not both.", "type": { "type": "nullable", "underlying_type": { @@ -419,7 +419,7 @@ } }, "tool_choice": { - "description": "Controls which (if any) tool is called by the model. `none` means the model will not call any tool and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools. Specifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool. `none` is the default when no tools are present. `auto` is the default if tools are present. ", + "description": "Controls which (if any) tool is called by the model. `none` means the model will not call any tool and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools. Specifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool. `none` is the default when no tools are present. `auto` is the default if tools are present.", "type": { "type": "nullable", "underlying_type": { @@ -429,7 +429,7 @@ } }, "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. ", + "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.", "type": { "type": "nullable", "underlying_type": { @@ -452,7 +452,7 @@ } }, "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. We generally recommend altering this or `temperature` but not both. ", + "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. We generally recommend altering this or `temperature` but not both.", "type": { "type": "nullable", "underlying_type": { @@ -462,7 +462,7 @@ } }, "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). ", + "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).", "type": { "type": "nullable", "underlying_type": { @@ -474,7 +474,7 @@ } }, "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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. ", + "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`. Setting to `{ \"type\": \"json_object\" }` enables JSON mode, which guarantees the message the model generates is valid JSON. **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.", "fields": { "type": { "description": "Must be one of `text` or `json_object`.", @@ -540,7 +540,7 @@ } }, "system_fingerprint": { - "description": "This fingerprint represents the backend configuration that the model runs with. Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism. ", + "description": "This fingerprint represents the backend configuration that the model runs with. Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism.", "type": { "type": "nullable", "underlying_type": { @@ -554,7 +554,7 @@ "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, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, `tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function. ", + "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, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, `tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function.", "type": { "name": "CreateChatCompletionResponseChoicesFinishReason", "type": "named" diff --git a/ndc-http-schema/openapi/testdata/petstore2/expected.json b/ndc-http-schema/openapi/testdata/petstore2/expected.json index 0f59723..cfb8e31 100644 --- a/ndc-http-schema/openapi/testdata/petstore2/expected.json +++ b/ndc-http-schema/openapi/testdata/petstore2/expected.json @@ -12,19 +12,21 @@ "securitySchemes": { "api_key": { "type": "apiKey", + "in": "header", + "name": "api_key", "value": { "env": "API_KEY" - }, - "in": "header", - "name": "api_key" + } }, "basic": { - "type": "http", - "value": { - "env": "BASIC_TOKEN" + "type": "basic", + "header": "", + "username": { + "env": "BASIC_USERNAME" }, - "header": "Authorization", - "scheme": "Basic" + "password": { + "env": "BASIC_PASSWORD" + } }, "petstore_auth": { "type": "oauth2", @@ -69,9 +71,7 @@ "name": "aggregated_by", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -88,9 +88,7 @@ "name": "browsers", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -107,9 +105,7 @@ "name": "end_date", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -126,9 +122,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -145,9 +139,7 @@ "name": "offset", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -163,9 +155,7 @@ "name": "on-behalf-of", "in": "header", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -179,9 +169,7 @@ "name": "start_date", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -219,9 +207,7 @@ "name": "list_id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -237,9 +223,7 @@ "name": "on-behalf-of", "in": "header", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -276,9 +260,7 @@ "name": "on-behalf-of", "in": "header", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -316,9 +298,7 @@ "name": "username", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -335,10 +315,7 @@ "method": "get", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -359,9 +336,7 @@ "name": "status", "in": "query", "schema": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -381,10 +356,7 @@ "method": "get", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -405,9 +377,7 @@ "name": "tags", "in": "query", "schema": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -460,9 +430,7 @@ "name": "orderId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "maximum": 10, "minimum": 1 } @@ -499,9 +467,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -537,7 +503,7 @@ }, "arguments": { "username": { - "description": "The name that needs to be fetched. Use user1 for testing. ", + "description": "The name that needs to be fetched. Use user1 for testing.", "type": { "name": "String", "type": "named" @@ -546,9 +512,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -581,9 +545,7 @@ "name": "client_name", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -600,9 +562,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -619,9 +579,7 @@ "name": "offset", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -638,9 +596,7 @@ "name": "owner", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -690,9 +646,7 @@ "name": "password", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -706,9 +660,7 @@ "name": "username", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -734,9 +686,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "unique_clicks": { @@ -749,9 +699,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -769,9 +717,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "opens": { @@ -784,9 +730,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "unique_clicks": { @@ -799,9 +743,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "unique_opens": { @@ -814,9 +756,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -832,9 +772,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -847,9 +785,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -861,9 +797,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -879,9 +813,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -894,9 +826,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -913,9 +843,7 @@ } }, "http": { - "type": [ - "number" - ] + "type": ["number"] } }, "name": { @@ -928,9 +856,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -943,9 +869,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -958,9 +882,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -979,13 +901,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1006,9 +924,7 @@ } }, "http": { - "type": [ - "array" - ] + "type": ["array"] } }, "first_name": { @@ -1021,9 +937,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1037,9 +951,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "custom_spf": { @@ -1049,9 +961,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "default": { @@ -1061,9 +971,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "dns": { @@ -1073,9 +981,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "domain": { @@ -1085,9 +991,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -1097,9 +1001,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "ips": { @@ -1112,9 +1014,7 @@ "type": "array" }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { "type": null } @@ -1127,9 +1027,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "subdomain": { @@ -1142,9 +1040,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "user_id": { @@ -1154,9 +1050,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "username": { @@ -1166,9 +1060,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "valid": { @@ -1178,9 +1070,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -1195,9 +1085,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "domain_spf": { @@ -1207,9 +1095,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "mail_server": { @@ -1219,9 +1105,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "subdomain_spf": { @@ -1231,9 +1115,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1248,9 +1130,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "host": { @@ -1260,9 +1140,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1272,9 +1150,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "valid": { @@ -1284,9 +1160,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -1301,9 +1175,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "host": { @@ -1313,9 +1185,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1325,9 +1195,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "valid": { @@ -1337,9 +1205,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -1354,9 +1220,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "host": { @@ -1366,9 +1230,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1378,9 +1240,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "valid": { @@ -1390,9 +1250,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -1407,9 +1265,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "host": { @@ -1419,9 +1275,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1431,9 +1285,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "valid": { @@ -1443,9 +1295,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -1462,9 +1312,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "stats": { @@ -1480,13 +1328,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1504,9 +1348,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "name": { @@ -1519,9 +1361,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1534,9 +1374,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1555,9 +1393,7 @@ } }, "http": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -1574,9 +1410,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "stats": { @@ -1592,13 +1426,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1616,9 +1446,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "name": { @@ -1631,9 +1459,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1646,9 +1472,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1665,9 +1489,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "client_name": { @@ -1680,9 +1502,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "client_secret": { @@ -1695,9 +1515,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "client_secret_expires_at": { @@ -1710,9 +1528,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -1726,9 +1542,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1744,9 +1558,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "id": { @@ -1758,9 +1570,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -1773,9 +1583,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -1788,9 +1596,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -1803,9 +1609,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -1819,9 +1623,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1837,9 +1639,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "field": { @@ -1864,9 +1664,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -1876,9 +1674,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "photoUrls": { @@ -1890,13 +1686,9 @@ "type": "array" }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -1910,9 +1702,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "tags": { @@ -1927,9 +1717,7 @@ } }, "http": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -1957,9 +1745,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -1972,9 +1758,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1990,9 +1774,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -2005,9 +1787,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2024,9 +1804,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "status": { @@ -2039,9 +1817,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2058,9 +1834,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "file": { @@ -2073,9 +1847,7 @@ } }, "http": { - "type": [ - "file" - ] + "type": ["file"] } } } @@ -2091,9 +1863,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "firstName": { @@ -2105,9 +1875,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -2119,9 +1887,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -2134,9 +1900,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "password": { @@ -2148,9 +1912,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "phone": { @@ -2162,9 +1924,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "userStatus": { @@ -2177,9 +1937,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -2192,9 +1950,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2207,10 +1963,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -2230,9 +1983,7 @@ "http": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2280,9 +2031,7 @@ "name": "orderId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "minimum": 1 } } @@ -2303,10 +2052,7 @@ "method": "delete", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -2326,9 +2072,7 @@ "name": "api_key", "in": "header", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -2342,9 +2086,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2377,9 +2119,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2413,9 +2153,7 @@ "http": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2447,9 +2185,7 @@ "http": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2466,10 +2202,7 @@ "method": "put", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -2489,9 +2222,7 @@ "http": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2511,10 +2242,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -2534,9 +2262,7 @@ "http": { "in": "formData", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -2550,9 +2276,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2587,9 +2311,7 @@ "http": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -2603,9 +2325,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2625,10 +2345,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -2648,9 +2365,7 @@ "http": { "in": "formData", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -2664,9 +2379,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2697,11 +2410,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "day", - "week", - "month" - ], + "one_of": ["day", "week", "month"], "type": "enum" } }, @@ -2709,11 +2418,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "date", - "text", - "number" - ], + "one_of": ["date", "text", "number"], "type": "enum" } }, @@ -2756,11 +2461,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "placed", - "approved", - "delivered" - ], + "one_of": ["placed", "approved", "delivered"], "type": "enum" } }, @@ -2768,11 +2469,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "available", - "pending", - "sold" - ], + "one_of": ["available", "pending", "sold"], "type": "enum" } }, diff --git a/ndc-http-schema/openapi/testdata/petstore2/schema.json b/ndc-http-schema/openapi/testdata/petstore2/schema.json index ecb7550..88ff895 100644 --- a/ndc-http-schema/openapi/testdata/petstore2/schema.json +++ b/ndc-http-schema/openapi/testdata/petstore2/schema.json @@ -246,7 +246,7 @@ { "arguments": { "username": { - "description": "The name that needs to be fetched. Use user1 for testing. ", + "description": "The name that needs to be fetched. Use user1 for testing.", "type": { "name": "String", "type": "named" diff --git a/ndc-http-schema/openapi/testdata/petstore3/expected.json b/ndc-http-schema/openapi/testdata/petstore3/expected.json index 241c6d3..4ae617e 100644 --- a/ndc-http-schema/openapi/testdata/petstore3/expected.json +++ b/ndc-http-schema/openapi/testdata/petstore3/expected.json @@ -18,19 +18,21 @@ "securitySchemes": { "api_key": { "type": "apiKey", + "in": "header", + "name": "api_key", "value": { "env": "PET_STORE_API_KEY" - }, - "in": "header", - "name": "api_key" + } }, "basic": { - "type": "http", - "value": { - "env": "PET_STORE_BASIC_TOKEN" + "type": "basic", + "header": "", + "username": { + "env": "PET_STORE_BASIC_USERNAME" }, - "header": "Authorization", - "scheme": "basic" + "password": { + "env": "PET_STORE_BASIC_PASSWORD" + } }, "petstore_auth": { "type": "oauth2", @@ -48,10 +50,7 @@ "security": [ {}, { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "version": "1.0.19" @@ -80,9 +79,7 @@ "name": "collection_method", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -119,9 +116,7 @@ "name": "customer", "in": "query", "schema": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -158,9 +153,7 @@ "name": "ending_before", "in": "query", "schema": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -183,13 +176,9 @@ "name": "expand", "in": "query", "schema": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -209,9 +198,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -229,9 +216,7 @@ "name": "starting_after", "in": "query", "schema": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -250,9 +235,7 @@ "name": "status", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -270,9 +253,7 @@ "name": "subscription", "in": "query", "schema": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -290,10 +271,7 @@ "method": "get", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -314,9 +292,7 @@ "name": "start_date", "in": "query", "schema": { - "type": [ - "number" - ] + "type": ["number"] } } }, @@ -334,9 +310,7 @@ "name": "status", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -356,10 +330,7 @@ "method": "get", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -384,13 +355,9 @@ "name": "tags", "in": "query", "schema": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -444,9 +411,7 @@ "name": "orderId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -467,10 +432,7 @@ "api_key": [] }, { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -488,9 +450,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -527,7 +487,7 @@ }, "arguments": { "username": { - "description": "The name that needs to be fetched. Use user1 for testing. ", + "description": "The name that needs to be fetched. Use user1 for testing.", "type": { "name": "String", "type": "named" @@ -536,9 +496,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -571,9 +529,7 @@ "name": "password", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -590,9 +546,7 @@ "name": "username", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -616,9 +570,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "state": { @@ -630,9 +582,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "street": { @@ -644,9 +594,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "zip": { @@ -658,9 +606,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -676,9 +622,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -691,9 +635,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -705,9 +647,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -723,9 +663,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -738,9 +676,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -756,9 +692,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -772,9 +706,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "object": { @@ -784,9 +716,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "url": { @@ -796,9 +726,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "pattern": "^/v1/invoices", "maxLength": 5000 } @@ -816,9 +744,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "id": { @@ -830,9 +756,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -845,9 +769,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -860,9 +782,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -875,9 +795,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -891,9 +809,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -909,9 +825,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "field": { @@ -936,9 +850,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -948,9 +860,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "photoUrls": { @@ -962,13 +872,9 @@ "type": "array" }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -982,9 +888,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "tags": { @@ -999,9 +903,7 @@ } }, "http": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -1018,9 +920,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "allow_promotion_codes": { @@ -1033,9 +933,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "automatic_tax": { @@ -1048,9 +946,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "billing_address_collection": { @@ -1063,9 +959,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "cancel_url": { @@ -1078,9 +972,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -1094,9 +986,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 200 } }, @@ -1110,9 +1000,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "currency": { @@ -1125,9 +1013,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "custom_fields": { @@ -1143,13 +1029,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1163,9 +1045,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "customer": { @@ -1178,9 +1058,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -1194,9 +1072,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "customer_email": { @@ -1209,9 +1085,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "customer_update": { @@ -1224,9 +1098,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "discounts": { @@ -1242,13 +1114,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1265,13 +1133,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -1286,9 +1150,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -1302,9 +1164,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "line_items": { @@ -1320,13 +1180,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1340,9 +1196,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "metadata": { @@ -1355,9 +1209,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "mode": { @@ -1370,9 +1222,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "payment_intent_data": { @@ -1385,9 +1235,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "payment_method_collection": { @@ -1400,9 +1248,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "payment_method_configuration": { @@ -1415,9 +1261,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 100 } }, @@ -1431,9 +1275,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "payment_method_types": { @@ -1449,13 +1291,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -1469,9 +1307,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "redirect_on_completion": { @@ -1484,9 +1320,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "return_url": { @@ -1499,9 +1333,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -1515,9 +1347,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "shipping_address_collection": { @@ -1530,9 +1360,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "shipping_options": { @@ -1548,13 +1376,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1568,9 +1392,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "subscription_data": { @@ -1583,9 +1405,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "success_url": { @@ -1598,9 +1418,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -1614,9 +1432,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "ui_mode": { @@ -1629,9 +1445,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1648,9 +1462,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1666,9 +1478,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "enabled": { @@ -1677,9 +1487,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -1693,9 +1501,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "liability": { @@ -1707,9 +1513,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1725,9 +1529,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -1736,9 +1538,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1755,9 +1555,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "promotions": { @@ -1769,9 +1567,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "terms_of_service": { @@ -1783,9 +1579,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1798,9 +1592,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1816,9 +1608,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "key": { @@ -1827,9 +1617,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 200 } }, @@ -1839,9 +1627,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "numeric": { @@ -1853,9 +1639,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "optional": { @@ -1867,9 +1651,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "text": { @@ -1881,9 +1663,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "type": { @@ -1892,9 +1672,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1910,13 +1688,9 @@ "type": "array" }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1930,9 +1704,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 100 } }, @@ -1942,9 +1714,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 100 } } @@ -1958,9 +1728,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 50 } }, @@ -1970,9 +1738,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1988,9 +1754,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "minimum_length": { @@ -2002,9 +1766,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2020,9 +1782,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "minimum_length": { @@ -2034,9 +1794,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2053,9 +1811,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "shipping_address": { @@ -2067,9 +1823,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "submit": { @@ -2081,9 +1835,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "terms_of_service_acceptance": { @@ -2095,9 +1847,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2110,9 +1860,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1200 } } @@ -2126,9 +1874,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1200 } } @@ -2142,9 +1888,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1200 } } @@ -2158,9 +1902,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1200 } } @@ -2178,9 +1920,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "name": { @@ -2192,9 +1932,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "shipping": { @@ -2206,9 +1944,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2224,9 +1960,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -2239,9 +1973,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -2256,9 +1988,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "invoice_data": { @@ -2270,9 +2000,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2291,13 +2019,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -2314,13 +2038,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -2333,9 +2053,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1500 } }, @@ -2348,9 +2066,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -2363,9 +2079,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "metadata": { @@ -2377,9 +2091,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "rendering_options": { @@ -2391,9 +2103,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -2406,9 +2116,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 40 } }, @@ -2418,9 +2126,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 140 } } @@ -2437,9 +2143,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -2448,9 +2152,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2466,9 +2168,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -2484,9 +2184,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "dynamic_tax_rates": { @@ -2501,13 +2199,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -2521,9 +2215,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -2536,9 +2228,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "quantity": { @@ -2550,9 +2240,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "tax_rates": { @@ -2567,13 +2255,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -2588,9 +2272,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "maximum": { @@ -2602,9 +2284,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "minimum": { @@ -2616,9 +2296,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2631,9 +2309,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "product": { @@ -2645,9 +2321,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -2660,9 +2334,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "recurring": { @@ -2674,9 +2346,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "tax_behavior": { @@ -2688,9 +2358,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "unit_amount": { @@ -2702,9 +2370,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "unit_amount_decimal": { @@ -2716,9 +2382,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "decimal" } } @@ -2735,9 +2399,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 40000 } }, @@ -2753,13 +2415,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -2772,9 +2430,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "name": { @@ -2783,9 +2439,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -2798,9 +2452,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -2814,9 +2466,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "interval_count": { @@ -2828,9 +2478,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -2847,9 +2495,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "capture_method": { @@ -2861,9 +2507,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "description": { @@ -2875,9 +2519,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1000 } }, @@ -2890,9 +2532,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "on_behalf_of": { @@ -2904,9 +2544,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "receipt_email": { @@ -2918,9 +2556,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "setup_future_usage": { @@ -2932,9 +2568,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "shipping": { @@ -2946,9 +2580,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "statement_descriptor": { @@ -2960,9 +2592,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 22 } }, @@ -2975,9 +2605,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 22 } }, @@ -2990,9 +2618,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "transfer_group": { @@ -3004,9 +2630,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3019,9 +2643,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "carrier": { @@ -3033,9 +2655,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3045,9 +2665,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3060,9 +2678,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3075,9 +2691,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -3094,9 +2708,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3109,9 +2721,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3121,9 +2731,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3136,9 +2744,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3151,9 +2757,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -3166,9 +2770,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -3185,9 +2787,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "destination": { @@ -3196,9 +2796,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3215,9 +2813,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "affirm": { @@ -3229,9 +2825,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "afterpay_clearpay": { @@ -3243,9 +2837,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "alipay": { @@ -3257,9 +2849,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "au_becs_debit": { @@ -3271,9 +2861,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "bacs_debit": { @@ -3285,9 +2873,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "bancontact": { @@ -3299,9 +2885,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "boleto": { @@ -3313,9 +2897,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "card": { @@ -3327,9 +2909,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "cashapp": { @@ -3341,9 +2921,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "customer_balance": { @@ -3355,9 +2933,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "eps": { @@ -3369,9 +2945,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "fpx": { @@ -3383,9 +2957,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "giropay": { @@ -3397,9 +2969,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "grabpay": { @@ -3411,9 +2981,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "ideal": { @@ -3425,9 +2993,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "klarna": { @@ -3439,9 +3005,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "konbini": { @@ -3453,9 +3017,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "link": { @@ -3467,9 +3029,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "oxxo": { @@ -3481,9 +3041,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "p24": { @@ -3495,9 +3053,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "paynow": { @@ -3509,9 +3065,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "paypal": { @@ -3523,9 +3077,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "pix": { @@ -3537,9 +3089,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "revolut_pay": { @@ -3551,9 +3101,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "sepa_debit": { @@ -3565,9 +3113,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "sofort": { @@ -3579,9 +3125,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "swish": { @@ -3593,9 +3137,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "us_bank_account": { @@ -3607,9 +3149,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "wechat_pay": { @@ -3621,9 +3161,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -3639,9 +3177,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "currency": { @@ -3653,9 +3189,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "mandate_options": { @@ -3667,9 +3201,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "setup_future_usage": { @@ -3681,9 +3213,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "verification_method": { @@ -3695,9 +3225,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3713,9 +3241,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "default_for": { @@ -3730,13 +3256,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -3749,9 +3271,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 500 } }, @@ -3764,9 +3284,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "transaction_type": { @@ -3778,9 +3296,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3796,9 +3312,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3814,9 +3328,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3832,9 +3344,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3850,9 +3360,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3868,9 +3376,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3886,9 +3392,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3904,9 +3408,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "setup_future_usage": { @@ -3918,9 +3420,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -3936,9 +3436,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "request_three_d_secure": { @@ -3950,9 +3448,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "setup_future_usage": { @@ -3964,9 +3460,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "statement_descriptor_suffix_kana": { @@ -3978,9 +3472,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 22 } }, @@ -3993,9 +3485,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 17 } } @@ -4012,9 +3502,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -4030,9 +3518,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4048,9 +3534,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "funding_type": { @@ -4062,9 +3546,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "setup_future_usage": { @@ -4076,9 +3558,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4094,9 +3574,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "requested_address_types": { @@ -4111,13 +3589,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -4127,9 +3601,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4142,9 +3614,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -4161,9 +3631,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4179,9 +3647,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4197,9 +3663,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4215,9 +3679,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4233,9 +3695,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4251,9 +3711,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4269,9 +3727,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "setup_future_usage": { @@ -4283,9 +3739,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4301,9 +3755,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4319,9 +3771,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "setup_future_usage": { @@ -4333,9 +3783,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4351,9 +3799,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "tos_shown_and_accepted": { @@ -4365,9 +3811,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -4383,9 +3827,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4401,9 +3843,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "preferred_locale": { @@ -4415,9 +3855,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "reference": { @@ -4429,9 +3867,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 127 } }, @@ -4444,9 +3880,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 32 } }, @@ -4459,9 +3893,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4477,9 +3909,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -4495,9 +3925,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4513,9 +3941,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4531,9 +3957,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4549,9 +3973,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -4568,9 +3990,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "setup_future_usage": { @@ -4582,9 +4002,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "verification_method": { @@ -4596,9 +4014,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4617,13 +4033,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -4640,13 +4052,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4663,9 +4071,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -4675,9 +4081,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "setup_future_usage": { @@ -4689,9 +4093,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4705,9 +4107,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -4724,9 +4124,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 1000 } }, @@ -4739,9 +4137,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "on_behalf_of": { @@ -4753,9 +4149,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4772,13 +4166,9 @@ "type": "array" }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4795,9 +4185,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -4810,9 +4198,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -4828,9 +4214,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "display_name": { @@ -4839,9 +4223,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 100 } }, @@ -4854,9 +4236,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "metadata": { @@ -4868,9 +4248,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "tax_behavior": { @@ -4882,9 +4260,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "tax_code": { @@ -4896,9 +4272,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -4910,9 +4284,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -4928,9 +4300,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "minimum": { @@ -4942,9 +4312,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -4957,9 +4325,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -4968,9 +4334,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -4983,9 +4347,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -4994,9 +4356,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -5009,9 +4369,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "currency": { @@ -5020,9 +4378,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "currency_options": { @@ -5034,9 +4390,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -5053,9 +4407,7 @@ } }, "http": { - "type": [ - "number" - ] + "type": ["number"] } }, "billing_cycle_anchor": { @@ -5067,9 +4419,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -5085,13 +4435,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -5105,9 +4451,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 500 } }, @@ -5120,9 +4464,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "metadata": { @@ -5134,9 +4476,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "on_behalf_of": { @@ -5148,9 +4488,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "proration_behavior": { @@ -5162,9 +4500,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "transfer_data": { @@ -5176,9 +4512,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "trial_end": { @@ -5190,9 +4524,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -5205,9 +4537,7 @@ } }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "trial_settings": { @@ -5219,9 +4549,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -5237,9 +4565,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -5255,9 +4581,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -5266,9 +4590,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5284,9 +4606,7 @@ } }, "http": { - "type": [ - "number" - ] + "type": ["number"] } }, "destination": { @@ -5295,9 +4615,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5310,9 +4628,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -5325,9 +4641,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5341,9 +4655,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -5363,13 +4675,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -5386,13 +4694,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -5404,9 +4708,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "binary" } }, @@ -5420,9 +4722,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "purpose": { @@ -5432,9 +4732,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5448,9 +4746,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "expires_at": { @@ -5462,9 +4758,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -5497,13 +4791,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -5518,9 +4808,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -5537,9 +4825,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5555,9 +4841,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -5569,9 +4853,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -5587,9 +4869,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "id": { @@ -5601,9 +4881,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -5616,9 +4894,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -5631,9 +4907,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -5646,9 +4920,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -5662,9 +4934,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5680,9 +4950,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -5695,9 +4963,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5712,9 +4978,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "cancelable": { @@ -5724,9 +4988,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "context": { @@ -5748,9 +5010,7 @@ "type": "named" }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -5761,9 +5021,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "description": { @@ -5776,9 +5034,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -5792,9 +5048,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "financial_account": { @@ -5804,9 +5058,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -5820,9 +5072,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -5833,9 +5083,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -5846,9 +5094,7 @@ "type": "named" }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "metadata": { @@ -5858,9 +5104,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "object": { @@ -5870,9 +5114,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "origin_payment_method": { @@ -5882,9 +5124,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -5898,9 +5138,7 @@ } }, "http": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "statement_descriptor": { @@ -5910,9 +5148,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } }, @@ -5923,9 +5159,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "status_transitions": { @@ -5934,9 +5168,7 @@ "type": "named" }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "transaction": { @@ -5963,9 +5195,7 @@ "type": "named" }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -5982,9 +5212,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -5998,9 +5226,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } }, @@ -6014,9 +5240,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "unix-time" } } @@ -6033,9 +5257,7 @@ } }, "http": { - "type": [ - "object" - ] + "type": ["object"] } }, "addresses": { @@ -6050,9 +5272,7 @@ } }, "http": { - "type": [ - "array" - ] + "type": ["array"] } }, "children": { @@ -6067,13 +5287,9 @@ } }, "http": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -6086,9 +5302,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "uuid" } }, @@ -6101,9 +5315,7 @@ } }, "http": { - "type": [ - "string" - ], + "type": ["string"], "format": "binary" } } @@ -6120,9 +5332,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "firstName": { @@ -6134,9 +5344,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -6148,9 +5356,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -6163,9 +5369,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "password": { @@ -6177,9 +5381,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "phone": { @@ -6191,9 +5393,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } }, "userStatus": { @@ -6206,9 +5406,7 @@ } }, "http": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -6221,9 +5419,7 @@ } }, "http": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -6249,9 +5445,7 @@ "name": "account", "in": "path", "schema": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -6397,9 +5591,7 @@ "explode": true }, "expand_json": { - "contentType": [ - "application/json" - ] + "contentType": ["application/json"] }, "file": { "headers": { @@ -6407,9 +5599,7 @@ "explode": false, "argumentName": "headerXRateLimitLimit", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -6444,9 +5634,7 @@ "explode": false, "argumentName": "headerXRateLimitLimit", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -6502,9 +5690,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "string" - ], + "type": ["string"], "maxLength": 5000 } } @@ -6522,10 +5708,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -6651,9 +5834,7 @@ "name": "orderId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -6674,10 +5855,7 @@ "method": "delete", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -6697,9 +5875,7 @@ "name": "api_key", "in": "header", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -6713,9 +5889,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -6749,9 +5923,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -6803,10 +5975,7 @@ "method": "put", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -6840,10 +6009,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -6864,9 +6030,7 @@ "name": "name", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -6880,9 +6044,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -6900,9 +6062,7 @@ "name": "status", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -6922,10 +6082,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -6949,9 +6106,7 @@ "name": "additionalMetadata", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -6978,9 +6133,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -7000,18 +6153,13 @@ "contentType": "multipart/form-data", "encoding": { "profileImage": { - "contentType": [ - "image/png", - "image/jpeg" - ], + "contentType": ["image/png", "image/jpeg"], "headers": { "X-Rate-Limit-Limit": { "explode": false, "argumentName": "headerXRateLimitLimit", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -7046,9 +6194,7 @@ "explode": false, "argumentName": "headerXRateLimitLimit", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -7079,10 +6225,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "never" - ], + "one_of": ["auto", "never"], "type": "enum" } }, @@ -7090,10 +6233,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "AC", - "AD" - ], + "one_of": ["AC", "AD"], "type": "enum" } }, @@ -7101,11 +6241,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "", - "exclude_tax", - "include_inclusive_tax" - ], + "one_of": ["", "exclude_tax", "include_inclusive_tax"], "type": "enum" } }, @@ -7113,10 +6249,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "required" - ], + "one_of": ["auto", "required"], "type": "enum" } }, @@ -7124,11 +6257,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "automatic", - "automatic_async", - "manual" - ], + "one_of": ["automatic", "automatic_async", "manual"], "type": "enum" } }, @@ -7136,11 +6265,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "android", - "ios", - "web" - ], + "one_of": ["android", "ios", "web"], "type": "enum" } }, @@ -7148,10 +6273,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "cad", - "usd" - ], + "one_of": ["cad", "usd"], "type": "enum" } }, @@ -7159,10 +6281,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "always", - "if_required" - ], + "one_of": ["always", "if_required"], "type": "enum" } }, @@ -7170,10 +6289,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "invoice", - "subscription" - ], + "one_of": ["invoice", "subscription"], "type": "enum" } }, @@ -7181,9 +6297,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "bank_transfer" - ], + "one_of": ["bank_transfer"], "type": "enum" } }, @@ -7191,12 +6305,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "day", - "month", - "week", - "year" - ], + "one_of": ["day", "month", "week", "year"], "type": "enum" } }, @@ -7204,11 +6313,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "bg", - "cs" - ], + "one_of": ["auto", "bg", "cs"], "type": "enum" } }, @@ -7216,11 +6321,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "cancel", - "create_invoice", - "pause" - ], + "one_of": ["cancel", "create_invoice", "pause"], "type": "enum" } }, @@ -7228,11 +6329,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "payment", - "setup", - "subscription" - ], + "one_of": ["payment", "setup", "subscription"], "type": "enum" } }, @@ -7240,10 +6337,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "never" - ], + "one_of": ["auto", "never"], "type": "enum" } }, @@ -7251,10 +6345,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "always", - "if_required" - ], + "one_of": ["always", "if_required"], "type": "enum" } }, @@ -7262,10 +6353,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "acss_debit", - "affirm" - ], + "one_of": ["acss_debit", "affirm"], "type": "enum" } }, @@ -7273,11 +6361,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "combined", - "interval", - "sporadic" - ], + "one_of": ["combined", "interval", "sporadic"], "type": "enum" } }, @@ -7285,12 +6369,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "balances", - "ownership", - "payment_method", - "transactions" - ], + "one_of": ["balances", "ownership", "payment_method", "transactions"], "type": "enum" } }, @@ -7298,10 +6377,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "hidden" - ], + "one_of": ["auto", "hidden"], "type": "enum" } }, @@ -7309,10 +6385,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "cs-CZ", - "da-DK" - ], + "one_of": ["cs-CZ", "da-DK"], "type": "enum" } }, @@ -7320,10 +6393,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "balances", - "transactions" - ], + "one_of": ["balances", "transactions"], "type": "enum" } }, @@ -7331,10 +6401,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "none" - ], + "one_of": ["auto", "none"], "type": "enum" } }, @@ -7342,10 +6409,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "create_prorations", - "none" - ], + "one_of": ["create_prorations", "none"], "type": "enum" } }, @@ -7353,11 +6417,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "always", - "if_required", - "never" - ], + "one_of": ["always", "if_required", "never"], "type": "enum" } }, @@ -7365,11 +6425,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "any", - "automatic", - "challenge" - ], + "one_of": ["any", "automatic", "challenge"], "type": "enum" } }, @@ -7377,10 +6433,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "aba", - "iban" - ], + "one_of": ["aba", "iban"], "type": "enum" } }, @@ -7388,10 +6441,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "off_session", - "on_session" - ], + "one_of": ["off_session", "on_session"], "type": "enum" } }, @@ -7399,10 +6449,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "never" - ], + "one_of": ["auto", "never"], "type": "enum" } }, @@ -7410,12 +6457,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "book", - "donate", - "pay" - ], + "one_of": ["auto", "book", "donate", "pay"], "type": "enum" } }, @@ -7423,11 +6465,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "exclusive", - "inclusive", - "unspecified" - ], + "one_of": ["exclusive", "inclusive", "unspecified"], "type": "enum" } }, @@ -7435,10 +6473,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "required" - ], + "one_of": ["none", "required"], "type": "enum" } }, @@ -7446,10 +6481,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "business", - "personal" - ], + "one_of": ["business", "personal"], "type": "enum" } }, @@ -7457,10 +6489,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "account", - "self" - ], + "one_of": ["account", "self"], "type": "enum" } }, @@ -7468,10 +6497,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "embedded", - "hosted" - ], + "one_of": ["embedded", "hosted"], "type": "enum" } }, @@ -7479,13 +6505,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "business_day", - "day", - "hour", - "month", - "week" - ], + "one_of": ["business_day", "day", "hour", "month", "week"], "type": "enum" } }, @@ -7493,11 +6513,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "automatic", - "instant", - "microdeposits" - ], + "one_of": ["automatic", "instant", "microdeposits"], "type": "enum" } }, @@ -7546,10 +6562,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "charge_automatically", - "send_invoice" - ], + "one_of": ["charge_automatically", "send_invoice"], "type": "enum" } }, @@ -7557,9 +6570,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "list" - ], + "one_of": ["list"], "type": "enum" } }, @@ -7567,13 +6578,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "draft", - "open", - "paid", - "uncollectible", - "void" - ], + "one_of": ["draft", "open", "paid", "uncollectible", "void"], "type": "enum" } }, @@ -7588,11 +6593,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "placed", - "approved", - "delivered" - ], + "one_of": ["placed", "approved", "delivered"], "type": "enum" } }, @@ -7607,11 +6608,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "available", - "pending", - "sold" - ], + "one_of": ["available", "pending", "sold"], "type": "enum" } }, @@ -7619,9 +6616,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "custom" - ], + "one_of": ["custom"], "type": "enum" } }, @@ -7629,11 +6624,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "dropdown", - "numeric", - "text" - ], + "one_of": ["dropdown", "numeric", "text"], "type": "enum" } }, @@ -7641,11 +6632,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session", - "on_session" - ], + "one_of": ["none", "off_session", "on_session"], "type": "enum" } }, @@ -7653,9 +6640,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7663,9 +6648,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7673,9 +6656,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7683,9 +6664,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7693,11 +6672,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session", - "on_session" - ], + "one_of": ["none", "off_session", "on_session"], "type": "enum" } }, @@ -7705,9 +6680,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7715,11 +6688,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session", - "on_session" - ], + "one_of": ["none", "off_session", "on_session"], "type": "enum" } }, @@ -7727,11 +6696,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session", - "on_session" - ], + "one_of": ["none", "off_session", "on_session"], "type": "enum" } }, @@ -7739,10 +6704,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "eu_bank_transfer", - "gb_bank_transfer" - ], + "one_of": ["eu_bank_transfer", "gb_bank_transfer"], "type": "enum" } }, @@ -7750,9 +6712,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7760,9 +6720,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7770,9 +6728,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7780,9 +6736,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7790,9 +6744,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7800,9 +6752,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7810,9 +6760,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7820,9 +6768,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7830,10 +6776,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session" - ], + "one_of": ["none", "off_session"], "type": "enum" } }, @@ -7841,9 +6784,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7851,9 +6792,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7861,9 +6800,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7871,10 +6808,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "", - "manual" - ], + "one_of": ["", "manual"], "type": "enum" } }, @@ -7882,11 +6816,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "", - "none", - "off_session" - ], + "one_of": ["", "none", "off_session"], "type": "enum" } }, @@ -7894,10 +6824,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session" - ], + "one_of": ["none", "off_session"], "type": "enum" } }, @@ -7905,11 +6832,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session", - "on_session" - ], + "one_of": ["none", "off_session", "on_session"], "type": "enum" } }, @@ -7917,9 +6840,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7927,11 +6848,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none", - "off_session", - "on_session" - ], + "one_of": ["none", "off_session", "on_session"], "type": "enum" } }, @@ -7939,10 +6856,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "automatic", - "instant" - ], + "one_of": ["automatic", "instant"], "type": "enum" } }, @@ -7950,9 +6864,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "none" - ], + "one_of": ["none"], "type": "enum" } }, @@ -7960,9 +6872,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "fixed_amount" - ], + "one_of": ["fixed_amount"], "type": "enum" } }, @@ -7970,11 +6880,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "placed", - "approved", - "delivered" - ], + "one_of": ["placed", "approved", "delivered"], "type": "enum" } }, @@ -8018,9 +6924,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "treasury.inbound_transfer" - ], + "one_of": ["treasury.inbound_transfer"], "type": "enum" } }, @@ -8028,12 +6932,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "canceled", - "failed", - "processing", - "succeeded" - ], + "one_of": ["canceled", "failed", "processing", "succeeded"], "type": "enum" } }, diff --git a/ndc-http-schema/openapi/testdata/petstore3/schema.json b/ndc-http-schema/openapi/testdata/petstore3/schema.json index fb20d94..ec0d6f6 100644 --- a/ndc-http-schema/openapi/testdata/petstore3/schema.json +++ b/ndc-http-schema/openapi/testdata/petstore3/schema.json @@ -227,7 +227,7 @@ { "arguments": { "username": { - "description": "The name that needs to be fetched. Use user1 for testing. ", + "description": "The name that needs to be fetched. Use user1 for testing.", "type": { "name": "String", "type": "named" diff --git a/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.json b/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.json index 90aeb3c..fcc8888 100644 --- a/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.json +++ b/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.json @@ -63,7 +63,7 @@ "description": "Get all available posts", "result_type": { "element_type": { - "name": "Post", + "name": "HasuraMockJsonPost", "type": "named" }, "type": "array" @@ -71,7 +71,7 @@ } }, "object_types": { - "Post": { + "HasuraMockJsonPost": { "fields": { "body": { "type": { @@ -150,7 +150,7 @@ "body": { "description": "Post object that needs to be added", "type": { - "name": "Post", + "name": "HasuraMockJsonPost", "type": "named" }, "http": { @@ -165,7 +165,7 @@ }, "description": "Create a post", "result_type": { - "name": "Post", + "name": "HasuraMockJsonPost", "type": "named" } } diff --git a/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.schema.json b/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.schema.json index ee901f9..296ae06 100644 --- a/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.schema.json +++ b/ndc-http-schema/openapi/testdata/prefix2/expected_multi_words.schema.json @@ -28,7 +28,7 @@ "name": "hasuraMockJsonGetPosts", "result_type": { "element_type": { - "name": "Post", + "name": "HasuraMockJsonPost", "type": "named" }, "type": "array" @@ -36,7 +36,7 @@ } ], "object_types": { - "Post": { + "HasuraMockJsonPost": { "fields": { "body": { "type": { @@ -83,7 +83,7 @@ "body": { "description": "Post object that needs to be added", "type": { - "name": "Post", + "name": "HasuraMockJsonPost", "type": "named" } } @@ -91,7 +91,7 @@ "description": "Create a post", "name": "hasuraMockJsonCreatePost", "result_type": { - "name": "Post", + "name": "HasuraMockJsonPost", "type": "named" } } diff --git a/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.json b/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.json index e8f9f34..a0c0a45 100644 --- a/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.json +++ b/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.json @@ -63,7 +63,7 @@ "description": "Get all available posts", "result_type": { "element_type": { - "name": "Post", + "name": "HasuraPost", "type": "named" }, "type": "array" @@ -71,7 +71,7 @@ } }, "object_types": { - "Post": { + "HasuraPost": { "fields": { "body": { "type": { @@ -150,7 +150,7 @@ "body": { "description": "Post object that needs to be added", "type": { - "name": "Post", + "name": "HasuraPost", "type": "named" }, "http": { @@ -165,7 +165,7 @@ }, "description": "Create a post", "result_type": { - "name": "Post", + "name": "HasuraPost", "type": "named" } } diff --git a/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.schema.json b/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.schema.json index 83455d9..a05b2d3 100644 --- a/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.schema.json +++ b/ndc-http-schema/openapi/testdata/prefix2/expected_single_word.schema.json @@ -28,7 +28,7 @@ "name": "hasuraGetPosts", "result_type": { "element_type": { - "name": "Post", + "name": "HasuraPost", "type": "named" }, "type": "array" @@ -36,7 +36,7 @@ } ], "object_types": { - "Post": { + "HasuraPost": { "fields": { "body": { "type": { @@ -83,7 +83,7 @@ "body": { "description": "Post object that needs to be added", "type": { - "name": "Post", + "name": "HasuraPost", "type": "named" } } @@ -91,7 +91,7 @@ "description": "Create a post", "name": "hasuraCreatePost", "result_type": { - "name": "Post", + "name": "HasuraPost", "type": "named" } } diff --git a/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.json b/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.json index 85ccfd7..c0100f9 100644 --- a/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.json +++ b/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.json @@ -12,19 +12,19 @@ "securitySchemes": { "app_key": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "APP_KEY_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } }, "user_key": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "USER_KEY_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } } }, "version": "1.2.2" @@ -61,7 +61,7 @@ } }, "kind": { - "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only ", + "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only", "type": { "type": "nullable", "underlying_type": { @@ -120,19 +120,19 @@ }, "description": "View notifications", "result_type": { - "name": "NotificationSlice", + "name": "HasuraOneSignalNotificationSlice", "type": "named" } } }, "object_types": { - "CreateNotificationSuccessResponse": { + "HasuraOneSignalCreateNotificationSuccessResponse": { "fields": { "errors": { "type": { "type": "nullable", "underlying_type": { - "name": "Notification200Errors", + "name": "HasuraOneSignalNotification200Errors", "type": "named" } }, @@ -184,7 +184,7 @@ } } }, - "DeliveryData": { + "HasuraOneSignalDeliveryData": { "fields": { "converted": { "description": "Number of messages that were clicked.", @@ -263,7 +263,7 @@ } } }, - "Filter": { + "HasuraOneSignalFilter": { "fields": { "field": { "description": "Name of the field to use as the first operand in the filter expression.", @@ -295,7 +295,7 @@ "relation": { "description": "Operator of a filter expression.", "type": { - "name": "FilterRelation", + "name": "HasuraOneSignalFilterRelation", "type": "named" }, "http": { @@ -321,13 +321,13 @@ } } }, - "NotificationInput": { + "HasuraOneSignalNotificationInput": { "fields": { "contents": { "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } }, @@ -352,7 +352,7 @@ } }, "data": { - "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1} ", + "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}", "type": { "type": "nullable", "underlying_type": { @@ -371,7 +371,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraOneSignalFilter", "type": "named" }, "type": "array" @@ -387,7 +387,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } }, @@ -430,7 +430,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } }, @@ -442,7 +442,7 @@ } } }, - "NotificationSlice": { + "HasuraOneSignalNotificationSlice": { "fields": { "limit": { "type": { @@ -463,7 +463,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "NotificationWithMeta", + "name": "HasuraOneSignalNotificationWithMeta", "type": "named" }, "type": "array" @@ -505,7 +505,7 @@ } } }, - "NotificationWithMeta": { + "HasuraOneSignalNotificationWithMeta": { "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.", @@ -527,7 +527,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } }, @@ -609,7 +609,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraOneSignalFilter", "type": "named" }, "type": "array" @@ -625,7 +625,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } }, @@ -698,7 +698,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "OutcomeData", + "name": "HasuraOneSignalOutcomeData", "type": "named" }, "type": "array" @@ -715,7 +715,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlatformDeliveryData", + "name": "HasuraOneSignalPlatformDeliveryData", "type": "named" } }, @@ -791,7 +791,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } }, @@ -820,7 +820,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlayerNotificationTargetTargetChannel", + "name": "HasuraOneSignalPlayerNotificationTargetTargetChannel", "type": "named" } }, @@ -847,11 +847,11 @@ } } }, - "OutcomeData": { + "HasuraOneSignalOutcomeData": { "fields": { "aggregation": { "type": { - "name": "OutcomeDataAggregation", + "name": "HasuraOneSignalOutcomeDataAggregation", "type": "named" }, "http": { @@ -884,14 +884,14 @@ } } }, - "PlatformDeliveryData": { + "HasuraOneSignalPlatformDeliveryData": { "description": "Hash of delivery statistics broken out by target device platform.", "fields": { "android": { "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -905,7 +905,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -919,7 +919,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -933,7 +933,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -947,7 +947,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -961,7 +961,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -975,7 +975,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -989,7 +989,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } }, @@ -1001,7 +1001,7 @@ } } }, - "StringMap": { + "HasuraOneSignalStringMap": { "fields": { "en": { "description": "Text in English. Will be used as a fallback", @@ -1042,7 +1042,7 @@ "body": { "description": "Request body of POST /notifications", "type": { - "name": "NotificationInput", + "name": "HasuraOneSignalNotificationInput", "type": "named" }, "http": { @@ -1052,13 +1052,13 @@ }, "description": "Create notification", "result_type": { - "name": "CreateNotificationSuccessResponse", + "name": "HasuraOneSignalCreateNotificationSuccessResponse", "type": "named" } } }, "scalar_types": { - "FilterRelation": { + "HasuraOneSignalFilterRelation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { @@ -1075,55 +1075,55 @@ "type": "enum" } }, - "Int32": { + "HasuraOneSignalNotification200Errors": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int32" + "type": "json" } }, - "Int64": { + "HasuraOneSignalOutcomeDataAggregation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int64" + "one_of": [ + "sum", + "count" + ], + "type": "enum" } }, - "JSON": { + "HasuraOneSignalPlayerNotificationTargetTargetChannel": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" } }, - "Notification200Errors": { + "Int32": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "type": "int32" } }, - "OutcomeDataAggregation": { + "Int64": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], - "type": "enum" + "type": "int64" } }, - "PlayerNotificationTargetTargetChannel": { + "JSON": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], - "type": "enum" + "type": "json" } }, "String": { diff --git a/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.schema.json b/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.schema.json index b121c4a..a34a6fd 100644 --- a/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.schema.json +++ b/ndc-http-schema/openapi/testdata/prefix3/expected_multi_words.schema.json @@ -11,7 +11,7 @@ } }, "kind": { - "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only ", + "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only", "type": { "type": "nullable", "underlying_type": { @@ -44,19 +44,19 @@ "description": "View notifications", "name": "hasuraOneSignalGetNotifications", "result_type": { - "name": "NotificationSlice", + "name": "HasuraOneSignalNotificationSlice", "type": "named" } } ], "object_types": { - "CreateNotificationSuccessResponse": { + "HasuraOneSignalCreateNotificationSuccessResponse": { "fields": { "errors": { "type": { "type": "nullable", "underlying_type": { - "name": "Notification200Errors", + "name": "HasuraOneSignalNotification200Errors", "type": "named" } } @@ -90,7 +90,7 @@ } } }, - "DeliveryData": { + "HasuraOneSignalDeliveryData": { "fields": { "converted": { "description": "Number of messages that were clicked.", @@ -144,7 +144,7 @@ } } }, - "Filter": { + "HasuraOneSignalFilter": { "fields": { "field": { "description": "Name of the field to use as the first operand in the filter expression.", @@ -166,7 +166,7 @@ "relation": { "description": "Operator of a filter expression.", "type": { - "name": "FilterRelation", + "name": "HasuraOneSignalFilterRelation", "type": "named" } }, @@ -182,13 +182,13 @@ } } }, - "NotificationInput": { + "HasuraOneSignalNotificationInput": { "fields": { "contents": { "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } } @@ -203,7 +203,7 @@ } }, "data": { - "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1} ", + "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}", "type": { "type": "nullable", "underlying_type": { @@ -217,7 +217,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraOneSignalFilter", "type": "named" }, "type": "array" @@ -228,7 +228,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } } @@ -255,14 +255,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } } } } }, - "NotificationSlice": { + "HasuraOneSignalNotificationSlice": { "fields": { "limit": { "type": { @@ -278,7 +278,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "NotificationWithMeta", + "name": "HasuraOneSignalNotificationWithMeta", "type": "named" }, "type": "array" @@ -305,7 +305,7 @@ } } }, - "NotificationWithMeta": { + "HasuraOneSignalNotificationWithMeta": { "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.", @@ -321,7 +321,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } } @@ -373,7 +373,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraOneSignalFilter", "type": "named" }, "type": "array" @@ -384,7 +384,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } } @@ -427,7 +427,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "OutcomeData", + "name": "HasuraOneSignalOutcomeData", "type": "named" }, "type": "array" @@ -439,7 +439,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlatformDeliveryData", + "name": "HasuraOneSignalPlatformDeliveryData", "type": "named" } } @@ -488,7 +488,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraOneSignalStringMap", "type": "named" } } @@ -507,7 +507,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlayerNotificationTargetTargetChannel", + "name": "HasuraOneSignalPlayerNotificationTargetTargetChannel", "type": "named" } } @@ -524,11 +524,11 @@ } } }, - "OutcomeData": { + "HasuraOneSignalOutcomeData": { "fields": { "aggregation": { "type": { - "name": "OutcomeDataAggregation", + "name": "HasuraOneSignalOutcomeDataAggregation", "type": "named" } }, @@ -546,14 +546,14 @@ } } }, - "PlatformDeliveryData": { + "HasuraOneSignalPlatformDeliveryData": { "description": "Hash of delivery statistics broken out by target device platform.", "fields": { "android": { "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -562,7 +562,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -571,7 +571,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -580,7 +580,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -589,7 +589,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -598,7 +598,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -607,7 +607,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } @@ -616,14 +616,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraOneSignalDeliveryData", "type": "named" } } } } }, - "StringMap": { + "HasuraOneSignalStringMap": { "fields": { "en": { "description": "Text in English. Will be used as a fallback", @@ -644,7 +644,7 @@ "body": { "description": "Request body of POST /notifications", "type": { - "name": "NotificationInput", + "name": "HasuraOneSignalNotificationInput", "type": "named" } } @@ -652,13 +652,13 @@ "description": "Create notification", "name": "hasuraOneSignalCreateNotification", "result_type": { - "name": "CreateNotificationSuccessResponse", + "name": "HasuraOneSignalCreateNotificationSuccessResponse", "type": "named" } } ], "scalar_types": { - "FilterRelation": { + "HasuraOneSignalFilterRelation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { @@ -675,55 +675,55 @@ "type": "enum" } }, - "Int32": { + "HasuraOneSignalNotification200Errors": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int32" + "type": "json" } }, - "Int64": { + "HasuraOneSignalOutcomeDataAggregation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int64" + "one_of": [ + "sum", + "count" + ], + "type": "enum" } }, - "JSON": { + "HasuraOneSignalPlayerNotificationTargetTargetChannel": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" } }, - "Notification200Errors": { + "Int32": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "type": "int32" } }, - "OutcomeDataAggregation": { + "Int64": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], - "type": "enum" + "type": "int64" } }, - "PlayerNotificationTargetTargetChannel": { + "JSON": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], - "type": "enum" + "type": "json" } }, "String": { diff --git a/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.json b/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.json index 4c1e863..66b500d 100644 --- a/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.json +++ b/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.json @@ -12,19 +12,19 @@ "securitySchemes": { "app_key": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "APP_KEY_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } }, "user_key": { "type": "http", + "header": "Authorization", + "scheme": "bearer", "value": { "env": "USER_KEY_TOKEN" - }, - "header": "Authorization", - "scheme": "bearer" + } } }, "version": "1.2.2" @@ -61,7 +61,7 @@ } }, "kind": { - "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only ", + "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only", "type": { "type": "nullable", "underlying_type": { @@ -120,19 +120,19 @@ }, "description": "View notifications", "result_type": { - "name": "NotificationSlice", + "name": "HasuraNotificationSlice", "type": "named" } } }, "object_types": { - "CreateNotificationSuccessResponse": { + "HasuraCreateNotificationSuccessResponse": { "fields": { "errors": { "type": { "type": "nullable", "underlying_type": { - "name": "Notification200Errors", + "name": "HasuraNotification200Errors", "type": "named" } }, @@ -184,7 +184,7 @@ } } }, - "DeliveryData": { + "HasuraDeliveryData": { "fields": { "converted": { "description": "Number of messages that were clicked.", @@ -263,7 +263,7 @@ } } }, - "Filter": { + "HasuraFilter": { "fields": { "field": { "description": "Name of the field to use as the first operand in the filter expression.", @@ -295,7 +295,7 @@ "relation": { "description": "Operator of a filter expression.", "type": { - "name": "FilterRelation", + "name": "HasuraFilterRelation", "type": "named" }, "http": { @@ -321,13 +321,13 @@ } } }, - "NotificationInput": { + "HasuraNotificationInput": { "fields": { "contents": { "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } }, @@ -352,7 +352,7 @@ } }, "data": { - "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1} ", + "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}", "type": { "type": "nullable", "underlying_type": { @@ -371,7 +371,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraFilter", "type": "named" }, "type": "array" @@ -387,7 +387,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } }, @@ -430,7 +430,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } }, @@ -442,7 +442,7 @@ } } }, - "NotificationSlice": { + "HasuraNotificationSlice": { "fields": { "limit": { "type": { @@ -463,7 +463,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "NotificationWithMeta", + "name": "HasuraNotificationWithMeta", "type": "named" }, "type": "array" @@ -505,7 +505,7 @@ } } }, - "NotificationWithMeta": { + "HasuraNotificationWithMeta": { "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.", @@ -527,7 +527,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } }, @@ -609,7 +609,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraFilter", "type": "named" }, "type": "array" @@ -625,7 +625,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } }, @@ -698,7 +698,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "OutcomeData", + "name": "HasuraOutcomeData", "type": "named" }, "type": "array" @@ -715,7 +715,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlatformDeliveryData", + "name": "HasuraPlatformDeliveryData", "type": "named" } }, @@ -791,7 +791,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } }, @@ -820,7 +820,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlayerNotificationTargetTargetChannel", + "name": "HasuraPlayerNotificationTargetTargetChannel", "type": "named" } }, @@ -847,11 +847,11 @@ } } }, - "OutcomeData": { + "HasuraOutcomeData": { "fields": { "aggregation": { "type": { - "name": "OutcomeDataAggregation", + "name": "HasuraOutcomeDataAggregation", "type": "named" }, "http": { @@ -884,14 +884,14 @@ } } }, - "PlatformDeliveryData": { + "HasuraPlatformDeliveryData": { "description": "Hash of delivery statistics broken out by target device platform.", "fields": { "android": { "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -905,7 +905,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -919,7 +919,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -933,7 +933,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -947,7 +947,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -961,7 +961,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -975,7 +975,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -989,7 +989,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } }, @@ -1001,7 +1001,7 @@ } } }, - "StringMap": { + "HasuraStringMap": { "fields": { "en": { "description": "Text in English. Will be used as a fallback", @@ -1042,7 +1042,7 @@ "body": { "description": "Request body of POST /notifications", "type": { - "name": "NotificationInput", + "name": "HasuraNotificationInput", "type": "named" }, "http": { @@ -1052,13 +1052,13 @@ }, "description": "Create notification", "result_type": { - "name": "CreateNotificationSuccessResponse", + "name": "HasuraCreateNotificationSuccessResponse", "type": "named" } } }, "scalar_types": { - "FilterRelation": { + "HasuraFilterRelation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { @@ -1075,55 +1075,55 @@ "type": "enum" } }, - "Int32": { + "HasuraNotification200Errors": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int32" + "type": "json" } }, - "Int64": { + "HasuraOutcomeDataAggregation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int64" + "one_of": [ + "sum", + "count" + ], + "type": "enum" } }, - "JSON": { + "HasuraPlayerNotificationTargetTargetChannel": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" } }, - "Notification200Errors": { + "Int32": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "type": "int32" } }, - "OutcomeDataAggregation": { + "Int64": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], - "type": "enum" + "type": "int64" } }, - "PlayerNotificationTargetTargetChannel": { + "JSON": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], - "type": "enum" + "type": "json" } }, "String": { diff --git a/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.schema.json b/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.schema.json index 130b90e..3fca3b8 100644 --- a/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.schema.json +++ b/ndc-http-schema/openapi/testdata/prefix3/expected_single_word.schema.json @@ -11,7 +11,7 @@ } }, "kind": { - "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only ", + "description": "Kind of notifications returned: * unset - All notification types (default) * `0` - Dashboard only * `1` - API only * `3` - Automated only", "type": { "type": "nullable", "underlying_type": { @@ -44,19 +44,19 @@ "description": "View notifications", "name": "hasuraGetNotifications", "result_type": { - "name": "NotificationSlice", + "name": "HasuraNotificationSlice", "type": "named" } } ], "object_types": { - "CreateNotificationSuccessResponse": { + "HasuraCreateNotificationSuccessResponse": { "fields": { "errors": { "type": { "type": "nullable", "underlying_type": { - "name": "Notification200Errors", + "name": "HasuraNotification200Errors", "type": "named" } } @@ -90,7 +90,7 @@ } } }, - "DeliveryData": { + "HasuraDeliveryData": { "fields": { "converted": { "description": "Number of messages that were clicked.", @@ -144,7 +144,7 @@ } } }, - "Filter": { + "HasuraFilter": { "fields": { "field": { "description": "Name of the field to use as the first operand in the filter expression.", @@ -166,7 +166,7 @@ "relation": { "description": "Operator of a filter expression.", "type": { - "name": "FilterRelation", + "name": "HasuraFilterRelation", "type": "named" } }, @@ -182,13 +182,13 @@ } } }, - "NotificationInput": { + "HasuraNotificationInput": { "fields": { "contents": { "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } } @@ -203,7 +203,7 @@ } }, "data": { - "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1} ", + "description": "Channel: Push Notifications Platform: Huawei A 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. Example: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}", "type": { "type": "nullable", "underlying_type": { @@ -217,7 +217,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraFilter", "type": "named" }, "type": "array" @@ -228,7 +228,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } } @@ -255,14 +255,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } } } } }, - "NotificationSlice": { + "HasuraNotificationSlice": { "fields": { "limit": { "type": { @@ -278,7 +278,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "NotificationWithMeta", + "name": "HasuraNotificationWithMeta", "type": "named" }, "type": "array" @@ -305,7 +305,7 @@ } } }, - "NotificationWithMeta": { + "HasuraNotificationWithMeta": { "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.", @@ -321,7 +321,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } } @@ -373,7 +373,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "Filter", + "name": "HasuraFilter", "type": "named" }, "type": "array" @@ -384,7 +384,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } } @@ -427,7 +427,7 @@ "type": "nullable", "underlying_type": { "element_type": { - "name": "OutcomeData", + "name": "HasuraOutcomeData", "type": "named" }, "type": "array" @@ -439,7 +439,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlatformDeliveryData", + "name": "HasuraPlatformDeliveryData", "type": "named" } } @@ -488,7 +488,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "StringMap", + "name": "HasuraStringMap", "type": "named" } } @@ -507,7 +507,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "PlayerNotificationTargetTargetChannel", + "name": "HasuraPlayerNotificationTargetTargetChannel", "type": "named" } } @@ -524,11 +524,11 @@ } } }, - "OutcomeData": { + "HasuraOutcomeData": { "fields": { "aggregation": { "type": { - "name": "OutcomeDataAggregation", + "name": "HasuraOutcomeDataAggregation", "type": "named" } }, @@ -546,14 +546,14 @@ } } }, - "PlatformDeliveryData": { + "HasuraPlatformDeliveryData": { "description": "Hash of delivery statistics broken out by target device platform.", "fields": { "android": { "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -562,7 +562,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -571,7 +571,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -580,7 +580,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -589,7 +589,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -598,7 +598,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -607,7 +607,7 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } @@ -616,14 +616,14 @@ "type": { "type": "nullable", "underlying_type": { - "name": "DeliveryData", + "name": "HasuraDeliveryData", "type": "named" } } } } }, - "StringMap": { + "HasuraStringMap": { "fields": { "en": { "description": "Text in English. Will be used as a fallback", @@ -644,7 +644,7 @@ "body": { "description": "Request body of POST /notifications", "type": { - "name": "NotificationInput", + "name": "HasuraNotificationInput", "type": "named" } } @@ -652,13 +652,13 @@ "description": "Create notification", "name": "hasuraCreateNotification", "result_type": { - "name": "CreateNotificationSuccessResponse", + "name": "HasuraCreateNotificationSuccessResponse", "type": "named" } } ], "scalar_types": { - "FilterRelation": { + "HasuraFilterRelation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { @@ -675,55 +675,55 @@ "type": "enum" } }, - "Int32": { + "HasuraNotification200Errors": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int32" + "type": "json" } }, - "Int64": { + "HasuraOutcomeDataAggregation": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "int64" + "one_of": [ + "sum", + "count" + ], + "type": "enum" } }, - "JSON": { + "HasuraPlayerNotificationTargetTargetChannel": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" } }, - "Notification200Errors": { + "Int32": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "type": "json" + "type": "int32" } }, - "OutcomeDataAggregation": { + "Int64": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], - "type": "enum" + "type": "int64" } }, - "PlayerNotificationTargetTargetChannel": { + "JSON": { "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], - "type": "enum" + "type": "json" } }, "String": { diff --git a/ndc-http-schema/schema/auth.go b/ndc-http-schema/schema/auth.go index cc288ef..606e8ce 100644 --- a/ndc-http-schema/schema/auth.go +++ b/ndc-http-schema/schema/auth.go @@ -11,11 +11,22 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" ) +const ( + HTTPAuthSchemeBearer = "bearer" + AuthorizationHeader = "Authorization" +) + +var ( + errSecuritySchemerRequired = errors.New("SecuritySchemer is required") +) + // SecuritySchemeType represents the authentication scheme enum type SecuritySchemeType string const ( APIKeyScheme SecuritySchemeType = "apiKey" + BasicAuthScheme SecuritySchemeType = "basic" + CookieAuthScheme SecuritySchemeType = "cookie" HTTPAuthScheme SecuritySchemeType = "http" OAuth2Scheme SecuritySchemeType = "oauth2" OpenIDConnectScheme SecuritySchemeType = "openIdConnect" @@ -25,6 +36,8 @@ const ( var securityScheme_enums = []SecuritySchemeType{ APIKeyScheme, HTTPAuthScheme, + BasicAuthScheme, + CookieAuthScheme, OAuth2Scheme, OpenIDConnectScheme, MutualTLSScheme, @@ -51,6 +64,7 @@ func (j *SecuritySchemeType) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -60,6 +74,7 @@ func ParseSecuritySchemeType(value string) (SecuritySchemeType, error) { if !slices.Contains(securityScheme_enums, result) { return result, fmt.Errorf("invalid SecuritySchemeType. Expected %+v, got <%s>", securityScheme_enums, value) } + return result, nil } @@ -95,6 +110,7 @@ func (j *APIKeyLocation) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -104,34 +120,35 @@ func ParseAPIKeyLocation(value string) (APIKeyLocation, error) { if !slices.Contains(apiKeyLocation_enums, result) { return result, fmt.Errorf("invalid APIKeyLocation. Expected %+v, got <%s>", apiKeyLocation_enums, value) } + return result, nil } +// SecuritySchemer abstracts an interface of SecurityScheme. +type SecuritySchemer interface { + GetType() SecuritySchemeType + Validate() error +} + // SecurityScheme contains authentication configurations. // The schema follows [OpenAPI 3] specification // // [OpenAPI 3]: https://swagger.io/docs/specification/authentication type SecurityScheme struct { - Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` - Value *utils.EnvString `json:"value,omitempty" mapstructure:"value" yaml:"value,omitempty"` - *APIKeyAuthConfig `yaml:",inline"` - *HTTPAuthConfig `yaml:",inline"` - *OAuth2Config `yaml:",inline"` - *OpenIDConfig `yaml:",inline"` - - value *string + SecuritySchemer } // JSONSchema is used to generate a custom jsonschema func (j SecurityScheme) JSONSchema() *jsonschema.Schema { + envStringRef := &jsonschema.Schema{ + Ref: "#/$defs/EnvString", + } apiKeySchema := orderedmap.New[string, *jsonschema.Schema]() apiKeySchema.Set("type", &jsonschema.Schema{ Type: "string", Enum: []any{APIKeyScheme}, }) - apiKeySchema.Set("value", &jsonschema.Schema{ - Type: "string", - }) + apiKeySchema.Set("value", envStringRef) apiKeySchema.Set("in", (APIKeyLocation("")).JSONSchema()) apiKeySchema.Set("name", &jsonschema.Schema{ Type: "string", @@ -142,9 +159,7 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { Type: "string", Enum: []any{HTTPAuthScheme}, }) - httpAuthSchema.Set("value", &jsonschema.Schema{ - Type: "string", - }) + httpAuthSchema.Set("value", envStringRef) httpAuthSchema.Set("header", &jsonschema.Schema{ Type: "string", }) @@ -152,6 +167,21 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { Type: "string", }) + basicAuthSchema := orderedmap.New[string, *jsonschema.Schema]() + basicAuthSchema.Set("type", &jsonschema.Schema{ + Type: "string", + Enum: []any{BasicAuthScheme}, + }) + basicAuthSchema.Set("username", envStringRef) + basicAuthSchema.Set("password", envStringRef) + httpAuthSchema.Set("header", &jsonschema.Schema{ + Description: "Request contains a header field in the form of Authorization: Basic ", + OneOf: []*jsonschema.Schema{ + {Type: "string"}, + {Type: "null"}, + }, + }) + oauth2Schema := orderedmap.New[string, *jsonschema.Schema]() oauth2Schema.Set("type", &jsonschema.Schema{ Type: "string", @@ -171,6 +201,18 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { Type: "string", }) + cookieSchema := orderedmap.New[string, *jsonschema.Schema]() + cookieSchema.Set("type", &jsonschema.Schema{ + Type: "string", + Enum: []any{CookieAuthScheme}, + }) + + mutualTLSSchema := orderedmap.New[string, *jsonschema.Schema]() + mutualTLSSchema.Set("type", &jsonschema.Schema{ + Type: "string", + Enum: []any{MutualTLSScheme}, + }) + return &jsonschema.Schema{ OneOf: []*jsonschema.Schema{ { @@ -178,6 +220,11 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { Required: []string{"type", "value", "in", "name"}, Properties: apiKeySchema, }, + { + Type: "object", + Properties: basicAuthSchema, + Required: []string{"type", "username", "password"}, + }, { Type: "object", Properties: httpAuthSchema, @@ -193,120 +240,299 @@ func (j SecurityScheme) JSONSchema() *jsonschema.Schema { Properties: oidcSchema, Required: []string{"type", "openIdConnectUrl"}, }, + { + Type: "object", + Properties: cookieSchema, + Required: []string{"type"}, + }, + { + Type: "object", + Properties: mutualTLSSchema, + Required: []string{"type"}, + }, }, } } +type rawSecurityScheme struct { + Type SecuritySchemeType `json:"type" yaml:"type"` +} + // UnmarshalJSON implements json.Unmarshaler. func (j *SecurityScheme) UnmarshalJSON(b []byte) error { - type Plain SecurityScheme - - var raw Plain - if err := json.Unmarshal(b, &raw); err != nil { + var rawScheme rawSecurityScheme + if err := json.Unmarshal(b, &rawScheme); err != nil { return err } - result := SecurityScheme(raw) - - if err := result.Validate(); err != nil { - return err - } - *j = result - return nil -} - -// Validate if the current instance is valid -func (ss *SecurityScheme) Validate() error { - if _, err := ParseSecuritySchemeType(string(ss.Type)); err != nil { - return err - } - switch ss.Type { + switch rawScheme.Type { case APIKeyScheme: - if ss.APIKeyAuthConfig == nil { - ss.APIKeyAuthConfig = &APIKeyAuthConfig{} + var config APIKeyAuthConfig + if err := json.Unmarshal(b, &config); err != nil { + return err } - return ss.APIKeyAuthConfig.Validate() + _ = config.Validate() + j.SecuritySchemer = &config + case BasicAuthScheme: + var config BasicAuthConfig + if err := json.Unmarshal(b, &config); err != nil { + return err + } + _ = config.Validate() + j.SecuritySchemer = &config case HTTPAuthScheme: - if ss.HTTPAuthConfig == nil { - ss.HTTPAuthConfig = &HTTPAuthConfig{} + var config HTTPAuthConfig + if err := json.Unmarshal(b, &config); err != nil { + return err } - return ss.HTTPAuthConfig.Validate() + _ = config.Validate() + j.SecuritySchemer = &config case OAuth2Scheme: - if ss.OAuth2Config == nil { - ss.OAuth2Config = &OAuth2Config{} + var config OAuth2Config + if err := json.Unmarshal(b, &config); err != nil { + return err } - return ss.OAuth2Config.Validate() + _ = config.Validate() + j.SecuritySchemer = &config case OpenIDConnectScheme: - if ss.OpenIDConfig == nil { - ss.OpenIDConfig = &OpenIDConfig{} + var config OpenIDConnectConfig + if err := json.Unmarshal(b, &config); err != nil { + return err } - return ss.OpenIDConfig.Validate() - } - - if ss.Value != nil { - value, err := ss.Value.Get() - if err != nil { - return fmt.Errorf("SecurityScheme.Value: %w", err) + _ = config.Validate() + j.SecuritySchemer = &config + case CookieAuthScheme: + j.SecuritySchemer = &CookieAuthConfig{ + Type: rawScheme.Type, } - if value != "" { - ss.value = &value + case MutualTLSScheme: + j.SecuritySchemer = &MutualTLSAuthConfig{ + Type: rawScheme.Type, } } return nil } -// GetValue get the authentication credential value -func (ss SecurityScheme) GetValue() string { - if ss.value != nil { - return *ss.value - } +// MarshalJSON implements json.Marshaler. +func (j SecurityScheme) MarshalJSON() ([]byte, error) { + return json.Marshal(j.SecuritySchemer) +} - if ss.Value != nil { - value, _ := ss.Value.Get() - return value +// Validate if the current instance is valid +func (ss *SecurityScheme) Validate() error { + if ss.SecuritySchemer == nil { + return errSecuritySchemerRequired } - return "" + return ss.SecuritySchemer.Validate() } // APIKeyAuthConfig contains configurations for [apiKey authentication] // // [apiKey authentication]: https://swagger.io/docs/specification/authentication/api-keys/ type APIKeyAuthConfig struct { - In APIKeyLocation `json:"in" mapstructure:"in" yaml:"in"` - Name string `json:"name" mapstructure:"name" yaml:"name"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + In APIKeyLocation `json:"in" mapstructure:"in" yaml:"in"` + Name string `json:"name" mapstructure:"name" yaml:"name"` + Value utils.EnvString `json:"value" mapstructure:"value" yaml:"value"` + + // cached values + value *string +} + +var _ SecuritySchemer = &APIKeyAuthConfig{} + +// NewAPIKeyAuthConfig creates a new APIKeyAuthConfig instance. +func NewAPIKeyAuthConfig(name string, in APIKeyLocation, value utils.EnvString) *APIKeyAuthConfig { + return &APIKeyAuthConfig{ + Type: APIKeyScheme, + Name: name, + In: in, + Value: value, + } +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *APIKeyAuthConfig) UnmarshalJSON(b []byte) error { + type Plain APIKeyAuthConfig + + var raw Plain + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + + result := APIKeyAuthConfig(raw) + _ = result.Validate() + *j = result + + return nil } // Validate if the current instance is valid -func (ss APIKeyAuthConfig) Validate() error { +func (ss *APIKeyAuthConfig) Validate() error { if ss.Name == "" { return errors.New("name is required for apiKey security") } if _, err := ParseAPIKeyLocation(string(ss.In)); err != nil { return err } + + value, err := ss.Value.Get() + if err != nil { + return fmt.Errorf("APIKeyAuthConfig.Value: %w", err) + } + if value != "" { + ss.value = &value + } + return nil } +// GetValue get the authentication credential value +func (ss APIKeyAuthConfig) GetType() SecuritySchemeType { + return ss.Type +} + +// GetValue get the authentication credential value +func (ss APIKeyAuthConfig) GetValue() string { + if ss.value != nil { + return *ss.value + } + + value, _ := ss.Value.Get() + + return value +} + // HTTPAuthConfig contains configurations for http authentication -// If the scheme is [basic] or [bearer], the authenticator follows OpenAPI 3 specification. +// If the scheme is [bearer], the authenticator follows OpenAPI 3 specification. // -// [basic]: https://swagger.io/docs/specification/authentication/basic-authentication // [bearer]: https://swagger.io/docs/specification/authentication/bearer-authentication type HTTPAuthConfig struct { - Header string `json:"header" mapstructure:"header" yaml:"header"` - Scheme string `json:"scheme" mapstructure:"scheme" yaml:"scheme"` + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Header string `json:"header" mapstructure:"header" yaml:"header"` + Scheme string `json:"scheme" mapstructure:"scheme" yaml:"scheme"` + Value utils.EnvString `json:"value" mapstructure:"value" yaml:"value"` + + // cached values + value *string +} + +var _ SecuritySchemer = &HTTPAuthConfig{} + +// NewHTTPAuthConfig creates a new HTTPAuthConfig instance. +func NewHTTPAuthConfig(scheme string, header string, value utils.EnvString) *HTTPAuthConfig { + return &HTTPAuthConfig{ + Type: HTTPAuthScheme, + Header: header, + Scheme: scheme, + Value: value, + } } // Validate if the current instance is valid -func (ss HTTPAuthConfig) Validate() error { +func (ss *HTTPAuthConfig) Validate() error { if ss.Scheme == "" { return errors.New("schema is required for http security") } + + value, err := ss.Value.Get() + if err != nil { + return fmt.Errorf("APIKeyAuthConfig.Value: %w", err) + } + if value != "" { + ss.value = &value + } + + return nil +} + +// GetValue get the authentication credential value +func (ss HTTPAuthConfig) GetType() SecuritySchemeType { + return ss.Type +} + +// GetValue get the authentication credential value +func (ss HTTPAuthConfig) GetValue() string { + if ss.value != nil { + return *ss.value + } + + value, _ := ss.Value.Get() + + return value +} + +// BasicAuthConfig contains configurations for the [basic] authentication. +// +// [basic]: https://swagger.io/docs/specification/authentication/basic-authentication +type BasicAuthConfig struct { + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + Header string `json:"header" mapstructure:"header" yaml:"header"` + Username utils.EnvString `json:"username" mapstructure:"username" yaml:"username"` + Password utils.EnvString `json:"password" mapstructure:"password" yaml:"password"` + + // cached values + username *string + password *string +} + +// NewBasicAuthConfig creates a new BasicAuthConfig instance. +func NewBasicAuthConfig(username, password utils.EnvString) *BasicAuthConfig { + return &BasicAuthConfig{ + Type: BasicAuthScheme, + Username: username, + Password: password, + } +} + +// Validate if the current instance is valid +func (ss *BasicAuthConfig) Validate() error { + user, err := ss.Username.Get() + if err != nil { + return fmt.Errorf("BasicAuthConfig.User: %w", err) + } + + // user and password can be empty. + ss.username = &user + + password, err := ss.Password.Get() + if err != nil { + return fmt.Errorf("BasicAuthConfig.Password: %w", err) + } + ss.password = &password + return nil } +// GetValue get the authentication credential value +func (ss BasicAuthConfig) GetType() SecuritySchemeType { + return ss.Type +} + +// GetUsername get the username value +func (ss BasicAuthConfig) GetUsername() string { + if ss.username != nil { + return *ss.username + } + + value, _ := ss.Username.Get() + + return value +} + +// GetPassword get the password value +func (ss BasicAuthConfig) GetPassword() string { + if ss.password != nil { + return *ss.password + } + + value, _ := ss.Password.Get() + + return value +} + // OAuthFlowType represents the OAuth flow type enum type OAuthFlowType string @@ -337,6 +563,7 @@ func (j *OAuthFlowType) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -346,6 +573,7 @@ func ParseOAuthFlowType(value string) (OAuthFlowType, error) { if !slices.Contains(oauthFlow_enums, result) { return result, fmt.Errorf("invalid OAuthFlowType. Expected %+v, got <%s>", oauthFlow_enums, value) } + return result, nil } @@ -381,6 +609,7 @@ func (ss OAuthFlow) Validate(flowType OAuthFlowType) error { return fmt.Errorf("refreshUrl: %w", err) } } + return nil } @@ -388,9 +617,25 @@ func (ss OAuthFlow) Validate(flowType OAuthFlowType) error { // // [OAuth 2.0]: https://swagger.io/docs/specification/authentication/oauth2 type OAuth2Config struct { + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` Flows map[OAuthFlowType]OAuthFlow `json:"flows" mapstructure:"flows" yaml:"flows"` } +var _ SecuritySchemer = &OAuth2Config{} + +// NewOAuth2Config creates a new OAuth2Config instance. +func NewOAuth2Config(flows map[OAuthFlowType]OAuthFlow) *OAuth2Config { + return &OAuth2Config{ + Type: OAuth2Scheme, + Flows: flows, + } +} + +// GetValue get the authentication credential value +func (ss OAuth2Config) GetType() SecuritySchemeType { + return ss.Type +} + // Validate if the current instance is valid func (ss OAuth2Config) Validate() error { if len(ss.Flows) == 0 { @@ -402,18 +647,35 @@ func (ss OAuth2Config) Validate() error { return fmt.Errorf("%s: %w", key, err) } } + return nil } -// OpenIDConfig contains configurations for [OpenID Connect] API specification +// OpenIDConnectConfig contains configurations for [OpenID Connect] API specification // // [OpenID Connect]: https://swagger.io/docs/specification/authentication/openid-connect-discovery -type OpenIDConfig struct { - OpenIDConnectURL string `json:"openIdConnectUrl" mapstructure:"openIdConnectUrl" yaml:"openIdConnectUrl"` +type OpenIDConnectConfig struct { + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` + OpenIDConnectURL string `json:"openIdConnectUrl" mapstructure:"openIdConnectUrl" yaml:"openIdConnectUrl"` +} + +var _ SecuritySchemer = &OpenIDConnectConfig{} + +// NewOpenIDConnectConfig creates a new OpenIDConnectConfig instance. +func NewOpenIDConnectConfig(oidcURL string) *OpenIDConnectConfig { + return &OpenIDConnectConfig{ + Type: OpenIDConnectScheme, + OpenIDConnectURL: oidcURL, + } +} + +// GetValue get the authentication credential value +func (ss OpenIDConnectConfig) GetType() SecuritySchemeType { + return ss.Type } // Validate if the current instance is valid -func (ss OpenIDConfig) Validate() error { +func (ss OpenIDConnectConfig) Validate() error { if ss.OpenIDConnectURL == "" { return errors.New("openIdConnectUrl is required for oidc security") } @@ -421,6 +683,55 @@ func (ss OpenIDConfig) Validate() error { if _, err := parseRelativeOrHttpURL(ss.OpenIDConnectURL); err != nil { return fmt.Errorf("openIdConnectUrl: %w", err) } + + return nil +} + +// CookieAuthConfig represents a cookie authentication configuration. +type CookieAuthConfig struct { + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` +} + +var _ SecuritySchemer = &CookieAuthConfig{} + +// NewCookieAuthConfig creates a new CookieAuthConfig instance. +func NewCookieAuthConfig() *CookieAuthConfig { + return &CookieAuthConfig{ + Type: CookieAuthScheme, + } +} + +// GetValue get the authentication credential value +func (ss CookieAuthConfig) GetType() SecuritySchemeType { + return ss.Type +} + +// Validate if the current instance is valid +func (ss CookieAuthConfig) Validate() error { + return nil +} + +// MutualTLSAuthConfig represents a mutualTLS authentication configuration. +type MutualTLSAuthConfig struct { + Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` +} + +var _ SecuritySchemer = &MutualTLSAuthConfig{} + +// NewMutualTLSAuthConfig creates a new MutualTLSAuthConfig instance. +func NewMutualTLSAuthConfig() *MutualTLSAuthConfig { + return &MutualTLSAuthConfig{ + Type: MutualTLSScheme, + } +} + +// GetValue get the authentication credential value +func (ss MutualTLSAuthConfig) GetType() SecuritySchemeType { + return ss.Type +} + +// Validate if the current instance is valid +func (ss MutualTLSAuthConfig) Validate() error { return nil } @@ -441,6 +752,7 @@ func (as AuthSecurity) Name() string { return k } } + return "" } @@ -451,6 +763,7 @@ func (as AuthSecurity) Scopes() []string { return scopes } } + return []string{} } @@ -472,11 +785,13 @@ func (ass AuthSecurities) IsOptional() bool { if ass.IsEmpty() { return true } + for _, as := range ass { if as.IsOptional() { return true } } + return false } @@ -492,6 +807,7 @@ func (ass AuthSecurities) Get(name string) AuthSecurity { return as } } + return nil } @@ -500,5 +816,6 @@ func (ass AuthSecurities) First() AuthSecurity { for _, as := range ass { return as } + return nil } diff --git a/ndc-http-schema/schema/enum.go b/ndc-http-schema/schema/enum.go index b80e5c7..fbb1ced 100644 --- a/ndc-http-schema/schema/enum.go +++ b/ndc-http-schema/schema/enum.go @@ -44,6 +44,7 @@ func (j *SchemaSpecType) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -53,6 +54,7 @@ func ParseSchemaSpecType(value string) (SchemaSpecType, error) { if !slices.Contains(schemaSpecType_enums, result) { return result, fmt.Errorf("invalid SchemaSpecType. Expected %+v, got <%s>", schemaSpecType_enums, value) } + return result, nil } @@ -87,6 +89,7 @@ func (j *SchemaFileFormat) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -101,6 +104,7 @@ func ParseSchemaFileFormat(extension string) (SchemaFileFormat, error) { if !result.IsValid() { return result, fmt.Errorf("invalid SchemaFileFormat. Expected %+v, got <%s>", schemaFileFormat_enums, extension) } + return result, nil } @@ -142,6 +146,7 @@ func (j *ParameterLocation) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -156,6 +161,7 @@ func ParseParameterLocation(input string) (ParameterLocation, error) { if !result.IsValid() { return result, fmt.Errorf("invalid ParameterLocation. Expected %+v, got <%s>", parameterLocation_enums, input) } + return result, nil } @@ -275,6 +281,7 @@ func (j *ParameterEncodingStyle) UnmarshalJSON(b []byte) error { } *j = result + return nil } @@ -289,5 +296,6 @@ func ParseParameterEncodingStyle(input string) (ParameterEncodingStyle, error) { if !result.IsValid() { return result, fmt.Errorf("invalid ParameterEncodingStyle. Expected %+v, got <%s>", parameterEncodingStyle_enums, input) } + return result, nil } diff --git a/ndc-http-schema/schema/schema.go b/ndc-http-schema/schema/schema.go index e9d688a..ea437be 100644 --- a/ndc-http-schema/schema/schema.go +++ b/ndc-http-schema/schema/schema.go @@ -61,6 +61,7 @@ func (ndc NDCHttpSchema) ToSchemaResponse() *schema.SchemaResponse { for key, object := range ndc.ObjectTypes { objectTypes[key] = object.Schema() } + return &schema.SchemaResponse{ Collections: []schema.CollectionInfo{}, ScalarTypes: ndc.ScalarTypes, @@ -205,6 +206,7 @@ func (eo *EncodingObject) GetHeader(key string) *RequestParameter { if !ok { return nil } + return &result } @@ -277,6 +279,7 @@ func (j OperationInfo) FunctionSchema(name string) schema.FunctionInfo { for key, argument := range j.Arguments { arguments[key] = argument.ArgumentInfo } + return schema.FunctionInfo{ Name: name, Arguments: arguments, @@ -291,6 +294,7 @@ func (j OperationInfo) ProcedureSchema(name string) schema.ProcedureInfo { for key, argument := range j.Arguments { arguments[key] = argument.ArgumentInfo } + return schema.ProcedureInfo{ Name: name, Arguments: arguments, @@ -317,6 +321,7 @@ func (of ObjectType) Schema() schema.ObjectType { for key, field := range of.Fields { result.Fields[key] = field.Schema() } + return result } @@ -421,5 +426,6 @@ func toAnySlice[T any](values []T) []any { for i, v := range values { results[i] = v } + return results } diff --git a/ndc-http-schema/schema/setting.go b/ndc-http-schema/schema/setting.go index b42cac6..561c58c 100644 --- a/ndc-http-schema/schema/setting.go +++ b/ndc-http-schema/schema/setting.go @@ -34,6 +34,7 @@ func (j *NDCHttpSettings) UnmarshalJSON(b []byte) error { _ = result.Validate() *j = result + return nil } @@ -167,6 +168,7 @@ func parseRelativeOrHttpURL(input string) (*url.URL, error) { if strings.HasPrefix(input, "/") { return &url.URL{Path: input}, nil } + return parseHttpURL(input) } diff --git a/ndc-http-schema/schema/setting_test.go b/ndc-http-schema/schema/setting_test.go index aab6e01..985ffe6 100644 --- a/ndc-http-schema/schema/setting_test.go +++ b/ndc-http-schema/schema/setting_test.go @@ -11,6 +11,7 @@ import ( ) func TestNDCHttpSettings(t *testing.T) { + t.Setenv("PET_STORE_API_KEY", "api_key") testCases := []struct { name string input string @@ -41,6 +42,33 @@ func TestNDCHttpSettings(t *testing.T) { "in": "header", "name": "api_key" }, + "http": { + "type": "http", + "value": { + "env": "PET_STORE_API_KEY" + }, + "scheme": "bearer", + "header": "Authorization" + }, + "basic": { + "type": "basic", + "username": { + "value": "user" + }, + "password": { + "value": "password" + } + }, + "cookie": { + "type": "cookie" + }, + "mutualTLS": { + "type": "mutualTLS" + }, + "oidc": { + "type": "openIdConnect", + "openIdConnectUrl": "http://localhost:8080/oauth/token" + }, "petstore_auth": { "type": "oauth2", "flows": { @@ -73,16 +101,44 @@ func TestNDCHttpSettings(t *testing.T) { }, SecuritySchemes: map[string]SecurityScheme{ "api_key": { - Type: APIKeyScheme, - Value: utils.ToPtr(utils.NewEnvStringVariable("PET_STORE_API_KEY")), - APIKeyAuthConfig: &APIKeyAuthConfig{ - In: APIKeyInHeader, - Name: "api_key", + SecuritySchemer: &APIKeyAuthConfig{ + Type: APIKeyScheme, + In: APIKeyInHeader, + Name: "api_key", + Value: utils.NewEnvStringVariable("PET_STORE_API_KEY"), + value: utils.ToPtr("api_key"), + }, + }, + "basic": { + SecuritySchemer: &BasicAuthConfig{ + Type: BasicAuthScheme, + Username: utils.NewEnvStringValue("user"), + Password: utils.NewEnvStringValue("password"), + username: utils.ToPtr("user"), + password: utils.ToPtr("password"), + }, + }, + "http": { + SecuritySchemer: &HTTPAuthConfig{ + Type: HTTPAuthScheme, + Header: "Authorization", + Scheme: "bearer", + Value: utils.NewEnvStringVariable("PET_STORE_API_KEY"), + value: utils.ToPtr("api_key"), }, }, + "cookie": { + SecuritySchemer: NewCookieAuthConfig(), + }, + "mutualTLS": { + SecuritySchemer: NewMutualTLSAuthConfig(), + }, + "oidc": { + SecuritySchemer: NewOpenIDConnectConfig("http://localhost:8080/oauth/token"), + }, "petstore_auth": { - Type: OAuth2Scheme, - OAuth2Config: &OAuth2Config{ + SecuritySchemer: &OAuth2Config{ + Type: OAuth2Scheme, Flows: map[OAuthFlowType]OAuthFlow{ ImplicitFlow: { AuthorizationURL: "https://petstore3.swagger.io/oauth/authorize", @@ -117,7 +173,12 @@ func TestNDCHttpSettings(t *testing.T) { } assert.DeepEqual(t, tc.expected.Headers, result.Headers) assert.DeepEqual(t, tc.expected.Security, result.Security) - assert.DeepEqual(t, tc.expected.SecuritySchemes, result.SecuritySchemes, cmp.Exporter(func(t reflect.Type) bool { return true })) + for key, expectedSS := range tc.expected.SecuritySchemes { + ss := result.SecuritySchemes[key] + ss.JSONSchema() + assert.Equal(t, expectedSS.GetType(), ss.GetType()) + assert.DeepEqual(t, expectedSS.SecuritySchemer, ss.SecuritySchemer, cmp.Exporter(func(t reflect.Type) bool { return true })) + } assert.DeepEqual(t, tc.expected.Version, result.Version) _, err := json.Marshal(tc.expected) diff --git a/ndc-http-schema/utils/file.go b/ndc-http-schema/utils/file.go index 9168f55..aa8db40 100644 --- a/ndc-http-schema/utils/file.go +++ b/ndc-http-schema/utils/file.go @@ -81,6 +81,7 @@ func ReadFileFromPath(filePath string) ([]byte, error) { if errorMsg == "" { errorMsg = resp.Status } + return nil, fmt.Errorf("failed to download file from %s: %s", filePath, errorMsg) } } else { @@ -119,11 +120,13 @@ func WalkFiles(filePath string, callback func(data []byte) error) error { if errorMsg == "" { errorMsg = resp.Status } + return fmt.Errorf("failed to download file from %s: %s", filePath, errorMsg) } if len(result) == 0 { return fmt.Errorf("failed to read file from %s: no content", filePath) } + return callback(result) } @@ -140,6 +143,7 @@ func WalkFiles(filePath string, callback func(data []byte) error) error { if len(result) == 0 { return fmt.Errorf("failed to read file from %s: no content", p) } + return callback(result) } @@ -166,5 +170,6 @@ func ResolveFilePath(dir string, filePath string) string { if !strings.HasPrefix(filePath, "/") && !strings.HasPrefix(filePath, "\\") && !strings.HasPrefix(filePath, "http") { return path.Join(dir, filePath) } + return filePath } diff --git a/ndc-http-schema/utils/patch.go b/ndc-http-schema/utils/patch.go index 638c8fe..d5a9bdb 100644 --- a/ndc-http-schema/utils/patch.go +++ b/ndc-http-schema/utils/patch.go @@ -55,6 +55,7 @@ func ApplyPatchToHTTPSchema(input *schema.NDCHttpSchema, patchFiles []PatchConfi if err := json.Unmarshal(rawResult, &result); err != nil { return nil, err } + return &result, nil } @@ -130,6 +131,7 @@ func convertMaybeYAMLToJSONBytes(input []byte) ([]byte, error) { if err := yaml.Unmarshal(input, &anyOutput); err != nil { return nil, fmt.Errorf("input bytes are not in either yaml or json format: %w", err) } + return json.Marshal(anyOutput) } @@ -144,5 +146,6 @@ func guessPatchStrategy(runes []byte) (PatchStrategy, error) { if runes[0] == '[' && runes[len(runes)-1] == ']' { return PatchStrategyJSON6902, nil } + return "", errUnknownPatchStrategy } diff --git a/ndc-http-schema/utils/string.go b/ndc-http-schema/utils/string.go index 52cf0de..b9ba4a4 100644 --- a/ndc-http-schema/utils/string.go +++ b/ndc-http-schema/utils/string.go @@ -1,9 +1,7 @@ package utils import ( - "fmt" "regexp" - "slices" "strings" "unicode" "unicode/utf8" @@ -24,12 +22,13 @@ func ToCamelCase(input string) string { if pascalCase == "" { return pascalCase } + return strings.ToLower(pascalCase[:1]) + pascalCase[1:] } // StringSliceToCamelCase convert a slice of strings to camelCase func StringSliceToCamelCase(inputs []string) string { - return fmt.Sprintf("%s%s", ToCamelCase(inputs[0]), StringSliceToPascalCase(inputs[1:])) + return ToCamelCase(inputs[0]) + StringSliceToPascalCase(inputs[1:]) } // ToPascalCase convert a string to PascalCase @@ -45,6 +44,7 @@ func ToPascalCase(input string) string { } parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:] } + return strings.Join(parts, "") } @@ -62,6 +62,7 @@ func stringSliceToCase(inputs []string, convert func(string) string, sep string) } results = append(results, convert(trimmed)) } + return strings.Join(results, sep) } @@ -78,22 +79,26 @@ func ToSnakeCase(input string) string { char := rune(input[i]) if char == '_' || char == '-' { sb.WriteRune('_') + continue } if unicode.IsDigit(char) || unicode.IsLower(char) { sb.WriteRune(char) + continue } if unicode.IsUpper(char) { if i == 0 { sb.WriteRune(unicode.ToLower(char)) + continue } prevChar := rune(input[i-1]) if unicode.IsDigit(prevChar) || unicode.IsLower(prevChar) { sb.WriteRune('_') sb.WriteRune(unicode.ToLower(char)) + continue } if i < inputLen-1 { @@ -101,6 +106,7 @@ func ToSnakeCase(input string) string { if unicode.IsUpper(prevChar) && unicode.IsLetter(nextChar) && !unicode.IsUpper(nextChar) { sb.WriteRune('_') sb.WriteRune(unicode.ToLower(char)) + continue } } @@ -108,6 +114,7 @@ func ToSnakeCase(input string) string { sb.WriteRune(unicode.ToLower(char)) } } + return sb.String() } @@ -143,6 +150,10 @@ func SplitStringsAndTrimSpaces(input string, sep string) []string { // StripHTMLTags aggressively strips HTML tags from a string. It will only keep anything between `>` and `<`. func StripHTMLTags(str string) string { + if str == "" { + return "" + } + // Setup a string builder and allocate enough memory for the new string. var builder strings.Builder builder.Grow(len(str) + utf8.UTFMax) @@ -172,13 +183,15 @@ func StripHTMLTags(str string) string { builder.WriteString(str[end:start]) } in = true + continue } // else c == htmlTagEnd in = false end = i + 1 } - return builder.String() + + return strings.TrimSpace(builder.String()) } // RemoveYAMLSpecialCharacters remote special characters to avoid YAML unmarshaling error @@ -187,26 +200,54 @@ func RemoveYAMLSpecialCharacters(input []byte) []byte { inputLength := len(input) for i := 0; i < inputLength; i++ { c := input[i] - switch c { - case '\n', '\t': + r := rune(c) + switch { + case unicode.IsSpace(r): sb.WriteRune(' ') - case '\\': - if i < inputLength-1 { - if slices.Contains([]byte{'n', 's', 't'}, input[i+1]) { - sb.WriteRune(' ') - } else { - sb.WriteByte(c) - sb.WriteByte(input[i+1]) - } + case c == '\\' && i < inputLength-1: + switch input[i+1] { + case 'b', 'n', 'r', 't', 'f': + sb.WriteRune(' ') i++ - } - default: - r := rune(c) - if !unicode.IsControl(r) && utf8.ValidRune(r) && r != utf8.RuneError { + case 'u': + u := getu4(input[i:]) + if u > -1 && utf8.ValidRune(u) { + sb.WriteRune(u) + } + i += 5 + default: sb.WriteByte(c) } + case !unicode.IsControl(r) && unicode.IsPrint(r) && utf8.ValidRune(r) && r != utf8.RuneError: + sb.WriteByte(c) } } return []byte(strings.ToValidUTF8(sb.String(), "")) } + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. Borrow from the standard [JSON decoder] library +// +// [JSON decoder](https://github.com/golang/go/blob/0a6f05e30f58023bf45f747a79c20751db2bcfe7/src/encoding/json/decode.go#L1151) +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + var r rune + for _, c := range s[2:6] { + switch { + case '0' <= c && c <= '9': + c -= '0' + case 'a' <= c && c <= 'f': + c = c - 'a' + 10 + case 'A' <= c && c <= 'F': + c = c - 'A' + 10 + default: + return -1 + } + r = r*16 + rune(c) + } + + return r +} diff --git a/ndc-http-schema/utils/string_test.go b/ndc-http-schema/utils/string_test.go new file mode 100644 index 0000000..5d68c9b --- /dev/null +++ b/ndc-http-schema/utils/string_test.go @@ -0,0 +1,25 @@ +package utils + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestRemoveYAMLSpecialCharacters(t *testing.T) { + testCases := []struct { + Input string + Expected string + }{ + { + Input: "\b\t\u0009Some\u0000thing\\u0002", + Expected: " Something", + }, + } + + for _, tc := range testCases { + t.Run(tc.Expected, func(t *testing.T) { + assert.Equal(t, tc.Expected, string(RemoveYAMLSpecialCharacters([]byte(tc.Input)))) + }) + } +}