use actix_web::{ body::MessageBody, dev::{ServiceFactory, ServiceRequest, ServiceResponse}, get, guard::{Guard, GuardContext}, middleware::DefaultHeaders, web, App, Error, HttpServer, Responder, }; mod v1 { use super::*; pub struct ApiGuard; impl Guard for ApiGuard { fn check(&self, ctx: &GuardContext<'_>) -> bool { ctx.head() .headers() .get("Accept-Version") .map_or(false, |hv| hv.as_bytes() == b"1") } } #[get("/hello")] pub async fn hello() -> impl Responder { "Hello World from v1 API!" } } mod v2 { use super::*; pub struct ApiGuard; impl Guard for ApiGuard { fn check(&self, ctx: &GuardContext<'_>) -> bool { ctx.head() .headers() .get("Accept-Version") .map_or(false, |hv| hv.as_bytes() == b"2") } } #[get("/hello")] pub async fn hello() -> impl Responder { "Hello World from the awesome new v2 API!" } } fn create_app() -> App< impl ServiceFactory< ServiceRequest, Response = ServiceResponse, Config = (), InitError = (), Error = Error, >, > { App::new() .service(web::scope("/api").guard(v1::ApiGuard).service(v1::hello)) .service(web::scope("/api").guard(v2::ApiGuard).service(v2::hello)) // using this form of API version selection means that we need to send a Vary header so that // caches won't try to serve the wrong response .wrap(DefaultHeaders::new().add(("Vary", "Accept-Version"))) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(create_app) .bind(("127.0.0.1", 8080))? .run() .await } #[cfg(test)] mod tests { use actix_web::test::{self, TestRequest}; use super::*; #[actix_web::test] async fn api_versioning() { let app = test::init_service(create_app()).await; let req = TestRequest::with_uri("/api/hello").insert_header(("Accept-Version", "1")); let res = test::call_and_read_body(&app, req.to_request()).await; assert_eq!(res, "Hello World from v1 API!"); let req = TestRequest::with_uri("/api/hello").insert_header(("Accept-Version", "2")); let res = test::call_and_read_body(&app, req.to_request()).await; assert_eq!(res, "Hello World from the awesome new v2 API!"); } }