emery 0.0.1 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/emery/type.rb CHANGED
@@ -1,176 +1,185 @@
1
- module Emery
2
- module T
3
- def T.check_not_nil(type, value)
4
- if value == nil
5
- raise TypeError.new("Type #{type.to_s} does not allow nil value")
6
- end
1
+ module T
2
+ def T.check_not_nil(type, value)
3
+ if value == nil
4
+ raise TypeError.new("Type #{type.to_s} does not allow nil value")
7
5
  end
6
+ end
8
7
 
9
- class UntypedType
10
- def to_s
11
- "Untyped"
12
- end
13
- def check(value)
14
- T.check_not_nil(self, value)
15
- end
8
+ class UntypedType
9
+ def to_s
10
+ "Untyped"
16
11
  end
12
+ def check(value)
13
+ T.check_not_nil(self, value)
14
+ end
15
+ end
17
16
 
18
- class Nilable
19
- attr_reader :type
20
- def initialize(type)
21
- @type = type
22
- end
23
- def to_s
24
- "Nilable[#{type.to_s}]"
25
- end
26
- def check(value)
27
- if value != nil
28
- T.check(type, value)
29
- end
30
- end
31
- def ==(other)
32
- T.instance_of?(Nilable, other) and self.type == other.type
17
+ class Nilable
18
+ attr_reader :type
19
+ def initialize(type)
20
+ @type = type
21
+ end
22
+ def to_s
23
+ "Nilable[#{type.to_s}]"
24
+ end
25
+ def check(value)
26
+ if value != nil
27
+ T.check(type, value)
33
28
  end
34
29
  end
30
+ def ==(other)
31
+ T.instance_of?(Nilable, other) and self.type == other.type
32
+ end
33
+ end
35
34
 
36
- class AnyType
37
- attr_reader :types
38
- def initialize(*types)
39
- @types = types
40
- end
41
- def to_s
42
- "Any[#{types.map { |t| t.to_s}.join(', ')}]"
43
- end
44
- def check(value)
45
- types.each do |type|
46
- begin
47
- T.check(type, value)
48
- return
49
- rescue TypeError
50
- end
51
- end
35
+ class AnyType
36
+ attr_reader :types
37
+ def initialize(*types)
38
+ @types = types
39
+ end
40
+ def to_s
41
+ "Any[#{types.map { |t| t.to_s}.join(', ')}]"
42
+ end
43
+ def check(value)
44
+ type = types.find {|t| T.instance_of?(t, value) }
45
+ if type == nil
52
46
  raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - any of #{@types.map { |t| t.to_s}.join(', ')} required")
53
47
  end
54
- def ==(other)
55
- T.instance_of?(AnyType, other) and (self.types - other.types).empty?
56
- end
57
48
  end
49
+ def ==(other)
50
+ T.instance_of?(AnyType, other) and (self.types - other.types).empty?
51
+ end
52
+ end
58
53
 
59
- class ArrayType
60
- attr_reader :item_type
61
- def initialize(item_type)
62
- @item_type = item_type
63
- end
64
- def to_s
65
- "Array[#{item_type.to_s}]"
66
- end
67
- def check(value)
68
- T.check_not_nil(self, value)
69
- if !value.is_a? Array
70
- raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - Array is required")
71
- end
72
- value.each { |item_value| T.check(item_type, item_value) }
73
- end
74
- def ==(other)
75
- T.instance_of?(ArrayType, other) and self.item_type == other.item_type
76
- end
54
+ class UnionType < AnyType
55
+ attr_reader :cases
56
+ def initialize(cases)
57
+ @cases = cases
58
+ super(*cases.values)
77
59
  end
60
+ def to_s
61
+ "Union[#{cases.map { |k, t| "#{k}: #{t}"}.join(', ')}]"
62
+ end
63
+ end
78
64
 
79
- class HashType
80
- attr_reader :key_type
81
- attr_reader :value_type
82
- def initialize(key_type, value_type)
83
- @key_type = key_type
84
- @value_type = value_type
85
- end
86
- def to_s
87
- "Hash[#{@key_type.to_s}, #{@value_type.to_s}]"
88
- end
89
- def check(value)
90
- T.check_not_nil(self, value)
91
- if !value.is_a? Hash
92
- raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - Hash is required")
93
- end
94
- value.each do |item_key, item_value|
95
- T.check(@key_type, item_key)
96
- T.check(@value_type, item_value)
97
- end
98
- end
99
- def ==(other)
100
- T.instance_of?(HashType, other) and self.key_type == other.key_type and self.value_type == other.value_type
65
+ class ArrayType
66
+ attr_reader :item_type
67
+ def initialize(item_type)
68
+ @item_type = item_type
69
+ end
70
+ def to_s
71
+ "Array[#{item_type.to_s}]"
72
+ end
73
+ def check(value)
74
+ T.check_not_nil(self, value)
75
+ if !value.is_a? Array
76
+ raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - Array is required")
101
77
  end
78
+ value.each { |item_value| T.check(item_type, item_value) }
102
79
  end
80
+ def ==(other)
81
+ T.instance_of?(ArrayType, other) and self.item_type == other.item_type
82
+ end
83
+ end
103
84
 
104
- class StringFormatted
105
- attr_reader :regex
106
- def initialize(regex)
107
- @regex = regex
108
- end
109
- def to_s
110
- "String<#@regex>"
85
+ class HashType
86
+ attr_reader :key_type
87
+ attr_reader :value_type
88
+ def initialize(key_type, value_type)
89
+ @key_type = key_type
90
+ @value_type = value_type
91
+ end
92
+ def to_s
93
+ "Hash[#{@key_type.to_s}, #{@value_type.to_s}]"
94
+ end
95
+ def check(value)
96
+ T.check_not_nil(self, value)
97
+ if !value.is_a? Hash
98
+ raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - Hash is required")
111
99
  end
112
- def check(value)
113
- T.check_not_nil(self, value)
114
- if !value.is_a? String
115
- raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - String is required for StringFormatted")
116
- end
117
- if !@regex.match?(value)
118
- raise TypeError.new("Value '#{value.inspect.to_s}' is not in required format '#{@regex}'")
119
- end
100
+ value.each do |item_key, item_value|
101
+ T.check(@key_type, item_key)
102
+ T.check(@value_type, item_value)
120
103
  end
121
104
  end
105
+ def ==(other)
106
+ T.instance_of?(HashType, other) and self.key_type == other.key_type and self.value_type == other.value_type
107
+ end
108
+ end
122
109
 
123
- def T.check(type, value)
124
- if type.methods.include? :check
125
- type.check(value)
126
- else
127
- if type != NilClass
128
- T.check_not_nil(type, value)
129
- end
130
- if !value.is_a? type
131
- raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - #{type} is required")
132
- end
110
+ class StringFormatted
111
+ attr_reader :regex
112
+ def initialize(regex)
113
+ @regex = regex
114
+ end
115
+ def to_s
116
+ "String<#@regex>"
117
+ end
118
+ def check(value)
119
+ T.check_not_nil(self, value)
120
+ if !value.is_a? String
121
+ raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - String is required for StringFormatted")
122
+ end
123
+ if !@regex.match?(value)
124
+ raise TypeError.new("Value '#{value.inspect.to_s}' is not in required format '#{@regex}'")
133
125
  end
134
- return value
135
126
  end
127
+ end
136
128
 
137
- def T.instance_of?(type, value)
138
- begin
139
- T.check(type, value)
140
- true
141
- rescue TypeError
142
- false
129
+ def T.check(type, value)
130
+ if type.methods.include? :check
131
+ type.check(value)
132
+ else
133
+ if type != NilClass
134
+ T.check_not_nil(type, value)
135
+ end
136
+ if !value.is_a? type
137
+ raise TypeError.new("Value '#{value.inspect.to_s}' type is #{value.class} - #{type} is required")
143
138
  end
144
139
  end
140
+ return value
141
+ end
145
142
 
146
- def T.nilable(value_type)
147
- Nilable.new(value_type)
143
+ def T.instance_of?(type, value)
144
+ begin
145
+ T.check(type, value)
146
+ true
147
+ rescue TypeError
148
+ false
148
149
  end
150
+ end
149
151
 
150
- def T.array(item_type)
151
- ArrayType.new(item_type)
152
- end
152
+ def T.nilable(value_type)
153
+ Nilable.new(value_type)
154
+ end
153
155
 
154
- def T.hash(key_type, value_type)
155
- HashType.new(key_type, value_type)
156
- end
156
+ def T.array(item_type)
157
+ ArrayType.new(item_type)
158
+ end
157
159
 
158
- def T.any(*typdefs)
159
- AnyType.new(*typdefs)
160
- end
160
+ def T.hash(key_type, value_type)
161
+ HashType.new(key_type, value_type)
162
+ end
161
163
 
162
- def T.check_var(var_name, type, value)
163
- begin
164
- check(type, value)
165
- return value
166
- rescue TypeError => e
167
- raise TypeError.new("Variable #{var_name} type check failed, expected type: #{type.to_s}, value: #{value}")
168
- end
164
+ def T.any(*typdefs)
165
+ AnyType.new(*typdefs)
166
+ end
167
+
168
+ def T.union(*cases)
169
+ UnionType.new(*cases)
170
+ end
171
+
172
+ def T.check_var(var_name, type, value)
173
+ begin
174
+ check(type, value)
175
+ return value
176
+ rescue TypeError => e
177
+ raise TypeError.new("Variable #{var_name} type check failed, expected type: #{type.to_s}, value: #{value}")
169
178
  end
170
179
  end
180
+ end
171
181
 
172
- Boolean = T.any(TrueClass, FalseClass)
173
- Untyped = T::UntypedType.new
174
- NilableUntyped = T.nilable(Untyped)
175
- UUID = T::StringFormatted.new(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
176
- end
182
+ Boolean = T.any(TrueClass, FalseClass)
183
+ Untyped = T::UntypedType.new
184
+ NilableUntyped = T.nilable(Untyped)
185
+ UUID = T::StringFormatted.new(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
@@ -1,115 +1,112 @@
1
1
  require "test/unit/runner/junitxml"
2
2
 
3
- require 'emery/dataclass'
4
- require 'emery/jsoner'
3
+ require 'emery'
5
4
 
6
- module Emery
7
- class DataClassTypeEquality < Test::Unit::TestCase
8
- def test_equals
9
- assert_true TheClass == TheClass
10
- end
5
+ class DataClassTypeEquality < Test::Unit::TestCase
6
+ def test_equals
7
+ assert_true TheClass == TheClass
8
+ end
11
9
 
12
- def test_not_equals
13
- assert_false TheClass == TheClassWithNested
14
- end
10
+ def test_not_equals
11
+ assert_false TheClass == TheClassWithNested
15
12
  end
13
+ end
16
14
 
17
- class DataClassFields < Test::Unit::TestCase
18
- def test_fields_meta
19
- assert_equal ({:string => String, :int => Integer}), TheClass.json_attributes, "Attributes with types should be available on data class"
20
- end
15
+ class DataClassFields < Test::Unit::TestCase
16
+ def test_fields_meta
17
+ assert_equal ({:string => String, :int => Integer}), TheClass.json_attributes, "Attributes with types should be available on data class"
18
+ end
21
19
 
22
- def test_read
23
- obj = TheClass.new(string: "the string", int: 123)
24
- assert_equal "the string", obj.string, "Immutable field should be readable"
25
- assert_equal 123, obj.int, "Mutable field should be readable"
26
- end
20
+ def test_read
21
+ obj = TheClass.new(string: "the string", int: 123)
22
+ assert_equal "the string", obj.string, "Immutable field should be readable"
23
+ assert_equal 123, obj.int, "Mutable field should be readable"
24
+ end
27
25
 
28
- def test_write_mutable
29
- obj = TheClass.new(string: "the string", int: 123)
30
- obj.int = 124
31
- assert_equal 124, obj.int, "Mutable field should be writable"
32
- end
26
+ def test_write_mutable
27
+ obj = TheClass.new(string: "the string", int: 123)
28
+ obj.int = 124
29
+ assert_equal 124, obj.int, "Mutable field should be writable"
30
+ end
33
31
 
34
- def test_write_immutable
35
- obj = TheClass.new(string: "the string", int: 123)
36
- assert_raise NoMethodError do
37
- obj.string = "the other string"
38
- end
32
+ def test_write_immutable
33
+ obj = TheClass.new(string: "the string", int: 123)
34
+ assert_raise NoMethodError do
35
+ obj.string = "the other string"
39
36
  end
40
37
  end
38
+ end
41
39
 
42
- class DataClassEquality < Test::Unit::TestCase
43
- def test_nil
44
- assert_not_equal nil, TheClass.new(string: "the string", int: 123), "Object should not be equal to nil"
45
- end
40
+ class DataClassEquality < Test::Unit::TestCase
41
+ def test_nil
42
+ assert_not_equal nil, TheClass.new(string: "the string", int: 123), "Object should not be equal to nil"
43
+ end
46
44
 
47
- def test_same_fields_values
48
- assert_equal TheClass.new(string: "the string", int: 123), TheClass.new(string: "the string", int: 123), "Objects with same fields should be equal"
49
- end
45
+ def test_same_fields_values
46
+ assert_equal TheClass.new(string: "the string", int: 123), TheClass.new(string: "the string", int: 123), "Objects with same fields should be equal"
47
+ end
50
48
 
51
- def test_different_fields_values
52
- assert_not_equal TheClass.new(string: "the string 2", int: 123), TheClass.new(string: "the string", int: 123), "Objects with different fields should not be equal"
53
- end
49
+ def test_different_fields_values
50
+ assert_not_equal TheClass.new(string: "the string 2", int: 123), TheClass.new(string: "the string", int: 123), "Objects with different fields should not be equal"
54
51
  end
52
+ end
55
53
 
56
- class DataClassDeserialization < Test::Unit::TestCase
57
- def test_deserialize_object
58
- data = Jsoner.from_json(TheClass, '{"string": "the string", "int": 123}')
59
- T.check(TheClass, data)
60
- assert_equal TheClass.new(string: "the string", int: 123), data, "Should parse data class object"
61
- end
54
+ class DataClassDeserialization < Test::Unit::TestCase
55
+ def test_deserialize_object
56
+ data = Jsoner.from_json(TheClass, '{"string": "the string", "int": 123}')
57
+ T.check(TheClass, data)
58
+ assert_equal TheClass.new(string: "the string", int: 123), data, "Should parse data class object"
59
+ end
62
60
 
63
- def test_deserialize_nested_object
64
- data = Jsoner.from_json(TheClassWithNested, '{"nested": {"string": "the string", "int": 123}}')
65
- T.check(TheClassWithNested, data)
66
- assert_equal TheClassWithNested.new(nested: TheClass.new(string: "the string", int: 123)), data, "Should parse nested data class object"
67
- end
61
+ def test_deserialize_nested_object
62
+ data = Jsoner.from_json(TheClassWithNested, '{"nested": {"string": "the string", "int": 123}}')
63
+ T.check(TheClassWithNested, data)
64
+ assert_equal TheClassWithNested.new(nested: TheClass.new(string: "the string", int: 123)), data, "Should parse nested data class object"
65
+ end
68
66
 
69
- def test_deserialize_object_fail
70
- assert_raise JsonerError do
71
- Jsoner.from_json(TheClass, '"string"')
72
- end
67
+ def test_deserialize_object_fail
68
+ assert_raise JsonerError do
69
+ Jsoner.from_json(TheClass, '"string"')
73
70
  end
74
-
75
71
  end
76
72
 
77
- class DataClassSerialization < Test::Unit::TestCase
78
- def test_serialize_object
79
- assert_equal '{"string":"the string","int":123}', Jsoner.to_json(TheClass, TheClass.new(string: "the string", int: 123)), "nil should be serializable to JSON"
80
- end
73
+ end
81
74
 
82
- def test_serialize_array_of_objects
83
- assert_equal '[{"string":"the string","int":123},{"string":"the string 2","int":456}]', Jsoner.to_json(T.array(TheClass), [TheClass.new(string: "the string", int: 123), TheClass.new(string: "the string 2", int: 456)]), "Array of objects should be serializable to JSON"
84
- end
75
+ class DataClassSerialization < Test::Unit::TestCase
76
+ def test_serialize_object
77
+ assert_equal '{"string":"the string","int":123}', Jsoner.to_json(TheClass, TheClass.new(string: "the string", int: 123)), "nil should be serializable to JSON"
85
78
  end
86
79
 
87
- class DataClassCopy < Test::Unit::TestCase
88
- def test_copy
89
- a = TheClass.new(string: "the string", int: 123)
90
- b = a.copy(string: "the other string")
91
- assert_equal "the string", a.string
92
- assert_equal "the other string", b.string
93
- end
80
+ def test_serialize_array_of_objects
81
+ assert_equal '[{"string":"the string","int":123},{"string":"the string 2","int":456}]', Jsoner.to_json(T.array(TheClass), [TheClass.new(string: "the string", int: 123), TheClass.new(string: "the string 2", int: 456)]), "Array of objects should be serializable to JSON"
82
+ end
83
+ end
84
+
85
+ class DataClassCopy < Test::Unit::TestCase
86
+ def test_copy
87
+ a = TheClass.new(string: "the string", int: 123)
88
+ b = a.copy(string: "the other string")
89
+ assert_equal "the string", a.string
90
+ assert_equal "the other string", b.string
91
+ end
94
92
 
95
- def test_copy_non_existing_field
96
- assert_raise TypeError do
97
- a = TheClass.new(string: "the string", int: 123)
98
- a.copy(non_existing: "the other string")
99
- end
93
+ def test_copy_non_existing_field
94
+ assert_raise TypeError do
95
+ a = TheClass.new(string: "the string", int: 123)
96
+ a.copy(non_existing: "the other string")
100
97
  end
101
98
  end
99
+ end
102
100
 
103
- class TheClass
104
- include DataClass
101
+ class TheClass
102
+ include DataClass
105
103
 
106
- val :string, String
107
- var :int, Integer
108
- end
104
+ val :string, String
105
+ var :int, Integer
106
+ end
109
107
 
110
- class TheClassWithNested
111
- include DataClass
108
+ class TheClassWithNested
109
+ include DataClass
112
110
 
113
- val :nested, TheClass
114
- end
111
+ val :nested, TheClass
115
112
  end