Open Galitan-dev opened 1 year ago
I'm already planning to implement it, but I wanted to let you know so you can share your opinion. Also, writing an issue helps me make my thoughts clearer in my head 😅.
In case you want to monitor progress: https://github.com/Galitan-dev/poem/tree/feat/tera
The example is already 13 lines shorter!
use poem::{
get, handler,
listener::TcpListener,
web::Path,
Route, Server,
EndpointExt,
tera::{TeraTemplating, TeraTemplate, Tera, Context}
};
#[handler]
fn hello(Path(name): Path<String>, tera: Tera) -> TeraTemplate {
let mut context = Context::new();
context.insert("name", &name);
tera.render("index.html.tera", &context)
}
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
let app = Route::new()
.at("/hello/:name", get(hello))
.with(TeraTemplating::from_glob("templates/**/*"));
Server::new(TcpListener::bind("127.0.0.1:3000"))
.run(app)
.await
}
Something screams at me that someone should write a macro for the context tempvar, so the ergonomics are more like python flask and similar, like this:
tera.render("index.html.tera", ctx!(name: name))
// pseudo, will not compile
macro_rules ctx {
($($key: name: $value:token)) => {
let mut context = Context::new();
(context.insert("$key", &$value))+;
context
}
}
It's way to late in the evening and probably someone already thought of this, but that would probalby improve ergonomics.
Something screams at me that someone should write a macro for the context tempvar, so the ergonomics are more like python flask and similar, like this:
tera.render("index.html.tera", ctx!(name: name))
// pseudo, will not compile macro_rules ctx { ($($key: name: $value:token)) => { let mut context = Context::new(); (context.insert("$key", &$value))+; context } }
It's way to late in the evening and probably someone already thought of this, but that would probalby improve ergonomics.
I will work around this is a great idea
Here's the result:
#[handler]
fn hello(Path(name): Path<String>, tera: Tera) -> TeraTemplate {
tera.render("index.html.tera", &ctx!{ "name": &name })
}
#[macro_export]
macro_rules! ctx {
{ $( $key:literal: $value:expr ),* } => {
{
let mut context = Context::new();
$(
context.insert($key, $value);
)*
context
}
};
}
The Tera I18N filter is ready!
<h1>{{ "welcome" | translate(name=name) }}</h1>
#[handler]
fn hello(Path(name): Path<String>, tera: Tera) -> TeraTemplate {
tera.render("hello.html.tera", &ctx!{ "name": &name })
}
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
let resources = I18NResources::builder()
.add_path("resources")
.build()
.unwrap();
let app = Route::new()
.at("/", get(index))
.at("/hello/:name", get(hello))
.with(TeraTemplating::from_glob("templates/**/*"))
.using(filters::translate)
.data(resources);
Server::new(TcpListener::bind("127.0.0.1:3000"))
.run(app)
.await
}
So it seems I started writing my own Tera glue library at the same time lol, I only just came across this issue/PR.
My attempt is hosted on Gitlab: https://gitlab.com/nebneb0703/poem-tera
It isn't in a polished/mergeable state, for example it is missing docs and examples, but I thought it could be good to compare and pick out the best parts to submit to the final PR.
I wanted a similar experience to Rocket so my solution also includes a file watcher for debug builds.
So it seems I started writing my own Tera glue library at the same time lol, I only just came across this issue/PR.
My attempt is hosted on Gitlab: https://gitlab.com/nebneb0703/poem-tera
It isn't in a polished/mergeable state, for example it is missing docs and examples, but I thought it could be good to compare and pick out the best parts to submit to the final PR.
I wanted a similar experience to Rocket so my solution also includes a file watcher for debug builds.
Nice ! I knew that I couldn't be the only one to have this need. That's why I created this issue. I already opened a PR but we can open a second one to add your ideas. I will check your repository.
So it seems I started writing my own Tera glue library at the same time lol, I only just came across this issue/PR. My attempt is hosted on Gitlab: https://gitlab.com/nebneb0703/poem-tera It isn't in a polished/mergeable state, for example it is missing docs and examples, but I thought it could be good to compare and pick out the best parts to submit to the final PR. I wanted a similar experience to Rocket so my solution also includes a file watcher for debug builds.
Nice ! I knew that I couldn't be the only one to have this need. That's why I created this issue. I already opened a PR but we can open a second one to add your ideas. I will check your repository.
Great work! I didn't know we could have different codes depending on the target. I see, we ended up doing quite the same thing 😅. The main notable differences are:
So I suggest that we make a second PR, where we first add a "from_directory" constructor and your live reloading implementation. Second, we can try implementing the layer around tera I spoke about sooner basing on your Tera struct.
The issue I am currently facing is intercepting the handler's response while having the tera::Tera instance in order to render poem::Template. Any Ideas? Here's the current code that intercepts the handler's response:
impl IntoResult<Html<String>> for TeraTemplatingResult {
fn into_result(self) -> Result<Html<String>> {
if let Err(err) = &self {
println!("{err:?}");
}
self.map_err(InternalServerError).map(Html)
}
}
When testing my crate I did this:
#[poem::handler]
async fn index(tera: Tera) -> impl IntoResponse {
tera.render("index.html.tera", &Default::default()).await
.map(IntoResponse::into_response)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
Obviously this is not an ideal solution to have the user do.
Maybe we could newtype around Result<Html<String>>
, for example struct Template
and we can add methods to customise error behaviour, but with sensible defaults.
Yes, because the tera error are just structure we will need to format them ourself. And yes we some how need a Template struct containing the Tera instance or what else. Also we can change a bit the poem logic and add a middleware after the response if it doesnt still exist.
For those interested, we continued talking on Discord.
Support Tera Templating
Description
Adding a Middleware which injects a Tera instance to each request (like Data) to avoid using lazy_static as the current example does and adding a wrapper for tera::Result to avoid the redundant mapping.
Usage Example
Future Improvement Ideas