I wanted to have a custom type in my application for IP addresses - IPv4 and IPv6 types.

My first idea was build the custom type based on top of Dry::Types::Strict::String.constrained  Having a quick look at the constrained type, I realised that its dry-logic that dictates the predicate logic. Another quick search over the Github issues, and I see that I am not the only one who wanted something similar. I found a similar issue that I was trying to solve.

Solution

use the case? predicate.

From the Github issue:


Anything that responds to === can be used, including strings, numbers, classes, ranges, regexes, and even procs.

So the case? predicate responds to ===, You can pass a pattern and the predicate will check if the pattern matches your input, just what I wanted.

module Types
  include Dry.Types

  ipv4 = ->(input) { IpValidator.valid_ipv4?(input) }
  IPv4 = Types::String.constrained(case: ipv4)

  ipv6 = ->(input) { IpValidator.valid_ipv6?(input) }
  IPv6 = Types::String.constrained(case: ipv6)
end

In the above snipped, I have an ipv4 and ipv6 lambda which takes an input and checks whether its a valid ipv4 or ipv6 using IpValidator class, which is a wrapper to check the IP address.

With these 2 lambdas, I can now build my custom types by passing them to the case? predicate which will check if the input and the pattern matches.

Simple usage:

Types::IPv4.call('127.0.0.1')
Types::IPv6.call('2001:0db8:85a3:0000:0000:8a2e:0370:7334')

Hope that helps someone. Enjoy.!