Actix Web - Routes

Boilerplate setup for Actix Web routes

Old Typewritter
Old Typewritter

Note to future self regarding routing set up for Actix Web.

cargo new actix_web_routes
cargo add actix-web env_logger log
cargo add serde --features derive

#![allow(clippy::needless_return)]
use actix_web::{middleware::Logger, web, App, HttpServer};

pub mod routes {

    pub mod things {
        use actix_web::{get, web, HttpResponse, Responder};
        use serde::Deserialize;

        pub const PAGE_DEFAULT: u64 = 1;
        pub const PER_PAGE_DEFAULT: u64 = 10;

        #[derive(Deserialize, Copy, Clone)]
        pub struct ThingsQueryParams {
            pub page: Option<u64>,
            pub per_page: Option<u64>,
        }

        impl ThingsQueryParams {
            pub fn get_pagination(self) -> (u64, u64) {
                let page = self.page.unwrap_or(PAGE_DEFAULT);
                let per_page = self.per_page.unwrap_or(PER_PAGE_DEFAULT);

                (page, per_page)
            }
        }

        #[get("")]
        pub async fn get_things(query: web::Query<ThingsQueryParams>) -> impl Responder {
            let (page, per_page) = query.get_pagination();

            let message = format!("The Query {:?} of things from page:{:?}", per_page, page);

            log::info!("{:?}", message);

            return HttpResponse::Ok().body(message);
        }

        #[get("{id}")]
        pub async fn get_things_id(path: web::Path<u32>) -> impl Responder {
            let thing_id = path.into_inner();
            let message = format!("Geth thing_id:{}", thing_id);

            log::info!("Get things_id:{}", thing_id);

            return HttpResponse::Ok().body(message);
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn test_get_pagination_with_all() {
            let query = things::ThingsQueryParams {
                page: Some(45),
                per_page: Some(54),
            };

            let (page, per_page) = query.get_pagination();

            assert_eq!(45, page);
            assert_eq!(54, per_page);
        }

        #[test]
        fn test_get_pagination_with_page() {
            let query = things::ThingsQueryParams {
                page: Some(45),
                per_page: None,
            };

            let (page, per_page) = query.get_pagination();

            assert_eq!(45, page);
            assert_eq!(things::PER_PAGE_DEFAULT, per_page);
        }

        #[test]
        fn test_get_pagination_with_per_page() {
            let query = things::ThingsQueryParams {
                page: None,
                per_page: Some(54),
            };

            let (page, per_page) = query.get_pagination();

            assert_eq!(things::PAGE_DEFAULT, page);
            assert_eq!(54, per_page);
        }

        #[test]
        fn test_get_pagination_with_none() {
            let query = things::ThingsQueryParams {
                page: None,
                per_page: None,
            };

            let (page, per_page) = query.get_pagination();

            assert_eq!(things::PAGE_DEFAULT, page);
            assert_eq!(things::PER_PAGE_DEFAULT, per_page);
        }
    }
}

pub mod v1_api {
    use crate::routes;
    use actix_web::web;

    pub fn routes(config: &mut web::ServiceConfig) {
        config.service(
            web::scope("/things")
                .service(routes::things::get_things)
                .service(routes::things::get_things_id),
        );
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .service(web::scope("/api/v1").configure(v1_api::routes))
    })
    .bind(("127.0.0.1", 8083))?
    .run()
    .await
}