Skip to content

Commit

Permalink
Use a simpler approach to add contract support
Browse files Browse the repository at this point in the history
  • Loading branch information
timriley committed Aug 27, 2024
1 parent 92295f3 commit 4b90cb9
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/hanami/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ def self.params(_klass = nil)
"To use `params`, please add 'hanami/validations' gem to your Gemfile"
end

def self.contract
raise NoMethodError,
"To use `contract`, please add 'hanami/validations' gem to your Gemfile"
end

# @overload self.append_before(*callbacks, &block)
# Define a callback for an Action.
# The callback will be executed **before** the action is called, in the
Expand Down
10 changes: 10 additions & 0 deletions lib/hanami/action/validatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ def params(klass = nil, &blk)

@params_class = klass
end

def contract(&block)
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))

klass.class_eval do
@_validator = Class.new(Dry::Validation::Contract, &block).new
end

@params_class = klass
end
end
end
end
Expand Down
46 changes: 46 additions & 0 deletions spec/support/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1902,3 +1902,49 @@ def call(env)
end
end
end

class ContractAction < Hanami::Action
contract do
params do
required(:birth_date).filled(:date)
required(:book).schema do
required(:title).filled(:str?)
end
end

rule(:birth_date) do
key.failure("you must be 18 years or older") if value < Date.today << (12 * 18)
end
end

def handle(request, response)
if request.params.valid?
response.status = 201
response.body = JSON.generate(
new_name: request.params[:book][:title].upcase
)
else
response.body = {errors: request.params.errors.to_h}
response.status = 302
end
end
end

class WhitelistedUploadDslContractAction < Hanami::Action
contract do
params do
required(:id).maybe(:integer)
required(:upload).filled
end
end

def handle(req, res)
res.body = req.params.to_h.inspect
end
end

class RawContractAction < Hanami::Action
def handle(req, res)
res.body = req.params.to_h.inspect
end
end
65 changes: 65 additions & 0 deletions spec/unit/hanami/action/contract_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

require "rack"

RSpec.describe "Contracts" do
describe "when defined as block in action" do
let(:action) { ContractAction.new }

context "when it has errors" do
it "returns them" do
response = action.call("birth_date" => "2000-01-01")

expect(response.status).to eq 302
expect(response.body).to eq ["{:errors=>{:book=>[\"is missing\"], :birth_date=>[\"you must be 18 years or older\"]}}"]
end
end

context "when it is valid" do
it "works" do
response = action.call("birth_date" => Date.today - (365 * 15), "book" => {"title" => "Hanami"})

expect(response.status).to eq 201
expect(response.body).to eq ["{\"new_name\":\"HANAMI\"}"]
end
end
end

describe "#raw" do
context "when this feature isn't enabled" do
let(:action) { RawContractAction.new }

it "raw gets all params" do
File.open("spec/support/fixtures/multipart-upload.png", "rb") do |upload|
response = action.call("id" => "1", "unknown" => "2", "upload" => upload)

expect(response[:params][:id]).to eq("1")
expect(response[:params][:unknown]).to eq("2")
expect(FileUtils.cmp(response[:params][:upload], upload)).to be(true)

expect(response[:params].raw.fetch("id")).to eq("1")
expect(response[:params].raw.fetch("unknown")).to eq("2")
expect(response[:params].raw.fetch("upload")).to eq(upload)
end
end
end

context "when this feature is enabled" do
let(:action) { WhitelistedUploadDslContractAction.new }

it "raw gets all params" do
Tempfile.create("multipart-upload") do |upload|
response = action.call("id" => "1", "unknown" => "2", "upload" => upload, "_csrf_token" => "3")

expect(response[:params][:id]).to eq(1)
expect(response[:params][:unknown]).to be(nil)
expect(response[:params][:upload]).to eq(upload)

expect(response[:params].raw.fetch("id")).to eq("1")
expect(response[:params].raw.fetch("unknown")).to eq("2")
expect(response[:params].raw.fetch("upload")).to eq(upload)
end
end
end
end
end

0 comments on commit 4b90cb9

Please sign in to comment.