From f8e126635de2304204978a6e863f0ff7197c91ac Mon Sep 17 00:00:00 2001 From: Paul Sturgess Date: Tue, 21 Nov 2023 12:11:27 +0000 Subject: [PATCH] Expose error responses in the spec output (#25) Apia defines errors responses like this: ```ruby potential_error 'DiskNotFound' do code :disk_not_found description 'No disk was found matching any of the criteria provided in the arguments' http_status 404 end ``` So now we declare these in the responses for each endpoint. https://swagger.io/docs/specification/describing-responses/ closes: https://github.com/krystal/apia-openapi/issues/2 --- Gemfile | 1 + Gemfile.lock | 6 + .../authenticators/main_authenticator.rb | 57 +++ .../time_controller_authenticator.rb | 13 + .../authenticators/time_now_authenticator.rb | 17 + examples/core_api/base.rb | 4 +- .../core_api/controllers/time_controller.rb | 3 + examples/core_api/endpoints/test_endpoint.rb | 13 + .../core_api/endpoints/time_now_endpoint.rb | 6 + .../core_api/errors/rate_limit_reached.rb | 17 + examples/core_api/main_authenticator.rb | 45 -- lib/apia/open_api/helpers.rb | 18 +- lib/apia/open_api/objects.rb | 2 +- lib/apia/open_api/objects/path.rb | 11 +- lib/apia/open_api/objects/response.rb | 147 +++++- lib/apia/open_api/objects/schema.rb | 27 +- lib/apia/open_api/specification.rb | 8 +- spec/support/fixtures/openapi.json | 430 +++++++++++++++--- 18 files changed, 691 insertions(+), 134 deletions(-) create mode 100644 examples/core_api/authenticators/main_authenticator.rb create mode 100644 examples/core_api/authenticators/time_controller_authenticator.rb create mode 100644 examples/core_api/authenticators/time_now_authenticator.rb create mode 100644 examples/core_api/errors/rate_limit_reached.rb delete mode 100644 examples/core_api/main_authenticator.rb diff --git a/Gemfile b/Gemfile index 50f3acd..751680d 100644 --- a/Gemfile +++ b/Gemfile @@ -11,5 +11,6 @@ gem "rspec", "~> 3.0" gem "rubocop", "~> 1.21" group :test do + gem "pry" gem "simplecov", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 1675a7f..26f433c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,17 +16,22 @@ GEM json rack ast (2.4.2) + coderay (1.1.3) concurrent-ruby (1.2.2) diff-lcs (1.5.0) docile (1.4.0) i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) + method_source (1.0.0) minitest (5.20.0) parallel (1.23.0) parser (3.2.2.3) ast (~> 2.4.1) racc + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) racc (1.7.1) rack (3.0.8) rainbow (3.1.1) @@ -75,6 +80,7 @@ PLATFORMS DEPENDENCIES apia (~> 3.5) apia-open_api! + pry rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.21) diff --git a/examples/core_api/authenticators/main_authenticator.rb b/examples/core_api/authenticators/main_authenticator.rb new file mode 100644 index 0000000..5cfca34 --- /dev/null +++ b/examples/core_api/authenticators/main_authenticator.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module CoreAPI + module Authenticators + class MainAuthenticator < Apia::Authenticator + + BEARER_TOKEN = "example" + + type :bearer + + potential_error "InvalidToken" do + code :invalid_token + description "The token provided is invalid. In this example, you should provide '#{BEARER_TOKEN}'." + http_status 403 + + field :given_token, type: :string + end + + potential_error "UnauthorizedNetworkForAPIToken" do + code :unauthorized_network_for_api_token + description "Network is not allowed to access the API with this API token" + http_status 403 + + field :ip_address, :string do + description "The IP address the request was received from" + end + end + + def call + configure_cors_response + return if request.options? + + given_token = request.headers["authorization"]&.sub(/\ABearer /, "") + if given_token == BEARER_TOKEN + request.identity = { name: "Example User", id: 1234 } + else + raise_error "CoreAPI/MainAuthenticator/InvalidToken", given_token: given_token.to_s + end + end + + private + + # These are not strictly required, but it allows the app to work with swagger-ui. + def configure_cors_response + # Define a list of cors methods that are permitted for the request. + cors.methods = %w[GET POST PUT PATCH DELETE OPTIONS] + + # Define a list of cors headers that are permitted for the request. + cors.headers = %w[Authorization Content-Type] # or allow all with '*' + + # Define a the hostname to allow for CORS requests. + cors.origin = "*" # or 'example.com' + end + + end + end +end diff --git a/examples/core_api/authenticators/time_controller_authenticator.rb b/examples/core_api/authenticators/time_controller_authenticator.rb new file mode 100644 index 0000000..843dc31 --- /dev/null +++ b/examples/core_api/authenticators/time_controller_authenticator.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "core_api/errors/rate_limit_reached" + +module CoreAPI + module Authenticators + class TimeControllerAuthenticator < Apia::Authenticator + + potential_error CoreAPI::Errors::RateLimitReached + + end + end +end diff --git a/examples/core_api/authenticators/time_now_authenticator.rb b/examples/core_api/authenticators/time_now_authenticator.rb new file mode 100644 index 0000000..28bf5dd --- /dev/null +++ b/examples/core_api/authenticators/time_now_authenticator.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "core_api/errors/rate_limit_reached" + +module CoreAPI + module Authenticators + class TimeNowAuthenticator < Apia::Authenticator + + potential_error "WrongDayOfWeek" do + code :wrong_day_of_week + description "You called this API on the wrong day of the week, try again tomorrow" + http_status 503 + end + + end + end +end diff --git a/examples/core_api/base.rb b/examples/core_api/base.rb index 551e6e3..011aaed 100644 --- a/examples/core_api/base.rb +++ b/examples/core_api/base.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require "core_api/main_authenticator" +require "core_api/authenticators/main_authenticator" require "core_api/controllers/time_controller" require "core_api/endpoints/test_endpoint" module CoreAPI class Base < Apia::API - authenticator MainAuthenticator + authenticator Authenticators::MainAuthenticator scopes do add "time", "Allows time telling functions" diff --git a/examples/core_api/controllers/time_controller.rb b/examples/core_api/controllers/time_controller.rb index 1e3e5df..17d8aed 100644 --- a/examples/core_api/controllers/time_controller.rb +++ b/examples/core_api/controllers/time_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "core_api/objects/time" +require "core_api/authenticators/time_controller_authenticator" require "core_api/argument_sets/time_lookup_argument_set" require "core_api/endpoints/time_now_endpoint" @@ -11,6 +12,8 @@ class TimeController < Apia::Controller name "Time API" description "Returns the time in varying ways" + authenticator Authenticators::TimeControllerAuthenticator + endpoint :now, Endpoints::TimeNowEndpoint # TODO: add example of multiple objects using the same objects, to ensure diff --git a/examples/core_api/endpoints/test_endpoint.rb b/examples/core_api/endpoints/test_endpoint.rb index 8470230..18df3b6 100644 --- a/examples/core_api/endpoints/test_endpoint.rb +++ b/examples/core_api/endpoints/test_endpoint.rb @@ -7,8 +7,10 @@ module Endpoints class TestEndpoint < Apia::Endpoint description "Returns the current time" + argument :object, type: ArgumentSets::ObjectLookup, required: true argument :scalar, type: :string, required: true + field :time, type: Objects::Time, include: "unix,day_of_week,year[as_string]", null: true do condition do |o| o[:time].year.to_s == "2023" @@ -17,8 +19,19 @@ class TestEndpoint < Apia::Endpoint field :object_id, type: :string do backend { |o| o[:object_id][:id] } end + scope "time" + potential_error "InvalidTest" do + code :invalid_test + http_status 400 + end + + potential_error "AnotherInvalidTest" do + code :another_invalid_test + http_status 400 + end + def call object = request.arguments[:object].resolve response.add_field :time, get_time_now diff --git a/examples/core_api/endpoints/time_now_endpoint.rb b/examples/core_api/endpoints/time_now_endpoint.rb index b3c6e1b..5c1b3e7 100644 --- a/examples/core_api/endpoints/time_now_endpoint.rb +++ b/examples/core_api/endpoints/time_now_endpoint.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "core_api/authenticators/time_now_authenticator" require "core_api/objects/time_zone" module CoreAPI @@ -7,14 +8,19 @@ module Endpoints class TimeNowEndpoint < Apia::Endpoint description "Returns the current time" + + authenticator Authenticators::TimeNowAuthenticator + argument :timezone, type: Objects::TimeZone argument :time_zones, [Objects::TimeZone], required: true argument :filters, [:string] + field :time, type: Objects::Time field :time_zones, type: [Objects::TimeZone] field :filters, [:string], null: true field :my_polymorph, type: Objects::MonthPolymorph field :my_partial_polymorph, type: Objects::MonthPolymorph, include: "number", null: true + scope "time" def call diff --git a/examples/core_api/errors/rate_limit_reached.rb b/examples/core_api/errors/rate_limit_reached.rb new file mode 100644 index 0000000..213a6b6 --- /dev/null +++ b/examples/core_api/errors/rate_limit_reached.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CoreAPI + module Errors + class RateLimitReached < Apia::Error + + code :rate_limit_reached + http_status 429 + description "You have reached the rate limit for this type of request" + + field :total_permitted, type: :integer do + description "The total number of requests per minute that are permitted" + end + + end + end +end diff --git a/examples/core_api/main_authenticator.rb b/examples/core_api/main_authenticator.rb deleted file mode 100644 index 5a61e6e..0000000 --- a/examples/core_api/main_authenticator.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module CoreAPI - class MainAuthenticator < Apia::Authenticator - - BEARER_TOKEN = "example" - - type :bearer - - potential_error "InvalidToken" do - code :invalid_token - description "The token provided is invalid. In this example, you should provide '#{BEARER_TOKEN}'." - http_status 403 - - field :given_token, type: :string - end - - def call - configure_cors_response - return if request.options? - - given_token = request.headers["authorization"]&.sub(/\ABearer /, "") - if given_token == BEARER_TOKEN - request.identity = { name: "Example User", id: 1234 } - else - raise_error "CoreAPI/MainAuthenticator/InvalidToken", given_token: given_token.to_s - end - end - - private - - # These are not strictly required, but it allows the app to work with swagger-ui. - def configure_cors_response - # Define a list of cors methods that are permitted for the request. - cors.methods = %w[GET POST PUT PATCH DELETE OPTIONS] - - # Define a list of cors headers that are permitted for the request. - cors.headers = %w[Authorization Content-Type] # or allow all with '*' - - # Define a the hostname to allow for CORS requests. - cors.origin = "*" # or 'example.com' - end - - end -end diff --git a/lib/apia/open_api/helpers.rb b/lib/apia/open_api/helpers.rb index 814ca15..bf51411 100644 --- a/lib/apia/open_api/helpers.rb +++ b/lib/apia/open_api/helpers.rb @@ -8,7 +8,7 @@ module Helpers # A component schema is a re-usable schema that can be referenced by other parts of the spec # e.g. { "$ref": "#/components/schemas/PaginationObject" } def add_to_components_schemas(definition, id, **schema_opts) - return unless @spec.dig(:components, :schemas, id).nil? + return true unless @spec.dig(:components, :schemas, id).nil? component_schema = {} @spec[:components][:schemas][id] = component_schema @@ -19,6 +19,11 @@ def add_to_components_schemas(definition, id, **schema_opts) id: id, **schema_opts ).add_to_spec + + return true if component_schema.present? + + @spec[:components][:schemas].delete(id) + false end def convert_type_to_open_api_data_type(type) @@ -35,13 +40,18 @@ def convert_type_to_open_api_data_type(type) def generate_schema_ref(definition, id: nil, **schema_opts) id ||= generate_id_from_definition(definition.type.klass.definition) - add_to_components_schemas(definition, id, **schema_opts) - { "$ref": "#/components/schemas/#{id}" } + success = add_to_components_schemas(definition, id, **schema_opts) + + if success + { "$ref": "#/components/schemas/#{id}" } + else # no properties were defined, so just declare an object with unknown properties + { type: "object" } + end end # forward slashes do not work in ids (e.g. schema ids) def generate_id_from_definition(definition) - definition.id.gsub(/\//, "_") + definition.id.gsub(/\//, "") end def formatted_description(description) diff --git a/lib/apia/open_api/objects.rb b/lib/apia/open_api/objects.rb index c611bd2..0c2fb42 100644 --- a/lib/apia/open_api/objects.rb +++ b/lib/apia/open_api/objects.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -Dir.glob(File.join(File.dirname(__FILE__), "objects", "*.rb")).each do |file| +Dir.glob(File.join(File.dirname(__FILE__), "objects", "*.rb")).sort.each do |file| require_relative file end diff --git a/lib/apia/open_api/objects/path.rb b/lib/apia/open_api/objects/path.rb index 19e1ed2..1f6e82a 100644 --- a/lib/apia/open_api/objects/path.rb +++ b/lib/apia/open_api/objects/path.rb @@ -29,10 +29,11 @@ class Path include Apia::OpenApi::Helpers - def initialize(spec:, path_ids:, route:, name:) + def initialize(spec:, path_ids:, route:, name:, api_authenticator:) @spec = spec @path_ids = path_ids @route = route + @api_authenticator = api_authenticator @route_spec = { operationId: convert_route_to_id, tags: [name] @@ -69,7 +70,13 @@ def add_request_body end def add_responses - Response.new(spec: @spec, path_ids: @path_ids, route: @route, route_spec: @route_spec).add_to_spec + Response.new( + spec: @spec, + path_ids: @path_ids, + route: @route, + route_spec: @route_spec, + api_authenticator: @api_authenticator + ).add_to_spec end # It's worth creating a 'nice' operationId for each route, as this is used as the diff --git a/lib/apia/open_api/objects/response.rb b/lib/apia/open_api/objects/response.rb index 7ae6048..ccb68de 100644 --- a/lib/apia/open_api/objects/response.rb +++ b/lib/apia/open_api/objects/response.rb @@ -28,38 +28,43 @@ class Response include Apia::OpenApi::Helpers - def initialize(spec:, path_ids:, route:, route_spec:) + def initialize(spec:, path_ids:, route:, route_spec:, api_authenticator:) @spec = spec @path_ids = path_ids @route = route @endpoint = route.endpoint @route_spec = route_spec + @api_authenticator = api_authenticator @http_status = @endpoint.definition.http_status end def add_to_spec - response_schema = { - properties: generate_properties_for_response - } + add_sucessful_response_schema + add_error_response_schemas + end + + private + def add_sucessful_response_schema + content_schema = { + properties: generate_properties_for_successful_response + } required_fields = @endpoint.definition.fields.select { |_, field| field.condition.nil? } - response_schema[:required] = required_fields.keys if required_fields.any? + content_schema[:required] = required_fields.keys if required_fields.any? @route_spec[:responses] = { "#{@http_status}": { description: @endpoint.definition.description || "", content: { "application/json": { - schema: response_schema + schema: content_schema } } } } end - private - - def generate_properties_for_response + def generate_properties_for_successful_response @endpoint.definition.fields.reduce({}) do |props, (name, field)| props.merge(generate_properties_for_field(name, field)) end @@ -146,11 +151,127 @@ def field_includes_all_properties?(field) def generate_field_id(field_name) [ - @route_spec[:operationId].sub(":", "_").gsub(":", "").split("/"), + @route_spec[:operationId].gsub(":", "_").gsub("/", "_").camelize, @http_status, - "response", - field_name - ].flatten.join("_") + "Response", + field_name.to_s + ].flatten.join("_").camelize + end + + def add_error_response_schemas + grouped_potential_errors = potential_errors.map(&:definition).group_by do |d| + d.http_status_code.to_s.to_sym + end + + sorted_grouped_potential_errors = grouped_potential_errors.sort_by do |http_status_code, _| + http_status_code.to_s.to_i + end + + sorted_grouped_potential_errors.each do |http_status_code, potential_errors| + add_error_response_schema_for_http_status_code(http_status_code, potential_errors) + end + end + + def api_authenticator_potential_errors + @api_authenticator&.definition&.potential_errors + end + + def potential_errors + argument_set = @endpoint.definition.argument_set + lookup_argument_set_errors = argument_set.collate_objects(Apia::ObjectSet.new).values.map do |o| + o.type.klass.definition.try(:potential_errors) + end.compact + + [ + api_authenticator_potential_errors, + @route.controller&.definition&.authenticator&.definition&.potential_errors, + @endpoint.definition&.authenticator&.definition&.potential_errors, + lookup_argument_set_errors, + @endpoint.definition.potential_errors + ].compact.flatten + end + + def add_error_response_schema_for_http_status_code(http_status_code, potential_errors) + response_schema = generate_potential_error_ref(http_status_code, potential_errors) + @route_spec[:responses].merge!(response_schema) + end + + def generate_potential_error_ref(http_status_code, potential_errors) + { "#{http_status_code}": generate_ref("responses", http_status_code, potential_errors) } + end + + def generate_ref(namespace, http_status_code, definitions) + id = generate_id_for_error_ref(http_status_code, definitions) + if namespace == "responses" + add_to_responses_components(http_status_code, definitions, id) + else + add_to_schemas_components(definitions.first, id) + end + { "$ref": "#/components/#{namespace}/#{id}" } + end + + def generate_id_for_error_ref(http_status_code, definitions) + if definitions == api_authenticator_potential_errors.map(&:definition) + "APIAuthenticator#{http_status_code}Response" + elsif definitions.length == 1 + "#{generate_id_from_definition(definitions.first)}Response" + else + [ + @route_spec[:operationId].sub(":", "_").gsub(":", "").split("/"), + http_status_code, + "Response" + ].flatten.join("_").camelize + end + end + + def add_to_responses_components(http_status_code, definitions, id) + return unless @spec.dig(:components, :components, id).nil? + + component_schema = { + description: "#{http_status_code} error response" + } + + if definitions.length == 1 + definition = definitions.first + component_schema[:description] = definition.description if definition.description.present? + schema = generate_schema_properties_for_definition(definition) + else # the same http status code is used for multiple errors + schema = { oneOf: definitions.map { |d| generate_ref("schemas", http_status_code, [d]) } } + end + + component_schema[:content] = { + "application/json": { + schema: schema + } + } + @spec[:components][:responses] ||= {} + @spec[:components][:responses][id] = component_schema + component_schema + end + + def generate_schema_properties_for_definition(definition) + detail = generate_schema_ref(definition, id: generate_id_from_definition(definition)) + { + properties: { + code: { + type: "string", + enum: [definition.code] + }, + description: { type: "string" }, + detail: detail + } + } + end + + def add_to_schemas_components(definition, id) + @spec[:components][:schemas] ||= {} + component_schema = { + type: "object" + } + component_schema[:description] = definition.description if definition.description.present? + component_schema.merge!(generate_schema_properties_for_definition(definition)) + @spec[:components][:schemas][id] = component_schema + component_schema end end diff --git a/lib/apia/open_api/objects/schema.rb b/lib/apia/open_api/objects/schema.rb index 918a431..1bd6d93 100644 --- a/lib/apia/open_api/objects/schema.rb +++ b/lib/apia/open_api/objects/schema.rb @@ -46,7 +46,7 @@ def initialize(spec:, definition:, schema:, id:, endpoint: nil, path: nil) end def add_to_spec - if @definition.type.polymorph? + if @definition.try(:type)&.polymorph? build_schema_for_polymorph return @schema end @@ -67,21 +67,32 @@ def build_schema_for_polymorph @schema[:properties][@definition.name.to_s] = { oneOf: refs } end + def error_definition? + @definition.is_a?(Apia::Definitions::Error) + end + + def enum_definition? + @definition.try(:type)&.enum? + end + def generate_child_schemas - if @definition.type.argument_set? + if error_definition? + @children = @definition.fields.values + elsif @definition.type.argument_set? @children = @definition.type.klass.definition.arguments.values - @schema[:description] = "All '#{@definition.name}[]' params are mutually exclusive, only one can be provided." + @schema[:description] = + "All '#{@definition.name}[]' params are mutually exclusive, only one can be provided." elsif @definition.type.object? @children = @definition.type.klass.definition.fields.values - elsif @definition.type.enum? + elsif enum_definition? @children = @definition.type.klass.definition.values.values end return if @children.empty? - all_properties_included = @definition.type.enum? || @endpoint.nil? + all_properties_included = error_definition? || enum_definition? || @endpoint.nil? @children.each do |child| - next unless @endpoint.nil? || (!@definition.type.enum? && @endpoint.include_field?(@path + [child.name])) + next unless @endpoint.nil? || (!enum_definition? && @endpoint.include_field?(@path + [child.name])) if child.respond_to?(:array?) && child.array? generate_schema_for_child_array(@schema, child, all_properties_included) @@ -104,7 +115,7 @@ def generate_schema_for_child_array(schema, child, all_properties_included) end def generate_schema_for_child(schema, child, all_properties_included) - if @definition.type.enum? + if enum_definition? schema[:type] = "string" schema[:enum] = @children.map { |c| c[:name] } elsif child.type.argument_set? || child.type.enum? || child.type.polymorph? @@ -137,7 +148,7 @@ def generate_properties_for_object(schema, child, all_properties_included) child_path = @path.nil? ? nil : @path + [child] schema[:properties][child.name.to_s] = generate_schema_ref( child, - id: "#{@id}_#{child.name}", + id: "#{@id}_#{child.name}".camelize, endpoint: @endpoint, path: child_path ) diff --git a/lib/apia/open_api/specification.rb b/lib/apia/open_api/specification.rb index 4ded9c7..bf4f62c 100644 --- a/lib/apia/open_api/specification.rb +++ b/lib/apia/open_api/specification.rb @@ -61,7 +61,13 @@ def add_paths @api.definition.route_set.routes.each do |route| next unless route.endpoint.definition.schema? # not all routes should be documented - Objects::Path.new(spec: @spec, path_ids: @path_ids, route: route, name: @name).add_to_spec + Objects::Path.new( + spec: @spec, + path_ids: @path_ids, + route: route, + name: @name, + api_authenticator: @api.definition.authenticator + ).add_to_spec end end diff --git a/spec/support/fixtures/openapi.json b/spec/support/fixtures/openapi.json index 605bd3d..4399466 100644 --- a/spec/support/fixtures/openapi.json +++ b/spec/support/fixtures/openapi.json @@ -37,7 +37,7 @@ "name": "timezone", "in": "query", "schema": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" }, "required": true } @@ -60,6 +60,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/CoreAPIArgumentSetsTimeLookupArgumentSetInvalidTimeResponse" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "429": { + "$ref": "#/components/responses/CoreAPIErrorsRateLimitReachedResponse" } } } @@ -76,10 +85,10 @@ "schema": { "properties": { "time": { - "$ref": "#/components/schemas/CoreAPI_ArgumentSets_TimeLookupArgumentSet" + "$ref": "#/components/schemas/CoreAPIArgumentSetsTimeLookupArgumentSet" }, "timezone": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, "required": [ @@ -108,6 +117,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/CoreAPIArgumentSetsTimeLookupArgumentSetInvalidTimeResponse" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "429": { + "$ref": "#/components/responses/CoreAPIErrorsRateLimitReachedResponse" } } } @@ -126,7 +144,7 @@ "times": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_ArgumentSets_TimeLookupArgumentSet" + "$ref": "#/components/schemas/CoreAPIArgumentSetsTimeLookupArgumentSet" } } }, @@ -153,7 +171,7 @@ "times": { "type": "array", "items": { - "$ref": "#/components/schemas/post_example_format_multiple_200_response_times" + "$ref": "#/components/schemas/PostExampleFormatMultiple200ResponseTimes" } } }, @@ -164,6 +182,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/CoreAPIArgumentSetsTimeLookupArgumentSetInvalidTimeResponse" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "429": { + "$ref": "#/components/responses/CoreAPIErrorsRateLimitReachedResponse" } } } @@ -179,7 +206,7 @@ "name": "timezone", "in": "query", "schema": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, { @@ -188,7 +215,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, "required": true @@ -212,12 +239,12 @@ "schema": { "properties": { "time": { - "$ref": "#/components/schemas/CoreAPI_Objects_Time" + "$ref": "#/components/schemas/CoreAPIObjectsTime" }, "time_zones": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, "filters": { @@ -230,15 +257,15 @@ "my_polymorph": { "oneOf": [ { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthLong" + "$ref": "#/components/schemas/CoreAPIObjectsMonthLong" }, { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthShort" + "$ref": "#/components/schemas/CoreAPIObjectsMonthShort" } ] }, "my_partial_polymorph": { - "$ref": "#/components/schemas/get_time_now_200_response_my_partial_polymorph", + "$ref": "#/components/schemas/GetTimeNow200ResponseMyPartialPolymorph", "nullable": true } }, @@ -252,6 +279,12 @@ } } } + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "503": { + "$ref": "#/components/responses/CoreAPIAuthenticatorsTimeNowAuthenticatorWrongDayOfWeekResponse" } } }, @@ -266,12 +299,12 @@ "schema": { "properties": { "timezone": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" }, "time_zones": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, "filters": { @@ -296,12 +329,12 @@ "schema": { "properties": { "time": { - "$ref": "#/components/schemas/CoreAPI_Objects_Time" + "$ref": "#/components/schemas/CoreAPIObjectsTime" }, "time_zones": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, "filters": { @@ -314,15 +347,15 @@ "my_polymorph": { "oneOf": [ { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthLong" + "$ref": "#/components/schemas/CoreAPIObjectsMonthLong" }, { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthShort" + "$ref": "#/components/schemas/CoreAPIObjectsMonthShort" } ] }, "my_partial_polymorph": { - "$ref": "#/components/schemas/post_time_now_200_response_my_partial_polymorph", + "$ref": "#/components/schemas/PostTimeNow200ResponseMyPartialPolymorph", "nullable": true } }, @@ -336,6 +369,12 @@ } } } + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "503": { + "$ref": "#/components/responses/CoreAPIAuthenticatorsTimeNowAuthenticatorWrongDayOfWeekResponse" } } } @@ -380,7 +419,7 @@ "schema": { "properties": { "time": { - "$ref": "#/components/schemas/get_test_object_200_response_time", + "$ref": "#/components/schemas/GetTestObject200ResponseTime", "nullable": true }, "object_id": { @@ -393,6 +432,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/GetTestObject400Response" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "404": { + "$ref": "#/components/responses/CoreAPIArgumentSetsObjectLookupObjectNotFoundResponse" } } }, @@ -407,7 +455,7 @@ "schema": { "properties": { "object": { - "$ref": "#/components/schemas/CoreAPI_ArgumentSets_ObjectLookup" + "$ref": "#/components/schemas/CoreAPIArgumentSetsObjectLookup" }, "scalar": { "type": "string" @@ -429,7 +477,7 @@ "schema": { "properties": { "time": { - "$ref": "#/components/schemas/post_test_object_200_response_time", + "$ref": "#/components/schemas/PostTestObject200ResponseTime", "nullable": true }, "object_id": { @@ -442,6 +490,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/PostTestObject400Response" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "404": { + "$ref": "#/components/responses/CoreAPIArgumentSetsObjectLookupObjectNotFoundResponse" } } } @@ -473,7 +530,7 @@ "name": "timezone", "in": "query", "schema": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" }, "required": true } @@ -496,6 +553,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/CoreAPIArgumentSetsTimeLookupArgumentSetInvalidTimeResponse" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "429": { + "$ref": "#/components/responses/CoreAPIErrorsRateLimitReachedResponse" } } }, @@ -510,10 +576,10 @@ "schema": { "properties": { "time": { - "$ref": "#/components/schemas/CoreAPI_ArgumentSets_TimeLookupArgumentSet" + "$ref": "#/components/schemas/CoreAPIArgumentSetsTimeLookupArgumentSet" }, "timezone": { - "$ref": "#/components/schemas/CoreAPI_Objects_TimeZone" + "$ref": "#/components/schemas/CoreAPIObjectsTimeZone" } }, "required": [ @@ -542,6 +608,15 @@ } } } + }, + "400": { + "$ref": "#/components/responses/CoreAPIArgumentSetsTimeLookupArgumentSetInvalidTimeResponse" + }, + "403": { + "$ref": "#/components/responses/APIAuthenticator403Response" + }, + "429": { + "$ref": "#/components/responses/CoreAPIErrorsRateLimitReachedResponse" } } } @@ -549,7 +624,7 @@ }, "components": { "schemas": { - "CoreAPI_Objects_TimeZone": { + "CoreAPIObjectsTimeZone": { "type": "string", "enum": [ "Europe/London", @@ -557,7 +632,67 @@ "Asia/Singapore" ] }, - "CoreAPI_ArgumentSets_TimeLookupArgumentSet": { + "CoreAPIAuthenticatorsMainAuthenticatorInvalidToken": { + "type": "object", + "properties": { + "given_token": { + "type": "string" + } + } + }, + "CoreAPIAuthenticatorsMainAuthenticatorInvalidTokenResponse": { + "type": "object", + "description": "The token provided is invalid. In this example, you should provide 'example'.", + "properties": { + "code": { + "type": "string", + "enum": [ + "invalid_token" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "$ref": "#/components/schemas/CoreAPIAuthenticatorsMainAuthenticatorInvalidToken" + } + } + }, + "CoreAPIAuthenticatorsMainAuthenticatorUnauthorizedNetworkForAPIToken": { + "type": "object", + "properties": { + "ip_address": { + "type": "string" + } + } + }, + "CoreAPIAuthenticatorsMainAuthenticatorUnauthorizedNetworkForAPITokenResponse": { + "type": "object", + "description": "Network is not allowed to access the API with this API token", + "properties": { + "code": { + "type": "string", + "enum": [ + "unauthorized_network_for_api_token" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "$ref": "#/components/schemas/CoreAPIAuthenticatorsMainAuthenticatorUnauthorizedNetworkForAPIToken" + } + } + }, + "CoreAPIErrorsRateLimitReached": { + "type": "object", + "properties": { + "total_permitted": { + "type": "integer" + } + } + }, + "CoreAPIArgumentSetsTimeLookupArgumentSet": { "description": "All 'time[]' params are mutually exclusive, only one can be provided.", "type": "object", "properties": { @@ -572,24 +707,24 @@ "unix" ] }, - "post_example_format_multiple_200_response_times": { + "PostExampleFormatMultiple200ResponseTimes": { "type": "object", "properties": { "unix": { "type": "integer" }, "year": { - "$ref": "#/components/schemas/post_example_format_multiple_200_response_times_year" + "$ref": "#/components/schemas/PostExampleFormatMultiple200ResponseTimesYear" }, "as_array_of_objects": { "type": "array", "items": { - "$ref": "#/components/schemas/post_example_format_multiple_200_response_times_as_array_of_objects" + "$ref": "#/components/schemas/PostExampleFormatMultiple200ResponseTimesAsArrayOfObjects" } } } }, - "post_example_format_multiple_200_response_times_year": { + "PostExampleFormatMultiple200ResponseTimesYear": { "type": "object", "properties": { "as_string": { @@ -597,7 +732,7 @@ } } }, - "post_example_format_multiple_200_response_times_as_array_of_objects": { + "PostExampleFormatMultiple200ResponseTimesAsArrayOfObjects": { "type": "object", "properties": { "as_integer": { @@ -605,23 +740,23 @@ } } }, - "CoreAPI_Objects_Time": { + "CoreAPIObjectsTime": { "type": "object", "properties": { "unix": { "type": "integer" }, "day_of_week": { - "$ref": "#/components/schemas/CoreAPI_Objects_Day" + "$ref": "#/components/schemas/CoreAPIObjectsDay" }, "full": { "type": "string" }, "year": { - "$ref": "#/components/schemas/CoreAPI_Objects_Year" + "$ref": "#/components/schemas/CoreAPIObjectsYear" }, "month": { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthPolymorph" + "$ref": "#/components/schemas/CoreAPIObjectsMonthPolymorph" }, "as_array": { "type": "array", @@ -632,7 +767,7 @@ "as_array_of_objects": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_Objects_Year" + "$ref": "#/components/schemas/CoreAPIObjectsYear" } }, "as_decimal": { @@ -643,7 +778,7 @@ } } }, - "CoreAPI_Objects_Day": { + "CoreAPIObjectsDay": { "type": "string", "enum": [ "Sunday", @@ -655,7 +790,7 @@ "Saturday" ] }, - "CoreAPI_Objects_Year": { + "CoreAPIObjectsYear": { "type": "object", "properties": { "as_integer": { @@ -673,27 +808,27 @@ "as_array_of_enums": { "type": "array", "items": { - "$ref": "#/components/schemas/CoreAPI_Objects_Day" + "$ref": "#/components/schemas/CoreAPIObjectsDay" } } } }, - "CoreAPI_Objects_MonthPolymorph": { + "CoreAPIObjectsMonthPolymorph": { "type": "object", "properties": { "month": { "oneOf": [ { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthLong" + "$ref": "#/components/schemas/CoreAPIObjectsMonthLong" }, { - "$ref": "#/components/schemas/CoreAPI_Objects_MonthShort" + "$ref": "#/components/schemas/CoreAPIObjectsMonthShort" } ] } } }, - "CoreAPI_Objects_MonthLong": { + "CoreAPIObjectsMonthLong": { "type": "object", "properties": { "number": { @@ -704,7 +839,7 @@ } } }, - "CoreAPI_Objects_MonthShort": { + "CoreAPIObjectsMonthShort": { "type": "object", "properties": { "number": { @@ -715,7 +850,7 @@ } } }, - "get_time_now_200_response_my_partial_polymorph": { + "GetTimeNow200ResponseMyPartialPolymorph": { "type": "object", "properties": { "number": { @@ -723,7 +858,7 @@ } } }, - "post_time_now_200_response_my_partial_polymorph": { + "PostTimeNow200ResponseMyPartialPolymorph": { "type": "object", "properties": { "number": { @@ -731,21 +866,21 @@ } } }, - "get_test_object_200_response_time": { + "GetTestObject200ResponseTime": { "type": "object", "properties": { "unix": { "type": "integer" }, "day_of_week": { - "$ref": "#/components/schemas/CoreAPI_Objects_Day" + "$ref": "#/components/schemas/CoreAPIObjectsDay" }, "year": { - "$ref": "#/components/schemas/get_test_object_200_response_time_year" + "$ref": "#/components/schemas/GetTestObject200ResponseTimeYear" } } }, - "get_test_object_200_response_time_year": { + "GetTestObject200ResponseTimeYear": { "type": "object", "properties": { "as_string": { @@ -753,7 +888,41 @@ } } }, - "CoreAPI_ArgumentSets_ObjectLookup": { + "CoreAPIEndpointsTestEndpointInvalidTestResponse": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "invalid_test" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "type": "object" + } + } + }, + "CoreAPIEndpointsTestEndpointAnotherInvalidTestResponse": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "another_invalid_test" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "type": "object" + } + } + }, + "CoreAPIArgumentSetsObjectLookup": { "description": "All 'object[]' params are mutually exclusive, only one can be provided.", "type": "object", "properties": { @@ -765,21 +934,21 @@ } } }, - "post_test_object_200_response_time": { + "PostTestObject200ResponseTime": { "type": "object", "properties": { "unix": { "type": "integer" }, "day_of_week": { - "$ref": "#/components/schemas/CoreAPI_Objects_Day" + "$ref": "#/components/schemas/CoreAPIObjectsDay" }, "year": { - "$ref": "#/components/schemas/post_test_object_200_response_time_year" + "$ref": "#/components/schemas/PostTestObject200ResponseTimeYear" } } }, - "post_test_object_200_response_time_year": { + "PostTestObject200ResponseTimeYear": { "type": "object", "properties": { "as_string": { @@ -788,8 +957,153 @@ } } }, + "responses": { + "CoreAPIArgumentSetsTimeLookupArgumentSetInvalidTimeResponse": { + "description": "400 error response", + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "type": "string", + "enum": [ + "invalid_time" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "type": "object" + } + } + } + } + } + }, + "APIAuthenticator403Response": { + "description": "403 error response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CoreAPIAuthenticatorsMainAuthenticatorInvalidTokenResponse" + }, + { + "$ref": "#/components/schemas/CoreAPIAuthenticatorsMainAuthenticatorUnauthorizedNetworkForAPITokenResponse" + } + ] + } + } + } + }, + "CoreAPIErrorsRateLimitReachedResponse": { + "description": "You have reached the rate limit for this type of request", + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "type": "string", + "enum": [ + "rate_limit_reached" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "$ref": "#/components/schemas/CoreAPIErrorsRateLimitReached" + } + } + } + } + } + }, + "CoreAPIAuthenticatorsTimeNowAuthenticatorWrongDayOfWeekResponse": { + "description": "You called this API on the wrong day of the week, try again tomorrow", + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "type": "string", + "enum": [ + "wrong_day_of_week" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "type": "object" + } + } + } + } + } + }, + "GetTestObject400Response": { + "description": "400 error response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CoreAPIEndpointsTestEndpointInvalidTestResponse" + }, + { + "$ref": "#/components/schemas/CoreAPIEndpointsTestEndpointAnotherInvalidTestResponse" + } + ] + } + } + } + }, + "CoreAPIArgumentSetsObjectLookupObjectNotFoundResponse": { + "description": "No object was found matching any of the criteria provided in the arguments", + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "type": "string", + "enum": [ + "object_not_found" + ] + }, + "description": { + "type": "string" + }, + "detail": { + "type": "object" + } + } + } + } + } + }, + "PostTestObject400Response": { + "description": "400 error response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CoreAPIEndpointsTestEndpointInvalidTestResponse" + }, + { + "$ref": "#/components/schemas/CoreAPIEndpointsTestEndpointAnotherInvalidTestResponse" + } + ] + } + } + } + } + }, "securitySchemes": { - "CoreAPI_MainAuthenticator": { + "CoreAPIAuthenticatorsMainAuthenticator": { "scheme": "bearer", "type": "http" } @@ -797,7 +1111,7 @@ }, "security": [ { - "CoreAPI_MainAuthenticator": [ + "CoreAPIAuthenticatorsMainAuthenticator": [ ] }