Skip to main content

ohkami/
lib.rs

1/* Run doctest for sample codes in README */
2#![cfg_attr(feature="DEBUG", doc = include_str!("../../README.md"))]
3
4//! <div align="center">
5//!     <h1>Ohkami</h1>
6//!     Ohkami <em>- [狼] wolf in Japanese -</em> is a performant, declarative, and runtime-flexible web framework for Rust.
7//! </div>
8//!
9//! <br>
10//!
11//! - *macro-less and type-safe* APIs for declarative, ergonomic code
12//! - *runtime-flexible* : `tokio`, `smol`, `nio`, `glommio`, `monoio`, `compio` and `worker` (Cloudflare Workers), `lambda` (AWS Lambda)
13//! - good performance, no-network testing, well-structured middlewares, Server-Sent Events, WebSocket, highly integrated OpenAPI document generation, ...
14//!
15//! See [GitHub repo](https://github.com/ohkami-rs/ohkami) for details!
16
17#![cfg_attr(docsrs, feature(doc_cfg))]
18#![allow(incomplete_features)]
19#![cfg_attr(
20    feature = "nightly",
21    feature(specialization, try_trait_v2, impl_trait_in_assoc_type,)
22)]
23#![allow(
24    clippy::non_minimal_cfg, // for `cfg(any(...))` for various `rt_*` features
25    clippy::result_large_err, // TODO: reduce the size of `Response` (and also `Request`) (most parts are their headers!)
26)]
27
28#[cfg(any(
29    all(
30        feature = "rt_tokio",
31        any(
32            feature = "rt_smol",
33            feature = "rt_nio",
34            feature = "rt_glommio",
35            feature = "rt_monoio",
36            feature = "rt_compio",
37            feature = "rt_worker"
38        )
39    ),
40    all(
41        feature = "rt_smol",
42        any(
43            feature = "rt_nio",
44            feature = "rt_glommio",
45            feature = "rt_monoio",
46            feature = "rt_compio",
47            feature = "rt_worker",
48            feature = "rt_tokio"
49        )
50    ),
51    all(
52        feature = "rt_nio",
53        any(
54            feature = "rt_glommio",
55            feature = "rt_monoio",
56            feature = "rt_compio",
57            feature = "rt_worker",
58            feature = "rt_tokio",
59            feature = "rt_smol"
60        )
61    ),
62    all(
63        feature = "rt_glommio",
64        any(
65            feature = "rt_monoio",
66            feature = "rt_compio",
67            feature = "rt_worker",
68            feature = "rt_tokio",
69            feature = "rt_smol",
70            feature = "rt_nio"
71        )
72    ),
73    all(
74        feature = "rt_monoio",
75        any(
76            feature = "rt_compio",
77            feature = "rt_worker",
78            feature = "rt_tokio",
79            feature = "rt_smol",
80            feature = "rt_nio",
81            feature = "rt_glommio"
82        )
83    ),
84    all(
85        feature = "rt_compio",
86        any(
87            feature = "rt_worker",
88            feature = "rt_tokio",
89            feature = "rt_smol",
90            feature = "rt_nio",
91            feature = "rt_glommio",
92            feature = "rt_monoio"
93        )
94    ),
95    all(
96        feature = "rt_worker",
97        any(
98            feature = "rt_tokio",
99            feature = "rt_smol",
100            feature = "rt_nio",
101            feature = "rt_glommio",
102            feature = "rt_monoio",
103            feature = "rt_compio"
104        )
105    ),
106))]
107compile_error! {"
108    Can't activate multiple `rt_*` features at once!
109"}
110
111#[cfg(feature = "__rt_native__")]
112mod __rt__ {
113    #![allow(unused)]
114
115    #[cfg(feature = "__io_futures__")]
116    pub(crate) use futures_util::{AsyncReadExt as AsyncRead, AsyncWriteExt as AsyncWrite};
117    #[cfg(feature = "__io_tokio__")]
118    pub(crate) use tokio::io::{AsyncReadExt as AsyncRead, AsyncWriteExt as AsyncWrite};
119
120    #[cfg(feature = "rt_compio")]
121    pub(crate) use compio::net::{TcpListener, ToSocketAddrsAsync as ToSocketAddrs};
122    #[cfg(feature = "rt_smol")]
123    pub(crate) use smol::net::{AsyncToSocketAddrs as ToSocketAddrs, TcpListener, TcpStream};
124    #[cfg(feature = "rt_tokio")]
125    pub(crate) use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
126    #[cfg(feature = "rt_glommio")]
127    pub(crate) use {
128        glommio::net::{TcpListener, TcpStream},
129        std::net::ToSocketAddrs,
130    };
131    #[cfg(feature = "rt_monoio")]
132    pub(crate) use {
133        monoio::net::TcpListener, monoio_compat::TcpStreamCompat as TcpStream,
134        std::net::ToSocketAddrs,
135    };
136    #[cfg(feature = "rt_nio")]
137    pub(crate) use {
138        nio::net::{TcpListener, TcpStream},
139        std::net::ToSocketAddrs,
140    };
141
142    #[cfg(feature = "rt_compio")]
143    pub(crate) type TcpStream = compio::io::compat::AsyncStream<compio::net::TcpStream>;
144
145    #[cfg(not(feature = "rt_nio"))]
146    #[inline(always)]
147    pub(crate) async fn accept(
148        listener: &TcpListener,
149    ) -> std::io::Result<(TcpStream, std::net::SocketAddr)> {
150        #[cfg(any(feature = "rt_tokio", feature = "rt_smol"))]
151        {
152            listener.accept().await
153        }
154        #[cfg(any(feature = "rt_glommio"))]
155        {
156            let connection = listener.accept().await?;
157            let addr = connection.peer_addr()?;
158            Ok((connection, addr))
159        }
160        #[cfg(any(feature = "rt_monoio", feature = "rt_compio"))]
161        {
162            let (conn, addr) = listener.accept().await?;
163            Ok((TcpStream::new(conn), addr))
164        }
165    }
166
167    pub trait IntoTcpListener<T> {
168        fn into_tcp_listener(self) -> impl Future<Output = TcpListener>;
169    }
170    impl IntoTcpListener<()> for TcpListener {
171        async fn into_tcp_listener(self) -> TcpListener {
172            self
173        }
174    }
175    impl<A: ToSocketAddrs> IntoTcpListener<A> for A {
176        async fn into_tcp_listener(self) -> TcpListener {
177            let binded = TcpListener::bind(self);
178
179            #[cfg(not(any(feature = "rt_glommio", feature = "rt_monoio")))]
180            let binded = binded.await;
181
182            binded.expect("Failed to bind TCP listener")
183        }
184    }
185
186    #[cfg(feature = "rt_tokio")]
187    pub(crate) use tokio::time::sleep;
188    #[cfg(feature = "rt_smol")]
189    pub(crate) async fn sleep(duration: std::time::Duration) {
190        smol::Timer::after(duration).await;
191    }
192    #[cfg(feature = "rt_compio")]
193    pub(crate) use compio::runtime::time::sleep;
194    #[cfg(feature = "rt_glommio")]
195    pub(crate) use glommio::timer::sleep;
196    #[cfg(feature = "rt_monoio")]
197    pub(crate) use monoio::time::sleep;
198    #[cfg(feature = "rt_nio")]
199    pub(crate) use nio::sleep;
200
201    #[cfg(feature = "__rt_threaded__")]
202    mod task {
203        pub trait Task: std::future::Future<Output: Send + 'static> + Send + 'static {}
204        impl<F: std::future::Future<Output: Send + 'static> + Send + 'static> Task for F {}
205    }
206    #[cfg(not(feature = "__rt_threaded__"))]
207    mod task {
208        pub trait Task: std::future::Future + 'static {}
209        impl<F: std::future::Future + 'static> Task for F {}
210    }
211
212    pub(crate) fn spawn(task: impl task::Task) {
213        #[cfg(feature = "rt_tokio")]
214        tokio::task::spawn(task);
215
216        #[cfg(feature = "rt_smol")]
217        smol::spawn(task).detach();
218
219        #[cfg(feature = "rt_glommio")]
220        glommio::spawn_local(task).detach();
221
222        #[cfg(feature = "rt_monoio")]
223        monoio::spawn(task);
224
225        #[cfg(feature = "rt_compio")]
226        compio::runtime::spawn(task).detach();
227    }
228
229    #[cfg(test)]
230    pub(crate) mod testing {
231        pub(crate) fn block_on<F: Future>(future: F) -> F::Output {
232            #[cfg(feature = "rt_tokio")]
233            return tokio::runtime::Builder::new_current_thread()
234                .enable_all()
235                .build()
236                .unwrap()
237                .block_on(future);
238
239            #[cfg(feature = "rt_smol")]
240            return smol::block_on(future);
241
242            #[cfg(feature = "rt_nio")]
243            return nio::RuntimeBuilder::new().build().unwrap().block_on(future);
244
245            #[cfg(feature = "rt_glommio")]
246            return glommio::LocalExecutor::default().run(future);
247
248            #[cfg(feature = "rt_monoio")]
249            return monoio::RuntimeBuilder::<monoio::FusionDriver>::new()
250                .enable_all()
251                .build()
252                .unwrap()
253                .block_on(future);
254
255            #[cfg(feature = "rt_compio")]
256            return compio::runtime::Runtime::new().unwrap().block_on(future);
257        }
258
259        pub(crate) const PORT: u16 = {
260            #[cfg(feature = "rt_tokio")]
261            {
262                3001
263            }
264            #[cfg(feature = "rt_smol")]
265            {
266                3003
267            }
268            #[cfg(feature = "rt_nio")]
269            {
270                3004
271            }
272            #[cfg(feature = "rt_glommio")]
273            {
274                3005
275            }
276            #[cfg(feature = "rt_monoio")]
277            {
278                3006
279            }
280            #[cfg(feature = "rt_compio")]
281            {
282                3007
283            }
284        };
285    }
286}
287
288pub mod util;
289
290mod config;
291pub use config::Config;
292
293#[cfg(debug_assertions)]
294#[cfg(feature = "__rt__")]
295pub mod testing;
296
297pub mod request;
298pub use ::ohkami_macros::FromRequest;
299pub use request::{FromRequest, Method, Request};
300
301mod response;
302pub use response::{IntoResponse, Response, Status};
303
304#[cfg(feature = "__rt_native__")]
305mod session;
306
307#[cfg(feature = "__rt__")]
308mod router;
309
310#[cfg(feature = "__rt__")]
311mod ohkami;
312#[cfg(feature = "__rt__")]
313pub use ohkami::{Ohkami, Route};
314
315pub mod fang;
316pub use fang::{Fang, FangAction, FangProc, handler};
317
318pub mod header;
319
320pub mod claw;
321pub use claw::{Cookie, Json, Path, Query};
322
323#[cfg(feature = "sse")]
324#[cfg_attr(docsrs, doc(cfg(feature = "sse")))]
325pub mod sse;
326
327#[cfg(feature = "ws")]
328#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
329pub mod ws;
330
331#[cfg(feature = "rt_lambda")]
332mod x_lambda;
333/* TODO
334#[cfg(feature="rt_lambda")]
335pub use x_lambda::*;
336*/
337
338#[cfg(feature = "rt_worker")]
339mod x_worker;
340#[cfg(feature = "rt_worker")]
341pub use x_worker::*;
342
343pub mod prelude {
344    pub use crate::fang::Context;
345    pub use crate::serde::{Deserialize, Serialize};
346    pub use crate::util::FangAction;
347    pub use crate::{IntoResponse, Json, Method, Path, Query, Request, Response, Status};
348
349    #[cfg(feature = "__rt__")]
350    pub use crate::{Ohkami, Route};
351}
352
353/// Somthing almost [serde](https://crates.io/crates/serde) + [serde_json](https://crates.io/crates/serde_json).
354///
355/// ---
356/// *not_need_serde_in_your_dependencies.rs*
357/// ```
358/// use ohkami::serde::{json, Serialize};
359///
360/// #[derive(Serialize)]
361/// struct User {
362///     #[serde(rename = "username")]
363///     name: String,
364///     age:  u8,
365/// }
366///
367/// # fn _user() {
368/// let user = User {
369///     name: String::from("ABC"),
370///     age:  200,
371/// };
372/// assert_eq!(json::to_string(&user).unwrap(), r#"
373///     {"age":200,"username":"ABC"}
374/// "#);
375/// # }
376/// ```
377/// ---
378pub mod serde {
379    pub use ::ohkami_macros::{Deserialize, Serialize};
380    pub use ::serde::de::{self, Deserialize, Deserializer};
381    pub use ::serde::ser::{self, Serialize, Serializer};
382    pub use ::serde_json as json;
383}
384
385#[cfg(feature = "openapi")]
386#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
387/// # Highly integrated OpenAPI support for Ohkami
388pub mod openapi {
389    pub use ::ohkami_macros::{Schema, operation};
390    pub use ::ohkami_openapi::document::Server;
391    pub use ::ohkami_openapi::*;
392
393    #[cfg(feature = "__rt__")]
394    #[derive(Clone)]
395    pub struct OpenAPI<'s> {
396        pub title: &'static str,
397        pub version: &'static str,
398        pub servers: &'s [Server],
399    }
400
401    /// A fang to set OpenAPI tag for handlers of a `Ohkami`
402    ///
403    /// ## note
404    ///
405    /// * OpenAPI tags are inherited and stacked for child `Ohkami`s (if any).
406    /// * This is a fang, but introduces NO runtime overhead.
407    ///
408    /// ## example
409    ///
410    /// ```ignore
411    /// use ohkami::prelude::*;
412    /// use ohkami::openapi;
413    ///
414    /// #[tokio::main]
415    /// async fn main() {
416    ///     let users_ohkami = Ohkami::new((
417    ///         openapi::Tag("users"),
418    ///         "/"
419    ///             .GET(list_users),
420    ///             .POST(create_user),
421    ///         "/:id"
422    ///             .GET(get_user_profile),
423    ///     ));
424    ///
425    ///     Ohkami::new((
426    ///         "/users".By(users_ohkami),
427    ///
428    ///         // ...
429    ///     )).howl("localhost:5050").await
430    /// }
431    /// # async fn list_users() {}
432    /// # async fn create_user() {}
433    /// # async fn get_user_profile() {}
434    /// ```
435    pub struct Tag(pub &'static str);
436    impl<I: crate::FangProc> crate::Fang<I> for Tag {
437        /// just pass `inner` through
438        type Proc = I;
439        fn chain(&self, inner: I) -> Self::Proc {
440            inner
441        }
442
443        /// add tag for operations of `Ohkami` having this `Tag`
444        fn openapi_map_operation(&self, operation: Operation) -> Operation {
445            operation.with_tag(self.0)
446        }
447    }
448}
449
450#[doc(hidden)]
451pub mod __internal__ {
452    pub use ::serde;
453
454    pub use ohkami_macros::consume_struct;
455
456    pub use crate::fang::Fangs;
457
458    /* for benchmarks */
459    #[cfg(feature = "DEBUG")]
460    pub use crate::{
461        request::{RequestHeader, RequestHeaders},
462        response::{ResponseHeader, ResponseHeaders},
463    };
464}