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.
- 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
|