Composing schemas

Warning

This feature is experimental until dry-schema reaches 2.0.0

You can compose schemas using the following standard logic operators:

  • s1 & s2 - both schemas must pass
  • s1 | s2 - both or one of the schemas must pass
  • s1 > s2 - if s1 passes then s2 must be pass too, otherwise the entire statement passes
Info

Currently "xor" is not supported because it's not yet clear how to generate errors messages in this case

Using &

When you compose schemas using &, the right side will be applied only when left side passed first:

Role = Dry::Schema.JSON do
  required(:id).filled(:string)
end

Expirable = Dry::Schema.JSON do
  required(:expires_on).value(:date)
end

User = Dry::Schema.JSON do
  required(:name).filled(:string)
  required(:role).hash(Role & Expirable)
end

puts User.(name: "Jane", role: { id: "admin", expires_on: "2020-05-01" }).errors.to_h.inspect
# {}

puts User.(name: "Jane", role: { id: "", expires_on: "2020-05-01" }).errors.to_h.inspect
# {role: {id: ["must be filled"]}}

puts User.(name: "Jane", role: { id: "admin", expires_on: "oops" }).errors.to_h.inspect
# {role: {expires_on: ["must be a date"]}}

Using |

When you use |, both schemas will be applied and the error messages will be nested under special :or key:

RoleID = Dry::Schema.JSON do
  required(:id).filled(:string)
end

RoleTitle = Dry::Schema.JSON do
  required(:title).filled(:string)
end

User = Dry::Schema.JSON do
  required(:name).filled(:string)
  required(:role).hash(RoleID | RoleTitle)
end

puts User.(name: "Jane", role: {id: "admin"}).errors.to_h.inspect
# {}

puts User.(name: "Jane", role: {title: "Admin"}).errors.to_h.inspect

puts User.(name: "Jane", role: { id: ""}).errors.to_h.inspect
# {:role=>{:or=>[{:id=>["must be filled"]}, {:title=>["is missing"]}]}}

puts User.(name: "Jane", role: { title: ""}).errors.to_h.inspect
# {:role=>{:or=>[{:id=>["is missing"]}, {:title=>["must be filled"]}]}}

Using >

When you compose schemas using >, the right side will be applied only if the left side passed:

RoleID = Dry::Schema.JSON do
  required(:id).filled(:string)
end

RoleTitle = Dry::Schema.JSON do
  required(:title).filled(:string)
end

User = Dry::Schema.JSON do
  required(:name).filled(:string)
  required(:role).hash(RoleID > RoleTitle)
end

puts User.(name: "Jane", role: {id: "admin", title: "Admin"}).errors.to_h.inspect
# {}

puts User.(name: "Jane", role: { id: ""}).errors.to_h.inspect
# {}

puts User.(name: "Jane", role: {title: "Admin"}).errors.to_h.inspect
# {}

puts User.(name: "Jane", role: { id: "admin", title: ""}).errors.to_h.inspect
# {:role=>{:title=>["must be filled"]}}

octocatEdit on GitHub