rasti-types 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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