From fca163cf1c4173be62ad8fc3dbc08343a9c7958e Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Tue, 3 Sep 2024 17:15:01 +0700 Subject: [PATCH] improve jsonschema generation --- command/convert.go | 41 +- config.example.yaml | 3 + jsonschema/convert-config.jsonschema | 40 +- jsonschema/generator.go | 14 +- jsonschema/ndc-rest-schema.jsonschema | 117 ++- openapi/internal/oas2_operation.go | 6 + openapi/internal/oas3_operation.go | 6 + openapi/internal/types.go | 1 + openapi/oas2_test.go | 18 + openapi/oas3_test.go | 44 +- .../prefix2/expected_multi_words.json | 175 ++++ .../prefix2/expected_single_word.json | 175 ++++ openapi/testdata/prefix2/source.json | 90 ++ .../prefix3/expected_multi_words.json | 787 ++++++++++++++++++ .../prefix3/expected_single_word.json | 787 ++++++++++++++++++ openapi/testdata/prefix3/source.json | 746 +++++++++++++++++ utils/string.go | 8 +- 17 files changed, 2982 insertions(+), 76 deletions(-) create mode 100644 openapi/testdata/prefix2/expected_multi_words.json create mode 100644 openapi/testdata/prefix2/expected_single_word.json create mode 100644 openapi/testdata/prefix2/source.json create mode 100644 openapi/testdata/prefix3/expected_multi_words.json create mode 100644 openapi/testdata/prefix3/expected_single_word.json create mode 100644 openapi/testdata/prefix3/source.json diff --git a/command/convert.go b/command/convert.go index 7b63ec1..a3c720b 100644 --- a/command/convert.go +++ b/command/convert.go @@ -23,6 +23,7 @@ type ConvertCommandArguments struct { Format string `help:"The output format, is one of json, yaml. If the output is set, automatically detect the format in the output file extension" default:"json"` Strict bool `help:"Require strict validation" default:"false"` Pure bool `help:"Return the pure NDC schema only" default:"false"` + Prefix string `help:"Add a prefix to the function and procedure names"` TrimPrefix string `help:"Trim the prefix in URL, e.g. /v1"` EnvPrefix string `help:"The environment variable prefix for security values, e.g. PET_STORE"` MethodAlias map[string]string `help:"Alias names for HTTP method. Used for prefix renaming, e.g. getUsers, postUser"` @@ -40,6 +41,7 @@ func CommandConvertToNDCSchema(args *ConvertCommandArguments, logger *slog.Logge slog.String("output", args.Output), slog.String("spec", args.Spec), slog.String("format", args.Format), + slog.String("prefix", args.Prefix), slog.String("trim_prefix", args.TrimPrefix), slog.String("env_prefix", args.EnvPrefix), slog.Any("patch_before", args.PatchBefore), @@ -125,17 +127,30 @@ func CommandConvertToNDCSchema(args *ConvertCommandArguments, logger *slog.Logge // ConvertConfig represents the content of convert config file type ConvertConfig struct { - File string `json:"file" yaml:"file"` - Spec schema.SchemaSpecType `json:"spec,omitempty" yaml:"spec"` - MethodAlias map[string]string `json:"methodAlias,omitempty" yaml:"methodAlias"` - TrimPrefix string `json:"trimPrefix,omitempty" yaml:"trimPrefix"` - EnvPrefix string `json:"envPrefix,omitempty" yaml:"envPrefix"` - Pure bool `json:"pure,omitempty" yaml:"pure"` - Strict bool `json:"strict,omitempty" yaml:"strict"` - PatchBefore []utils.PatchConfig `json:"patchBefore,omitempty" yaml:"patchBefore"` - PatchAfter []utils.PatchConfig `json:"patchAfter,omitempty" yaml:"patchAfter"` - AllowedContentTypes []string `json:"allowedContentTypes,omitempty" yaml:"allowedContentTypes"` - Output string `json:"output,omitempty" yaml:"output"` + // File path needs to be converted + File string `json:"file" yaml:"file" jsonschema:"required"` + // The API specification of the file, is one of oas3 (openapi3), oas2 (openapi2) + Spec schema.SchemaSpecType `json:"spec,omitempty" yaml:"spec" jsonschema:"default=oas3"` + // Alias names for HTTP method. Used for prefix renaming, e.g. getUsers, postUser + MethodAlias map[string]string `json:"methodAlias,omitempty" yaml:"methodAlias"` + // Add a prefix to the function and procedure names + Prefix string `json:"prefix,omitempty" yaml:"prefix"` + // Trim the prefix in URL, e.g. /v1 + TrimPrefix string `json:"trimPrefix,omitempty" yaml:"trimPrefix"` + // The environment variable prefix for security values, e.g. PET_STORE + EnvPrefix string `json:"envPrefix,omitempty" yaml:"envPrefix"` + // Return the pure NDC schema only + Pure bool `json:"pure,omitempty" yaml:"pure"` + // Require strict validation + Strict bool `json:"strict,omitempty" yaml:"strict"` + // Patch files to be applied into the input file before converting + PatchBefore []utils.PatchConfig `json:"patchBefore,omitempty" yaml:"patchBefore"` + // Patch files to be applied into the input file after converting + PatchAfter []utils.PatchConfig `json:"patchAfter,omitempty" yaml:"patchAfter"` + // Allowed content types. All content types are allowed by default + AllowedContentTypes []string `json:"allowedContentTypes,omitempty" yaml:"allowedContentTypes"` + // The location where the ndc schema file will be generated. Print to stdout if not set + Output string `json:"output,omitempty" yaml:"output"` } // ConvertToNDCSchema converts to NDC REST schema from config @@ -155,6 +170,7 @@ func ConvertToNDCSchema(config *ConvertConfig, logger *slog.Logger) (*schema.NDC var errs []error options := openapi.ConvertOptions{ MethodAlias: config.MethodAlias, + Prefix: config.Prefix, TrimPrefix: config.TrimPrefix, EnvPrefix: config.EnvPrefix, AllowedContentTypes: config.AllowedContentTypes, @@ -192,6 +208,9 @@ func ResolveConvertConfigArguments(config *ConvertConfig, configDir string, args if len(args.MethodAlias) > 0 { config.MethodAlias = args.MethodAlias } + if args.Prefix != "" { + config.Prefix = args.Prefix + } if args.TrimPrefix != "" { config.TrimPrefix = args.TrimPrefix } diff --git a/config.example.yaml b/config.example.yaml index 05f65aa..e2955fc 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -10,6 +10,9 @@ output: "" # @enum: oas2, oas3 spec: oas3 +# -- Add a prefix to the function and procedure names +# prefix: "" + # -- Trim the prefix in URL, e.g. /v1 # trimPrefix: "" diff --git a/jsonschema/convert-config.jsonschema b/jsonschema/convert-config.jsonschema index 586e911..cdea656 100644 --- a/jsonschema/convert-config.jsonschema +++ b/jsonschema/convert-config.jsonschema @@ -6,56 +6,72 @@ "ConvertConfig": { "properties": { "file": { - "type": "string" + "type": "string", + "description": "File path needs to be converted" }, "spec": { - "$ref": "#/$defs/SchemaSpecType" + "$ref": "#/$defs/SchemaSpecType", + "description": "The API specification of the file, is one of oas3 (openapi3), oas2 (openapi2)" }, "methodAlias": { "additionalProperties": { "type": "string" }, - "type": "object" + "type": "object", + "description": "Alias names for HTTP method. Used for prefix renaming, e.g. getUsers, postUser" + }, + "prefix": { + "type": "string", + "description": "Add a prefix to the function and procedure names" }, "trimPrefix": { - "type": "string" + "type": "string", + "description": "Trim the prefix in URL, e.g. /v1" }, "envPrefix": { - "type": "string" + "type": "string", + "description": "The environment variable prefix for security values, e.g. PET_STORE" }, "pure": { - "type": "boolean" + "type": "boolean", + "description": "Return the pure NDC schema only" }, "strict": { - "type": "boolean" + "type": "boolean", + "description": "Require strict validation" }, "patchBefore": { "items": { "$ref": "#/$defs/PatchConfig" }, - "type": "array" + "type": "array", + "description": "Patch files to be applied into the input file before converting" }, "patchAfter": { "items": { "$ref": "#/$defs/PatchConfig" }, - "type": "array" + "type": "array", + "description": "Patch files to be applied into the input file after converting" }, "allowedContentTypes": { "items": { "type": "string" }, - "type": "array" + "type": "array", + "description": "Allowed content types. All content types are allowed by default" }, "output": { - "type": "string" + "type": "string", + "description": "The location where the ndc schema file will be generated. Print to stdout if not set" } }, "additionalProperties": false, "type": "object", "required": [ "file" - ] + ], + "description": "ConvertConfig represents the content of convert config file" }, "PatchConfig": { "properties": { diff --git a/jsonschema/generator.go b/jsonschema/generator.go index 0386772..a64eeb8 100644 --- a/jsonschema/generator.go +++ b/jsonschema/generator.go @@ -20,7 +20,12 @@ func main() { } func jsonSchemaConvertConfig() error { - reflectSchema := jsonschema.Reflect(&command.ConvertConfig{}) + r := new(jsonschema.Reflector) + if err := r.AddGoComments("github.com/hasura/ndc-rest-schema/command", "../command"); err != nil { + return err + } + reflectSchema := r.Reflect(&command.ConvertConfig{}) + schemaBytes, err := json.MarshalIndent(reflectSchema, "", " ") if err != nil { return err @@ -30,7 +35,12 @@ func jsonSchemaConvertConfig() error { } func jsonSchemaNdcRESTSchema() error { - reflectSchema := jsonschema.Reflect(&schema.NDCRestSchema{}) + r := new(jsonschema.Reflector) + if err := r.AddGoComments("github.com/hasura/ndc-rest-schema/schema", "../schema"); err != nil { + return err + } + + reflectSchema := r.Reflect(&schema.NDCRestSchema{}) schemaBytes, err := json.MarshalIndent(reflectSchema, "", " ") if err != nil { return err diff --git a/jsonschema/ndc-rest-schema.jsonschema b/jsonschema/ndc-rest-schema.jsonschema index 0d4658b..1887e85 100644 --- a/jsonschema/ndc-rest-schema.jsonschema +++ b/jsonschema/ndc-rest-schema.jsonschema @@ -34,7 +34,8 @@ "items": { "$ref": "#/$defs/AuthSecurity" }, - "type": "array" + "type": "array", + "description": "AuthSecurities wraps list of security requirements with helpers" }, "AuthSecurity": { "additionalProperties": { @@ -43,7 +44,8 @@ }, "type": "array" }, - "type": "object" + "type": "object", + "description": "AuthSecurity wraps the raw security requirement with helpers" }, "CollectionInfo": { "properties": { @@ -100,29 +102,35 @@ "EncodingObject": { "properties": { "style": { - "$ref": "#/$defs/ParameterEncodingStyle" + "$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" + "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" + "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" + "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" + "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" + "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" }, "EnvInt": { "oneOf": [ @@ -180,6 +188,9 @@ }, "NDCRestSchema": { "properties": { + "$schema": { + "type": "string" + }, "settings": { "$ref": "#/$defs/NDCRestSettings" }, @@ -187,25 +198,30 @@ "items": { "$ref": "#/$defs/CollectionInfo" }, - "type": "array" + "type": "array", + "description": "Collections which are available for queries" }, "functions": { "items": { "$ref": "#/$defs/RESTFunctionInfo" }, - "type": "array" + "type": "array", + "description": "Functions (i.e. collections which return a single column and row)" }, "object_types": { - "$ref": "#/$defs/SchemaResponseObjectTypes" + "$ref": "#/$defs/SchemaResponseObjectTypes", + "description": "A list of object types which can be used as the types of arguments, or return\ntypes of procedures. Names should not overlap with scalar type names." }, "procedures": { "items": { "$ref": "#/$defs/RESTProcedureInfo" }, - "type": "array" + "type": "array", + "description": "Procedures which are available for execution as part of mutations" }, "scalar_types": { - "$ref": "#/$defs/SchemaResponseScalarTypes" + "$ref": "#/$defs/SchemaResponseScalarTypes", + "description": "A list of scalar types which will be used as the types of collection columns" } }, "additionalProperties": false, @@ -216,7 +232,8 @@ "object_types", "procedures", "scalar_types" - ] + ], + "description": "NDCRestSchema extends the [NDC SchemaResponse] with OpenAPI REST information" }, "NDCRestSettings": { "properties": { @@ -233,7 +250,8 @@ "type": "object" }, "timeout": { - "$ref": "#/$defs/EnvInt" + "$ref": "#/$defs/EnvInt", + "description": "configure the request timeout in seconds, default 30s" }, "retry": { "$ref": "#/$defs/RetryPolicySetting" @@ -255,7 +273,8 @@ "type": "object", "required": [ "servers" - ] + ], + "description": "NDCRestSettings represent global settings of the REST API, including base URL, headers, etc..." }, "ObjectField": { "properties": { @@ -356,7 +375,8 @@ "arguments", "name", "result_type" - ] + ], + "description": "RESTFunctionInfo extends NDC query function with OpenAPI REST information" }, "RESTProcedureInfo": { "properties": { @@ -383,7 +403,8 @@ "arguments", "name", "result_type" - ] + ], + "description": "RESTProcedureInfo extends NDC mutation procedure with OpenAPI REST information" }, "Request": { "properties": { @@ -419,7 +440,8 @@ "$ref": "#/$defs/AuthSecurities" }, "timeout": { - "type": "integer" + "type": "integer", + "description": "configure the request timeout in seconds, default 30s" }, "servers": { "items": { @@ -441,7 +463,8 @@ "type": "object", "required": [ "response" - ] + ], + "description": "Request represents the HTTP request information of the webhook" }, "RequestBody": { "properties": { @@ -459,30 +482,36 @@ } }, "additionalProperties": false, - "type": "object" + "type": "object", + "description": "RequestBody defines flexible request body with content types" }, "RequestParameter": { "properties": { "style": { - "$ref": "#/$defs/ParameterEncodingStyle" + "$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" + "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" + "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" + "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" + "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" @@ -498,7 +527,8 @@ } }, "additionalProperties": false, - "type": "object" + "type": "object", + "description": "RequestParameter represents an HTTP request parameter" }, "Response": { "properties": { @@ -515,35 +545,43 @@ "RetryPolicy": { "properties": { "times": { - "type": "integer" + "type": "integer", + "description": "Number of retry times" }, "delay": { - "type": "integer" + "type": "integer", + "description": "Delay retry delay in milliseconds" }, "httpStatus": { "items": { "type": "integer" }, - "type": "array" + "type": "array", + "description": "HTTPStatus retries if the remote service returns one of these http status" } }, "additionalProperties": false, - "type": "object" + "type": "object", + "description": "RetryPolicy represents the retry policy of request" }, "RetryPolicySetting": { "properties": { "times": { - "$ref": "#/$defs/EnvInt" + "$ref": "#/$defs/EnvInt", + "description": "Number of retry times" }, "delay": { - "$ref": "#/$defs/EnvInt" + "$ref": "#/$defs/EnvInt", + "description": "Delay retry delay in milliseconds" }, "httpStatus": { - "$ref": "#/$defs/EnvInts" + "$ref": "#/$defs/EnvInts", + "description": "HTTPStatus retries if the remote service returns one of these http status" } }, "additionalProperties": false, - "type": "object" + "type": "object", + "description": "RetryPolicySetting represents retry policy settings" }, "ScalarType": { "properties": { @@ -698,7 +736,8 @@ "type": "object" }, "timeout": { - "$ref": "#/$defs/EnvInt" + "$ref": "#/$defs/EnvInt", + "description": "configure the request timeout in seconds, default 30s" }, "retry": { "$ref": "#/$defs/RetryPolicySetting" @@ -717,7 +756,8 @@ "type": "object", "required": [ "url" - ] + ], + "description": "ServerConfig contains server configurations" }, "Type": { "type": "object" @@ -771,7 +811,8 @@ "type": "object", "required": [ "type" - ] + ], + "description": "TypeSchema represents a serializable object of OpenAPI schema that is used for validation" }, "UniquenessConstraint": { "properties": { diff --git a/openapi/internal/oas2_operation.go b/openapi/internal/oas2_operation.go index 2d812cd..8788f93 100644 --- a/openapi/internal/oas2_operation.go +++ b/openapi/internal/oas2_operation.go @@ -36,6 +36,9 @@ 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), @@ -102,6 +105,9 @@ 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), diff --git a/openapi/internal/oas3_operation.go b/openapi/internal/oas3_operation.go index 48090e0..e20df08 100644 --- a/openapi/internal/oas3_operation.go +++ b/openapi/internal/oas3_operation.go @@ -36,6 +36,9 @@ func (oc *oas3OperationBuilder) BuildFunction(itemGet *v3.Operation) (*rest.REST if funcName == "" { funcName = buildPathMethodName(oc.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), @@ -88,6 +91,9 @@ func (oc *oas3OperationBuilder) BuildProcedure(operation *v3.Operation) (*rest.R procName = buildPathMethodName(oc.pathKey, oc.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", oc.pathKey), diff --git a/openapi/internal/types.go b/openapi/internal/types.go index 50d095b..33726e8 100644 --- a/openapi/internal/types.go +++ b/openapi/internal/types.go @@ -112,6 +112,7 @@ var defaultScalarTypes = map[rest.ScalarName]*schema.ScalarType{ type ConvertOptions struct { MethodAlias map[string]string AllowedContentTypes []string + Prefix string TrimPrefix string EnvPrefix string Strict bool diff --git a/openapi/oas2_test.go b/openapi/oas2_test.go index b785dd2..83aa40f 100644 --- a/openapi/oas2_test.go +++ b/openapi/oas2_test.go @@ -32,6 +32,24 @@ func TestOpenAPIv2ToRESTSchema(t *testing.T) { Source: "testdata/petstore2/swagger.json", Expected: "testdata/petstore2/expected.json", }, + // go run . convert -f ./openapi/testdata/prefix2/source.json -o ./openapi/testdata/prefix2/expected_single_word.json --spec oas2 --prefix hasura + { + Name: "prefix2_single_word", + Source: "testdata/prefix2/source.json", + Expected: "testdata/prefix2/expected_single_word.json", + Options: ConvertOptions{ + Prefix: "hasura", + }, + }, + // go run . convert -f ./openapi/testdata/prefix2/source.json -o ./openapi/testdata/prefix2/expected_multi_words.json --spec oas2 --prefix hasura_mock_json + { + Name: "prefix2_single_word", + Source: "testdata/prefix2/source.json", + Expected: "testdata/prefix2/expected_multi_words.json", + Options: ConvertOptions{ + Prefix: "hasura_mock_json", + }, + }, } for _, tc := range testCases { diff --git a/openapi/oas3_test.go b/openapi/oas3_test.go index 057dc54..e439961 100644 --- a/openapi/oas3_test.go +++ b/openapi/oas3_test.go @@ -15,29 +15,52 @@ import ( func TestOpenAPIv3ToRESTSchema(t *testing.T) { testCases := []struct { - Name string - Source string - EnvPrefix string - Expected string + Name string + Source string + Expected string + Options ConvertOptions }{ // go run . convert -f ./openapi/testdata/petstore3/source.json -o ./openapi/testdata/petstore3/expected.json --trim-prefix /v1 --spec openapi3 --env-prefix PET_STORE { - Name: "petstore3", - Source: "testdata/petstore3/source.json", - Expected: "testdata/petstore3/expected.json", - EnvPrefix: "PET_STORE", + Name: "petstore3", + Source: "testdata/petstore3/source.json", + Expected: "testdata/petstore3/expected.json", + Options: ConvertOptions{ + TrimPrefix: "/v1", + EnvPrefix: "PET_STORE", + }, }, // go run . convert -f ./openapi/testdata/onesignal/source.json -o ./openapi/testdata/onesignal/expected.json --spec openapi3 { Name: "onesignal", Source: "testdata/onesignal/source.json", Expected: "testdata/onesignal/expected.json", + Options: ConvertOptions{}, }, // go run . convert -f ./openapi/testdata/openai/source.json -o ./openapi/testdata/openai/expected.json --spec openapi3 { Name: "openai", Source: "testdata/openai/source.json", Expected: "testdata/openai/expected.json", + Options: ConvertOptions{}, + }, + // go run . convert -f ./openapi/testdata/prefix3/source.json -o ./openapi/testdata/prefix3/expected_single_word.json --spec openapi3 --prefix hasura + { + Name: "prefix3_single_word", + Source: "testdata/prefix3/source.json", + Expected: "testdata/prefix3/expected_single_word.json", + Options: ConvertOptions{ + Prefix: "hasura", + }, + }, + // go run . convert -f ./openapi/testdata/prefix3/source.json -o ./openapi/testdata/prefix3/expected_multi_words.json --spec openapi3 --prefix hasura_one_signal + { + Name: "prefix3_multi_words", + Source: "testdata/prefix3/source.json", + Expected: "testdata/prefix3/expected_multi_words.json", + Options: ConvertOptions{ + Prefix: "hasura_one_signal", + }, }, } @@ -51,10 +74,7 @@ func TestOpenAPIv3ToRESTSchema(t *testing.T) { var expected schema.NDCRestSchema assertNoError(t, json.Unmarshal(expectedBytes, &expected)) - output, errs := OpenAPIv3ToNDCSchema(sourceBytes, ConvertOptions{ - EnvPrefix: tc.EnvPrefix, - TrimPrefix: "/v1", - }) + output, errs := OpenAPIv3ToNDCSchema(sourceBytes, tc.Options) if output == nil { t.Fatal(errors.Join(errs...)) } diff --git a/openapi/testdata/prefix2/expected_multi_words.json b/openapi/testdata/prefix2/expected_multi_words.json new file mode 100644 index 0000000..19d8168 --- /dev/null +++ b/openapi/testdata/prefix2/expected_multi_words.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://raw.githubusercontent.com/hasura/ndc-rest-schema/main/jsonschema/ndc-rest-schema.jsonschema", + "settings": { + "servers": [ + { + "url": "{{SERVER_URL:-https://jsonplaceholder.typicode.com}}" + } + ], + "timeout": "{{TIMEOUT}}", + "retry": { + "times": "{{RETRY_TIMES}}", + "delay": "{{RETRY_DELAY}}", + "httpStatus": "{{RETRY_HTTP_STATUS}}" + }, + "version": "1.0.0" + }, + "collections": [], + "functions": [ + { + "request": { + "url": "/posts", + "method": "get", + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + }, + { + "name": "userId", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + } + ], + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "id": { + "description": "Filter by post ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available posts", + "name": "hasuraMockJsonGetPosts", + "result_type": { + "element_type": { + "name": "Post", + "type": "named" + }, + "type": "array" + } + } + ], + "object_types": { + "Post": { + "fields": { + "body": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "request": { + "url": "/posts", + "method": "post", + "requestBody": { + "contentType": "application/json", + "schema": { + "type": "Post" + } + }, + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "body": { + "description": "Post object that needs to be added", + "type": { + "name": "Post", + "type": "named" + } + } + }, + "description": "Create a post", + "name": "hasuraMockJsonCreatePost", + "result_type": { + "name": "Post", + "type": "named" + } + } + ], + "scalar_types": { + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + } + } +} diff --git a/openapi/testdata/prefix2/expected_single_word.json b/openapi/testdata/prefix2/expected_single_word.json new file mode 100644 index 0000000..a3ef003 --- /dev/null +++ b/openapi/testdata/prefix2/expected_single_word.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://raw.githubusercontent.com/hasura/ndc-rest-schema/main/jsonschema/ndc-rest-schema.jsonschema", + "settings": { + "servers": [ + { + "url": "{{SERVER_URL:-https://jsonplaceholder.typicode.com}}" + } + ], + "timeout": "{{TIMEOUT}}", + "retry": { + "times": "{{RETRY_TIMES}}", + "delay": "{{RETRY_DELAY}}", + "httpStatus": "{{RETRY_HTTP_STATUS}}" + }, + "version": "1.0.0" + }, + "collections": [], + "functions": [ + { + "request": { + "url": "/posts", + "method": "get", + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + }, + { + "name": "userId", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + } + ], + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "id": { + "description": "Filter by post ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "userId": { + "description": "Filter by user ID", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "Get all available posts", + "name": "hasuraGetPosts", + "result_type": { + "element_type": { + "name": "Post", + "type": "named" + }, + "type": "array" + } + } + ], + "object_types": { + "Post": { + "fields": { + "body": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "title": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "userId": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "request": { + "url": "/posts", + "method": "post", + "requestBody": { + "contentType": "application/json", + "schema": { + "type": "Post" + } + }, + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "body": { + "description": "Post object that needs to be added", + "type": { + "name": "Post", + "type": "named" + } + } + }, + "description": "Create a post", + "name": "hasuraCreatePost", + "result_type": { + "name": "Post", + "type": "named" + } + } + ], + "scalar_types": { + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + } + } +} diff --git a/openapi/testdata/prefix2/source.json b/openapi/testdata/prefix2/source.json new file mode 100644 index 0000000..54e2ccc --- /dev/null +++ b/openapi/testdata/prefix2/source.json @@ -0,0 +1,90 @@ +{ + "swagger": "2.0", + "info": { + "description": "Fake Online REST API for Testing and Prototyping", + "version": "1.0.0", + "title": "JSON Placeholder" + }, + "host": "jsonplaceholder.typicode.com", + "schemes": ["https"], + "paths": { + "/posts": { + "get": { + "tags": ["posts"], + "operationId": "getPosts", + "summary": "Get all available posts", + "parameters": [ + { + "name": "id", + "in": "query", + "type": "integer", + "description": "Filter by post ID", + "required": false + }, + { + "name": "userId", + "in": "query", + "type": "integer", + "description": "Filter by user ID", + "required": false + } + ], + "produces": ["application/json"], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Post" + } + } + } + } + }, + "post": { + "tags": ["posts"], + "operationId": "createPost", + "summary": "Create a post", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Post object that needs to be added", + "required": true, + "schema": { "$ref": "#/definitions/Post" } + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "200": { + "description": "successful operation", + "schema": { "$ref": "#/definitions/Post" } + } + } + } + } + }, + "definitions": { + "Post": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userId": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "body": { + "type": "string" + } + } + } + } +} diff --git a/openapi/testdata/prefix3/expected_multi_words.json b/openapi/testdata/prefix3/expected_multi_words.json new file mode 100644 index 0000000..434cc73 --- /dev/null +++ b/openapi/testdata/prefix3/expected_multi_words.json @@ -0,0 +1,787 @@ +{ + "$schema": "https://raw.githubusercontent.com/hasura/ndc-rest-schema/main/jsonschema/ndc-rest-schema.jsonschema", + "settings": { + "servers": [ + { + "url": "{{SERVER_URL:-https://onesignal.com/api/v1}}" + } + ], + "timeout": "{{TIMEOUT}}", + "retry": { + "times": "{{RETRY_TIMES}}", + "delay": "{{RETRY_DELAY}}", + "httpStatus": "{{RETRY_HTTP_STATUS}}" + }, + "securitySchemes": { + "app_key": { + "type": "http", + "value": "{{APP_KEY_TOKEN}}", + "header": "Authorization", + "scheme": "bearer" + }, + "user_key": { + "type": "http", + "value": "{{USER_KEY_TOKEN}}", + "header": "Authorization", + "scheme": "bearer" + } + }, + "version": "1.2.2" + }, + "collections": [], + "functions": [ + { + "request": { + "url": "/notifications", + "method": "get", + "parameters": [ + { + "name": "app_id", + "in": "query", + "schema": { + "type": "String" + } + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + } + ], + "security": [ + { + "app_key": [] + } + ], + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "app_id": { + "description": "The app ID that you want to view notifications from", + "type": { + "name": "String", + "type": "named" + } + }, + "kind": { + "description": "Kind of notifications returned:\n * unset - All notification types (default)\n * `0` - Dashboard only\n * `1` - API only\n * `3` - Automated only\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "limit": { + "description": "How many notifications to return. Max is 50. Default is 50.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "offset": { + "description": "Page offset. Default is 0. Results are sorted by queued_at in descending order. queued_at is a representation of the time that the notification was queued at.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "View notifications", + "name": "hasuraOneSignalGetNotifications", + "result_type": { + "name": "NotificationSlice", + "type": "named" + } + } + ], + "object_types": { + "CreateNotificationSuccessResponse": { + "fields": { + "errors": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Notification200Errors", + "type": "named" + } + } + }, + "external_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "recipients": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "DeliveryData": { + "fields": { + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "Filter": { + "fields": { + "field": { + "description": "Name of the field to use as the first operand in the filter expression.", + "type": { + "name": "String", + "type": "named" + } + }, + "key": { + "description": "If `field` is `tag`, this field is *required* to specify `key` inside the tags.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "relation": { + "description": "Operator of a filter expression.", + "type": { + "name": "FilterRelation", + "type": "named" + } + }, + "value": { + "description": "Constant value to use as the second operand in the filter expression. This value is *required* when the relation operator is a binary operator.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "NotificationInput": { + "fields": { + "send_after": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + } + } + }, + "NotificationSlice": { + "fields": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "notifications": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "NotificationWithMeta", + "type": "named" + }, + "type": "array" + } + } + }, + "offset": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "total_count": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "NotificationWithMeta": { + "fields": { + "completed_at": { + "description": "Unix timestamp indicating when notification delivery completed. The delivery duration from start to finish can be calculated with completed_at - send_after.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "excluded_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "include_player_ids": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "included_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "outcomes": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "OutcomeData", + "type": "named" + }, + "type": "array" + } + } + }, + "platform_delivery_stats": { + "description": "Hash of delivery statistics broken out by target device platform.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlatformDeliveryData", + "type": "named" + } + } + }, + "queued_at": { + "description": "Unix timestamp indicating when the notification was created.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "remaining": { + "description": "Number of notifications that have not been sent out yet. This can mean either our system is still processing the notification or you have delayed options set.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "send_after": { + "description": "Unix timestamp indicating when notification delivery should begin.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "target_channel": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlayerNotificationTargetTargetChannel", + "type": "named" + } + } + }, + "throttle_rate_per_minute": { + "description": "number of push notifications sent per minute. Paid Feature Only. If throttling is not enabled for the app or the notification, and for free accounts, null is returned. Refer to Throttling for more details.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "OutcomeData": { + "fields": { + "aggregation": { + "type": { + "name": "OutcomeDataAggregation", + "type": "named" + } + }, + "id": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PlatformDeliveryData": { + "description": "Hash of delivery statistics broken out by target device platform.", + "fields": { + "android": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "chrome_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "edge_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "firefox_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "ios": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "safari_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "sms": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + } + } + }, + "StringMap": { + "fields": { + "en": { + "description": "Text in English. Will be used as a fallback", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "request": { + "url": "/notifications", + "method": "post", + "security": [ + { + "app_key": [] + } + ], + "requestBody": { + "contentType": "application/json", + "schema": { + "type": "NotificationInput" + } + }, + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "body": { + "description": "Request body of POST /notifications", + "type": { + "name": "NotificationInput", + "type": "named" + } + } + }, + "description": "Create notification", + "name": "hasuraOneSignalCreateNotification", + "result_type": { + "name": "CreateNotificationSuccessResponse", + "type": "named" + } + } + ], + "scalar_types": { + "FilterRelation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "\u003e", + "\u003c", + "=", + "!=", + "exists", + "not_exists", + "time_elapsed_gt", + "time_elapsed_lt" + ], + "type": "enum" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "Notification200Errors": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "OutcomeDataAggregation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "sum", + "count" + ], + "type": "enum" + } + }, + "PlayerNotificationTargetTargetChannel": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + } + } +} diff --git a/openapi/testdata/prefix3/expected_single_word.json b/openapi/testdata/prefix3/expected_single_word.json new file mode 100644 index 0000000..c3be9d5 --- /dev/null +++ b/openapi/testdata/prefix3/expected_single_word.json @@ -0,0 +1,787 @@ +{ + "$schema": "https://raw.githubusercontent.com/hasura/ndc-rest-schema/main/jsonschema/ndc-rest-schema.jsonschema", + "settings": { + "servers": [ + { + "url": "{{SERVER_URL:-https://onesignal.com/api/v1}}" + } + ], + "timeout": "{{TIMEOUT}}", + "retry": { + "times": "{{RETRY_TIMES}}", + "delay": "{{RETRY_DELAY}}", + "httpStatus": "{{RETRY_HTTP_STATUS}}" + }, + "securitySchemes": { + "app_key": { + "type": "http", + "value": "{{APP_KEY_TOKEN}}", + "header": "Authorization", + "scheme": "bearer" + }, + "user_key": { + "type": "http", + "value": "{{USER_KEY_TOKEN}}", + "header": "Authorization", + "scheme": "bearer" + } + }, + "version": "1.2.2" + }, + "collections": [], + "functions": [ + { + "request": { + "url": "/notifications", + "method": "get", + "parameters": [ + { + "name": "app_id", + "in": "query", + "schema": { + "type": "String" + } + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "Int32", + "nullable": true + } + } + ], + "security": [ + { + "app_key": [] + } + ], + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "app_id": { + "description": "The app ID that you want to view notifications from", + "type": { + "name": "String", + "type": "named" + } + }, + "kind": { + "description": "Kind of notifications returned:\n * unset - All notification types (default)\n * `0` - Dashboard only\n * `1` - API only\n * `3` - Automated only\n", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "limit": { + "description": "How many notifications to return. Max is 50. Default is 50.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "offset": { + "description": "Page offset. Default is 0. Results are sorted by queued_at in descending order. queued_at is a representation of the time that the notification was queued at.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "description": "View notifications", + "name": "hasuraGetNotifications", + "result_type": { + "name": "NotificationSlice", + "type": "named" + } + } + ], + "object_types": { + "CreateNotificationSuccessResponse": { + "fields": { + "errors": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Notification200Errors", + "type": "named" + } + } + }, + "external_id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "recipients": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "DeliveryData": { + "fields": { + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "Filter": { + "fields": { + "field": { + "description": "Name of the field to use as the first operand in the filter expression.", + "type": { + "name": "String", + "type": "named" + } + }, + "key": { + "description": "If `field` is `tag`, this field is *required* to specify `key` inside the tags.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "relation": { + "description": "Operator of a filter expression.", + "type": { + "name": "FilterRelation", + "type": "named" + } + }, + "value": { + "description": "Constant value to use as the second operand in the filter expression. This value is *required* when the relation operator is a binary operator.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, + "NotificationInput": { + "fields": { + "send_after": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "TimestampTZ", + "type": "named" + } + } + } + } + }, + "NotificationSlice": { + "fields": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "notifications": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "NotificationWithMeta", + "type": "named" + }, + "type": "array" + } + } + }, + "offset": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "total_count": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "NotificationWithMeta": { + "fields": { + "completed_at": { + "description": "Unix timestamp indicating when notification delivery completed. The delivery duration from start to finish can be calculated with completed_at - send_after.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "contents": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "converted": { + "description": "Number of messages that were clicked.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "custom_data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "data": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "JSON", + "type": "named" + } + } + }, + "errored": { + "description": "Number of errors reported.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "excluded_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "failed": { + "description": "Number of messages sent to unsubscribed devices.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "filters": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "Filter", + "type": "named" + }, + "type": "array" + } + } + }, + "headings": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "id": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "include_player_ids": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "included_segments": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "String", + "type": "named" + }, + "type": "array" + } + } + }, + "outcomes": { + "type": { + "type": "nullable", + "underlying_type": { + "element_type": { + "name": "OutcomeData", + "type": "named" + }, + "type": "array" + } + } + }, + "platform_delivery_stats": { + "description": "Hash of delivery statistics broken out by target device platform.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlatformDeliveryData", + "type": "named" + } + } + }, + "queued_at": { + "description": "Unix timestamp indicating when the notification was created.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "received": { + "description": "Number of devices that received the message.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "remaining": { + "description": "Number of notifications that have not been sent out yet. This can mean either our system is still processing the notification or you have delayed options set.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "send_after": { + "description": "Unix timestamp indicating when notification delivery should begin.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int64", + "type": "named" + } + } + }, + "subtitle": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "StringMap", + "type": "named" + } + } + }, + "successful": { + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "target_channel": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "PlayerNotificationTargetTargetChannel", + "type": "named" + } + } + }, + "throttle_rate_per_minute": { + "description": "number of push notifications sent per minute. Paid Feature Only. If throttling is not enabled for the app or the notification, and for free accounts, null is returned. Refer to Throttling for more details.", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } + }, + "OutcomeData": { + "fields": { + "aggregation": { + "type": { + "name": "OutcomeDataAggregation", + "type": "named" + } + }, + "id": { + "type": { + "name": "String", + "type": "named" + } + }, + "value": { + "type": { + "name": "Int32", + "type": "named" + } + } + } + }, + "PlatformDeliveryData": { + "description": "Hash of delivery statistics broken out by target device platform.", + "fields": { + "android": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "chrome_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "edge_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "email": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "firefox_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "ios": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "safari_web_push": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + }, + "sms": { + "type": { + "type": "nullable", + "underlying_type": { + "name": "DeliveryData", + "type": "named" + } + } + } + } + }, + "StringMap": { + "fields": { + "en": { + "description": "Text in English. Will be used as a fallback", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + } + }, + "procedures": [ + { + "request": { + "url": "/notifications", + "method": "post", + "security": [ + { + "app_key": [] + } + ], + "requestBody": { + "contentType": "application/json", + "schema": { + "type": "NotificationInput" + } + }, + "response": { + "contentType": "application/json" + } + }, + "arguments": { + "body": { + "description": "Request body of POST /notifications", + "type": { + "name": "NotificationInput", + "type": "named" + } + } + }, + "description": "Create notification", + "name": "hasuraCreateNotification", + "result_type": { + "name": "CreateNotificationSuccessResponse", + "type": "named" + } + } + ], + "scalar_types": { + "FilterRelation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "\u003e", + "\u003c", + "=", + "!=", + "exists", + "not_exists", + "time_elapsed_gt", + "time_elapsed_lt" + ], + "type": "enum" + } + }, + "Int32": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int32" + } + }, + "Int64": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "int64" + } + }, + "JSON": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "Notification200Errors": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "json" + } + }, + "OutcomeDataAggregation": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "sum", + "count" + ], + "type": "enum" + } + }, + "PlayerNotificationTargetTargetChannel": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "one_of": [ + "push", + "email", + "sms" + ], + "type": "enum" + } + }, + "String": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "string" + } + }, + "TimestampTZ": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "timestamptz" + } + } + } +} diff --git a/openapi/testdata/prefix3/source.json b/openapi/testdata/prefix3/source.json new file mode 100644 index 0000000..6e7a108 --- /dev/null +++ b/openapi/testdata/prefix3/source.json @@ -0,0 +1,746 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.2.2", + "title": "OneSignal", + "description": "A powerful way to send personalized messages at scale and build effective customer engagement strategies. Learn more at onesignal.com", + "contact": { + "name": "OneSignal DevRel", + "email": "devrel@onesignal.com", + "url": "https://onesignal.com" + }, + "termsOfService": "https://onesignal.com/tos" + }, + "servers": [ + { + "url": "https://onesignal.com/api/v1" + } + ], + "components": { + "securitySchemes": { + "app_key": { + "type": "http", + "scheme": "bearer" + }, + "user_key": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "Button": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + } + }, + "required": ["id"] + }, + + "Operator": { + "type": "object", + "properties": { + "operator": { + "type": "string", + "enum": ["OR", "AND"], + "description": "Strictly, this must be either `\"OR\"`, or `\"AND\"`. It can be used to compose Filters as part of a Filters object." + } + } + }, + "Purchase": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "The unique identifier of the purchased item." + }, + "amount": { + "type": "string", + "description": "The amount, in USD, spent purchasing the item." + }, + "iso": { + "type": "string", + "description": "The 3-letter ISO 4217 currency code. Required for correct storage and conversion of amount." + }, + "count": { + "type": "number" + } + }, + "required": ["sku", "amount", "iso"] + }, + "SegmentNotificationTarget": { + "type": "object", + "properties": { + "included_segments": { + "type": "array", + "items": { + "type": "string" + } + }, + "excluded_segments": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PlayerNotificationTarget": { + "type": "object", + "properties": { + "include_player_ids": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "deprecated": true + }, + "target_channel": { + "type": "string", + "enum": ["push", "email", "sms"] + } + } + }, + "NotificationTarget": { + "anyOf": [ + { + "$ref": "#/components/schemas/SegmentNotificationTarget" + }, + { + "$ref": "#/components/schemas/PlayerNotificationTarget" + } + ] + }, + "BasicNotification": { + "allOf": [ + { + "$ref": "#/components/schemas/NotificationTarget" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "contents": { + "allOf": [ + { + "$ref": "#/components/schemas/StringMap" + }, + { + "description": "Required unless content_available=true or template_id is set.\nThe message's content (excluding the title), a map of language codes to text for each language.\nEach hash must have a language code string for a key, mapped to the localized text you would like users to receive for that language.\nThis field supports inline substitutions.\nEnglish must be included in the hash.\nExample: {\"en\": \"English Message\", \"es\": \"Spanish Message\"}\n", + "writeOnly": true + } + ] + }, + "headings": { + "allOf": [ + { + "$ref": "#/components/schemas/StringMap" + }, + { + "description": "The message's title, a map of language codes to text for each language. Each hash must have a language code string for a key, mapped to the localized text you would like users to receive for that language.\nThis field supports inline substitutions.\nExample: {\"en\": \"English Title\", \"es\": \"Spanish Title\"}\n", + "writeOnly": true + } + ] + }, + "subtitle": { + "allOf": [ + { + "$ref": "#/components/schemas/StringMap" + }, + { + "description": "The message's subtitle, a map of language codes to text for each language. Each hash must have a language code string for a key, mapped to the localized text you would like users to receive for that language.\nThis field supports inline substitutions.\nExample: {\"en\": \"English Subtitle\", \"es\": \"Spanish Subtitle\"}\n", + "writeOnly": true + } + ] + }, + "data": { + "type": "object", + "description": "Channel: Push Notifications\nPlatform: Huawei\nA custom map of data that is passed back to your app. Same as using Additional Data within the dashboard. Can use up to 2048 bytes of data.\nExample: {\"abc\": 123, \"foo\": \"bar\", \"event_performed\": true, \"amount\": 12.1}\n", + "writeOnly": true, + "nullable": true + }, + "filters": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/Filter" + } + }, + "custom_data": { + "type": "object", + "writeOnly": true, + "nullable": true + } + } + }, + { + "required": ["app_id"] + } + ] + }, + "Notification": { + "allOf": [ + { + "$ref": "#/components/schemas/BasicNotification" + }, + { + "type": "object", + "properties": { + "send_after": { + "type": "string", + "format": "date-time", + "writeOnly": true, + "nullable": true + } + } + } + ] + }, + "NotificationWithMeta": { + "allOf": [ + { + "$ref": "#/components/schemas/BasicNotification" + }, + { + "$ref": "#/components/schemas/DeliveryData" + }, + { + "$ref": "#/components/schemas/OutcomesData" + }, + { + "type": "object", + "properties": { + "remaining": { + "type": "integer", + "description": "Number of notifications that have not been sent out yet. This can mean either our system is still processing the notification or you have delayed options set." + }, + "successful": { + "type": "integer", + "description": "Number of notifications that were successfully delivered." + }, + "failed": { + "type": "integer", + "description": "Number of notifications that could not be delivered due to those devices being unsubscribed." + }, + "errored": { + "type": "integer", + "description": "Number of notifications that could not be delivered due to an error. You can find more information by viewing the notification in the dashboard." + }, + "converted": { + "type": "integer", + "description": "Number of users who have clicked / tapped on your notification." + }, + "queued_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp indicating when the notification was created." + }, + "send_after": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp indicating when notification delivery should begin.", + "nullable": true + }, + "completed_at": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Unix timestamp indicating when notification delivery completed. The delivery duration from start to finish can be calculated with completed_at - send_after." + }, + "platform_delivery_stats": { + "$ref": "#/components/schemas/PlatformDeliveryData" + }, + "received": { + "type": "integer", + "nullable": true, + "description": "Confirmed Deliveries number of devices that received the push notification. Paid Feature Only. Free accounts will see 0." + }, + "throttle_rate_per_minute": { + "type": "integer", + "nullable": true, + "description": "number of push notifications sent per minute. Paid Feature Only. If throttling is not enabled for the app or the notification, and for free accounts, null is returned. Refer to Throttling for more details." + } + } + } + ] + }, + "StringMap": { + "type": "object", + "properties": { + "en": { + "type": "string", + "description": "Text in English. Will be used as a fallback" + } + } + }, + "NotificationSlice": { + "type": "object", + "properties": { + "total_count": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "notifications": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationWithMeta" + } + } + } + }, + "PlatformDeliveryData": { + "type": "object", + "description": "Hash of delivery statistics broken out by target device platform.", + "properties": { + "edge_web_push": { + "$ref": "#/components/schemas/DeliveryData" + }, + "chrome_web_push": { + "$ref": "#/components/schemas/DeliveryData" + }, + "firefox_web_push": { + "$ref": "#/components/schemas/DeliveryData" + }, + "safari_web_push": { + "$ref": "#/components/schemas/DeliveryData" + }, + "android": { + "$ref": "#/components/schemas/DeliveryData" + }, + "ios": { + "$ref": "#/components/schemas/DeliveryData" + }, + "sms": { + "allOf": [ + { + "$ref": "#/components/schemas/DeliveryData" + }, + { + "properties": { + "provider_successful": { + "type": "integer", + "description": "Number of messages reported as delivered successfully by the SMS service provider.", + "nullable": true + }, + "provider_failed": { + "type": "integer", + "description": "Number of recipients who didn't receive your message as reported by the SMS service provider.", + "nullable": true + }, + "provider_errored": { + "type": "integer", + "description": "Number of errors reported by the SMS service provider.", + "nullable": true + } + } + } + ] + }, + "email": { + "allOf": [ + { + "$ref": "#/components/schemas/DeliveryData" + }, + { + "properties": { + "opened": { + "type": "integer", + "description": "Number of times an email has been opened.", + "nullable": true + }, + "unique_opens": { + "type": "integer", + "description": "Number of unique recipients who have opened your email.", + "nullable": true + }, + "clicks": { + "type": "integer", + "description": "Number of clicked links from your email. This can include the recipient clicking email links multiple times.", + "nullable": true + }, + "unique_clicks": { + "type": "integer", + "description": "Number of unique clicks that your recipients have made on links from your email.", + "nullable": true + }, + "bounced": { + "type": "integer", + "description": "Number of recipients who registered as a hard or soft bounce and didn't receive your email.", + "nullable": true + }, + "reported_spam": { + "type": "integer", + "description": "Number of recipients who reported this email as spam.", + "nullable": true + }, + "unsubscribed": { + "type": "integer", + "description": "Number of recipients who opted out of your emails using the unsubscribe link in this email.", + "nullable": true + } + } + } + ] + } + } + }, + "DeliveryData": { + "type": "object", + "properties": { + "successful": { + "type": "integer", + "description": "Number of messages delivered to push servers, mobile carriers, or email service providers.", + "nullable": true + }, + "failed": { + "type": "integer", + "description": "Number of messages sent to unsubscribed devices.", + "nullable": true + }, + "errored": { + "type": "integer", + "description": "Number of errors reported.", + "nullable": true + }, + "converted": { + "type": "integer", + "description": "Number of messages that were clicked.", + "nullable": true + }, + "received": { + "type": "integer", + "description": "Number of devices that received the message.", + "nullable": true + } + } + }, + "OutcomeData": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "value": { + "type": "integer" + }, + "aggregation": { + "type": "string", + "enum": ["sum", "count"] + } + }, + "required": ["id", "value", "aggregation"] + }, + "OutcomesData": { + "type": "object", + "properties": { + "outcomes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutcomeData" + } + } + } + }, + "Filter": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "Name of the field to use as the first operand in the filter expression." + }, + "key": { + "type": "string", + "description": "If `field` is `tag`, this field is *required* to specify `key` inside the tags." + }, + "value": { + "type": "string", + "description": "Constant value to use as the second operand in the filter expression. This value is *required* when the relation operator is a binary operator." + }, + "relation": { + "type": "string", + "description": "Operator of a filter expression.", + "enum": [ + ">", + "<", + "=", + "!=", + "exists", + "not_exists", + "time_elapsed_gt", + "time_elapsed_lt" + ] + } + }, + "required": ["field", "relation"] + }, + "InvalidIdentifierError": { + "type": "object", + "properties": { + "invalid_external_user_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Returned if using include_external_user_ids" + }, + "invalid_player_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Returned if using include_player_ids and some were valid and others were not." + } + } + }, + "NoSubscribersError": { + "type": "array", + "items": { + "type": "string", + "format": "string" + }, + "description": "Returned if no subscribed players.\n" + }, + "Notification200Errors": { + "oneOf": [ + { + "$ref": "#/components/schemas/InvalidIdentifierError" + }, + { + "$ref": "#/components/schemas/NoSubscribersError" + } + ] + }, + "IdentityObject": { + "type": "object", + "additionalProperties": true + }, + "CreateNotificationSuccessResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "recipients": { + "type": "integer" + }, + "external_id": { + "type": "string", + "nullable": true + }, + "errors": { + "$ref": "#/components/schemas/Notification200Errors" + } + } + }, + "CancelNotificationSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + }, + "NotificationHistorySuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "destination_url": { + "type": "string" + } + } + }, + "GenericError": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "title": { + "type": "string" + } + } + } + } + } + }, + "RateLimiterError": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "title": { + "type": "string" + } + } + } + } + } + } + } + }, + "paths": { + "/notifications": { + "get": { + "operationId": "get_notifications", + "summary": "View notifications", + "description": "View the details of multiple notifications", + "parameters": [ + { + "in": "query", + "name": "app_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The app ID that you want to view notifications from" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + }, + "required": false, + "description": "How many notifications to return. Max is 50. Default is 50." + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + }, + "required": false, + "description": "Page offset. Default is 0. Results are sorted by queued_at in descending order. queued_at is a representation of the time that the notification was queued at." + }, + { + "in": "query", + "name": "kind", + "schema": { + "type": "integer", + "enum": [0, 1, 3] + }, + "description": "Kind of notifications returned:\n * unset - All notification types (default)\n * `0` - Dashboard only\n * `1` - API only\n * `3` - Automated only\n", + "required": false + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSlice" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericError" + } + } + } + }, + "429": { + "description": "Rate Limit Exceeded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimiterError" + } + } + } + } + }, + "security": [ + { + "app_key": [] + } + ] + }, + "post": { + "operationId": "create_notification", + "summary": "Create notification", + "description": "Sends notifications to your users\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Notification" + } + } + } + }, + "responses": { + "200": { + "description": "OK, invalid_player_ids, invalid_external_user_ids or No Subscribed Players\nIf a message was successfully created, you will get a 200 response and an id for the notification.\nIf the 200 response contains \"invalid_player_ids\" or \"invalid_external_user_ids\" this will mark devices that exist in the provided app_id but are no longer subscribed.\nIf no id is returned, then a message was not created and the targeted User IDs do not exist under the provided app_id.\nAny User IDs sent in the request that do not exist under the specified app_id will be ignored.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateNotificationSuccessResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericError" + } + } + } + }, + "429": { + "description": "Rate Limit Exceeded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimiterError" + } + } + } + } + }, + "security": [ + { + "app_key": [] + } + ] + } + } + } +} diff --git a/utils/string.go b/utils/string.go index d2b0cf8..b2d65dd 100644 --- a/utils/string.go +++ b/utils/string.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "regexp" "strings" "unicode" @@ -19,6 +20,11 @@ func ToCamelCase(input string) string { 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:])) +} + // ToPascalCase convert a string to PascalCase func ToPascalCase(input string) string { if input == "" { @@ -108,7 +114,7 @@ func ToConstantCase(input string) string { return strings.ToUpper(ToSnakeCase(input)) } -// StringSliceToConstantCase convert a slice of string to PascalCase +// StringSliceToConstantCase convert a slice of string to CONSTANT_CASE func StringSliceToConstantCase(inputs []string) string { return strings.ToUpper(StringSliceToSnakeCase(inputs)) }