taro 1.0.0 → 1.2.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 +4 -4
 - data/CHANGELOG.md +35 -1
 - data/README.md +77 -14
 - data/lib/taro/errors.rb +7 -1
 - data/lib/taro/export/open_api_v3.rb +54 -23
 - data/lib/taro/rails/active_declarations.rb +1 -1
 - data/lib/taro/rails/declaration.rb +50 -9
 - data/lib/taro/rails/generators/install_generator.rb +1 -1
 - data/lib/taro/rails/generators/templates/errors_type.erb +15 -10
 - data/lib/taro/rails/normalized_route.rb +8 -0
 - data/lib/taro/rails/response_validation.rb +7 -57
 - data/lib/taro/rails/response_validator.rb +109 -0
 - data/lib/taro/rails/tasks/export.rake +5 -1
 - data/lib/taro/rails.rb +1 -2
 - data/lib/taro/types/base_type.rb +2 -0
 - data/lib/taro/types/coercion.rb +28 -17
 - data/lib/taro/types/enum_type.rb +2 -2
 - data/lib/taro/types/field.rb +8 -16
 - data/lib/taro/types/field_validation.rb +1 -1
 - data/lib/taro/types/list_type.rb +4 -6
 - data/lib/taro/types/object_types/free_form_type.rb +1 -0
 - data/lib/taro/types/object_types/no_content_type.rb +1 -0
 - data/lib/taro/types/object_types/page_info_type.rb +2 -0
 - data/lib/taro/types/object_types/page_type.rb +15 -25
 - data/lib/taro/types/scalar/iso8601_date_type.rb +1 -0
 - data/lib/taro/types/scalar/iso8601_datetime_type.rb +1 -0
 - data/lib/taro/types/scalar/timestamp_type.rb +1 -0
 - data/lib/taro/types/scalar/uuid_v4_type.rb +1 -0
 - data/lib/taro/types/shared/deprecation.rb +3 -0
 - data/lib/taro/types/shared/derived_types.rb +27 -0
 - data/lib/taro/types/shared/errors.rb +3 -1
 - data/lib/taro/types/shared/fields.rb +6 -5
 - data/lib/taro/types/shared/item_type.rb +1 -0
 - data/lib/taro/types/shared/object_coercion.rb +13 -0
 - data/lib/taro/types/shared/openapi_name.rb +8 -6
 - data/lib/taro/types/shared/rendering.rb +11 -25
 - data/lib/taro/version.rb +1 -1
 - data/tasks/benchmark.rake +1 -1
 - metadata +7 -5
 - data/lib/taro/types/shared/derivable_types.rb +0 -9
 
| 
         @@ -0,0 +1,109 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Taro::Rails::ResponseValidator = Struct.new(:controller, :declaration, :rendered) do
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.call(*args)
         
     | 
| 
      
 3 
     | 
    
         
            +
                new(*args).call
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def call
         
     | 
| 
      
 7 
     | 
    
         
            +
                if declared_return_type < Taro::Types::ScalarType
         
     | 
| 
      
 8 
     | 
    
         
            +
                  check_scalar
         
     | 
| 
      
 9 
     | 
    
         
            +
                elsif declared_return_type < Taro::Types::ListType &&
         
     | 
| 
      
 10 
     | 
    
         
            +
                      declared_return_type.item_type < Taro::Types::ScalarType
         
     | 
| 
      
 11 
     | 
    
         
            +
                  check_scalar_array
         
     | 
| 
      
 12 
     | 
    
         
            +
                elsif declared_return_type < Taro::Types::EnumType
         
     | 
| 
      
 13 
     | 
    
         
            +
                  check_enum
         
     | 
| 
      
 14 
     | 
    
         
            +
                else
         
     | 
| 
      
 15 
     | 
    
         
            +
                  check_custom_type
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def declared_return_type
         
     | 
| 
      
 20 
     | 
    
         
            +
                @declared_return_type ||= begin
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return_type = declaration.returns[controller.status] ||
         
     | 
| 
      
 22 
     | 
    
         
            +
                                fail_with('No return type declared for this status.')
         
     | 
| 
      
 23 
     | 
    
         
            +
                  nesting ? return_type.fields.fetch(nesting).type : return_type
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def fail_with(message)
         
     | 
| 
      
 28 
     | 
    
         
            +
                raise Taro::ResponseError, <<~MSG
         
     | 
| 
      
 29 
     | 
    
         
            +
                  Response validation error for
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #{controller.class}##{controller.action_name}, code #{controller.status}":
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #{message}
         
     | 
| 
      
 32 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              # support `returns :some_nesting, type: 'SomeType'` (ad-hoc return type)
         
     | 
| 
      
 36 
     | 
    
         
            +
              def nesting
         
     | 
| 
      
 37 
     | 
    
         
            +
                @nesting ||= declaration.return_nestings[controller.status]
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              def denest_rendered
         
     | 
| 
      
 41 
     | 
    
         
            +
                assert_rendered_is_a_hash
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                if rendered.key?(nesting)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  rendered[nesting]
         
     | 
| 
      
 45 
     | 
    
         
            +
                elsif rendered.key?(nesting.to_s)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  rendered[nesting.to_s]
         
     | 
| 
      
 47 
     | 
    
         
            +
                else
         
     | 
| 
      
 48 
     | 
    
         
            +
                  fail_with_nesting_error
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def assert_rendered_is_a_hash
         
     | 
| 
      
 53 
     | 
    
         
            +
                rendered.is_a?(Hash) || fail_with("Expected Hash, got #{rendered.class}.")
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              def fail_with_nesting_error
         
     | 
| 
      
 57 
     | 
    
         
            +
                fail_with "Expected key :#{nesting}, got: #{rendered.keys}."
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
              # For scalar and enum types, we want to support e.g. `render json: 42`,
         
     | 
| 
      
 61 
     | 
    
         
            +
              # and not require using the type as in `BeautifulNumbersEnum.render(42)`.
         
     | 
| 
      
 62 
     | 
    
         
            +
              def check_scalar(type = declared_return_type, value = subject)
         
     | 
| 
      
 63 
     | 
    
         
            +
                case type.openapi_type
         
     | 
| 
      
 64 
     | 
    
         
            +
                when :integer, :number then value.is_a?(Numeric)
         
     | 
| 
      
 65 
     | 
    
         
            +
                when :string           then value.is_a?(String) || value.is_a?(Symbol)
         
     | 
| 
      
 66 
     | 
    
         
            +
                when :boolean          then [true, false].include?(value)
         
     | 
| 
      
 67 
     | 
    
         
            +
                end || fail_with("Expected a #{type.openapi_type}, got: #{value.class}.")
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
              def subject
         
     | 
| 
      
 71 
     | 
    
         
            +
                @subject ||= nesting ? denest_rendered : rendered
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
              def check_scalar_array
         
     | 
| 
      
 75 
     | 
    
         
            +
                subject.is_a?(Array) || fail_with('Expected an Array.')
         
     | 
| 
      
 76 
     | 
    
         
            +
                subject.empty? || check_scalar(declared_return_type.item_type, subject.first)
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              def check_enum
         
     | 
| 
      
 80 
     | 
    
         
            +
                # coercion checks non-emptyness + enum match
         
     | 
| 
      
 81 
     | 
    
         
            +
                declared_return_type.new(subject).coerce_response
         
     | 
| 
      
 82 
     | 
    
         
            +
              rescue Taro::Error => e
         
     | 
| 
      
 83 
     | 
    
         
            +
                fail_with(e.message)
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              # For complex/object types, we ensure conformance by checking whether
         
     | 
| 
      
 87 
     | 
    
         
            +
              # the type was used for rendering. This has performance benefits compared
         
     | 
| 
      
 88 
     | 
    
         
            +
              # to going over the structure a second time.
         
     | 
| 
      
 89 
     | 
    
         
            +
              def check_custom_type
         
     | 
| 
      
 90 
     | 
    
         
            +
                # Ignore types without a specified structure.
         
     | 
| 
      
 91 
     | 
    
         
            +
                return if declared_return_type <= Taro::Types::ObjectTypes::FreeFormType
         
     | 
| 
      
 92 
     | 
    
         
            +
                return if declared_return_type <= Taro::Types::ObjectTypes::NoContentType
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                strict_check_custom_type
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
              def strict_check_custom_type
         
     | 
| 
      
 98 
     | 
    
         
            +
                used_type, rendered_object_id = declared_return_type.last_render
         
     | 
| 
      
 99 
     | 
    
         
            +
                used_type&.<=(declared_return_type) || fail_with(<<~MSG)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  Expected to use #{declared_return_type}.render, but the last type rendered
         
     | 
| 
      
 101 
     | 
    
         
            +
                  was: #{used_type || 'no type'}.
         
     | 
| 
      
 102 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                rendered_object_id == subject.__id__ || fail_with(<<~MSG)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  #{declared_return_type}.render was called, but the result
         
     | 
| 
      
 106 
     | 
    
         
            +
                  of this call was not used in the response.
         
     | 
| 
      
 107 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 108 
     | 
    
         
            +
              end
         
     | 
| 
      
 109 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -10,6 +10,10 @@ task 'taro:export' => :environment do 
     | 
|
| 
       10 
10 
     | 
    
         
             
                version: Taro.config.api_version,
         
     | 
| 
       11 
11 
     | 
    
         
             
              )
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
              data = export. 
     | 
| 
      
 13 
     | 
    
         
            +
              data = export.send("to_#{Taro.config.export_format}")
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              FileUtils.mkdir_p(File.dirname(Taro.config.export_path))
         
     | 
| 
       14 
16 
     | 
    
         
             
              File.write(Taro.config.export_path, data)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              puts "Exported #{Taro.config.api_name} to #{Taro.config.export_path}"
         
     | 
| 
       15 
19 
     | 
    
         
             
            end
         
     | 
    
        data/lib/taro/rails.rb
    CHANGED
    
    
    
        data/lib/taro/types/base_type.rb
    CHANGED
    
    | 
         @@ -9,6 +9,8 @@ 
     | 
|
| 
       9 
9 
     | 
    
         
             
            Taro::Types::BaseType = Data.define(:object) do
         
     | 
| 
       10 
10 
     | 
    
         
             
              require_relative "shared"
         
     | 
| 
       11 
11 
     | 
    
         
             
              extend Taro::Types::Shared::AdditionalProperties
         
     | 
| 
      
 12 
     | 
    
         
            +
              extend Taro::Types::Shared::Deprecation
         
     | 
| 
      
 13 
     | 
    
         
            +
              extend Taro::Types::Shared::DerivedTypes
         
     | 
| 
       12 
14 
     | 
    
         
             
              extend Taro::Types::Shared::Description
         
     | 
| 
       13 
15 
     | 
    
         
             
              extend Taro::Types::Shared::OpenAPIName
         
     | 
| 
       14 
16 
     | 
    
         
             
              extend Taro::Types::Shared::OpenAPIType
         
     | 
    
        data/lib/taro/types/coercion.rb
    CHANGED
    
    | 
         @@ -1,42 +1,52 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Taro::Types::Coercion
         
     | 
| 
       2 
     | 
    
         
            -
              KEYS = %i[type array_of page_of].freeze
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
2 
     | 
    
         
             
              class << self
         
     | 
| 
       5 
3 
     | 
    
         
             
                def call(arg)
         
     | 
| 
       6 
4 
     | 
    
         
             
                  validate_hash(arg)
         
     | 
| 
       7 
5 
     | 
    
         
             
                  from_hash(arg)
         
     | 
| 
       8 
6 
     | 
    
         
             
                end
         
     | 
| 
       9 
7 
     | 
    
         | 
| 
      
 8 
     | 
    
         
            +
                # Coercion keys can be expanded by the DerivedTypes module.
         
     | 
| 
      
 9 
     | 
    
         
            +
                def keys
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @keys ||= %i[type]
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def derived_suffix
         
     | 
| 
      
 14 
     | 
    
         
            +
                  '_of'
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
       10 
17 
     | 
    
         
             
                private
         
     | 
| 
       11 
18 
     | 
    
         | 
| 
       12 
19 
     | 
    
         
             
                def validate_hash(arg)
         
     | 
| 
       13 
20 
     | 
    
         
             
                  arg.is_a?(Hash) || raise(Taro::ArgumentError, <<~MSG)
         
     | 
| 
       14 
     | 
    
         
            -
                    Type coercion argument must be a Hash, got: #{arg. 
     | 
| 
      
 21 
     | 
    
         
            +
                    Type coercion argument must be a Hash, got: #{arg.class}
         
     | 
| 
       15 
22 
     | 
    
         
             
                  MSG
         
     | 
| 
       16 
23 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
                  types = arg.slice(* 
     | 
| 
      
 24 
     | 
    
         
            +
                  types = arg.slice(*keys)
         
     | 
| 
       18 
25 
     | 
    
         
             
                  types.size == 1 || raise(Taro::ArgumentError, <<~MSG)
         
     | 
| 
       19 
     | 
    
         
            -
                    Exactly one of  
     | 
| 
      
 26 
     | 
    
         
            +
                    Exactly one of #{keys.join(', ')} must be given, got: #{types}
         
     | 
| 
       20 
27 
     | 
    
         
             
                  MSG
         
     | 
| 
       21 
28 
     | 
    
         
             
                end
         
     | 
| 
       22 
29 
     | 
    
         | 
| 
       23 
30 
     | 
    
         
             
                def from_hash(hash)
         
     | 
| 
       24 
     | 
    
         
            -
                   
     | 
| 
       25 
     | 
    
         
            -
                     
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                     
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                     
     | 
| 
      
 31 
     | 
    
         
            +
                  keys.each do |key|
         
     | 
| 
      
 32 
     | 
    
         
            +
                    next unless (value = hash[key])
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    # e.g. `returns type: 'MyType'` -> MyType
         
     | 
| 
      
 35 
     | 
    
         
            +
                    return from_string(value) if key == :type
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    # DerivedTypes
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # e.g. `returns array_of: 'MyType'` -> MyType.array
         
     | 
| 
      
 39 
     | 
    
         
            +
                    return from_string(value).send(key.to_s.chomp(derived_suffix))
         
     | 
| 
       32 
40 
     | 
    
         
             
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  raise NotImplementedError, "Unsupported type coercion #{hash}"
         
     | 
| 
       33 
43 
     | 
    
         
             
                end
         
     | 
| 
       34 
44 
     | 
    
         | 
| 
       35 
45 
     | 
    
         
             
                def from_string(arg)
         
     | 
| 
       36 
46 
     | 
    
         
             
                  shortcuts[arg] || from_class(Object.const_get(arg.to_s))
         
     | 
| 
       37 
47 
     | 
    
         
             
                rescue NameError
         
     | 
| 
       38 
48 
     | 
    
         
             
                  raise Taro::ArgumentError, <<~MSG
         
     | 
| 
       39 
     | 
    
         
            -
                     
     | 
| 
      
 49 
     | 
    
         
            +
                    No such type: #{arg}. It should be a type-class name
         
     | 
| 
       40 
50 
     | 
    
         
             
                    or one of #{shortcuts.keys.map(&:inspect).join(', ')}.
         
     | 
| 
       41 
51 
     | 
    
         
             
                  MSG
         
     | 
| 
       42 
52 
     | 
    
         
             
                end
         
     | 
| 
         @@ -56,15 +66,16 @@ module Taro::Types::Coercion 
     | 
|
| 
       56 
66 
     | 
    
         
             
                  @shortcuts ||= {
         
     | 
| 
       57 
67 
     | 
    
         
             
                    # rubocop:disable Layout/HashAlignment - buggy cop
         
     | 
| 
       58 
68 
     | 
    
         
             
                    'Boolean'   => Taro::Types::Scalar::BooleanType,
         
     | 
| 
      
 69 
     | 
    
         
            +
                    'Date'      => Taro::Types::Scalar::ISO8601DateType,
         
     | 
| 
      
 70 
     | 
    
         
            +
                    'DateTime'  => Taro::Types::Scalar::ISO8601DateTimeType,
         
     | 
| 
       59 
71 
     | 
    
         
             
                    'Float'     => Taro::Types::Scalar::FloatType,
         
     | 
| 
       60 
72 
     | 
    
         
             
                    'FreeForm'  => Taro::Types::ObjectTypes::FreeFormType,
         
     | 
| 
       61 
73 
     | 
    
         
             
                    'Integer'   => Taro::Types::Scalar::IntegerType,
         
     | 
| 
      
 74 
     | 
    
         
            +
                    'NoContent' => Taro::Types::ObjectTypes::NoContentType,
         
     | 
| 
       62 
75 
     | 
    
         
             
                    'String'    => Taro::Types::Scalar::StringType,
         
     | 
| 
      
 76 
     | 
    
         
            +
                    'Time'      => Taro::Types::Scalar::ISO8601DateTimeType,
         
     | 
| 
       63 
77 
     | 
    
         
             
                    'Timestamp' => Taro::Types::Scalar::TimestampType,
         
     | 
| 
       64 
78 
     | 
    
         
             
                    'UUID'      => Taro::Types::Scalar::UUIDv4Type,
         
     | 
| 
       65 
     | 
    
         
            -
                    'Date'      => Taro::Types::Scalar::ISO8601DateType,
         
     | 
| 
       66 
     | 
    
         
            -
                    'Time'      => Taro::Types::Scalar::ISO8601DateTimeType,
         
     | 
| 
       67 
     | 
    
         
            -
                    'DateTime'  => Taro::Types::Scalar::ISO8601DateTimeType,
         
     | 
| 
       68 
79 
     | 
    
         
             
                    # rubocop:enable Layout/HashAlignment - buggy cop
         
     | 
| 
       69 
80 
     | 
    
         
             
                  }.freeze
         
     | 
| 
       70 
81 
     | 
    
         
             
                end
         
     | 
    
        data/lib/taro/types/enum_type.rb
    CHANGED
    
    | 
         @@ -18,7 +18,7 @@ class Taro::Types::EnumType < Taro::Types::BaseType 
     | 
|
| 
       18 
18 
     | 
    
         
             
                if self.class.values.include?(value)
         
     | 
| 
       19 
19 
     | 
    
         
             
                  value
         
     | 
| 
       20 
20 
     | 
    
         
             
                else
         
     | 
| 
       21 
     | 
    
         
            -
                  input_error("must be  
     | 
| 
      
 21 
     | 
    
         
            +
                  input_error("must be #{self.class.values.map(&:inspect).join(' or ')}")
         
     | 
| 
       22 
22 
     | 
    
         
             
                end
         
     | 
| 
       23 
23 
     | 
    
         
             
              end
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
         @@ -28,7 +28,7 @@ class Taro::Types::EnumType < Taro::Types::BaseType 
     | 
|
| 
       28 
28 
     | 
    
         
             
                if self.class.values.include?(value)
         
     | 
| 
       29 
29 
     | 
    
         
             
                  value
         
     | 
| 
       30 
30 
     | 
    
         
             
                else
         
     | 
| 
       31 
     | 
    
         
            -
                  response_error("must be  
     | 
| 
      
 31 
     | 
    
         
            +
                  response_error("must be #{self.class.values.map(&:inspect).join(' or ')}")
         
     | 
| 
       32 
32 
     | 
    
         
             
                end
         
     | 
| 
       33 
33 
     | 
    
         
             
              end
         
     | 
| 
       34 
34 
     | 
    
         | 
    
        data/lib/taro/types/field.rb
    CHANGED
    
    | 
         @@ -1,11 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require_relative 'field_validation'
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc) do
         
     | 
| 
      
 3 
     | 
    
         
            +
            Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc, :deprecated) do
         
     | 
| 
       4 
4 
     | 
    
         
             
              include Taro::Types::FieldValidation
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
              def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
              def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil, deprecated: nil)
         
     | 
| 
       7 
7 
     | 
    
         
             
                enum = coerce_to_enum(enum)
         
     | 
| 
       8 
     | 
    
         
            -
                super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:)
         
     | 
| 
      
 8 
     | 
    
         
            +
                super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:, deprecated:)
         
     | 
| 
       9 
9 
     | 
    
         
             
              end
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
              def value_for_input(object)
         
     | 
| 
         @@ -40,14 +40,15 @@ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, 
     | 
|
| 
       40 
40 
     | 
    
         
             
              end
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
              def retrieve_response_value(object, context, object_is_hash)
         
     | 
| 
       43 
     | 
    
         
            -
                if  
     | 
| 
       44 
     | 
    
         
            -
                  retrieve_hash_value(object)
         
     | 
| 
       45 
     | 
    
         
            -
                elsif context&.resolve?(method)
         
     | 
| 
      
 43 
     | 
    
         
            +
                if context&.resolve?(method)
         
     | 
| 
       46 
44 
     | 
    
         
             
                  context.public_send(method)
         
     | 
| 
      
 45 
     | 
    
         
            +
                elsif object_is_hash
         
     | 
| 
      
 46 
     | 
    
         
            +
                  retrieve_hash_value(object)
         
     | 
| 
       47 
47 
     | 
    
         
             
                elsif object.respond_to?(method, true)
         
     | 
| 
       48 
48 
     | 
    
         
             
                  object.public_send(method)
         
     | 
| 
       49 
49 
     | 
    
         
             
                else
         
     | 
| 
       50 
     | 
    
         
            -
                   
     | 
| 
      
 50 
     | 
    
         
            +
                  # Note that the ObjectCoercion module rescues this and adds context.
         
     | 
| 
      
 51 
     | 
    
         
            +
                  raise Taro::ResponseError, "No such method or resolver `:#{method}`."
         
     | 
| 
       51 
52 
     | 
    
         
             
                end
         
     | 
| 
       52 
53 
     | 
    
         
             
              end
         
     | 
| 
       53 
54 
     | 
    
         | 
| 
         @@ -65,14 +66,5 @@ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, 
     | 
|
| 
       65 
66 
     | 
    
         | 
| 
       66 
67 
     | 
    
         
             
                type_obj = type.new(value)
         
     | 
| 
       67 
68 
     | 
    
         
             
                from_input ? type_obj.coerce_input : type_obj.coerce_response
         
     | 
| 
       68 
     | 
    
         
            -
              rescue Taro::Error => e
         
     | 
| 
       69 
     | 
    
         
            -
                raise e.class, "#{e.message}, after using method/key `:#{method}` to resolve field `#{name}`"
         
     | 
| 
       70 
     | 
    
         
            -
              end
         
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
              def raise_response_coercion_error(object)
         
     | 
| 
       73 
     | 
    
         
            -
                raise Taro::ResponseError, <<~MSG
         
     | 
| 
       74 
     | 
    
         
            -
                  Failed to coerce value #{object.inspect} for field `#{name}` using method/key `:#{method}`.
         
     | 
| 
       75 
     | 
    
         
            -
                  It is not a valid #{type} value.
         
     | 
| 
       76 
     | 
    
         
            -
                MSG
         
     | 
| 
       77 
69 
     | 
    
         
             
              end
         
     | 
| 
       78 
70 
     | 
    
         
             
            end
         
     | 
| 
         @@ -18,7 +18,7 @@ module Taro::Types::FieldValidation 
     | 
|
| 
       18 
18 
     | 
    
         
             
              end
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
              def validate_enum_inclusion(value, for_input)
         
     | 
| 
       21 
     | 
    
         
            -
                return if enum.nil? || enum.include?(value)
         
     | 
| 
      
 21 
     | 
    
         
            +
                return if enum.nil? || null && value.nil? || enum.include?(value)
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
                raise for_input ? Taro::InputError : Taro::ResponseError, <<~MSG
         
     | 
| 
       24 
24 
     | 
    
         
             
                  Field #{name} has an invalid value #{value.inspect} (expected one of #{enum.inspect})
         
     | 
    
        data/lib/taro/types/list_type.rb
    CHANGED
    
    | 
         @@ -2,7 +2,6 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # Unlike other types, this one should not be manually inherited from,
         
     | 
| 
       3 
3 
     | 
    
         
             
            # but is used indirectly via `array_of: SomeType`.
         
     | 
| 
       4 
4 
     | 
    
         
             
            class Taro::Types::ListType < Taro::Types::BaseType
         
     | 
| 
       5 
     | 
    
         
            -
              extend Taro::Types::Shared::DerivableType
         
     | 
| 
       6 
5 
     | 
    
         
             
              extend Taro::Types::Shared::ItemType
         
     | 
| 
       7 
6 
     | 
    
         | 
| 
       8 
7 
     | 
    
         
             
              self.openapi_type = :array
         
     | 
| 
         @@ -20,11 +19,10 @@ class Taro::Types::ListType < Taro::Types::BaseType 
     | 
|
| 
       20 
19 
     | 
    
         
             
                item_type = self.class.item_type
         
     | 
| 
       21 
20 
     | 
    
         
             
                object.map { |el| item_type.new(el).coerce_response }
         
     | 
| 
       22 
21 
     | 
    
         
             
              end
         
     | 
| 
       23 
     | 
    
         
            -
            end
         
     | 
| 
       24 
22 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
              def self.array
         
     | 
| 
       28 
     | 
    
         
            -
                Taro::Types::ListType.for(self)
         
     | 
| 
      
 23 
     | 
    
         
            +
              def self.default_openapi_name
         
     | 
| 
      
 24 
     | 
    
         
            +
                "#{item_type.openapi_name}_List"
         
     | 
| 
       29 
25 
     | 
    
         
             
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              define_derived_type :array, 'Taro::Types::ListType'
         
     | 
| 
       30 
28 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Taro::Types::ObjectTypes::FreeFormType < Taro::Types::ObjectType
         
     | 
| 
       2 
2 
     | 
    
         
             
              self.desc = 'An arbitrary, unvalidated Hash or JSON object. Use with care.'
         
     | 
| 
       3 
3 
     | 
    
         
             
              self.additional_properties = true
         
     | 
| 
      
 4 
     | 
    
         
            +
              self.openapi_name = 'FreeForm'
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
6 
     | 
    
         
             
              def coerce_input
         
     | 
| 
       6 
7 
     | 
    
         
             
                object.is_a?(Hash) && object || input_error('must be a Hash')
         
     | 
| 
         @@ -1,4 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Taro::Types::ObjectTypes::PageInfoType < Taro::Types::ObjectType
         
     | 
| 
      
 2 
     | 
    
         
            +
              self.openapi_name = 'PageInfo'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
       2 
4 
     | 
    
         
             
              field :has_previous_page, type: 'Boolean', null: false, desc: 'Whether there is a previous page of results'
         
     | 
| 
       3 
5 
     | 
    
         
             
              field :has_next_page, type: 'Boolean', null: false, desc: 'Whether there is another page of results'
         
     | 
| 
       4 
6 
     | 
    
         
             
              field :start_cursor, type: 'String', null: true, desc: 'The first cursor in the current page of results (null if zero results)'
         
     | 
| 
         @@ -4,42 +4,32 @@ 
     | 
|
| 
       4 
4 
     | 
    
         
             
            #
         
     | 
| 
       5 
5 
     | 
    
         
             
            # The gem rails_cursor_pagination must be installed to use this.
         
     | 
| 
       6 
6 
     | 
    
         
             
            #
         
     | 
| 
       7 
     | 
    
         
            -
            class Taro::Types::ObjectTypes::PageType < Taro::Types:: 
     | 
| 
       8 
     | 
    
         
            -
              extend Taro::Types::Shared::DerivableType
         
     | 
| 
      
 7 
     | 
    
         
            +
            class Taro::Types::ObjectTypes::PageType < Taro::Types::ObjectType
         
     | 
| 
       9 
8 
     | 
    
         
             
              extend Taro::Types::Shared::ItemType
         
     | 
| 
       10 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
              def self.derive_from(from_type)
         
     | 
| 
      
 11 
     | 
    
         
            +
                super
         
     | 
| 
      
 12 
     | 
    
         
            +
                field(:page, array_of: from_type.name, null: false)
         
     | 
| 
      
 13 
     | 
    
         
            +
                field(:page_info, type: 'Taro::Types::ObjectTypes::PageInfoType', null: false)
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
       11 
16 
     | 
    
         
             
              def coerce_input
         
     | 
| 
       12 
17 
     | 
    
         
             
                input_error 'PageTypes cannot be used as input types'
         
     | 
| 
       13 
18 
     | 
    
         
             
              end
         
     | 
| 
       14 
19 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
              def  
     | 
| 
       16 
     | 
    
         
            -
                 
     | 
| 
       17 
     | 
    
         
            -
                   
     | 
| 
      
 20 
     | 
    
         
            +
              def self.render(relation, after:, limit: 20, order_by: nil, order: nil)
         
     | 
| 
      
 21 
     | 
    
         
            +
                result = RailsCursorPagination::Paginator.new(
         
     | 
| 
      
 22 
     | 
    
         
            +
                  relation, limit:, order_by:, order:, after:
         
     | 
| 
       18 
23 
     | 
    
         
             
                ).fetch
         
     | 
| 
       19 
     | 
    
         
            -
                coerce_paginated_list(list)
         
     | 
| 
       20 
     | 
    
         
            -
              end
         
     | 
| 
       21 
24 
     | 
    
         | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
                item_type = self.class.item_type
         
     | 
| 
       24 
     | 
    
         
            -
                items = list[:page].map do |item|
         
     | 
| 
       25 
     | 
    
         
            -
                  item_type.new(item[:data]).coerce_response
         
     | 
| 
       26 
     | 
    
         
            -
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
                result[:page].map! { |el| el.fetch(:data) }
         
     | 
| 
       27 
26 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
                 
     | 
| 
       29 
     | 
    
         
            -
                  self.class.items_key => items,
         
     | 
| 
       30 
     | 
    
         
            -
                  page_info: Taro::Types::ObjectTypes::PageInfoType.new(list[:page_info]).coerce_response,
         
     | 
| 
       31 
     | 
    
         
            -
                }
         
     | 
| 
      
 27 
     | 
    
         
            +
                super(result)
         
     | 
| 
       32 
28 
     | 
    
         
             
              end
         
     | 
| 
       33 
29 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
               
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                :page
         
     | 
| 
      
 30 
     | 
    
         
            +
              def self.default_openapi_name
         
     | 
| 
      
 31 
     | 
    
         
            +
                "#{item_type.openapi_name}_Page"
         
     | 
| 
       37 
32 
     | 
    
         
             
              end
         
     | 
| 
       38 
     | 
    
         
            -
            end
         
     | 
| 
       39 
33 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
            class Taro::Types::BaseType
         
     | 
| 
       42 
     | 
    
         
            -
              def self.page
         
     | 
| 
       43 
     | 
    
         
            -
                Taro::Types::ObjectTypes::PageType.for(self)
         
     | 
| 
       44 
     | 
    
         
            -
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
              define_derived_type :page, 'Taro::Types::ObjectTypes::PageType'
         
     | 
| 
       45 
35 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,5 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Taro::Types::Scalar::ISO8601DateTimeType < Taro::Types::ScalarType
         
     | 
| 
       2 
2 
     | 
    
         
             
              self.desc = 'Represents a time as DateTime in ISO8601 format.'
         
     | 
| 
      
 3 
     | 
    
         
            +
              self.openapi_name = 'ISO8601DateTime'
         
     | 
| 
       3 
4 
     | 
    
         
             
              self.openapi_type = :string
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
6 
     | 
    
         
             
              PATTERN = /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(Z|[+-](0[0-9]|1[0-4]):[0-5]\d)\z/
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Types::Shared::DerivedTypes
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Adds `name` as a method to all type classes and adds
         
     | 
| 
      
 3 
     | 
    
         
            +
              # `name`_of as a supported key to the Coercion module.
         
     | 
| 
      
 4 
     | 
    
         
            +
              # When `name` is called on a type class T, it returns a new subclass
         
     | 
| 
      
 5 
     | 
    
         
            +
              # S inheriting from `type` and passes T to S::derive_from.
         
     | 
| 
      
 6 
     | 
    
         
            +
              def define_derived_type(name, type)
         
     | 
| 
      
 7 
     | 
    
         
            +
                root = Taro::Types::BaseType
         
     | 
| 
      
 8 
     | 
    
         
            +
                raise ArgumentError, "#{name} is already in use" if root.respond_to?(name)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                ckey = :"#{name}#{Taro::Types::Coercion.derived_suffix}"
         
     | 
| 
      
 11 
     | 
    
         
            +
                ckeys = Taro::Types::Coercion.keys
         
     | 
| 
      
 12 
     | 
    
         
            +
                raise ArgumentError, "#{ckey} is already in use" if ckeys.include?(ckey)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                root.define_singleton_method(name) do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  derived_types[type] ||= begin
         
     | 
| 
      
 16 
     | 
    
         
            +
                    type_class = Taro::Types::Coercion.call(type:)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    Class.new(type_class).tap { |t| t.derive_from(self) }
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                ckeys << ckey
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def derived_types
         
     | 
| 
      
 25 
     | 
    
         
            +
                @derived_types ||= {}
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -8,6 +8,8 @@ module Taro::Types::Shared::Errors 
     | 
|
| 
       8 
8 
     | 
    
         
             
              end
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
              def coerce_error_message(msg)
         
     | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
      
 11 
     | 
    
         
            +
                type_desc = (self.class.name || self.class.superclass.name)
         
     | 
| 
      
 12 
     | 
    
         
            +
                            .sub(/^Taro::Types::.*?([^:]+)Type$/, '\1')
         
     | 
| 
      
 13 
     | 
    
         
            +
                "#{object.class} is not valid as #{type_desc}: #{msg}"
         
     | 
| 
       12 
14 
     | 
    
         
             
              end
         
     | 
| 
       13 
15 
     | 
    
         
             
            end
         
     | 
| 
         @@ -2,7 +2,7 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            module Taro::Types::Shared::Fields
         
     | 
| 
       3 
3 
     | 
    
         
             
              # Field types are set using class name Strings. The respective type classes
         
     | 
| 
       4 
4 
     | 
    
         
             
              # are evaluated lazily to allow for circular or recursive type references,
         
     | 
| 
       5 
     | 
    
         
            -
              # and to avoid unnecessary  
     | 
| 
      
 5 
     | 
    
         
            +
              # and to avoid unnecessary autoloading of all types in dev/test envs.
         
     | 
| 
       6 
6 
     | 
    
         
             
              def field(name, **kwargs)
         
     | 
| 
       7 
7 
     | 
    
         
             
                defined_at = kwargs[:defined_at] || caller_locations(1..1)[0]
         
     | 
| 
       8 
8 
     | 
    
         
             
                validate_name(name, defined_at:)
         
     | 
| 
         @@ -27,11 +27,12 @@ module Taro::Types::Shared::Fields 
     | 
|
| 
       27 
27 
     | 
    
         
             
                [true, false].include?(kwargs[:null]) ||
         
     | 
| 
       28 
28 
     | 
    
         
             
                  raise(Taro::ArgumentError, "null has to be specified as true or false for field #{name} at #{defined_at}")
         
     | 
| 
       29 
29 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                 
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
      
 30 
     | 
    
         
            +
                c_keys = Taro::Types::Coercion.keys
         
     | 
| 
      
 31 
     | 
    
         
            +
                (type_keys = (kwargs.keys & c_keys)).size == 1 ||
         
     | 
| 
      
 32 
     | 
    
         
            +
                  raise(Taro::ArgumentError, "exactly one of #{c_keys.join(', ')} must be given for field #{name} at #{defined_at}")
         
     | 
| 
       32 
33 
     | 
    
         | 
| 
       33 
34 
     | 
    
         
             
                kwargs[type_keys.first].class == String ||
         
     | 
| 
       34 
     | 
    
         
            -
                  raise(Taro::ArgumentError, "#{ 
     | 
| 
      
 35 
     | 
    
         
            +
                  raise(Taro::ArgumentError, "#{type_keys.first} must be a String for field #{name} at #{defined_at}")
         
     | 
| 
       35 
36 
     | 
    
         
             
              end
         
     | 
| 
       36 
37 
     | 
    
         | 
| 
       37 
38 
     | 
    
         
             
              def validate_no_override(name, defined_at:)
         
     | 
| 
         @@ -46,7 +47,7 @@ module Taro::Types::Shared::Fields 
     | 
|
| 
       46 
47 
     | 
    
         
             
              def evaluate_field_defs
         
     | 
| 
       47 
48 
     | 
    
         
             
                field_defs.transform_values do |field_def|
         
     | 
| 
       48 
49 
     | 
    
         
             
                  type = Taro::Types::Coercion.call(field_def)
         
     | 
| 
       49 
     | 
    
         
            -
                  Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion 
     | 
| 
      
 50 
     | 
    
         
            +
                  Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion.keys), type:)
         
     | 
| 
       50 
51 
     | 
    
         
             
                end
         
     | 
| 
       51 
52 
     | 
    
         
             
              end
         
     | 
| 
       52 
53 
     | 
    
         | 
| 
         @@ -3,6 +3,8 @@ module Taro::Types::Shared::ObjectCoercion 
     | 
|
| 
       3 
3 
     | 
    
         
             
              def coerce_input
         
     | 
| 
       4 
4 
     | 
    
         
             
                self.class.fields.transform_values do |field|
         
     | 
| 
       5 
5 
     | 
    
         
             
                  field.value_for_input(object)
         
     | 
| 
      
 6 
     | 
    
         
            +
                rescue Taro::Error => e
         
     | 
| 
      
 7 
     | 
    
         
            +
                  raise_enriched_coercion_error(e, field)
         
     | 
| 
       6 
8 
     | 
    
         
             
                end
         
     | 
| 
       7 
9 
     | 
    
         
             
              end
         
     | 
| 
       8 
10 
     | 
    
         | 
| 
         @@ -11,6 +13,17 @@ module Taro::Types::Shared::ObjectCoercion 
     | 
|
| 
       11 
13 
     | 
    
         
             
                object_is_hash = object.is_a?(Hash)
         
     | 
| 
       12 
14 
     | 
    
         
             
                self.class.fields.transform_values do |field|
         
     | 
| 
       13 
15 
     | 
    
         
             
                  field.value_for_response(object, context: self, object_is_hash:)
         
     | 
| 
      
 16 
     | 
    
         
            +
                rescue Taro::Error => e
         
     | 
| 
      
 17 
     | 
    
         
            +
                  raise_enriched_coercion_error(e, field)
         
     | 
| 
       14 
18 
     | 
    
         
             
                end
         
     | 
| 
       15 
19 
     | 
    
         
             
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def raise_enriched_coercion_error(error, field)
         
     | 
| 
      
 22 
     | 
    
         
            +
                # The indentation is on purpose. These errors can be recursively rescued
         
     | 
| 
      
 23 
     | 
    
         
            +
                # and re-raised by a tree of object types, which should be made apparent.
         
     | 
| 
      
 24 
     | 
    
         
            +
                raise error.class, <<~MSG
         
     | 
| 
      
 25 
     | 
    
         
            +
                  Failed to read #{self.class.name} field `#{field.name}` from #{object.class}:
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #{error.message.lines.map { |line| "  #{line}" }.join}
         
     | 
| 
      
 27 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
       16 
29 
     | 
    
         
             
            end
         
     | 
| 
         @@ -5,13 +5,19 @@ module Taro::Types::Shared::OpenAPIName 
     | 
|
| 
       5 
5 
     | 
    
         
             
                @openapi_name ||= default_openapi_name
         
     | 
| 
       6 
6 
     | 
    
         
             
              end
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
      
 8 
     | 
    
         
            +
              def openapi_name?
         
     | 
| 
      
 9 
     | 
    
         
            +
                !!openapi_name
         
     | 
| 
      
 10 
     | 
    
         
            +
              rescue Taro::Error
         
     | 
| 
      
 11 
     | 
    
         
            +
                false
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
       8 
14 
     | 
    
         
             
              def openapi_name=(arg)
         
     | 
| 
       9 
15 
     | 
    
         
             
                arg.nil? || arg.is_a?(String) ||
         
     | 
| 
       10 
16 
     | 
    
         
             
                  raise(Taro::ArgumentError, 'openapi_name must be a String')
         
     | 
| 
       11 
17 
     | 
    
         
             
                @openapi_name = arg
         
     | 
| 
       12 
18 
     | 
    
         
             
              end
         
     | 
| 
       13 
19 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
              def default_openapi_name 
     | 
| 
      
 20 
     | 
    
         
            +
              def default_openapi_name
         
     | 
| 
       15 
21 
     | 
    
         
             
                if self < Taro::Types::EnumType ||
         
     | 
| 
       16 
22 
     | 
    
         
             
                   self < Taro::Types::InputType ||
         
     | 
| 
       17 
23 
     | 
    
         
             
                   self < Taro::Types::ObjectType
         
     | 
| 
         @@ -19,12 +25,8 @@ module Taro::Types::Shared::OpenAPIName 
     | 
|
| 
       19 
25 
     | 
    
         
             
                    raise(Taro::Error, 'openapi_name must be set for anonymous type classes')
         
     | 
| 
       20 
26 
     | 
    
         
             
                elsif self < Taro::Types::ScalarType
         
     | 
| 
       21 
27 
     | 
    
         
             
                  openapi_type
         
     | 
| 
       22 
     | 
    
         
            -
                elsif self < Taro::Types::ListType
         
     | 
| 
       23 
     | 
    
         
            -
                  "#{item_type.openapi_name}_List"
         
     | 
| 
       24 
     | 
    
         
            -
                elsif self < Taro::Types::ObjectTypes::PageType
         
     | 
| 
       25 
     | 
    
         
            -
                  "#{item_type.openapi_name}_Page"
         
     | 
| 
       26 
28 
     | 
    
         
             
                else
         
     | 
| 
       27 
     | 
    
         
            -
                  raise  
     | 
| 
      
 29 
     | 
    
         
            +
                  raise Taro::Error, 'no default_openapi_name implemented for this type'
         
     | 
| 
       28 
30 
     | 
    
         
             
                end
         
     | 
| 
       29 
31 
     | 
    
         
             
              end
         
     | 
| 
       30 
32 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,36 +1,22 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # The `::render` method is intended for use in controllers.
         
     | 
| 
       2 
     | 
    
         
            -
            # Special types (e.g. PageType) may accept kwargs for `#coerce_response`.
         
     | 
| 
       3 
1 
     | 
    
         
             
            module Taro::Types::Shared::Rendering
         
     | 
| 
       4 
     | 
    
         
            -
               
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
                  MSG
         
     | 
| 
       10 
     | 
    
         
            -
                end
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                result = new(object).coerce_response(**opts)
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                # Only mark this as the used type if coercion worked so that
         
     | 
| 
       15 
     | 
    
         
            -
                # rescue_from can be used to render another type.
         
     | 
| 
       16 
     | 
    
         
            -
                self.rendering = self
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 2 
     | 
    
         
            +
              # The `::render` method is intended for use in controllers.
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Overrides of this method must call super.
         
     | 
| 
      
 4 
     | 
    
         
            +
              def render(object)
         
     | 
| 
      
 5 
     | 
    
         
            +
                result = new(object).coerce_response
         
     | 
| 
      
 6 
     | 
    
         
            +
                self.last_render = [self, result.__id__]
         
     | 
| 
       18 
7 
     | 
    
         
             
                result
         
     | 
| 
       19 
8 
     | 
    
         
             
              end
         
     | 
| 
       20 
9 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
              def  
     | 
| 
       22 
     | 
    
         
            -
                ActiveSupport::IsolatedExecutionState[: 
     | 
| 
       23 
     | 
    
         
            -
              end
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
              def rendering
         
     | 
| 
       26 
     | 
    
         
            -
                ActiveSupport::IsolatedExecutionState[:taro_type_rendering]
         
     | 
| 
      
 10 
     | 
    
         
            +
              def last_render=(info)
         
     | 
| 
      
 11 
     | 
    
         
            +
                ActiveSupport::IsolatedExecutionState[:taro_last_render] = info
         
     | 
| 
       27 
12 
     | 
    
         
             
              end
         
     | 
| 
       28 
13 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
              def  
     | 
| 
       30 
     | 
    
         
            -
                ActiveSupport::IsolatedExecutionState[: 
     | 
| 
      
 14 
     | 
    
         
            +
              def last_render
         
     | 
| 
      
 15 
     | 
    
         
            +
                ActiveSupport::IsolatedExecutionState[:taro_last_render]
         
     | 
| 
       31 
16 
     | 
    
         
             
              end
         
     | 
| 
       32 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
              # get the last used type for assertions in tests/specs
         
     | 
| 
       33 
19 
     | 
    
         
             
              def used_in_response
         
     | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
      
 20 
     | 
    
         
            +
                last_render.to_a.first
         
     | 
| 
       35 
21 
     | 
    
         
             
              end
         
     | 
| 
       36 
22 
     | 
    
         
             
            end
         
     | 
    
        data/lib/taro/version.rb
    CHANGED