Custom dry type with multiple constraints

Hi :slight_smile:

I was just wondering the best way to define a custom dry type with some constraints, and then use it in a dry contract?

For example, let’s say we wanted a custom Time type which was constructed from a string. This type would fail to be created if it didn’t meet the following constraints:

  1. The input string must be in an ISO8601 format like "2019-01-11T01:12:34Z". It could also optionally include milliseconds For example, "2019-01-11T01:12:34.567Z".
  2. The date time must be in UTC. For example, "2019-01-11T01:12:34+11:00" would fail.

Just looking at the documentation, I was unsure how to implement this. If we follow an example from the custom types constructor docs, I’m not sure exactly how to return meaningful errors from a custom constructor. There would be errors unique to each specific constraint, for example if the input string was in a valid format, but, was not in UTC.

Some example code to demonstrate my problem.

CustomTime = Types.Constructor(Types::Strict::Time) do |value|
  result = construct(value.to_s)
  if value.success?
    result.value!
  else
    # What should be done here?
    # Can I throw some specific DRY exception?
    # If i just return nil, I lose details of the error.
  end
end

FORMAT_WITH_MS = '%Y-%m-%dT%H:%M:%S.%L%z'.freeze
FORMAT_WITHOUT_MS = '%Y-%m-%dT%H:%M:%S%z'.freeze

def construct(input)
  parse(input).bind do |time|
    if utc?(time)
      Success(time)
    else
      Failure('date time must be UTC')
    end
  end
end

def parse(input)
  Success(Time.strptime(input, FORMAT_WITH_MS))
rescue ArgumentError
  begin
    Success(Time.strptime(input, FORMAT_WITHOUT_MS))
  rescue ArgumentError
    Failure('date time must be in correct format')
  end
end

def utc?(time)
  time.zone == 'UTC'
end

If the CustomTime type was used in a dry contract to validate some HTTP request payload which contained an invalid date time, I’d like for the specific time related error to be returned in the result from calling the contract.

Please let me know If I’m going about this completely wrong. I’m new to the dry gems, so would love the opinion of a contributor or experienced user on the best way forward in a situation like this.

Thanks!

I’d recommend using contract rules for that. Such complex types can be used for coercion purposes but not for validation error messages.