Skip to content

Commit

Permalink
Tidy, update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
timriley committed Aug 28, 2024
1 parent 37dbc3e commit 397bc60
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 55 deletions.
20 changes: 13 additions & 7 deletions lib/hanami/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,28 @@ def self.params_class
@params_class || BaseParams
end

# Placeholder implementation for params class method
#
# Raises a developer friendly error to include `hanami/validations`.
# Placeholder for the `.params` method. Raises an error when the hanami-validations gem is not
# installed.
#
# @raise [NoMethodError]
#
# @api public
# @since 2.0.0
def self.params(_klass = nil)
raise NoMethodError,
"To use `params`, please add 'hanami/validations' gem to your Gemfile"
message = %(To use `.params`, please add the "hanami-validations" gem to your Gemfile)
raise NoMethodError, message
end

# Placeholder for the `.contract` method. Raises an error when the hanami-validations gem is not
# installed.
#
# @raise [NoMethodError]
#
# @api public
# @since 2.2.0
def self.contract
raise NoMethodError,
"To use `contract`, please add 'hanami/validations' gem to your Gemfile"
message = %(To use `.contract`, please add the "hanami-validations" gem to your Gemfile)
raise NoMethodError, message
end

# @overload self.append_before(*callbacks, &block)
Expand Down
37 changes: 13 additions & 24 deletions lib/hanami/action/params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,37 +111,26 @@ def _nested_attribute(keys, key)
end
end

# Define params validations
# Defines validations for the params, using the `params` schema of a dry-validation contract.
#
# @param blk [Proc] the validations definitions
# @param block [Proc] the schema definition
#
# @since 0.7.0
#
# @see https://guides.hanamirb.org/validations/overview
#
# @example
# class Signup < Hanami::Action
# MEGABYTE = 1024 ** 2
#
# params do
# required(:first_name).filled(:str?)
# required(:last_name).filled(:str?)
# required(:email).filled?(:str?, format?: /\A.+@.+\z/)
# required(:password).filled(:str?).confirmation
# required(:terms_of_service).filled(:bool?)
# required(:age).filled(:int?, included_in?: 18..99)
# optional(:avatar).filled(size?: 1..(MEGABYTE * 3))
# end
# @see https://dry-rb.org/gems/dry-validation/
#
# def handle(req, *)
# halt 400 unless req.params.valid?
# # ...
# end
# end
# @api public
# @since 0.7.0
def self.params(&block)
@_validator = Class.new(Validator) { params(&block || -> {}) }.new
end

# Defines validations for the params, using a dry-validation contract.
#
# @param block [Proc] the contract definition
#
# @see https://dry-rb.org/gems/dry-validation/
#
# @api public
# @since 2.2.0
def self.contract(&block)
@_validator = Class.new(Validator, &block).new
end
Expand Down
130 changes: 106 additions & 24 deletions lib/hanami/action/validatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,32 @@ def self.included(base)
# @since 0.1.0
# @api private
module ClassMethods
# Whitelist valid parameters to be passed to Hanami::Action#call.
# Defines a validation schema for the params passed to {Hanami::Action#call}.
#
# This feature isn't mandatory, but higly recommended for security
# reasons.
# This feature isn't mandatory, but is highly recommended for secure handling of params:
# because params come from an untrusted source, it's good practice to filter these to only
# the keys and types required for your action's use case.
#
# Because params come into your application from untrusted sources, it's
# a good practice to filter only the wanted keys that serve for your
# specific use case.
# The given block is evaluated inside a `params` schema of a `Dry::Validation::Contract`
# class. This constrains the validation to simple structure and type rules only. If you want
# to use all the features of dry-validation contracts, use {#contract} instead.
#
# Once whitelisted, the params are available as an Hash with symbols
# as keys.
# The resulting contract becomes part of a dedicated params class for the action, inheriting
# from {Hanami::Action::Params}.
#
# It accepts an anonymous block where all the params can be listed.
# It internally creates an inner class which inherits from
# Hanami::Action::Params.
#
# Alternatively, it accepts an concrete class that should inherit from
# Hanami::Action::Params.
# Instead of defining the params validation schema inline, you can alternatively provide a
# concrete params class, which should inherit from {Hanami::Action::Params}.
#
# @param klass [Class,nil] a Hanami::Action::Params subclass
# @param blk [Proc] a block which defines the whitelisted params
# @param block [Proc] the params schema definition
#
# @return void
#
# @see #contract
# @see Hanami::Action::Params
# @see https://guides.hanamirb.org//validations/overview
# @see https://dry-rb.org/gems/dry-validation/
#
# @example Anonymous Block
# @example Inline definition
# require "hanami/controller"
#
# class Signup < Hanami::Action
Expand All @@ -78,9 +76,11 @@ module ClassMethods
# require "hanami/controller"
#
# class SignupParams < Hanami::Action::Params
# required(:first_name)
# required(:last_name)
# required(:email)
# params do
# required(:first_name)
# required(:last_name)
# required(:email)
# end
# end
#
# class Signup < Hanami::Action
Expand All @@ -95,8 +95,8 @@ module ClassMethods
# end
# end
#
# @since 0.3.0
# @api public
# @since 0.3.0
def params(klass = nil, &block)
if klass.nil?
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))
Expand All @@ -106,9 +106,91 @@ def params(klass = nil, &block)
@params_class = klass
end

def contract(&block)
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))
klass.contract(&block)
# Defines a validation contract for the params passed to {Hanami::Action#call}.
#
# This feature isn't mandatory, but is highly recommended for secure handling of params:
# because params come from an untrusted source, it's good practice to filter these to only
# the keys and types required for your action's use case.
#
# The given block is evaluated inside a `Dry::Validation::Contract` class. This allows you
# to use all features of dry-validation contracts
#
# The resulting contract becomes part of a dedicated params class for the action, inheriting
# from {Hanami::Action::Params}.
#
# Instead of defining the params validation contract inline, you can alternatively provide a
# concrete params class, which should inherit from {Hanami::Action::Params}.
#
# @param klass [Class,nil] a Hanami::Action::Params subclass
# @param block [Proc] the params schema definition
#
# @return void
#
# @see #params
# @see Hanami::Action::Params
# @see https://dry-rb.org/gems/dry-validation/
#
# @example Inline definition
# require "hanami/controller"
#
# class Signup < Hanami::Action
# contract do
# params do
# required(:first_name)
# required(:last_name)
# required(:email)
# end
#
# rule(:email) do
# # custom rule logic here
# end
# end
#
# def handle(req, *)
# puts req.params.class # => Signup::Params
# puts req.params.class.superclass # => Hanami::Action::Params
#
# puts req.params[:first_name] # => "Luca"
# puts req.params[:admin] # => nil
# end
# end
#
# @example Concrete class
# require "hanami/controller"
#
# class SignupParams < Hanami::Action::Params
# contract do
# params do
# required(:first_name)
# required(:last_name)
# required(:email)
# end
#
# rule(:email) do
# # custom rule logic here
# end
# end
# end
#
# class Signup < Hanami::Action
# params SignupParams
#
# def handle(req, *)
# puts req.params.class # => SignupParams
# puts req.params.class.superclass # => Hanami::Action::Params
#
# req.params[:first_name] # => "Luca"
# req.params[:admin] # => nil
# end
# end
#
# @api public
# @since 2.2.0
def contract(klass = nil, &block)
if klass.nil?
klass = const_set(PARAMS_CLASS_NAME, Class.new(Params))
klass.contract(&block)
end

@params_class = klass
end
Expand Down

0 comments on commit 397bc60

Please sign in to comment.