The idea is separation type checks from domain-level checks.
Type checks can also sometimes benefit from more meaningful messages. For example, I am trying to parse a HTTP GET parameter from string into a Date
. I want to support YYYY-MM-DD
format and nothing else, so a naive implementation would do something like Date.strptime(value, '%Y-%m-%d')
.
Following the doc, I have come up with something like this:
my_schema = Dry::Schema.Params do
date_from_string = Dry::Types['date'].constructor do |input|
Date.strptime(input, '%Y-%m-%d')
end
optional(:my_date).filter(:str?, :filled?).maybe(date_from_string)
end
And the results are as follows:
my_schema.call({my_date: '2020-10-12'})
# => #<Dry::Schema::Result{:my_date=>Mon, 12 Oct 2020} errors={}>
my_schema.call({my_date: nil})
# => #<Dry::Schema::Result{:my_date=>nil} errors={:my_date=>["must be a string"]}>
my_schema.call({my_date: ''})
# => #<Dry::Schema::Result{:my_date=>""} errors={:my_date=>["must be filled"]}>
my_schema.call({my_date: '2020/10/11'})
=> #<Dry::Schema::Result{:my_date=>"2020/10/11"} errors={:my_date=>["must be a date"]}>
Note how in the last example the string represents a date in some locales, but in an ambiguous manner, i.e. in the US it would mean Nov 10th and in Italy it would mean Oct 11th. Our API refuses to guess, and wants the dates only in the ISO format, but the users from US or Italy may be confused: why is 2020/10/11 not a date when it obviously is a date?
Ideally, the API would return an error saying “date must be in YYYY-MM-DD format”, but I see no way to do that with “just a predicate” approach. I could add a format-based validator (smth like /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/
) as a filter, but it would still return a super generic is in invalid format
message.
How would you suggest getting the desired output using dry-schema?