From e95cfee0a6fa4ad0567eaa576ca5089f0a011dca Mon Sep 17 00:00:00 2001 From: supermomonga Date: Mon, 8 Jan 2024 17:08:58 +0900 Subject: [PATCH 01/16] skeleton task --- .../install_generator.rb | 21 +++++++++ .../rake_task.rb | 46 +++++++++++++++++++ spec/sample_app/Rakefile | 4 ++ spec/sample_app/lib/tasks/task.rake | 4 ++ 4 files changed, 75 insertions(+) create mode 100644 lib/generators/openapi_rails_typed_parameters/install_generator.rb create mode 100644 lib/openapi_rails_typed_parameters/rake_task.rb create mode 100644 spec/sample_app/Rakefile create mode 100644 spec/sample_app/lib/tasks/task.rake diff --git a/lib/generators/openapi_rails_typed_parameters/install_generator.rb b/lib/generators/openapi_rails_typed_parameters/install_generator.rb new file mode 100644 index 0000000..0882c92 --- /dev/null +++ b/lib/generators/openapi_rails_typed_parameters/install_generator.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails' + +module OpenapiRailsTypedParameters + class InstallGenerator < Rails::Generators::Base + create_file 'lib/tasks/openapi_rails_typed_parameters.rake', <<~RUBY + begin + require 'openapi_rails_typed_parameters/rake_task' + + OpenapiRailsTypedParameters::RakeTask.new do |task| + # Base path for RBS generation. + # default: Rails.root / 'sig/openapi_rails_typed_parameters' + # task.sig_root_dir = Rails.root / 'sig/openapi_rails_typed_parameters' + end + rescue LoadError + # failed to load openapi_rails_typed_parameters. Skip to load openapi_rails_typed_parameters tasks. + end + RUBY + end +end diff --git a/lib/openapi_rails_typed_parameters/rake_task.rb b/lib/openapi_rails_typed_parameters/rake_task.rb new file mode 100644 index 0000000..e9a12a8 --- /dev/null +++ b/lib/openapi_rails_typed_parameters/rake_task.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rake' +require 'rake/tasklib' +require 'rails' + +module OpenapiRailsTypedParameters + class RakeTask < Rake::TaskLib + attr_accessor :name + attr_writer :sig_root_dir + + def initialize(name: :openapi_rails_typed_parameters, &block) + super() + + @name = name + @sig_root_dir = Rails.root / 'sig/openapi_rails_typed_parameters' + + block&.call(self) + + def_generate + end + + def def_generate + desc 'Generate RBS files for given OpenAPI schema' + task("#{name}:generate": :environment) do + require 'openapi_rails_typed_parameters' + + Rails.application.eager_load! + + config = OpenapiRailsTypedParameters.configuration + validator = OpenapiFirst.load(config.schema_path) + validator.operations.each do |operation| + path = Rails.application.routes.recognize_path(operation.path, method: operation.method) + controller_name = "#{path[:controller]}_controller".camelize + action_name = path[:action] + rbs = <<~RBS + class #{controller_name} + def self.typed_params_for: (:#{action_name}) -> nil + end + RBS + puts rbs + end + end + end + end +end diff --git a/spec/sample_app/Rakefile b/spec/sample_app/Rakefile new file mode 100644 index 0000000..9dc8d07 --- /dev/null +++ b/spec/sample_app/Rakefile @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require_relative 'app' +Rails.application.load_tasks diff --git a/spec/sample_app/lib/tasks/task.rake b/spec/sample_app/lib/tasks/task.rake new file mode 100644 index 0000000..f50fa39 --- /dev/null +++ b/spec/sample_app/lib/tasks/task.rake @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require 'openapi_rails_typed_parameters/rake_task' +OpenapiRailsTypedParameters::RakeTask.new From 79eac3aefc5302e02ebf87ea4e721777e928c5d8 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sat, 27 Jan 2024 04:44:08 +0900 Subject: [PATCH 02/16] WIP: RBS generation --- lib/openapi_rails_typed_parameters.rb | 1 + .../rake_task.rb | 22 +--- .../type_generator.rb | 118 ++++++++++++++++++ spec/openapi_rails_typed_parameters_spec.rb | 18 +++ spec/sample_app/app.rb | 8 +- spec/sample_app/schema.yml | 41 ++++++ spec/type_generator_spec.rb | 21 ++++ 7 files changed, 210 insertions(+), 19 deletions(-) create mode 100644 lib/openapi_rails_typed_parameters/type_generator.rb create mode 100644 spec/type_generator_spec.rb diff --git a/lib/openapi_rails_typed_parameters.rb b/lib/openapi_rails_typed_parameters.rb index 7dbfb0c..7a1707b 100644 --- a/lib/openapi_rails_typed_parameters.rb +++ b/lib/openapi_rails_typed_parameters.rb @@ -4,4 +4,5 @@ require_relative 'openapi_rails_typed_parameters/configuration' require_relative 'openapi_rails_typed_parameters/handler' require_relative 'openapi_rails_typed_parameters/railtie' +require_relative 'openapi_rails_typed_parameters/type_generator' require_relative 'openapi_rails_typed_parameters/version' diff --git a/lib/openapi_rails_typed_parameters/rake_task.rb b/lib/openapi_rails_typed_parameters/rake_task.rb index e9a12a8..449b8d9 100644 --- a/lib/openapi_rails_typed_parameters/rake_task.rb +++ b/lib/openapi_rails_typed_parameters/rake_task.rb @@ -17,29 +17,15 @@ def initialize(name: :openapi_rails_typed_parameters, &block) block&.call(self) - def_generate + define_generate_task end - def def_generate + def define_generate_task desc 'Generate RBS files for given OpenAPI schema' task("#{name}:generate": :environment) do require 'openapi_rails_typed_parameters' - - Rails.application.eager_load! - - config = OpenapiRailsTypedParameters.configuration - validator = OpenapiFirst.load(config.schema_path) - validator.operations.each do |operation| - path = Rails.application.routes.recognize_path(operation.path, method: operation.method) - controller_name = "#{path[:controller]}_controller".camelize - action_name = path[:action] - rbs = <<~RBS - class #{controller_name} - def self.typed_params_for: (:#{action_name}) -> nil - end - RBS - puts rbs - end + type_generator = OpenapiRailsTypedParameters::TypeGenerator.new + type_generator.generate_rbs(rbs_file_path: '') end end end diff --git a/lib/openapi_rails_typed_parameters/type_generator.rb b/lib/openapi_rails_typed_parameters/type_generator.rb new file mode 100644 index 0000000..a443507 --- /dev/null +++ b/lib/openapi_rails_typed_parameters/type_generator.rb @@ -0,0 +1,118 @@ + +module OpenapiRailsTypedParameters + class TypeGenerator + def generate_rbs(rbs_file_path:) + + config = OpenapiRailsTypedParameters.configuration + validator = OpenapiFirst.load(config.schema_path) + + return <<~RBS + #{parameter_definitions(validator:)} + + #{controller_definitions(validator:)} + RBS + end + + private + + def operation_to_type_name(operation:) + # TODO: sequence number for duped name + verb = operation.method + path = + operation. + path. + scan(/[\w_]+/). + join('_') + return "#{verb}_#{path}_params" + end + + def parameter_definitions(validator:) + lines = [] + + operations = validator.operations + operations.each do |operation| + type_name = operation_to_type_name(operation:) + + path_params_rbs = + operation. + query_parameters&. + parameters. + then { _1 || []}. + map { |param| + type = param.schema['type'] + optional = param.required? ? '' : '?' + "#{param.name}: #{type}#{optional}" + }. + join(",\n") + + query_params_rbs = + operation. + query_parameters&. + parameters. + then { _1 || []}. + map { |param| + type = param.schema['type'].camelize + optional = param.required? ? '' : '?' + "#{param.name}: #{type}#{optional}" + }. + join(",\n") + + lines << <<~RBS + type #{type_name} = { + path_params: { + #{path_params_rbs} + }, + query_params: { + #{query_params_rbs} + }, + body: { + }, + valid: bool + } + RBS + end + + return lines.join("\n") + end + + def controller_definitions(validator:) + Rails.application.eager_load! + route_inspector = ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes) + journy_routes = route_inspector.instance_variable_get(:@routes) + + # @type var params_definitions: Hash[String, Hash[String, untyped]] + params_definitions = {} + validator.operations.each do |operation| + puts "Find: #{operation.method} #{operation.path}" + path = journy_routes.find { |route| + route.path.match?(operation.path) && route.verb.downcase.to_sym == operation.method.to_sym + } + + # path not found + next unless path + + controller_name = "#{path.defaults[:controller]}_controller".camelize + action_name = path.defaults[:action] + + params_definitions[controller_name] ||= {} + params_definitions[controller_name][action_name] = operation + + end + + lines = [] + params_definitions.map do |controller_name, action_definitions| + lines << "class #{controller_name}" + action_definitions.each.with_index do |(action_name, operation), i| + type_name = operation_to_type_name(operation:) + if i == 0 + lines << " def self.typed_params_for: (:#{action_name}) -> #{type_name}" + else + lines << " | (:#{action_name}) -> #{type_name}" + end + end + lines << "end" + end + return lines.join("\n") + end + end +end diff --git a/spec/openapi_rails_typed_parameters_spec.rb b/spec/openapi_rails_typed_parameters_spec.rb index 4d1d0f5..ac7910d 100644 --- a/spec/openapi_rails_typed_parameters_spec.rb +++ b/spec/openapi_rails_typed_parameters_spec.rb @@ -44,4 +44,22 @@ end end end + + describe 'Path parameters'do + context 'with valid query' do + it 'returns response' do + get '/users/123' + expected = { + path_params: { + id: 123 + }, + query_params: {}, + body: nil, + valid: true + } + actual = JSON.parse(response.body, symbolize_names: true) + expect(actual).to eq expected + end + end + end end diff --git a/spec/sample_app/app.rb b/spec/sample_app/app.rb index e01f1ed..4bcf9ef 100644 --- a/spec/sample_app/app.rb +++ b/spec/sample_app/app.rb @@ -33,7 +33,13 @@ def index end def show - render json: {} + typed_params = typed_params_for(:show) + typed_params.validate! + render json: typed_params.to_h + rescue OpenapiFirst::RequestInvalidError => e + render json: { + message: e.message + }, status: :bad_request end def create diff --git a/spec/sample_app/schema.yml b/spec/sample_app/schema.yml index f038e9a..0582556 100644 --- a/spec/sample_app/schema.yml +++ b/spec/sample_app/schema.yml @@ -24,3 +24,44 @@ paths: required: false schema: type: integer + post: + requestBody: + content: + application/json: + schema: + type: object + properties: + - name: role + required: true + schema: + type: string + enum: [ admin, maintainer ] + /users/{user_id}: + get: + parameters: + - name: id + in: path + required: true + schema: + type: integer + /users/{user_id}/articles: + get: + parameters: + - name: user_id + in: path + required: true + schema: + type: integer + /users/{user_id}/articles/{article_id}: + get: + parameters: + - name: user_id + in: path + required: true + schema: + type: integer + - name: article_id + in: path + required: true + schema: + type: integer diff --git a/spec/type_generator_spec.rb b/spec/type_generator_spec.rb new file mode 100644 index 0000000..7bf9637 --- /dev/null +++ b/spec/type_generator_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe OpenapiRailsTypedParameters::TypeGenerator do + describe 'generate_rbs' do + context 'a' do + it 'generates correct RBS file' do + type_generator = OpenapiRailsTypedParameters::TypeGenerator.new + actual = type_generator.generate_rbs(rbs_file_path: '') + expected = <<~RBS + class UsersController + def self.typed_params_for: (:index) -> nil + end + RBS + + expect(actual).to eq(expected) + end + end + end +end From df40b8023a5c47d435831ef8200db8b41406042d Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sat, 27 Jan 2024 17:07:07 +0900 Subject: [PATCH 03/16] rake task --- .../rake_task.rb | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/openapi_rails_typed_parameters/rake_task.rb b/lib/openapi_rails_typed_parameters/rake_task.rb index 449b8d9..47bebb2 100644 --- a/lib/openapi_rails_typed_parameters/rake_task.rb +++ b/lib/openapi_rails_typed_parameters/rake_task.rb @@ -25,8 +25,31 @@ def define_generate_task task("#{name}:generate": :environment) do require 'openapi_rails_typed_parameters' type_generator = OpenapiRailsTypedParameters::TypeGenerator.new - type_generator.generate_rbs(rbs_file_path: '') + rbs = type_generator.generate_rbs + file_path = File.join(@sig_root_dir, 'action_controller.rbs') + + options = parse_options(argv: ARGV) + + if File.exist?(file_path) && options[:force] == false + abort "RBS file '#{file_path}' already exists. use `--force` option to overwrite." + else + File.write(file_path, rbs) + end end end + + private + + def parse_options(argv:) + options = {} + + option_parser = OptionParser.new + option_parser.banner = 'Usage: openapi_rails_typed_parameters:generate [options]' + option_parser.on('-f', '--force', FalseClass, 'Force overwrite RBS file if it\'s already exists.') + args = option_parser.order(argv) + option_parser.parse(args, into: options) + + options + end end end From 5d5d4123b29dc3fae5ce82a8ec85e73f5e8dfbed Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sat, 27 Jan 2024 17:07:37 +0900 Subject: [PATCH 04/16] format --- .../type_generator.rb | 65 +++++++++---------- spec/openapi_rails_typed_parameters_spec.rb | 2 +- spec/type_generator_spec.rb | 2 +- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/lib/openapi_rails_typed_parameters/type_generator.rb b/lib/openapi_rails_typed_parameters/type_generator.rb index a443507..4d75df5 100644 --- a/lib/openapi_rails_typed_parameters/type_generator.rb +++ b/lib/openapi_rails_typed_parameters/type_generator.rb @@ -1,12 +1,12 @@ +# frozen_string_literal: true module OpenapiRailsTypedParameters class TypeGenerator - def generate_rbs(rbs_file_path:) - + def generate_rbs config = OpenapiRailsTypedParameters.configuration validator = OpenapiFirst.load(config.schema_path) - return <<~RBS + <<~RBS #{parameter_definitions(validator:)} #{controller_definitions(validator:)} @@ -19,11 +19,11 @@ def operation_to_type_name(operation:) # TODO: sequence number for duped name verb = operation.method path = - operation. - path. - scan(/[\w_]+/). - join('_') - return "#{verb}_#{path}_params" + operation + .path + .scan(/[\w_]+/) + .join('_') + "#{verb}_#{path}_params" end def parameter_definitions(validator:) @@ -34,28 +34,28 @@ def parameter_definitions(validator:) type_name = operation_to_type_name(operation:) path_params_rbs = - operation. - query_parameters&. - parameters. - then { _1 || []}. - map { |param| + operation + .query_parameters + &.parameters + .then { _1 || [] } + .map do |param| type = param.schema['type'] optional = param.required? ? '' : '?' "#{param.name}: #{type}#{optional}" - }. - join(",\n") + end + .join(",\n") query_params_rbs = - operation. - query_parameters&. - parameters. - then { _1 || []}. - map { |param| + operation + .query_parameters + &.parameters + .then { _1 || [] } + .map do |param| type = param.schema['type'].camelize optional = param.required? ? '' : '?' "#{param.name}: #{type}#{optional}" - }. - join(",\n") + end + .join(",\n") lines << <<~RBS type #{type_name} = { @@ -72,7 +72,7 @@ def parameter_definitions(validator:) RBS end - return lines.join("\n") + lines.join("\n") end def controller_definitions(validator:) @@ -84,9 +84,9 @@ def controller_definitions(validator:) params_definitions = {} validator.operations.each do |operation| puts "Find: #{operation.method} #{operation.path}" - path = journy_routes.find { |route| + path = journy_routes.find do |route| route.path.match?(operation.path) && route.verb.downcase.to_sym == operation.method.to_sym - } + end # path not found next unless path @@ -96,7 +96,6 @@ def controller_definitions(validator:) params_definitions[controller_name] ||= {} params_definitions[controller_name][action_name] = operation - end lines = [] @@ -104,15 +103,15 @@ def controller_definitions(validator:) lines << "class #{controller_name}" action_definitions.each.with_index do |(action_name, operation), i| type_name = operation_to_type_name(operation:) - if i == 0 - lines << " def self.typed_params_for: (:#{action_name}) -> #{type_name}" - else - lines << " | (:#{action_name}) -> #{type_name}" - end + lines << if i.zero? + " def self.typed_params_for: (:#{action_name}) -> #{type_name}" + else + " | (:#{action_name}) -> #{type_name}" + end end - lines << "end" + lines << 'end' end - return lines.join("\n") + lines.join("\n") end end end diff --git a/spec/openapi_rails_typed_parameters_spec.rb b/spec/openapi_rails_typed_parameters_spec.rb index ac7910d..4e06998 100644 --- a/spec/openapi_rails_typed_parameters_spec.rb +++ b/spec/openapi_rails_typed_parameters_spec.rb @@ -45,7 +45,7 @@ end end - describe 'Path parameters'do + describe 'Path parameters' do context 'with valid query' do it 'returns response' do get '/users/123' diff --git a/spec/type_generator_spec.rb b/spec/type_generator_spec.rb index 7bf9637..643aaab 100644 --- a/spec/type_generator_spec.rb +++ b/spec/type_generator_spec.rb @@ -7,7 +7,7 @@ context 'a' do it 'generates correct RBS file' do type_generator = OpenapiRailsTypedParameters::TypeGenerator.new - actual = type_generator.generate_rbs(rbs_file_path: '') + actual = type_generator.generate_rbs expected = <<~RBS class UsersController def self.typed_params_for: (:index) -> nil From ed69e5cd6bb94a5cdf2a2067a241f0ae3b625bb0 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sat, 27 Jan 2024 17:12:28 +0900 Subject: [PATCH 05/16] sort require order --- .rubocop.yml | 4 ++++ lib/openapi_rails_typed_parameters/rake_task.rb | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 962e0df..eea3727 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,3 +39,7 @@ Style/FrozenStringLiteralComment: Enabled: true EnforcedStyle: always_true SafeAutoCorrect: true + +Style/RequireOrder: + Enabled: true + SafeAutoCorrect: true diff --git a/lib/openapi_rails_typed_parameters/rake_task.rb b/lib/openapi_rails_typed_parameters/rake_task.rb index 47bebb2..caa82cb 100644 --- a/lib/openapi_rails_typed_parameters/rake_task.rb +++ b/lib/openapi_rails_typed_parameters/rake_task.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true +require 'optparse' +require 'rails' require 'rake' require 'rake/tasklib' -require 'rails' module OpenapiRailsTypedParameters class RakeTask < Rake::TaskLib From bdc179ca68c46bf601f3e1fd2a080ca150d59311 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sat, 27 Jan 2024 21:55:52 +0900 Subject: [PATCH 06/16] test formatted RBSs --- .../type_generator.rb | 90 +++++++++++-------- spec/type_generator_spec.rb | 34 +++++-- 2 files changed, 82 insertions(+), 42 deletions(-) diff --git a/lib/openapi_rails_typed_parameters/type_generator.rb b/lib/openapi_rails_typed_parameters/type_generator.rb index 4d75df5..48f5b86 100644 --- a/lib/openapi_rails_typed_parameters/type_generator.rb +++ b/lib/openapi_rails_typed_parameters/type_generator.rb @@ -1,33 +1,38 @@ # frozen_string_literal: true +require 'rbs' + module OpenapiRailsTypedParameters class TypeGenerator - def generate_rbs - config = OpenapiRailsTypedParameters.configuration - validator = OpenapiFirst.load(config.schema_path) + private attr_accessor :config + private attr_accessor :validator - <<~RBS - #{parameter_definitions(validator:)} + def initialize + @config = OpenapiRailsTypedParameters.configuration + @validator = OpenapiFirst.load(@config.schema_path) + end - #{controller_definitions(validator:)} + def generate_rbs + rbs = <<~RBS + #{generate_parameter_definitions} + #{generate_controller_definitions} RBS + return format(rbs) end - private - def operation_to_type_name(operation:) - # TODO: sequence number for duped name + # TODO: Use same naming as Rails path helper verb = operation.method path = operation .path .scan(/[\w_]+/) .join('_') - "#{verb}_#{path}_params" + return "#{verb}_#{path}" end - def parameter_definitions(validator:) - lines = [] + def generate_parameter_definitions + definitions = [] operations = validator.operations operations.each do |operation| @@ -35,11 +40,11 @@ def parameter_definitions(validator:) path_params_rbs = operation - .query_parameters + .path_parameters &.parameters .then { _1 || [] } .map do |param| - type = param.schema['type'] + type = param.schema['type'].camelize optional = param.required? ? '' : '?' "#{param.name}: #{type}#{optional}" end @@ -57,25 +62,22 @@ def parameter_definitions(validator:) end .join(",\n") - lines << <<~RBS + definitions << <<~RBS type #{type_name} = { - path_params: { - #{path_params_rbs} - }, - query_params: { - #{query_params_rbs} - }, - body: { - }, + path_params: { #{path_params_rbs} }, + query_params: { #{query_params_rbs} }, + body: __todo__, valid: bool } RBS end - lines.join("\n") + # return 'type hoge = {hi: Integer}' + rbs = format(definitions.join("\n")) + return rbs end - def controller_definitions(validator:) + def generate_controller_definitions Rails.application.eager_load! route_inspector = ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes) journy_routes = route_inspector.instance_variable_get(:@routes) @@ -99,19 +101,33 @@ def controller_definitions(validator:) end lines = [] - params_definitions.map do |controller_name, action_definitions| - lines << "class #{controller_name}" - action_definitions.each.with_index do |(action_name, operation), i| - type_name = operation_to_type_name(operation:) - lines << if i.zero? - " def self.typed_params_for: (:#{action_name}) -> #{type_name}" - else - " | (:#{action_name}) -> #{type_name}" - end + params_definitions + .sort_by { |controller_name, _| controller_name } + .map do |controller_name, action_definitions| + lines << "class #{controller_name}" + action_definitions + .sort_by { |action_name, _| action_name } + .each.with_index do |(action_name, operation), i| + type_name = operation_to_type_name(operation:) + lines << if i.zero? + " def self.typed_params_for: (:#{action_name}) -> #{type_name}" + else + " | (:#{action_name}) -> #{type_name}" + end + end + lines << 'end' end - lines << 'end' - end - lines.join("\n") + return format(lines.join("\n")) + end + + def format(rbs) + signature = RBS::Parser.parse_signature(rbs) + stream = StringIO.new + writer = RBS::Writer.new(out: stream) + writer.write(signature[1] + signature[2]) + formatted = stream.string + stream.close + return formatted end end end diff --git a/spec/type_generator_spec.rb b/spec/type_generator_spec.rb index 643aaab..7b7b08f 100644 --- a/spec/type_generator_spec.rb +++ b/spec/type_generator_spec.rb @@ -4,17 +4,41 @@ RSpec.describe OpenapiRailsTypedParameters::TypeGenerator do describe 'generate_rbs' do - context 'a' do - it 'generates correct RBS file' do + context 'Parameter types' do + it 'generates correct types' do type_generator = OpenapiRailsTypedParameters::TypeGenerator.new - actual = type_generator.generate_rbs + actual = type_generator.generate_parameter_definitions + expected = <<~RBS + type get_users = { + path_params: { + }, + query_params: { + role: String, + minimum: Integer?, + maximum: Integer? + }, + body: __todo__, + valid: bool + } + RBS + + expect(actual).to eq(type_generator.format(expected)) + end + end + + context 'Controller actions' do + it 'generates correct actions' do + type_generator = OpenapiRailsTypedParameters::TypeGenerator.new + actual = type_generator.generate_controller_definitions expected = <<~RBS class UsersController - def self.typed_params_for: (:index) -> nil + def self.typed_params_for: (:create) -> post_users + | (:index) -> get_users + | (:show) -> get_users_user_id end RBS - expect(actual).to eq(expected) + expect(actual).to eq(type_generator.format(expected)) end end end From bc898cf8fc04e9304057155fb7bae2c98225e598 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sat, 27 Jan 2024 21:56:00 +0900 Subject: [PATCH 07/16] rubocop --- .rubocop.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index eea3727..d800eb3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -43,3 +43,10 @@ Style/FrozenStringLiteralComment: Style/RequireOrder: Enabled: true SafeAutoCorrect: true + +Style/AccessModifierDeclarations: + Enabled: true + EnforcedStyle: inline + +Style/RedundantReturn: + Enabled: false From da5cd680fa102418ec5998cc45fb6010ded2d974 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sun, 28 Jan 2024 02:19:26 +0900 Subject: [PATCH 08/16] spec --- spec/openapi_rails_typed_parameters_spec.rb | 2 +- spec/sample_app/app.rb | 1 + spec/spec_helper.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/openapi_rails_typed_parameters_spec.rb b/spec/openapi_rails_typed_parameters_spec.rb index 4e06998..97f0eea 100644 --- a/spec/openapi_rails_typed_parameters_spec.rb +++ b/spec/openapi_rails_typed_parameters_spec.rb @@ -51,7 +51,7 @@ get '/users/123' expected = { path_params: { - id: 123 + user_id: 123 }, query_params: {}, body: nil, diff --git a/spec/sample_app/app.rb b/spec/sample_app/app.rb index 4bcf9ef..741ecf0 100644 --- a/spec/sample_app/app.rb +++ b/spec/sample_app/app.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'action_controller/railtie' +require_relative '../../lib/openapi_rails_typed_parameters' class SampleApp < Rails::Application config.active_support.cache_format_version = 7.0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b8bdf7..ee9b1e5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true require 'openapi_rails_typed_parameters' -require 'sample_app/app' require 'rspec/rails' +require 'sample_app/app' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure From 489e42a76200053c1a244b582091314b59fea2ae Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sun, 28 Jan 2024 02:19:49 +0900 Subject: [PATCH 09/16] setup steep --- Steepfile | 21 +++ rbs_collection.lock.yaml | 280 +++++++++++++++++++++++++++++++++++++++ rbs_collection.yaml | 19 +++ 3 files changed, 320 insertions(+) create mode 100644 Steepfile create mode 100644 rbs_collection.lock.yaml create mode 100644 rbs_collection.yaml diff --git a/Steepfile b/Steepfile new file mode 100644 index 0000000..e9fcc21 --- /dev/null +++ b/Steepfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +D = Steep::Diagnostic + +target :lib do + signature 'sig' + + check 'lib' + check 'Gemfile' + check 'Rakefile' + # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default) + # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting + # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting + # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting +end + +target :spec do + signature 'sig' + + check 'spec' +end diff --git a/rbs_collection.lock.yaml b/rbs_collection.lock.yaml new file mode 100644 index 0000000..924f73a --- /dev/null +++ b/rbs_collection.lock.yaml @@ -0,0 +1,280 @@ +--- +path: ".gem_rbs_collection" +gems: +- name: abbrev + version: '0' + source: + type: stdlib +- name: actionpack + version: '6.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: actionview + version: '6.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: activesupport + version: '7.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: ast + version: '2.4' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: base64 + version: '0' + source: + type: stdlib +- name: bigdecimal + version: '0' + source: + type: stdlib +- name: cgi + version: '0' + source: + type: stdlib +- name: concurrent-ruby + version: '1.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: connection_pool + version: '2.4' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: csv + version: '0' + source: + type: stdlib +- name: date + version: '0' + source: + type: stdlib +- name: erb + version: '0' + source: + type: stdlib +- name: fileutils + version: '0' + source: + type: stdlib +- name: forwardable + version: '0' + source: + type: stdlib +- name: i18n + version: '1.10' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: io-console + version: '0' + source: + type: stdlib +- name: json + version: '0' + source: + type: stdlib +- name: listen + version: '3.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: logger + version: '0' + source: + type: stdlib +- name: minitest + version: '0' + source: + type: stdlib +- name: monitor + version: '0' + source: + type: stdlib +- name: mutex_m + version: '0' + source: + type: stdlib +- name: nokogiri + version: '1.11' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: openapi_parameters + version: 0.3.2 + source: + type: rubygems +- name: optparse + version: '0' + source: + type: stdlib +- name: parallel + version: '1.20' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: parser + version: '3.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: pathname + version: '0' + source: + type: stdlib +- name: rack + version: '2.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rails-dom-testing + version: '2.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: railties + version: '6.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rainbow + version: '3.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rake + version: '13.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rbs + version: 3.4.1 + source: + type: rubygems +- name: rdoc + version: '0' + source: + type: stdlib +- name: regexp_parser + version: '2.8' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rubocop + version: '1.57' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rubocop-ast + version: '1.30' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: securerandom + version: '0' + source: + type: stdlib +- name: singleton + version: '0' + source: + type: stdlib +- name: steep + version: 1.6.0 + source: + type: rubygems +- name: strscan + version: '0' + source: + type: stdlib +- name: tempfile + version: '0' + source: + type: stdlib +- name: thor + version: '1.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 846c09971455f0e144cef2f5a6c9fe6d8905d3e1 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: time + version: '0' + source: + type: stdlib +- name: timeout + version: '0' + source: + type: stdlib +- name: tsort + version: '0' + source: + type: stdlib +- name: uri + version: '0' + source: + type: stdlib +gemfile_lock_path: Gemfile.lock diff --git a/rbs_collection.yaml b/rbs_collection.yaml new file mode 100644 index 0000000..d6f0582 --- /dev/null +++ b/rbs_collection.yaml @@ -0,0 +1,19 @@ +# Download sources +sources: + - type: git + name: ruby/gem_rbs_collection + remote: https://github.com/ruby/gem_rbs_collection.git + revision: main + repo_dir: gems + +# You can specify local directories as sources also. +# - type: local +# path: path/to/your/local/repository + +# A directory to install the downloaded RBSs +path: .gem_rbs_collection + +gems: + - name: rbs + - name: pathname + - name: logger From 4d46c187bef6fe12c086d331778631141244bd34 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sun, 28 Jan 2024 02:19:55 +0900 Subject: [PATCH 10/16] fix --- spec/sample_app/schema.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/sample_app/schema.yml b/spec/sample_app/schema.yml index 0582556..61b4007 100644 --- a/spec/sample_app/schema.yml +++ b/spec/sample_app/schema.yml @@ -39,7 +39,7 @@ paths: /users/{user_id}: get: parameters: - - name: id + - name: user_id in: path required: true schema: From 80fc8899bf3a0a3385af3ea5648dc1878741c581 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sun, 28 Jan 2024 02:20:22 +0900 Subject: [PATCH 11/16] wip --- .../type_generator.rb | 11 +++++-- spec/type_generator_spec.rb | 30 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/openapi_rails_typed_parameters/type_generator.rb b/lib/openapi_rails_typed_parameters/type_generator.rb index 48f5b86..94ec059 100644 --- a/lib/openapi_rails_typed_parameters/type_generator.rb +++ b/lib/openapi_rails_typed_parameters/type_generator.rb @@ -73,7 +73,7 @@ def generate_parameter_definitions end # return 'type hoge = {hi: Integer}' - rbs = format(definitions.join("\n")) + rbs = format(definitions.join) return rbs end @@ -117,7 +117,8 @@ def generate_controller_definitions end lines << 'end' end - return format(lines.join("\n")) + rbs = format(lines.join("\n")) + return rbs end def format(rbs) @@ -125,7 +126,11 @@ def format(rbs) stream = StringIO.new writer = RBS::Writer.new(out: stream) writer.write(signature[1] + signature[2]) - formatted = stream.string + formatted = + stream + .string + # remove multiple newlines + .gsub(/\n{2,}/, "\n") stream.close return formatted end diff --git a/spec/type_generator_spec.rb b/spec/type_generator_spec.rb index 7b7b08f..4a825fd 100644 --- a/spec/type_generator_spec.rb +++ b/spec/type_generator_spec.rb @@ -6,12 +6,14 @@ describe 'generate_rbs' do context 'Parameter types' do it 'generates correct types' do + OpenapiRailsTypedParameters.configure do |_config| + # TODO + end type_generator = OpenapiRailsTypedParameters::TypeGenerator.new actual = type_generator.generate_parameter_definitions expected = <<~RBS type get_users = { - path_params: { - }, + path_params: { }, query_params: { role: String, minimum: Integer?, @@ -20,6 +22,30 @@ body: __todo__, valid: bool } + type post_users = { + path_params: { }, + query_params: { }, + body: __todo__, + valid: bool + } + type get_users_user_id = { + path_params: { user_id: Integer }, + query_params: { }, + body: __todo__, + valid: bool + } + type get_users_user_id_articles = { + path_params: { user_id: Integer }, + query_params: { }, + body: __todo__, + valid: bool + } + type get_users_user_id_articles_article_id = { + path_params: { user_id: Integer, article_id: Integer }, + query_params: { }, + body: __todo__, + valid: bool + } RBS expect(actual).to eq(type_generator.format(expected)) From 402c3e30d2c77ff11d39e5e64965d1519c2edf50 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Sun, 28 Jan 2024 02:21:23 +0900 Subject: [PATCH 12/16] rubocop --- lib/openapi_rails_typed_parameters/rake_task.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/openapi_rails_typed_parameters/rake_task.rb b/lib/openapi_rails_typed_parameters/rake_task.rb index caa82cb..6348b2e 100644 --- a/lib/openapi_rails_typed_parameters/rake_task.rb +++ b/lib/openapi_rails_typed_parameters/rake_task.rb @@ -39,9 +39,7 @@ def define_generate_task end end - private - - def parse_options(argv:) + private def parse_options(argv:) options = {} option_parser = OptionParser.new From 2173dbab1c2fb1b4f336615bf1a1fb266e5fb18b Mon Sep 17 00:00:00 2001 From: supermomonga Date: Mon, 29 Jan 2024 03:52:45 +0900 Subject: [PATCH 13/16] RBS --- rbs_collection.lock.yaml | 4 ++++ sig/configuration.rbs | 5 +++++ sig/handler.rbs | 6 ++++++ sig/install_generator.rbs | 2 ++ sig/openapi_rails_typed_parameters.rbs | 7 ++++++- sig/railtie.rbs | 2 ++ sig/rake_task.rbs | 10 ++++++++++ sig/type_generator.rbs | 12 ++++++++++++ sig/typed_parameters.rbs | 17 +++++++++++++++++ 9 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 sig/configuration.rbs create mode 100644 sig/handler.rbs create mode 100644 sig/install_generator.rbs create mode 100644 sig/railtie.rbs create mode 100644 sig/rake_task.rbs create mode 100644 sig/type_generator.rbs create mode 100644 sig/typed_parameters.rbs diff --git a/rbs_collection.lock.yaml b/rbs_collection.lock.yaml index 924f73a..42fb757 100644 --- a/rbs_collection.lock.yaml +++ b/rbs_collection.lock.yaml @@ -141,6 +141,10 @@ gems: version: '0' source: type: stdlib +- name: orthoses + version: 1.13.0 + source: + type: rubygems - name: parallel version: '1.20' source: diff --git a/sig/configuration.rbs b/sig/configuration.rbs new file mode 100644 index 0000000..bbc3340 --- /dev/null +++ b/sig/configuration.rbs @@ -0,0 +1,5 @@ +class OpenapiRailsTypedParameters::Configuration + @schema_path: String + attr_accessor schema_path: String + private def initialize: () -> void +end diff --git a/sig/handler.rbs b/sig/handler.rbs new file mode 100644 index 0000000..37035b8 --- /dev/null +++ b/sig/handler.rbs @@ -0,0 +1,6 @@ +class OpenapiRailsTypedParameters::Handler + attr_accessor self.configuration: OpenapiRailsTypedParameters::Configuration + attr_accessor self.validator: untyped + def self.build_validator: () -> __todo__ + def self.build_validator_if_needed: () -> __todo__ +end diff --git a/sig/install_generator.rbs b/sig/install_generator.rbs new file mode 100644 index 0000000..725d6b1 --- /dev/null +++ b/sig/install_generator.rbs @@ -0,0 +1,2 @@ +class OpenapiRailsTypedParameters::InstallGenerator < ::Rails::Generators::Base +end diff --git a/sig/openapi_rails_typed_parameters.rbs b/sig/openapi_rails_typed_parameters.rbs index a55d3d1..22bde21 100644 --- a/sig/openapi_rails_typed_parameters.rbs +++ b/sig/openapi_rails_typed_parameters.rbs @@ -1,4 +1,9 @@ module OpenapiRailsTypedParameters + @@configuration: untyped + + def self.configuration: () -> OpenapiRailsTypedParameters::Configuration + + def self.configure: () -> nil + VERSION: String - # See the writing guide of rbs: https://github.com/ruby/rbs#guides end diff --git a/sig/railtie.rbs b/sig/railtie.rbs new file mode 100644 index 0000000..73e6a9e --- /dev/null +++ b/sig/railtie.rbs @@ -0,0 +1,2 @@ +class OpenapiRailsTypedParameters::Railtie < ::Rails::Railtie +end diff --git a/sig/rake_task.rbs b/sig/rake_task.rbs new file mode 100644 index 0000000..bb2f7e3 --- /dev/null +++ b/sig/rake_task.rbs @@ -0,0 +1,10 @@ +class OpenapiRailsTypedParameters::RakeTask < ::Rake::TaskLib + @name: String + @sig_root_dir: String + attr_accessor name: String + attr_writer sig_root_dir: String + + def initialize: (?name: Symbol) ?{ () -> untyped } -> void + def define_generate_task: () -> void + private def parse_options: (argv: Array[String]) -> { force: bool } +end diff --git a/sig/type_generator.rbs b/sig/type_generator.rbs new file mode 100644 index 0000000..205d3ba --- /dev/null +++ b/sig/type_generator.rbs @@ -0,0 +1,12 @@ +class OpenapiRailsTypedParameters::TypeGenerator + @config: OpenapiRailsTypedParameters::Configuration + @validator: __todo__ + private attr_accessor config: OpenapiRailsTypedParameters::Configuration + private attr_accessor validator: __todo__ + private def initialize: () -> void + def generate_rbs: () -> String + def operation_to_type_name: (operation: __todo__) -> String + def generate_parameter_definitions: () -> String + def generate_controller_definitions: () -> String + def format: (String rbs) -> String +end diff --git a/sig/typed_parameters.rbs b/sig/typed_parameters.rbs new file mode 100644 index 0000000..3d6ebc4 --- /dev/null +++ b/sig/typed_parameters.rbs @@ -0,0 +1,17 @@ + + +class OpenapiRailsTypedParameters::TypedParameters + type parameter_value = String | Integer | Symbol | bool + type parameters = Hash[Symbol, parameter_value?] + + @request: __todo__ + attr_reader request: __todo__ + private def initialize: (request: __todo__) -> void + def path_params: () -> parameters + def query_params: () -> parameters + def valid?: () -> bool + def to_h: () -> { path_params: parameters, query_params: parameters, body: String?, valid: bool } + def validate!: () -> void + def body: () -> String + def validate: () -> __todo__ +end From 3c4ec53e0f22ad6a5657d392f1f2829d8a167dcb Mon Sep 17 00:00:00 2001 From: supermomonga Date: Mon, 29 Jan 2024 03:53:19 +0900 Subject: [PATCH 14/16] rbs prototype using orthoses --- Gemfile | 4 ++++ Gemfile.lock | 3 +++ Rakefile | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/Gemfile b/Gemfile index 316b434..3cacbd5 100644 --- a/Gemfile +++ b/Gemfile @@ -17,3 +17,7 @@ group :lint do gem 'rubocop' gem 'steep' end + +group :rbs do + gem 'orthoses' +end diff --git a/Gemfile.lock b/Gemfile.lock index 9e06cb2..b68e54e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,6 +97,8 @@ GEM openapi_parameters (0.3.2) rack (>= 2.2) zeitwerk (~> 2.6) + orthoses (1.13.0) + rbs (~> 3.0) parallel (1.24.0) parser (3.3.0.0) ast (~> 2.4.1) @@ -214,6 +216,7 @@ PLATFORMS DEPENDENCIES debug openapi_rails_typed_parameters! + orthoses rake rspec rspec-rails diff --git a/Rakefile b/Rakefile index 4964751..92a88c0 100644 --- a/Rakefile +++ b/Rakefile @@ -10,3 +10,47 @@ require 'rubocop/rake_task' RuboCop::RakeTask.new task default: %i[spec rubocop] + +desc 'Generate RBS' +task :generate_rbs do + require 'active_support/testing/stream' + require 'orthoses' + require_relative 'lib/openapi_rails_typed_parameters' + + namespace = OpenapiRailsTypedParameters.to_s + + out = Pathname(__FILE__).dirname / 'sig/generated' + + begin + out.rmtree + rescue StandardError + nil + end + + Orthoses.logger.level = :warn + Orthoses::Builder.new do + use Orthoses::CreateFileByName, + to: 'sig/generated', + rmtree: true, + header: '# Generated code' + use Orthoses::Filter do |name, _content| + name.start_with?(namespace) + end + use Orthoses::Mixin + use Orthoses::Constant + use Orthoses::Trace, + patterns: [namespace, "#{namespace}::*"] + use Orthoses::RBSPrototypeRB, + paths: Dir.glob('lib/**/*.rb') + run lambda { + _ = OpenapiRailsTypedParameters::VERSION + Class.new.extend(ActiveSupport::Testing::Stream).instance_exec do + silence_stream($stdout) do + require 'rspec' + spec_files = Dir.glob('spec/**/*_spec.rb') + RSpec::Core::Runner.run(spec_files) + end + end + } + end.call +end From 525ed59c507a36936bb55a221e70ae2f5914f539 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Mon, 29 Jan 2024 03:53:27 +0900 Subject: [PATCH 15/16] binstubs --- bin/rdbg | 27 +++++++++++++++++++++++++++ bin/rspec | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100755 bin/rdbg create mode 100755 bin/rspec diff --git a/bin/rdbg b/bin/rdbg new file mode 100755 index 0000000..5e3b279 --- /dev/null +++ b/bin/rdbg @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rdbg' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("debug", "rdbg") diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 0000000..cb53ebe --- /dev/null +++ b/bin/rspec @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rspec-core", "rspec") From 2b21cdbc84b23197b18e5394e5b49b4879604aa6 Mon Sep 17 00:00:00 2001 From: supermomonga Date: Mon, 29 Jan 2024 04:32:54 +0900 Subject: [PATCH 16/16] readme --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dbc7d32..e333233 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,62 @@ Add `openapi_rails_typed_parameters` to your Gemfile. gem 'openapi_rails_typed_parameters' ``` +Then, install rake task by generator. + +```sh +bin/rails g openapi_rails_typed_parameters:install +``` + +## Usage + +Run `openapi_rails_typed_parameters:generate` rake task. + +```sh +bin/rails openapi_rails_typed_parameters:generate +``` + +If you update your OpenAPI definition, then generate again with `--force` option. It overwrites RBSs. + +```sh +bin/rails openapi_rails_typed_parameters:generate --force +``` + ## Usage +Add `using OpenapiRailsTypedParameters` to your controller class. You can access statically typed parameters via `typed_params` method. + +```rb +class UsersController < ApplicationController + using OpenapiRailsTypedParameters + + def index + # BEFORE: Default Rails code. + params.permit(:role) + role_string = params[:role] + role = + if role_string.present? + if ['admin', 'maintainer', 'member'].include?(role_string) + role_string.to_sym + else + raise 'Unknown `role` passed. available values are: [admin, maintainer, member].' + end + else + :member # fallback to default + end + + # AFTER: Typed parameters way. + # role is validated, and it's type coerced. + role = typed_params.query_params.role # :admin, :maintainer or :member + + @users = User.where(role:) + render :index + end +end + +``` + +## RBS generation, static typing + Please add an initializer to your Rails application and specify the path to the OpenAPI schema file. e.g.) `config/initializers/openapi.rb` @@ -50,7 +104,29 @@ OpenapiRailsTypedParameters.configure do |config| end ``` -Then, add `using OpenapiRailsTypedParameters` to your controller class. You can access statically typed parameters via `typed_parameters` method. +If you want to customize generator behavior, edit `lib/tasks/openapi_rails_typed_parameters.rake`. + +Then, you can use statically typed parameters. Please use `typed_params_for(:action_name)` instead of `typed_params`. + +Enjoy statically typed params with your favorite LSP server. + +```rb +class UsersController < ApplicationController + using OpenapiRailsTypedParameters + + def index + # BEFORE: RBS not injected. + _ = typed_params + + # AFTER: RBS injected. + _ = typed_params_for(:index) + + role = typed_params_for(:index).query_params.role + @users = User.where(role:) + render :index + end +end +``` ## Example @@ -70,7 +146,8 @@ paths: required: true schema: type: string - enum: [ admin, maintainer ] + enum: [ admin, maintainer, member ] + default: member - name: minimum in: query required: false