chuchi/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[macro_use]
5mod macros;
6
7pub mod resources;
8use resources::Resources;
9
10pub mod state;
11
12pub mod routes;
13use routes::{Catcher, ParamsNames, RawRoute, Route, Routes};
14
15#[macro_use]
16pub mod util;
17
18pub mod into;
19use into::IntoRoute;
20
21pub mod error;
22pub use error::{Error, Result};
23
24pub mod extractor;
25pub use extractor::Res;
26
27mod server;
28use server::Server;
29
30mod routing;
31use routing::{RequestConfigs, ServerShared};
32use tracing::info;
33
34#[cfg(feature = "fs")]
35#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
36pub mod fs;
37
38#[cfg(feature = "json")]
39#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
40pub mod json;
41
42#[cfg(feature = "ws")]
43#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
44pub mod ws;
45
46#[cfg(feature = "graphql")]
47#[cfg_attr(docsrs, doc(cfg(feature = "graphql")))]
48pub mod graphql;
49
50#[cfg(feature = "api")]
51#[cfg_attr(docsrs, doc(cfg(feature = "api")))]
52pub mod api;
53
54#[cfg(feature = "json")]
55#[doc(hidden)]
56pub use serde_json;
57
58pub mod service {
59	pub use crate::server::ChuchiService;
60}
61
62use std::any::Any;
63use std::net::SocketAddr;
64use std::sync::Arc;
65use std::time::Duration;
66
67use tokio::net::ToSocketAddrs;
68use tokio::task::JoinHandle;
69
70pub use chuchi_core::{
71	body, header, request, response, Body, Request, Response,
72};
73
74pub use chuchi_codegen::*;
75
76/// Prepares a server.
77pub async fn build(addr: impl ToSocketAddrs) -> Result<Chuchi> {
78	Chuchi::new(addr).await
79}
80
81/// `Chuchi` gathers all materials needed to start a server.
82pub struct Chuchi {
83	addr: SocketAddr,
84	resources: Resources,
85	routes: Routes,
86	configs: RequestConfigs,
87}
88
89impl Chuchi {
90	pub(crate) async fn new<A>(addr: A) -> Result<Self>
91	where
92		A: ToSocketAddrs,
93	{
94		let mut me = Self::new_localhost();
95		me.update_addr(addr).await?;
96
97		Ok(me)
98	}
99
100	/// Creates a `Chuchi` instance with localhost as default address.
101	pub fn new_localhost() -> Self {
102		Self {
103			addr: ([127, 0, 0, 1], 0).into(),
104			resources: Resources::new(),
105			routes: Routes::new(),
106			configs: RequestConfigs::new(),
107		}
108	}
109
110	/// Returns a reference to the current data.
111	pub fn resources(&self) -> &Resources {
112		&self.resources
113	}
114
115	pub fn add_resource<R>(&mut self, resource: R)
116	where
117		R: Any + Send + Sync,
118	{
119		self.resources.insert(resource);
120	}
121
122	/// Adds a `RawRoute` to the chuchi.
123	pub fn add_raw_route<R>(&mut self, route: R)
124	where
125		R: RawRoute + 'static,
126	{
127		let path = route.path();
128		let names = ParamsNames::parse(&path.path);
129		route.validate_requirements(&names, &self.resources);
130		self.routes.push_raw(path, route)
131	}
132
133	/// Adds a `Route` to the chuchi.
134	pub fn add_route<R>(&mut self, route: R)
135	where
136		R: IntoRoute + 'static,
137	{
138		let route = route.into_route();
139		let path = route.path();
140		let names = ParamsNames::parse(&path.path);
141		route.validate_requirements(&names, &self.resources);
142		self.routes.push(path, route)
143	}
144
145	/// Adds a `Catcher` to the chuchi.
146	pub fn add_catcher<C>(&mut self, catcher: C)
147	where
148		C: Catcher + 'static,
149	{
150		catcher.validate_data(&self.resources);
151		self.routes.push_catcher(catcher)
152	}
153
154	/// Sets the request size limit. The default is 4 kilobytes.
155	///
156	/// This can be changed in every Route.
157	///
158	/// ## Panics
159	/// If the size is zero.
160	// todo should this be called `set_request_size_limit`?
161	pub fn request_size_limit(&mut self, size_limit: usize) {
162		self.configs.size_limit(size_limit)
163	}
164
165	/// Sets the request timeout. The default is 60 seconds.
166	///
167	/// This can be changed in every Route.
168	// todo should this be called `set_request_timeout`?
169	pub fn request_timeout(&mut self, timeout: Duration) {
170		self.configs.timeout(timeout)
171	}
172
173	/// Updates the socket address that the server will bind to.
174	///
175	/// This can only be called before the server is built.
176	pub async fn update_addr<A>(&mut self, addr: A) -> Result<()>
177	where
178		A: ToSocketAddrs,
179	{
180		let addr = tokio::net::lookup_host(addr)
181			.await
182			.map_err(Error::from_server_error)?
183			.next()
184			.unwrap();
185		self.addr = addr;
186		Ok(())
187	}
188
189	/// Binds to the address and prepares to serve requests.
190	///
191	/// You need to call run on the `ChuchiServer` so that it starts handling
192	/// requests.
193	pub async fn build(self) -> Result<ChuchiServer> {
194		let wood = Arc::new(ServerShared::new(
195			self.resources,
196			self.routes,
197			self.configs,
198		));
199
200		let server = Server::bind(self.addr, wood.clone()).await?;
201
202		Ok(ChuchiServer {
203			shared: wood,
204			server,
205		})
206	}
207
208	/// Starts the chuchi server.
209	///
210	/// ## Note
211	/// Under normal conditions this function should run forever.
212	pub async fn run(self) -> Result<()> {
213		let server = self.build().await?;
214		server.run().await
215	}
216
217	/// Starts the chuchi server, and spawns it on a new tokio task.
218	///
219	/// ## Note
220	/// Under normal conditions this task should run forever.
221	pub fn run_task(self) -> JoinHandle<()> {
222		tokio::spawn(async move { self.run().await.unwrap() })
223	}
224
225	/// Creates a `ChuchiShared` without starting the server.
226	///
227	/// In most cases you should use `build` and then call `shared` on the `ChuchiServer`.
228	///
229	/// Creating a `ChuchiShared` might be useful for testing or if you want to
230	/// manually create a server.
231	pub fn into_shared(self) -> ChuchiShared {
232		let wood = Arc::new(ServerShared::new(
233			self.resources,
234			self.routes,
235			self.configs,
236		));
237
238		ChuchiShared { inner: wood }
239	}
240}
241
242/// A `Server` which is ready to be started.
243pub struct ChuchiServer {
244	shared: Arc<ServerShared>,
245	server: Server,
246}
247
248impl ChuchiServer {
249	pub fn local_addr(&self) -> Option<SocketAddr> {
250		self.server.local_addr().ok()
251	}
252
253	pub fn shared(&self) -> ChuchiShared {
254		ChuchiShared {
255			inner: self.shared.clone(),
256		}
257	}
258
259	pub async fn run(self) -> Result<()> {
260		info!("Running server on addr: {}", self.local_addr().unwrap());
261
262		#[cfg(any(feature = "http1", feature = "http2"))]
263		{
264			self.server.serve().await
265		}
266
267		#[cfg(not(any(feature = "http1", feature = "http2")))]
268		{
269			panic!("http1 or http2 feature must be enabled")
270		}
271	}
272}
273
274#[derive(Clone)]
275pub struct ChuchiShared {
276	inner: Arc<ServerShared>,
277}
278
279impl ChuchiShared {
280	#[deprecated = "Use `ChuchiShared::resources` instead."]
281	pub fn data(&self) -> &Resources {
282		self.inner.data()
283	}
284
285	/// Returns a reference to the resources.
286	pub fn resources(&self) -> &Resources {
287		self.inner.data()
288	}
289
290	/// Routes the request to normal routes and returns their result.
291	///
292	/// Useful for tests and niche applications.
293	///
294	/// Returns None if no route was found matching the request.
295	pub async fn route(&self, req: &mut Request) -> Option<Result<Response>> {
296		routing::route(&self.inner, req).await
297	}
298}