Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@ Style/FrozenStringLiteralComment:
Enabled: true
EnforcedStyle: always_true
SafeAutoCorrect: true

Style/RequireOrder:
Enabled: true
SafeAutoCorrect: true

Style/AccessModifierDeclarations:
Enabled: true
EnforcedStyle: inline

Style/RedundantReturn:
Enabled: false
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ group :lint do
gem 'rubocop'
gem 'steep'
end

group :rbs do
gem 'orthoses'
end
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -214,6 +216,7 @@ PLATFORMS
DEPENDENCIES
debug
openapi_rails_typed_parameters!
orthoses
rake
rspec
rspec-rails
Expand Down
81 changes: 79 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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

Expand All @@ -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
Expand Down
44 changes: 44 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 21 additions & 0 deletions Steepfile
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions bin/rdbg
Original file line number Diff line number Diff line change
@@ -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")
27 changes: 27 additions & 0 deletions bin/rspec
Original file line number Diff line number Diff line change
@@ -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")
21 changes: 21 additions & 0 deletions lib/generators/openapi_rails_typed_parameters/install_generator.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions lib/openapi_rails_typed_parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
54 changes: 54 additions & 0 deletions lib/openapi_rails_typed_parameters/rake_task.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'optparse'
require 'rails'
require 'rake'
require 'rake/tasklib'

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)

define_generate_task
end

def define_generate_task
desc 'Generate RBS files for given OpenAPI schema'
task("#{name}:generate": :environment) do
require 'openapi_rails_typed_parameters'
type_generator = OpenapiRailsTypedParameters::TypeGenerator.new
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
Loading