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

OpenAPI POC #17

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ gem 'solargraph'

gem 'rake'
gem 'rubocop'

gem 'pry'
3 changes: 3 additions & 0 deletions examples/config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ require 'apia'
require 'apia/rack'
require 'core_api/base'

require 'apia-openapi'

use Apia::OpenAPI::Rack, 'CoreAPI::Base', '/core/v1/schema/openapi.json', base_url: 'http://127.0.0.1:9292/core/v1/'
use Apia::Rack, CoreAPI::Base, '/core/v1', development: true

app = proc do
Expand Down
29 changes: 29 additions & 0 deletions examples/core_api/argument_sets/object_lookup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module CoreAPI
module ArgumentSets
class ObjectLookup < Apia::LookupArgumentSet

name 'Object Lookup'
description 'Provides for objects to be looked up'

argument :id, type: :string
argument :permalink, type: :string

potential_error 'ObjectNotFound' do
code :object_not_found
description 'No object was found matching any of the criteria provided in the arguments'
http_status 404
end

resolver do |set, request, scope|
objects = [{id: "123", permalink: "perma-123"}]
object = objects.find { |o| o[:id] == set[:id] || o[:permalink] == set[:permalink] }
raise_error 'ObjectNotFound' if object.nil?

object
end

end
end
end
6 changes: 6 additions & 0 deletions examples/core_api/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'core_api/main_authenticator'
require 'core_api/controllers/time_controller'
require 'core_api/endpoints/test_endpoint'

module CoreAPI
class Base < Apia::API
Expand All @@ -17,11 +18,16 @@ class Base < Apia::API

get 'example/format', controller: Controllers::TimeController, endpoint: :format
post 'example/format', controller: Controllers::TimeController, endpoint: :format
post 'example/format_multiple', controller: Controllers::TimeController, endpoint: :format_multiple

group :time do
name 'Time functions'
description 'Everything related to time elements'
get 'time/now', endpoint: Endpoints::TimeNowEndpoint
post 'time/now', endpoint: Endpoints::TimeNowEndpoint

get 'test/:object', endpoint: Endpoints::TestEndpoint
post 'test/:object', endpoint: Endpoints::TestEndpoint

group :formatting do
name 'Formatting'
Expand Down
20 changes: 19 additions & 1 deletion examples/core_api/controllers/time_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,38 @@ module Controllers
class TimeController < Apia::Controller

name 'Time API'
description 'Returns the current time in varying ways'
description 'Returns the time in varying ways'

endpoint :now, Endpoints::TimeNowEndpoint

# TODO: add example of multiple objects using the same objects, to ensure
# we are handing circular references correctly
endpoint :format do
description 'Format the given time'
argument :time, type: ArgumentSets::TimeLookupArgumentSet, required: true
argument :timezone, type: Objects::TimeZone
field :formatted_time, type: :string
action do
time = request.arguments[:time]
response.add_field :formatted_time, time.resolve.to_s
end
end

endpoint :format_multiple do
description 'Format the given times'
argument :times, type: [ArgumentSets::TimeLookupArgumentSet], required: true
field :formatted_times, type: [:string]
field :times, type: [Objects::Time], include: 'unix,year[as_string],as_array_of_objects[as_integer]'
action do
times = []
request.arguments[:times].each do |time|
times << time.resolve
end
response.add_field :formatted_times, times.map(&:to_s).join(", ")
response.add_field :times, times
end
end

end
end
end
35 changes: 35 additions & 0 deletions examples/core_api/endpoints/test_endpoint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require 'core_api/argument_sets/object_lookup'

module CoreAPI
module Endpoints
class TestEndpoint < Apia::Endpoint

description 'Returns the current time'
argument :object, type: ArgumentSets::ObjectLookup, 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'
end
end
field :object_id, type: :string do
backend { |o| o[:object_id][:id] }
end
scope 'time'

def call
object = request.arguments[:object].resolve
response.add_field :time, get_time_now
response.add_field :object_id, object
end

private

def get_time_now
Time.now
end

end
end
end
13 changes: 12 additions & 1 deletion examples/core_api/endpoints/time_now_endpoint.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# frozen_string_literal: true

require 'core_api/objects/time_zone'

module CoreAPI
module Endpoints
class TimeNowEndpoint < Apia::Endpoint

description 'Returns the current time'
field :time, type: Objects::Time, include: 'unix,day_of_week'
argument :timezone, type: Objects::TimeZone
argument :time_zones, [Objects::TimeZone]
argument :filters, [:string]
field :time, type: Objects::Time
field :time_zones, type: [Objects::TimeZone]
field :filters, [:string]
field :my_polymorph, type: Objects::MonthPolymorph
scope 'time'

def call
response.add_field :time, get_time_now
response.add_field :filters, request.arguments[:filters]
response.add_field :time_zones, request.arguments[:time_zones]
response.add_field :my_polymorph, get_time_now
end

private
Expand Down
8 changes: 4 additions & 4 deletions examples/core_api/main_authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ def call
cors.methods = %w[GET POST PUT PATCH DELETE OPTIONS]

# Define a list of cors headers that are permitted for the request.
cors.headers = %w[X-Custom-Header]
cors.headers = %w[Authorization Content-Type] # or allow all with '*'

# Define a the hostname to allow for CORS requests.
cors.origin = '*' # or 'example.com'
cors.origin = 'krystal.uk'

return if request.options?

given_token = request.headers['authorization']&.sub(/\ABearer /, '')
case given_token
when 'example'
if given_token == 'example'
request.identity = { name: 'Example User', id: 1234 }
else
raise_error 'CoreAPI/MainAuthenticator/InvalidToken', given_token: given_token.to_s
Expand Down
15 changes: 15 additions & 0 deletions examples/core_api/objects/month_long.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module CoreAPI
module Objects
class MonthLong < Apia::Object

description 'Represents a month'

field :month_long, type: :string do
backend { |t| t.strftime('%B') }
end

end
end
end
15 changes: 15 additions & 0 deletions examples/core_api/objects/month_polymorph.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'core_api/objects/month_long'
require 'core_api/objects/month_short'

module CoreAPI
module Objects
class MonthPolymorph < Apia::Polymorph

option 'MonthLong', type: CoreAPI::Objects::MonthLong, matcher: proc { |time| time.sec.even? }
option 'MonthShort', type: CoreAPI::Objects::MonthShort, matcher: proc { |time| time.sec.odd? }

end
end
end
15 changes: 15 additions & 0 deletions examples/core_api/objects/month_short.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module CoreAPI
module Objects
class MonthShort < Apia::Object

description 'Represents a month'

field :month_short, type: :string do
backend { |t| t.strftime('%b') }
end

end
end
end
34 changes: 28 additions & 6 deletions examples/core_api/objects/time.rb
Original file line number Diff line number Diff line change
@@ -1,29 +1,51 @@
# frozen_string_literal: true

require 'core_api/objects/day'
require 'core_api/objects/year'
require 'core_api/objects/month_polymorph'

module CoreAPI
module Objects
class Time < Apia::Object

description 'Represents a time'

field :unix, type: :integer do
backend(&:to_i)
field :unix, type: :unix_time do
backend { |t| t }
end

field :day_of_week, type: Objects::Day do
backend { |t| t.strftime('%A') }
end

field :month, type: :string do
backend { |t| t.strftime('%b') }
end

field :full, type: :string do
backend { |t| t.to_s }
end

field :year, type: Objects::Year do
backend { |t| t.year }
end

field :month, type: Objects::MonthPolymorph do
backend { |t| t }
end

field :as_array, type: [:integer] do
backend { |t| [t.year, t.month, t.day, t.hour, t.min, t.sec] }
end

field :as_array_of_objects, type: [Objects::Year] do
backend { |t| [t.year] }
end

field :as_decimal, type: :decimal do
backend { |t| t.to_f }
end

field :as_base64, type: :base64 do
backend { |t| Base64.encode64(t.to_s) }
end

end
end
end
13 changes: 13 additions & 0 deletions examples/core_api/objects/time_zone.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module CoreAPI
module Objects
class TimeZone < Apia::Enum

value 'Europe/London'
value 'Europe/Madrid'
value 'Asia/Singapore'

end
end
end
21 changes: 21 additions & 0 deletions examples/core_api/objects/year.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require 'core_api/objects/day'

module CoreAPI
module Objects
class Year < Apia::Object

description 'Represents a year'

field :as_integer, type: :integer do
backend(&:to_i)
end

field :as_string, type: :string do
backend(&:to_s)
end

end
end
end
1 change: 1 addition & 0 deletions lib/apia-openapi.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'apia-openapi/rack'
51 changes: 51 additions & 0 deletions lib/apia-openapi/rack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'apia-openapi/schema'

module Apia
module OpenAPI
class Rack

def initialize(app, api, path, **options)
@app = app
@api = api
@path = "/#{path.sub(/\A\/+/, '').sub(/\/+\z/, '')}"

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on a [library input](2) may run slow on strings with many repetitions of '/'.
@options = options
end

def development?
env_is_dev = ENV['RACK_ENV'] == 'development'
return true if env_is_dev && @options[:development].nil?

@options[:development] == true
end

def api
return Object.const_get(@api) if @api.is_a?(String) && development?
return @cached_api ||= Object.const_get(@api) if @api.is_a?(String)

@api
end

def base_url
@options[:base_url] || 'https://api.example.com/api/v1'
end

def call(env)
# if @options[:hosts]&.none? { |host| host == env['HTTP_HOST'] }
# return @app.call(env)
# end

unless env['PATH_INFO'] == @path
return @app.call(env)
end

schema = Schema.new(api, base_url)
body = schema.json

[200, { 'Content-Type' => 'application/json', 'Content-Length' => body.bytesize.to_s }, [body]]
end

end
end
end
Loading