hanami-controller/spec/support/fixtures.rb

1912 lines
40 KiB
Ruby

# frozen_string_literal: true
require "json"
require "digest/md5"
require "hanami/router"
require "hanami/middleware/body_parser"
require "hanami/utils/escape"
require_relative "./renderer"
require_relative "./validations"
HTTP_TEST_STATUSES_WITHOUT_BODY = Set.new((100..199).to_a << 204 << 304).freeze
HTTP_TEST_STATUSES = {
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
103 => "Early Hints",
200 => "OK",
201 => "Created",
202 => "Accepted",
203 => "Non-Authoritative Information",
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
207 => "Multi-Status",
208 => "Already Reported",
226 => "IM Used",
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Found",
303 => "See Other",
304 => "Not Modified",
305 => "Use Proxy",
306 => "(Unused)",
307 => "Temporary Redirect",
308 => "Permanent Redirect",
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
411 => "Length Required",
412 => "Precondition Failed",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
416 => "Range Not Satisfiable",
417 => "Expectation Failed",
421 => "Misdirected Request",
422 => "Unprocessable Entity",
423 => "Locked",
424 => "Failed Dependency",
425 => "Too Early",
426 => "Upgrade Required",
428 => "Precondition Required",
429 => "Too Many Requests",
431 => "Request Header Fields Too Large",
451 => "Unavailable for Legal Reasons",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported",
506 => "Variant Also Negotiates",
507 => "Insufficient Storage",
508 => "Loop Detected",
509 => "Bandwidth Limit Exceeded",
510 => "Not Extended",
511 => "Network Authentication Required"
}.freeze
class RecordNotFound < StandardError
end
module Test
class Index < Hanami::Action
def handle(req, res)
res[:xyz] = req.params[:name]
end
end
end
class CallAction < Hanami::Action
def handle(_req, res)
res.status = 201
res.body = "Hi from TestAction!"
res.headers.merge!("X-Custom" => "OK")
end
end
class UncheckedErrorCallAction < Hanami::Action
def handle(_req, _res)
raise
end
end
class ErrorCallAction < Hanami::Action
config.handle_exception RuntimeError => 500
def handle(_req, _res)
raise
end
end
class MyCustomError < StandardError; end
class ErrorCallFromInheritedErrorClass < Hanami::Action
config.handle_exception StandardError => :handler
def handle(*)
raise MyCustomError
end
private
def handler(_req, res, *)
res.status = 501
res.body = "An inherited exception occurred!"
end
end
class ErrorCallFromInheritedErrorClassStack < Hanami::Action
config.handle_exception StandardError => :standard_handler
config.handle_exception MyCustomError => :handler
def handle(*)
raise MyCustomError
end
private
def handler(_req, res, *)
res.status = 501
res.body = "MyCustomError was thrown"
end
def standard_handler(_req, res, *)
res.status = 501
res.body = "An unknown error was thrown"
end
end
class ErrorCallWithSymbolMethodNameAsHandlerAction < Hanami::Action
config.handle_exception StandardError => :handler
def handle(*)
raise StandardError
end
private
def handler(_req, res, *)
res.status = 501
res.body = "Please go away!"
end
end
class ErrorCallWithStringMethodNameAsHandlerAction < Hanami::Action
config.handle_exception StandardError => "standard_error_handler"
def handle(*)
raise StandardError
end
private
def standard_error_handler(_req, res, exception)
res.status = 502
res.body = exception.message
end
end
class ErrorCallWithUnsetStatusResponse < Hanami::Action
config.handle_exception ArgumentError => "arg_error_handler"
def handle(*)
raise ArgumentError
end
private
def arg_error_handler(*)
end
end
class ErrorCallWithSpecifiedStatusCodeAction < Hanami::Action
config.handle_exception StandardError => 422
def handle(_req, _res)
raise StandardError
end
end
class BeforeMethodAction < Hanami::Action
before :set_article, :reverse_article, :log_request
append_before :add_first_name_to_logger, :add_last_name_to_logger
prepend_before :add_title_to_logger
def handle(req, res)
end
private
def set_article(*, res)
res[:article] = "Bonjour!"
end
def reverse_article(*, res)
res[:article] = res[:article].reverse
end
def log_request(req, res)
res[:arguments] = []
res[:arguments] << req.class.name
res[:arguments] << res.class.name
end
def add_first_name_to_logger(*, res)
res[:logger] << "John"
end
def add_last_name_to_logger(*, res)
res[:logger] << "Doe"
end
def add_title_to_logger(*, res)
res[:logger] = []
res[:logger] << "Mr."
end
end
class SubclassBeforeMethodAction < BeforeMethodAction
before :upcase_article
private
def upcase_article
@article.upcase!
end
end
class ParamsBeforeMethodAction < BeforeMethodAction
private
def upcase_article
end
def set_article(req, res)
res[:exposed_params] = req.params
res[:article] = super(req, res) + req.params[:bang]
end
end
class ErrorBeforeMethodAction < BeforeMethodAction
private
def set_article
raise
end
end
class HandledErrorBeforeMethodAction < BeforeMethodAction
config.handle_exception RecordNotFound => 404
private
def set_article
raise RecordNotFound.new
end
end
class BeforeBlockAction < Hanami::Action
before { |_, res| res[:article] = "Good morning!" }
before { |_, res| res[:article] = res[:article].reverse }
before { |req, res| res[:arguments] = [req.class.name, res.class.name] }
def handle(req, res)
end
end
class YieldBeforeBlockAction < BeforeBlockAction
before { |req, res| res[:yielded_params] = req.params }
end
class AfterMethodAction < Hanami::Action
after :set_egg, :scramble_egg, :log_request
append_after :add_first_name_to_logger, :add_last_name_to_logger
prepend_after :add_title_to_logger
def handle(*)
end
private
def set_egg(*, res)
res[:egg] = "Egg!"
end
def scramble_egg(*, res)
res[:egg] = "gE!g"
end
def log_request(req, res)
res[:arguments] = []
res[:arguments] << req.class.name
res[:arguments] << res.class.name
end
def add_first_name_to_logger(*, res)
res[:logger] << "Jane"
end
def add_last_name_to_logger(*, res)
res[:logger] << "Dixit"
end
def add_title_to_logger(*, res)
res[:logger] = []
res[:logger] << "Mrs."
end
end
class AfterBlockAction < Hanami::Action
after { |_, res| res[:egg] = "Coque" }
after { |_, res| res[:egg] = res[:egg].reverse }
after { |req, res| res[:arguments] = [req.class.name, res.class.name] }
def handle(*)
end
end
class YieldAfterBlockAction < AfterBlockAction
after { |req, res| res[:meaning_of_life_params] = req.params }
def handle(*)
end
end
class MissingRequestSessionAction < Hanami::Action
def handle(req, _)
req.session[:user_id]
end
end
class MissingResponseSessionAction < Hanami::Action
def handle(_, res)
res.session[:user_id] = 23
end
end
class MissingResponseFlashAction < Hanami::Action
def handle(_, res)
res.flash[:error] = "ouch"
end
end
class SessionAction < Hanami::Action
include Hanami::Action::Session
def handle(req, res)
end
end
class FlashAction < Hanami::Action
include Hanami::Action::Session
def handle(*, res)
res.flash[:error] = "ouch"
end
end
class RedirectAction < Hanami::Action
def handle(*, res)
res.redirect_to "/destination"
end
end
class StatusRedirectAction < Hanami::Action
def handle(*, res)
res.redirect_to "/destination", status: 301
end
end
class SafeStringRedirectAction < Hanami::Action
def handle(*, res)
location = Hanami::Utils::Escape::SafeString.new("/destination")
res.redirect_to location
end
end
class GetCookiesAction < Hanami::Action
include Hanami::Action::Cookies
def handle(*, res)
res.body = res.cookies[:foo]
end
end
class ChangeCookiesAction < Hanami::Action
include Hanami::Action::Cookies
def handle(*, res)
res.body = res.cookies[:foo]
res.cookies[:foo] = "baz"
end
end
class GetDefaultCookiesAction < Hanami::Action
include Hanami::Action::Cookies
config.cookies = {domain: "hanamirb.org", path: "/controller", secure: true, httponly: true}
def handle(*, res)
res.body = ""
res.cookies[:bar] = "foo"
end
end
class GetOverwrittenCookiesAction < Hanami::Action
include Hanami::Action::Cookies
config.cookies = {domain: "hanamirb.org", path: "/controller", secure: true, httponly: true}
def handle(*, res)
res.body = ""
res.cookies[:bar] = {value: "foo", domain: "hanamirb.com", path: "/action", secure: false, httponly: false}
end
end
class GetAutomaticallyExpiresCookiesAction < Hanami::Action
include Hanami::Action::Cookies
def handle(*, res)
res.cookies[:bar] = {value: "foo", max_age: 120}
end
end
class SetCookiesAction < Hanami::Action
include Hanami::Action::Cookies
def handle(*, res)
res.body = "yo"
res.cookies[:foo] = "yum!"
end
end
class SetCookiesWithOptionsAction < Hanami::Action
include Hanami::Action::Cookies
def initialize(expires: Time.now.utc)
@expires = expires
super()
end
def handle(*, res)
res.cookies[:kukki] =
{value: "yum!", domain: "hanamirb.org", path: "/controller", expires: @expires, secure: true, httponly: true}
end
end
class RemoveCookiesAction < Hanami::Action
include Hanami::Action::Cookies
def handle(*, res)
res.cookies[:rm] = nil
end
end
class IterateCookiesAction < Hanami::Action
include Hanami::Action::Cookies
def handle(*, res)
result = []
res.cookies.each do |key, value|
result << "'#{key}' has value '#{value}'"
end
res.body = result.join(", ")
end
end
class ThrowCodeAction < Hanami::Action
def handle(req, *)
halt req.params[:status].to_i, req.params[:message]
end
end
class CatchAndThrowSymbolAction < Hanami::Action
def handle(_req, _res)
catch :done do
throw :done, 1
raise "This code shouldn't be reachable" # rubocop:disable Lint/UnreachableCode
end
end
end
class ThrowBeforeMethodAction < Hanami::Action
before :authorize!
before :set_body
def handle(_req, res)
res.body = "Hello!"
end
private
def authorize!
halt 401
end
def set_body
res.body = "Hi!"
end
end
class ThrowBeforeBlockAction < Hanami::Action
before { halt 401 }
before { res.body = "Hi!" }
def handle(_req, res)
res.body = "Hello!"
end
end
class ThrowAfterMethodAction < Hanami::Action
after :raise_timeout!
after :set_body
def handle(_req, res)
res.body = "Hello!"
end
private
def raise_timeout!
halt 408
end
def set_body
res.body = "Later!"
end
end
class ThrowAfterBlockAction < Hanami::Action
after { halt 408 }
after { res.body = "Later!" }
def handle(_req, res)
res.body = "Hello!"
end
end
class HandledExceptionAction < Hanami::Action
config.handle_exception RecordNotFound => 404
def handle(_req, _res)
raise RecordNotFound.new
end
end
class DomainLogicException < StandardError
end
class GlobalHandledExceptionAction < Hanami::Action
config.handle_exception DomainLogicException => 400
def handle(_req, _res)
raise DomainLogicException.new
end
end
class UnhandledExceptionAction < Hanami::Action
def handle(_req, _res)
raise RecordNotFound.new
end
end
class ParamsAction < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class WhitelistedParamsAction < Hanami::Action
class Params < Hanami::Action::Params
params do
if RSpec::Support::Validations.version?(2)
required(:id).maybe(:integer)
else
required(:id).maybe(:int?)
end
required(:article).schema do
required(:tags).each(:str?)
end
end
end
params Params
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class WhitelistedDslAction < Hanami::Action
params do
required(:username).filled
end
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class WhitelistedUploadDslAction < Hanami::Action
params do
if RSpec::Support::Validations.version?(2)
required(:id).maybe(:integer)
else
required(:id).maybe(:int?)
end
required(:upload).filled
end
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class ParamsValidationAction < Hanami::Action
params do
required(:email).filled(:str?)
end
def handle(req, *)
halt 400 unless req.params.valid?
end
end
class TestParams < Hanami::Action::Params
params do
required(:email).filled(format?: /\A.+@.+\z/)
if RSpec::Support::Validations.version?(2)
optional(:password).filled(:str?)
else
optional(:password).filled(:str?).confirmation
end
required(:name).filled
if RSpec::Support::Validations.version?(2)
required(:tos).value(:bool)
required(:age).value(:integer)
else
required(:tos).filled(:bool?)
required(:age).filled(:int?)
end
required(:address).schema do
required(:line_one).filled
required(:deep).schema do
required(:deep_attr).filled(:str?)
end
end
optional(:array).maybe do
each do
schema do
required(:name).filled(:str?)
end
end
end
end
end
class NestedParams < Hanami::Action::Params
params do
required(:signup).schema do
required(:name).filled(:str?)
required(:age).filled(:int?, gteq?: 18)
end
end
end
class Root < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
res.headers.merge!("X-Test" => "test")
end
end
module About
class Team < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
res.headers.merge!("X-Test" => "test")
end
end
class Contacts < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
end
module Identity
class Show < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class New < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Create < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Edit < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Update < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Destroy < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
end
module Flowers
class Index < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Show < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class New < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Create < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Edit < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Update < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
class Destroy < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
end
module Painters
class Update < Hanami::Action
params do
required(:painter).schema do
required(:first_name).filled(:str?)
required(:last_name).filled(:str?)
optional(:paintings).maybe do
each do
schema do
required(:name).filled
end
end
end
end
end
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
end
module Dashboard
class Index < Hanami::Action
include Hanami::Action::Session
before :authenticate!
def handle(*, res)
res.body = "User ID from session: #{res.session[:user_id]}"
end
private
def authenticate!(*, res)
halt 401 unless loggedin?(res)
end
def loggedin?(res)
res.session.key?(:user_id)
end
end
end
module Sessions
class Create < Hanami::Action
include Hanami::Action::Session
def handle(*, res)
res.session[:user_id] = 23
res.redirect_to "/"
end
end
class Destroy < Hanami::Action
include Hanami::Action::Session
def handle(*, res)
res.session[:user_id] = nil
end
end
class Disabled < Hanami::Action
def handle(*, res)
res.session[:user_id] = 23
end
end
end
class StandaloneSession < Hanami::Action
include Hanami::Action::Session
def handle(*, res)
res.session[:age] = Time.now.year - 1982
end
end
module Glued
class SendFile < Hanami::Action
def handle(_req, _res)
send_file "test.txt"
end
end
end
class ArtistNotFound < StandardError
end
module App
class CustomError < StandardError
end
class StandaloneAction < Hanami::Action
config.handle_exception App::CustomError => 400
def handle(_req, _res)
raise App::CustomError
end
end
end
module App2
class CustomError < StandardError
end
module Standalone
class Index < Hanami::Action
config.handle_exception App2::CustomError => 400
def handle(_req, _res)
raise App2::CustomError
end
end
end
end
module MusicPlayer
module Controllers
module Authentication
def self.included(action)
action.class_eval do
before do |_, res|
res[:current_user] = current_user
end
end
end
private
def current_user
"Luca"
end
end
class Dashboard
class Index < Hanami::Action
include Hanami::Action::Cookies
include Hanami::Action::Session
include MusicPlayer::Controllers::Authentication
def handle(_req, res)
res.body = "Muzic!"
res.headers["X-Frame-Options"] = "ALLOW FROM https://example.org"
end
end
class Show < Hanami::Action
include Hanami::Action::Cookies
include Hanami::Action::Session
include MusicPlayer::Controllers::Authentication
def handle(_req, _res)
raise ArgumentError
end
end
end
module Artists
class Index < Hanami::Action
include Hanami::Action::Cookies
include Hanami::Action::Session
include MusicPlayer::Controllers::Authentication
def handle(_req, res)
res.body = current_user
end
end
class Show < Hanami::Action
include Hanami::Action::Cookies
include Hanami::Action::Session
include MusicPlayer::Controllers::Authentication
config.handle_exception ArtistNotFound => 404
def handle(_req, _res)
raise ArtistNotFound
end
end
end
end
class StandaloneAction < Hanami::Action
include Hanami::Action::Cookies
include Hanami::Action::Session
include MusicPlayer::Controllers::Authentication
def handle(_req, _res)
raise ArgumentError
end
end
class Application
def initialize
Hanami::Action.configure do |config|
config.handle_exception ArgumentError => 400
config.default_headers(
"X-Frame-Options" => "DENY"
)
end
end
end
end
class VisibilityAction < Hanami::Action
include Hanami::Action::Cookies
include Hanami::Action::Session
def handle(*, res)
res.body = "x"
res.status = 201
res.format = :json
res.headers.merge!("X-Custom" => "OK", "Y-Custom" => "YO")
res.session[:foo] = "bar"
end
end
module SendFileTest
module Files
class Show < Hanami::Action
def handle(req, res)
id = req.params[:id]
# This if statement is only for testing purpose
case id
when "1"
res.send_file Pathname.new("test.txt")
when "2"
res.send_file Pathname.new("hanami.png")
when "3"
res.send_file Pathname.new("Gemfile")
when "100"
res.send_file Pathname.new("unknown.txt")
else
# a more realistic example of globbing ':id(.:format)'
resource = repository_dot_find_by_id(id)
# this is usually 406, but I want to distinguish it from the 406 below.
halt 400 unless resource
extension = req.params[:format]
case extension
when "html"
# in reality we'd render a template here, but as a test fixture, we'll simulate that answer
# we should have also checked #accept? but w/e
res.body = ::File.read(Pathname.new("spec/support/fixtures/#{resource.asset_path}.html"))
res.status = 200
res.format = :html
when "json", nil
res.format = :json
res.send_file Pathname.new("#{resource.asset_path}.json")
else
halt 406
end
end
end
private
Model = Struct.new(:id, :asset_path)
def repository_dot_find_by_id(id)
return nil unless id =~ /^\d+$/
Model.new(id.to_i, "resource-#{id}")
end
end
class UnsafeLocal < Hanami::Action
def handle(*, res)
res.unsafe_send_file "Gemfile"
end
end
class UnsafePublic < Hanami::Action
def handle(*, res)
res.unsafe_send_file "spec/support/fixtures/test.txt"
end
end
class UnsafeAbsolute < Hanami::Action
def handle(*, res)
res.unsafe_send_file Pathname.new("Gemfile").realpath
end
end
class UnsafeMissingLocal < Hanami::Action
def handle(*, res)
res.unsafe_send_file "missing"
end
end
class UnsafeMissingAbsolute < Hanami::Action
def handle(_req, res)
res.unsafe_send_file Pathname.new(".").join("missing")
end
end
class Flow < Hanami::Action
def handle(*, res)
res.send_file Pathname.new("test.txt")
res.redirect_to "/"
end
end
class Glob < Hanami::Action
def handle(*)
halt 202
end
end
class BeforeCallback < Hanami::Action
before :before_callback
def handle(*, res)
res.send_file Pathname.new("test.txt")
end
private
def before_callback(*, res)
res.headers["X-Callbacks"] = "before"
end
end
class AfterCallback < Hanami::Action
after :after_callback
def handle(*, res)
res.send_file Pathname.new("test.txt")
end
private
def after_callback(*, res)
res.headers["X-Callbacks"] = "after"
end
end
end
class Application
def initialize
Hanami::Action.configure do |config|
config.public_directory = "spec/support/fixtures"
end
router = Hanami::Router.new do
get "/files/flow", to: Files::Flow.new
get "/files/unsafe_local", to: Files::UnsafeLocal.new
get "/files/unsafe_public", to: Files::UnsafePublic.new
get "/files/unsafe_absolute", to: Files::UnsafeAbsolute.new
get "/files/unsafe_missing_local", to: Files::UnsafeMissingLocal.new
get "/files/unsafe_missing_absolute", to: Files::UnsafeMissingAbsolute.new
get "/files/before_callback", to: Files::BeforeCallback.new
get "/files/after_callback", to: Files::AfterCallback.new
get "/files/:id(.:format)", to: Files::Show.new
get "/files/(*glob)", to: Files::Glob.new
end
@app = Rack::Builder.new do
use Rack::Lint
run router
end.to_app
end
def call(env)
@app.call(env)
end
end
end
module HeadTest
module Home
class Index < Hanami::Action
include Hanami::Action::Session
def handle(_req, res)
res.body = "index"
end
end
class Code < Hanami::Action
include Hanami::Action::Cache
include Hanami::Action::Session
def handle(req, res)
content = "code"
res.headers.merge!(
"Allow" => "GET, HEAD",
"Content-Encoding" => "identity",
"Content-Language" => "en",
"Content-Length" => content.length,
"Content-Location" => "relativeURI",
"Content-MD5" => Digest::MD5.hexdigest(content),
"Expires" => "Thu, 01 Dec 1994 16:00:00 GMT",
"Last-Modified" => "Wed, 21 Jan 2015 11:32:10 GMT"
)
res.status = req.params[:code].to_i
res.body = "code"
end
end
class Override < Hanami::Action
include Hanami::Action::Session
def handle(_req, res)
res.headers.merge!(
"Last-Modified" => "Fri, 27 Nov 2015 13:32:36 GMT",
"X-Rate-Limit" => "4000",
"X-No-Pass" => "true"
)
res.status = 204
end
private
def keep_response_header?(header)
super || header == "X-Rate-Limit"
end
end
end
class Application
def initialize
Hanami::Action.configure do |config|
config.default_headers = {
"X-Frame-Options" => "DENY"
}
end
router = Hanami::Router.new do
get "/", to: Home::Index.new
get "/code/:code", to: Home::Code.new
get "/override", to: Home::Override.new
end
@app = Rack::Builder.new do
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run router
end.to_app
end
def call(env)
@app.call(env)
end
end
end
module FullStack
module Controllers
module Home
class Index < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*, res)
res[:greeting] = "Hello"
end
end
class Head < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*, res)
res.body = "foo"
end
end
end
module Books
class Index < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*)
end
end
class Create < Hanami::Action
include Hanami::Action::Session
include Inspector
params do
required(:title).filled(:str?)
end
def handle(req, res)
req.params.valid?
res.redirect_to "/books"
end
end
class Update < Hanami::Action
include Hanami::Action::Session
include Inspector
params do
if RSpec::Support::Validations.version?(2)
required(:id).value(:integer)
else
required(:id).value(:int?)
end
required(:book).schema do
required(:title).filled(:str?)
required(:author).schema do
required(:name).filled(:str?)
required(:favourite_colour)
end
end
end
def handle(req, res)
valid = req.params.valid?
res.status = 201
res.body = JSON.generate(
symbol_access: req.params[:book][:author] && req.params[:book][:author][:name],
valid: valid,
errors: req.params.errors.to_h
)
end
end
end
module Settings
class Index < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*)
end
end
class Create < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*, res)
res.flash[:message] = "Saved!"
res.redirect_to "/settings"
end
end
end
module Poll
class Start < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*, res)
res.redirect_to "/poll/1"
end
end
class Step1 < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(req, res)
if req.env["REQUEST_METHOD"] == "GET"
res.flash[:notice] = "Start the poll"
else
res.flash[:notice] = "Step 1 completed"
res.redirect_to "/poll/2"
end
end
end
class Step2 < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(req, res)
if req.env["REQUEST_METHOD"] == "POST"
res.flash[:notice] = "Poll completed"
res.redirect_to "/"
end
end
end
end
module Users
class Show < Hanami::Action
include Hanami::Action::Session
include Inspector
before :redirect_to_root
after :set_body
def handle(*, res)
res.body = "call method shouldn't be called"
end
private
def redirect_to_root(*, res)
res.redirect_to "/"
end
def set_body
res.body = "after callback shouldn't be called"
end
end
end
end
class Application
def initialize
routes = Hanami::Router.new do
get "/", to: FullStack::Controllers::Home::Index.new
get "/head", to: FullStack::Controllers::Home::Head.new
resources :books, only: %i[index create update]
get "/settings", to: FullStack::Controllers::Settings::Index.new
post "/settings", to: FullStack::Controllers::Settings::Create.new
get "/poll", to: FullStack::Controllers::Poll::Start.new
prefix "poll" do
get "/1", to: FullStack::Controllers::Poll::Step1.new
post "/1", to: FullStack::Controllers::Poll::Step1.new
get "/2", to: FullStack::Controllers::Poll::Step2.new
post "/2", to: FullStack::Controllers::Poll::Step2.new
end
prefix "users" do
get "/1", to: FullStack::Controllers::Users::Show.new
end
end
@renderer = Renderer.new
@app = Rack::Builder.new do
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run routes
end.to_app
end
def call(env)
@renderer.render(env, @app.call(env))
end
end
end
class MethodInspectionAction < Hanami::Action
def handle(req, res)
res.body = req.request_method
end
end
class RackExceptionAction < Hanami::Action
class TestException < ::StandardError
end
def handle(_req, _res)
raise TestException.new
end
end
class HandledRackExceptionAction < Hanami::Action
class TestException < ::StandardError
end
config.handle_exception TestException => 500
def handle(_req, _res)
raise TestException.new
end
end
class HandledRackExceptionSubclassAction < Hanami::Action
class TestException < ::StandardError
end
class TestSubclassException < TestException
end
config.handle_exception TestException => 500
def handle(_req, _res)
raise TestSubclassException.new
end
end
module SessionWithCookies
module Controllers
module Home
class Index < Hanami::Action
include Hanami::Action::Session
include Hanami::Action::Cookies
def handle(req, res)
end
end
end
end
class Application
def initialize
resolver = EndpointResolver.new(namespace: SessionWithCookies::Controllers)
routes = Hanami::Router.new(resolver: resolver) do
get "/", to: SessionWithCookies::Controllers::Home::Index.new
end
@renderer = Renderer.new
@app = Rack::Builder.new do
use Rack::Lint
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run routes
end.to_app
end
def call(env)
@renderer.render(env, @app.call(env))
end
end
end
module SessionsWithoutCookies
module Controllers
module Home
class Index < Hanami::Action
include Hanami::Action::Session
include Inspector
def handle(*)
end
end
end
end
class Application
def initialize
routes = Hanami::Router.new do
get "/", to: SessionsWithoutCookies::Controllers::Home::Index.new
end
@renderer = Renderer.new
@app = Rack::Builder.new do
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run routes
end.to_app
end
def call(env)
@renderer.render(env, @app.call(env))
end
end
end
module Mimes
class Default < Hanami::Action
def handle(_req, res)
res.body = res.format
end
end
class Custom < Hanami::Action
def handle(_req, res)
res.format = :xml
res.body = res.format
end
end
class Latin < Hanami::Action
def handle(_req, res)
res.charset = "latin1"
res.format = :html
res.body = res.format
end
end
class Accept < Hanami::Action
def handle(req, res)
res.headers["X-AcceptDefault"] = req.accept?("application/octet-stream").to_s
res.headers["X-AcceptHtml"] = req.accept?("text/html").to_s
res.headers["X-AcceptXml"] = req.accept?("application/xml").to_s
res.headers["X-AcceptJson"] = req.accept?("text/json").to_s
res.body = res.format
end
end
class CustomFromAccept < Hanami::Action
config.format custom: "application/custom"
accept :json, :custom
def handle(*, res)
res.body = res.format
end
end
class Restricted < Hanami::Action
config.format custom: "application/custom"
accept :html, :json, :custom
def handle(_req, res)
res.body = res.format
end
end
class NoContent < Hanami::Action
def handle(_req, res)
res.status = 204
end
end
class OverrideDefaultResponse < Hanami::Action
def handle(*, res)
res.format = :xml
end
private
def default_response_format
:json
end
end
class Strict < Hanami::Action
accept :json
def handle(_req, res)
res.body = res.format
end
end
class Application
def initialize
@router = Hanami::Router.new do
get "/", to: Mimes::Default.new
get "/custom", to: Mimes::Custom.new
get "/accept", to: Mimes::Accept.new
get "/restricted", to: Mimes::Restricted.new
get "/latin", to: Mimes::Latin.new
get "/nocontent", to: Mimes::NoContent.new
get "/overwritten_format", to: Mimes::OverrideDefaultResponse.new
get "/custom_from_accept", to: Mimes::CustomFromAccept.new
get "/strict", to: Mimes::Strict.new
end
end
def call(env)
@router.call(env)
end
end
end
module MimesWithDefault
class Default < Hanami::Action
config.default_response_format = :html
accept :json
def handle(*, res)
res.body = res.format
end
end
class Application
def initialize
@router = Hanami::Router.new do
get "/default_and_accept", to: MimesWithDefault::Default.new
end
end
def call(env)
@router.call(env)
end
end
end
module RouterIntegration
class Application
def initialize
routes = Hanami::Router.new do
get "/", to: Root.new
get "/team", to: About::Team.new
get "/contacts", to: About::Contacts.new
resource :identity
resources :flowers
resources :painters, only: [:update]
end
@app = Rack::Builder.new do
use Rack::Lint
use Hanami::Middleware::BodyParser, :json
run routes
end.to_app
end
def call(env)
@app.call(env)
end
end
end
module SessionIntegration
class Application
def initialize
resolver = EndpointResolver.new
routes = Hanami::Router.new(resolver: resolver) do
get "/", to: Dashboard::Index.new
post "/login", to: Session::Create.new
delete "/logout", to: Sessions::Destroy.new
end
@app = Rack::Builder.new do
use Rack::Lint
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run routes
end.to_app
end
def call(env)
@app.call(env)
end
end
end
module StandaloneSessionIntegration
class Application
def initialize
@app = Rack::Builder.new do
use Rack::Lint
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run StandaloneSession.new
end
end
def call(env)
@app.call(env)
end
end
end
module Flash
module Controllers
module Home
class Index < Hanami::Action
include Hanami::Action::Session
def handle(req, res)
res.flash[:hello] = "world"
if req.env["REQUEST_METHOD"] == "GET"
res.redirect_to "/books"
else
res.redirect_to "/print"
end
end
end
class Books < Hanami::Action
include Hanami::Action::Session
def handle(_, res)
res.body = "flash_empty: #{res.flash.empty?} flash: #{res.flash.inspect}"
end
end
class Print < Hanami::Action
include Hanami::Action::Session
def handle(_, res)
res.body = res.flash[:hello]
end
end
class EachRedirect < Hanami::Action
include Hanami::Action::Session
def handle(_, res)
res.flash[:hello] = "world"
res.redirect_to "/each"
end
end
class Each < Hanami::Action
include Hanami::Action::Session
def handle(_, res)
each_result = []
res.flash.each { |type, message| each_result << [type, message] }
res.body = "flash_each: #{each_result}"
end
end
class MapRedirect < Hanami::Action
include Hanami::Action::Session
def handle(_, res)
res.flash[:hello] = "world"
res.redirect_to "/map"
end
end
class Map < Hanami::Action
include Hanami::Action::Session
def handle(_, res)
res.body = "flash_map: #{res.flash.map { |type, message| [type, message] }}"
end
end
class Disabled < Hanami::Action
def handle(_, res)
res.flash[:error] = "ouch"
end
end
end
end
class Application
def initialize
routes = Hanami::Router.new do
get "/", to: Flash::Controllers::Home::Index.new
post "/", to: Flash::Controllers::Home::Index.new
get "/print", to: Flash::Controllers::Home::Print.new
get "/books", to: Flash::Controllers::Home::Books.new
get "/map_redirect", to: Flash::Controllers::Home::MapRedirect.new
get "/each_redirect", to: Flash::Controllers::Home::EachRedirect.new
get "/map", to: Flash::Controllers::Home::Map.new
get "/each", to: Flash::Controllers::Home::Each.new
get "/disabled", to: Flash::Controllers::Home::Disabled.new
end
@middleware = Rack::Builder.new do
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
run routes
end
end
def call(env)
@middleware.call(env)
end
end
end
module Inheritance
class Action < Hanami::Action
before :log_base_action
private
def log_base_action(*, res)
res[:base_action] = true
end
end
class AuthenticatedAction < Action
before :authenticate!
private
def authenticate!(*, res)
res[:authenticated] = true
end
end
module Controllers
module Books
class RestfulAction < AuthenticatedAction
before :find_book
after :render
private
def find_book(req, res)
res[:book] = "book #{req.params[:id]}"
end
def render(*, res)
res.body = res.exposures.keys
end
end
class Show < RestfulAction
def handle(*, res)
res[:found] = true
end
end
class Destroy < Show
def handle(*, res)
super
res[:destroyed] = true
end
end
end
end
class Application
def initialize
@routes = Hanami::Router.new do
resources :books, only: %i[show destroy]
end
end
def call(env)
@routes.call(env)
end
end
end