rasti-types 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ module Rasti
2
+ module Types
3
+ class Float
4
+ class << self
5
+
6
+ include Castable
7
+
8
+ FORMAT = /^(\d+\.)?\d+$/
9
+
10
+ private
11
+
12
+ def valid?(value)
13
+ !value.nil? && (valid_string?(value) || transformable?(value))
14
+ end
15
+
16
+ def transform(value)
17
+ value.to_f
18
+ end
19
+
20
+ def valid_string?(value)
21
+ value.is_a?(::String) && value.match(FORMAT)
22
+ end
23
+
24
+ def transformable?(value)
25
+ !value.is_a?(::String) && value.respond_to?(:to_f)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ module Rasti
2
+ module Types
3
+ class Hash
4
+
5
+ include Castable
6
+
7
+ attr_reader :key_type, :value_type
8
+
9
+ def self.[](key_type, value_type)
10
+ new key_type, value_type
11
+ end
12
+
13
+ def to_s
14
+ "#{self.class}[#{key_type}, #{value_type}]"
15
+ end
16
+ alias_method :inspect, :to_s
17
+
18
+ private
19
+
20
+ def initialize(key_type, value_type)
21
+ @key_type = key_type
22
+ @value_type = value_type
23
+ end
24
+
25
+ def valid?(value)
26
+ value.is_a? ::Hash
27
+ end
28
+
29
+ def transform(value)
30
+ result = {}
31
+ errors = {}
32
+
33
+ value.each do |k,v|
34
+ begin
35
+ result[key_type.cast k] = value_type.cast v
36
+
37
+ rescue CompoundError => ex
38
+ ex.errors.each do |key, messages|
39
+ errors["#{k}.#{key}"] = messages
40
+ end
41
+
42
+ rescue => error
43
+ errors[k] = [error.message]
44
+ end
45
+ end
46
+
47
+ raise MultiCastError.new(self, value, errors) unless errors.empty?
48
+
49
+ result
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ module Rasti
2
+ module Types
3
+ class Integer < Float
4
+ class << self
5
+
6
+ private
7
+
8
+ def transform(value)
9
+ value.to_i
10
+ end
11
+
12
+ def transformable?(value)
13
+ !value.is_a?(::String) && value.respond_to?(:to_i)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Rasti
2
+ module Types
3
+ class IO
4
+ class << self
5
+
6
+ include Castable
7
+
8
+ private
9
+
10
+ def valid?(value)
11
+ value.respond_to?(:read) && value.respond_to?(:write)
12
+ end
13
+
14
+ def transform(value)
15
+ value
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module Rasti
2
+ module Types
3
+ class Model
4
+
5
+ include Castable
6
+
7
+ attr_reader :model
8
+
9
+ def self.[](model)
10
+ new(model)
11
+ end
12
+
13
+ def to_s
14
+ "#{self.class}[#{model}]"
15
+ end
16
+ alias_method :inspect, :to_s
17
+
18
+ private
19
+
20
+ def initialize(model)
21
+ @model = model
22
+ end
23
+
24
+ def valid?(value)
25
+ value.is_a? ::Hash
26
+ end
27
+
28
+ def transform(value)
29
+ model.new value
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ module Rasti
2
+ module Types
3
+ class Regexp
4
+ class << self
5
+
6
+ include Castable
7
+
8
+ private
9
+
10
+ def valid?(value)
11
+ value.is_a?(::Regexp) || value.is_a?(::String)
12
+ end
13
+
14
+ def transform(value)
15
+ value.is_a?(::Regexp) ? value.source : ::Regexp.compile(value).source
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ module Rasti
2
+ module Types
3
+ class String
4
+ class << self
5
+
6
+ include Castable
7
+
8
+ def [](format)
9
+ Class.new(self) do
10
+ @format = format.is_a?(::String) ? ::Regexp.new(format) : format
11
+ end
12
+ end
13
+
14
+ def to_s
15
+ name || "#{superclass.name}[#{format.inspect}]"
16
+ end
17
+ alias_method :inspect, :to_s
18
+
19
+ private
20
+
21
+ attr_reader :format
22
+
23
+ def valid?(value)
24
+ !value.nil? && value.respond_to?(:to_s) && valid_format?(value)
25
+ end
26
+
27
+ def valid_format?(value)
28
+ format.nil? || !value.to_s.match(format).nil?
29
+ end
30
+
31
+ def transform(value)
32
+ value.to_s
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ module Rasti
2
+ module Types
3
+ class Symbol
4
+ class << self
5
+
6
+ include Castable
7
+
8
+ private
9
+
10
+ def valid?(value)
11
+ !value.nil? && (value.respond_to?(:to_sym) || value.respond_to?(:to_s))
12
+ end
13
+
14
+ def transform(value)
15
+ value.respond_to?(:to_sym) ? value.to_sym : value.to_s.to_sym
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ module Rasti
2
+ module Types
3
+ class Time
4
+
5
+ include Castable
6
+
7
+ attr_reader :format
8
+
9
+ def self.[](format)
10
+ new format
11
+ end
12
+
13
+ def to_s
14
+ "#{self.class}['#{format}']"
15
+ end
16
+ alias_method :inspect, :to_s
17
+
18
+ private
19
+
20
+ def initialize(format)
21
+ @format = format
22
+ end
23
+
24
+ def valid?(value)
25
+ value.is_a?(::String) || value.respond_to?(:to_time)
26
+ end
27
+
28
+ def transform(value)
29
+ value.is_a?(::String) ? string_to_time(value) : value.to_time
30
+ end
31
+
32
+ def string_to_time(value)
33
+ ::Time.strptime(value, format)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ module Rasti
2
+ module Types
3
+ UUID = String[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+$/]
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Rasti
2
+ module Types
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rasti/types/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rasti-types'
8
+ spec.version = Rasti::Types::VERSION
9
+ spec.authors = ['Gabriel Naiman']
10
+ spec.email = ['gabynaiman@gmail.com']
11
+ spec.summary = 'Type casting'
12
+ spec.description = 'Type casting'
13
+ spec.homepage = 'https://github.com/gabynaiman/rasti-types'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'multi_require', '~> 1.0'
22
+
23
+ spec.add_development_dependency 'rake', '~> 12.0'
24
+ spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
25
+ spec.add_development_dependency 'minitest-colorin', '~> 0.1'
26
+ spec.add_development_dependency 'minitest-line', '~> 0.6'
27
+ spec.add_development_dependency 'simplecov', '~> 0.12'
28
+ spec.add_development_dependency 'coveralls', '~> 0.8'
29
+ spec.add_development_dependency 'pry-nav', '~> 0.2'
30
+ end
@@ -0,0 +1,55 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Types::Array do
4
+
5
+ VALID_ARRAY = [1, '2', Time.now]
6
+ INVALID_ARRAY = [nil, 1, 'text', :symbol, {a: 1, b: 2}, Object.new]
7
+
8
+ it "#{VALID_ARRAY.inspect} -> #{VALID_ARRAY.map(&:to_i)}" do
9
+ Rasti::Types::Array[Rasti::Types::Integer].cast(VALID_ARRAY).must_equal VALID_ARRAY.map(&:to_i)
10
+ end
11
+
12
+ it "#{VALID_ARRAY.inspect} -> #{VALID_ARRAY.map(&:to_s)}" do
13
+ Rasti::Types::Array[Rasti::Types::String].cast(VALID_ARRAY).must_equal VALID_ARRAY.map(&:to_s)
14
+ end
15
+
16
+ INVALID_ARRAY.each do |value|
17
+ it "#{value.inspect} -> CastError" do
18
+ error = proc { Rasti::Types::Array[Rasti::Types::String].cast(value) }.must_raise Rasti::Types::CastError
19
+ error.message.must_equal "Invalid cast: #{as_string(value)} -> Rasti::Types::Array[Rasti::Types::String]"
20
+ end
21
+ end
22
+
23
+ describe 'Multi cast errors' do
24
+
25
+ it 'Array of integers' do
26
+ array = [1, 2 , 'a', 3, 'c', 4, nil]
27
+
28
+ error = proc { Rasti::Types::Array[Rasti::Types::Integer].cast(array) }.must_raise Rasti::Types::MultiCastError
29
+
30
+ error.errors.must_equal 3 => ["Invalid cast: 'a' -> Rasti::Types::Integer"],
31
+ 5 => ["Invalid cast: 'c' -> Rasti::Types::Integer"],
32
+ 7 => ["Invalid cast: nil -> Rasti::Types::Integer"]
33
+
34
+ error.message.must_equal "Cast errors:\n- 3: [\"Invalid cast: 'a' -> Rasti::Types::Integer\"]\n- 5: [\"Invalid cast: 'c' -> Rasti::Types::Integer\"]\n- 7: [\"Invalid cast: nil -> Rasti::Types::Integer\"]"
35
+ end
36
+
37
+ it 'Array of models' do
38
+ array = [
39
+ {x: 1, y: 2},
40
+ {y: 2},
41
+ {x: 1},
42
+ {x: 3, y: 4}
43
+ ]
44
+
45
+ error = proc { Rasti::Types::Array[Rasti::Types::Model[Point]].cast(array) }.must_raise Rasti::Types::MultiCastError
46
+
47
+ error.errors.must_equal '2.x' => ['not present'],
48
+ '3.y' => ['not present']
49
+
50
+ error.message.must_equal "Cast errors:\n- 2.x: [\"not present\"]\n- 3.y: [\"not present\"]"
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,24 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Types::Boolean do
4
+
5
+ [true, 'true', 'True', 'TRUE', 'T'].each do |value|
6
+ it "#{value.inspect} -> true" do
7
+ Rasti::Types::Boolean.cast(value).must_equal true
8
+ end
9
+ end
10
+
11
+ [false, 'false', 'False', 'FALSE', 'F'].each do |value|
12
+ it "#{value.inspect} -> false" do
13
+ Rasti::Types::Boolean.cast(value).must_equal false
14
+ end
15
+ end
16
+
17
+ [nil, 'text', 123, :false, :true, Time.now, [1,2], {a: 1, b: 2}, Object.new].each do |value|
18
+ it "#{value.inspect} -> CastError" do
19
+ error = proc { Rasti::Types::Boolean.cast(value) }.must_raise Rasti::Types::CastError
20
+ error.message.must_equal "Invalid cast: #{as_string(value)} -> Rasti::Types::Boolean"
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,5 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
5
+ SimpleCov.start
data/spec/enum_spec.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Types::Enum do
4
+
5
+ enum = Rasti::Types::Enum[:a,:b,:c]
6
+
7
+ [:a, 'b', "c"].each do |value|
8
+ it "#{value.inspect} -> #{enum}" do
9
+ Rasti::Types::Enum[:a,:b,:c].cast(value).must_equal value.to_s
10
+ end
11
+ end
12
+
13
+ [nil, 'text', :symbol, '999'.to_sym, [1,2], {a: 1, b: 2}, Object.new].each do |value|
14
+ it "#{value.inspect} -> CastError" do
15
+ error = proc { Rasti::Types::Enum[:a,:b,:c].cast(value) }.must_raise Rasti::Types::CastError
16
+ error.message.must_equal "Invalid cast: #{as_string(value)} -> Rasti::Types::Enum[\"a\", \"b\", \"c\"]"
17
+ end
18
+ end
19
+
20
+ it 'Constants' do
21
+ enum = Rasti::Types::Enum[:first_value, 'SecondValue', 'THIRD_VALUE']
22
+
23
+ enum.first_value.must_equal 'first_value'
24
+ enum.second_value.must_equal 'SecondValue'
25
+ enum.third_value.must_equal 'THIRD_VALUE'
26
+ end
27
+
28
+ end