Switch to Roda

A first stab at replacing Sinatra with Roda
This commit is contained in:
Hakan Ensari 2020-05-08 14:50:09 +01:00
parent 535471509b
commit 82666af2d6
7 changed files with 81 additions and 96 deletions

View File

@ -1,7 +1,7 @@
Metrics/AbcSize: Metrics/AbcSize:
Max: 21.79 Max: 21.79
Metrics/BlockLength: Metrics/BlockLength:
ExcludedMethods: ['describe', 'helpers'] ExcludedMethods: ['describe', 'route']
Metrics/MethodLength: Metrics/MethodLength:
Max: 13 Max: 13
Minitest: Minitest:

View File

@ -7,11 +7,12 @@ ruby '2.7.1'
gem 'money' gem 'money'
gem 'oj' gem 'oj'
gem 'ox' gem 'ox'
gem 'rack-contrib'
gem 'rack-cors' gem 'rack-cors'
gem 'rake' gem 'rake'
gem 'roda'
gem 'rufus-scheduler' gem 'rufus-scheduler'
gem 'sequel_pg' gem 'sequel_pg'
gem 'sinatra'
gem 'unicorn' gem 'unicorn'
group :test do group :test do

View File

@ -25,8 +25,6 @@ GEM
minitest (>= 4, < 6) minitest (>= 4, < 6)
money (6.13.7) money (6.13.7)
i18n (>= 0.6.4, <= 2) i18n (>= 0.6.4, <= 2)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
oj (3.10.6) oj (3.10.6)
ox (2.13.2) ox (2.13.2)
parallel (1.19.1) parallel (1.19.1)
@ -36,16 +34,18 @@ GEM
public_suffix (4.0.4) public_suffix (4.0.4)
raabro (1.1.6) raabro (1.1.6)
rack (2.2.2) rack (2.2.2)
rack-contrib (2.2.0)
rack (~> 2.0)
rack-cors (1.1.1) rack-cors (1.1.1)
rack (>= 2.0.0) rack (>= 2.0.0)
rack-protection (2.0.8.1)
rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rainbow (3.0.0) rainbow (3.0.0)
raindrops (0.19.1) raindrops (0.19.1)
rake (13.0.1) rake (13.0.1)
rexml (3.2.4) rexml (3.2.4)
roda (3.31.0)
rack
rubocop (0.82.0) rubocop (0.82.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
@ -61,7 +61,6 @@ GEM
rubocop-sequel (0.0.6) rubocop-sequel (0.0.6)
rubocop (~> 0.55, >= 0.55) rubocop (~> 0.55, >= 0.55)
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
ruby2_keywords (0.0.2)
rufus-scheduler (3.6.0) rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6) fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.5) safe_yaml (1.0.5)
@ -73,12 +72,6 @@ GEM
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov-html (0.12.2) simplecov-html (0.12.2)
sinatra (2.0.8.1)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.8.1)
tilt (~> 2.0)
tilt (2.0.10)
tzinfo (2.0.2) tzinfo (2.0.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (1.7.0) unicode-display_width (1.7.0)
@ -101,16 +94,17 @@ DEPENDENCIES
money money
oj oj
ox ox
rack-contrib
rack-cors rack-cors
rack-test rack-test
rake rake
roda
rubocop-minitest rubocop-minitest
rubocop-performance rubocop-performance
rubocop-sequel rubocop-sequel
rufus-scheduler rufus-scheduler
sequel_pg sequel_pg
simplecov simplecov
sinatra
unicorn unicorn
vcr vcr
webmock webmock

View File

@ -3,4 +3,4 @@
require './config/environment' require './config/environment'
require 'web/server' require 'web/server'
run Sinatra::Application run Web::Server.freeze.app

View File

@ -1,103 +1,93 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'oj' require 'oj'
require 'rack/contrib/jsonp'
require 'rack/cors' require 'rack/cors'
require 'sinatra' require 'roda'
require 'currency_names' require 'currency_names'
require 'query' require 'query'
require 'quote' require 'quote'
use Rack::Cors do module Web
class Server < Roda
use Rack::Cors do
allow do allow do
origins '*' origins '*'
resource '*', headers: :any, methods: :get resource '*', headers: :any, methods: %i[get options]
end end
end end
use Rack::JSONP
configure :development do plugin :caching
set :show_exceptions, :after_handler
end
configure :production do plugin :error_handler do |_error|
disable :dump_errors request.halt [422, {}, nil]
end end
configure :test do plugin :indifferent_params
set :raise_errors, false
end
set :static_cache_control, [:public, max_age: 300] plugin :json, content_type: 'application/json; charset=utf-8',
serializer: ->(o) { Oj.dump(o, mode: :compat) }
helpers do plugin :params_capturing
def end_of_day_quote
@end_of_day_quote ||= begin route do |r|
query = Query.build(params) r.root do
{ docs: 'https://www.frankfurter.app/docs' }
end
r.is(/latest|current/) do
r.params['date'] = Date.today.to_s
quote = quote_end_of_day(r)
r.etag quote.cache_key
quote.formatted
end
r.is(/(\d{4}-\d{2}-\d{2})/) do
r.params['date'] = r.params['captures'].first
quote = quote_end_of_day(r)
r.etag quote.cache_key
quote.formatted
end
r.is(/(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2})?/) do
r.params['start_date'] = r.params['captures'].first
r.params['end_date'] = r.params['captures'][1] || Date.today.to_s
quote = quote_interval(r)
r.etag quote.cache_key
quote.formatted
end
r.is 'currencies' do
currency_names = CurrencyNames.new
r.etag currency_names.cache_key
currency_names.formatted
end
end
private
def quote_end_of_day(request)
query = Query.build(request.params)
quote = Quote::EndOfDay.new(**query) quote = Quote::EndOfDay.new(**query)
quote.perform quote.perform
halt 404 if quote.not_found? request.halt [404, {}, nil] if quote.not_found?
quote quote
end end
end
def interval_quote def quote_interval(request)
@interval_quote ||= begin query = Query.build(request.params)
query = Query.build(params)
quote = Quote::Interval.new(**query) quote = Quote::Interval.new(**query)
quote.perform quote.perform
halt 404 if quote.not_found? request.halt [404, {}, nil] if quote.not_found?
quote quote
end end
end end
def json(data)
json = Oj.dump(data, mode: :compat)
callback = params['callback']
if callback
content_type :js, charset: Encoding::UTF_8
"#{callback}(#{json})"
else
content_type :json, charset: Encoding::UTF_8
json
end
end
end
get '/' do
json({ docs: 'https://www.frankfurter.app/docs' })
end
get '/(?:latest|current)', mustermann_opts: { type: :regexp } do
params[:date] = Date.today.to_s
etag end_of_day_quote.cache_key
json end_of_day_quote.formatted
end
get '/(?<date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
etag end_of_day_quote.cache_key
json end_of_day_quote.formatted
end
get '/(?<start_date>\d{4}-\d{2}-\d{2})\.\.(?<end_date>\d{4}-\d{2}-\d{2})?',
mustermann_opts: { type: :regexp } do
@params[:end_date] ||= Date.today.to_s
etag interval_quote.cache_key
json interval_quote.formatted
end
get '/currencies' do
currency_names = CurrencyNames.new
etag currency_names.cache_key
json currency_names.formatted
end
not_found do
halt 404
end
error do
halt 422
end end

View File

@ -7,7 +7,7 @@ require 'web/server'
describe 'the server' do describe 'the server' do
include Rack::Test::Methods include Rack::Test::Methods
let(:app) { Sinatra::Application } let(:app) { Web::Server.freeze }
def json def json
Oj.load(last_response.body) Oj.load(last_response.body)

View File

@ -7,7 +7,7 @@ require 'web/server'
describe 'the server' do describe 'the server' do
include Rack::Test::Methods include Rack::Test::Methods
let(:app) { Sinatra::Application } let(:app) { Web::Server.freeze }
let(:json) { Oj.load(last_response.body) } let(:json) { Oj.load(last_response.body) }
let(:headers) { last_response.headers } let(:headers) { last_response.headers }
@ -100,11 +100,11 @@ describe 'the server' do
it 'handles JSONP' do it 'handles JSONP' do
get '/latest?callback=foo' get '/latest?callback=foo'
_(last_response.body).must_be :start_with?, 'foo' _(last_response.body).must_be :start_with?, '/**/foo'
end end
it 'sets charset to UTF-8' do it 'sets charset to utf-8' do
get '/currencies' get '/currencies'
_(last_response.headers['content-type']).must_be :end_with?, 'charset=UTF-8' _(last_response.headers['content-type']).must_be :end_with?, 'charset=utf-8'
end end
end end