camille 1.2.0 → 1.4.0

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: 82a091b46cd7266990fb540e54febefd78773260f780283d624fa88321090d81
4
- data.tar.gz: 33a660f689f967f3be04e9bc70b15214fa28d36c74df223357f66dbe17bce7b1
3
+ metadata.gz: 5f58ec63581094cce8faf1d3f9dd3e1909ebe3fb188e8c7ed59c09a74645793b
4
+ data.tar.gz: badb42b9b2bb52426060ba9c62544ac6660334784b458360e82594d09ea785d5
5
5
  SHA512:
6
- metadata.gz: 32442ecdc91cd62092d3d49ce67c6cfe2ccedaeb1a285a1e6f44615792ce9408d8d245690cb96ff269853a72d3807080f1bdcb88639439b4e957a4d8ced726f4
7
- data.tar.gz: 21398e4687f2a7e3f56b06b86ad1c86a80bbe8719c57227a413df09ad7251c28e86af89145b9656243d80c68b9eeb4a9dea412b1a6e67c6189cc6b55d2af943f
6
+ metadata.gz: fd2e3eb6d0f629cf9eaacb6c8c8f09c92bd9b77a9815a7025717913d2542a6f38451a6f1c16b3cfdecc5c09c22f2949ab6b9cb77b9f349cec88bd22cef7ee375
7
+ data.tar.gz: 99177b3fe5a775615b8bf01cb88e23a3e6100409fee7a1a0af486ad918adf6de4c0c52d8e9b7c660f08110825ecd1609a713f9a7dc739cdb4a6ae67a13753af3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Added
6
+
7
+ * Added Camille::Controller::ParamsTypeError and Camille::Controller::ResponseTypeError to indicate source of TypeError.
8
+
9
+ ## 1.3.0
10
+
11
+ ### Added
12
+
13
+ * Added option to check controller params type: `config.check_params`.
14
+
3
15
  ## 1.2.0
4
16
 
5
17
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- camille (1.1.0)
4
+ camille (1.3.0)
5
5
  rails (>= 6.1, < 8.1)
6
6
 
7
7
  GEM
@@ -41,6 +41,10 @@ module Camille
41
41
  raise NotImplementedError
42
42
  end
43
43
 
44
+ def check_params value
45
+ check value
46
+ end
47
+
44
48
  def self.| other
45
49
  Camille::Type.instance(self) | other
46
50
  end
@@ -53,6 +57,10 @@ module Camille
53
57
  Camille::Type.instance(self)[]
54
58
  end
55
59
 
60
+ def self.check_params value
61
+ Camille::Type.instance(self).check_params value
62
+ end
63
+
56
64
  def self.directly_instantiable?
57
65
  instance_method(:initialize).arity == 0
58
66
  end
@@ -3,7 +3,7 @@ module Camille
3
3
  class Configuration
4
4
  class << self
5
5
  attr_reader :response_key_converter, :params_key_converter
6
- attr_accessor :ts_header, :ts_location
6
+ attr_accessor :ts_header, :ts_location, :check_params
7
7
 
8
8
  def load_default_configurations
9
9
  self.response_key_converter = lambda do |string|
@@ -13,6 +13,8 @@ module Camille
13
13
  self.params_key_converter = lambda do |string|
14
14
  string.underscore
15
15
  end
16
+
17
+ self.check_params = false
16
18
  end
17
19
 
18
20
  def response_key_converter= lambda
@@ -1,6 +1,8 @@
1
1
  module Camille
2
2
  module Controller
3
3
  class TypeError < ::StandardError; end
4
+ class ParamsTypeError < TypeError; end
5
+ class ResponseTypeError < TypeError; end
4
6
  class ArgumentError < ::ArgumentError; end
5
7
  class MissingRenderError < ::StandardError; end
6
8
 
@@ -23,7 +25,7 @@ module Camille
23
25
  if result.type_error?
24
26
  string_io = StringIO.new
25
27
  Camille::TypeErrorPrinter.new(result).print(string_io)
26
- raise TypeError.new("\nType check failed for response.\n#{string_io.string}")
28
+ raise ResponseTypeError.new("\nType check failed for response.\n#{string_io.string}")
27
29
  else
28
30
  rendered = result.render.json
29
31
  super(json: rendered)
@@ -43,7 +45,26 @@ module Camille
43
45
  Camille::Loader.check_and_raise_exception
44
46
  if endpoint = camille_endpoint
45
47
  begin
46
- params.deep_transform_keys!{|key| Camille::Configuration.params_key_converter.call(key.to_s)}
48
+ if Camille::Configuration.check_params
49
+ # New way: check params against the endpoint's params_type if it exists
50
+ if endpoint.params_type
51
+ params_to_check = params.to_unsafe_h
52
+ check_result = endpoint.params_type.check_params(params_to_check)
53
+
54
+ if check_result.type_error?
55
+ string_io = StringIO.new
56
+ Camille::TypeErrorPrinter.new(check_result).print(string_io)
57
+ raise ParamsTypeError.new("\nType check failed for params.\n#{string_io.string}")
58
+ else
59
+ # Use the checked value which already has snake_case keys
60
+ self.params = ActionController::Parameters.new(check_result.value)
61
+ end
62
+ end
63
+ else
64
+ # Old way: just transform keys
65
+ params.deep_transform_keys!{|key| Camille::Configuration.params_key_converter.call(key.to_s)}
66
+ end
67
+
47
68
  result = super
48
69
  # When there's no `render` call, Rails will return status 204
49
70
  if response.status == 204
@@ -4,4 +4,9 @@ Camille.configure do |config|
4
4
  // DO NOT EDIT! This file is automatically generated.
5
5
  import request from './request'
6
6
  EOF
7
+
8
+ # Enable type checking for controller parameters.
9
+ # When enabled, params will be validated against the schema and automatically
10
+ # converted from camelCase to snake_case.
11
+ config.check_params = true
7
12
  end
data/lib/camille/type.rb CHANGED
@@ -23,10 +23,18 @@ module Camille
23
23
  @underlying.check value
24
24
  end
25
25
 
26
+ def check_params value
27
+ @underlying.check_params value
28
+ end
29
+
26
30
  def self.check value
27
31
  new.check value
28
32
  end
29
33
 
34
+ def self.check_params value
35
+ new.check_params value
36
+ end
37
+
30
38
  def self.klass_name
31
39
  name.gsub(/^Camille::Types::/, '')
32
40
  end
@@ -9,32 +9,41 @@ module Camille
9
9
  end
10
10
 
11
11
  def check value
12
- if value.is_a? ::Array
13
- results = value.map.with_index do |element, index|
14
- [index, @content.check(element)]
15
- end
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
12
+ check_with_method(value, :check)
13
+ end
24
14
 
25
- if errors.empty?
26
- Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
27
- else
28
- Camille::TypeError.new(**errors.to_h)
29
- end
30
- else
31
- Camille::TypeError.new("Expected array, got #{value.inspect}.")
32
- end
15
+ def check_params value
16
+ check_with_method(value, :check_params)
33
17
  end
34
18
 
35
19
  def literal
36
20
  "#{@content.literal}[]"
37
21
  end
22
+
23
+ private
24
+ def check_with_method value, method_name
25
+ if value.is_a? ::Array
26
+ results = value.map.with_index do |element, index|
27
+ [index, @content.public_send(method_name, element)]
28
+ end
29
+
30
+ errors = results.map do |index, result|
31
+ if result.type_error?
32
+ ["array[#{index}]", result]
33
+ else
34
+ nil
35
+ end
36
+ end.compact
37
+
38
+ if errors.empty?
39
+ Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
40
+ else
41
+ Camille::TypeError.new(**errors.to_h)
42
+ end
43
+ else
44
+ Camille::TypeError.new("Expected array, got #{value.inspect}.")
45
+ end
46
+ end
38
47
  end
39
48
  end
40
49
  end
@@ -12,27 +12,36 @@ module Camille
12
12
  end
13
13
 
14
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
19
- )
20
- else
21
- right_result = @right.check left_result.value
22
- if right_result.type_error?
23
- Camille::TypeError.new(
24
- 'intersection.right' => right_result
25
- )
26
- else
27
- Camille::Checked.new(fingerprint, right_result.value)
28
- end
29
- end
15
+ check_with_method(value, :check)
16
+ end
17
+
18
+ def check_params value
19
+ check_with_method(value, :check_params)
30
20
  end
31
21
 
32
22
  def literal
33
23
  "(#{@left.literal} & #{@right.literal})"
34
24
  end
35
25
 
26
+ private
27
+ def check_with_method value, method_name
28
+ left_result = @left.public_send(method_name, value)
29
+ if left_result.type_error?
30
+ Camille::TypeError.new(
31
+ 'intersection.left' => left_result
32
+ )
33
+ else
34
+ right_result = @right.public_send(method_name, left_result.value)
35
+ if right_result.type_error?
36
+ Camille::TypeError.new(
37
+ 'intersection.right' => right_result
38
+ )
39
+ else
40
+ Camille::Checked.new(fingerprint, right_result.value)
41
+ end
42
+ end
43
+ end
44
+
36
45
  end
37
46
  end
38
47
  end
@@ -47,6 +47,52 @@ module Camille
47
47
  end
48
48
  end
49
49
 
50
+ def check_params value
51
+ if value.is_a? Hash
52
+ # Convert camelCase keys to snake_case
53
+ converted_value = value.transform_keys do |key|
54
+ Camille::KeyConverter.convert_params_key(key.to_s)
55
+ end
56
+
57
+ fields_with_string_keys = @fields.transform_keys(&:to_s)
58
+ optional_keys_as_strings = @optional_keys.map(&:to_s)
59
+
60
+ # Now check using the regular check method which expects snake_case
61
+ keys = (fields_with_string_keys.keys + converted_value.keys).uniq
62
+ keys_to_check, keys_to_skip = keys.partition{|key| fields_with_string_keys[key]}
63
+
64
+ results = keys_to_check.map do |key|
65
+ type = fields_with_string_keys[key]
66
+ if optional_keys_as_strings.include?(key) && converted_value[key].nil?
67
+ nil
68
+ else
69
+ [key, type.check_params(converted_value[key])]
70
+ end
71
+ end.compact
72
+
73
+ errors = results.map do |key, result|
74
+ if result.type_error?
75
+ [key.to_s, result]
76
+ else
77
+ nil
78
+ end
79
+ end.compact
80
+
81
+ skipped_pairs = keys_to_skip.map do |key|
82
+ [key, converted_value[key]]
83
+ end
84
+
85
+ if errors.empty?
86
+ object = Hash[results.map{|key, checked| [key, checked.value]}.concat(skipped_pairs).to_h]
87
+ Camille::Checked.new(fingerprint, object)
88
+ else
89
+ Camille::TypeError.new(**errors.to_h)
90
+ end
91
+ else
92
+ Camille::TypeError.new("Expected hash, got #{value.inspect}.")
93
+ end
94
+ end
95
+
50
96
  def literal
51
97
  "{#{@fields.map{|k,v| "#{literal_key k}: #{v.literal}"}.join(', ')}}"
52
98
  end
@@ -9,32 +9,41 @@ module Camille
9
9
  end
10
10
 
11
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])]
15
- end
16
-
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
12
+ check_with_method(value, :check)
13
+ end
24
14
 
25
- if errors.empty?
26
- Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
27
- else
28
- Camille::TypeError.new(**errors.to_h)
29
- end
30
- else
31
- Camille::TypeError.new("Expected array of size #{@elements.size}, got #{value.inspect}.")
32
- end
15
+ def check_params value
16
+ check_with_method(value, :check_params)
33
17
  end
34
18
 
35
19
  def literal
36
20
  "[#{elements.map(&:literal).join(', ')}]"
37
21
  end
22
+
23
+ private
24
+ def check_with_method value, method_name
25
+ if value.is_a?(::Array) && value.size == @elements.size
26
+ results = @elements.map.with_index do |type, index|
27
+ [index, type.public_send(method_name, value[index])]
28
+ end
29
+
30
+ errors = results.map do |index, result|
31
+ if result.type_error?
32
+ ["tuple[#{index}]", result]
33
+ else
34
+ nil
35
+ end
36
+ end.compact
37
+
38
+ if errors.empty?
39
+ Camille::Checked.new(fingerprint, results.map{|_, checked| checked.value})
40
+ else
41
+ Camille::TypeError.new(**errors.to_h)
42
+ end
43
+ else
44
+ Camille::TypeError.new("Expected array of size #{@elements.size}, got #{value.inspect}.")
45
+ end
46
+ end
38
47
  end
39
48
  end
40
49
  end
@@ -10,25 +10,34 @@ module Camille
10
10
  end
11
11
 
12
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
20
- )
21
- else
22
- Camille::Checked.new(fingerprint, right_result.value)
23
- end
24
- else
25
- Camille::Checked.new(fingerprint, left_result.value)
26
- end
13
+ check_with_method(value, :check)
14
+ end
15
+
16
+ def check_params value
17
+ check_with_method(value, :check_params)
27
18
  end
28
19
 
29
20
  def literal
30
21
  "(#{@left.literal} | #{@right.literal})"
31
22
  end
23
+
24
+ private
25
+ def check_with_method value, method_name
26
+ left_result = @left.public_send(method_name, value)
27
+ if left_result.type_error?
28
+ right_result = @right.public_send(method_name, value)
29
+ if right_result.type_error?
30
+ Camille::TypeError.new(
31
+ 'union.left' => left_result,
32
+ 'union.right' => right_result
33
+ )
34
+ else
35
+ Camille::Checked.new(fingerprint, right_result.value)
36
+ end
37
+ else
38
+ Camille::Checked.new(fingerprint, left_result.value)
39
+ end
40
+ end
32
41
  end
33
42
  end
34
43
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Camille
4
- VERSION = "1.2.0"
4
+ VERSION = "1.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: camille
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - merely