type 0.1.0 → 0.2.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.
- data/.travis.yml +9 -0
- data/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +7 -0
- data/README.md +29 -0
- data/lib/type/builtin.rb +2 -109
- data/lib/type/builtin/collection.rb +49 -0
- data/lib/type/builtin/scalar.rb +95 -0
- data/lib/type/definition.rb +6 -5
- data/lib/type/definition/collection.rb +1 -1
- data/lib/type/definition/nilable.rb +1 -1
- data/lib/type/version.rb +1 -1
- data/spec/spec_helper.rb +66 -0
- data/spec/type/builtin/collection_spec.rb +73 -0
- data/spec/type/builtin/scalar_spec.rb +121 -0
- data/spec/type/definition_spec.rb +2 -0
- metadata +11 -4
- data/spec/type/builtin_spec.rb +0 -242
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
## Next release (TBD)
|
4
4
|
|
5
|
+
## 0.2.0 (2014-02-01)
|
6
|
+
|
7
|
+
- Improve Type::Boolean to cast truthy and falsy objects to true and false.
|
8
|
+
- Improve exception message when casting an out-of-range integer via one of
|
9
|
+
the range-limited Integer type definitions (e.g., Int32)
|
10
|
+
- Improve performance when casting already-valid objects.
|
11
|
+
- Fixed tests for JRuby and Type::Int64
|
12
|
+
- Fixed Type::Hash to make it less surprising on Ruby 2+ via `Kernel::Hash()`
|
13
|
+
|
5
14
|
## 0.1.0 (2014-01-29)
|
6
15
|
|
7
16
|
- Initial Implementation
|
data/CONTRIBUTING.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -37,6 +37,12 @@ Type::Int32!(input) # alias for Type::Int32.cast!(input)
|
|
37
37
|
`Type` comes with a variety of built-in type defintions, which can be used for
|
38
38
|
validation or casting.
|
39
39
|
|
40
|
+
- [Scalar](#scalar-type-definitions)
|
41
|
+
- [Nilable Modifier](#nilable-type-definitions)
|
42
|
+
- [Collection](#collection-type-definitions)
|
43
|
+
- [Constrained Collections](#constrained-collection-type-definitions)
|
44
|
+
- [Nilable Constrained Collections](#nilable-constrained-collection-type-definitions)
|
45
|
+
|
40
46
|
### Scalar Type Definitions:
|
41
47
|
|
42
48
|
The most basic type definitions are scalar
|
@@ -63,6 +69,21 @@ Type::Int32!('three')
|
|
63
69
|
#! Type::CastError: Could not cast "three"(String) with Type::Int32
|
64
70
|
~~~
|
65
71
|
|
72
|
+
The complete list of built-in scalar type definitions is:
|
73
|
+
|
74
|
+
~~~ ruby
|
75
|
+
Type::Integer # {x∈ℤ}
|
76
|
+
Type::Int32 # {x∈ℤ|[-2^31,2^31)}
|
77
|
+
Type::Int64 # {x∈ℤ|[-2^63,2^63)}
|
78
|
+
Type::UInt32 # {x∈ℕ|[0,2^32)}
|
79
|
+
Type::UInt64 # {x∈ℕ|[0,2^64)}
|
80
|
+
Type::Float # {x∈ℝ,+∞,-∞}
|
81
|
+
Type::Float32 # {x∈ℝ}
|
82
|
+
Type::Float64 # {x∈ℝ}
|
83
|
+
Type::Boolean # {true,false}
|
84
|
+
Type::String # any string
|
85
|
+
~~~
|
86
|
+
|
66
87
|
### Nilable Type Definitions:
|
67
88
|
|
68
89
|
Any `Type::Definition` can be declared nilable -- that is, it will report `nil`
|
@@ -109,6 +130,14 @@ Type::Set!('foo')
|
|
109
130
|
#! Type::CastError: Could not cast "foo"(String) with Type::Set
|
110
131
|
~~~
|
111
132
|
|
133
|
+
The complete list of built-in collection type definitions is:
|
134
|
+
|
135
|
+
~~~ ruby
|
136
|
+
Type::Array
|
137
|
+
Type::Set
|
138
|
+
Type::Hash
|
139
|
+
~~~
|
140
|
+
|
112
141
|
### Constrained Collection Type Definitions:
|
113
142
|
|
114
143
|
The real power of type-casting collections is when their contents can also be
|
data/lib/type/builtin.rb
CHANGED
@@ -1,111 +1,4 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
scalar(:Integer) do
|
6
|
-
validate do |input|
|
7
|
-
input.kind_of?(::Integer)
|
8
|
-
end
|
9
|
-
cast do |input|
|
10
|
-
Kernel::Integer(input)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
scalar(:Int32).from(:Integer) do
|
15
|
-
int32_range = (-(1 << 31) ... (1 << 31))
|
16
|
-
validate do |input|
|
17
|
-
int32_range.include?(input)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
scalar(:Int64).from(:Integer) do
|
22
|
-
int64_range = (-(1 << 63) ... (1 << 63))
|
23
|
-
validate do |input|
|
24
|
-
int64_range.include?(input)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
scalar(:UInt32).from(:Integer) do
|
29
|
-
int32_range = (0 ... (1 << 32))
|
30
|
-
validate do |input|
|
31
|
-
int32_range.include?(input)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
scalar(:UInt64).from(:Integer) do
|
36
|
-
int64_range = (0 ... (1 << 64))
|
37
|
-
validate do |input|
|
38
|
-
int64_range.include?(input)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
scalar(:Float) do
|
43
|
-
validate do |input|
|
44
|
-
input.kind_of?(::Float)
|
45
|
-
end
|
46
|
-
cast do |input|
|
47
|
-
Kernel::Float(input)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
scalar(:Float32).from(:Float) do
|
52
|
-
validate do |input|
|
53
|
-
input.finite?
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
scalar(:Float64).from(:Float) do
|
58
|
-
validate do |input|
|
59
|
-
input.finite?
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
scalar(:Boolean) do
|
64
|
-
require 'set'
|
65
|
-
booleans = Set.new([true, false])
|
66
|
-
validate do |input|
|
67
|
-
booleans.include?(input)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
scalar(:String) do
|
72
|
-
validate do |input|
|
73
|
-
input.kind_of?(::String)
|
74
|
-
end
|
75
|
-
cast do |input|
|
76
|
-
Kernel::String(input)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
collection(:Array) do
|
81
|
-
validate do |input|
|
82
|
-
input.kind_of?(::Array)
|
83
|
-
end
|
84
|
-
cast do |input|
|
85
|
-
Kernel::Array(input)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
collection(:Hash) do
|
90
|
-
validate do |input|
|
91
|
-
input.kind_of?(::Hash)
|
92
|
-
end
|
93
|
-
cast do |input|
|
94
|
-
if Kernel.respond_to?(:Hash)
|
95
|
-
Kernel::Hash(input)
|
96
|
-
else
|
97
|
-
::Hash[input]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
collection(:Set) do
|
103
|
-
require 'set'
|
104
|
-
validate do |input|
|
105
|
-
input.kind_of?(::Set)
|
106
|
-
end
|
107
|
-
cast do |input|
|
108
|
-
::Set.new(input)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
3
|
+
require 'type/builtin/scalar'
|
4
|
+
require 'type/builtin/collection'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# The built-in collection types are defined here.
|
4
|
+
module Type
|
5
|
+
collection(:Array) do
|
6
|
+
validate do |input|
|
7
|
+
input.kind_of?(::Array)
|
8
|
+
end
|
9
|
+
cast do |input|
|
10
|
+
Kernel::Array(input)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
collection(:Hash) do
|
15
|
+
validate do |input|
|
16
|
+
input.kind_of?(::Hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
cast_1_9 = proc do |input|
|
20
|
+
::Hash[input]
|
21
|
+
end
|
22
|
+
|
23
|
+
cast_2_0 = proc do |input|
|
24
|
+
begin
|
25
|
+
Kernel::Hash(input)
|
26
|
+
rescue TypeError
|
27
|
+
cast_1_9[input]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
cast do |input|
|
32
|
+
if Kernel.respond_to?(:Hash)
|
33
|
+
cast_2_0[input]
|
34
|
+
else
|
35
|
+
cast_1_9[input]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
collection(:Set) do
|
41
|
+
require 'set'
|
42
|
+
validate do |input|
|
43
|
+
input.kind_of?(::Set)
|
44
|
+
end
|
45
|
+
cast do |input|
|
46
|
+
::Set.new(input)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# The built-in scalar types are defined here.
|
4
|
+
module Type
|
5
|
+
scalar(:Integer) do
|
6
|
+
validate do |input|
|
7
|
+
input.kind_of?(::Integer)
|
8
|
+
end
|
9
|
+
cast do |input|
|
10
|
+
Kernel::Integer(input)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
scalar(:Int32).from(:Integer) do
|
15
|
+
int32_range = (-(1 << 31) ... (1 << 31))
|
16
|
+
validate do |input|
|
17
|
+
unless int32_range.include?(input)
|
18
|
+
raise RangeError.new("#{input} not in #{int32_range}")
|
19
|
+
end
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
scalar(:Int64).from(:Integer) do
|
25
|
+
int64_range = (-(1 << 63) ... (1 << 63))
|
26
|
+
validate do |input|
|
27
|
+
unless int64_range.include?(input)
|
28
|
+
raise RangeError.new("#{input} not in #{int64_range}")
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
scalar(:UInt32).from(:Integer) do
|
35
|
+
uint32_range = (0 ... (1 << 32))
|
36
|
+
validate do |input|
|
37
|
+
unless uint32_range.include?(input)
|
38
|
+
raise RangeError.new("#{input} not in #{uint32_range}")
|
39
|
+
end
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
scalar(:UInt64).from(:Integer) do
|
45
|
+
uint64_range = (0 ... (1 << 64))
|
46
|
+
validate do |input|
|
47
|
+
unless uint64_range.include?(input)
|
48
|
+
raise RangeError.new("#{input} not in #{uint64_range}")
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
scalar(:Float) do
|
55
|
+
validate do |input|
|
56
|
+
input.kind_of?(::Float)
|
57
|
+
end
|
58
|
+
cast do |input|
|
59
|
+
Kernel::Float(input)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
scalar(:Float32).from(:Float) do
|
64
|
+
validate do |input|
|
65
|
+
input.finite?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
scalar(:Float64).from(:Float) do
|
70
|
+
validate do |input|
|
71
|
+
input.finite?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
scalar(:Boolean) do
|
76
|
+
require 'set'
|
77
|
+
booleans = Set.new([true, false])
|
78
|
+
validate do |input|
|
79
|
+
booleans.include?(input)
|
80
|
+
end
|
81
|
+
cast do |input|
|
82
|
+
input ? true : false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
scalar(:String) do
|
87
|
+
validate do |input|
|
88
|
+
input.kind_of?(::String)
|
89
|
+
end
|
90
|
+
cast do |input|
|
91
|
+
raise TypeError if input.nil?
|
92
|
+
Kernel::String(input)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/type/definition.rb
CHANGED
@@ -89,10 +89,12 @@ module Type
|
|
89
89
|
attr_reader :name
|
90
90
|
|
91
91
|
# @param input [Object]
|
92
|
+
# @param squash_exceptions [Boolean] (true)
|
92
93
|
# @return [Boolean]
|
93
|
-
def valid?(input)
|
94
|
+
def valid?(input, squash_exceptions = true)
|
94
95
|
validators.all? { |proc| proc[input] }
|
95
|
-
rescue
|
96
|
+
rescue Exception
|
97
|
+
raise unless squash_exceptions
|
96
98
|
false
|
97
99
|
end
|
98
100
|
|
@@ -100,12 +102,11 @@ module Type
|
|
100
102
|
# @return [Object] the result of casting, guaranteed to be valid.
|
101
103
|
# @raise [Type::CastError]
|
102
104
|
def cast!(input)
|
103
|
-
input
|
104
|
-
raise CastError.new(input, self) if input.nil?
|
105
|
+
return input if valid?(input)
|
105
106
|
castors.reduce(input) do |intermediate, castor|
|
106
107
|
castor[intermediate]
|
107
108
|
end.tap do |output|
|
108
|
-
raise ValidationError.new(output, self) unless valid?(output)
|
109
|
+
raise ValidationError.new(output, self) unless valid?(output, false)
|
109
110
|
end
|
110
111
|
rescue
|
111
112
|
raise CastError.new(input, self)
|
data/lib/type/version.rb
CHANGED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
RSpec::Matchers.define(:cast) do |input|
|
4
|
+
match do |definition|
|
5
|
+
begin
|
6
|
+
@actual = definition.cast!(input)
|
7
|
+
if @chained
|
8
|
+
failure_message_for_should do
|
9
|
+
"Expected result to be #{@expected.inspect}(#{@expected.class}) " +
|
10
|
+
"but got #{@actual.inspect}(#{@actual.class}) instead"
|
11
|
+
end
|
12
|
+
@expected == @actual
|
13
|
+
else
|
14
|
+
true
|
15
|
+
end
|
16
|
+
rescue Type::CastError => cast_error
|
17
|
+
failure_message_for_should do
|
18
|
+
"#{definition} failed to cast #{input.inspect}(#{input.class}) " +
|
19
|
+
"by raising #{cast_error}(#{cast_error.cause})."
|
20
|
+
end
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
description do
|
26
|
+
"cast #{input.inspect}(#{input.class})"
|
27
|
+
end
|
28
|
+
|
29
|
+
chain(:to) do |expected|
|
30
|
+
description do
|
31
|
+
"cast #{input.inspect}(#{input.class}) to #{expected.inspect}(#{expected.class})"
|
32
|
+
end
|
33
|
+
@chained = true
|
34
|
+
@expected = expected
|
35
|
+
end
|
36
|
+
|
37
|
+
chain(:unchanged) do
|
38
|
+
description do
|
39
|
+
"cast #{input.inspect}(#{input.class}) unchanged"
|
40
|
+
end
|
41
|
+
@chained = true
|
42
|
+
@expected = input
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
RSpec::Matchers.define :validate do |input|
|
47
|
+
match do |definition|
|
48
|
+
definition.valid?(input)
|
49
|
+
end
|
50
|
+
description do
|
51
|
+
"validate #{input.inspect}(#{input.class})"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
shared_examples_for 'Type::Definition::Nilable compatibility' do
|
56
|
+
context 'when nilable' do
|
57
|
+
subject { described_class.nilable }
|
58
|
+
it { should be_a_kind_of Type::Definition::Nilable }
|
59
|
+
it { should be_nilable }
|
60
|
+
it { should cast(nil).to(nil) }
|
61
|
+
it { should validate(nil) }
|
62
|
+
end
|
63
|
+
it { should_not be_a_kind_of Type::Definition::Nilable }
|
64
|
+
it { should_not be_nilable }
|
65
|
+
it { should_not validate(nil) }
|
66
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'type'
|
3
|
+
|
4
|
+
require_relative '../../spec_helper'
|
5
|
+
|
6
|
+
describe Type::Array do
|
7
|
+
its(:to_s) { should match(/Type::Array/) }
|
8
|
+
it { should_not cast(nil) }
|
9
|
+
it { should be_a_kind_of Type::Definition::Collection }
|
10
|
+
it { should validate(['asdf']) }
|
11
|
+
it { should cast(['foo']).unchanged }
|
12
|
+
it { should cast(['asdf', 1]).unchanged }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Type::Array.of(:String) do
|
16
|
+
its(:to_s) { should match(/Type::Array\(.*String.*\)/) }
|
17
|
+
it { should_not cast(nil) }
|
18
|
+
it { should be_a_kind_of Type::Definition::Collection::Constrained }
|
19
|
+
it { should validate(['asdf']) }
|
20
|
+
it { should_not validate([nil, 'asdf']) }
|
21
|
+
it { should_not validate([:asdf]) }
|
22
|
+
it { should cast([:abc, 1]).to(['abc', '1']) }
|
23
|
+
it { should_not cast([nil, 1]) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe Type::Array.of(:String?) do
|
27
|
+
it { should be_a_kind_of Type::Definition::Collection::Constrained }
|
28
|
+
it { should_not cast(nil) }
|
29
|
+
it { should validate(['asdf']) }
|
30
|
+
it { should validate([nil, 'asdf']) }
|
31
|
+
it { should_not validate([:asdf]) }
|
32
|
+
it { should cast([:abc, 1]).to(['abc', '1']) }
|
33
|
+
it { should cast([nil, 1]).to([nil, '1']) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe Type::Hash do
|
37
|
+
its(:to_s) { should match(/Type::Hash/) }
|
38
|
+
it { should_not cast(nil) }
|
39
|
+
it { should cast([[1, 2], [3, 4]]).to(1 => 2, 3 => 4) }
|
40
|
+
it { should_not cast(17) }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Type::Hash.of(:String => :Integer) do
|
44
|
+
its(:to_s) { should match(/Type::Hash\(.*String.*Integer.*\)/) }
|
45
|
+
it { should_not cast(nil) }
|
46
|
+
it { should be_a_kind_of Type::Definition::Collection::Constrained }
|
47
|
+
it { should validate('foo' => 12) }
|
48
|
+
it { should_not validate(foo: 12) }
|
49
|
+
it { should_not validate('foo' => '12') }
|
50
|
+
it { should cast('foo' => '12', :bar => 3).to('foo' => 12, 'bar' => 3) }
|
51
|
+
it { should cast('foo' => 12, 'bar' => 3).unchanged }
|
52
|
+
it { should cast([['12', 34], [56, '78']]).to('12' => 34, '56' => 78) }
|
53
|
+
it { should_not cast('foo' => 'foo') }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe Type::Set do
|
57
|
+
it { should_not cast(nil) }
|
58
|
+
it { should_not validate([123, 456]) }
|
59
|
+
it { should validate(Set.new([123, 456])) }
|
60
|
+
it { should_not validate(17) }
|
61
|
+
it { should cast([123, 456]).to(Set.new([123, 456])) }
|
62
|
+
it { should cast(Set.new([123, 456])).to(Set.new([123, 456])) }
|
63
|
+
it { should_not cast(17) }
|
64
|
+
end
|
65
|
+
|
66
|
+
describe Type::Set.of(:Integer) do
|
67
|
+
its(:to_s) { should match(/Type::Set(.*Integer.*)/) }
|
68
|
+
it { should_not cast(nil) }
|
69
|
+
it { should validate(Set.new([1, 2, 3, 4])) }
|
70
|
+
it { should_not validate([1, 2, 3, 4]) }
|
71
|
+
it { should cast(Set.new([1, 2, 3, 4])).unchanged }
|
72
|
+
it { should cast([1, 2, 3, 4]).to(Set.new([1, 2, 3, 4])) }
|
73
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'type'
|
3
|
+
|
4
|
+
require_relative '../../spec_helper'
|
5
|
+
|
6
|
+
shared_examples_for 'Type::Definition::Scalar' do
|
7
|
+
include_examples 'Type::Definition::Nilable compatibility'
|
8
|
+
it { should be_a_kind_of Type::Definition }
|
9
|
+
it { should be_a_kind_of Type::Definition::Scalar }
|
10
|
+
end
|
11
|
+
|
12
|
+
shared_examples_for 'Type::Integer' do
|
13
|
+
it_should_behave_like 'Type::Definition::Scalar'
|
14
|
+
it { should_not cast(nil) }
|
15
|
+
|
16
|
+
it { should cast(414).unchanged }
|
17
|
+
it { should cast('123').to(123) }
|
18
|
+
it { should cast(456).to(456) }
|
19
|
+
it { should cast(Math::PI).to(3) } # alabama ftw
|
20
|
+
|
21
|
+
it { should_not cast('not a number') }
|
22
|
+
it { should_not cast(Hash.new) }
|
23
|
+
|
24
|
+
it { should validate(123) }
|
25
|
+
it { should_not validate('123') }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Type::Integer do
|
29
|
+
it_should_behave_like 'Type::Integer'
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples_for 'bounded Type::Integer' do
|
33
|
+
it_should_behave_like 'Type::Integer'
|
34
|
+
|
35
|
+
let(:range_max) { valid_range.end - (valid_range.exclude_end? ? 1 : 0) }
|
36
|
+
let(:range_min) { valid_range.begin }
|
37
|
+
|
38
|
+
it { should cast(range_max).unchanged }
|
39
|
+
it { should cast(range_min).unchanged }
|
40
|
+
|
41
|
+
it { should_not cast(range_max + 1) }
|
42
|
+
it { should_not cast(range_min - 1) }
|
43
|
+
|
44
|
+
it { should validate(range_max) }
|
45
|
+
it { should_not validate(range_max + 1) }
|
46
|
+
it { should validate(range_min) }
|
47
|
+
it { should_not validate(range_min - 1) }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe Type::Int32 do
|
51
|
+
let(:valid_range) { (-1 << 31)...(1 << 31) }
|
52
|
+
it_should_behave_like 'bounded Type::Integer'
|
53
|
+
end
|
54
|
+
|
55
|
+
describe Type::Int64 do
|
56
|
+
let(:valid_range) { (-1 << 63)...(1 << 63) }
|
57
|
+
it_should_behave_like 'bounded Type::Integer'
|
58
|
+
end
|
59
|
+
|
60
|
+
describe Type::UInt32 do
|
61
|
+
let(:valid_range) { 0...(1 << 32) }
|
62
|
+
it_should_behave_like 'bounded Type::Integer'
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Type::UInt64 do
|
66
|
+
let(:valid_range) { 0...(1 << 64) }
|
67
|
+
it_should_behave_like 'bounded Type::Integer'
|
68
|
+
end
|
69
|
+
|
70
|
+
describe Type::Boolean do
|
71
|
+
it_should_behave_like 'Type::Definition::Scalar'
|
72
|
+
it { should validate true }
|
73
|
+
it { should validate false }
|
74
|
+
it { should_not validate nil }
|
75
|
+
it { should_not validate 'true' }
|
76
|
+
it { should_not validate 'false' }
|
77
|
+
it { should cast(true).unchanged }
|
78
|
+
it { should cast(false).unchanged }
|
79
|
+
it { should cast(nil).to(false) }
|
80
|
+
it { should cast(Object.new).to(true) }
|
81
|
+
end
|
82
|
+
|
83
|
+
shared_examples_for 'Type::Float' do
|
84
|
+
it_should_behave_like 'Type::Definition::Scalar'
|
85
|
+
it { should_not cast(nil) }
|
86
|
+
it { should cast(10).to(10.0) }
|
87
|
+
it { should cast(12.3).unchanged }
|
88
|
+
it { should cast('12.3').to(12.3) }
|
89
|
+
it { should cast('123e-1').to(12.3) }
|
90
|
+
it { should cast('12.3e10').to(123000000000.0) }
|
91
|
+
it { should cast('123e10').to(1230000000000.0) }
|
92
|
+
it { should_not cast('a string') }
|
93
|
+
it { should_not cast(Hash.new) }
|
94
|
+
it { should validate(12.3) }
|
95
|
+
it { should_not validate(12) }
|
96
|
+
end
|
97
|
+
|
98
|
+
describe Type::Float do
|
99
|
+
include_examples 'Type::Float'
|
100
|
+
it { should validate(Float::INFINITY) }
|
101
|
+
it { should validate(-Float::INFINITY) }
|
102
|
+
end
|
103
|
+
|
104
|
+
describe Type::Float32 do
|
105
|
+
include_examples 'Type::Float'
|
106
|
+
it { should_not validate(Float::INFINITY) }
|
107
|
+
it { should_not validate(-Float::INFINITY) }
|
108
|
+
end
|
109
|
+
|
110
|
+
describe Type::Float64 do
|
111
|
+
include_examples 'Type::Float'
|
112
|
+
it { should_not validate(Float::INFINITY) }
|
113
|
+
it { should_not validate(-Float::INFINITY) }
|
114
|
+
end
|
115
|
+
|
116
|
+
describe Type::String do
|
117
|
+
its(:to_s) { should match(/Type::String/) }
|
118
|
+
it { should_not cast(nil) }
|
119
|
+
it_should_behave_like 'Type::Definition::Scalar'
|
120
|
+
it { should cast(:abc).to('abc') }
|
121
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: type
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-01
|
12
|
+
date: 2014-02-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -101,6 +101,7 @@ extra_rdoc_files: []
|
|
101
101
|
files:
|
102
102
|
- .githooks/pre-commit/ruby-appraiser
|
103
103
|
- .gitignore
|
104
|
+
- .travis.yml
|
104
105
|
- CHANGELOG.md
|
105
106
|
- CONTRIBUTING.md
|
106
107
|
- Gemfile
|
@@ -109,6 +110,8 @@ files:
|
|
109
110
|
- Rakefile
|
110
111
|
- lib/type.rb
|
111
112
|
- lib/type/builtin.rb
|
113
|
+
- lib/type/builtin/collection.rb
|
114
|
+
- lib/type/builtin/scalar.rb
|
112
115
|
- lib/type/definition.rb
|
113
116
|
- lib/type/definition/collection.rb
|
114
117
|
- lib/type/definition/collection/constrained.rb
|
@@ -117,7 +120,9 @@ files:
|
|
117
120
|
- lib/type/definition/scalar.rb
|
118
121
|
- lib/type/error.rb
|
119
122
|
- lib/type/version.rb
|
120
|
-
- spec/
|
123
|
+
- spec/spec_helper.rb
|
124
|
+
- spec/type/builtin/collection_spec.rb
|
125
|
+
- spec/type/builtin/scalar_spec.rb
|
121
126
|
- spec/type/definition_spec.rb
|
122
127
|
- type.gemspec
|
123
128
|
homepage: https://github.com/simplymeasured/type-gem
|
@@ -146,6 +151,8 @@ signing_key:
|
|
146
151
|
specification_version: 3
|
147
152
|
summary: Type validation and Type casting
|
148
153
|
test_files:
|
149
|
-
- spec/
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- spec/type/builtin/collection_spec.rb
|
156
|
+
- spec/type/builtin/scalar_spec.rb
|
150
157
|
- spec/type/definition_spec.rb
|
151
158
|
has_rdoc:
|
data/spec/type/builtin_spec.rb
DELETED
@@ -1,242 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'type'
|
3
|
-
|
4
|
-
RSpec::Matchers.define(:cast) do |input|
|
5
|
-
match do |definition|
|
6
|
-
begin
|
7
|
-
@actual = definition.cast!(input)
|
8
|
-
if @chained
|
9
|
-
failure_message_for_should do
|
10
|
-
"Expected result to be #{@expected.inspect}(#{@expected.class}) " +
|
11
|
-
"but got #{@actual.inspect}(#{@actual.class}) instead"
|
12
|
-
end
|
13
|
-
@expected == @actual
|
14
|
-
else
|
15
|
-
true
|
16
|
-
end
|
17
|
-
rescue Type::CastError => cast_error
|
18
|
-
failure_message_for_should do
|
19
|
-
"#{definition} failed to cast #{input.inspect}(#{input.class}) " +
|
20
|
-
"by raising #{cast_error}(#{cast_error.cause})."
|
21
|
-
end
|
22
|
-
false
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
description do
|
27
|
-
"cast #{input.inspect}(#{input.class})"
|
28
|
-
end
|
29
|
-
|
30
|
-
chain(:to) do |expected|
|
31
|
-
description do
|
32
|
-
"cast #{input.inspect}(#{input.class}) to #{expected.inspect}(#{expected.class})"
|
33
|
-
end
|
34
|
-
@chained = true
|
35
|
-
@expected = expected
|
36
|
-
end
|
37
|
-
|
38
|
-
chain(:unchanged) do
|
39
|
-
description do
|
40
|
-
"cast #{input.inspect}(#{input.class}) unchanged"
|
41
|
-
end
|
42
|
-
@chained = true
|
43
|
-
@expected = input
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
RSpec::Matchers.define :validate do |input|
|
48
|
-
match do |definition|
|
49
|
-
definition.valid?(input)
|
50
|
-
end
|
51
|
-
description do
|
52
|
-
"validate #{input.inspect}(#{input.class})"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
shared_examples_for 'Type::Definition::Nilable compatibility' do
|
57
|
-
context 'when nilable' do
|
58
|
-
subject { described_class.nilable }
|
59
|
-
it { should be_a_kind_of Type::Definition::Nilable }
|
60
|
-
it { should be_nilable }
|
61
|
-
it { should cast(nil).to(nil) }
|
62
|
-
it { should validate(nil) }
|
63
|
-
it { should_not cast(Object.new) unless described_class == Type::String }
|
64
|
-
end
|
65
|
-
it { should_not be_a_kind_of Type::Definition::Nilable }
|
66
|
-
it { should_not be_nilable }
|
67
|
-
it { should_not cast(nil) }
|
68
|
-
it { should_not validate(nil) }
|
69
|
-
it { should_not cast(Object.new) unless described_class == Type::String }
|
70
|
-
end
|
71
|
-
|
72
|
-
shared_examples_for 'Type::Definition::Scalar' do
|
73
|
-
include_examples 'Type::Definition::Nilable compatibility'
|
74
|
-
it { should be_a_kind_of Type::Definition }
|
75
|
-
it { should be_a_kind_of Type::Definition::Scalar }
|
76
|
-
end
|
77
|
-
|
78
|
-
shared_examples_for 'Type::Integer' do
|
79
|
-
it_should_behave_like 'Type::Definition::Scalar'
|
80
|
-
|
81
|
-
it { should cast(414).unchanged }
|
82
|
-
it { should cast('123').to(123) }
|
83
|
-
it { should cast(456).to(456) }
|
84
|
-
it { should cast(Math::PI).to(3) } # alabama ftw
|
85
|
-
|
86
|
-
it { should_not cast('not a number') }
|
87
|
-
it { should_not cast(Hash.new) }
|
88
|
-
|
89
|
-
it { should validate(123) }
|
90
|
-
it { should_not validate('123') }
|
91
|
-
end
|
92
|
-
|
93
|
-
shared_examples_for 'bounded Type::Integer' do
|
94
|
-
it_should_behave_like 'Type::Integer'
|
95
|
-
|
96
|
-
let(:range_max) { valid_range.end - (valid_range.exclude_end? ? 1 : 0) }
|
97
|
-
let(:range_min) { valid_range.begin }
|
98
|
-
|
99
|
-
it { should cast(range_max).unchanged }
|
100
|
-
it { should cast(range_min).unchanged }
|
101
|
-
|
102
|
-
it { should_not cast(range_max.next) }
|
103
|
-
it { should_not cast(range_min.pred) }
|
104
|
-
|
105
|
-
it { should validate(range_max) }
|
106
|
-
it { should_not validate(range_max.next) }
|
107
|
-
end
|
108
|
-
|
109
|
-
describe Type::Integer do
|
110
|
-
it_should_behave_like 'Type::Integer'
|
111
|
-
end
|
112
|
-
|
113
|
-
describe Type::Int32 do
|
114
|
-
let(:valid_range) { (-1 << 31)...(1 << 31) }
|
115
|
-
it_should_behave_like 'bounded Type::Integer'
|
116
|
-
end
|
117
|
-
|
118
|
-
describe Type::Int64 do
|
119
|
-
let(:valid_range) { (-1 << 63)...(1 << 63) }
|
120
|
-
it_should_behave_like 'bounded Type::Integer'
|
121
|
-
end
|
122
|
-
|
123
|
-
describe Type::UInt32 do
|
124
|
-
let(:valid_range) { 0...(1 << 32) }
|
125
|
-
it_should_behave_like 'bounded Type::Integer'
|
126
|
-
end
|
127
|
-
|
128
|
-
describe Type::UInt64 do
|
129
|
-
let(:valid_range) { 0...(1 << 64) }
|
130
|
-
it_should_behave_like 'bounded Type::Integer'
|
131
|
-
end
|
132
|
-
|
133
|
-
describe Type::Boolean do
|
134
|
-
it_should_behave_like 'Type::Definition::Scalar'
|
135
|
-
it { should validate true }
|
136
|
-
it { should validate false }
|
137
|
-
it { should_not validate nil }
|
138
|
-
it { should_not validate 'true' }
|
139
|
-
it { should_not validate 'false' }
|
140
|
-
it { should cast(true).unchanged }
|
141
|
-
it { should cast(false).unchanged }
|
142
|
-
end
|
143
|
-
|
144
|
-
shared_examples_for 'Type::Float' do
|
145
|
-
it_should_behave_like 'Type::Definition::Scalar'
|
146
|
-
it { should cast(10).to(10.0) }
|
147
|
-
it { should cast(12.3).unchanged }
|
148
|
-
it { should cast('12.3').to(12.3) }
|
149
|
-
it { should cast('123e-1').to(12.3) }
|
150
|
-
it { should cast('12.3e10').to(123000000000.0) }
|
151
|
-
it { should cast('123e10').to(1230000000000.0) }
|
152
|
-
it { should_not cast('a string') }
|
153
|
-
it { should_not cast(Hash.new) }
|
154
|
-
it { should validate(12.3) }
|
155
|
-
it { should_not validate(12) }
|
156
|
-
end
|
157
|
-
|
158
|
-
describe Type::Float do
|
159
|
-
include_examples 'Type::Float'
|
160
|
-
it { should validate(Float::INFINITY) }
|
161
|
-
it { should validate(-Float::INFINITY) }
|
162
|
-
end
|
163
|
-
|
164
|
-
describe Type::Float32 do
|
165
|
-
include_examples 'Type::Float'
|
166
|
-
it { should_not validate(Float::INFINITY) }
|
167
|
-
it { should_not validate(-Float::INFINITY) }
|
168
|
-
end
|
169
|
-
|
170
|
-
describe Type::Float64 do
|
171
|
-
include_examples 'Type::Float'
|
172
|
-
it { should_not validate(Float::INFINITY) }
|
173
|
-
it { should_not validate(-Float::INFINITY) }
|
174
|
-
end
|
175
|
-
|
176
|
-
describe Type::String do
|
177
|
-
its(:to_s) { should match(/Type::String/) }
|
178
|
-
it_should_behave_like 'Type::Definition::Scalar'
|
179
|
-
it { should cast(:abc).to('abc') }
|
180
|
-
end
|
181
|
-
|
182
|
-
describe Type::Array do
|
183
|
-
its(:to_s) { should match(/Type::Array/) }
|
184
|
-
it { should be_a_kind_of Type::Definition::Collection }
|
185
|
-
it { should validate(['asdf']) }
|
186
|
-
it { should cast(['foo']).unchanged }
|
187
|
-
it { should cast(['asdf', 1]).unchanged }
|
188
|
-
end
|
189
|
-
|
190
|
-
describe Type::Array.of(:String) do
|
191
|
-
its(:to_s) { should match(/Type::Array\(.*String.*\)/) }
|
192
|
-
it { should be_a_kind_of Type::Definition::Collection::Constrained }
|
193
|
-
it { should validate(['asdf']) }
|
194
|
-
it { should_not validate([nil, 'asdf']) }
|
195
|
-
it { should_not validate([:asdf]) }
|
196
|
-
it { should cast([:abc, 1]).to(['abc', '1']) }
|
197
|
-
it { should_not cast([nil, 1]) }
|
198
|
-
end
|
199
|
-
|
200
|
-
describe Type::Array.of(:String?) do
|
201
|
-
it { should be_a_kind_of Type::Definition::Collection::Constrained }
|
202
|
-
it { should validate(['asdf']) }
|
203
|
-
it { should validate([nil, 'asdf']) }
|
204
|
-
it { should_not validate([:asdf]) }
|
205
|
-
it { should cast([:abc, 1]).to(['abc', '1']) }
|
206
|
-
it { should cast([nil, 1]).to([nil, '1']) }
|
207
|
-
end
|
208
|
-
|
209
|
-
describe Type::Hash do
|
210
|
-
its(:to_s) { should match(/Type::Hash/) }
|
211
|
-
it { should cast([[1, 2], [3, 4]]).to(1 => 2, 3 => 4) }
|
212
|
-
it { should_not cast(17) }
|
213
|
-
end
|
214
|
-
|
215
|
-
describe Type::Hash.of(:String => :Integer) do
|
216
|
-
its(:to_s) { should match(/Type::Hash\(.*String.*Integer.*\)/) }
|
217
|
-
it { should be_a_kind_of Type::Definition::Collection::Constrained }
|
218
|
-
it { should validate('foo' => 12) }
|
219
|
-
it { should_not validate(foo: 12) }
|
220
|
-
it { should_not validate('foo' => '12') }
|
221
|
-
it { should cast('foo' => '12', :bar => 3).to('foo' => 12, 'bar' => 3) }
|
222
|
-
it { should cast('foo' => 12, 'bar' => 3).unchanged }
|
223
|
-
it { should cast([['12', 34], [56, '78']]).to('12' => 34, '56' => 78) }
|
224
|
-
it { should_not cast('foo' => 'foo') }
|
225
|
-
end
|
226
|
-
|
227
|
-
describe Type::Set do
|
228
|
-
it { should_not validate([123, 456]) }
|
229
|
-
it { should validate(Set.new([123, 456])) }
|
230
|
-
it { should_not validate(17) }
|
231
|
-
it { should cast([123, 456]).to(Set.new([123, 456])) }
|
232
|
-
it { should cast(Set.new([123, 456])).to(Set.new([123, 456])) }
|
233
|
-
it { should_not cast(17) }
|
234
|
-
end
|
235
|
-
|
236
|
-
describe Type::Set.of(:Integer) do
|
237
|
-
its(:to_s) { should match(/Type::Set(.*Integer.*)/) }
|
238
|
-
it { should validate(Set.new([1, 2, 3, 4])) }
|
239
|
-
it { should_not validate([1, 2, 3, 4]) }
|
240
|
-
it { should cast(Set.new([1, 2, 3, 4])).unchanged }
|
241
|
-
it { should cast([1, 2, 3, 4]).to(Set.new([1, 2, 3, 4])) }
|
242
|
-
end
|