I’ve managed to create my own types, use my own registry, and get custom types mostly working with a schema. It took a bit of a code dive, but… it’s all good.
One of the issues I have in making a custom type work like a built-in type is when handling errors from the constructor.
Let’s look at one of the built-ins:
module Dry
module Types
module Coercions
module JSON
def self.to_decimal(input, &block)
if input.is_a?(::Float)
input.to_d
else
BigDecimal(input)
end
rescue ArgumentError, TypeError
if block_given?
yield
else
raise CoercionError, "#{input} cannot be coerced to decimal"
end
end
end
end
end
end
In looking at my own implementation and the built-ins, I see that all the built-ins coercion methods yield
to a block if there is a coercion failure or raise a Dry::Types::CoercionError
. Okay.
So if I try something like this: Dry::Types['json.decimal']['abc']
I get: Dry::Types::CoercionError (abc cannot be coerced to decimal)
Okay, cool. So then if I make a schema:
schema = Dry::Schema.JSON do
optional(:num).filled(:decimal)
end
schema.(num: 'abc') => #<Dry::Schema::Result{:num=>"abc"} errors={:num=>["must be a decimal"]}>
The block is passing in the error.
So then, I make a custom type:
Line = Types::String.constructor do |input, &block|
input.to_s.strip.tap do |str|
if ["\n", "\r"].any? { |c| input.include?(c) }
return yield if block_given?
raise Dry::Types::CoercionError, "Must be a single line."
end
end
end
schema = Dry::Schema.JSON do
optional(:name).filled(Line)
end
schema.(name: "Hello\nWorld") => Dry::Types::CoercionError (Must be a single line.)
block_given?
is false when I jump in there with a debugger. What do I have to do in order for the schema to pass a block with an error and not raise the exception? I feel like I’m missing generating an error message, but I figured I’d ask around here before I went any further down the rabbit hole on my own.
I know I can accomplish a lot of this stuff with macros with Dry-Validation. However, if I want to:
- Produce a custom Type that isn’t just a string (like a UUID object) this is problematic and I can’t get the behavior consistent with the built-in types.
- For the sake of convenience, the example above, would be such a common pattern that just setting the schema without having to write rules every single time I want to accomplish this is a lot easier on the eyes and fingers.