Schemas
Schemas are a crucial part of dry-validation
, they pre-process the data before it's validated by the rules and can provide detailed error messages. By default, the exposed DSL for defining schemas uses dry-schema
under the hood.
Defining a schema without coercion
To define a schema that does not perform coercion, use the schema
method:
class NewUserContract < Dry::Validation::Contract
schema do
required(:email).value(:string)
required(:age).value(:integer)
end
end
Now when a contract is applied, it will check the structure and types of the input:
contract = NewUserContract.new
result = contract.call(unexpected: :reality, age: 21)
# => #<Dry::Validation::Result{:age=>21} errors={:email=>["is missing"]}>
result.to_h
# => {:age=>21}
result.errors.to_h
# => {:email=>["is missing"]}
Defining a schema with Params coercion
To define a schema suitable for validating HTTP parameters, use the params
method:
class NewUserContract < Dry::Validation::Contract
params do
required(:email).value(:string)
required(:age).value(:integer)
end
end
The major difference between params
and the plain schema
is that params
will perform params-specific coercions before applying the contract's rules. For example, it will coerce strings into integers:
result = contract.call('email' => 'jane@doe.org', 'age' => '21')
# => #<Dry::Validation::Result{:email=>"jane@doe.org", :age=>21} errors={}>
result.to_h
# => {:email=>"jane@doe.org", :age=>21}
Defining a schema with JSON coercion
You can also use json
to define a schema suitable for validating JSON data:
class NewUserContract < Dry::Validation::Contract
json do
required(:email).value(:string)
required(:age).value(:integer)
end
end
The coercion logic is different to params
. For example, since JSON natively supports integers, it will not coerce them from strings:
result = contract.call('email' => 'jane@doe.org', 'age' => '21')
# => #<Dry::Validation::Result{:email=>"jane@doe.org", :age=>"21"} errors={:age=>["must be an integer"]}>
result = contract.call('email' => 'jane@doe.org', 'age' => 21)
# => #<Dry::Validation::Result{:email=>"jane@doe.org", :age=>21} errors={}>
result.to_h
# => {:email=>"jane@doe.org", :age=>21}
Re-using schemas
You can re-use an existing schema, or even multiple schemas, by simply passing them to schema definition method. All schema types support this feature.
require "dry/validation"
AddressSchema = Dry::Schema.Params do
required(:country).value(:string)
required(:zipcode).value(:string)
required(:street).value(:string)
end
ContactSchema = Dry::Schema.Params do
required(:email).value(:string)
required(:mobile).value(:string)
end
class NewUserContract < Dry::Validation::Contract
params(AddressSchema, ContactSchema) do
required(:name).value(:string)
required(:age).value(:integer)
end
end
contract = NewUserContract.new
contract.(name: "Jane", age: "31", email: "jane@doe.org", country: "foo")
# => #<Dry::Validation::Result{:name=>"Jane", :age=>31, :email=>"jane@doe.org",
# :country=>"foo"} errors={:zipcode=>["is missing"], :street=>["is missing"],
# :mobile=>["is missing"]}>
Using custom types
When you define a schema using params
or json
, the coercion logic is handled by type objects that are resolved from the type specifications within the schema. For example, when you use params
and define the type to be an :integer
, then the resolved type will be Dry::Schema::Types::Params::Integer
. This is just a convenience to make schema definition more concise.
If you want to use custom types, you can pass them explicitly when defining your schema:
module Types
include Dry::Types()
StrippedString = Types::String.constructor(&:strip)
end
class NewUserContract < Dry::Validation::Contract
params do
required(:email).value(Types::StrippedString)
required(:age).value(:integer)
end
end
Now your type will be applied:
contract.call(email: ' jane@doe ', age: 21)
# => #<Dry::Validation::Result{:email=>"jane@doe", :age=>21} errors={}>
Learn more
- dry-schema learn how to fully leverage schemas!
- dry-types learn more about the coercion backend used in the schemas
- rules learn how to define validation rules in addition to schemas