Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose error responses in the spec output #25

Merged
merged 1 commit into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ gem "rspec", "~> 3.0"
gem "rubocop", "~> 1.21"

group :test do
gem "pry"
gem "simplecov", require: false
end
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -75,6 +80,7 @@ PLATFORMS
DEPENDENCIES
apia (~> 3.5)
apia-open_api!
pry
rake (~> 13.0)
rspec (~> 3.0)
rubocop (~> 1.21)
Expand Down
57 changes: 57 additions & 0 deletions examples/core_api/authenticators/main_authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module CoreAPI
module Authenticators
class MainAuthenticator < Apia::Authenticator
Copy link
Contributor Author

@paulsturgess paulsturgess Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is the same as before, i just decided to create an /authenticators directory and move it there, seeing as i wanted to add some more.


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
13 changes: 13 additions & 0 deletions examples/core_api/authenticators/time_controller_authenticator.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions examples/core_api/authenticators/time_now_authenticator.rb
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions examples/core_api/base.rb
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
3 changes: 3 additions & 0 deletions examples/core_api/controllers/time_controller.rb
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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
Expand Down
13 changes: 13 additions & 0 deletions examples/core_api/endpoints/test_endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions examples/core_api/endpoints/time_now_endpoint.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# frozen_string_literal: true

require "core_api/authenticators/time_now_authenticator"
require "core_api/objects/time_zone"

module CoreAPI
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
Expand Down
17 changes: 17 additions & 0 deletions examples/core_api/errors/rate_limit_reached.rb
Original file line number Diff line number Diff line change
@@ -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
45 changes: 0 additions & 45 deletions examples/core_api/main_authenticator.rb

This file was deleted.

18 changes: 14 additions & 4 deletions lib/apia/open_api/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Comment on lines +22 to +26
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the schema is blank, then we don't need a $ref for it. This can happen with potential errors when they have no additional fields and thus the detail property is actually just an empty object.

end

def convert_type_to_open_api_data_type(type)
Expand All @@ -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(/\//, "")
Comment on lines -44 to +54
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to get rid of the underscores from our ids that we use for component schemas. I thought it looked odd to have camelcase and underscores together. There is no strict format required for these in the spec, but this follows the naming pattern used in the examples.

end

def formatted_description(description)
Expand Down
2 changes: 1 addition & 1 deletion lib/apia/open_api/objects.rb
Original file line number Diff line number Diff line change
@@ -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|
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make rubocop happy

require_relative file
end

Expand Down
11 changes: 9 additions & 2 deletions lib/apia/open_api/objects/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
Loading