Created
May 7, 2020 14:22
-
-
Save ismasan/7a9ddef43075165cc7daf59b78649cd3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Identities | |
TypeError = Class.new(ArgumentError) | |
class Nope | |
def matches?(_) | |
false | |
end | |
def call(value) | |
value | |
end | |
end | |
class Identity | |
NOOP = ->(v) { v } | |
attr_reader :name | |
def initialize(name, sub: Nope.new) | |
@name = name | |
@coercions = {} | |
@sub = sub | |
end | |
def coerces(type, coercion = nil, &block) | |
coercion = block unless coercion | |
coercion = NOOP unless coercion | |
coercions[type] = coercion | |
self | |
end | |
def [](child) | |
copy(sub: child) | |
end | |
def call(value) | |
return sub.call(value) if sub.matches?(value) | |
coercion = coercion_for(value) | |
raise TypeError, "#{value.inspect} (#{value.class}) cannot be coerced into #{name}" unless coercion | |
coercion.call(value) | |
end | |
def matches?(value) | |
sub.matches?(value) || !!coercion_for(value) | |
end | |
def copy(sub: nil) | |
self.class.new(name, sub: sub || @sub).tap do |i| | |
coercions.each do |k, v| | |
i.coerces k, v | |
end | |
end | |
end | |
protected | |
attr_reader :coercions | |
def coercion_for(value) | |
coercion = nil | |
coercions.each do |type, callable| | |
if type.is_a?(::Class) | |
coercion = callable and break if value.is_a?(type) | |
else | |
coercion = callable and break if type === value | |
end | |
end | |
coercion | |
end | |
private | |
attr_reader :sub | |
end | |
String = Identity.new('String').tap do |i| | |
i.coerces ::String, ->(value) { value } | |
end | |
Integer = Identity.new('Integer').tap do |i| | |
i.coerces ::Numeric, ->(value) { value.to_i } | |
end | |
Boolean = Identity.new('Boolean').tap do |i| | |
i.coerces TrueClass, ->(v) { v } | |
i.coerces FalseClass, ->(v) { v } | |
end | |
Maybe = Identity.new('Maybe').tap do |i| | |
i.coerces NilClass, ->(v) { v } | |
end | |
CSV = Identity.new('CSV').tap do |i| | |
i.coerces ::String, ->(v) { v.split(/\s*,\s*/) } | |
end | |
module Lax | |
String = Identities::String.copy.tap do |i| | |
i.coerces BigDecimal, ->(value) { value.to_s('F') } | |
i.coerces Numeric, ->(value) { value.to_s } | |
end | |
Integer = Identities::Integer.copy.tap do |i| | |
i.coerces /^\d+$/, ->(value) { value.to_i } | |
i.coerces /^\d+.\d*?$/, ->(value) { value.to_i } | |
end | |
end | |
module Forms | |
Boolean = Identities::Boolean.copy.tap do |i| | |
i.coerces /^true$/i, ->(_) { true } | |
i.coerces '1', ->(_) { true } | |
i.coerces 1, ->(_) { true } | |
i.coerces /^false$/i, ->(_) { false } | |
i.coerces '0', ->(_) { false } | |
i.coerces 0, ->(_) { false } | |
end | |
end | |
end | |
RSpec.describe Identities do | |
specify Identities::String do | |
expect(Identities::String.call('aa')).to eq('aa') | |
expect { | |
Identities::String.call(10) | |
}.to raise_error(Identities::TypeError) | |
end | |
specify Identities::Lax::String do | |
expect(Identities::Lax::String.call('aa')).to eq('aa') | |
expect(Identities::Lax::String.call(11)).to eq('11') | |
expect(Identities::Lax::String.call(11.10)).to eq('11.1') | |
expect(Identities::Lax::String.call(BigDecimal('111.2011'))).to eq('111.2011') | |
expect { | |
Identities::String.call(true) | |
}.to raise_error(Identities::TypeError) | |
end | |
specify Identities::Lax::Integer do | |
expect(Identities::Lax::Integer.call(113)).to eq(113) | |
expect(Identities::Lax::Integer.call(113.10)).to eq(113) | |
expect(Identities::Lax::Integer.call('113')).to eq(113) | |
expect(Identities::Lax::Integer.call('113.10')).to eq(113) | |
expect { | |
Identities::Lax::Integer.call('nope') | |
}.to raise_error(Identities::TypeError) | |
end | |
specify Identities::Boolean do | |
expect(Identities::Boolean.call(true)).to be true | |
expect(Identities::Boolean.call(false)).to be false | |
expect { | |
Identities::Boolean.call('true') | |
}.to raise_error(Identities::TypeError) | |
end | |
specify Identities::Forms::Boolean do | |
expect(Identities::Forms::Boolean.call(true)).to be true | |
expect(Identities::Forms::Boolean.call(false)).to be false | |
expect(Identities::Forms::Boolean.call('true')).to be true | |
expect(Identities::Forms::Boolean.matches?('true')).to be true | |
expect(Identities::Forms::Boolean.matches?('false')).to be true | |
expect(Identities::Forms::Boolean.call('false')).to be false | |
expect(Identities::Forms::Boolean.call('1')).to be true | |
expect(Identities::Forms::Boolean.call('0')).to be false | |
expect(Identities::Forms::Boolean.call(1)).to be true | |
expect(Identities::Forms::Boolean.call(0)).to be false | |
expect { | |
Identities::Forms::Boolean.call('nope') | |
}.to raise_error(Identities::TypeError) | |
end | |
specify Identities::Maybe do | |
expect(Identities::Maybe[Identities::String].call(nil)).to be nil | |
expect(Identities::Maybe[Identities::String].call('foo')).to eq 'foo' | |
expect(Identities::Maybe[Identities::String].matches?(nil)).to be true | |
expect(Identities::Maybe[Identities::String].matches?('foo')).to be true | |
expect(Identities::Maybe[Identities::String].matches?(11)).to be false | |
expect(Identities::Maybe[Identities::Lax::String].matches?(11)).to be true | |
end | |
specify Identities::CSV do | |
expect(Identities::CSV.call('one,two, three , four')).to eq(%w[one two three four]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment