taro 0.0.0 → 1.0.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/.rspec +3 -0
 - data/.rubocop.yml +22 -0
 - data/CHANGELOG.md +5 -0
 - data/README.md +262 -1
 - data/Rakefile +11 -0
 - data/lib/taro/config.rb +22 -0
 - data/lib/taro/errors.rb +6 -0
 - data/lib/taro/export/base.rb +29 -0
 - data/lib/taro/export/open_api_v3.rb +189 -0
 - data/lib/taro/export.rb +3 -0
 - data/lib/taro/rails/active_declarations.rb +19 -0
 - data/lib/taro/rails/declaration.rb +101 -0
 - data/lib/taro/rails/declaration_buffer.rb +24 -0
 - data/lib/taro/rails/dsl.rb +18 -0
 - data/lib/taro/rails/generators/install_generator.rb +19 -0
 - data/lib/taro/rails/generators/templates/enum_type.erb +4 -0
 - data/lib/taro/rails/generators/templates/error_type.erb +10 -0
 - data/lib/taro/rails/generators/templates/errors_type.erb +25 -0
 - data/lib/taro/rails/generators/templates/input_type.erb +4 -0
 - data/lib/taro/rails/generators/templates/no_content_type.erb +4 -0
 - data/lib/taro/rails/generators/templates/object_type.erb +4 -0
 - data/lib/taro/rails/generators/templates/scalar_type.erb +4 -0
 - data/lib/taro/rails/generators.rb +3 -0
 - data/lib/taro/rails/normalized_route.rb +29 -0
 - data/lib/taro/rails/param_parsing.rb +19 -0
 - data/lib/taro/rails/railtie.rb +15 -0
 - data/lib/taro/rails/response_validation.rb +63 -0
 - data/lib/taro/rails/route_finder.rb +35 -0
 - data/lib/taro/rails/tasks/export.rake +15 -0
 - data/lib/taro/rails.rb +18 -0
 - data/lib/taro/types/base_type.rb +17 -0
 - data/lib/taro/types/coercion.rb +72 -0
 - data/lib/taro/types/enum_type.rb +43 -0
 - data/lib/taro/types/field.rb +78 -0
 - data/lib/taro/types/field_validation.rb +27 -0
 - data/lib/taro/types/input_type.rb +13 -0
 - data/lib/taro/types/list_type.rb +30 -0
 - data/lib/taro/types/object_type.rb +19 -0
 - data/lib/taro/types/object_types/free_form_type.rb +13 -0
 - data/lib/taro/types/object_types/no_content_type.rb +16 -0
 - data/lib/taro/types/object_types/page_info_type.rb +6 -0
 - data/lib/taro/types/object_types/page_type.rb +45 -0
 - data/lib/taro/types/scalar/boolean_type.rb +19 -0
 - data/lib/taro/types/scalar/float_type.rb +15 -0
 - data/lib/taro/types/scalar/integer_type.rb +11 -0
 - data/lib/taro/types/scalar/iso8601_date_type.rb +23 -0
 - data/lib/taro/types/scalar/iso8601_datetime_type.rb +25 -0
 - data/lib/taro/types/scalar/string_type.rb +15 -0
 - data/lib/taro/types/scalar/timestamp_type.rb +23 -0
 - data/lib/taro/types/scalar/uuid_v4_type.rb +22 -0
 - data/lib/taro/types/scalar_type.rb +7 -0
 - data/lib/taro/types/shared/additional_properties.rb +12 -0
 - data/lib/taro/types/shared/custom_field_resolvers.rb +33 -0
 - data/lib/taro/types/shared/derivable_types.rb +9 -0
 - data/lib/taro/types/shared/description.rb +9 -0
 - data/lib/taro/types/shared/errors.rb +13 -0
 - data/lib/taro/types/shared/fields.rb +57 -0
 - data/lib/taro/types/shared/item_type.rb +16 -0
 - data/lib/taro/types/shared/object_coercion.rb +16 -0
 - data/lib/taro/types/shared/openapi_name.rb +30 -0
 - data/lib/taro/types/shared/openapi_type.rb +27 -0
 - data/lib/taro/types/shared/rendering.rb +36 -0
 - data/lib/taro/types/shared.rb +3 -0
 - data/lib/taro/types.rb +3 -0
 - data/lib/taro/version.rb +2 -3
 - data/lib/taro.rb +1 -6
 - data/tasks/benchmark.rake +40 -0
 - data/tasks/benchmark_1kb.json +23 -0
 - metadata +90 -7
 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Buffers api declarations in rails controllers (e.g. `param :foo, ...`)
         
     | 
| 
      
 2 
     | 
    
         
            +
            # until the next action method is defined (e.g. `def create`).
         
     | 
| 
      
 3 
     | 
    
         
            +
            module Taro::Rails::DeclarationBuffer
         
     | 
| 
      
 4 
     | 
    
         
            +
              def buffered_declaration(controller_class)
         
     | 
| 
      
 5 
     | 
    
         
            +
                buffered_declarations[controller_class] ||= Taro::Rails::Declaration.new
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def buffered_declarations
         
     | 
| 
      
 9 
     | 
    
         
            +
                @buffered_declarations ||= {}
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def apply_buffered_declaration(controller_class, action_name)
         
     | 
| 
      
 13 
     | 
    
         
            +
                declaration = pop_buffered_declaration(controller_class)
         
     | 
| 
      
 14 
     | 
    
         
            +
                return unless declaration
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                declaration.finalize(controller_class:, action_name:)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                Taro::Rails.apply(declaration:, controller_class:, action_name:)
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def pop_buffered_declaration(controller_class)
         
     | 
| 
      
 22 
     | 
    
         
            +
                buffered_declarations.delete(controller_class)
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Rails::DSL
         
     | 
| 
      
 2 
     | 
    
         
            +
              def api(summary, **kwargs)
         
     | 
| 
      
 3 
     | 
    
         
            +
                Taro::Rails.buffered_declaration(self).add_info(summary, **kwargs)
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def param(param_name, **kwargs)
         
     | 
| 
      
 7 
     | 
    
         
            +
                Taro::Rails.buffered_declaration(self).add_param(param_name, **kwargs)
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def returns(field_name = nil, **kwargs)
         
     | 
| 
      
 11 
     | 
    
         
            +
                Taro::Rails.buffered_declaration(self).add_return(field_name, **kwargs)
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def method_added(method_name)
         
     | 
| 
      
 15 
     | 
    
         
            +
                Taro::Rails.apply_buffered_declaration(self, method_name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                super
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rails/generators'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rails/generators/base'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class Taro::Rails::Generators::InstallGenerator < ::Rails::Generators::Base
         
     | 
| 
      
 5 
     | 
    
         
            +
              desc 'Set up Taro base type files in your Rails app'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              class_option :dir, type: :string, default: "app/types"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              source_root File.expand_path("templates", __dir__)
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              # :nocov:
         
     | 
| 
      
 12 
     | 
    
         
            +
              def create_type_files
         
     | 
| 
      
 13 
     | 
    
         
            +
                Dir["#{self.class.source_root}/**/*.erb"].each do |tmpl|
         
     | 
| 
      
 14 
     | 
    
         
            +
                  dest_dir = options[:dir].chomp('/')
         
     | 
| 
      
 15 
     | 
    
         
            +
                  copy_file tmpl, "#{dest_dir}/#{File.basename(tmpl).sub('erb', 'rb')}"
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
              # :nocov:
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # This file is generated by taro.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # This and ErrorsType are a starting point for unified error presentation.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # You can use them to render errors from various sources (ActiveRecord etc.)
         
     | 
| 
      
 4 
     | 
    
         
            +
            # and render error responses in rescue_from in a consistent way.
         
     | 
| 
      
 5 
     | 
    
         
            +
            # You can customize these files to fit your needs, or delete them.
         
     | 
| 
      
 6 
     | 
    
         
            +
            class ErrorType < ObjectType
         
     | 
| 
      
 7 
     | 
    
         
            +
              field :attribute, type: 'String', null: true, desc: 'Attribute name'
         
     | 
| 
      
 8 
     | 
    
         
            +
              field :code, type: 'String', null: false, method: :type, desc: 'Error code'
         
     | 
| 
      
 9 
     | 
    
         
            +
              field :message, type: 'String', null: true, desc: 'Error message'
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # This file is generated by taro.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # This and ErrorType are a starting point for unified error presentation.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # You can use them to render errors from various sources (ActiveRecord etc.)
         
     | 
| 
      
 4 
     | 
    
         
            +
            # and render error responses in rescue_from in a consistent way.
         
     | 
| 
      
 5 
     | 
    
         
            +
            # You can customize these files to fit your needs, or delete them.
         
     | 
| 
      
 6 
     | 
    
         
            +
            class ErrorsType < Taro::Types::ListType
         
     | 
| 
      
 7 
     | 
    
         
            +
              self.item_type = ErrorType
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              def coerce_input
         
     | 
| 
      
 10 
     | 
    
         
            +
                input_error 'ErrorsType cannot be used as input type'
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def coerce_response
         
     | 
| 
      
 14 
     | 
    
         
            +
                case object.class.name
         
     | 
| 
      
 15 
     | 
    
         
            +
                when 'ActiveRecord::Base'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  super(object.errors.errors)
         
     | 
| 
      
 17 
     | 
    
         
            +
                when 'ActiveModel::Errors'
         
     | 
| 
      
 18 
     | 
    
         
            +
                  super(object.errors)
         
     | 
| 
      
 19 
     | 
    
         
            +
                when 'Hash', 'Interactor::Context'
         
     | 
| 
      
 20 
     | 
    
         
            +
                  super(object[:errors])
         
     | 
| 
      
 21 
     | 
    
         
            +
                else # e.g. Array
         
     | 
| 
      
 22 
     | 
    
         
            +
                  super(object)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Taro::Rails::NormalizedRoute = Data.define(:rails_route) do
         
     | 
| 
      
 2 
     | 
    
         
            +
              def ignored?
         
     | 
| 
      
 3 
     | 
    
         
            +
                verb.to_s.empty? || patch_update?
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 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 
     | 
    
         
            +
              def verb
         
     | 
| 
      
 9 
     | 
    
         
            +
                rails_route.verb.to_s.scan(/\w+/).sort.last&.downcase
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              # Rails has both PATCH and PUT routes for updates. We only need one copy.
         
     | 
| 
      
 13 
     | 
    
         
            +
              def patch_update?
         
     | 
| 
      
 14 
     | 
    
         
            +
                verb == 'patch' && rails_route.requirements[:action] == 'update'
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def openapi_path
         
     | 
| 
      
 18 
     | 
    
         
            +
                rails_route.path.spec.to_s.gsub('(.:format)', '').gsub(/:(\w+)/, '{\1}')
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def path_params
         
     | 
| 
      
 22 
     | 
    
         
            +
                openapi_path.scan(/{(\w+)}/).flatten.map(&:to_sym)
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def endpoint
         
     | 
| 
      
 26 
     | 
    
         
            +
                controller, action = rails_route.requirements.values_at(:controller, :action)
         
     | 
| 
      
 27 
     | 
    
         
            +
                "#{controller}##{action}"
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Rails::ParamParsing
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.install(controller_class:, action_name:)
         
     | 
| 
      
 3 
     | 
    
         
            +
                return unless Taro.config.parse_params
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                key = [controller_class, action_name]
         
     | 
| 
      
 6 
     | 
    
         
            +
                return if installed[key]
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                installed[key] = true
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                controller_class.before_action(only: action_name) do
         
     | 
| 
      
 11 
     | 
    
         
            +
                  declaration = Taro::Rails.declaration_for(self)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @api_params = declaration.parse_params(params)
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def self.installed
         
     | 
| 
      
 17 
     | 
    
         
            +
                @installed ||= {}
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Taro::Rails::Railtie < ::Rails::Railtie
         
     | 
| 
      
 2 
     | 
    
         
            +
              initializer("taro") do |app|
         
     | 
| 
      
 3 
     | 
    
         
            +
                # The `:action_controller` hook fires for both ActionController::API
         
     | 
| 
      
 4 
     | 
    
         
            +
                # and ActionController::Base, executing the block in their context.
         
     | 
| 
      
 5 
     | 
    
         
            +
                ActiveSupport.on_load(:action_controller) do
         
     | 
| 
      
 6 
     | 
    
         
            +
                  extend Taro::Rails::DSL
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                app.reloader.to_prepare do
         
     | 
| 
      
 10 
     | 
    
         
            +
                  Taro::Rails.reset
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              rake_tasks { Dir["#{__dir__}/tasks/**/*.rake"].each { |f| load f } }
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Rails::ResponseValidation
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.install(controller_class:, action_name:)
         
     | 
| 
      
 3 
     | 
    
         
            +
                return unless Taro.config.validate_response
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                key = [controller_class, action_name]
         
     | 
| 
      
 6 
     | 
    
         
            +
                return if installed[key]
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                installed[key] = true
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                controller_class.around_action(only: action_name) do |_, block|
         
     | 
| 
      
 11 
     | 
    
         
            +
                  Taro::Types::BaseType.rendering = nil
         
     | 
| 
      
 12 
     | 
    
         
            +
                  block.call
         
     | 
| 
      
 13 
     | 
    
         
            +
                  Taro::Rails::ResponseValidation.call(self)
         
     | 
| 
      
 14 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 15 
     | 
    
         
            +
                  Taro::Types::BaseType.rendering = nil
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def self.installed
         
     | 
| 
      
 20 
     | 
    
         
            +
                @installed ||= {}
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def self.call(controller)
         
     | 
| 
      
 24 
     | 
    
         
            +
                declaration = Taro::Rails.declaration_for(controller)
         
     | 
| 
      
 25 
     | 
    
         
            +
                nesting = declaration.return_nestings[controller.status]
         
     | 
| 
      
 26 
     | 
    
         
            +
                expected = declaration.returns[controller.status]
         
     | 
| 
      
 27 
     | 
    
         
            +
                if nesting
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # case: `returns :some_nesting, type: 'SomeType'` (ad-hoc return type)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  check_nesting(controller.response, nesting)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  expected = expected.fields[nesting].type
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                check_expected_type_was_used(controller, expected)
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              def self.check_nesting(response, nesting)
         
     | 
| 
      
 37 
     | 
    
         
            +
                return unless /json/.match?(response.media_type)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                first_key = response.body.to_s[/\A{\s*"([^"]+)"/, 1]
         
     | 
| 
      
 40 
     | 
    
         
            +
                first_key == nesting.to_s || raise(Taro::ResponseError, <<~MSG)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  Expected response to be nested in "#{nesting}" key, but it was not.
         
     | 
| 
      
 42 
     | 
    
         
            +
                  (First JSON key in response: "#{first_key}".)
         
     | 
| 
      
 43 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              def self.check_expected_type_was_used(controller, expected)
         
     | 
| 
      
 47 
     | 
    
         
            +
                used = Taro::Types::BaseType.rendering
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                if expected.nil?
         
     | 
| 
      
 50 
     | 
    
         
            +
                  raise(Taro::ResponseError, <<~MSG)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    No matching return type declared in #{controller.class}##{controller.action_name}\
         
     | 
| 
      
 52 
     | 
    
         
            +
                    for status #{controller.status}.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                used&.<=(expected) || raise(Taro::ResponseError, <<~MSG)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  Expected #{controller.class}##{controller.action_name} to use #{expected}.render,
         
     | 
| 
      
 58 
     | 
    
         
            +
                  but #{used ? "#{used}.render" : 'no type render method'} was called.
         
     | 
| 
      
 59 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                Taro::Types::BaseType.used_in_response = used # for comparisons in specs
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Rails::RouteFinder
         
     | 
| 
      
 2 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 3 
     | 
    
         
            +
                def call(controller_class:, action_name:)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  endpoint = "#{controller_class.controller_path}##{action_name}"
         
     | 
| 
      
 5 
     | 
    
         
            +
                  cache[endpoint] || []
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def clear_cache
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @cache = nil
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                private
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def cache
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @cache ||= build_cache
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def build_cache
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # Build a Hash like
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # { 'users#show' } => [#<NormalizedRoute>, #<NormalizedRoute>] }
         
     | 
| 
      
 21 
     | 
    
         
            +
                  rails_routes.each_with_object({}) do |rails_route, hash|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    route = Taro::Rails::NormalizedRoute.new(rails_route:)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    next if route.ignored?
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    (hash[route.endpoint] ||= []) << route
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def rails_routes
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # make sure routes are loaded
         
     | 
| 
      
 31 
     | 
    
         
            +
                  Rails.application.reload_routes! unless Rails.application.routes.routes.any?
         
     | 
| 
      
 32 
     | 
    
         
            +
                  Rails.application.routes.routes
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            desc 'Export all taro API declarations to a file'
         
     | 
| 
      
 2 
     | 
    
         
            +
            task 'taro:export' => :environment do
         
     | 
| 
      
 3 
     | 
    
         
            +
              # make sure all declarations have been seen
         
     | 
| 
      
 4 
     | 
    
         
            +
              Rails.application.eager_load!
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              # 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 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              data = export.result.send("to_#{Taro.config.export_format}")
         
     | 
| 
      
 14 
     | 
    
         
            +
              File.write(Taro.config.export_path, data)
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/taro/rails.rb
    ADDED
    
    | 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # :nocov:
         
     | 
| 
      
 2 
     | 
    
         
            +
            return unless defined?(::Rails)
         
     | 
| 
      
 3 
     | 
    
         
            +
            # :nocov:
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Taro::Rails
         
     | 
| 
      
 6 
     | 
    
         
            +
              Dir[File.join(__dir__, "rails", "*.rb")].each { |f| require f }
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              extend ActiveDeclarations
         
     | 
| 
      
 9 
     | 
    
         
            +
              extend DeclarationBuffer
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def self.reset
         
     | 
| 
      
 12 
     | 
    
         
            +
                buffered_declarations.clear
         
     | 
| 
      
 13 
     | 
    
         
            +
                declarations_map.clear
         
     | 
| 
      
 14 
     | 
    
         
            +
                RouteFinder.clear_cache
         
     | 
| 
      
 15 
     | 
    
         
            +
                Taro::Types::BaseType.rendering = nil
         
     | 
| 
      
 16 
     | 
    
         
            +
                Taro::Types::BaseType.used_in_response = nil
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Abstract base class for all types.
         
     | 
| 
      
 2 
     | 
    
         
            +
            #
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Concrete type classes must set `self.openapi_type` and implement
         
     | 
| 
      
 4 
     | 
    
         
            +
            # the `#coerce_input` and `#coerce_response` methods.
         
     | 
| 
      
 5 
     | 
    
         
            +
            #
         
     | 
| 
      
 6 
     | 
    
         
            +
            # Instances of types are initialized with the object that they represent.
         
     | 
| 
      
 7 
     | 
    
         
            +
            # The object is a parameter hash for inputs and a manually passed hash
         
     | 
| 
      
 8 
     | 
    
         
            +
            # or object when rendering a response.
         
     | 
| 
      
 9 
     | 
    
         
            +
            Taro::Types::BaseType = Data.define(:object) do
         
     | 
| 
      
 10 
     | 
    
         
            +
              require_relative "shared"
         
     | 
| 
      
 11 
     | 
    
         
            +
              extend Taro::Types::Shared::AdditionalProperties
         
     | 
| 
      
 12 
     | 
    
         
            +
              extend Taro::Types::Shared::Description
         
     | 
| 
      
 13 
     | 
    
         
            +
              extend Taro::Types::Shared::OpenAPIName
         
     | 
| 
      
 14 
     | 
    
         
            +
              extend Taro::Types::Shared::OpenAPIType
         
     | 
| 
      
 15 
     | 
    
         
            +
              extend Taro::Types::Shared::Rendering
         
     | 
| 
      
 16 
     | 
    
         
            +
              include Taro::Types::Shared::Errors
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,72 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Types::Coercion
         
     | 
| 
      
 2 
     | 
    
         
            +
              KEYS = %i[type array_of page_of].freeze
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 5 
     | 
    
         
            +
                def call(arg)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  validate_hash(arg)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  from_hash(arg)
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                private
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def validate_hash(arg)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  arg.is_a?(Hash) || raise(Taro::ArgumentError, <<~MSG)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    Type coercion argument must be a Hash, got: #{arg.inspect} (#{arg.class})
         
     | 
| 
      
 15 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  types = arg.slice(*KEYS)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  types.size == 1 || raise(Taro::ArgumentError, <<~MSG)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    Exactly one of type, array_of, or page_of must be given, got: #{types}
         
     | 
| 
      
 20 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def from_hash(hash)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  if hash[:type]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    from_string(hash[:type])
         
     | 
| 
      
 26 
     | 
    
         
            +
                  elsif (inner_type = hash[:array_of])
         
     | 
| 
      
 27 
     | 
    
         
            +
                    from_string(inner_type).array
         
     | 
| 
      
 28 
     | 
    
         
            +
                  elsif (inner_type = hash[:page_of])
         
     | 
| 
      
 29 
     | 
    
         
            +
                    from_string(inner_type).page
         
     | 
| 
      
 30 
     | 
    
         
            +
                  else
         
     | 
| 
      
 31 
     | 
    
         
            +
                    raise NotImplementedError, 'Unsupported type coercion'
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def from_string(arg)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  shortcuts[arg] || from_class(Object.const_get(arg.to_s))
         
     | 
| 
      
 37 
     | 
    
         
            +
                rescue NameError
         
     | 
| 
      
 38 
     | 
    
         
            +
                  raise Taro::ArgumentError, <<~MSG
         
     | 
| 
      
 39 
     | 
    
         
            +
                    Unsupported type: #{arg}. It should be a type-class name
         
     | 
| 
      
 40 
     | 
    
         
            +
                    or one of #{shortcuts.keys.map(&:inspect).join(', ')}.
         
     | 
| 
      
 41 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def from_class(arg)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  arg < Taro::Types::BaseType || raise(Taro::ArgumentError, <<~MSG)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    Unsupported type: #{arg}. It should be a subclass of Taro::Types::BaseType.
         
     | 
| 
      
 47 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  arg
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                # Map some Ruby class names and other shortcuts to built-in types
         
     | 
| 
      
 53 
     | 
    
         
            +
                # to support e.g. `returns 'String'`, or `field :foo, type: 'Boolean'` etc.
         
     | 
| 
      
 54 
     | 
    
         
            +
                require 'date'
         
     | 
| 
      
 55 
     | 
    
         
            +
                def shortcuts
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @shortcuts ||= {
         
     | 
| 
      
 57 
     | 
    
         
            +
                    # rubocop:disable Layout/HashAlignment - buggy cop
         
     | 
| 
      
 58 
     | 
    
         
            +
                    'Boolean'   => Taro::Types::Scalar::BooleanType,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    'Float'     => Taro::Types::Scalar::FloatType,
         
     | 
| 
      
 60 
     | 
    
         
            +
                    'FreeForm'  => Taro::Types::ObjectTypes::FreeFormType,
         
     | 
| 
      
 61 
     | 
    
         
            +
                    'Integer'   => Taro::Types::Scalar::IntegerType,
         
     | 
| 
      
 62 
     | 
    
         
            +
                    'String'    => Taro::Types::Scalar::StringType,
         
     | 
| 
      
 63 
     | 
    
         
            +
                    'Timestamp' => Taro::Types::Scalar::TimestampType,
         
     | 
| 
      
 64 
     | 
    
         
            +
                    'UUID'      => Taro::Types::Scalar::UUIDv4Type,
         
     | 
| 
      
 65 
     | 
    
         
            +
                    'Date'      => Taro::Types::Scalar::ISO8601DateType,
         
     | 
| 
      
 66 
     | 
    
         
            +
                    'Time'      => Taro::Types::Scalar::ISO8601DateTimeType,
         
     | 
| 
      
 67 
     | 
    
         
            +
                    'DateTime'  => Taro::Types::Scalar::ISO8601DateTimeType,
         
     | 
| 
      
 68 
     | 
    
         
            +
                    # rubocop:enable Layout/HashAlignment - buggy cop
         
     | 
| 
      
 69 
     | 
    
         
            +
                  }.freeze
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Abstract class.
         
     | 
| 
      
 2 
     | 
    
         
            +
            class Taro::Types::EnumType < Taro::Types::BaseType
         
     | 
| 
      
 3 
     | 
    
         
            +
              extend Taro::Types::Shared::ItemType
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              def self.value(value)
         
     | 
| 
      
 6 
     | 
    
         
            +
                self.item_type = Taro::Types::Coercion.call(type: value.class.name)
         
     | 
| 
      
 7 
     | 
    
         
            +
                @openapi_type ||= item_type.openapi_type
         
     | 
| 
      
 8 
     | 
    
         
            +
                values << value
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def self.values
         
     | 
| 
      
 12 
     | 
    
         
            +
                @values ||= []
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def coerce_input
         
     | 
| 
      
 16 
     | 
    
         
            +
                self.class.raise_if_empty_enum
         
     | 
| 
      
 17 
     | 
    
         
            +
                value = self.class.item_type.new(object).coerce_input
         
     | 
| 
      
 18 
     | 
    
         
            +
                if self.class.values.include?(value)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  value
         
     | 
| 
      
 20 
     | 
    
         
            +
                else
         
     | 
| 
      
 21 
     | 
    
         
            +
                  input_error("must be one of #{self.class.values}")
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def coerce_response
         
     | 
| 
      
 26 
     | 
    
         
            +
                self.class.raise_if_empty_enum
         
     | 
| 
      
 27 
     | 
    
         
            +
                value = self.class.item_type.new(object).coerce_response
         
     | 
| 
      
 28 
     | 
    
         
            +
                if self.class.values.include?(value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  value
         
     | 
| 
      
 30 
     | 
    
         
            +
                else
         
     | 
| 
      
 31 
     | 
    
         
            +
                  response_error("must be one of #{self.class.values}")
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              def self.raise_if_empty_enum
         
     | 
| 
      
 36 
     | 
    
         
            +
                values.empty? && raise(Taro::RuntimeError, "Enum #{self} has no values")
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def self.inherited(subclass)
         
     | 
| 
      
 40 
     | 
    
         
            +
                subclass.instance_variable_set(:@values, values.dup)
         
     | 
| 
      
 41 
     | 
    
         
            +
                super
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'field_validation'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc) do
         
     | 
| 
      
 4 
     | 
    
         
            +
              include Taro::Types::FieldValidation
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil)
         
     | 
| 
      
 7 
     | 
    
         
            +
                enum = coerce_to_enum(enum)
         
     | 
| 
      
 8 
     | 
    
         
            +
                super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:)
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def value_for_input(object)
         
     | 
| 
      
 12 
     | 
    
         
            +
                value = object[name] if object
         
     | 
| 
      
 13 
     | 
    
         
            +
                value = coerce_value(value, true)
         
     | 
| 
      
 14 
     | 
    
         
            +
                validated_value(value)
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def value_for_response(object, context: nil, object_is_hash: true)
         
     | 
| 
      
 18 
     | 
    
         
            +
                value = retrieve_response_value(object, context, object_is_hash)
         
     | 
| 
      
 19 
     | 
    
         
            +
                value = coerce_value(value, false)
         
     | 
| 
      
 20 
     | 
    
         
            +
                validated_value(value, false)
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def default_specified?
         
     | 
| 
      
 24 
     | 
    
         
            +
                !default.equal?(:none)
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def openapi_type
         
     | 
| 
      
 28 
     | 
    
         
            +
                type.openapi_type
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              private
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              def coerce_to_enum(arg)
         
     | 
| 
      
 34 
     | 
    
         
            +
                return if arg.nil?
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                enum = arg.to_a
         
     | 
| 
      
 37 
     | 
    
         
            +
                test = Class.new(Taro::Types::EnumType) { arg.each { |v| value(v) } }
         
     | 
| 
      
 38 
     | 
    
         
            +
                test.raise_if_empty_enum
         
     | 
| 
      
 39 
     | 
    
         
            +
                enum
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              def retrieve_response_value(object, context, object_is_hash)
         
     | 
| 
      
 43 
     | 
    
         
            +
                if object_is_hash
         
     | 
| 
      
 44 
     | 
    
         
            +
                  retrieve_hash_value(object)
         
     | 
| 
      
 45 
     | 
    
         
            +
                elsif context&.resolve?(method)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  context.public_send(method)
         
     | 
| 
      
 47 
     | 
    
         
            +
                elsif object.respond_to?(method, true)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  object.public_send(method)
         
     | 
| 
      
 49 
     | 
    
         
            +
                else
         
     | 
| 
      
 50 
     | 
    
         
            +
                  raise_response_coercion_error(object)
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
              def retrieve_hash_value(object)
         
     | 
| 
      
 55 
     | 
    
         
            +
                if object.key?(method.to_s)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  object[method.to_s]
         
     | 
| 
      
 57 
     | 
    
         
            +
                else
         
     | 
| 
      
 58 
     | 
    
         
            +
                  object[method]
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              def coerce_value(value, from_input)
         
     | 
| 
      
 63 
     | 
    
         
            +
                return if value.nil? && null
         
     | 
| 
      
 64 
     | 
    
         
            +
                return default if value.nil? && default_specified?
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                type_obj = type.new(value)
         
     | 
| 
      
 67 
     | 
    
         
            +
                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 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Taro::Types::FieldValidation
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Validate the value against the field properties. This method will raise
         
     | 
| 
      
 3 
     | 
    
         
            +
              # a Taro::InputError or Taro::ResponseError if the value is not matching.
         
     | 
| 
      
 4 
     | 
    
         
            +
              def validated_value(value, for_input = true)
         
     | 
| 
      
 5 
     | 
    
         
            +
                validate_null_and_ok?(value, for_input)
         
     | 
| 
      
 6 
     | 
    
         
            +
                validate_enum_inclusion(value, for_input)
         
     | 
| 
      
 7 
     | 
    
         
            +
                value
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              private
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def validate_null_and_ok?(value, for_input)
         
     | 
| 
      
 13 
     | 
    
         
            +
                return if null || !value.nil?
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                raise for_input ? Taro::InputError : Taro::ResponseError, <<~MSG
         
     | 
| 
      
 16 
     | 
    
         
            +
                  Field #{name} is not nullable (got #{value.inspect})
         
     | 
| 
      
 17 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def validate_enum_inclusion(value, for_input)
         
     | 
| 
      
 21 
     | 
    
         
            +
                return if enum.nil? || enum.include?(value)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                raise for_input ? Taro::InputError : Taro::ResponseError, <<~MSG
         
     | 
| 
      
 24 
     | 
    
         
            +
                  Field #{name} has an invalid value #{value.inspect} (expected one of #{enum.inspect})
         
     | 
| 
      
 25 
     | 
    
         
            +
                MSG
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Abstract base class for input types, i.e. types without response rendering.
         
     | 
| 
      
 2 
     | 
    
         
            +
            class Taro::Types::InputType < Taro::Types::BaseType
         
     | 
| 
      
 3 
     | 
    
         
            +
              require_relative "shared"
         
     | 
| 
      
 4 
     | 
    
         
            +
              extend Taro::Types::Shared::Fields
         
     | 
| 
      
 5 
     | 
    
         
            +
              include Taro::Types::Shared::CustomFieldResolvers
         
     | 
| 
      
 6 
     | 
    
         
            +
              include Taro::Types::Shared::ObjectCoercion
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              self.openapi_type = :object
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def coerce_response
         
     | 
| 
      
 11 
     | 
    
         
            +
                response_error 'InputTypes cannot be used as response types'
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Abstract base class for List types (arrays in OpenAPI terms).
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Unlike other types, this one should not be manually inherited from,
         
     | 
| 
      
 3 
     | 
    
         
            +
            # but is used indirectly via `array_of: SomeType`.
         
     | 
| 
      
 4 
     | 
    
         
            +
            class Taro::Types::ListType < Taro::Types::BaseType
         
     | 
| 
      
 5 
     | 
    
         
            +
              extend Taro::Types::Shared::DerivableType
         
     | 
| 
      
 6 
     | 
    
         
            +
              extend Taro::Types::Shared::ItemType
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              self.openapi_type = :array
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def coerce_input
         
     | 
| 
      
 11 
     | 
    
         
            +
                object.instance_of?(Array) || input_error('must be an Array')
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                item_type = self.class.item_type
         
     | 
| 
      
 14 
     | 
    
         
            +
                object.map { |el| item_type.new(el).coerce_input }
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def coerce_response
         
     | 
| 
      
 18 
     | 
    
         
            +
                object.respond_to?(:map) || response_error('must be an Enumerable')
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                item_type = self.class.item_type
         
     | 
| 
      
 21 
     | 
    
         
            +
                object.map { |el| item_type.new(el).coerce_response }
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            # add shortcut to other types
         
     | 
| 
      
 26 
     | 
    
         
            +
            class Taro::Types::BaseType
         
     | 
| 
      
 27 
     | 
    
         
            +
              def self.array
         
     | 
| 
      
 28 
     | 
    
         
            +
                Taro::Types::ListType.for(self)
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Abstract base class for renderable types with fields.
         
     | 
| 
      
 2 
     | 
    
         
            +
            class Taro::Types::ObjectType < Taro::Types::BaseType
         
     | 
| 
      
 3 
     | 
    
         
            +
              require_relative "shared"
         
     | 
| 
      
 4 
     | 
    
         
            +
              extend Taro::Types::Shared::Fields
         
     | 
| 
      
 5 
     | 
    
         
            +
              include Taro::Types::Shared::CustomFieldResolvers
         
     | 
| 
      
 6 
     | 
    
         
            +
              include Taro::Types::Shared::ObjectCoercion
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              self.openapi_type = :object
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def self.inherited(subclass)
         
     | 
| 
      
 11 
     | 
    
         
            +
                subclass.instance_variable_set(:@response_types, [Hash])
         
     | 
| 
      
 12 
     | 
    
         
            +
                subclass.instance_variable_set(:@input_types, [Hash])
         
     | 
| 
      
 13 
     | 
    
         
            +
                super
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            module Taro::Types::ObjectTypes
         
     | 
| 
      
 18 
     | 
    
         
            +
              Dir[File.join(__dir__, 'object_types', '**', '*.rb')].each { |f| require f }
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Taro::Types::ObjectTypes::FreeFormType < Taro::Types::ObjectType
         
     | 
| 
      
 2 
     | 
    
         
            +
              self.desc = 'An arbitrary, unvalidated Hash or JSON object. Use with care.'
         
     | 
| 
      
 3 
     | 
    
         
            +
              self.additional_properties = true
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              def coerce_input
         
     | 
| 
      
 6 
     | 
    
         
            +
                object.is_a?(Hash) && object || input_error('must be a Hash')
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              def coerce_response
         
     | 
| 
      
 10 
     | 
    
         
            +
                object.respond_to?(:as_json) && (res = object.as_json).is_a?(Hash) && res ||
         
     | 
| 
      
 11 
     | 
    
         
            +
                  response_error('must return a Hash from #as_json')
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     |