Skip to content

Latest commit

 

History

History
131 lines (117 loc) · 3.07 KB

input_data_validation.md

File metadata and controls

131 lines (117 loc) · 3.07 KB

Input Data Validation

Usually, input definitions are quite simple and raise an error if the provided data does not match. How to return a meaningful error result when input data does not conform to specifications.

This example uses dry-validation, but you are free on how to validate your input data.

{% filter remove_code_promt %}

>> require "dry/validation"

>> class User
..   def initialize(name:, age:)
..     @name, @age = name, age
..   end
..   attr_reader :name, :age
..
..   class << self
..     attr_accessor :has_db
..   end
..
..   def save
..     !!User.has_db
..   end
.. 
..   def errors
..     User.has_db ? nil : { database: ["not connected"] }
..   end
.. end

>> Dry::Validation.load_extensions(:predicates_as_macros)
.. class CreateUserContract < Dry::Validation::Contract
..   import_predicates_as_macros
..
..   schema do
..     required(:name).filled(:string)
..     required(:age).value(:integer)
..   end
..
..   rule(:age).validate(gteq?: 18)
.. end
.. 
.. class CreateUser
..   include Teckel::Operation
..   result!
.. 
..   input CreateUserContract.new
..
..   input_constructor(->(input){
..     result = self.class.input.call(input)
..     if result.success?
..       result.to_h
..     else
..       fail!(message: "Input data validation failed", errors: result.errors.to_h)
..     end
..   })
.. 
..   output Types.Instance(User)
..   error  Types::Hash.schema(
..     message: Types::String,
..     errors: Types::Hash.map(Types::Symbol, Types::Array.of(Types::String))
..   )
.. 
..   def call(input)
..     user = User.new(**input)
..     
..     if user.save
..       success! user
..     else
..       fail!(message: "Could not save User", errors: user.errors)
..     end
..   end
.. 
..   finalize!
.. end

{% endfilter %}

{% filter remove_code_promt %}

>> User.has_db = true
>> CreateUser.call(name: "Bob", age: 23).success
=> #<User:<...> @age=23, @name="Bob">

{% endfilter %}

Error from response from our validation in input_constructor:

{% filter remove_code_promt %}

>> CreateUser.call(name: "Bob", age: 10).failure
=> {:errors=>{:age=>["must be greater than or equal to 18"]},
    :message=>"Input data validation failed"}

{% endfilter %}

Error response from the our operation call:

{% filter remove_code_promt %}

>> User.has_db = false
>> CreateUser.call(name: "Bob", age: 23).failure
=> {:errors=>{:database=>["not connected"]}, :message=>"Could not save User"}

{% endfilter %}

Errors raised in input_constructor need to conform to the defined error:

>> class IncorrectFailure
..   include Teckel::Operation
.. 
..   result!
.. 
..   input(->(input) { input }) # pass
..   input_constructor(->(_input) {
..     fail!("Input data validation failed")
..   })
.. 
..   output none
..   error  Types::Hash.schema(message: Types::String)
.. 
..   def call(_); end
..
..   finalize!
.. end

>> IncorrectFailure.call rescue $ERROR_INFO
=> #<Dry::Types::ConstraintError:<...> "Input data validation failed" violates constraints (type?(Hash, "Input data validation failed") failed)>