rasti-types 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/Rakefile +25 -0
- data/lib/rasti-types.rb +1 -0
- data/lib/rasti/types.rb +12 -0
- data/lib/rasti/types/array.rb +54 -0
- data/lib/rasti/types/boolean.rb +40 -0
- data/lib/rasti/types/castable.rb +25 -0
- data/lib/rasti/types/enum.rb +46 -0
- data/lib/rasti/types/errors.rb +66 -0
- data/lib/rasti/types/float.rb +31 -0
- data/lib/rasti/types/hash.rb +54 -0
- data/lib/rasti/types/integer.rb +19 -0
- data/lib/rasti/types/io.rb +21 -0
- data/lib/rasti/types/model.rb +34 -0
- data/lib/rasti/types/regexp.rb +21 -0
- data/lib/rasti/types/string.rb +38 -0
- data/lib/rasti/types/symbol.rb +21 -0
- data/lib/rasti/types/time.rb +38 -0
- data/lib/rasti/types/uuid.rb +5 -0
- data/lib/rasti/types/version.rb +5 -0
- data/rasti-types.gemspec +30 -0
- data/spec/array_spec.rb +55 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/coverage_helper.rb +5 -0
- data/spec/enum_spec.rb +28 -0
- data/spec/float_spec.rb +18 -0
- data/spec/hash_spec.rb +52 -0
- data/spec/integer_spec.rb +18 -0
- data/spec/io_spec.rb +18 -0
- data/spec/minitest_helper.rb +37 -0
- data/spec/model_spec.rb +24 -0
- data/spec/regexp_spec.rb +18 -0
- data/spec/string_formatted_spec.rb +20 -0
- data/spec/string_spec.rb +16 -0
- data/spec/symbol_spec.rb +16 -0
- data/spec/time_spec.rb +25 -0
- data/spec/uuid_spec.rb +18 -0
- metadata +221 -0
@@ -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
|
data/rasti-types.gemspec
ADDED
@@ -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
|
data/spec/array_spec.rb
ADDED
@@ -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
|
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
|