0

I tried to make actix-web and Tera work together, from examples I found online.

This is my code:

use actix_web::{get, web, Result, App, HttpServer, HttpRequest, Responder, HttpResponse};
use serde::{Deserialize,Serialize};
use tera::{Tera, Context};
use lazy_static::lazy_static;


lazy_static! {
    pub static ref TEMPLATES: Tera = {
        let mut tera = match Tera::new("templates/**/*.html") {
            Ok(t) => t,
            Err(e) => {
                println!("Parsing error(s): {}", e);
                ::std::process::exit(1);
            }
        };
        tera.autoescape_on(vec![".html", ".sql"]);
        tera
    };
}


#[derive(Serialize)]
pub struct Response {
    pub message: String,
}

#[get("/")]
async fn index(tera: web::Data<Tera>) -> impl Responder {
    let mut context = Context::new();
    let template = tera.render("test.page.html", &context).expect("Error");
    HttpResponse::Ok().body(template)
}

async fn not_found() -> Result<HttpResponse> {
    let response = Response {
        message: "Resource not found".to_string(),
    };
    Ok(HttpResponse::NotFound().json(response))
}


#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "debug");
    env_logger::init();
    HttpServer::new(|| App::new()
        .service(index)
        .default_service(web::route().to(not_found)))
        .bind(("127.0.0.1", 9090))?
        .run()
        .await        
}
 

But when I go to the url I get this error:

[2023-08-29T11:27:53Z INFO  actix_server::builder] starting 8 workers
[2023-08-29T11:27:53Z INFO  actix_server::server] Actix runtime found; starting in Actix runtime
[2023-08-29T11:28:07Z DEBUG actix_web::data] Failed to extract `Data<tera::tera::Tera>` for `index` handler. For the Data extractor to work correctly, wrap the data with `Data::new()` and pass it to `App::app_data()`. Ensure that types align in both the set and retrieve calls.

Why Do I need to wrap it in Data::new() and use App::app_data()?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
user63898
  • 29,839
  • 85
  • 272
  • 514

1 Answers1

1

Why Do I need to wrap it in Data::new() and use App::app_data()?

Because Actix can't pass the extractor to your handler if it doesn't know about it.

Since you have used a lazy_static to have just one instance of Tera, you don't need to pass it to your handler as an extractor - just use the static!

#[get("/")]
async fn index() -> impl Responder {
    let mut context = Context::new();
    let template = TEMPLATES.render("test.page.html", &context).expect("Error");
    HttpResponse::Ok().body(template)
}

But you don't need to use lazy_static. Actix-web lets you define a shared resource and make it available to every request using an extractor. It's not clear which approach you wanted to take (since you did a bit of both!) but the other way, using shared state, is like this:

fn create_templates() - Tera {
    let mut tera = Tera::new("templates/**/*.html").expect("failed to parse template");
    tera.autoescape_on(vec![".html", ".sql"]);
    tera
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new()
       .service(index)
       .app_data(Data::new(create_templates()))
       .default_service(web::route().to(not_found)))
       .bind(("127.0.0.1", 9090))?
       .run()
       .await        
}

#[get("/")]
async fn index(tera: web::Data<Tera>) -> impl Responder {
    let mut context = Context::new();
    let template = tera.render("test.page.html", &context).expect("Error");
    HttpResponse::Ok().body(template)
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204