rustactix-webtera

actix_web + tera tamplates give me [actix_web::data] Failed to extract `Data<tera::tera::Tera>` for `index` handler wrap the data with `Data::new()`


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()?


Solution

  • 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)
    }