How should I properly pass a Postgresql query into Askama markdown?!

I've now been stuck for 3 days trying to get the Askama template engine to render some text that is stored in my database in markdown format. There is not a single example on the internet and yet this template engine gets pushed as excellent.

I've now moved the sql query (I had it before in a handler but I couldn't pass it to Askama) into my async main in order have a shot at passing the data as a state.


// I want to pass this into a handler as a state in order for Askama
// to to render the values inside into a template. 
#[derive(Clone)]
struct AppState<'a> {
    title: &'a str,
    post_body: &'a str, 
} 

// My SQL query only returns strings so I need this which I then try
// to pass into the struct above this one. 
#[derive(FromRow,Debug, Clone)]
struct Post {
    title: String,
    post_body: String,
}

#[tokio::main]
async fn main() {
    let db_connection_str = std::env::var("DATABASE_URL")
        .unwrap_or_else(|_| "postgres://postgres:password@localhost/blog_posts".to_string());
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect(&db_connection_str)
        .await
        .expect("can't connect to database");

    let my_addr = SocketAddr::from(([127,0,0,1], 3000));
    let serve_dir = get_service(ServeDir::new("assets")).handle_error(handle_error);

    // I make the SQL query here so that I don't have to call it 
    // in a handler. 
    let  MYPOST = sqlx::query_as::<_,Post>("select title, post_body from my_posts")
        .fetch_all(&pool)
        .await
       .unwrap();

    // I then try to create the state using the query above. It will fail saying that 
    // the values I pass in don't live long enough. 
    let shared_state = Arc::new(AppState{pt: &MYPOST[0].title, pb: &MYPOST[0].post_body});

    let my_app = Router::new()
        .route("/posts", get(blog_handler))
        .with_state(shared_state)
        .nest_service("/assets", serve_dir.clone())
        .fallback_service(serve_dir);

    axum::Server::bind(&my_addr)
        .serve(my_app.into_make_service())
        .await
        .unwrap();
}

// I had to place '_ in here to get the compiler to shut up. 
// I'm not sure if I'm extracting the values correctly from State<Arc<AppState>>>
// PostTemplate is an Askama Template with the value types being 'a &str 
async fn blog_handler(State(state): State<Arc<AppState<'_>>>) -> impl IntoResponse + '_ {
    let template = PostTemplate{post_title: &state.title, post_body: &state.post_body};
    HtmlTemplate(template)
}

So my issue is that MYPOST, inside of async main, keeps getting dropped for some unknown
reason and so shared_state complains about the values not living long enough.

Also Askama's markdown engine doesn't take a String so I have to create an app state using 'a.

Store the owned Strings in AppState instead of borrowing them. The variable will be dropped when the function returns, but you're trying to send references to the variable to other threads which could outlive the function [1]

If you want to avoid rendering the template in the handler and let the IntoResponse impl handle it, I think you're going to have to create copies of the strings for PostTemplate as well, since the borrow checker can't guarantee the Arc stays alive once the function returns.


  1. In practice returning from main will terminate the program, but it's possible other threads could observe the freed memory between the function returning and the threads getting killed which will still be UB ↩ī¸Ž

1 Like

Alright, I changed AppState to only take type strings. But my handler is still buggy.

async fn blog_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
    let template = PostTemplate{post_title: &state.title, post_body: &state.post_body};
    HtmlTemplate(template)
}
error[E0515]: cannot return value referencing local variable `state`

88 |     let template = PostTemplate{post_title: &state.title, post_body: &state.post_body};
   |                                                     ----- `state` is borrowed here
89 |     HtmlTemplate(template)
   |     ^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

I can't just pass String into the template in my handler because Askama's markdown won't take it as this github issue shows here

I've tried to get the sql query stored globally as a static variable but I am still too noob to figure it out.

Ahh that's annoying. Just call Template's render method manually then, so you don't have to return the template struct.

1 Like

Wow, thanks so much!! Here's what I changed in my code in case anyone else has problems with Askama's markdown:

// removed <'a>
#[derive(Clone)]
struct AppState {
    title: String,
    poist_body: String, 
} 

// in async main I now pass ownership to the state 
let shared_state = Arc::new(AppState{title: MYPOST[0].title.clone(), post_body: MYPOST[0].post_body.clone()});

// now I render it directly inside of the handler
async fn blog_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
    let x = &state.title;
    let z = &state.post_body;
    let template = PostTemplate{post_title: &x, post_body: &z};
    match template.render() {
            Ok(html) => Html(html).into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to render template. Error {}", err),
            ).into_response(),
    }
}