jet
jet copied to clipboard
Option to not abort template rendering process
Having to check every variable on whether it exists or not before using it makes Jet templates kind of verbose. Also, front end developers are used to invalid expressions simply returning an empty string (e. g. when using Handlebars) instead of aborting the whole template rendering process.
I was wondering if it would be feasible to disable the default behavior (abort template rendering) when an invalid expression is encountered.
To demonstrate, I've created a PR here: #109
+1
I believe this is solved with https://github.com/CloudyKit/jet/pull/152
Mhmm... not really I'd say. This issue asks to silently ignore undefined variables etc by default (or at least by a configurable flag). Try/catch doesn't do that.
Gotcha. Well, it seemed like a way to avoid aborting the entire template process when you find an invalid expression. In either case, dunno that it's a great idea to ignore undefined variables; seems like it would make things more buggy.
🤷♂️
For user defined templates this is a pretty important feature. You can't guarantee that user's input (either templates or variables) are always accurate. I think an option to use Resolve()
vs resolve()
and ignore errors makes a lot of sense!
Jet isn't intended to be used with unchecked user input, at least it's definitely not something I would recommend.
The Resolve() vs resolve() proposition sounds interesting, even though the code would fall apart the first time you pass a buggy resolved value to a function or something...
I hear you. RE. unchecked user input, was actually attempting to compile this with GOOS=js and GOARCH=wasm to run this in a separate webassembly environment. I'll investigate other things for now :)
For the fun of it, I spent 30 minutes hacking together a feature that allows "custom resolvers". The diff is here:
https://github.com/CloudyKit/jet/compare/master...tonyhb:feature/custom-resolver
This lets us do whatever we want, including skipping errors, getting a list of all variables used in a template, etc.
It adds a single interface - Resovler
:
type Resolver interface {
Resolve(name string) (reflect.Value, error)
}
This allows us to define custom functions overriding default functionality in Resolve. You can always get the default resolver functionality within Runtime by calling runtime.DefaultResolver()
.
Usage:
type CustomResolver struct {
default jet.Resolver
}
func (cr *CustomResolver) Resolve(name string) (reflect.Value, error) {
fmt.Printf("resolving %s\n", name)
// Here, we can ignore the error from our default resolver, choose to do no resolving at all, etc.
return cr.default(name)
}
var buf bytes.Buffer
tpl, err := jet.NewSet(jet.NewInMemLoader()).Parse("some-tpl.tpl". source)
runtime := tpl.Runtime()
runtime.WithResolver(&customResolver{default: runtime.DefaultResolver()})
tpl.ExecuteWith(runtime, buf, nil, nil)
I don't like how I have to expose the Runtime()
func within templates to get the default resolver at all to be honest, but yeah, like I said - it's a hacky POC to show that interfaces here might help.
Opened #186 to discuss the Resolver direction specifically, so that we don't pollute this issue with off-topic discussions :)