Class: Taro::Rails::ResponseValidator
- Inherits:
-
Struct
- Object
- Struct
- Taro::Rails::ResponseValidator
- Defined in:
- lib/taro/rails/response_validator.rb
Overview
This runs on for every response, so we are using Struct instead of Data here for performance reasons: bugs.ruby-lang.org/issues/19693
Instance Attribute Summary collapse
-
#controller ⇒ Object
Returns the value of attribute controller.
-
#declaration ⇒ Object
Returns the value of attribute declaration.
-
#rendered ⇒ Object
Returns the value of attribute rendered.
Class Method Summary collapse
Instance Method Summary collapse
- #call ⇒ Object
- #check(type, value) ⇒ Object
-
#check_custom_type(type, value) ⇒ Object
For complex/object types, we ensure conformance by checking whether the type was used for rendering.
- #check_enum(type, value) ⇒ Object
-
#check_scalar(type, value) ⇒ Object
For scalar and enum types, we want to support e.g.
- #check_scalar_array(type, value) ⇒ Object
- #declared_return_type ⇒ Object
-
#denest_rendered(nesting) ⇒ Object
support ‘returns :some_nesting, type: ’SomeType’‘ used like `render json: { some_nesting: SomeType.render(some_object) }`.
-
#fail_if_declaration_expected ⇒ Object
Rack, Rails and gems commonly trigger rendering of 400, 404, 500 etc.
- #fail_with(message) ⇒ Object
- #strict_check_custom_type(type, value) ⇒ Object
Instance Attribute Details
#controller ⇒ Object
Returns the value of attribute controller
4 5 6 |
# File 'lib/taro/rails/response_validator.rb', line 4 def controller @controller end |
#declaration ⇒ Object
Returns the value of attribute declaration
4 5 6 |
# File 'lib/taro/rails/response_validator.rb', line 4 def declaration @declaration end |
#rendered ⇒ Object
Returns the value of attribute rendered
4 5 6 |
# File 'lib/taro/rails/response_validator.rb', line 4 def rendered @rendered end |
Class Method Details
.call(controller, declaration, rendered) ⇒ Object
5 6 7 |
# File 'lib/taro/rails/response_validator.rb', line 5 def self.call(controller, declaration, rendered) new(controller, declaration, rendered).call end |
Instance Method Details
#call ⇒ Object
9 10 11 12 13 14 15 16 17 18 |
# File 'lib/taro/rails/response_validator.rb', line 9 def call if declared_return_type.nil? fail_if_declaration_expected elsif declared_return_type < Taro::Types::NestedResponseType field = declared_return_type.nesting_field check(field.type, denest_rendered(field.name)) else check(declared_return_type, rendered) end end |
#check(type, value) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/taro/rails/response_validator.rb', line 55 def check(type, value) if type < Taro::Types::ScalarType check_scalar(type, value) elsif type < Taro::Types::ListType && type.item_type < Taro::Types::ScalarType check_scalar_array(type, value) elsif type < Taro::Types::EnumType check_enum(type, value) else check_custom_type(type, value) end end |
#check_custom_type(type, value) ⇒ Object
For complex/object types, we ensure conformance by checking whether the type was used for rendering. This has performance benefits compared to going over the structure a second time.
93 94 95 96 97 98 99 |
# File 'lib/taro/rails/response_validator.rb', line 93 def check_custom_type(type, value) # Ignore types without a specified structure. return if type <= Taro::Types::ObjectTypes::FreeFormType return if type <= Taro::Types::ObjectTypes::NoContentType strict_check_custom_type(type, value) end |
#check_enum(type, value) ⇒ Object
83 84 85 86 87 88 |
# File 'lib/taro/rails/response_validator.rb', line 83 def check_enum(type, value) # coercion checks non-emptyness + enum match type.new(value).cached_coerce_response rescue Taro::Error => e fail_with(e.) end |
#check_scalar(type, value) ⇒ Object
For scalar and enum types, we want to support e.g. ‘render json: 42`, and not require using the type as in `BeautifulNumbersEnum.render(42)`.
70 71 72 73 74 75 76 |
# File 'lib/taro/rails/response_validator.rb', line 70 def check_scalar(type, value) case type.openapi_type when :integer, :number then value.is_a?(Numeric) when :string then value.is_a?(String) || value.is_a?(Symbol) when :boolean then [true, false].include?(value) end || fail_with("Expected a #{type.openapi_type}, got: #{value.class}.") end |
#check_scalar_array(type, value) ⇒ Object
78 79 80 81 |
# File 'lib/taro/rails/response_validator.rb', line 78 def check_scalar_array(type, value) value.is_a?(Array) || fail_with('Expected an Array.') value.empty? || check_scalar(type.item_type, value.first) end |
#declared_return_type ⇒ Object
20 21 22 |
# File 'lib/taro/rails/response_validator.rb', line 20 def declared_return_type @declared_return_type ||= declaration.returns[controller.status] end |
#denest_rendered(nesting) ⇒ Object
support ‘returns :some_nesting, type: ’SomeType’‘ used like `render json: { some_nesting: SomeType.render(some_object) }`
45 46 47 48 49 50 51 52 53 |
# File 'lib/taro/rails/response_validator.rb', line 45 def denest_rendered(nesting) rendered.is_a?(Hash) || fail_with("Expected Hash, got #{rendered.class}.") if rendered.key?(nesting) rendered[nesting] else fail_with "Expected key :#{nesting}, got: #{rendered.keys}." end end |
#fail_if_declaration_expected ⇒ Object
Rack, Rails and gems commonly trigger rendering of 400, 404, 500 etc. Declaring these codes should be optional. Otherwise the api schema would get bloated as there are no “global” return declarations in OpenAPI v3, and we’d need to export all of these for every single endpoint. v4 might change this. github.com/OAI/OpenAPI-Specification/issues/521
29 30 31 32 33 |
# File 'lib/taro/rails/response_validator.rb', line 29 def fail_if_declaration_expected controller.status.to_s.match?(/^[123]|422/) && fail_with(<<~MSG) No return type declared for this status. MSG end |
#fail_with(message) ⇒ Object
35 36 37 38 39 40 41 |
# File 'lib/taro/rails/response_validator.rb', line 35 def fail_with() raise Taro::ResponseError.new(<<~MSG, rendered, self) Response validation error for #{controller.class}##{controller.action_name}, code #{controller.status}": #{} MSG end |
#strict_check_custom_type(type, value) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/taro/rails/response_validator.rb', line 101 def strict_check_custom_type(type, value) used_type, rendered_object_id = type.last_render used_type == type || used_type&.<(type) || fail_with(<<~MSG) Expected to use #{type}.render, but the last type rendered was: #{used_type || 'no type'}. MSG rendered_object_id == value.__id__ || fail_with(<<~MSG) #{type}.render was called, but the result of this call was not used in the response. MSG end |