camille 0.5.16 → 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 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