Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
ruby = "4.0.7"
6 changes: 4 additions & 2 deletions .rubocop_rbs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ plugins:
Style/RbsInline/MissingTypeAnnotation:
EnforcedStyle: method_type_signature
Exclude:
- 'spec/**/*'
- '**/spec/**/*'
Style/RbsInline/UntypedInstanceVariable:
Exclude:
- 'lib/structured_params/params.rb'
- 'lib/structured_params.rb'
- 'structured_params-openapi/lib/structured_params/openapi/configuration.rb'
- 'structured_params-openapi/lib/structured_params/openapi/registry.rb'

Style/RbsInline/RequireRbsInlineComment:
Exclude:
- 'spec/**/*'
- '**/spec/**/*'
1 change: 0 additions & 1 deletion .ruby-version

This file was deleted.

8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

## 主要ディレクトリ

- `structured_params-openapi/`: OpenAPI drift check の別 gem(`structured_params-openapi`)。
独自の `lib/`・`sig/`・`spec/`・`docs/` と gemspec を持ちます。コアとは同じリポジトリで管理し、
テストは `cd structured_params-openapi && bundle exec rspec` で実行します。

- `lib/structured_params.rb`: エントリーポイント
- `lib/structured_params/params.rb`: コアの `Params` 実装
- `lib/structured_params/type/`: `array` / `object` 型ハンドラ
Expand Down Expand Up @@ -61,9 +65,9 @@ bundle exec rubocop lib/structured_params/params.rb

`lefthook.yml` によりコミット前に以下が走ります。

- `bundle exec rbs-inline --output=sig lib/**/*.rb`
- `bundle exec rbs-inline --output=sig lib/**/*.rb`(`structured_params-openapi/` でも同様)
- `bundle exec rubocop`
- `bundle exec rspec`
- `bundle exec rspec`(`structured_params-openapi/` でも実行)
- `bundle exec steep check`

フックで失敗しない状態まで揃えてから完了扱いにしてください。
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
source 'https://rubygems.org'

gemspec
gemspec path: 'structured_params-openapi'

# Allow testing different Rails versions via environment variable
rails_version = ENV['RAILS_VERSION'] || '~> 8.0.0'
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,26 @@ def create
end
```

### 3. OpenAPI Drift Check (optional gem)

Detecting differences between your OpenAPI schemas and Params classes is provided by a
separate gem, [`structured_params-openapi`](structured_params-openapi/README.md):

```ruby
# Gemfile
gem 'structured_params-openapi'
```

```sh
bundle exec rake structured_params:openapi:drift_check
```

## Documentation

- **[Installation and Setup](docs/installation.md)** - Getting started with StructuredParams
- **[Basic Usage](docs/basic-usage.md)** - Parameter classes, nested objects, and arrays
- **[Validation](docs/validation.md)** - Using ActiveModel validations with nested structures
- **[OpenAPI Drift Check](structured_params-openapi/docs/drift-check.md)** - Detect differences between request schemas and Params classes (`structured_params-openapi` gem)
- **[Strong Parameters](docs/strong-parameters.md)** - Automatic permit list generation
- **[Error Handling](docs/error-handling.md)** - Flat and structured error formats
- **[Serialization](docs/serialization.md)** - Converting parameters to hashes and JSON
Expand Down
15 changes: 15 additions & 0 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,26 @@ def create
end
```

### 3. OpenAPI drift check(別 gem)

OpenAPI の schema と Params クラスの差分検出は、別 gem の
[`structured_params-openapi`](structured_params-openapi/README_ja.md) として提供しています。

```ruby
# Gemfile
gem 'structured_params-openapi'
```

```sh
bundle exec rake structured_params:openapi:drift_check
```

## ドキュメント

- **[インストールとセットアップ](docs/installation.md)** - StructuredParams の始め方
- **[基本的な使い方](docs/basic-usage.md)** - パラメータクラス、ネストオブジェクト、配列
- **[バリデーション](docs/validation.md)** - ネスト構造での ActiveModel バリデーション
- **[OpenAPI Drift Check](structured_params-openapi/docs/drift-check_ja.md)** - OpenAPI schema と Params class の乖離検出(`structured_params-openapi` gem)
- **[Strong Parameters](docs/strong-parameters.md)** - permit リストの自動生成
- **[エラーハンドリング](docs/error-handling.md)** - フラット形式と構造化形式のエラー
- **[シリアライゼーション](docs/serialization.md)** - パラメータのハッシュと JSON 変換
Expand Down
7 changes: 7 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ if RUBY_VERSION >= '3.3.0'
default_tasks << :steep
end

# The OpenAPI plugin lives in its own gem directory with its own spec suite.
desc 'Run the structured_params-openapi specs'
task :spec_openapi do
Dir.chdir('structured_params-openapi') { sh 'bundle exec rspec' }
end
default_tasks << :spec_openapi

task default: default_tasks
10 changes: 10 additions & 0 deletions Steepfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ target :lib do
hash[Steep::Diagnostic::Ruby::LibraryRBSError] = nil
end
end

target :openapi do
signature 'sig', 'structured_params-openapi/sig'

check 'structured_params-openapi/lib'

configure_code_diagnostics do |hash|
hash[Steep::Diagnostic::Ruby::LibraryRBSError] = nil
end
end
4 changes: 3 additions & 1 deletion lefthook.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
prepare-commit-msg:
commands:
rbs-inline:
run: bundle exec rbs-inline --output=sig lib/**/*.rb && git add sig/
run: bundle exec rbs-inline --output=sig lib/**/*.rb && (cd structured_params-openapi && bundle exec rbs-inline --output=sig lib/**/*.rb) && git add sig/ structured_params-openapi/sig/

pre-commit:
commands:
rubocop:
run: ./bin/rubocop
rspec:
run: bundle exec rspec
rspec-open-api:
run: cd structured_params-openapi && bundle exec rspec
steep:
run: bundle exec steep check
4 changes: 2 additions & 2 deletions lib/structured_params/errors.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# rbs_inline: enabled
# frozen_string_literal: true

# rubocop:disable Style/OptionalBooleanParameter
module StructuredParams
# Custom errors collection that handles nested attribute names
class Errors < ActiveModel::Errors
# Override to_hash to maintain compatibility with ActiveModel::Errors by default
# Add structured option to get nested structure for dot-notation attributes
#: (?bool, ?structured: false) -> Hash[Symbol, String]
#: (?bool, structured: bool) -> Hash[Symbol, untyped]
# rubocop:disable-next Style/OptionalBooleanParameter
def to_hash(full_messages = false, structured: false)
if structured
attribute_messages_hash = build_attribute_messages_hash(full_messages)
Expand Down Expand Up @@ -43,6 +43,7 @@ def messages(structured: false)
# Build a hash with attribute names as keys and their error messages as values
# This is used for to_hash(structured: true)
#: (bool) -> Hash[Symbol, Array[String]]
# rubocop:disable-next Style/OptionalBooleanParameter
def build_attribute_messages_hash(full_messages = false)
message_method = full_messages ? :full_message : :message

Expand All @@ -65,4 +66,3 @@ def build_nested_hash(target_hash, flat_hash, separator = '.')
end
end
end
# rubocop:enable Style/OptionalBooleanParameter
3 changes: 1 addition & 2 deletions lib/structured_params/params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module StructuredParams
# <%= f.text_field :name %>
# <%= f.text_field :email %>
# <% end %>
# rubocop:disable Metrics/ClassLength
# rubocop:disable-next Metrics/ClassLength
class Params
include ActiveModel::Model
include ActiveModel::Attributes
Expand Down Expand Up @@ -347,5 +347,4 @@ def import_structured_errors(structured_errors, prefix)
end
end
end
# rubocop:enable Metrics/ClassLength
end
3 changes: 2 additions & 1 deletion sig/structured_params/errors.rbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Generated from lib/structured_params/errors.rb with RBS::Inline

# rubocop:disable Style/OptionalBooleanParameter
module StructuredParams
# Custom errors collection that handles nested attribute names
class Errors < ActiveModel::Errors
# Override to_hash to maintain compatibility with ActiveModel::Errors by default
# Add structured option to get nested structure for dot-notation attributes
# : (?bool, ?structured: false) -> Hash[Symbol, String]
# : (?bool, structured: bool) -> Hash[Symbol, untyped]
# rubocop:disable-next Style/OptionalBooleanParameter
def to_hash: (?bool, ?structured: false) -> Hash[Symbol, String]
| (?bool, structured: bool) -> Hash[Symbol, untyped]

Expand All @@ -26,6 +26,7 @@ module StructuredParams
# Build a hash with attribute names as keys and their error messages as values
# This is used for to_hash(structured: true)
# : (bool) -> Hash[Symbol, Array[String]]
# rubocop:disable-next Style/OptionalBooleanParameter
def build_attribute_messages_hash: (bool) -> Hash[Symbol, Array[String]]

# Build a nested hash structure from flat dot-notation keys
Expand Down
2 changes: 1 addition & 1 deletion sig/structured_params/params.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ module StructuredParams
# <%= f.text_field :name %>
# <%= f.text_field :email %>
# <% end %>
# rubocop:disable Metrics/ClassLength
# rubocop:disable-next Metrics/ClassLength
class Params
include ActiveModel::Model

Expand Down
3 changes: 1 addition & 2 deletions spec/errors_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
errors.add('items.1.subitems.0.description', 'is too long')
end

# rubocop:disable RSpec/ExampleLength
# rubocop:disable-next RSpec/ExampleLength
it 'creates deep nested structure' do
expect(errors.to_hash(false, structured: true)).to eq({
items: {
Expand All @@ -127,7 +127,6 @@
}
})
end
# rubocop:enable RSpec/ExampleLength
end

context 'with mixed flat and nested attributes' do
Expand Down
3 changes: 1 addition & 2 deletions spec/form_object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require 'spec_helper'

# rubocop:disable RSpec/DescribeClass
# rubocop:disable-next RSpec/DescribeClass
RSpec.describe 'StructuredParams::Params as Form Object' do
describe '.form_class?' do
it 'returns true for classes with a Form suffix' do
Expand Down Expand Up @@ -258,4 +258,3 @@
end
end
end
# rubocop:enable RSpec/DescribeClass
12 changes: 4 additions & 8 deletions spec/permit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require 'spec_helper'

# rubocop:disable RSpec/DescribeClass
# rubocop:disable-next RSpec/DescribeClass
RSpec.describe 'StructuredParams::Params.permit' do
describe 'Form Object context (with nested params structure)' do
let(:params) do
Expand Down Expand Up @@ -79,7 +79,7 @@
)
end

# rubocop:disable RSpec/MultipleExpectations
# rubocop:disable-next RSpec/MultipleExpectations
it 'permits array parameters' do
permitted = UserParameter.permit(params)

Expand All @@ -90,7 +90,6 @@
expect(permitted[:hobbies][0][:extra]).to be_nil
expect(permitted[:tags]).to eq(%w[tag1 tag2 tag3])
end
# rubocop:enable RSpec/MultipleExpectations
end

context 'with namespaced form' do
Expand Down Expand Up @@ -150,7 +149,7 @@
)
end

# rubocop:disable RSpec/MultipleExpectations
# rubocop:disable-next RSpec/MultipleExpectations
it 'permits nested object parameters without require' do
permitted = UserParameter.permit(params, require: false)

Expand All @@ -161,7 +160,6 @@
expect(permitted[:address][:extra]).to be_nil
expect(permitted[:extra_field]).to be_nil
end
# rubocop:enable RSpec/MultipleExpectations
end

context 'with arrays' do
Expand All @@ -179,7 +177,7 @@
)
end

# rubocop:disable RSpec/MultipleExpectations
# rubocop:disable-next RSpec/MultipleExpectations
it 'permits array parameters without require' do
permitted = UserParameter.permit(params, require: false)

Expand All @@ -191,7 +189,6 @@
expect(permitted[:tags]).to eq(%w[tag1 tag2 tag3])
expect(permitted[:extra_field]).to be_nil
end
# rubocop:enable RSpec/MultipleExpectations
end

context 'when user manually extracts nested params' do
Expand All @@ -217,4 +214,3 @@
end
end
end
# rubocop:enable RSpec/DescribeClass
2 changes: 2 additions & 0 deletions structured_params-openapi/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--color
--require spec_helper
21 changes: 21 additions & 0 deletions structured_params-openapi/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2024 Mizuki Yamamoto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
34 changes: 34 additions & 0 deletions structured_params-openapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# structured_params-openapi

English | [日本語](README_ja.md)

OpenAPI drift check for [StructuredParams](https://github.com/Syati/structured_params).
It detects differences between OpenAPI request schemas and `StructuredParams::Params`
classes, so a change on one side cannot silently diverge from the other.

## Installation

```ruby
# Gemfile
gem 'structured_params-openapi'
```

Bundler requires it automatically (`structured_params/openapi`). In a Rails application
the Rake task `structured_params:openapi:drift_check` is registered for you.

## Usage

```ruby
# config/initializers/structured_params_openapi.rb
StructuredParams::OpenAPI.configure do |config|
config.document = Rails.root.join('docs/api/openapi.yaml').to_s
config.parameter_class_filter = ->(klass) { klass.name&.start_with?('Api::') }
end
```

```sh
bundle exec rake structured_params:openapi:drift_check
```

See the [guide](docs/drift-check.md) for naming conventions, multiple OpenAPI files,
ignoring findings, and the supported schema features.
Loading
Loading