Thy Build Status

Thy is a minimal, strict runtime type system for Ruby.

Motivation

There are many existing runtime type systems for Ruby, most notably, dry-types. Here's why you might prefer Thy:

  • Thy encourages strictness. Avoiding type coercion as much as possible is an excellent way to keep your code predictable and straightforward.
  • Minimalism. Thy's API only has what's needed, nothing more.
  • No Any type. Don't worry, if you have to, you can get it!

Installation

Add this line to your application's Gemfile:

gem 'thy'

And then execute:

$ bundle

Or install it yourself as:

$ gem install thy

Usage

It's recommended not to use Thy directly (although you can), but to create a module for working with types, and to include Thy::Types into it.

module Types
  include Thy::Types
end

To run a type check, call check on a type you need. For example:

module Types
  include Thy::Types
end

Types::String.check('a string').success? # => true
Types::Integer.check(3.14).success?      # => false

If a check fails, a message describing the failure is provided:

Types::String.check(3).failure? # => true
Types::String.check(3).message # => "Expected a String, but got: 3"

Custom types

Thy allows you to define custom types via Thy::Type's constructor:

module Types
  include Thy::Types

  NonZeroValue = Thy::Type.new { |value| value != 0 }
end

Providing meaningful error messages

module Types
  include Thy::Types

  NonZeroValue = Thy::Type.new do |value|
    value != 0 || Thy::Result::Failure.new("Expected #{value.inspect} to be nonzero")
  end
end

Building on top of existing types

Extending existing types is simple, too:

module Types
  include Thy::Types

  NonZeroInteger = Thy.refine_type(Integer, Thy::Type.new { |v| v != 0 })
end

Direct usage

You can use Thy types directly, for example, if you'd like to play around in the console:

Thy::String.check('Bob').success? # => true
PositiveInteger = Thy.refine_type(Integer, Thy::Type.new { |v| v > 0 })

Built-in type reference

Primitive types

# String
Thy::String.check('Bob').success? # => true

# Symbol
Thy::Symbol.check(:bob).success? # => true

# Numeric
Thy::Numeric.check(3.14).success? # => true
Thy::Numeric.check(0).success? # => true

# Float
Thy::Float.check(3.14).success? # => true

# Integer
Thy::Integer.check(3).success? # => true

# Boolean
Thy::Boolean.check(true).success? # => true
Thy::Boolean.check(false).success? # => true

# Time
Thy::Time.check(Time.now).success? # => true
Thy::Time.check(0).success? # => false

# DateTime
require 'date'
Thy::DateTime.check(DateTime.now).success? # => true
Thy::DateTime.check(0).success? # => false

# nil
Thy::Nil.check(nil).success? # => true
Thy::Nil.check(0).success? # => false

Parameterized types

# Array
Thy::Array(Thy::Integer).check([1, 2, 3]).success? # => true
Thy::Array(Thy::String).check(%w[hi hello sup]).success? # => true

# Hash
Thy::Hash({
  name: Thy::String,
  age: Thy::Integer,
}).check({ name: 'Bob', age: 18 }).success? # => true

# Map
Thy::Map(Thy::Symbol, Thy::Integer).check({ a: 1, b: 2 }).success? # => true

# Enum
Thy::Enum(Thy::Integer, Thy::Float).check(3.14).success? # => true
Thy::Enum(Thy::Integer, Thy::Float).check(3).success? # => true

# Variant
Thy::Variant('USD', 'EUR').check('EUR').success? # => true

# Option
Thy::Option(Thy::String).check('a string').success? # => true
Thy::Option(Thy::String).check(nil).success? # => true

# InstanceOf
class A; end
class B < A; end
class C; end
Thy::InstanceOf(A).check(B.new).success? # => true
Thy::InstanceOf(A).check(C.new).success? # => false

# ClassExtending
Thy::ClassExtending(A).check(B).success? # => true
Thy::ClassExtending(A).check(C).success? # => false

Other types

# UntypedArray
Thy::UntypedArray.check(1, 'string', 3.14).success? # => true

# UntypedHash
Thy::UntypedHash.check({ a: 1, b: 'c', 1 => true }).success? # => true

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/akxcv/thy.

License

The gem is available as open source under the terms of the MIT License.