0

I am working with Rust and Axum on a simple REST API project. What I am trying to accomplish is to merge a route, http verb, and handler together to mimic what controllers in other languages/frameworks do. Everything is looking really good except that when I launch the server and hit the /projects:9090 endpoint, I receive a 404 error rather than a list or empty list. I suspect that my issue is how I am mutating the router instance.

use crate::state::AppState;
use axum::response::IntoResponse;
use axum::Router;
use serde::{Deserialize, Serialize};
mod create;
mod delete;
mod find_one;
mod health_check;
mod list;
mod update;

const HEALTH_CHECK_PATH: &str = "/health_check";
const PROJECTS_PATH: &str = "/projects";
const PROJECTS_NAME_PATH: &str = "/projects/:name";

#[derive(Clone, Debug)]
pub struct Controller {
    pub(crate) router: Router<AppState>,
}

impl Controller {
    pub fn new() -> Controller {
        let controller = Controller {
            router: Router::new(),
        };

        controller
            .create()
            .delete()
            .find_one()
            .health_check()
            .list()
            .update();

        controller
    }
}

and here is the list() method...

use crate::state::AppState;
use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Json;
use axum_macros::debug_handler;
use serde::Deserialize;
use crate::controller::{Controller, PROJECTS_PATH};

impl Controller {
    pub fn list(&self) -> &Controller {
        // #[derive(Debug, Deserialize, Default)]
        // pub struct Pagination {
        //     pub offset: Option<usize>,
        //     pub limit: Option<usize>,
        // }

        #[debug_handler]
        pub async fn handler(
            //pagination: Option<Query<Pagination>>,
            State(state): State<AppState>,
        ) -> impl IntoResponse {
            //let Query(_pagination) = pagination.unwrap_or_default();
            match state.query_port.read().unwrap().list() {
                Ok(res) => (StatusCode::OK, Json(res)).into_response(),
                Err(err) => {
                    (StatusCode::INTERNAL_SERVER_ERROR, Json(err.to_string())).into_response()
                }
            }
        }

        let _ = self.router.clone().route(PROJECTS_PATH, get(handler));
        self
    }
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
A Bit of Help
  • 1,368
  • 19
  • 36
  • 1
    _We_'d really appreciate if you would take your time to create a [**Minimal Reproducible** Example](https://stackoverflow.com/help/minimal-reproducible-example) instead of just dumping part of your code here. But it's not too late to fix! And it's also likely to draw more eyes to your question. – Chayim Friedman Aug 11 '23 at 08:50

1 Answers1

0

When you clone() the router then modify the cloned router then discard it, you did basically nothing. You should mutate the original router.

Now I guess that the reason you cloned the router is because you tried without it and the compiler complained "cannot move out of...". But blindly adding a clone does not always help. Like in this case.

It would help if there would be a route() method that takes &mut self and modifies the router in place. But unfortunately there isn't such method. So for now you can use the techniques mentioned in Temporarily move out of borrowed content, the easiest of which (with no dependencies) is to take() the value (since Router implements Default), modify it, then put it back:

self.router = std::mem::take(&mut self.router).route(PROJECTS_PATH, get(handler));
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77