Extend documentation of custom filters - #744
Conversation
djc
left a comment
There was a problem hiding this comment.
Thanks, some great improvements here!
| [#custom-filters]: #custom-filters | ||
|
|
||
| To define your own filters, simply have a module named filters in scope of the context deriving a `Template` impl. | ||
| To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl and defines the filters as functions within this module. The functions must have a first argument `&str`, but otherwise can have other inputs. The output must be `::askama::Result<String>`. |
There was a problem hiding this comment.
There are actually no restrictions on the arguments, and while the return type must be an ::askama::Result<T>, T can be of any type.
There was a problem hiding this comment.
Thank you!
I was checking it again and wanted to say that "the first argument to the filter is a reference to the expression it is applied to", but it is not totally true and found some unexpected (for me) behaviour:
Consider the templates
{{ 2|my_filter }}
and
{{ (1 + 1)|my_filter }}
I would expect both to work the same, but they do not: the first case is 2 is given directly to my filter, while in the second case &(1 + 1) is given as reference.
The problem arises for the signature of the filter: should it take a reference or not?
fn my_filter(n: usize) -> ::askama::Result<String>
works for the first case and
fn my_filter(n: &usize) -> ::askama::Result<String>
works for the second.
Question.
Is this expected for you, or should 2 be past a reference &2?
There was a problem hiding this comment.
I think this is expected, although I agree that it is quite subtle -- would be great to document it! I think the recommended way to abstract over the difference would be to use a trait. For example, std::fmt::Display has a blanket implementation for &T where T: std::fmt::Display, so you could take an impl std::fmt::Display and receive either a &T or a T. In other cases, you might be able to define your own trait in a similar way.
There was a problem hiding this comment.
Okay!
To document what values are given as references and which are not, where can I look?
I would assume that everything that needs evaluation, is the result of a filter, or is a field is passed by reference. But literal values are passed without reference. Where should I look for these "literal values"?
There was a problem hiding this comment.
I think you should not document the exact rules, since they might change across otherwise semver-compatible releases. See Expr::is_copyable() for the heuristics we're using. That method is actually slightly misnamed -- naming it the other way around might make more sense (something like needs_borrow()).
There was a problem hiding this comment.
Perfect! I changed the wording, I hope it is clear enough without too much detail.
saona-raimundo
left a comment
There was a problem hiding this comment.
Question on macro expansion: should expressions always be given by reference to filters?
| [#custom-filters]: #custom-filters | ||
|
|
||
| To define your own filters, simply have a module named filters in scope of the context deriving a `Template` impl. | ||
| To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl and defines the filters as functions within this module. The functions must have a first argument `&str`, but otherwise can have other inputs. The output must be `::askama::Result<String>`. |
There was a problem hiding this comment.
Thank you!
I was checking it again and wanted to say that "the first argument to the filter is a reference to the expression it is applied to", but it is not totally true and found some unexpected (for me) behaviour:
Consider the templates
{{ 2|my_filter }}
and
{{ (1 + 1)|my_filter }}
I would expect both to work the same, but they do not: the first case is 2 is given directly to my filter, while in the second case &(1 + 1) is given as reference.
The problem arises for the signature of the filter: should it take a reference or not?
fn my_filter(n: usize) -> ::askama::Result<String>
works for the first case and
fn my_filter(n: &usize) -> ::askama::Result<String>
works for the second.
Question.
Is this expected for you, or should 2 be past a reference &2?
- Mention that Askama may pass arguments by reference. - Best practice is to bound parameters by traits - No exact wording on how Askama resolves the reference issue, so that documentation does not goes out of date so soon.
djc
left a comment
There was a problem hiding this comment.
Great work, some minor clarifications/edits suggested.
The return type of filters do not necessarily implement Display, only the final result of a chain of filters.
Related to #743 and #102
Extends documentation of custom filters.