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

response from template #682

Merged
merged 4 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,4 @@ features = ["testing"]
loco-rs = { path = ".", features = ["testing"] }
rstest = "0.21.0"
insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] }
tree-fs = { version = "0.1.0" }
2 changes: 1 addition & 1 deletion examples/demo/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Hooks for App {
.add_route(controllers::notes::routes())
.add_route(controllers::auth::routes())
.add_route(controllers::mysession::routes())
.add_route(controllers::dashboard::routes())
.add_route(controllers::view_engine::routes())
.add_route(controllers::user::routes())
.add_route(controllers::upload::routes())
.add_route(controllers::responses::routes())
Expand Down
2 changes: 1 addition & 1 deletion examples/demo/src/controllers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pub mod auth;
pub mod cache;
pub mod dashboard;
pub mod middlewares;
pub mod mylayer;
pub mod mysession;
pub mod notes;
pub mod responses;
pub mod upload;
pub mod user;
pub mod view_engine;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::unused_async)]
use loco_rs::prelude::*;
use serde_json::json;

use crate::{initializers::hello_view_engine::HelloView, views};

Expand All @@ -9,7 +10,7 @@ use crate::{initializers::hello_view_engine::HelloView, views};
///
/// This function will return an error if render fails
pub async fn render_home(ViewEngine(v): ViewEngine<TeraView>) -> Result<Response> {
views::dashboard::home(&v)
views::engine::home(&v)
}

/// Hello
Expand All @@ -24,9 +25,14 @@ pub async fn render_hello(ViewEngine(v): ViewEngine<HelloView>) -> Result<Respon
format::render().view(&v, "foobar", ())
}

pub async fn render_simple() -> Result<Response> {
format::render().template("{{name}} website", json!({"name": "Loco"}))
}

pub fn routes() -> Routes {
Routes::new()
.prefix("dashboard")
.prefix("view-engine")
.add("/home", get(render_home))
.add("/hello", get(render_hello))
.add("/simple", get(render_simple))
}
File renamed without changes.
2 changes: 1 addition & 1 deletion examples/demo/src/views/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod auth;
pub mod dashboard;
pub mod engine;
pub mod notes;
pub mod upload;
pub mod user;
5 changes: 3 additions & 2 deletions examples/demo/tests/cmd/cli.trycmd
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ $ blo-cli routes --environment test
[POST] /auth/verify
[GET] /cache
[POST] /cache/insert
[GET] /dashboard/hello
[GET] /dashboard/home
[GET] /mylayer/admin
[GET] /mylayer/echo
[GET] /mylayer/user
Expand All @@ -115,6 +113,9 @@ $ blo-cli routes --environment test
[POST] /user/convert/user
[GET] /user/current
[GET] /user/current_api_key
[GET] /view-engine/hello
[GET] /view-engine/home
[GET] /view-engine/simple

```

Expand Down
1 change: 1 addition & 0 deletions examples/demo/tests/requests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ mod prepare_data;
mod responses;
mod upload;
mod user;
mod view_engine;
8 changes: 8 additions & 0 deletions examples/demo/tests/requests/snapshots/hello@view_engine.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: tests/requests/view_engine.rs
expression: "(response.status_code(), response.text())"
---
(
200,
"hello",
)
8 changes: 8 additions & 0 deletions examples/demo/tests/requests/snapshots/home@view_engine.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: tests/requests/view_engine.rs
expression: "(response.status_code(), response.text())"
---
(
200,
"<html><body>\n <img src=\"/static/image.png\" width=\"200\"/>\n <br/>\n find this tera template at <code>assets/views/home/hello.html</code>: \n <br/>\n <br/>\n Hello World!, \n <br/>\n Hallo Welt!\n \n</body></html>\n ",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: tests/requests/view_engine.rs
expression: "(response.status_code(), response.text())"
---
(
200,
"Loco website",
)
34 changes: 34 additions & 0 deletions examples/demo/tests/requests/view_engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use blo::app::App;
use insta::assert_debug_snapshot;
use loco_rs::testing;
use rstest::rstest;
use serial_test::serial;
// TODO: see how to dedup / extract this to app-local test utils
// not to framework, because that would require a runtime dep on insta
macro_rules! configure_insta {
($($expr:expr),*) => {
let mut settings = insta::Settings::clone_current();
settings.set_prepend_module_to_snapshot(false);
settings.set_snapshot_suffix("view_engine");
let _guard = settings.bind_to_scope();
};
}

#[rstest]
#[case("home")]
#[case("hello")]
#[case("simple")]
#[tokio::test]
#[serial]
async fn can_get_view_engine(#[case] uri: &str) {
configure_insta!();
testing::request::<App, _, _>(|request, _ctx| async move {
let response = request.get(&format!("/view-engine/{uri}")).await;

assert_debug_snapshot!(
uri.replace('/', "_"),
(response.status_code(), response.text())
);
})
.await;
}
33 changes: 31 additions & 2 deletions src/controller/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ use hyper::{header, StatusCode};
use serde::Serialize;
use serde_json::json;

use super::views::ViewRenderer;
use crate::{controller::Json, Result};
use crate::{
controller::{
views::{self, ViewRenderer},
Json,
},
Result,
};

/// Returns an empty response.
///
Expand Down Expand Up @@ -169,6 +174,18 @@ where
html(&res)
}

/// Render template from string
///
/// # Errors
///
/// This function will return an error if rendering fails
pub fn template<S>(template: &str, data: S) -> Result<Response>
where
S: Serialize,
{
html(&views::template(template, data)?)
}

pub struct RenderBuilder {
response: Builder,
}
Expand Down Expand Up @@ -278,6 +295,18 @@ impl RenderBuilder {
self.html(&content)
}

/// Render template located by `key`
///
/// # Errors
///
/// This function will return an error if rendering fails
pub fn template<S>(self, template: &str, data: S) -> Result<Response>
where
S: Serialize,
{
html(&views::template(template, data)?)
}

/// Finalize and return a HTML response
///
/// # Errors
Expand Down
60 changes: 55 additions & 5 deletions src/controller/views/engines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use serde::Serialize;

use crate::{controller::views::ViewRenderer, Error, Result};

const VIEWS_DIR: &str = "assets/views/";
const VIEWS_GLOB: &str = "assets/views/**/*.html";
const VIEWS_DIR: &str = "assets/views";

#[derive(Clone, Debug)]
pub struct TeraView {
Expand All @@ -20,13 +19,29 @@ impl TeraView {
///
/// This function will return an error if building fails
pub fn build() -> Result<Self> {
if !Path::new(VIEWS_DIR).exists() {
Self::from_custom_dir(&VIEWS_DIR)
}

/// Create a Tera view engine from a custom directory
///
/// # Errors
///
/// This function will return an error if building fails
pub fn from_custom_dir<P: AsRef<Path>>(path: &P) -> Result<Self> {
if !path.as_ref().exists() {
return Err(Error::string(&format!(
"missing views directory: `{VIEWS_DIR}`"
"missing views directory: `{}`",
path.as_ref().display()
)));
}

let tera = tera::Tera::new(VIEWS_GLOB)?;
let tera = tera::Tera::new(
path.as_ref()
.join("**")
.join("*.html")
.to_str()
.ok_or_else(|| Error::string("invalid blob"))?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blob -> glob

)?;
let ctx = tera::Context::default();
Ok(Self {
tera,
Expand Down Expand Up @@ -63,3 +78,38 @@ impl ViewRenderer for TeraView {
Ok(self.tera.render(key, &context)?)
}
}

#[cfg(test)]
mod tests {
use serde_json::json;
use tree_fs;

use super::*;
#[test]
fn can_render_view() {
let yaml_content = r"
files:
- path: template/test.html
content: |-
generate test.html file: {{foo}}
- path: template/test2.html
content: |-
generate test2.html file: {{bar}}
";

let tree_res = tree_fs::from_yaml_str(yaml_content).unwrap();
let v = TeraView::from_custom_dir(&tree_res).unwrap();

assert_eq!(
v.render("template/test.html", json!({"foo": "foo-txt"}))
.unwrap(),
"generate test.html file: foo-txt"
);

assert_eq!(
v.render("template/test2.html", json!({"bar": "bar-txt"}))
.unwrap(),
"generate test2.html file: bar-txt"
);
}
}
25 changes: 25 additions & 0 deletions src/controller/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,31 @@ impl<E> ViewEngine<E> {
}
}

/// A struct representing an inline Tera view renderer.
///
/// This struct provides functionality to render templates using the Tera templating engine
/// directly from raw template strings.
///
/// # Example
/// ```
/// use serde_json::json;
/// use loco_rs::controller::views;
/// let render = views::template("{{name}} website", json!({"name": "Loco"})).unwrap();
/// assert_eq!(render, "Loco website");
/// ```
///
/// # Errors
///
/// This function will return an error if building fails
pub fn template<S>(template: &str, data: S) -> Result<String>
where
S: Serialize,
{
let mut tera = tera::Tera::default();
tera.add_raw_template("default", template)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok(tera.render("default", &tera::Context::from_serialize(data)?)?)
}

impl<E> From<E> for ViewEngine<E> {
fn from(inner: E) -> Self {
Self::new(inner)
Expand Down
Loading