Problems trying to work with Axum Handlers and the Scraper crate

Hi guys!

I'm stuck on a question about asynchrony in rust. I'm trying to create a simple API using Axum (the Shuttle version to deploy there) and the scraper crate.

Basically, the compiler tells me (with the help from the axum debug_handler macro) that I'm using values that are not "Send", which I can't understand as I'm awaiting for the futures. Can someone help me to understand what I'm doing wrong here?

Follow bellow the compiler messages:

error[E0277]: the trait bound `fn(axum::extract::State<MyState>) -> impl Future<Output = Result<impl IntoResponse, impl IntoResponse>> {scrape}: Handler<_, _>` is not satisfied
   --> src/main.rs:153:31
    |
153 |         .route("/scrape", get(scrape))
    |                           --- ^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(axum::extract::State<MyState>) -> impl Future<Output = Result<impl IntoResponse, impl IntoResponse>> {scrape}`
    |                           |
    |                           required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S>`:
              <axum::handler::Layered<L, H, T, S> as Handler<T, S>>
              <MethodRouter<S> as Handler<(), S>>
note: required by a bound in `axum::routing::get`
   --> /Users/J165437/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.5/src/routing/method_routing.rs:385:1
    |
385 | top_level_handler_fn!(get, GET);
    | ^^^^^^^^^^^^^^^^^^^^^^---^^^^^^
    | |                     |
    | |                     required by a bound in this function
    | required by this bound in `get`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `scrape` is not `Send`
   |
   = help: within `ego_tree::Node<Node>`, the trait `Sync` is not implemented for `Cell<NonZero<usize>>`, which is required by `impl Future<Output = Result<impl IntoResponse, impl IntoResponse>>: Send`
   = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:99:23
   |
90 |     while let (Some(name_element), Some(date_element), Some(children_element)) =
   |                     ------------ has type `ElementRef<'_>` which is not `Send`
...
99 |         match defunto.await {
   |                       ^^^^^ await occurs here, with `name_element` maybe used later
note: required by a bound in `__axum_macros_check_scrape_future::check`
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `scrape` is not `Send`
   |
   = help: within `ego_tree::Node<Node>`, the trait `Sync` is not implemented for `UnsafeCell<tendril::tendril::Buffer>`, which is required by `impl Future<Output = Result<impl IntoResponse, impl IntoResponse>>: Send`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:99:23
   |
90 |     while let (Some(name_element), Some(date_element), Some(children_element)) =
   |                     ------------ has type `ElementRef<'_>` which is not `Send`
...
99 |         match defunto.await {
   |                       ^^^^^ await occurs here, with `name_element` maybe used later
note: required by a bound in `__axum_macros_check_scrape_future::check`
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `scrape` is not `Send`
   |
   = help: within `ego_tree::Node<Node>`, the trait `Sync` is not implemented for `UnsafeCell<std::option::Option<std::option::Option<tendril::tendril::Tendril<tendril::fmt::UTF8>>>>`, which is required by `impl Future<Output = Result<impl IntoResponse, impl IntoResponse>>: Send`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:99:23
   |
90 |     while let (Some(name_element), Some(date_element), Some(children_element)) =
   |                     ------------ has type `ElementRef<'_>` which is not `Send`
...
99 |         match defunto.await {
   |                       ^^^^^ await occurs here, with `name_element` maybe used later
note: required by a bound in `__axum_macros_check_scrape_future::check`
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `scrape` is not `Send`
   |
   = help: within `ego_tree::Node<Node>`, the trait `Sync` is not implemented for `UnsafeCell<std::option::Option<Vec<string_cache::atom::Atom<markup5ever::LocalNameStaticSet>>>>`, which is required by `impl Future<Output = Result<impl IntoResponse, impl IntoResponse>>: Send`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:99:23
   |
90 |     while let (Some(name_element), Some(date_element), Some(children_element)) =
   |                     ------------ has type `ElementRef<'_>` which is not `Send`
...
99 |         match defunto.await {
   |                       ^^^^^ await occurs here, with `name_element` maybe used later
note: required by a bound in `__axum_macros_check_scrape_future::check`
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `scrape` is not `Send`
   |
   = help: within `ego_tree::Node<Node>`, the trait `Sync` is not implemented for `*mut tendril::fmt::UTF8`, which is required by `impl Future<Output = Result<impl IntoResponse, impl IntoResponse>>: Send`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:99:23
   |
90 |     while let (Some(name_element), Some(date_element), Some(children_element)) =
   |                     ------------ has type `ElementRef<'_>` which is not `Send`
...
99 |         match defunto.await {
   |                       ^^^^^ await occurs here, with `name_element` maybe used later
note: required by a bound in `__axum_macros_check_scrape_future::check`
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `scrape` is not `Send`
   |
   = help: within `ego_tree::Node<Node>`, the trait `Sync` is not implemented for `Cell<usize>`, which is required by `impl Future<Output = Result<impl IntoResponse, impl IntoResponse>>: Send`
   = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicUsize` instead
note: future is not `Send` as this value is used across an await
  --> src/main.rs:99:23
   |
90 |     while let (Some(name_element), Some(date_element), Some(children_element)) =
   |                     ------------ has type `ElementRef<'_>` which is not `Send`
...
99 |         match defunto.await {
   |                       ^^^^^ await occurs here, with `name_element` maybe used later
note: required by a bound in `__axum_macros_check_scrape_future::check`
  --> src/main.rs:66:1
   |
66 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `clovis-bevilacqua` (bin "clovis-bevilacqua") due to 7 previous errors

And my full source code:

use std::borrow::Borrow;

use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};

use axum_macros::debug_handler;
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgPool, Pool, Postgres};
use reqwest;
use scraper::{Html, Selector};
use tokio::task;

async fn get_falecido_by_nome_data(
    nome: String,
    date: String,
    pool: &Pool<Postgres>,
) -> Result<Falecido, sqlx::Error> {
    match sqlx::query_as::<_, Falecido>("SELECT id, nome, data, filhos FROM falecidos WHERE nome = $1 AND data = $2")
        .bind(nome)
        .bind(date)
        .fetch_one(pool)
        .await
    {
        Ok(defunto) => Ok(defunto),
        Err(e) => Err(e),
    }
}

async fn retrieve(
    Path(nome): Path<String>,
    Path(date): Path<String>,
    State(state): State<MyState>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    match sqlx::query_as::<_, Falecido>("SELECT id FROM falecidos WHERE nome = $1 AND data = $2")
        .bind(nome)
        .bind(date)
        .fetch_one(&state.pool)
        .await
    {
        Ok(todo) => Ok((StatusCode::OK, Json(todo))),
        Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
    }
}

async fn add(
    State(state): State<MyState>,
    Json(data): Json<FalecidoNew>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    match sqlx::query_as::<_, Falecido>("INSERT INTO falecidos (nome, data, filhos) VALUES ($1, $2, $3) RETURNING id, nome, data, filhos")
        .bind(&data.nome)
        .bind(&data.data)
        .bind(&data.filhos)
        .fetch_one(&state.pool)
        .await
    {
        Ok(todo) => Ok((StatusCode::CREATED, Json(todo))),
        Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
    }
}

#[debug_handler]
async fn scrape(
    State(state): State<MyState>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    let url = "https://setec.sp.gov.br/falecimentos.html";

    let body = task::spawn_blocking(move || {
        reqwest::blocking::get(url).unwrap().text().unwrap()
    }).await.unwrap();

    let document = Html::parse_document(&body);

    // Ajuste os seletores conforme necessário
    let name_selector = Selector::parse("div.falecimento-nome").unwrap();
    let date_selector = Selector::parse("div.falecimento-data").unwrap();
    let children_selector = Selector::parse("div.falecimento-filhos").unwrap();

    let mut names = document.select(&name_selector);
    let mut dates = document.select(&date_selector);
    let mut children = document.select(&children_selector);

    let mut falecidos_adicionados = 0;
    let mut falecidos: Vec<Falecido> = Vec::new();
    
    while let (Some(name_element), Some(date_element), Some(children_element)) =
        (names.next(), dates.next(), children.next())
    {
        let nome = name_element.text().collect::<Vec<_>>().join(" ").clone();
        let data = date_element.text().collect::<Vec<_>>().join(" ").clone();
        let filhos = children_element.text().collect::<Vec<_>>().join(" ").clone();

        // Verifica se já existe um registro com o mesmo nome e data
        let defunto = get_falecido_by_nome_data(nome.to_string(), data.to_string(), state.pool.borrow());
        match defunto.await {
            Ok(_) => {
                println!("Falecido {nome} já cadastrado");
            }
            Err(_) => {
                // Insere o falecido no banco de dados
                match sqlx::query_as::<_, Falecido>("INSERT INTO falecidos (nome, data, filhos) VALUES ($1, $2, $3) RETURNING id, nome, data, filhos")
                    .bind(nome.to_string())
                    .bind(data.to_string())
                    .bind(filhos.to_string())
                    .fetch_one(&state.pool)
                    .await
                    {
                        Ok(defunto) => {
                            falecidos.push(defunto);
                            falecidos_adicionados += 1;
                        },
                        Err(e) => {
                            println!("Erro ao adicionar falecido {0}", e.to_string());
                        },
                    }
                
            }
        }
    }
    if falecidos_adicionados > 0{
        Ok((StatusCode::CREATED, Json(falecidos)))
    }        
    else if falecidos_adicionados == 0 {
        Ok((StatusCode::NO_CONTENT, Json(falecidos)))
    }        
    else{
        let response = format!("Erro ao adicionar falecidos");
        Err((StatusCode::BAD_REQUEST, response.to_string()))
    }
        
}

#[derive(Clone)]
struct MyState {
    pool: PgPool,
}

#[shuttle_runtime::main]
async fn main(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_axum::ShuttleAxum {
    sqlx::migrate!()
        .run(&pool)
        .await
        .expect("Failed to run migrations");

    let state = MyState { pool };
    let router = Router::new()
        .route("/falecidos", post(add))
        .route("/falecidos/:id", get(retrieve))
        .route("/scrape", get(scrape))
        .with_state(state);

    Ok(router.into())
}

#[derive(Deserialize)]
struct FalecidoNew {
    pub nome: String,
    pub data: String,
    pub filhos: String,
}

#[derive(Serialize, FromRow)]
struct Falecido {
    pub id: i32,
    pub nome: String,
    pub data: String,
    pub filhos: String,
}

I appreciate any information that helps me solve this problem.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.