Separating schema and domain validation

I’ve started using the dry ecosystem in various parts of our fairly standard Rails application (in production). We really like the DSL that dry-validation/schema provides and think the idea behind separating schema/structure validation from domain validation is great.

However, we want to take the separation further and actually report the violations from those separated validations differently. For example, we have a JS front-end for a signup form which posts via fetch to our backend. We want any structure violations (e.g. missing keys or perhaps the values in the dropdown aren’t what we expect) to raise, so our developers can fix a mistake (rather than the user being told they are missing a key!). We then want the domain validation (“this value must filled”) to be reported back to actual users.

We currently do this with dry-schema at the controller level for validating the structure of the API request and failures here will raise. We then define a dry-validation separately for domain rules. The issue we have with this is that there is a lot of duplication and “redundant” definitions.

For example, through the first structure step we have already required keys to be present and certain types to be defined. It’s a little redundant to then have to redefine keys and types, when they actually aren’t the target of the second validation and won’t be used at all in the errors we show the user.

The solution perhaps is to allow key(:something) (so you aren’t redefining the required-ness) and allow leaving off a type (which currently works but looks like it’s getting deprecated).

I imagine the real problem here is that this use case isn’t really what you had in mind for these gems and they are more for “pure” API systems where all the errors (both structure and domain) are reported back to the “user”.

Do you have any thoughts on the separation that I am trying to achieve here?

Hey Adam!

This is a really good use case and even though it’s not clearly visible, the gems are intended to be used like that. You can completely avoid duplication by reusing your schemas, here’s an example:

# frozen_string_literal: true

require "dry/schema"
require "dry/validation"

Dry::Validation.load_extensions(:predicates_as_macros)

UserSchema = Dry::Schema.Params do
  required(:name).value(:string)
end

class UserContract < Dry::Validation::Contract
  import_predicates_as_macros

  schema(UserSchema)

  rule(:name).validate(:filled?)
end

contract = UserContract.new

puts contract.(UserSchema.(name: "").to_h).errors.to_h.inspect
# {:name=>["must be filled"]}

Notice that the contract will apply it’s schema too - this means you’d be applying same schemas twice - we could consider adding a feature that would prevent that via some option. It’s something to discuss, ie we could make contracts accept a schema result object and when that happens it would know there’s no need to re-apply it’s own schema.

Does this make sense?

Thanks for the reply!

I did stumble across this way of reusing schemas yesterday actually! The use case I have for this where this appears to fall down (though I haven’t tried) is where the schema for API is slightly different than the schema for the “user”.

For example, the user can input a number into a text box. The schema in the controller needs to accept a string (since there’s not guarantee we get an integer, even if using a number input) but the schema at domain level wants to validate and coerce it into an integer. Can you override parts of the schema you are borrowing from?

As an aside, these validations are being used in conjunction with dry-struct. I wonder if it’s possible to use the “schema” defined by the struct’s attributes as the schema for the validation? Because you definitely end up duplicating knowledge here too (with both expected types and key requirements). This may be straying a bit far from the intended use-case (coupling structs to validations).

I’d actually coerce already at the controller level - this is an HTTP-specific concern (or at least you can treat it like such).

So this keeps coming back - doing the other way around would feel more appropriate. You can generate structs from schemas. I said in the past that we’d have that feature one day and I really wanna do it soon. People keep asking about it on a regular basis!

1 Like

So this keeps coming back - doing the other way around would feel more appropriate. You can generate structs from schemas. I said in the past that we’d have that feature one day and I really wanna do it soon. People keep asking about it on a regular basis!

Yep, this makes sense!

I’d actually coerce already at the controller level - this is an HTTP-specific concern (or at least you can treat it like such).

I guess it depends how you view it. It’s not a HTTP-specific concern in this case, because the user could enter text into input field (or even a decimal is more realistic) and we want to give them an error, rather than using controller-level schemas (which raise when there is a violation). But yeah, there are a few ways to solve this I guess like duplicating the schema, ensuring the value is an integer on the front-end first (though it’s nice to have all the type checking/coercion purely on the back-end for us).

Thanks for you help anyways, this thread has been useful!

1 Like