Skip to main content

sod_actix_web/
lib.rs

1//! # sod-actix-web
2//!
3//! This crate provides [`sod::Service`] abstractions around [`actix_web`] via [`Handler`] implementations.
4//!
5//! # Handlers
6//!
7//! The [`ServiceHandler`] acts as an [`actix_web`] [`Handler`], dispatching requests to an underlying
8//! [`sod::AsyncService`] or [`sod::Service`] implementation.
9//!
10//! ## Service I/O
11//!
12//! The input to the underlying [`AsyncService`] is directly compatible with the native [`FromRequest`] trait
13//! in [`actix_web`]. As such, a tuple of [`FromRequest`] impls can be handled as input for an [`AsyncService`].
14//!
15//! The output from the underlying [`AsyncService`] must implement the native [`Responder`] trait from [`actix_web`].
16//! This means that all output type from the service should be compatible with all output types from [`actix_web`].
17//! This should include a simple [`String`] or full [`actix_web::HttpResponse`].
18//!
19//! ## Greet Server Example
20//!
21//! The following example mirrors the default [`actix_web`] greeter example, except it uses the service abstraction
22//! provided by this library:
23//!
24//! ```rust,no_run
25//! use actix_web::{web, App, HttpServer};
26//! use sod::Service;
27//! use sod_actix_web::ServiceHandler;
28//!
29//! #[actix_web::main]
30//! async fn main() -> std::io::Result<()> {
31//!     struct GreetService;
32//!     impl Service for GreetService {
33//!         type Input = web::Path<String>;
34//!         type Output = String;
35//!         type Error = std::convert::Infallible;
36//!         fn process(&self, name: web::Path<String>) -> Result<Self::Output, Self::Error> {
37//!             Ok(format!("Hello {name}!"))
38//!         }
39//!     }
40//!
41//!     HttpServer::new(|| {
42//!         App::new().service(
43//!             web::resource("/greet/{name}").route(web::get().to(ServiceHandler::new(GreetService.into_async()))),
44//!         )
45//!     })
46//!     .bind(("127.0.0.1", 8080))?
47//!     .run()
48//!     .await
49//! }
50//! ```
51//!
52//! ## Math Server Example
53//!
54//! The following example is slightly more advanced, demonstrating how [`AsyncService`] and a tuple of inputs may be used:
55//!
56//! ```rust,no_run
57//! use std::{io::Error, io::ErrorKind};
58//! use actix_web::{web, App, HttpServer};
59//! use serde_derive::Deserialize;
60//! use sod::{async_trait, AsyncService};
61//! use sod_actix_web::ServiceHandler;
62//!
63//! #[actix_web::main]
64//! async fn main() -> std::io::Result<()> {
65//!     #[derive(Debug, Deserialize)]
66//!     pub struct MathParams {
67//!         a: i64,
68//!         b: i64,
69//!     }
70//!
71//!     struct MathService;
72//!     #[async_trait]
73//!     impl AsyncService for MathService {
74//!         type Input = (web::Path<String>, web::Query<MathParams>);
75//!         type Output = String;
76//!         type Error = Error;
77//!         async fn process(
78//!             &self,
79//!             (func, params): (web::Path<String>, web::Query<MathParams>),
80//!         ) -> Result<Self::Output, Self::Error> {
81//!             let value = match func.as_str() {
82//!                 "add" => params.a + params.b,
83//!                 "sub" => params.a - params.b,
84//!                 "mul" => params.a * params.b,
85//!                 "div" => params.a / params.b,
86//!                 _ => return Err(Error::new(ErrorKind::Other, "invalid func")),
87//!             };
88//!             Ok(format!("{value}"))
89//!         }
90//!     }
91//!
92//!     HttpServer::new(|| {
93//!         App::new().service(
94//!             web::resource("/math/{func}").route(web::get().to(ServiceHandler::new(MathService))),
95//!         )
96//!     })
97//!     .bind(("127.0.0.1", 8080))?
98//!     .run()
99//!     .await
100//! }
101//! ```
102//!
103//! # WebSockets
104//!
105//! WebSocket [`sod::Service`] abstractions are provided in the [`ws`] module.
106
107use std::{future::Future, marker::PhantomData, pin::Pin, sync::Arc};
108
109use actix_web::{FromRequest, Handler, Responder, ResponseError};
110use sod::AsyncService;
111
112mod sealed;
113pub mod ws;
114
115/// The highest level abstraction provided by this library. It is used to encapsulate underlying [`sod::Service`]
116/// impls with an [`actix_web`] [`Handler`] that can be natively wired into an Actix [`actix_web::App`].
117///
118/// Input tuples of [`FromRequest`] and outputs of [`Responder`] the responder trait make this directly compatible
119/// with the native Actix request and response types.
120///
121/// See the this module's documentation for details and examples.
122pub struct ServiceHandler<Args, S>
123where
124    Args: FromRequest + 'static,
125    S: AsyncService<Input = Args> + 'static,
126    S::Output: Responder + 'static,
127    S::Error: ResponseError + 'static,
128{
129    service: Arc<S>,
130    _phantom: PhantomData<fn(Args)>,
131}
132impl<Args, S> ServiceHandler<Args, S>
133where
134    Args: FromRequest + 'static,
135    S: AsyncService<Input = Args> + 'static,
136    S::Output: Responder + 'static,
137    S::Error: ResponseError + 'static,
138{
139    /// Encapsulate the given [`sod::AsyncService`] or [`sod::Service`] to be used as an [`actix_web::Handler`]
140    pub fn new(service: S) -> Self {
141        Self {
142            service: Arc::new(service),
143            _phantom: PhantomData,
144        }
145    }
146}
147impl<Args, S> Clone for ServiceHandler<Args, S>
148where
149    Args: FromRequest + 'static,
150    S: AsyncService<Input = Args> + 'static,
151    S::Output: Responder + 'static,
152    S::Error: ResponseError + 'static,
153{
154    fn clone(&self) -> Self {
155        Self {
156            service: Arc::clone(&self.service),
157            _phantom: PhantomData,
158        }
159    }
160}
161impl<Args, S> Handler<Args> for ServiceHandler<Args, S>
162where
163    Args: FromRequest + Send + 'static,
164    S: AsyncService<Input = Args> + Send + Sync + 'static,
165    S::Output: Responder + Send + 'static,
166    S::Error: ResponseError + Send + 'static,
167{
168    type Output = Result<S::Output, S::Error>;
169    type Future = Pin<Box<dyn Future<Output = Result<S::Output, S::Error>> + Send>>;
170    fn call(&self, args: Args) -> Self::Future {
171        let service = Arc::clone(&self.service);
172        Box::pin(async move { service.process(args).await })
173    }
174}