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}