[dry-validation] Validation passed when params missing and rise an error when it's present

I don’t know what I missed here but I’ve got strange behaviour inside of my validator. Based on that JSON request:

  "sdd_setup_request": {
    "return_url": "https://example.com/return",
    "data": {
        (...)
    }
  }
}

I’m trying to validate it using that validator class:

  params do
    required(:sdd_setup_request).schema do
      required(:return_url).value(:string)
      required(:data).hash do
       (...)
      end
    end
  end
end

But still gets an error of sdd_setup_request is missing, when I change it to optional inside the validator class it will be passed. But then when I want to test if the other validations work and remove return_url form the JSON file I should get an error of missing return_url but the validation will pass instead

If I understand the problem correctly, it looks to me that you need to flip schema to hash and you’ll be fine. Here’s a runnable code snippet that will print true in the command line for you:

#! /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

require "json"

payload = <<~JSON
  {
    "sdd_setup_request": {
      "return_url": "https://example.com/return",
      "data": {}
    }
  }
JSON

Request = Dry::Schema.JSON do
  required(:sdd_setup_request).hash do
    required(:return_url).filled :string
    required(:data).maybe :hash
  end
end

puts Request.call(JSON(payload)).success?

Alright, I forgot to mention that I’m using this in Rails app and based on the docs:

dry-schema is used as the schema engine in dry-validation which is the recommended solution for business domain validations

If I understand correctly, the only change I should do is to change params to schema like below:

class SddRequestValidator < Dry::Validation::Contract
schema do
required(:sdd_setup_request).hash do
required(:return_url).value(:string)
required(:data).hash do
end
end
end
end

That doesn’t change anything.

To make it work I had to pass request.params.to_h instead of regular params. No idea why, it’s Rails 7 btw.

That’s because dry-validation expects a Hash object, and params in rails is an ActiveSupport::Parameters object. I would suggest calling to_unsafe_h on it before passing it in.