camille 0.5.16 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c64a49ba269a9488e2990dce715d59e21ec603bb09453bc07b48f1b29cf80183
4
- data.tar.gz: 482a7672c68154b5ab94c68318b2672f02f76793ad08372570a1b5ec4049e7b8
3
+ metadata.gz: 7812549c03ae51c6ea3883e982770259b1757ce13b9014e63f21bf87a6522e02
4
+ data.tar.gz: b562c295c761474368defa4aaa5ba544a5c82d54ad013eafcd1fee5305a68b82
5
5
  SHA512:
6
- metadata.gz: d6ba48e7bc89d06d8ad92eb375cac5d02d783fc35c5732215f068630b57918a5b0bfaccc89b76d49983b384fbac31e2bbbe208d14526ea37489864e2cb2cee67
7
- data.tar.gz: cb52ae9bdfc4445c098040b576d1e6910da8b699a615bfea339ed2bf24b0b180408d67a53b03a1c6259aadf3bda9f407d25c5fc17a12fbd22fb42c0aa0b20637
6
+ metadata.gz: 924f63558e9540098f5073ed9b806b3e73169324ad5dea5da1967f275922396f81e21db4968ba3298baa3443d65ae09561c8612a87da15dfa1549ea1404cfafb
7
+ data.tar.gz: 60fe1b8a0baf1df20417518f74e977431f7f9df30b1d5a6087bdef96a78b40d6782f2677f9bbdcac44e674cd676456b50c719ec0e194b069edaa8960afc7e7c5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Fixed
6
+
7
+ * Tuple type checking now checks length.
8
+
9
+ ## 0.6.0
10
+
11
+ ### Added
12
+
13
+ * Support Rails 8.0
14
+
3
15
  ## 0.5.16
4
16
 
5
17
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,100 +1,115 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- camille (0.5.15)
5
- rails (>= 6.1, < 8)
4
+ camille (0.6.0)
5
+ rails (>= 6.1, < 8.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actioncable (7.0.4.2)
11
- actionpack (= 7.0.4.2)
12
- activesupport (= 7.0.4.2)
10
+ actioncable (7.2.2.1)
11
+ actionpack (= 7.2.2.1)
12
+ activesupport (= 7.2.2.1)
13
13
  nio4r (~> 2.0)
14
14
  websocket-driver (>= 0.6.1)
15
- actionmailbox (7.0.4.2)
16
- actionpack (= 7.0.4.2)
17
- activejob (= 7.0.4.2)
18
- activerecord (= 7.0.4.2)
19
- activestorage (= 7.0.4.2)
20
- activesupport (= 7.0.4.2)
21
- mail (>= 2.7.1)
22
- net-imap
23
- net-pop
24
- net-smtp
25
- actionmailer (7.0.4.2)
26
- actionpack (= 7.0.4.2)
27
- actionview (= 7.0.4.2)
28
- activejob (= 7.0.4.2)
29
- activesupport (= 7.0.4.2)
30
- mail (~> 2.5, >= 2.5.4)
31
- net-imap
32
- net-pop
33
- net-smtp
34
- rails-dom-testing (~> 2.0)
35
- actionpack (7.0.4.2)
36
- actionview (= 7.0.4.2)
37
- activesupport (= 7.0.4.2)
38
- rack (~> 2.0, >= 2.2.0)
15
+ zeitwerk (~> 2.6)
16
+ actionmailbox (7.2.2.1)
17
+ actionpack (= 7.2.2.1)
18
+ activejob (= 7.2.2.1)
19
+ activerecord (= 7.2.2.1)
20
+ activestorage (= 7.2.2.1)
21
+ activesupport (= 7.2.2.1)
22
+ mail (>= 2.8.0)
23
+ actionmailer (7.2.2.1)
24
+ actionpack (= 7.2.2.1)
25
+ actionview (= 7.2.2.1)
26
+ activejob (= 7.2.2.1)
27
+ activesupport (= 7.2.2.1)
28
+ mail (>= 2.8.0)
29
+ rails-dom-testing (~> 2.2)
30
+ actionpack (7.2.2.1)
31
+ actionview (= 7.2.2.1)
32
+ activesupport (= 7.2.2.1)
33
+ nokogiri (>= 1.8.5)
34
+ racc
35
+ rack (>= 2.2.4, < 3.2)
36
+ rack-session (>= 1.0.1)
39
37
  rack-test (>= 0.6.3)
40
- rails-dom-testing (~> 2.0)
41
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
42
- actiontext (7.0.4.2)
43
- actionpack (= 7.0.4.2)
44
- activerecord (= 7.0.4.2)
45
- activestorage (= 7.0.4.2)
46
- activesupport (= 7.0.4.2)
38
+ rails-dom-testing (~> 2.2)
39
+ rails-html-sanitizer (~> 1.6)
40
+ useragent (~> 0.16)
41
+ actiontext (7.2.2.1)
42
+ actionpack (= 7.2.2.1)
43
+ activerecord (= 7.2.2.1)
44
+ activestorage (= 7.2.2.1)
45
+ activesupport (= 7.2.2.1)
47
46
  globalid (>= 0.6.0)
48
47
  nokogiri (>= 1.8.5)
49
- actionview (7.0.4.2)
50
- activesupport (= 7.0.4.2)
48
+ actionview (7.2.2.1)
49
+ activesupport (= 7.2.2.1)
51
50
  builder (~> 3.1)
52
- erubi (~> 1.4)
53
- rails-dom-testing (~> 2.0)
54
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
55
- activejob (7.0.4.2)
56
- activesupport (= 7.0.4.2)
51
+ erubi (~> 1.11)
52
+ rails-dom-testing (~> 2.2)
53
+ rails-html-sanitizer (~> 1.6)
54
+ activejob (7.2.2.1)
55
+ activesupport (= 7.2.2.1)
57
56
  globalid (>= 0.3.6)
58
- activemodel (7.0.4.2)
59
- activesupport (= 7.0.4.2)
60
- activerecord (7.0.4.2)
61
- activemodel (= 7.0.4.2)
62
- activesupport (= 7.0.4.2)
63
- activestorage (7.0.4.2)
64
- actionpack (= 7.0.4.2)
65
- activejob (= 7.0.4.2)
66
- activerecord (= 7.0.4.2)
67
- activesupport (= 7.0.4.2)
57
+ activemodel (7.2.2.1)
58
+ activesupport (= 7.2.2.1)
59
+ activerecord (7.2.2.1)
60
+ activemodel (= 7.2.2.1)
61
+ activesupport (= 7.2.2.1)
62
+ timeout (>= 0.4.0)
63
+ activestorage (7.2.2.1)
64
+ actionpack (= 7.2.2.1)
65
+ activejob (= 7.2.2.1)
66
+ activerecord (= 7.2.2.1)
67
+ activesupport (= 7.2.2.1)
68
68
  marcel (~> 1.0)
69
- mini_mime (>= 1.1.0)
70
- activesupport (7.0.4.2)
71
- concurrent-ruby (~> 1.0, >= 1.0.2)
69
+ activesupport (7.2.2.1)
70
+ base64
71
+ benchmark (>= 0.3)
72
+ bigdecimal
73
+ concurrent-ruby (~> 1.0, >= 1.3.1)
74
+ connection_pool (>= 2.2.5)
75
+ drb
72
76
  i18n (>= 1.6, < 2)
77
+ logger (>= 1.4.2)
73
78
  minitest (>= 5.1)
74
- tzinfo (~> 2.0)
75
- builder (3.2.4)
76
- concurrent-ruby (1.2.2)
79
+ securerandom (>= 0.3)
80
+ tzinfo (~> 2.0, >= 2.0.5)
81
+ base64 (0.2.0)
82
+ benchmark (0.4.0)
83
+ bigdecimal (3.1.8)
84
+ builder (3.3.0)
85
+ concurrent-ruby (1.3.4)
86
+ connection_pool (2.4.1)
77
87
  crass (1.0.6)
78
- date (3.3.4)
79
- diff-lcs (1.5.0)
80
- erubi (1.12.0)
88
+ date (3.4.1)
89
+ diff-lcs (1.5.1)
90
+ drb (2.2.1)
91
+ erubi (1.13.0)
81
92
  globalid (1.2.1)
82
93
  activesupport (>= 6.1)
83
- i18n (1.12.0)
94
+ i18n (1.14.6)
84
95
  concurrent-ruby (~> 1.0)
85
- loofah (2.19.1)
96
+ io-console (0.8.0)
97
+ irb (1.14.2)
98
+ rdoc (>= 4.0.0)
99
+ reline (>= 0.4.2)
100
+ logger (1.6.3)
101
+ loofah (2.23.1)
86
102
  crass (~> 1.0.2)
87
- nokogiri (>= 1.5.9)
103
+ nokogiri (>= 1.12.0)
88
104
  mail (2.8.1)
89
105
  mini_mime (>= 0.1.1)
90
106
  net-imap
91
107
  net-pop
92
108
  net-smtp
93
109
  marcel (1.0.4)
94
- method_source (1.0.0)
95
110
  mini_mime (1.1.5)
96
- minitest (5.17.0)
97
- net-imap (0.4.16)
111
+ minitest (5.25.4)
112
+ net-imap (0.5.1)
98
113
  date
99
114
  net-protocol
100
115
  net-pop (0.1.2)
@@ -103,69 +118,86 @@ GEM
103
118
  timeout
104
119
  net-smtp (0.5.0)
105
120
  net-protocol
106
- nio4r (2.7.3)
107
- nokogiri (1.14.2-x86_64-linux)
121
+ nio4r (2.7.4)
122
+ nokogiri (1.17.2-x86_64-linux)
108
123
  racc (~> 1.4)
109
- racc (1.6.2)
110
- rack (2.2.6.3)
111
- rack-test (2.0.2)
124
+ psych (5.2.1)
125
+ date
126
+ stringio
127
+ racc (1.8.1)
128
+ rack (3.1.8)
129
+ rack-session (2.0.0)
130
+ rack (>= 3.0.0)
131
+ rack-test (2.1.0)
112
132
  rack (>= 1.3)
113
- rails (7.0.4.2)
114
- actioncable (= 7.0.4.2)
115
- actionmailbox (= 7.0.4.2)
116
- actionmailer (= 7.0.4.2)
117
- actionpack (= 7.0.4.2)
118
- actiontext (= 7.0.4.2)
119
- actionview (= 7.0.4.2)
120
- activejob (= 7.0.4.2)
121
- activemodel (= 7.0.4.2)
122
- activerecord (= 7.0.4.2)
123
- activestorage (= 7.0.4.2)
124
- activesupport (= 7.0.4.2)
133
+ rackup (2.2.1)
134
+ rack (>= 3)
135
+ rails (7.2.2.1)
136
+ actioncable (= 7.2.2.1)
137
+ actionmailbox (= 7.2.2.1)
138
+ actionmailer (= 7.2.2.1)
139
+ actionpack (= 7.2.2.1)
140
+ actiontext (= 7.2.2.1)
141
+ actionview (= 7.2.2.1)
142
+ activejob (= 7.2.2.1)
143
+ activemodel (= 7.2.2.1)
144
+ activerecord (= 7.2.2.1)
145
+ activestorage (= 7.2.2.1)
146
+ activesupport (= 7.2.2.1)
125
147
  bundler (>= 1.15.0)
126
- railties (= 7.0.4.2)
127
- rails-dom-testing (2.0.3)
128
- activesupport (>= 4.2.0)
148
+ railties (= 7.2.2.1)
149
+ rails-dom-testing (2.2.0)
150
+ activesupport (>= 5.0.0)
151
+ minitest
129
152
  nokogiri (>= 1.6)
130
- rails-html-sanitizer (1.5.0)
131
- loofah (~> 2.19, >= 2.19.1)
132
- railties (7.0.4.2)
133
- actionpack (= 7.0.4.2)
134
- activesupport (= 7.0.4.2)
135
- method_source
153
+ rails-html-sanitizer (1.6.2)
154
+ loofah (~> 2.21)
155
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
156
+ railties (7.2.2.1)
157
+ actionpack (= 7.2.2.1)
158
+ activesupport (= 7.2.2.1)
159
+ irb (~> 1.13)
160
+ rackup (>= 1.0.0)
136
161
  rake (>= 12.2)
137
- thor (~> 1.0)
138
- zeitwerk (~> 2.5)
139
- rake (13.0.6)
140
- rspec (3.12.0)
141
- rspec-core (~> 3.12.0)
142
- rspec-expectations (~> 3.12.0)
143
- rspec-mocks (~> 3.12.0)
144
- rspec-core (3.12.1)
145
- rspec-support (~> 3.12.0)
146
- rspec-expectations (3.12.2)
162
+ thor (~> 1.0, >= 1.2.2)
163
+ zeitwerk (~> 2.6)
164
+ rake (13.2.1)
165
+ rdoc (6.9.0)
166
+ psych (>= 4.0.0)
167
+ reline (0.5.12)
168
+ io-console (~> 0.5)
169
+ rspec (3.13.0)
170
+ rspec-core (~> 3.13.0)
171
+ rspec-expectations (~> 3.13.0)
172
+ rspec-mocks (~> 3.13.0)
173
+ rspec-core (3.13.2)
174
+ rspec-support (~> 3.13.0)
175
+ rspec-expectations (3.13.3)
147
176
  diff-lcs (>= 1.2.0, < 2.0)
148
- rspec-support (~> 3.12.0)
149
- rspec-mocks (3.12.3)
177
+ rspec-support (~> 3.13.0)
178
+ rspec-mocks (3.13.2)
150
179
  diff-lcs (>= 1.2.0, < 2.0)
151
- rspec-support (~> 3.12.0)
152
- rspec-rails (6.0.1)
153
- actionpack (>= 6.1)
154
- activesupport (>= 6.1)
155
- railties (>= 6.1)
156
- rspec-core (~> 3.11)
157
- rspec-expectations (~> 3.11)
158
- rspec-mocks (~> 3.11)
159
- rspec-support (~> 3.11)
160
- rspec-support (3.12.0)
161
- thor (1.2.1)
162
- timeout (0.4.1)
180
+ rspec-support (~> 3.13.0)
181
+ rspec-rails (7.1.0)
182
+ actionpack (>= 7.0)
183
+ activesupport (>= 7.0)
184
+ railties (>= 7.0)
185
+ rspec-core (~> 3.13)
186
+ rspec-expectations (~> 3.13)
187
+ rspec-mocks (~> 3.13)
188
+ rspec-support (~> 3.13)
189
+ rspec-support (3.13.2)
190
+ securerandom (0.4.0)
191
+ stringio (3.1.2)
192
+ thor (1.3.2)
193
+ timeout (0.4.2)
163
194
  tzinfo (2.0.6)
164
195
  concurrent-ruby (~> 1.0)
196
+ useragent (0.16.11)
165
197
  websocket-driver (0.7.6)
166
198
  websocket-extensions (>= 0.1.0)
167
199
  websocket-extensions (0.1.5)
168
- zeitwerk (2.6.7)
200
+ zeitwerk (2.7.1)
169
201
 
170
202
  PLATFORMS
171
203
  x86_64-linux
@@ -0,0 +1,33 @@
1
+ require "benchmark/ips"
2
+
3
+ class A
4
+ def is_A?
5
+ true
6
+ end
7
+ end
8
+
9
+ a = A.new
10
+
11
+ Benchmark.ips do |x|
12
+ x.report("instance_of?") do
13
+ a.instance_of? A
14
+ end
15
+
16
+ x.report("is_a?") do
17
+ a.is_a? A
18
+ end
19
+
20
+ x.report("class ==") do
21
+ a.class == A
22
+ end
23
+
24
+ x.report("class.equal?") do
25
+ a.class.equal?(A)
26
+ end
27
+
28
+ x.report("is_A?") do
29
+ a.is_A?
30
+ end
31
+
32
+ x.compare!
33
+ end
@@ -0,0 +1,20 @@
1
+ require "benchmark/ips"
2
+
3
+ class A
4
+ def m
5
+ end
6
+ end
7
+
8
+ a = A.new
9
+
10
+ Benchmark.ips do |x|
11
+ x.report("respond_to?") do
12
+ a.respond_to? :m
13
+ end
14
+
15
+ x.report("calling method") do
16
+ a.m
17
+ end
18
+
19
+ x.compare!
20
+ end
@@ -6,4 +6,4 @@ gem "rake", "~> 13.0"
6
6
  gem "rspec", "~> 3.0"
7
7
  gem "rspec-rails"
8
8
 
9
- gem 'rails', '~> 6.1.0'
9
+ gem 'rails', '~> 7.2.0'
@@ -6,4 +6,4 @@ gem "rake", "~> 13.0"
6
6
  gem "rspec", "~> 3.0"
7
7
  gem "rspec-rails"
8
8
 
9
- gem 'rails', '~> 7.0.0'
9
+ gem 'rails', '~> 8.0.0'
@@ -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
@@ -0,0 +1,18 @@
1
+ module Camille
2
+ class Checked
3
+ attr_reader :fingerprint, :value
4
+
5
+ def initialize fingerprint, value
6
+ @fingerprint = fingerprint
7
+ @value = value
8
+ end
9
+
10
+ def checked?
11
+ true
12
+ end
13
+
14
+ def type_error?
15
+ false
16
+ end
17
+ end
18
+ end
@@ -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
- error, transformed = endpoint.response_type.transform_and_check(value)
23
- if error
22
+ result = endpoint.response_type.check(value)
23
+ if result.type_error?
24
24
  string_io = StringIO.new
25
- Camille::TypeErrorPrinter.new(error).print(string_io)
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: transformed)
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
@@ -3,5 +3,9 @@ module Camille
3
3
  def as_json options = nil
4
4
  transform_keys{|k, _| Camille::KeyConverter.convert_response_key(k)}.as_json
5
5
  end
6
+
7
+ def to_json options = nil
8
+ as_json.to_json
9
+ end
6
10
  end
7
11
  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
- @underlying.check value
22
- end
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
- error, _ = transform_and_check value
31
- error
28
+ result = check value
29
+ result.type_error? ? result : nil
32
30
  end
33
31
 
34
32
  def self.test value
@@ -27,5 +27,13 @@ module Camille
27
27
  def to_s
28
28
  inspect
29
29
  end
30
+
31
+ def checked?
32
+ false
33
+ end
34
+
35
+ def type_error?
36
+ true
37
+ end
30
38
  end
31
39
  end
@@ -2,6 +2,7 @@ module Camille
2
2
  module Types
3
3
  class Any < Camille::BasicType
4
4
  def check value
5
+ Camille::Checked.new(fingerprint, value)
5
6
  end
6
7
 
7
8
  def literal
@@ -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 transform_and_check value
11
+ def check value
11
12
  if value.is_a? ::Array
12
- transform_and_check_results = value.map.with_index do |element, index|
13
- [index, @content.transform_and_check(element)]
13
+ results = value.map.with_index do |element, index|
14
+ [index, @content.check(element)]
14
15
  end
15
- errors = transform_and_check_results.map do |index, (error, transformed)|
16
- ["array[#{index}]", error]
17
- end.select{|x| x[1]}
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
- transformed = transform_and_check_results.map{|_, (_, transformed)| transformed}
21
- [nil, transformed]
26
+ Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
22
27
  else
23
- [Camille::TypeError.new(**errors.to_h), nil]
28
+ Camille::TypeError.new(**errors.to_h)
24
29
  end
25
30
  else
26
- [Camille::TypeError.new("Expected array, got #{value.inspect}."), nil]
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
- unless value == false || value == true
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
- unless value == @value
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 transform_and_check value
14
- left_error, left_transformed = @left.transform_and_check value
15
- if left_error
16
- error = Camille::TypeError.new(
17
- 'intersection.left' => left_error
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
- right_error, right_transformed = @right.transform_and_check left_transformed
22
- if right_error
23
- error = Camille::TypeError.new(
24
- 'intersection.right' => right_error
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
- [nil, right_transformed]
27
+ Camille::Checked.new(fingerprint, right_result.value)
29
28
  end
30
29
  end
31
30
  end
@@ -2,7 +2,9 @@ module Camille
2
2
  module Types
3
3
  class Null < Camille::BasicType
4
4
  def check value
5
- unless value == nil
5
+ if value == nil
6
+ Camille::Checked.new(fingerprint, value)
7
+ else
6
8
  Camille::TypeError.new("Expected nil, got #{value.inspect}.")
7
9
  end
8
10
  end
@@ -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
- unless value.is_a?(Integer) || value.is_a?(Float)
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
- unless value == @value
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
@@ -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 transform_and_check value
13
+ def check value
13
14
  if value.is_a? Hash
14
15
  keys = (@fields.keys + value.keys).uniq
15
- transform_and_check_results = keys.map do |key|
16
- if type = @fields[key]
17
- if @optional_keys.include?(key) && value[key].nil?
18
- nil
19
- else
20
- [key, type.transform_and_check(value[key])]
21
- end
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
- [key, [nil, value[key]]]
31
+ nil
24
32
  end
25
33
  end.compact
26
34
 
27
- errors = transform_and_check_results.map do |key, (error, transformed)|
28
- [key.to_s, error]
29
- end.select{|x| x[1]}
35
+ skipped_pairs = keys_to_skip.map do |key|
36
+ [key, value[key]]
37
+ end
30
38
 
31
39
  if errors.empty?
32
- transformed = transform_and_check_results.map do |key, (error, transformed)|
33
- [key, transformed]
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
- [Camille::TypeError.new(**errors.to_h), nil]
43
+ Camille::TypeError.new(**errors.to_h)
38
44
  end
39
45
  else
40
- [Camille::TypeError.new("Expected hash, got #{value.inspect}."), nil]
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
@@ -1,9 +1,6 @@
1
1
  module Camille
2
2
  module Types
3
3
  class Omit < PickAndOmit
4
- def transform_and_check value
5
- processed_object.transform_and_check(value)
6
- end
7
4
 
8
5
  private
9
6
  def klass_name
@@ -1,9 +1,6 @@
1
1
  module Camille
2
2
  module Types
3
3
  class Pick < PickAndOmit
4
- def transform_and_check value
5
- processed_object.transform_and_check(value)
6
- end
7
4
 
8
5
  private
9
6
  def klass_name
@@ -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 transform_and_check value
13
+ def check value
13
14
  if value.is_a? ::Hash
14
15
 
15
- transform_and_check_results = value.map.with_index do |(k, v), index|
16
- [index, transform_and_check_pair(k, v)]
16
+ results = value.map.with_index do |(k, v), index|
17
+ [index, check_pair(k, v)]
17
18
  end
18
19
 
19
- errors = transform_and_check_results.map do |index, (error, transformed)|
20
- ["record[#{index}]", error]
21
- end.select{|x| x[1]}
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
- transformed = transform_and_check_results.map{|_, (_, transformed)| transformed}.to_h
25
- [nil, transformed]
29
+ Camille::Checked.new(fingerprint, results.map{|_, result| [result[0].value, result[1].value]}.to_h)
26
30
  else
27
- [Camille::TypeError.new(**errors.to_h), nil]
31
+ Camille::TypeError.new(**errors.to_h)
28
32
  end
29
33
  else
30
- [Camille::TypeError.new("Expected hash, got #{value.inspect}."), nil]
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 transform_and_check_pair key, value
44
- key_error, key_transformed = @key.transform_and_check key
45
- value_error, value_transformed = @value.transform_and_check value
47
+ def check_pair key, value
48
+ key_result = @key.check key
49
+ value_result = @value.check value
46
50
 
47
- if key_error.nil? && value_error.nil?
48
- [nil, [key_transformed, value_transformed]]
51
+ if key_result.checked? && value_result.checked?
52
+ [key_result, value_result]
49
53
  else
50
- errors = [['record.key', key_error], ['record.value', value_error]].select{|x| x[1]}.to_h
54
+ errors = [['record.key', key_result], ['record.value', value_result]].select{|x| x[1].type_error?}.to_h
51
55
 
52
- error = Camille::TypeError.new(**errors)
53
- [error, nil]
56
+ Camille::TypeError.new(**errors)
54
57
  end
55
58
  end
56
59
  end
@@ -2,7 +2,9 @@ module Camille
2
2
  module Types
3
3
  class String < Camille::BasicType
4
4
  def check value
5
- unless value.is_a?(::String) || value.is_a?(Symbol)
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
- unless transformed == @value
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
@@ -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 transform_and_check value
11
- if value.is_a? ::Array
12
- transform_and_check_results = @elements.map.with_index do |type, index|
13
- [index, type.transform_and_check(value[index])]
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 = transform_and_check_results.map do |index, (error, transformed)|
17
- ["tuple[#{index}]", error]
18
- end.select{|x| x[1]}
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
- transformed = transform_and_check_results.map{|index, (error, transformed)| transformed}
22
- [nil, transformed]
26
+ Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
23
27
  else
24
- [Camille::TypeError.new(**errors.to_h), nil]
28
+ Camille::TypeError.new(**errors.to_h)
25
29
  end
26
30
  else
27
- [Camille::TypeError.new("Expected array, got #{value.inspect}."), nil]
31
+ Camille::TypeError.new("Expected array of size #{@elements.size}, got #{value.inspect}.")
28
32
  end
29
33
  end
30
34
 
@@ -2,7 +2,9 @@ module Camille
2
2
  module Types
3
3
  class Undefined < Camille::BasicType
4
4
  def check value
5
- unless value == nil
5
+ if value == nil
6
+ Camille::Checked.new(fingerprint, value)
7
+ else
6
8
  Camille::TypeError.new("Expected nil, got #{value.inspect}.")
7
9
  end
8
10
  end
@@ -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 transform_and_check value
12
- left_error, left_transformed = @left.transform_and_check value
13
- if left_error
14
- right_error, right_transformed = @right.transform_and_check value
15
- if right_error
16
- error = Camille::TypeError.new(
17
- 'union.left' => left_error,
18
- 'union.right' => right_error
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
- [nil, right_transformed]
22
+ Camille::Checked.new(fingerprint, right_result.value)
23
23
  end
24
24
  else
25
- [nil, left_transformed]
25
+ Camille::Checked.new(fingerprint, left_result.value)
26
26
  end
27
27
  end
28
28
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Camille
4
- VERSION = "0.5.16"
4
+ VERSION = "0.6.1"
5
5
  end
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.5.16
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
- - Alyssa
7
+ - merely
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-31 00:00:00.000000000 Z
11
+ date: 2024-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '6.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '8'
22
+ version: '8.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,10 +29,10 @@ dependencies:
29
29
  version: '6.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '8'
32
+ version: '8.1'
33
33
  description: ''
34
34
  email:
35
- - git@alyssa.fm
35
+ - git@merely.ca
36
36
  executables: []
37
37
  extensions: []
38
38
  extra_rdoc_files: []
@@ -43,13 +43,16 @@ files:
43
43
  - Gemfile.lock
44
44
  - README.md
45
45
  - Rakefile
46
+ - benchmarks/checking_class_of_object.rb
47
+ - benchmarks/checking_method_of_object.rb
46
48
  - benchmarks/returning_multiple_values.rb
47
49
  - bin/console
48
50
  - bin/setup
49
- - gemfiles/rails-6.1
50
- - gemfiles/rails-7.0
51
+ - gemfiles/rails-7.2
52
+ - gemfiles/rails-8.0
51
53
  - lib/camille.rb
52
54
  - lib/camille/basic_type.rb
55
+ - lib/camille/checked.rb
53
56
  - lib/camille/code_generator.rb
54
57
  - lib/camille/configuration.rb
55
58
  - lib/camille/controller.rb
@@ -63,6 +66,8 @@ files:
63
66
  - lib/camille/generators/templates/schema_template.erb
64
67
  - lib/camille/generators/templates/type_template.erb
65
68
  - lib/camille/generators/type_generator.rb
69
+ - lib/camille/intersection_preprocessor.rb
70
+ - lib/camille/intersection_solver.rb
66
71
  - lib/camille/key_converter.rb
67
72
  - lib/camille/line.rb
68
73
  - lib/camille/loader.rb
@@ -116,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
121
  - !ruby/object:Gem::Version
117
122
  version: '0'
118
123
  requirements: []
119
- rubygems_version: 3.5.22
124
+ rubygems_version: 3.5.16
120
125
  signing_key:
121
126
  specification_version: 4
122
127
  summary: Typed API schema for Rails with TypeScript codegen