jet icon indicating copy to clipboard operation
jet copied to clipboard

Custom resolvers

Open tonyhb opened this issue 4 years ago • 2 comments

Opening a PR with a prototype as a means to discuss custom resolvers. Based off of this comment in #110, custom resolvers allow us to override default functionality when executing a template. This would open the possibility to:

  • Implement logging and tracing
  • Implement custom error handling logic
  • Implement introspection without navigating the AST

This PR is purely a concept. It has no tests, and it exposes *Runtime directly from a template, which I do not like. So, word of warning, this PR is not ready and the aim here is to open a discussion with maintainers and owners regarding whether Resolver interfaces can be considered.

Stealing from my previous comment:

It adds a single interface - Resolver:

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{})
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.

tonyhb avatar Dec 22 '20 11:12 tonyhb

Looks like there are some issues with templates halting execution when eg. iterating over nil ranges. So, no matter what, it doesn't seem like adding a Resolver interface magically lets us customize execution. Kinda writing off this idea but will keep this open for discussion.

edit:

It's possible to get around this by creating a type which implements Ranger.

tonyhb avatar Dec 22 '20 11:12 tonyhb

I like the overall idea, however I believe it would be good to give users a way to hook and redirect variable declarations as well in order to make a custom resolver really useful. I'm picturing something like a user defined and controlled reflect.Value store.

sauerbraten avatar Jan 04 '21 10:01 sauerbraten