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 validate function to simplify input validation for partials #13030

Open
michaeltlombardi opened this issue Nov 12, 2024 · 2 comments
Open
Assignees
Labels
Milestone

Comments

@michaeltlombardi
Copy link

As a Hugo theme developer, I want a simpler way to define validation for my partials so that I don't have to write numerous handlers.

I work on a set of (extensible, modular) Hugo themes and modules with many small partials. Many of the partials are meant to be reused within and external to the module they're defined in. I often find myself writing/re-writing boiler plate validation at the top of my partials to raise errors when they're passed invalid input types (e.g. getting a string when I expect an integer, or missing a key in the input map).

Being able to define expectations and pass those expectations and the partial's input to a function like validate would greatly simplify how much work I need to do for my partials, make them easier for me to document, and more immediately understandable for contributing and integrating developers.

Example usage

This example defines a partial, partials/funcs/double.html:

{{ $Input  := . -}}
{{ $Expect := dict "Type" "int" }}

{{ validate (dict "Expect" $Expect "Input" $Input) }}

{{ return (mul 2 $Input) }}

This partial expects to receive an integer as input. Sending it anything else - including nil - would raise an error in the validate function.

Altering the Expect map to allow for nil (in case you have specific handling complex enough to make a default or cond function call frustrating) might look like:

{{ $Input  := . -}}
{{ $Expect := dict "Type" "int" "AllowNil" }}

{{ validate (dict "Expect" $Expect "Input" $Input) }}

{{ return (mul 2 $Input) }}

Proposed function

The only way that occurs to me (not necessarily a good way) to handle this is with a new struct (e.g. Expected) and a template function (e.g. Validate). I don't have strong opinions about the names, just using these for clarity in this issue.

func (ns *Namespace) Validate(e Expected, v any) error {}

A very minimal gesture at the struct might look like:

struct Expected {
  // enum for supported data types: any (default), map, string, int, time, duration, context, etc.
  Type     ExpectedType
  AllowNil bool
  // Only used for objects to validate each key
  Keys     map[string]Expected
  // Only used for arrays
  Items    Expected
}

Being able to define min/max values etc would be really valuable, of course, but that leads to exponential complexity that can (maybe?) be deferred.

Alternative proposal

A thought that occurred to me but seems much more difficult (albeit also flexible) would be to allow partial developers to pass a JSON schema to validate the input against - but the main problem I see there is around jsonifying Hugo types, like page context - which could be very messy.

Tangential thoughts

Really, I think this problem is more rooted in the limitations of go templates - they don't have a way, as far as I can tell, to declare their input expectations or self-document. I find myself constantly fiddling around these edges, because I want my templates to be more reusable/understandable, if only for the me who returns to a project six months later.

I paused working on an implementation of this proposal as a partial in my own project to write this up, because I think this is likely easier to imlement and more performant as a template function, and could get wider use than a module defining a partial for this purpose.

@bep bep added this to the v0.138.0 milestone Nov 13, 2024
@bep bep self-assigned this Nov 13, 2024
@bep bep removed the NeedsTriage label Nov 13, 2024
@bep
Copy link
Member

bep commented Nov 14, 2024

I haven't seen a lot of people shouting loud about wanting something like this. Most uses of Hugo templates is fairly straight forward and most logic is "presentation logic". But I could be wrong.

In my head, assuming the input to a partial is a dict (which is very common), it could look like something like this:

{{ $expect := dict 
   "page" validation.IsPage.Optional
    "pageSize" (validation.IsInt.Gt 32)
}}
{{ $got := $ }}
{{ validation.Assert $expect $got }}

The above is very quickly jotted down and conceptual, perhaps some chaining e.g. {{ validation.IsPage.Or validation.IsSite }}

@michaeltlombardi
Copy link
Author

I suspect that the need is more related to theme/module developers as a subset of users - folks making partials for other folks to use, rather than defining partials for their own site - and something everyone more or less just works around right now.

Your example looks very much like what I would love to be able to do for my own partials.

@bep bep modified the milestones: v0.138.0, v0.139.0, v0.140.0 Nov 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants