Work in Progress - This gem is under active development and currently used in production with Grape only.
If you use dry-validation extensively, you know the pain of duplicating field definitions between your contracts and your API params. This gem eliminates that repetition by automatically generating Grape params (or Rails strong params) directly from your Dry::Validation contracts.
gem 'dry_params'class UserContract < Dry::Validation::Contract
params do
required(:name).filled(:string)
required(:age).filled(:integer)
optional(:email).maybe(:string)
end
end
DryParams.from(UserContract)
# => {
# name: { type: String, desc: "Name", required: true, documentation: { param_type: "body" } },
# age: { type: Integer, desc: "Age", required: true, documentation: { param_type: "body" } },
# email: { type: String, desc: "Email", required: false, documentation: { param_type: "body" } }
# }Use comment annotations to provide custom descriptions for your params:
class AppointmentCreateContract < Dry::Validation::Contract
params do
# @title = Title of the appointment
required(:title).filled(:string)
# @start_time = Start time in ISO8601 format
required(:start_time).filled(:time)
# @duration_minutes = Duration in minutes (default: 60)
optional(:duration_minutes).filled(:integer)
end
end
DryParams.from(AppointmentCreateContract)
# => {
# title: { type: String, desc: "Title of the appointment", required: true, ... },
# start_time: { type: Time, desc: "Start time in ISO8601 format", required: true, ... },
# duration_minutes: { type: Integer, desc: "Duration in minutes (default: 60)", required: false, ... }
# }The annotation format is # @field_name = Your description here. Without annotations, the field name is humanized (e.g., start_time becomes "Start time").
Usage:
desc "Create user", { params: DryParams.from(UserContract) }For query params:
desc "List users", { params: DryParams.from(UserFilterContract, param_type: 'query') }DryParams.from(UserContract, adapter: :rails)
# => [:name, :age, :email]With arrays and hashes:
class PostContract < Dry::Validation::Contract
params do
required(:title).filled(:string)
optional(:tags).filled(:array)
optional(:metadata).filled(:hash)
end
end
DryParams.from(PostContract, adapter: :rails)
# => [:title, { tags: [], metadata: {} }]Usage:
def post_params
params.permit(DryParams.from(PostContract, adapter: :rails))
endbundle exec bin/console # interactive console
bundle exec rspec # run tests- Grape adapter (production ready)
- Rails adapter (experimental)
- Nested contracts support
- Custom type mappings
MIT