camille 0.6.0 → 0.6.1
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/benchmarks/checking_class_of_object.rb +7 -0
- data/lib/camille/basic_type.rb +8 -5
- data/lib/camille/checked.rb +18 -0
- data/lib/camille/controller.rb +4 -4
- data/lib/camille/intersection_preprocessor.rb +61 -0
- data/lib/camille/intersection_solver.rb +50 -0
- data/lib/camille/pick_and_omit.rb +5 -0
- data/lib/camille/type.rb +6 -8
- data/lib/camille/type_error.rb +8 -0
- data/lib/camille/types/any.rb +1 -0
- data/lib/camille/types/array.rb +15 -10
- data/lib/camille/types/boolean.rb +3 -1
- data/lib/camille/types/boolean_literal.rb +4 -1
- data/lib/camille/types/intersection.rb +11 -12
- data/lib/camille/types/null.rb +3 -1
- data/lib/camille/types/number.rb +3 -2
- data/lib/camille/types/number_literal.rb +4 -1
- data/lib/camille/types/object.rb +30 -18
- data/lib/camille/types/omit.rb +0 -3
- data/lib/camille/types/pick.rb +0 -3
- data/lib/camille/types/record.rb +21 -18
- data/lib/camille/types/string.rb +3 -1
- data/lib/camille/types/string_literal.rb +4 -1
- data/lib/camille/types/tuple.rb +15 -11
- data/lib/camille/types/undefined.rb +3 -1
- data/lib/camille/types/union.rb +11 -11
- data/lib/camille/version.rb +1 -1
- data/lib/camille.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7812549c03ae51c6ea3883e982770259b1757ce13b9014e63f21bf87a6522e02
|
|
4
|
+
data.tar.gz: b562c295c761474368defa4aaa5ba544a5c82d54ad013eafcd1fee5305a68b82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 924f63558e9540098f5073ed9b806b3e73169324ad5dea5da1967f275922396f81e21db4968ba3298baa3443d65ae09561c8612a87da15dfa1549ea1404cfafb
|
|
7
|
+
data.tar.gz: 60fe1b8a0baf1df20417518f74e977431f7f9df30b1d5a6087bdef96a78b40d6782f2677f9bbdcac44e674cd676456b50c719ec0e194b069edaa8960afc7e7c5
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/lib/camille/basic_type.rb
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
|
|
1
3
|
module Camille
|
|
2
4
|
# This class specifies the methods available for all types includeing built-in and custom ones.
|
|
3
5
|
class BasicType
|
|
4
6
|
class InvalidTypeError < ::ArgumentError; end
|
|
5
7
|
|
|
8
|
+
attr_reader :fingerprint
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@fingerprint = Digest::MD5.hexdigest self.class.name
|
|
12
|
+
end
|
|
13
|
+
|
|
6
14
|
def | other
|
|
7
15
|
Camille::Types::Union.new(self, other)
|
|
8
16
|
end
|
|
@@ -15,11 +23,6 @@ module Camille
|
|
|
15
23
|
Camille::Types::Array.new(self)
|
|
16
24
|
end
|
|
17
25
|
|
|
18
|
-
def transform_and_check value
|
|
19
|
-
transformed = transform value
|
|
20
|
-
[check(value), transformed]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
26
|
def transform value
|
|
24
27
|
value
|
|
25
28
|
end
|
data/lib/camille/controller.rb
CHANGED
|
@@ -19,13 +19,13 @@ module Camille
|
|
|
19
19
|
if intended_status == 200 || intended_status == :ok
|
|
20
20
|
if render_options.has_key? :json
|
|
21
21
|
value = render_options[:json]
|
|
22
|
-
|
|
23
|
-
if
|
|
22
|
+
result = endpoint.response_type.check(value)
|
|
23
|
+
if result.type_error?
|
|
24
24
|
string_io = StringIO.new
|
|
25
|
-
Camille::TypeErrorPrinter.new(
|
|
25
|
+
Camille::TypeErrorPrinter.new(result).print(string_io)
|
|
26
26
|
raise TypeError.new("\nType check failed for response.\n#{string_io.string}")
|
|
27
27
|
else
|
|
28
|
-
super(json:
|
|
28
|
+
super(json: result.value)
|
|
29
29
|
end
|
|
30
30
|
else
|
|
31
31
|
raise ArgumentError.new("Expected key :json for `render` call.")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Camille
|
|
2
|
+
class IntersectionPreprocessor
|
|
3
|
+
class TypeNotCompatibleError < ArgumentError; end
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def process left, right
|
|
7
|
+
if left.instance_of?(Camille::Types::Object) && right.instance_of?(Camille::Types::Object)
|
|
8
|
+
overlapping_keys = left.fields.keys & right.fields.keys
|
|
9
|
+
|
|
10
|
+
new_left_fields = []
|
|
11
|
+
new_left_optional_keys = []
|
|
12
|
+
new_right_fields = []
|
|
13
|
+
new_right_optional_keys = []
|
|
14
|
+
|
|
15
|
+
(left.fields.keys - overlapping_keys).each do |key|
|
|
16
|
+
new_left_fields << [key, left.fields[key]]
|
|
17
|
+
new_left_optional_keys << key if left.optional_keys.include?(key)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
(right.fields.keys - overlapping_keys).each do |key|
|
|
21
|
+
new_right_fields << [key, right.fields[key]]
|
|
22
|
+
new_right_optional_keys << key if right.optional_keys.include?(key)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
overlapping_keys.map do |key|
|
|
26
|
+
processed_left, processed_right = IntersectionPreprocessor.process(left.fields[key], right.fields[key])
|
|
27
|
+
new_left_fields << [key, processed_left]
|
|
28
|
+
new_right_fields << [key, processed_right]
|
|
29
|
+
|
|
30
|
+
if left.optional_keys.include?(key) && right.optional_keys.include?(key)
|
|
31
|
+
new_left_optional_keys << key
|
|
32
|
+
new_right_optional_keys << key
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
[
|
|
37
|
+
Camille::Types::Object.new(**(apply_optional_to_fields new_left_fields, new_left_optional_keys).to_h),
|
|
38
|
+
Camille::Types::Object.new(**(apply_optional_to_fields new_right_fields, new_right_optional_keys).to_h)
|
|
39
|
+
]
|
|
40
|
+
else
|
|
41
|
+
if left.literal == right.literal
|
|
42
|
+
[left, Camille::Types::Any.new]
|
|
43
|
+
else
|
|
44
|
+
raise TypeNotCompatibleError.new "Cannot reconcile type #{left.literal} and type #{right.literal}."
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
def apply_optional_to_fields fields, optional_keys
|
|
51
|
+
fields.map do |key, type|
|
|
52
|
+
if optional_keys.include?(key)
|
|
53
|
+
["#{key.to_s}?".to_sym, type]
|
|
54
|
+
else
|
|
55
|
+
[key, type]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Camille
|
|
2
|
+
class IntersectionSolver
|
|
3
|
+
class TypeNotCompatibleError < ArgumentError; end
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def solve a, b
|
|
7
|
+
if a.instance_of?(Camille::Types::Object) && b.instance_of?(Camille::Types::Object)
|
|
8
|
+
overlapping_keys = a.fields.keys & b.fields.keys
|
|
9
|
+
|
|
10
|
+
new_fields = []
|
|
11
|
+
new_optional_keys = []
|
|
12
|
+
|
|
13
|
+
[a, b].each do |x|
|
|
14
|
+
(x.fields.keys - overlapping_keys).each do |key|
|
|
15
|
+
new_fields << [key, x.fields[key]]
|
|
16
|
+
new_optional_keys << key if x.optional_keys.include?(key)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
overlapping_keys.map do |key|
|
|
21
|
+
solved = IntersectionSolver.solve(a.fields[key], b.fields[key])
|
|
22
|
+
new_fields << [key, solved]
|
|
23
|
+
if a.optional_keys.include?(key) && b.optional_keys.include?(key)
|
|
24
|
+
new_optional_keys << key
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Camille::Types::Object.new(**(apply_optional_to_fields new_fields, new_optional_keys).to_h)
|
|
29
|
+
else
|
|
30
|
+
if a.literal == b.literal
|
|
31
|
+
a
|
|
32
|
+
else
|
|
33
|
+
raise TypeNotCompatibleError.new "Cannot reconcile type #{a.literal} and type #{b.literal}."
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def apply_optional_to_fields fields, optional_keys
|
|
40
|
+
fields.map do |key, type|
|
|
41
|
+
if optional_keys.include?(key)
|
|
42
|
+
["#{key.to_s}?".to_sym, type]
|
|
43
|
+
else
|
|
44
|
+
[key, type]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -18,6 +18,11 @@ module Camille
|
|
|
18
18
|
raise ArgumentError.new("The second argument of #{klass_name} has to be an array of symbols. Got #{keys.inspect}.")
|
|
19
19
|
end
|
|
20
20
|
@keys = keys
|
|
21
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@type.fingerprint}#{@keys.sort}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check value
|
|
25
|
+
processed_object.check(value)
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
def literal
|
data/lib/camille/type.rb
CHANGED
|
@@ -11,24 +11,22 @@ module Camille
|
|
|
11
11
|
|
|
12
12
|
def self.alias_of type
|
|
13
13
|
underlying = Camille::Type.instance(type)
|
|
14
|
+
fingerprint = underlying.fingerprint
|
|
14
15
|
|
|
15
16
|
define_method(:initialize) do
|
|
16
17
|
@underlying = underlying
|
|
18
|
+
@fingerprint = fingerprint
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def check value
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def transform_and_check value
|
|
25
|
-
transformed = transform value
|
|
26
|
-
@underlying.transform_and_check transformed
|
|
23
|
+
normalized = transform value
|
|
24
|
+
@underlying.check normalized
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def test value
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
result = check value
|
|
29
|
+
result.type_error? ? result : nil
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
def self.test value
|
data/lib/camille/type_error.rb
CHANGED
data/lib/camille/types/any.rb
CHANGED
data/lib/camille/types/array.rb
CHANGED
|
@@ -5,25 +5,30 @@ module Camille
|
|
|
5
5
|
|
|
6
6
|
def initialize content
|
|
7
7
|
@content = Camille::Type.instance content
|
|
8
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@content.fingerprint}"
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
def
|
|
11
|
+
def check value
|
|
11
12
|
if value.is_a? ::Array
|
|
12
|
-
|
|
13
|
-
[index, @content.
|
|
13
|
+
results = value.map.with_index do |element, index|
|
|
14
|
+
[index, @content.check(element)]
|
|
14
15
|
end
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
|
|
17
|
+
errors = results.map do |index, result|
|
|
18
|
+
if result.type_error?
|
|
19
|
+
["array[#{index}]", result]
|
|
20
|
+
else
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
end.compact
|
|
18
24
|
|
|
19
25
|
if errors.empty?
|
|
20
|
-
|
|
21
|
-
[nil, transformed]
|
|
26
|
+
Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
|
|
22
27
|
else
|
|
23
|
-
|
|
28
|
+
Camille::TypeError.new(**errors.to_h)
|
|
24
29
|
end
|
|
25
30
|
else
|
|
26
|
-
|
|
31
|
+
Camille::TypeError.new("Expected array, got #{value.inspect}.")
|
|
27
32
|
end
|
|
28
33
|
end
|
|
29
34
|
|
|
@@ -2,7 +2,9 @@ module Camille
|
|
|
2
2
|
module Types
|
|
3
3
|
class Boolean < Camille::BasicType
|
|
4
4
|
def check value
|
|
5
|
-
|
|
5
|
+
if value == false || value == true
|
|
6
|
+
Camille::Checked.new(fingerprint, value)
|
|
7
|
+
else
|
|
6
8
|
Camille::TypeError.new("Expected boolean, got #{value.inspect}.")
|
|
7
9
|
end
|
|
8
10
|
end
|
|
@@ -8,13 +8,16 @@ module Camille
|
|
|
8
8
|
def initialize value
|
|
9
9
|
if value == true || value == false
|
|
10
10
|
@value = value
|
|
11
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@value}"
|
|
11
12
|
else
|
|
12
13
|
raise ArgumentError.new("Expecting true or false, got #{value.inspect}")
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def check value
|
|
17
|
-
|
|
18
|
+
if value == @value
|
|
19
|
+
Camille::Checked.new(fingerprint, value)
|
|
20
|
+
else
|
|
18
21
|
Camille::TypeError.new("Expected boolean literal #{@value.inspect}, got #{value.inspect}.")
|
|
19
22
|
end
|
|
20
23
|
end
|
|
@@ -8,24 +8,23 @@ module Camille
|
|
|
8
8
|
def initialize left, right
|
|
9
9
|
@left = Camille::Type.instance left
|
|
10
10
|
@right = Camille::Type.instance right
|
|
11
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{[@left.fingerprint, @right.fingerprint].sort}"
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
'intersection.left' =>
|
|
14
|
+
def check value
|
|
15
|
+
left_result = @left.check value
|
|
16
|
+
if left_result.type_error?
|
|
17
|
+
Camille::TypeError.new(
|
|
18
|
+
'intersection.left' => left_result
|
|
18
19
|
)
|
|
19
|
-
[error, nil]
|
|
20
20
|
else
|
|
21
|
-
|
|
22
|
-
if
|
|
23
|
-
|
|
24
|
-
'intersection.right' =>
|
|
21
|
+
right_result = @right.check left_result.value
|
|
22
|
+
if right_result.type_error?
|
|
23
|
+
Camille::TypeError.new(
|
|
24
|
+
'intersection.right' => right_result
|
|
25
25
|
)
|
|
26
|
-
[error, nil]
|
|
27
26
|
else
|
|
28
|
-
|
|
27
|
+
Camille::Checked.new(fingerprint, right_result.value)
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
end
|
data/lib/camille/types/null.rb
CHANGED
data/lib/camille/types/number.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
module Camille
|
|
2
2
|
module Types
|
|
3
3
|
class Number < Camille::BasicType
|
|
4
|
-
|
|
5
4
|
def check value
|
|
6
|
-
|
|
5
|
+
if value.is_a?(Integer) || value.is_a?(Float)
|
|
6
|
+
Camille::Checked.new(fingerprint, value)
|
|
7
|
+
else
|
|
7
8
|
Camille::TypeError.new("Expected an integer or a float, got #{value.inspect}.")
|
|
8
9
|
end
|
|
9
10
|
end
|
|
@@ -8,13 +8,16 @@ module Camille
|
|
|
8
8
|
def initialize value
|
|
9
9
|
if value.is_a?(Integer) || value.is_a?(Float)
|
|
10
10
|
@value = value
|
|
11
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@value}"
|
|
11
12
|
else
|
|
12
13
|
raise ArgumentError.new("Expecting an integer or a float, got #{value.inspect}")
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def check value
|
|
17
|
-
|
|
18
|
+
if value == @value
|
|
19
|
+
Camille::Checked.new(fingerprint, value)
|
|
20
|
+
else
|
|
18
21
|
Camille::TypeError.new("Expected number literal #{@value.inspect}, got #{value.inspect}.")
|
|
19
22
|
end
|
|
20
23
|
end
|
data/lib/camille/types/object.rb
CHANGED
|
@@ -7,37 +7,43 @@ module Camille
|
|
|
7
7
|
def initialize fields
|
|
8
8
|
@optional_keys = []
|
|
9
9
|
@fields = normalize_fields fields
|
|
10
|
+
@fingerprint = generate_fingerprint
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
def
|
|
13
|
+
def check value
|
|
13
14
|
if value.is_a? Hash
|
|
14
15
|
keys = (@fields.keys + value.keys).uniq
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
keys_to_check, keys_to_skip = keys.partition{|key| @fields[key]}
|
|
17
|
+
|
|
18
|
+
results = keys_to_check.map do |key|
|
|
19
|
+
type = @fields[key]
|
|
20
|
+
if @optional_keys.include?(key) && value[key].nil?
|
|
21
|
+
nil
|
|
22
|
+
else
|
|
23
|
+
[key, type.check(value[key])]
|
|
24
|
+
end
|
|
25
|
+
end.compact
|
|
26
|
+
|
|
27
|
+
errors = results.map do |key, result|
|
|
28
|
+
if result.type_error?
|
|
29
|
+
[key.to_s, result]
|
|
22
30
|
else
|
|
23
|
-
|
|
31
|
+
nil
|
|
24
32
|
end
|
|
25
33
|
end.compact
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
[key
|
|
29
|
-
end
|
|
35
|
+
skipped_pairs = keys_to_skip.map do |key|
|
|
36
|
+
[key, value[key]]
|
|
37
|
+
end
|
|
30
38
|
|
|
31
39
|
if errors.empty?
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
end.to_h
|
|
35
|
-
[nil, Camille::ObjectHash[transformed]]
|
|
40
|
+
object = Camille::ObjectHash[results.map{|key, checked| [key, checked.value]}.concat(skipped_pairs).to_h]
|
|
41
|
+
Camille::Checked.new(fingerprint, object)
|
|
36
42
|
else
|
|
37
|
-
|
|
43
|
+
Camille::TypeError.new(**errors.to_h)
|
|
38
44
|
end
|
|
39
45
|
else
|
|
40
|
-
|
|
46
|
+
Camille::TypeError.new("Expected hash, got #{value.inspect}.")
|
|
41
47
|
end
|
|
42
48
|
end
|
|
43
49
|
|
|
@@ -74,6 +80,12 @@ module Camille
|
|
|
74
80
|
raise ArgumentError.new("Only keys satisfying `key == key.to_s.camelize.underscore` can be used.")
|
|
75
81
|
end
|
|
76
82
|
end
|
|
83
|
+
|
|
84
|
+
def generate_fingerprint
|
|
85
|
+
sorted_fields = @fields.sort_by{|k, v| k}.map{|k, v| [k, v.fingerprint]}
|
|
86
|
+
sorted_optional_keys = @optional_keys.sort
|
|
87
|
+
Digest::MD5.hexdigest "#{self.class.name}#{sorted_fields}#{sorted_optional_keys}"
|
|
88
|
+
end
|
|
77
89
|
end
|
|
78
90
|
end
|
|
79
91
|
end
|
data/lib/camille/types/omit.rb
CHANGED
data/lib/camille/types/pick.rb
CHANGED
data/lib/camille/types/record.rb
CHANGED
|
@@ -7,27 +7,31 @@ module Camille
|
|
|
7
7
|
def initialize key, value
|
|
8
8
|
@key = Camille::Type.instance key
|
|
9
9
|
@value = Camille::Type.instance value
|
|
10
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@key.fingerprint}#{@value.fingerprint}"
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
def
|
|
13
|
+
def check value
|
|
13
14
|
if value.is_a? ::Hash
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
[index,
|
|
16
|
+
results = value.map.with_index do |(k, v), index|
|
|
17
|
+
[index, check_pair(k, v)]
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
errors =
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
errors = results.map do |index, result|
|
|
21
|
+
if result.instance_of?(Camille::TypeError)
|
|
22
|
+
["record[#{index}]", result]
|
|
23
|
+
else
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end.compact
|
|
22
27
|
|
|
23
28
|
if errors.empty?
|
|
24
|
-
|
|
25
|
-
[nil, transformed]
|
|
29
|
+
Camille::Checked.new(fingerprint, results.map{|_, result| [result[0].value, result[1].value]}.to_h)
|
|
26
30
|
else
|
|
27
|
-
|
|
31
|
+
Camille::TypeError.new(**errors.to_h)
|
|
28
32
|
end
|
|
29
33
|
else
|
|
30
|
-
|
|
34
|
+
Camille::TypeError.new("Expected hash, got #{value.inspect}.")
|
|
31
35
|
end
|
|
32
36
|
end
|
|
33
37
|
|
|
@@ -40,17 +44,16 @@ module Camille
|
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
private
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
def check_pair key, value
|
|
48
|
+
key_result = @key.check key
|
|
49
|
+
value_result = @value.check value
|
|
46
50
|
|
|
47
|
-
if
|
|
48
|
-
[
|
|
51
|
+
if key_result.checked? && value_result.checked?
|
|
52
|
+
[key_result, value_result]
|
|
49
53
|
else
|
|
50
|
-
errors = [['record.key',
|
|
54
|
+
errors = [['record.key', key_result], ['record.value', value_result]].select{|x| x[1].type_error?}.to_h
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
[error, nil]
|
|
56
|
+
Camille::TypeError.new(**errors)
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
end
|
data/lib/camille/types/string.rb
CHANGED
|
@@ -2,7 +2,9 @@ module Camille
|
|
|
2
2
|
module Types
|
|
3
3
|
class String < Camille::BasicType
|
|
4
4
|
def check value
|
|
5
|
-
|
|
5
|
+
if value.is_a?(::String) || value.is_a?(Symbol)
|
|
6
|
+
Camille::Checked.new(fingerprint, value)
|
|
7
|
+
else
|
|
6
8
|
Camille::TypeError.new("Expected string, got #{value.inspect}.")
|
|
7
9
|
end
|
|
8
10
|
end
|
|
@@ -8,6 +8,7 @@ module Camille
|
|
|
8
8
|
def initialize value
|
|
9
9
|
if value.is_a?(::String)
|
|
10
10
|
@value = value
|
|
11
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@value}"
|
|
11
12
|
else
|
|
12
13
|
raise ArgumentError.new("Expecting a string, got #{value.inspect}")
|
|
13
14
|
end
|
|
@@ -15,7 +16,9 @@ module Camille
|
|
|
15
16
|
|
|
16
17
|
def check value
|
|
17
18
|
transformed = value.is_a?(Symbol) ? value.to_s : value
|
|
18
|
-
|
|
19
|
+
if transformed == @value
|
|
20
|
+
Camille::Checked.new(fingerprint, value)
|
|
21
|
+
else
|
|
19
22
|
Camille::TypeError.new("Expected string literal #{@value.inspect}, got #{value.inspect}.")
|
|
20
23
|
end
|
|
21
24
|
end
|
data/lib/camille/types/tuple.rb
CHANGED
|
@@ -5,26 +5,30 @@ module Camille
|
|
|
5
5
|
|
|
6
6
|
def initialize elements
|
|
7
7
|
@elements = elements.map{|e| Camille::Type.instance e}
|
|
8
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{@elements.map(&:fingerprint)}"
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
def
|
|
11
|
-
if value.is_a?
|
|
12
|
-
|
|
13
|
-
[index, type.
|
|
11
|
+
def check value
|
|
12
|
+
if value.is_a?(::Array) && value.size == @elements.size
|
|
13
|
+
results = @elements.map.with_index do |type, index|
|
|
14
|
+
[index, type.check(value[index])]
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
errors =
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
errors = results.map do |index, result|
|
|
18
|
+
if result.type_error?
|
|
19
|
+
["tuple[#{index}]", result]
|
|
20
|
+
else
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
end.compact
|
|
19
24
|
|
|
20
25
|
if errors.empty?
|
|
21
|
-
|
|
22
|
-
[nil, transformed]
|
|
26
|
+
Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
|
|
23
27
|
else
|
|
24
|
-
|
|
28
|
+
Camille::TypeError.new(**errors.to_h)
|
|
25
29
|
end
|
|
26
30
|
else
|
|
27
|
-
|
|
31
|
+
Camille::TypeError.new("Expected array of size #{@elements.size}, got #{value.inspect}.")
|
|
28
32
|
end
|
|
29
33
|
end
|
|
30
34
|
|
data/lib/camille/types/union.rb
CHANGED
|
@@ -6,23 +6,23 @@ module Camille
|
|
|
6
6
|
def initialize left, right
|
|
7
7
|
@left = Camille::Type.instance left
|
|
8
8
|
@right = Camille::Type.instance right
|
|
9
|
+
@fingerprint = Digest::MD5.hexdigest "#{self.class.name}#{[@left.fingerprint, @right.fingerprint].sort}"
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
def
|
|
12
|
-
|
|
13
|
-
if
|
|
14
|
-
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
'union.left' =>
|
|
18
|
-
'union.right' =>
|
|
12
|
+
def check value
|
|
13
|
+
left_result = @left.check value
|
|
14
|
+
if left_result.type_error?
|
|
15
|
+
right_result = @right.check value
|
|
16
|
+
if right_result.type_error?
|
|
17
|
+
Camille::TypeError.new(
|
|
18
|
+
'union.left' => left_result,
|
|
19
|
+
'union.right' => right_result
|
|
19
20
|
)
|
|
20
|
-
[error, nil]
|
|
21
21
|
else
|
|
22
|
-
|
|
22
|
+
Camille::Checked.new(fingerprint, right_result.value)
|
|
23
23
|
end
|
|
24
24
|
else
|
|
25
|
-
|
|
25
|
+
Camille::Checked.new(fingerprint, left_result.value)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
data/lib/camille/version.rb
CHANGED
data/lib/camille.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "active_support"
|
|
|
4
4
|
|
|
5
5
|
require_relative "camille/version"
|
|
6
6
|
require_relative "camille/object_hash"
|
|
7
|
+
require_relative "camille/checked"
|
|
7
8
|
require_relative "camille/basic_type"
|
|
8
9
|
require_relative "camille/types"
|
|
9
10
|
require_relative "camille/types/number"
|
|
@@ -14,6 +15,8 @@ require_relative "camille/types/object"
|
|
|
14
15
|
require_relative "camille/types/null"
|
|
15
16
|
require_relative "camille/types/undefined"
|
|
16
17
|
require_relative "camille/types/union"
|
|
18
|
+
require_relative "camille/intersection_solver"
|
|
19
|
+
require_relative "camille/intersection_preprocessor"
|
|
17
20
|
require_relative "camille/types/intersection"
|
|
18
21
|
require_relative "camille/types/tuple"
|
|
19
22
|
require_relative "camille/types/any"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: camille
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- merely
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-12-
|
|
11
|
+
date: 2024-12-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -52,6 +52,7 @@ files:
|
|
|
52
52
|
- gemfiles/rails-8.0
|
|
53
53
|
- lib/camille.rb
|
|
54
54
|
- lib/camille/basic_type.rb
|
|
55
|
+
- lib/camille/checked.rb
|
|
55
56
|
- lib/camille/code_generator.rb
|
|
56
57
|
- lib/camille/configuration.rb
|
|
57
58
|
- lib/camille/controller.rb
|
|
@@ -65,6 +66,8 @@ files:
|
|
|
65
66
|
- lib/camille/generators/templates/schema_template.erb
|
|
66
67
|
- lib/camille/generators/templates/type_template.erb
|
|
67
68
|
- lib/camille/generators/type_generator.rb
|
|
69
|
+
- lib/camille/intersection_preprocessor.rb
|
|
70
|
+
- lib/camille/intersection_solver.rb
|
|
68
71
|
- lib/camille/key_converter.rb
|
|
69
72
|
- lib/camille/line.rb
|
|
70
73
|
- lib/camille/loader.rb
|