Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contract support to actions #453

Merged
merged 6 commits into from
Sep 2, 2024
Merged

Add contract support to actions #453

merged 6 commits into from
Sep 2, 2024

Conversation

timriley
Copy link
Member

@timriley timriley commented Aug 27, 2024

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:

  • Add contract support as simply as possible
  • Retain a single, straightforward-to-maintain interface for the params object within actions
  • Eliminate the dependency on code inside hanami-validations

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 (hanami/validations#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

…dations code

Add `Hanami::Action::Params.contract` as a counterpart to `Hanami::Action::Params.params`, allowing `Hanami::Action.contract` to use this API as opposed to directly setting a class-level ivar. This cleans up the remaining “hacky” aspect of my initial `Hanami::Action.contract` introduction.

Secondly, stop using code from `Hanami::Validations` altogether. I always found this code too indirect and hard to follow. Instead, introduce the one bit of important logic (the base validator class permitting the `_csrf_token`) directly into `Hanami::Action::Params`, which means this class is now entirely standalone. This will make it easier to adjust/maintain in future.
@timriley timriley changed the title Use a simpler approach to add contract support Add contract support to actions Aug 28, 2024
@timriley timriley marked this pull request as ready for review August 28, 2024 12:21
@parndt
Copy link
Contributor

parndt commented Aug 28, 2024

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
Copy link
Member Author

@parndt yes, fixed now

# @example Concrete class
# require "hanami/controller"
#
# class SignupParams < Hanami::Action::Params
Copy link
Contributor

@alassek alassek Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm conflicted about this decision. The reason why I would most likely want to use a concrete class for this is to share validation rules between multiple things.

By basing this on Hanami::Action::Params, this requires a request env to initialize, which limits its usefulness to controllers only. This excludes non-request sharing, such as Operations or CLI tools.

I wish contracts were composable. If I could merge a preexisting contract into the action validator, this would be moot.

Another way you can share logic is via macros. My preference would be to define an abstract class to contain these macros because I may want to use different ones for different use-cases. But since you hard-code Dry::Validation::Contract as the base class here, I can only share macros globally. I don't know if that's a problem.

On the other hand, perhaps its reasonable that HTTP-boundary validations are their own thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alassek Thanks for raising this! I agree with you here. If I was building this feature from scratch today, I wouldn't have things run through these Hanami::Action::Params intermediary classes, and instead have dry-validation contracts be provided directly.

I'd describe this current approach as a vestige of the 1.x-era of Hanami design, where it was much more heavy-handed in "owning the APIs" rather than the lighter touch approach we take for our integration with the dry-rb gems today. It's also worth mentioning that back in those times, dry-validation might have seemed like a slightly less stable target, so the framework builders might've wanted this extra layer as "insulation."

In terms of why I implemented it this way here: I wanted a small, minimally-disruptive change as a first step, something I could merge confidently and use as a launching point for any next steps. I didn't say it out loud, but I did want us to get to the point of supporting dry-validation contracts directly, but I wasn't sure about when would be ideal for that.

Your question motivates me to have a quick run at it as a follow-up and see what might be possible :)

As an additional measure, I'm going to mark the new Hanami::API::Params.contract method here as @api private, as part of a "soft deprecation" approach to this direct use of the Params class. I'd much rather have users specify their validations at the action level only, and use contracts directly as a way to reuse it. Let's see how it goes!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've started this work in #454. I think I have a pretty clear path to getting it done, too. I'll leave another update over in that PR once it's ready!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#454 is ready now :)

@krzykamil
Copy link

krzykamil commented Sep 2, 2024

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.

@timriley timriley merged commit 74b652e into main Sep 2, 2024
8 checks passed
@timriley timriley deleted the contract-support-2 branch September 2, 2024 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Allow full dry-validation contracts to be used inside actions
5 participants