type 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|