taro 1.4.0 → 2.1.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 -0
 - data/README.md +138 -60
 - data/lib/taro/cache.rb +14 -0
 - data/lib/taro/common_returns.rb +31 -0
 - data/lib/taro/declaration.rb +82 -0
 - data/lib/taro/declarations.rb +34 -0
 - data/lib/taro/errors.rb +15 -2
 - data/lib/taro/export/base.rb +1 -1
 - data/lib/taro/export/open_api_v3.rb +20 -23
 - data/lib/taro/export.rb +1 -1
 - data/lib/taro/none.rb +2 -0
 - data/lib/taro/rails/active_declarations.rb +2 -10
 - data/lib/taro/rails/declaration.rb +9 -114
 - data/lib/taro/rails/declaration_buffer.rb +2 -1
 - data/lib/taro/rails/dsl.rb +13 -6
 - data/lib/taro/rails/generators/install_generator.rb +1 -1
 - data/lib/taro/rails/generators/templates/errors_type.erb +1 -1
 - data/lib/taro/rails/generators/templates/response_type.erb +4 -0
 - data/lib/taro/rails/generators.rb +1 -1
 - data/lib/taro/rails/normalized_route.rb +20 -38
 - data/lib/taro/rails/param_parsing.rb +5 -3
 - data/lib/taro/rails/railtie.rb +4 -0
 - data/lib/taro/rails/response_validator.rb +53 -52
 - data/lib/taro/rails/route_finder.rb +5 -7
 - data/lib/taro/rails/tasks/export.rake +10 -9
 - data/lib/taro/rails.rb +2 -3
 - data/lib/taro/return_def.rb +43 -0
 - data/lib/taro/route.rb +32 -0
 - data/lib/taro/status_code.rb +16 -0
 - data/lib/taro/types/base_type.rb +7 -1
 - data/lib/taro/types/coercion.rb +2 -2
 - data/lib/taro/types/enum_type.rb +1 -1
 - data/lib/taro/types/field.rb +17 -5
 - data/lib/taro/types/field_def.rb +62 -0
 - data/lib/taro/types/field_validation.rb +4 -6
 - data/lib/taro/types/input_type.rb +4 -9
 - data/lib/taro/types/list_type.rb +1 -1
 - data/lib/taro/types/nested_response_type.rb +16 -0
 - data/lib/taro/types/object_type.rb +2 -7
 - data/lib/taro/types/object_types/no_content_type.rb +1 -5
 - data/lib/taro/types/object_types/page_info_type.rb +1 -1
 - data/lib/taro/types/object_types/page_type.rb +1 -5
 - data/lib/taro/types/response_type.rb +8 -0
 - data/lib/taro/types/scalar/integer_param_type.rb +15 -0
 - data/lib/taro/types/scalar_type.rb +1 -1
 - data/lib/taro/types/shared/caching.rb +30 -0
 - data/lib/taro/types/shared/custom_field_resolvers.rb +2 -2
 - data/lib/taro/types/shared/derived_types.rb +34 -15
 - data/lib/taro/types/shared/equivalence.rb +14 -0
 - data/lib/taro/types/shared/errors.rb +8 -8
 - data/lib/taro/types/shared/fields.rb +10 -36
 - data/lib/taro/types/shared/name.rb +14 -0
 - data/lib/taro/types/shared/object_coercion.rb +0 -13
 - data/lib/taro/types/shared/openapi_name.rb +0 -6
 - data/lib/taro/types/shared/rendering.rb +5 -3
 - data/lib/taro/types/shared.rb +1 -1
 - data/lib/taro/types.rb +1 -1
 - data/lib/taro/version.rb +1 -1
 - data/lib/taro.rb +6 -1
 - metadata +19 -3
 
    
        data/lib/taro/export.rb
    CHANGED
    
    
    
        data/lib/taro/none.rb
    ADDED
    
    
| 
         @@ -1,19 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Taro::Rails::ActiveDeclarations
         
     | 
| 
       2 
2 
     | 
    
         
             
              def apply(declaration:, controller_class:, action_name:)
         
     | 
| 
       3 
     | 
    
         
            -
                 
     | 
| 
      
 3 
     | 
    
         
            +
                Taro.declarations["#{controller_class.name}##{action_name}"] = declaration
         
     | 
| 
       4 
4 
     | 
    
         
             
                Taro::Rails::ParamParsing.install(controller_class:, action_name:)
         
     | 
| 
       5 
5 
     | 
    
         
             
                Taro::Rails::ResponseValidation.install(controller_class:)
         
     | 
| 
       6 
6 
     | 
    
         
             
              end
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
              def declarations_map
         
     | 
| 
       9 
     | 
    
         
            -
                @declarations_map ||= {}
         
     | 
| 
       10 
     | 
    
         
            -
              end
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
              def declarations
         
     | 
| 
       13 
     | 
    
         
            -
                declarations_map.values.flat_map(&:values)
         
     | 
| 
       14 
     | 
    
         
            -
              end
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
8 
     | 
    
         
             
              def declaration_for(controller)
         
     | 
| 
       17 
     | 
    
         
            -
                 
     | 
| 
      
 9 
     | 
    
         
            +
                Taro.declarations["#{controller.class.name}##{controller.action_name}"]
         
     | 
| 
       18 
10 
     | 
    
         
             
              end
         
     | 
| 
       19 
11 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,122 +1,17 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            class Taro::Rails::Declaration
         
     | 
| 
       2 
     | 
    
         
            -
              attr_reader : 
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
              def initialize
         
     | 
| 
       5 
     | 
    
         
            -
                @params = Class.new(Taro::Types::InputType)
         
     | 
| 
       6 
     | 
    
         
            -
                @return_defs = {}
         
     | 
| 
       7 
     | 
    
         
            -
                @return_descriptions = {}
         
     | 
| 
       8 
     | 
    
         
            -
                @return_nestings = {}
         
     | 
| 
       9 
     | 
    
         
            -
              end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
              def add_info(summary, desc: nil, tags: nil)
         
     | 
| 
       12 
     | 
    
         
            -
                summary.is_a?(String) || raise(Taro::ArgumentError, 'api summary must be a String')
         
     | 
| 
       13 
     | 
    
         
            -
                @summary = summary
         
     | 
| 
       14 
     | 
    
         
            -
                @desc = desc
         
     | 
| 
       15 
     | 
    
         
            -
                @tags = Array(tags) if tags
         
     | 
| 
       16 
     | 
    
         
            -
              end
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
              def add_param(param_name, **kwargs)
         
     | 
| 
       19 
     | 
    
         
            -
                kwargs[:defined_at] = caller_locations(1..2)[1]
         
     | 
| 
       20 
     | 
    
         
            -
                @params.field(param_name, **kwargs)
         
     | 
| 
       21 
     | 
    
         
            -
              end
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
              def add_return(nesting = nil, code:, desc: nil, **kwargs)
         
     | 
| 
       24 
     | 
    
         
            -
                status = self.class.coerce_status_to_int(code)
         
     | 
| 
       25 
     | 
    
         
            -
                raise_if_already_declared(status)
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                kwargs[:nesting] = nesting
         
     | 
| 
       28 
     | 
    
         
            -
                check_return_kwargs(kwargs)
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                kwargs[:defined_at] = caller_locations(1..2)[1]
         
     | 
| 
       31 
     | 
    
         
            -
                return_defs[status] = kwargs
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                # response desc is required in openapi 3 – fall back to status code
         
     | 
| 
       34 
     | 
    
         
            -
                return_descriptions[status] = desc || code.to_s
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                # if a field name is provided, the response should be nested
         
     | 
| 
       37 
     | 
    
         
            -
                return_nestings[status] = nesting if nesting
         
     | 
| 
       38 
     | 
    
         
            -
              end
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
              # Return types are evaluated lazily to avoid unnecessary autoloading
         
     | 
| 
       41 
     | 
    
         
            -
              # of all types in dev/test envs.
         
     | 
| 
       42 
     | 
    
         
            -
              def returns
         
     | 
| 
       43 
     | 
    
         
            -
                @returns ||= evaluate_return_defs
         
     | 
| 
       44 
     | 
    
         
            -
              end
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
              def raise_if_already_declared(status)
         
     | 
| 
       47 
     | 
    
         
            -
                return_defs[status] &&
         
     | 
| 
       48 
     | 
    
         
            -
                  raise(Taro::ArgumentError, "response for status #{status} already declared")
         
     | 
| 
       49 
     | 
    
         
            -
              end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
              def parse_params(rails_params)
         
     | 
| 
       52 
     | 
    
         
            -
                params.new(rails_params.to_unsafe_h).coerce_input
         
     | 
| 
       53 
     | 
    
         
            -
              end
         
     | 
| 
      
 1 
     | 
    
         
            +
            class Taro::Rails::Declaration < Taro::Declaration
         
     | 
| 
      
 2 
     | 
    
         
            +
              attr_reader :controller_class, :action_name
         
     | 
| 
       54 
3 
     | 
    
         | 
| 
       55 
4 
     | 
    
         
             
              def finalize(controller_class:, action_name:)
         
     | 
| 
       56 
     | 
    
         
            -
                 
     | 
| 
       57 
     | 
    
         
            -
                 
     | 
| 
       58 
     | 
    
         
            -
                 
     | 
| 
       59 
     | 
    
         
            -
              end
         
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
              def routes=(arg)
         
     | 
| 
       62 
     | 
    
         
            -
                arg.is_a?(Array) || raise(Taro::ArgumentError, 'routes must be an Array')
         
     | 
| 
       63 
     | 
    
         
            -
                @routes = arg
         
     | 
| 
       64 
     | 
    
         
            -
              end
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
              def polymorphic_route?
         
     | 
| 
       67 
     | 
    
         
            -
                routes.size > 1
         
     | 
| 
       68 
     | 
    
         
            -
              end
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
              require 'rack'
         
     | 
| 
       71 
     | 
    
         
            -
              def self.coerce_status_to_int(status)
         
     | 
| 
       72 
     | 
    
         
            -
                # support using http status numbers directly
         
     | 
| 
       73 
     | 
    
         
            -
                return status if ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(status)
         
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
                # support using symbols, but coerce them to numbers
         
     | 
| 
       76 
     | 
    
         
            -
                ::Rack::Utils::SYMBOL_TO_STATUS_CODE[status] ||
         
     | 
| 
       77 
     | 
    
         
            -
                  raise(Taro::ArgumentError, "Invalid status: #{status.inspect}")
         
     | 
| 
       78 
     | 
    
         
            -
              end
         
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
              private
         
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
              def check_return_kwargs(kwargs)
         
     | 
| 
       83 
     | 
    
         
            -
                # For nested returns, evaluate_return_def calls ::field, which validates
         
     | 
| 
       84 
     | 
    
         
            -
                # field options, but does not trigger type autoloading.
         
     | 
| 
       85 
     | 
    
         
            -
                return evaluate_return_def(**kwargs) if kwargs[:nesting]
         
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
                if kwargs.key?(:null)
         
     | 
| 
       88 
     | 
    
         
            -
                  raise Taro::ArgumentError, <<~MSG
         
     | 
| 
       89 
     | 
    
         
            -
                    `null:` is not supported for top-level returns. If you want a nullable return
         
     | 
| 
       90 
     | 
    
         
            -
                    value, nest it, e.g. `returns :str, type: 'String', null: true`.
         
     | 
| 
       91 
     | 
    
         
            -
                  MSG
         
     | 
| 
       92 
     | 
    
         
            -
                end
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                bad_keys = kwargs.keys - (Taro::Types::Coercion.keys + %i[code desc nesting])
         
     | 
| 
       95 
     | 
    
         
            -
                return if bad_keys.empty?
         
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
                raise Taro::ArgumentError, "Invalid `returns` options: #{bad_keys.join(', ')}"
         
     | 
| 
       98 
     | 
    
         
            -
              end
         
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
              def evaluate_return_defs
         
     | 
| 
       101 
     | 
    
         
            -
                return_defs.transform_values { |defi| evaluate_return_def(**defi) }
         
     | 
| 
       102 
     | 
    
         
            -
              end
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
              def evaluate_return_def(nesting:, **kwargs)
         
     | 
| 
       105 
     | 
    
         
            -
                if nesting
         
     | 
| 
       106 
     | 
    
         
            -
                  # ad-hoc return type, requiring the actual return type to be nested
         
     | 
| 
       107 
     | 
    
         
            -
                  Class.new(Taro::Types::ObjectType).tap do |type|
         
     | 
| 
       108 
     | 
    
         
            -
                    type.field(nesting, null: false, **kwargs)
         
     | 
| 
       109 
     | 
    
         
            -
                  end
         
     | 
| 
       110 
     | 
    
         
            -
                else
         
     | 
| 
       111 
     | 
    
         
            -
                  Taro::Types::Coercion.call(kwargs)
         
     | 
| 
       112 
     | 
    
         
            -
                end
         
     | 
| 
      
 5 
     | 
    
         
            +
                @controller_class = controller_class
         
     | 
| 
      
 6 
     | 
    
         
            +
                @action_name = action_name
         
     | 
| 
      
 7 
     | 
    
         
            +
                @params.define_name("InputType(#{endpoint})")
         
     | 
| 
       113 
8 
     | 
    
         
             
              end
         
     | 
| 
       114 
9 
     | 
    
         | 
| 
       115 
     | 
    
         
            -
              def  
     | 
| 
       116 
     | 
    
         
            -
                 
     | 
| 
      
 10 
     | 
    
         
            +
              def endpoint
         
     | 
| 
      
 11 
     | 
    
         
            +
                action_name && "#{controller_class}##{action_name}"
         
     | 
| 
       117 
12 
     | 
    
         
             
              end
         
     | 
| 
       118 
13 
     | 
    
         | 
| 
       119 
     | 
    
         
            -
              def  
     | 
| 
       120 
     | 
    
         
            -
                routes 
     | 
| 
      
 14 
     | 
    
         
            +
              def routes
         
     | 
| 
      
 15 
     | 
    
         
            +
                @routes ||= Taro::Rails::RouteFinder.call(controller_class:, action_name:)
         
     | 
| 
       121 
16 
     | 
    
         
             
              end
         
     | 
| 
       122 
17 
     | 
    
         
             
            end
         
     | 
| 
         @@ -2,7 +2,8 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # until the next action method is defined (e.g. `def create`).
         
     | 
| 
       3 
3 
     | 
    
         
             
            module Taro::Rails::DeclarationBuffer
         
     | 
| 
       4 
4 
     | 
    
         
             
              def buffered_declaration(controller_class)
         
     | 
| 
       5 
     | 
    
         
            -
                buffered_declarations[controller_class] ||= 
     | 
| 
      
 5 
     | 
    
         
            +
                buffered_declarations[controller_class] ||=
         
     | 
| 
      
 6 
     | 
    
         
            +
                  Taro::Rails::Declaration.new(controller_class)
         
     | 
| 
       6 
7 
     | 
    
         
             
              end
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
       8 
9 
     | 
    
         
             
              def buffered_declarations
         
     | 
    
        data/lib/taro/rails/dsl.rb
    CHANGED
    
    | 
         @@ -1,18 +1,25 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Taro::Rails::DSL
         
     | 
| 
       2 
     | 
    
         
            -
              def api(summary, ** 
     | 
| 
       3 
     | 
    
         
            -
                Taro::Rails.buffered_declaration(self).add_info(summary, ** 
     | 
| 
      
 2 
     | 
    
         
            +
              def api(summary, **)
         
     | 
| 
      
 3 
     | 
    
         
            +
                Taro::Rails.buffered_declaration(self).add_info(summary, **)
         
     | 
| 
       4 
4 
     | 
    
         
             
              end
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
              def param(param_name, ** 
     | 
| 
       7 
     | 
    
         
            -
                 
     | 
| 
      
 6 
     | 
    
         
            +
              def param(param_name, **)
         
     | 
| 
      
 7 
     | 
    
         
            +
                defined_at = caller_locations(1..1)[0]
         
     | 
| 
      
 8 
     | 
    
         
            +
                Taro::Rails.buffered_declaration(self).add_param(param_name, defined_at:, **)
         
     | 
| 
       8 
9 
     | 
    
         
             
              end
         
     | 
| 
       9 
10 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
              def returns( 
     | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
      
 11 
     | 
    
         
            +
              def returns(nesting = nil, **)
         
     | 
| 
      
 12 
     | 
    
         
            +
                defined_at = caller_locations(1..1)[0]
         
     | 
| 
      
 13 
     | 
    
         
            +
                Taro::Rails.buffered_declaration(self).add_return(nesting, defined_at:, **)
         
     | 
| 
       12 
14 
     | 
    
         
             
              end
         
     | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
16 
     | 
    
         
             
              def method_added(method_name)
         
     | 
| 
       15 
17 
     | 
    
         
             
                Taro::Rails.apply_buffered_declaration(self, method_name)
         
     | 
| 
       16 
18 
     | 
    
         
             
                super
         
     | 
| 
       17 
19 
     | 
    
         
             
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def common_return(nesting = nil, **)
         
     | 
| 
      
 22 
     | 
    
         
            +
                defined_at = caller_locations(1..1)[0]
         
     | 
| 
      
 23 
     | 
    
         
            +
                Taro::CommonReturns.define(self, nesting, defined_at:, **)
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
       18 
25 
     | 
    
         
             
            end
         
     | 
| 
         @@ -12,7 +12,7 @@ class Taro::Rails::Generators::InstallGenerator < ::Rails::Generators::Base 
     | 
|
| 
       12 
12 
     | 
    
         
             
              def create_type_files
         
     | 
| 
       13 
13 
     | 
    
         
             
                Dir["#{self.class.source_root}/**/*.erb"].each do |tmpl|
         
     | 
| 
       14 
14 
     | 
    
         
             
                  dest_dir = options[:dir].chomp('/')
         
     | 
| 
       15 
     | 
    
         
            -
                  template tmpl, "#{dest_dir}/#{File.basename(tmpl 
     | 
| 
      
 15 
     | 
    
         
            +
                  template tmpl, "#{dest_dir}/#{File.basename(tmpl, '.erb')}.rb"
         
     | 
| 
       16 
16 
     | 
    
         
             
                end
         
     | 
| 
       17 
17 
     | 
    
         
             
              end
         
     | 
| 
       18 
18 
     | 
    
         
             
              # :nocov:
         
     | 
| 
         @@ -25,6 +25,6 @@ class ErrorsType < Taro::Types::ListType 
     | 
|
| 
       25 
25 
     | 
    
         
             
                    response_error("must be an Enumerable or an object with errors")
         
     | 
| 
       26 
26 
     | 
    
         
             
                  end
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
                list.map { |el| self.class.item_type.new(el). 
     | 
| 
      
 28 
     | 
    
         
            +
                list.map { |el| self.class.item_type.new(el).cached_coerce_response }
         
     | 
| 
       29 
29 
     | 
    
         
             
              end
         
     | 
| 
       30 
30 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,49 +1,31 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
              def ignored?
         
     | 
| 
       3 
     | 
    
         
            -
                verb.to_s.empty? || patch_update?
         
     | 
| 
       4 
     | 
    
         
            -
              end
         
     | 
| 
      
 1 
     | 
    
         
            +
            require_relative '../route'
         
     | 
| 
       5 
2 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
               
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
       15 
     | 
    
         
            -
              end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
              def openapi_path
         
     | 
| 
       18 
     | 
    
         
            -
                rails_route.path.spec.to_s.gsub('(.:format)', '').gsub(/:(\w+)/, '{\1}')
         
     | 
| 
       19 
     | 
    
         
            -
              end
         
     | 
| 
      
 3 
     | 
    
         
            +
            class Taro::Rails::NormalizedRoute < Taro::Route
         
     | 
| 
      
 4 
     | 
    
         
            +
              def initialize(rails_route)
         
     | 
| 
      
 5 
     | 
    
         
            +
                action, controller = rails_route.requirements.values_at(:action, :controller)
         
     | 
| 
      
 6 
     | 
    
         
            +
                # Journey::Route#verb is a String. Its usually something like 'POST', but
         
     | 
| 
      
 7 
     | 
    
         
            +
                # manual matched routes may have e.g. 'GET|POST' (🤢). We only need one copy.
         
     | 
| 
      
 8 
     | 
    
         
            +
                verb = rails_route.verb.to_s.scan(/\w+/).sort.last.to_s.downcase
         
     | 
| 
      
 9 
     | 
    
         
            +
                openapi_operation_id = "#{verb}_#{action}_#{controller}".gsub('/', '__')
         
     | 
| 
      
 10 
     | 
    
         
            +
                openapi_path = rails_route.path.spec.to_s.gsub('(.:format)', '').gsub(/:(\w+)/, '{\1}')
         
     | 
| 
      
 11 
     | 
    
         
            +
                endpoint = "#{controller}##{action}"
         
     | 
| 
       20 
12 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                "#{verb}_#{action}_#{controller.gsub('/', '__')}"
         
     | 
| 
      
 13 
     | 
    
         
            +
                super(endpoint:, openapi_operation_id:, openapi_path:, verb:)
         
     | 
| 
       23 
14 
     | 
    
         
             
              end
         
     | 
| 
       24 
15 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
              def  
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
       27 
     | 
    
         
            -
              end
         
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
              def endpoint
         
     | 
| 
       30 
     | 
    
         
            -
                "#{controller}##{action}"
         
     | 
| 
       31 
     | 
    
         
            -
              end
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
              def action
         
     | 
| 
       34 
     | 
    
         
            -
                rails_route.requirements[:action]
         
     | 
| 
      
 16 
     | 
    
         
            +
              def ignored?
         
     | 
| 
      
 17 
     | 
    
         
            +
                internal? || patch_update?
         
     | 
| 
       35 
18 
     | 
    
         
             
              end
         
     | 
| 
       36 
19 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
               
     | 
| 
       38 
     | 
    
         
            -
                rails_route.requirements[:controller]
         
     | 
| 
       39 
     | 
    
         
            -
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
              private
         
     | 
| 
       40 
21 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
               
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
      
 22 
     | 
    
         
            +
              # Internal routes of rails sometimes have no verb.
         
     | 
| 
      
 23 
     | 
    
         
            +
              def internal?
         
     | 
| 
      
 24 
     | 
    
         
            +
                verb.empty?
         
     | 
| 
       43 
25 
     | 
    
         
             
              end
         
     | 
| 
       44 
26 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
               
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
              # Rails has both PATCH and PUT routes for updates. We only need one copy.
         
     | 
| 
      
 28 
     | 
    
         
            +
              def patch_update?
         
     | 
| 
      
 29 
     | 
    
         
            +
                verb == 'patch' && endpoint.end_with?('#update')
         
     | 
| 
       47 
30 
     | 
    
         
             
              end
         
     | 
| 
       48 
     | 
    
         
            -
              alias to_s inspect
         
     | 
| 
       49 
31 
     | 
    
         
             
            end
         
     | 
| 
         @@ -7,9 +7,11 @@ module Taro::Rails::ParamParsing 
     | 
|
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
                installed[key] = true
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                controller_class. 
     | 
| 
       11 
     | 
    
         
            -
                  declaration = Taro::Rails.declaration_for(self)
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 10 
     | 
    
         
            +
                controller_class.prepend_before_action(only: action_name) do
         
     | 
| 
      
 11 
     | 
    
         
            +
                  declaration = Taro::Rails.declaration_for(self) || raise(
         
     | 
| 
      
 12 
     | 
    
         
            +
                    Taro::InvariantError, "missing Declaration for #{controller_class}##{action_name}"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  )
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @api_params = declaration.params.new(params.to_unsafe_h).coerce_input
         
     | 
| 
       13 
15 
     | 
    
         
             
                end
         
     | 
| 
       14 
16 
     | 
    
         
             
              end
         
     | 
| 
       15 
17 
     | 
    
         | 
    
        data/lib/taro/rails/railtie.rb
    CHANGED
    
    | 
         @@ -9,6 +9,10 @@ class Taro::Rails::Railtie < ::Rails::Railtie 
     | 
|
| 
       9 
9 
     | 
    
         
             
                app.reloader.to_prepare do
         
     | 
| 
       10 
10 
     | 
    
         
             
                  Taro::Rails.reset
         
     | 
| 
       11 
11 
     | 
    
         
             
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                app.config.after_initialize do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  Taro::Cache.cache_instance = Rails.cache
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
       12 
16 
     | 
    
         
             
              end
         
     | 
| 
       13 
17 
     | 
    
         | 
| 
       14 
18 
     | 
    
         
             
              rake_tasks { Dir["#{__dir__}/tasks/**/*.rake"].each { |f| load f } }
         
     | 
| 
         @@ -1,65 +1,70 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Taro::Rails::ResponseValidator = Struct.new(:controller, :declaration, :rendered) do
         
     | 
| 
       2 
     | 
    
         
            -
              def self.call( 
     | 
| 
       3 
     | 
    
         
            -
                new( 
     | 
| 
      
 2 
     | 
    
         
            +
              def self.call(controller, declaration, rendered)
         
     | 
| 
      
 3 
     | 
    
         
            +
                new(controller, declaration, rendered).call
         
     | 
| 
       4 
4 
     | 
    
         
             
              end
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
              def call
         
     | 
| 
       7 
     | 
    
         
            -
                if declared_return_type 
     | 
| 
       8 
     | 
    
         
            -
                   
     | 
| 
       9 
     | 
    
         
            -
                elsif declared_return_type < Taro::Types:: 
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                   
     | 
| 
       12 
     | 
    
         
            -
                elsif declared_return_type < Taro::Types::EnumType
         
     | 
| 
       13 
     | 
    
         
            -
                  check_enum
         
     | 
| 
      
 7 
     | 
    
         
            +
                if declared_return_type.nil?
         
     | 
| 
      
 8 
     | 
    
         
            +
                  fail_if_declaration_expected
         
     | 
| 
      
 9 
     | 
    
         
            +
                elsif declared_return_type < Taro::Types::NestedResponseType
         
     | 
| 
      
 10 
     | 
    
         
            +
                  field = declared_return_type.nesting_field
         
     | 
| 
      
 11 
     | 
    
         
            +
                  check(field.type, denest_rendered(field.name))
         
     | 
| 
       14 
12 
     | 
    
         
             
                else
         
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
      
 13 
     | 
    
         
            +
                  check(declared_return_type, rendered)
         
     | 
| 
       16 
14 
     | 
    
         
             
                end
         
     | 
| 
       17 
15 
     | 
    
         
             
              end
         
     | 
| 
       18 
16 
     | 
    
         | 
| 
       19 
17 
     | 
    
         
             
              def declared_return_type
         
     | 
| 
       20 
     | 
    
         
            -
                @declared_return_type ||=  
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
      
 18 
     | 
    
         
            +
                @declared_return_type ||= declaration.returns[controller.status]
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              # Rack, Rails and gems commonly trigger rendering of 400, 404, 500 etc.
         
     | 
| 
      
 22 
     | 
    
         
            +
              # Declaring these codes should be optional. Otherwise the api schema would get
         
     | 
| 
      
 23 
     | 
    
         
            +
              # bloated as there are no "global" return declarations in OpenAPI v3, and we'd
         
     | 
| 
      
 24 
     | 
    
         
            +
              # need to export all of these for every single endpoint. v4 might change this.
         
     | 
| 
      
 25 
     | 
    
         
            +
              # https://github.com/OAI/OpenAPI-Specification/issues/521
         
     | 
| 
      
 26 
     | 
    
         
            +
              def fail_if_declaration_expected
         
     | 
| 
      
 27 
     | 
    
         
            +
                controller.status.to_s.match?(/^[123]|422/) && fail_with(<<~MSG)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  No return type declared for this status.
         
     | 
| 
      
 29 
     | 
    
         
            +
                MSG
         
     | 
| 
       25 
30 
     | 
    
         
             
              end
         
     | 
| 
       26 
31 
     | 
    
         | 
| 
       27 
32 
     | 
    
         
             
              def fail_with(message)
         
     | 
| 
       28 
     | 
    
         
            -
                raise Taro::ResponseError,  
     | 
| 
      
 33 
     | 
    
         
            +
                raise Taro::ResponseError.new(<<~MSG, rendered, self)
         
     | 
| 
       29 
34 
     | 
    
         
             
                  Response validation error for
         
     | 
| 
       30 
35 
     | 
    
         
             
                  #{controller.class}##{controller.action_name}, code #{controller.status}":
         
     | 
| 
       31 
36 
     | 
    
         
             
                  #{message}
         
     | 
| 
       32 
37 
     | 
    
         
             
                MSG
         
     | 
| 
       33 
38 
     | 
    
         
             
              end
         
     | 
| 
       34 
39 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
              # support `returns :some_nesting, type: 'SomeType'` 
     | 
| 
       36 
     | 
    
         
            -
               
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
              def denest_rendered
         
     | 
| 
       41 
     | 
    
         
            -
                assert_rendered_is_a_hash
         
     | 
| 
      
 40 
     | 
    
         
            +
              # support `returns :some_nesting, type: 'SomeType'`
         
     | 
| 
      
 41 
     | 
    
         
            +
              # used like `render json: { some_nesting: SomeType.render(some_object) }`
         
     | 
| 
      
 42 
     | 
    
         
            +
              def denest_rendered(nesting)
         
     | 
| 
      
 43 
     | 
    
         
            +
                rendered.is_a?(Hash) || fail_with("Expected Hash, got #{rendered.class}.")
         
     | 
| 
       42 
44 
     | 
    
         | 
| 
       43 
45 
     | 
    
         
             
                if rendered.key?(nesting)
         
     | 
| 
       44 
46 
     | 
    
         
             
                  rendered[nesting]
         
     | 
| 
       45 
     | 
    
         
            -
                elsif rendered.key?(nesting.to_s)
         
     | 
| 
       46 
     | 
    
         
            -
                  rendered[nesting.to_s]
         
     | 
| 
       47 
47 
     | 
    
         
             
                else
         
     | 
| 
       48 
     | 
    
         
            -
                   
     | 
| 
      
 48 
     | 
    
         
            +
                  fail_with "Expected key :#{nesting}, got: #{rendered.keys}."
         
     | 
| 
       49 
49 
     | 
    
         
             
                end
         
     | 
| 
       50 
50 
     | 
    
         
             
              end
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
              def  
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
      
 52 
     | 
    
         
            +
              def check(type, value)
         
     | 
| 
      
 53 
     | 
    
         
            +
                if type < Taro::Types::ScalarType
         
     | 
| 
      
 54 
     | 
    
         
            +
                  check_scalar(type, value)
         
     | 
| 
      
 55 
     | 
    
         
            +
                elsif type < Taro::Types::ListType &&
         
     | 
| 
      
 56 
     | 
    
         
            +
                      type.item_type < Taro::Types::ScalarType
         
     | 
| 
      
 57 
     | 
    
         
            +
                  check_scalar_array(type, value)
         
     | 
| 
      
 58 
     | 
    
         
            +
                elsif type < Taro::Types::EnumType
         
     | 
| 
      
 59 
     | 
    
         
            +
                  check_enum(type, value)
         
     | 
| 
      
 60 
     | 
    
         
            +
                else
         
     | 
| 
      
 61 
     | 
    
         
            +
                  check_custom_type(type, value)
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
       58 
63 
     | 
    
         
             
              end
         
     | 
| 
       59 
64 
     | 
    
         | 
| 
       60 
65 
     | 
    
         
             
              # For scalar and enum types, we want to support e.g. `render json: 42`,
         
     | 
| 
       61 
66 
     | 
    
         
             
              # and not require using the type as in `BeautifulNumbersEnum.render(42)`.
         
     | 
| 
       62 
     | 
    
         
            -
              def check_scalar(type 
     | 
| 
      
 67 
     | 
    
         
            +
              def check_scalar(type, value)
         
     | 
| 
       63 
68 
     | 
    
         
             
                case type.openapi_type
         
     | 
| 
       64 
69 
     | 
    
         
             
                when :integer, :number then value.is_a?(Numeric)
         
     | 
| 
       65 
70 
     | 
    
         
             
                when :string           then value.is_a?(String) || value.is_a?(Symbol)
         
     | 
| 
         @@ -67,18 +72,14 @@ Taro::Rails::ResponseValidator = Struct.new(:controller, :declaration, :rendered 
     | 
|
| 
       67 
72 
     | 
    
         
             
                end || fail_with("Expected a #{type.openapi_type}, got: #{value.class}.")
         
     | 
| 
       68 
73 
     | 
    
         
             
              end
         
     | 
| 
       69 
74 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
              def  
     | 
| 
       71 
     | 
    
         
            -
                 
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       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)
         
     | 
| 
      
 75 
     | 
    
         
            +
              def check_scalar_array(type, value)
         
     | 
| 
      
 76 
     | 
    
         
            +
                value.is_a?(Array) || fail_with('Expected an Array.')
         
     | 
| 
      
 77 
     | 
    
         
            +
                value.empty? || check_scalar(type.item_type, value.first)
         
     | 
| 
       77 
78 
     | 
    
         
             
              end
         
     | 
| 
       78 
79 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
              def check_enum
         
     | 
| 
      
 80 
     | 
    
         
            +
              def check_enum(type, value)
         
     | 
| 
       80 
81 
     | 
    
         
             
                # coercion checks non-emptyness + enum match
         
     | 
| 
       81 
     | 
    
         
            -
                 
     | 
| 
      
 82 
     | 
    
         
            +
                type.new(value).cached_coerce_response
         
     | 
| 
       82 
83 
     | 
    
         
             
              rescue Taro::Error => e
         
     | 
| 
       83 
84 
     | 
    
         
             
                fail_with(e.message)
         
     | 
| 
       84 
85 
     | 
    
         
             
              end
         
     | 
| 
         @@ -86,23 +87,23 @@ Taro::Rails::ResponseValidator = Struct.new(:controller, :declaration, :rendered 
     | 
|
| 
       86 
87 
     | 
    
         
             
              # For complex/object types, we ensure conformance by checking whether
         
     | 
| 
       87 
88 
     | 
    
         
             
              # the type was used for rendering. This has performance benefits compared
         
     | 
| 
       88 
89 
     | 
    
         
             
              # to going over the structure a second time.
         
     | 
| 
       89 
     | 
    
         
            -
              def check_custom_type
         
     | 
| 
      
 90 
     | 
    
         
            +
              def check_custom_type(type, value)
         
     | 
| 
       90 
91 
     | 
    
         
             
                # Ignore types without a specified structure.
         
     | 
| 
       91 
     | 
    
         
            -
                return if  
     | 
| 
       92 
     | 
    
         
            -
                return if  
     | 
| 
      
 92 
     | 
    
         
            +
                return if type <= Taro::Types::ObjectTypes::FreeFormType
         
     | 
| 
      
 93 
     | 
    
         
            +
                return if type <= Taro::Types::ObjectTypes::NoContentType
         
     | 
| 
       93 
94 
     | 
    
         | 
| 
       94 
     | 
    
         
            -
                strict_check_custom_type
         
     | 
| 
      
 95 
     | 
    
         
            +
                strict_check_custom_type(type, value)
         
     | 
| 
       95 
96 
     | 
    
         
             
              end
         
     | 
| 
       96 
97 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
              def strict_check_custom_type
         
     | 
| 
       98 
     | 
    
         
            -
                used_type, rendered_object_id =  
     | 
| 
       99 
     | 
    
         
            -
                used_type 
     | 
| 
       100 
     | 
    
         
            -
                  Expected to use #{ 
     | 
| 
      
 98 
     | 
    
         
            +
              def strict_check_custom_type(type, value)
         
     | 
| 
      
 99 
     | 
    
         
            +
                used_type, rendered_object_id = type.last_render
         
     | 
| 
      
 100 
     | 
    
         
            +
                used_type == type || used_type&.<(type) || fail_with(<<~MSG)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  Expected to use #{type}.render, but the last type rendered
         
     | 
| 
       101 
102 
     | 
    
         
             
                  was: #{used_type || 'no type'}.
         
     | 
| 
       102 
103 
     | 
    
         
             
                MSG
         
     | 
| 
       103 
104 
     | 
    
         | 
| 
       104 
     | 
    
         
            -
                rendered_object_id ==  
     | 
| 
       105 
     | 
    
         
            -
                  #{ 
     | 
| 
      
 105 
     | 
    
         
            +
                rendered_object_id == value.__id__ || fail_with(<<~MSG)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  #{type}.render was called, but the result
         
     | 
| 
       106 
107 
     | 
    
         
             
                  of this call was not used in the response.
         
     | 
| 
       107 
108 
     | 
    
         
             
                MSG
         
     | 
| 
       108 
109 
     | 
    
         
             
              end
         
     | 
| 
         @@ -17,13 +17,11 @@ module Taro::Rails::RouteFinder 
     | 
|
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                def build_cache
         
     | 
| 
       19 
19 
     | 
    
         
             
                  # Build a Hash like
         
     | 
| 
       20 
     | 
    
         
            -
                  # { 'users#show'  
     | 
| 
       21 
     | 
    
         
            -
                  rails_routes 
     | 
| 
       22 
     | 
    
         
            -
                     
     | 
| 
       23 
     | 
    
         
            -
                     
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                    (hash[route.endpoint] ||= []) << route
         
     | 
| 
       26 
     | 
    
         
            -
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # { 'users#show' => [#<NormalizedRoute>, #<NormalizedRoute>] }
         
     | 
| 
      
 21 
     | 
    
         
            +
                  rails_routes
         
     | 
| 
      
 22 
     | 
    
         
            +
                    .map { |r| Taro::Rails::NormalizedRoute.new(r) }
         
     | 
| 
      
 23 
     | 
    
         
            +
                    .reject(&:ignored?)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    .group_by(&:endpoint)
         
     | 
| 
       27 
25 
     | 
    
         
             
                end
         
     | 
| 
       28 
26 
     | 
    
         | 
| 
       29 
27 
     | 
    
         
             
                def rails_routes
         
     | 
| 
         @@ -3,17 +3,18 @@ task 'taro:export' => :environment do 
     | 
|
| 
       3 
3 
     | 
    
         
             
              # make sure all declarations have been seen
         
     | 
| 
       4 
4 
     | 
    
         
             
              Rails.application.eager_load!
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
      
 6 
     | 
    
         
            +
              title = Taro.config.api_name
         
     | 
| 
      
 7 
     | 
    
         
            +
              version = Taro.config.api_version
         
     | 
| 
      
 8 
     | 
    
         
            +
              format = Taro.config.export_format
         
     | 
| 
      
 9 
     | 
    
         
            +
              path = Taro.config.export_path
         
     | 
| 
       6 
10 
     | 
    
         
             
              # the generator / openapi version might become a config option later
         
     | 
| 
       7 
     | 
    
         
            -
              export = Taro::Export::OpenAPIv3.call(
         
     | 
| 
       8 
     | 
    
         
            -
                declarations: Taro::Rails.declarations,
         
     | 
| 
       9 
     | 
    
         
            -
                title: Taro.config.api_name,
         
     | 
| 
       10 
     | 
    
         
            -
                version: Taro.config.api_version,
         
     | 
| 
       11 
     | 
    
         
            -
              )
         
     | 
| 
       12 
11 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
               
     | 
| 
      
 12 
     | 
    
         
            +
              export = Taro::Export::OpenAPIv3.call(title:, version:)
         
     | 
| 
       14 
13 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
               
     | 
| 
       16 
     | 
    
         
            -
              File.write(Taro.config.export_path, data)
         
     | 
| 
      
 14 
     | 
    
         
            +
              data = export.send("to_#{format}")
         
     | 
| 
       17 
15 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
               
     | 
| 
      
 16 
     | 
    
         
            +
              FileUtils.mkdir_p(File.dirname(path))
         
     | 
| 
      
 17 
     | 
    
         
            +
              File.write(path, data)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              puts "Exported the API #{title} v#{version} to #{path}"
         
     | 
| 
       19 
20 
     | 
    
         
             
            end
         
     | 
    
        data/lib/taro/rails.rb
    CHANGED
    
    | 
         @@ -3,15 +3,14 @@ return unless defined?(::Rails) 
     | 
|
| 
       3 
3 
     | 
    
         
             
            # :nocov:
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module Taro::Rails
         
     | 
| 
       6 
     | 
    
         
            -
              Dir[File.join(__dir__, "rails", "*.rb")].each { |f|  
     | 
| 
      
 6 
     | 
    
         
            +
              Dir[File.join(__dir__, "rails", "*.rb")].each { |f| require_relative f }
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              extend ActiveDeclarations
         
     | 
| 
       9 
9 
     | 
    
         
             
              extend DeclarationBuffer
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
              def self.reset
         
     | 
| 
       12 
12 
     | 
    
         
             
                buffered_declarations.clear
         
     | 
| 
       13 
     | 
    
         
            -
                declarations_map.clear
         
     | 
| 
       14 
13 
     | 
    
         
             
                RouteFinder.clear_cache
         
     | 
| 
       15 
     | 
    
         
            -
                Taro 
     | 
| 
      
 14 
     | 
    
         
            +
                Taro.reset
         
     | 
| 
       16 
15 
     | 
    
         
             
              end
         
     | 
| 
       17 
16 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Lazily-evaluated response type definition.
         
     | 
| 
      
 2 
     | 
    
         
            +
            class Taro::ReturnDef
         
     | 
| 
      
 3 
     | 
    
         
            +
              attr_reader :code, :defined_at, :desc, :nesting, :params
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              def initialize(code:, defined_at: nil, desc: nil, nesting: nil, **params)
         
     | 
| 
      
 6 
     | 
    
         
            +
                @code = Taro::StatusCode.coerce_to_int(code)
         
     | 
| 
      
 7 
     | 
    
         
            +
                @defined_at = defined_at
         
     | 
| 
      
 8 
     | 
    
         
            +
                @desc = desc
         
     | 
| 
      
 9 
     | 
    
         
            +
                @nesting = nesting
         
     | 
| 
      
 10 
     | 
    
         
            +
                @params = params
         
     | 
| 
      
 11 
     | 
    
         
            +
                validate
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def evaluate
         
     | 
| 
      
 15 
     | 
    
         
            +
                if nesting
         
     | 
| 
      
 16 
     | 
    
         
            +
                  Class.new(Taro::Types::NestedResponseType).tap do |type|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    type.field(nesting, defined_at:, null: false, **params)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                else
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Taro::Types::Coercion.call(params)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              private
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              def validate
         
     | 
| 
      
 27 
     | 
    
         
            +
                # For nested returns, call ::field, which validates
         
     | 
| 
      
 28 
     | 
    
         
            +
                # field options, but does not trigger type auto-loading.
         
     | 
| 
      
 29 
     | 
    
         
            +
                return evaluate if nesting
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                if params.key?(:null)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  raise Taro::ArgumentError, <<~MSG
         
     | 
| 
      
 33 
     | 
    
         
            +
                    `null:` is not supported for top-level returns. If you want a nullable return
         
     | 
| 
      
 34 
     | 
    
         
            +
                    value, nest it, e.g. `returns :str, type: 'String', null: true`.
         
     | 
| 
      
 35 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                bad_keys = params.keys - (Taro::Types::Coercion.keys + %i[defined_at])
         
     | 
| 
      
 39 
     | 
    
         
            +
                return if bad_keys.empty?
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                raise Taro::ArgumentError, "Invalid `returns` options: #{bad_keys.join(', ')}"
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/taro/route.rb
    ADDED
    
    | 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Taro::Route
         
     | 
| 
      
 2 
     | 
    
         
            +
              attr_reader :endpoint, :openapi_operation_id, :openapi_path, :verb
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              def initialize(endpoint:, openapi_operation_id:, openapi_path:, verb:)
         
     | 
| 
      
 5 
     | 
    
         
            +
                @endpoint = validate_string(endpoint:)
         
     | 
| 
      
 6 
     | 
    
         
            +
                @openapi_operation_id = validate_string(openapi_operation_id:)
         
     | 
| 
      
 7 
     | 
    
         
            +
                @openapi_path = validate_string(openapi_path:)
         
     | 
| 
      
 8 
     | 
    
         
            +
                @verb = validate_string(verb:).downcase
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def path_params
         
     | 
| 
      
 12 
     | 
    
         
            +
                openapi_path.scan(/{(\w+)}/).flatten.map(&:to_sym)
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def can_have_request_body?
         
     | 
| 
      
 16 
     | 
    
         
            +
                %w[patch post put].include?(verb)
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def inspect
         
     | 
| 
      
 20 
     | 
    
         
            +
                %(#<#{self.class} "#{verb} #{openapi_path}">)
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
              alias to_s inspect
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              private
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              def validate_string(**kwarg)
         
     | 
| 
      
 27 
     | 
    
         
            +
                name, arg = kwarg.first
         
     | 
| 
      
 28 
     | 
    
         
            +
                return arg if arg.is_a?(String)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                raise(Taro::ArgumentError, "#{name} must be a String, got #{arg.class}")
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rack'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Taro::StatusCode
         
     | 
| 
      
 4 
     | 
    
         
            +
              def self.coerce_to_int(arg)
         
     | 
| 
      
 5 
     | 
    
         
            +
                # support using http status numbers directly
         
     | 
| 
      
 6 
     | 
    
         
            +
                return arg if ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(arg)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # support using symbols, but coerce them to numbers
         
     | 
| 
      
 9 
     | 
    
         
            +
                ::Rack::Utils::SYMBOL_TO_STATUS_CODE[arg] ||
         
     | 
| 
      
 10 
     | 
    
         
            +
                  raise(Taro::ArgumentError, "Invalid status: #{arg.inspect}")
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def self.coerce_to_message(arg)
         
     | 
| 
      
 14 
     | 
    
         
            +
                ::Rack::Utils::HTTP_STATUS_CODES.fetch(coerce_to_int(arg))
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     |