Strange axum route trait error

in src/controllers/windows.rs:

pub fn router() -> Router {
    Router::new()
        .route("/", post(create_window))
        .route("/:region_country/:position_brand_model", get(get_window))
        .route("/", get(list_windows))
        .route("/", put(update_window))
        .route(
            "/:region_country/:position_brand_model",
            delete(delete_window),
        )
}

i'm getting this error on the "create_window":

the trait bound `fn(AuthenticatedUser, axum::Json<serde_json::Value>) -> impl std::future::Future<Output = Result<(StatusCode, axum::Json<serde_json::Value>), (StatusCode, axum::Json<serde_json::Value>)>> {create_window}: Handler<_, _>` is not satisfied
the following other types implement trait `Handler<T, S>`:
  <MethodRouter<S> as Handler<(), S>>
  <axum::handler::Layered<L, H, T, S> as Handler<T, S>>rustcClick for full compiler diagnostic
windows.rs(286, 21): required by a bound introduced by this call
method_routing.rs(140, 16): required by a bound in `post`
main::controllers::windows
pub async fn create_window(user: AuthenticatedUser, Json(payload): Json<Value>) -> Result<(StatusCode, Json<Value>), (StatusCode, Json<Value>)>
Go to Future | Sized

this is the problematic controller func:

pub async fn create_window(
    user: AuthenticatedUser,
    Json(payload): Json<Value>,
) -> Result<(StatusCode, Json<Value>), (StatusCode, Json<Value>)> {
    auth::check_admin(&user)?;
    let window = match parse_to_window(&payload) {
        Ok(window) => window,
        Err(err) => {
            return Err((
                StatusCode::BAD_REQUEST,
                Json(json!({ "error": format!("Failed to parse payload: {}", err) })),
            ));
        },
    };

    let app_state = app_state::get_app_state().await;
    match app_state.windows_svc.create_window(&window).await {
        Ok(created_window) => Ok((
            StatusCode::CREATED,
            Json(json!({ "message": "Window created", "data": created_window })),
        )),
        Err(e) => Err((
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json!({ "error": format!("Failed to create Window: {}", e) })),
        )),
    }
}

this is my service function:

pub async fn create_window(&self, window: &Window) -> Result<Window, AppError> {
        // Attempt to create the item in the table
        Window::create_item(window, &self.tbls.windows_tbl)
            .await
            .map_err(|e| AppError::GeneralError(e.to_string()))?;

        if let Err(e) = Window::update_brand_counter(
            window.key.partition_key.clone(),
            window.position.clone(),
            window.brand.clone(),
            true,
            &self.tbls.windows_tbl,
        )
        .await
        {
            // If update_brand_counter fails, delete the created window
            if let Err(delete_err) = Window::delete_item(
                &window.key.partition_key,
                &window.position,
                &self.tbls.windows_tbl,
            )
            .await
            {
                return Err(AppError::GeneralError(format!(
                    "Failed to update brand counter and then failed to delete item: {}. Delete error: {}",
                    e, delete_err
                )));
            }

            // Return the error for the failed brand counter update
            return Err(AppError::GeneralError(format!(
                "Failed to update brand counter: {}",
                e
            )));
        }

        Ok(window.clone())
    }

if i comment/remove the entire if block in the service func, the error disappears but obviously I need it because if update_brand_counter() fails, want to delete the newly created window item in dynamo.

here's the signature of the create_item():

pub async fn create_item(&self, tbl_handler: &TblHandler) -> Result<(), Box<dyn Error>> {

and the update_brand_counter():

pub async fn update_brand_counter(
        pk: String,
        position: String,
        name: String,
        is_increase: bool,
        tbl_handler: &TblHandler,
    ) -> Result<(), Box<dyn Error>> {

cargo check:

reigun@Reinaldos-MacBook-Pro carglass-be-rust % cargo check 

error[E0277]: the trait bound `fn(AuthenticatedUser, axum::Json<serde_json::Value>) -> impl std::future::Future<Output = Result<(StatusCode, axum::Json<serde_json::Value>), (StatusCode, axum::Json<serde_json::Value>)>> {create_window}: Handler<_, _>` is not satisfied
   --> src/controllers/windows.rs:286:26
    |
286 |         .route("/", post(create_window))
    |                     ---- ^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(AuthenticatedUser, Json<Value>) -> impl Future<Output = Result<(..., ...), ...>> {create_window}`
    |                     |
    |                     required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S>`:
              <MethodRouter<S> as Handler<(), S>>
              <axum::handler::Layered<L, H, T, S> as Handler<T, S>>
note: required by a bound in `post`
   --> /Users/reigun/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.5/src/routing/method_routing.rs:389:1
    |
389 | top_level_handler_fn!(post, POST);
    | ^^^^^^^^^^^^^^^^^^^^^^----^^^^^^^
    | |                     |
    | |                     required by a bound in this function
    | required by this bound in `post`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
warning: `carglass-be-rust` (bin "main") generated 6 warnings
error: could not compile `carglass-be-rust` (bin "main") due to 1 previous error; 6 warnings emitted

Can you figure out what's wrong? I've tried many things including trying to only use .map_err in the service func (async issues) and breaking up the if into sequential blocks instead of nested.

I've tried asking claude.ai and chatGPT but they both keep telling me to change the AuthenticatedUser implementation (which I can share as well if you'd like, should be pretty standard though)

Any help would be appreciated! Apologies if there's anything wrong with my first post. Please let me know if there's anything wrong with this and I'll edit ASAP

Cheers!

Please share the whole error message you get when you execute cargo check from your command line, IDEs are notorious for removing important aspects from the error message.

done

1 Like

Try #[debug_handler] and look at Debugging handler type errors.

2 Likes

Are you sure AuthenticatedUser is an extractor? I.e. if this is your type, did you implement FromRequest or FromRequestParts for it?

yes:

pub struct AuthenticatedUser {
    pub role: String
}

#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where S: Send+Sync
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
        // Load the .env file
        dotenv().ok();

        // Get the secret key from the environment variable
        let secret_key = env::var("JWT_SECRET").map_err(|_| AuthError::MissingToken)?;

        let auth_header = parts
            .headers
            .get("Authorization")
            .ok_or(AuthError::MissingToken)?
            .to_str()
            .map_err(|_| AuthError::InvalidToken)?;

        if !auth_header.starts_with("Bearer ") {
            return Err(AuthError::InvalidToken);
        }

        let token = auth_header.trim_start_matches("Bearer ");
        let decoding_key = DecodingKey::from_secret(secret_key.as_ref());
        let validation = Validation::new(Algorithm::HS256);

        let token_data = decode::<Claims>(token, &decoding_key, &validation)
            .map_err(|_| AuthError::InvalidToken)?;

        Ok(AuthenticatedUser {
            role: token_data.claims.role
        })
    }
}

everything works fine when I remove that if block inside the service func which is why I don't think there's anything wrong with AuthenticatedUser

This is probably preventing your future from being Send. I would use Box<dyn Error + Send>. Pretty much all errors should be Send already so this should just work. You can also add in Sync.

2 Likes

hi Drewtato, thx for advice. I'm still getting the same error though.

here's the fn you told me to change:

pub async fn create_item(&self, tbl_handler: &TblHandler) -> Result<(), Box<dyn Error + Send + Sync>> {
        let item = self.to_dynamodb_item().map_err(|e| Box::new(AppError::GeneralError(format!("Can't convert window object to dynamodb item: {}", e))) as Box<dyn Error + Send + Sync>)?;
        let condition_expression = "attribute_not_exists(#pk) AND attribute_not_exists(#sk)";
        let expression_attribute_names = HashMap::from([
            ("#pk".to_string(), PK_NAME.to_string()),
            ("#sk".to_string(), SK_NAME.to_string()),
        ]);
        // Send the request to create the item in DynamoDB, only if it doesn't exist
        tbl_handler
            .client
            .put_item()
            .table_name(tbl_handler.table_name.clone())
            .set_item(Some(item))
            .condition_expression(condition_expression)
            .set_expression_attribute_names(Some(expression_attribute_names)) // Set the attribute names
            .send()
            .await
            .map_err(|e| Box::new(AppError::GeneralError(format!("Item already exists: {}", e))) as Box<dyn Error + Send + Sync>)?;
        
        Ok(())
    }

As per chatGPT's advice, I also added the following to AppError:

unsafe impl Send for AppError {}
unsafe impl Sync for AppError {}

i also changed to_dynamodb_item's signature:

pub fn to_dynamodb_item(
        &self,
    ) -> Result<HashMap<String, AttributeValue>, Box<dyn Error + Send + Sync>> {

this has been blocking me so hard and for so long I wouldn't mind just inviting someone to the github repo and getting them to look at the whole app :man_facepalming:

OMG plz ignore my last post because it worked, i forgot to add Send + Sync to update_brand_counter(). Thank you @drewtato!

1 Like