hanami / controller

Complete, fast and testable actions for Rack and Hanami
http://hanamirb.org
MIT License
246 stars 111 forks source link

Add contract support to actions #453

Closed timriley closed 2 months ago

timriley commented 2 months ago

Build upon the foundation that @krzykamil made in #451 (thank you, @krzykamil, we truly wouldn't have gotten here without you! ❤️) and take a second approach at adding support for dry-validation contracts for Hanami::Action params.

The goals of this approach:

Here's how we do it:

  1. Add Hanami::Action.contract method as the counterpart to .params. This is the main point of interaction for users.
  2. Likewise, add the Hanami::Action::Params.contract method as the counterpart to .params. Unlike params, this passes the given block to the top-level of a Dry::Validation::Contract subclass, allowing the user to use all features of dry-validation contracts.
  3. Introduce the concept of a class-level @_validator inside Hanami::Action::Params. This was the missing concept in the previous approaches. This validator will the dry-validation contract created by either the params or contract methods.
  4. Stop including Hanami::Validations::Form inside Hanami::Action::Params. This module was doing very little, at the cost of a lot of (cross-gem!) indirection. It is now replaced by the @_validator as well as the Hanami::Action::Params::Validator base class that is used for the generated schema or contract, which is what ensures a _csrf_token param is permitted at all times. Now all the code dealing with param validation is kept all together inside hanami-controller, so it's easier to follow and maintain.

That's it!

@krzykamil and @solnic — given you were both active within #451, I'm keen for any feedback you may have here.

@parndt, I'll ping you here too, since I know you've been hanging out for this change :)

I've made a related PR (https://github.com/hanami/validations/pull/230) that removes all the code from hanami-validations and leaves it as a gem to manage the dry-validation dependency only. The code in hanami-validations that we shipped in 2.0 I left entirely as @api private, so it is safe to remove. The only remaining interaction with this gem was through Hanami::Action.params, which continues to work unchanged.

I do want to acknowledge one known limitation of the approach I've taken here: dry-validation contracts, unlike dry-schema schemas, are classes that are expected to be initialised, and as such, can receive their own dependencies. Right now, we do not support such use of contracts, because of how they are defined at the class level within actions. Later, we could choose to find a way to support contracts with dependencies, but I think we can safely leave that for the future after unlocking their basic usage with this PR. Users have a workaround in the meantime too, which is to make such contracts actual dependencies of the action, and then run then manually run params through those contracts within the #handle methods.

Resolves #441, resolves #451, resolves #434

parndt commented 2 months ago

Right now, we do not such such use of contracts, because of how they are defined at the class level within actions.

Should this say

Right now, we do not support such use of contracts, because of how they are defined at the class level within actions.

-such +support

timriley commented 2 months ago

@parndt yes, fixed now

krzykamil commented 2 months ago

Thanks for the mention, glad I was useful again :) I do quite enjoy how simple this implementation became after getting rid of the internal gem, cause when I worked on it Hanami::Validations::Form this inclusion in Params class was a hindrance. Wish I thought of simply getting rid of it lol.