I am processing some input from a web form of the shape “3 days” or “1 month”, etc. We use a third-party gem to do the conversion of the string to a duration as seconds. That gem returns nil
when it fails. We are wrapping the gem inside of a custom type.
How can I indicate to Dry-Types (and ultimately Dry-Validation) that a failure has occurred?
# frozen_string_literal: true
require 'dry-validation'
ONE_DAY = 24 * 60 * 60
TWO_DAY = 2 * ONE_DAY
# Represents some external code I cannot change
def duration_parsing_placeholder(v)
if v == "1 day"
ONE_DAY
elsif v == "2 days"
TWO_DAY
else
nil # indicates an error
end
end
module Types
include Dry.Types
end
Types::Duration = Types::Integer.constructor do |input|
if input == "" || input == "0"
# Want empty and zero strings to count as unsetting the value
nil
else
# If this returns nil, however, that's a failure to parse, and should be reported
duration_parsing_placeholder(input)
end
end
class MyContract < Dry::Validation::Contract
params do
required(:duration).maybe(Types::Duration)
end
rule(:duration) do
next unless value
key.failure "Duration must be less than 1 day" if value > ONE_DAY
end
end
# Correct
validation = MyContract.new.call(duration: '1 day')
raise "failed" unless validation.success?
raise "failed" unless validation[:duration] == 86400
# Correct
validation = MyContract.new.call(duration: '2 days')
raise "failed" unless validation.failure?
raise "failed" unless validation.errors[:duration] == ["Duration must be less than 1 day"]
# Correct
validation = MyContract.new.call(duration: '0')
raise "failed" unless validation.success?
raise "failed" unless validation.key? :duration
raise "failed" unless validation[:duration] == nil
# Correct
validation = MyContract.new.call(duration: '')
raise "failed" unless validation.success?
raise "failed" unless validation.key? :duration
raise "failed" unless validation[:duration] == nil
# Does not pass
validation = MyContract.new.call(duration: 'cows go moo')
raise "failed" unless validation.failure?
raise "failed" unless validation.errors[:duration] == ["Duration must be ..."]