# Top level logic validation

**URL:** <https://discourse.dry-rb.org/t/top-level-logic-validation/1515>\
**Category:** dry-validation\
**Created:** [September 13, 2022, 12:54pm UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515 "2022-09-13T12:54:01Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![PvPenny](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/pvpenny/32/796_2.png) [@PvPenny](https://discourse.dry-rb.org/u/PvPenny)\
**Post date:** [September 13, 2022, 12:54pm UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/1 "2022-09-13T12:54:01Z")

</div>

Hey there!

Is there any way I can validate, that my whole schema is one of existing?  
For example I have 2 schemas:

```auto
UserSchema = Dry::Schema.Params do
  required(:name).filled(:string)
end

AdminSchema = Dry::Schema.Params do
  required(:email).filled(:string)
end

```

can I make some kind of contract, that would be valid if one of schemas is valid? like

```auto
my_contract = Dry::Validation::Contract.build do
  params(AdminSchema | UserSchema) do
  end
end
end

```

And also are you planning to add `negation` operation to schema predicate?  
Thanks for the help! :3

---

<div class="post-metadata">

**Author:** ![bkuhlmann](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/bkuhlmann/32/557_2.png) [@bkuhlmann](https://discourse.dry-rb.org/u/bkuhlmann)\
**Post date:** [September 13, 2022, 7:26pm UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/2 "2022-09-13T19:26:43Z")

</div>

Hey Stas. 👋 This question is tangentially similar to what was asked earlier [here](https://discourse.dry-rb.org/t/showing-details-of-external-validators-failures/1498) but I think your question can probably be solved through pattern matching in this situation. Here’s a little script you can tinker with that should demonstrate a potential solution for you:

```ruby
#! /usr/bin/env ruby
# frozen_string_literal: true

# Save as `snippet`, then `chmod 755 snippet`, and run as `./snippet`.

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"

  gem "amazing_print"
  gem "debug"
  gem "dry-schema"
end

module Schemas
  User = Dry::Schema.Params { required(:name).filled :string }
  Admin = Dry::Schema.Params { required(:email).filled :string }
  Unknown = proc { "Unknown parameters." }
end

class Validator
  SCHEMAS = {user: Schemas::User, admin: Schemas::Admin, unknown: Schemas::Unknown}.freeze

  def initialize schemas: SCHEMAS
    @schemas = schemas
  end

  def call(parameters) = find_schema(parameters).call parameters

  private

  attr_reader :schemas

  def find_schema parameters
    case parameters
      in name:, **remainder unless remainder.key? :email then schemas.fetch :user
      in email:, **remainder unless remainder.key? :name then schemas.fetch :admin
      else schemas.fetch :unknown
    end
  end
end

validator = Validator.new

ap validator.call({name: "Jane Doe"})
ap validator.call({email: "Jill Smith"})
ap validator.call({name: "Invalid", email: "admin@example.com"})

```

When running the above you’ll see the following output:

```auto
#<Dry::Schema::Result{:name=>"Jane Doe"} errors={} path=[]>
#<Dry::Schema::Result{:email=>"Jill Smith"} errors={} path=[]>
"Unknown parameters."

```

You’ll notice that the validator handles a user, admin, and unknown data (simple use case but you can make it as sophisticated as needed). Basically, I’m using pattern matching to delegate to the appropriate schema to determine which schema to validate against. _This assumes you have distinguishing keys withing your parameters_ to route to the appropriate schema, though.

Anyway, might be of interest as one solution.

---

<div class="post-metadata">

**Author:** ![PvPenny](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/pvpenny/32/796_2.png) [@PvPenny](https://discourse.dry-rb.org/u/PvPenny)\
**Post date:** [September 14, 2022, 7:09am UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/3 "2022-09-14T07:09:29Z")

</div>

Hey, thanks! But it’s not what I wanna do, in my case I have input like

```auto
{
  name: 'John',
  email: 'test@test.ru',
  phone: '898989898',
....
}

```

and I wanna it would be correct and invalid only when name and email blank.  
Honestly, its for dynamic schema build, but I try to avoid monkey patching DSL or making something like `Schema.from_ast` 'cause it’s looks like it would take too much time 😅

---

<div class="post-metadata">

**Author:** ![alassek](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/alassek/32/314_2.png) [@alassek](https://discourse.dry-rb.org/u/alassek)\
**Post date:** [September 15, 2022, 4:07am UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/4 "2022-09-15T04:07:20Z")

</div>

Yes, you can compose a schema that has multiple parents. Like so:

```ruby
my_contract = Dry::Validation::Contract.build do
  params Dry::Schema.Params(parent: [UserSchema, AdminSchema])
end

```

---

<div class="post-metadata">

**Author:** ![PvPenny](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/pvpenny/32/796_2.png) [@PvPenny](https://discourse.dry-rb.org/u/PvPenny)\
**Post date:** [September 15, 2022, 7:44am UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/5 "2022-09-15T07:44:26Z")

</div>

In this case I have both validation from UserSchema and AdminSchema, and it’s only valid when both of validation passed, but I need, that result contract be valid even if one of schemas passed (like UserSchema || AdminSchema)

---

<div class="post-metadata">

**Author:** ![alassek](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/alassek/32/314_2.png) [@alassek](https://discourse.dry-rb.org/u/alassek)\
**Post date:** [September 15, 2022, 3:49pm UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/6 "2022-09-15T15:49:03Z")

</div>

Apologies, I misunderstood your question. What you’re trying to achieve was intended to be done with Contract objects, not schemas.

In this case, you cannot reuse `UserSchema` and `AdminSchema` to do this because you need to change the semantics of the schema to express this rule.

At the schema level, both keys must be optional because one or the other can be missing. The rule would be:

```ruby
my_contract = Dry::Validation::Contract.build do
  params do
    optional(:name).filled(:string)
    optional(:email).filled(:string)
  end

  rule :name, :email do
    unless values[:name] || values[:email]
      base.failure("must have one of: name, email")
    end
  end
end

```

---

<div class="post-metadata">

**Author:** ![bkuhlmann](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/bkuhlmann/32/557_2.png) [@bkuhlmann](https://discourse.dry-rb.org/u/bkuhlmann)\
**Post date:** [September 15, 2022, 4:21pm UTC](https://discourse.dry-rb.org/t/top-level-logic-validation/1515/7 "2022-09-15T16:21:01Z")

</div>

**Stas** : Oh, OK, I see what you are saying. I thought you needed to toggle between different schema versions in a sense but I see what you are saying now.

**Adam** : Thanks. I wasn’t aware you could have parents so that was fun to learn.
