Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/errgonomic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# A more opinionated blend with Rails presence.
require_relative 'errgonomic/presence'

# Bring in a subtle and manual type checker.
require_relative 'errgonomic/type'

# Bring in our Option and Result.
require_relative 'errgonomic/option'
require_relative 'errgonomic/result'
Expand Down
67 changes: 67 additions & 0 deletions lib/errgonomic/type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

class Object
# Returns the receiver if it matches the expected type, otherwise raises a TypeMismatchError.
# This is useful for enforcing type expectations in method arguments.
#
# @param type [Class] The expected type or module the receiver should be.
# @param message [String] Optional error message to raise if type doesn't match.
# @return [Object] The receiver if it is of the expected type.
# @example
# 'hello'.type_or_raise!(String) #=> "hello"
# 123.type_or_raise!(String, "We need a string!") #=> raise Errgonomic::TypeMismatchError, "We need a string!"
# 123.type_or_raise!(String) #=> raise Errgonomic::TypeMismatchError, "Expected String but got Integer"
def type_or_raise!(type, message = nil)
message ||= "Expected #{type} but got #{self.class}"
raise Errgonomic::TypeMismatchError, message unless is_a?(type)

self
end

alias type_or_raise type_or_raise!

# Returns the receiver if it matches the expected type, otherwise returns the default value.
#
# @param type [Class] The expected type or module the receiver should be.
# @param default [Object] The value to return if type doesn't match.
# @return [Object] The receiver if it is of the expected type, otherwise the default value.
# @example
# 'hello'.type_or(String, 'default') # => "hello"
# 123.type_or(String, 'default') # => "default"
def type_or(type, default)
return self if is_a?(type)

default
end

# Returns the receiver if it matches the expected type, otherwise returns the result of the block.
# Useful when constructing the default value is expensive.
#
# @param type [Class] The expected type or module the receiver should be.
# @param block [Proc] The block to call if type doesn't match.
# @return [Object] The receiver if it is of the expected type, otherwise the block result.
# @example
# 'hello'.type_or_else(String) { 'default' } # => "hello"
# 123.type_or_else(String) { 'default' } # => "default"
def type_or_else(type, &block)
return self if is_a?(type)

block.call
end

# Returns the receiver if it does not match the expected type, otherwise raises a TypeMismatchError.
#
# @param type [Class] The type or module the receiver should not be.
# @param message [String] Optional error message to raise if type matches.
# @return [Object] The receiver if it is not of the specified type.
# @example
# 'hello'.not_type_or_raise!(Integer) #=> "hello"
# 123.not_type_or_raise!(Integer, "We dont want an integer!") #=> raise Errgonomic::TypeMismatchError, "We dont want an integer!"
# 123.not_type_or_raise!(Integer) #=> raise Errgonomic::TypeMismatchError, "Expected anything but Integer but got Integer"
def not_type_or_raise!(type, message = nil)
message ||= "Expected anything but #{type} but got #{self.class}"
raise Errgonomic::TypeMismatchError, message if is_a?(type)

self
end
end