, QueryRejection> =
355 | Query::from_request_parts(parts, &()).await;
356 | if let Ok(params) = result_query {
357 | match params {
358 | Query(parse_params) => {
359 | query_map = parse_params;
360 | }
361 | }
362 | }
363 |
364 | Ok(ContextPart {
365 | version,
366 | connect_info,
367 | headers,
368 | method,
369 | uri,
370 | inner_state,
371 | params_map,
372 | query_map,
373 | })
374 | }
375 | }
376 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Graphul is an Express inspired web framework using a powerful extractor system. Designed to improve, speed, and scale your microservices with a friendly syntax, Graphul is built with Rust. that means Graphul gets memory safety, reliability, concurrency, and performance for free. helping to save money on infrastructure.
10 |
11 |
12 |
13 | ## [Buy a Coffee with Bitcoin ☕️](https://github.com/graphul-rs/graphul/blob/main/BUY-A-COFFEE.md)
14 |
15 | [](https://discord.gg/3WCMgT3KCS)
16 | Join our Discord server to chat with others in the Graphul community!
17 |
18 | ## Install
19 |
20 | #### Create a new project
21 | ```
22 | $ cargo init hello-app
23 |
24 | $ cd hello-app
25 | ```
26 |
27 |
28 | #### Add graphul dependency
29 |
30 | ```
31 | $ cargo add graphul
32 | ```
33 |
34 | ## ⚡️ Quickstart
35 |
36 | ```rust
37 | use graphul::{Graphul, http::Methods};
38 |
39 |
40 | #[tokio::main]
41 | async fn main() {
42 | let mut app = Graphul::new();
43 |
44 | app.get("/", || async {
45 | "Hello, World 👋!"
46 | });
47 |
48 | app.run("127.0.0.1:8000").await;
49 | }
50 | ```
51 |
52 | ## 👀 Examples
53 |
54 | Listed below are some of the common examples. If you want to see more code examples , please visit our [Examples Folder](https://github.com/graphul-rs/graphul/tree/main/examples)
55 |
56 | ## common examples
57 |
58 | - [Context](#-context)
59 | - [JSON](#-json)
60 | - [Resource](#-resource)
61 | - [Static files](#-static-files)
62 | - [Groups](#-groups)
63 | - [Share state](#-share-state)
64 | - [Share state with Resource](#-share-state-with-resource)
65 | - [Middleware](#-middleware)
66 | - [Routers](#-routers)
67 | - [Templates](#-templates)
68 | - [Swagger - OpenAPI](https://github.com/graphul-rs/graphul/tree/main/examples/utoipa-swagger-ui)
69 | - ⭐️ help us by adding a star on [GitHub Star](https://github.com/graphul-rs/graphul/stargazers) to the project
70 |
71 | ## 🛫 Graphul vs most famous frameworks out there
72 |
73 |
74 |
75 | ## 📖 Context
76 |
77 | ```rust
78 | use graphul::{http::Methods, Context, Graphul};
79 |
80 | #[tokio::main]
81 | async fn main() {
82 | let mut app = Graphul::new();
83 |
84 | // /samuel?country=Colombia
85 | app.get("/:name", |c: Context| async move {
86 | /*
87 | statically typed query param extraction
88 | let value: Json = match c.parse_params().await
89 | let value: Json = match c.parse_query().await
90 | */
91 |
92 | let name = c.params("name");
93 | let country = c.query("country");
94 | let ip = c.ip();
95 |
96 | format!("My name is {name}, I'm from {country}, my IP is {ip}",)
97 | });
98 |
99 | app.run("127.0.0.1:8000").await;
100 | }
101 | ```
102 |
103 | ## 📖 JSON
104 |
105 | ```rust
106 | use graphul::{Graphul, http::Methods, extract::Json};
107 | use serde_json::json;
108 |
109 | #[tokio::main]
110 | async fn main() {
111 | let mut app = Graphul::new();
112 |
113 | app.get("/", || async {
114 | Json(json!({
115 | "name": "full_name",
116 | "age": 98,
117 | "phones": [
118 | format!("+44 {}", 8)
119 | ]
120 | }))
121 | });
122 |
123 | app.run("127.0.0.1:8000").await;
124 | }
125 | ```
126 |
127 | ## 📖 Resource
128 |
129 | ```rust
130 | use std::collections::HashMap;
131 |
132 | use graphul::{
133 | async_trait,
134 | extract::Json,
135 | http::{resource::Resource, response::Response, StatusCode},
136 | Context, Graphul, IntoResponse,
137 | };
138 | use serde_json::json;
139 |
140 | type ResValue = HashMap;
141 |
142 | struct Article;
143 |
144 | #[async_trait]
145 | impl Resource for Article {
146 | async fn get(c: Context) -> Response {
147 | let posts = json!({
148 | "posts": ["Article 1", "Article 2", "Article ..."]
149 | });
150 | (StatusCode::OK, c.json(posts)).into_response()
151 | }
152 |
153 | async fn post(c: Context) -> Response {
154 | // you can use ctx.parse_params() or ctx.parse_query()
155 | let value: Json = match c.payload().await {
156 | Ok(data) => data,
157 | Err(err) => return err.into_response(),
158 | };
159 |
160 | (StatusCode::CREATED, value).into_response()
161 | }
162 |
163 | // you can use put, delete, head, patch and trace
164 | }
165 |
166 | #[tokio::main]
167 | async fn main() {
168 | let mut app = Graphul::new();
169 |
170 | app.resource("/article", Article);
171 |
172 | app.run("127.0.0.1:8000").await;
173 | }
174 | ```
175 |
176 | ## 📖 Static files
177 |
178 | ```rust
179 | use graphul::{Graphul, FolderConfig, FileConfig};
180 |
181 | #[tokio::main]
182 | async fn main() {
183 | let mut app = Graphul::new();
184 |
185 | // path = "/static", dir = public
186 | app.static_files("/static", "public", FolderConfig::default());
187 |
188 | // single page application
189 | app.static_files("/", "app/build", FolderConfig::spa());
190 |
191 | app.static_file("/about", "templates/about.html", FileConfig::default());
192 |
193 | app.run("127.0.0.1:8000").await;
194 | }
195 | ```
196 |
197 | ### 🌟 static files with custom config
198 |
199 | ```rust
200 | use graphul::{Graphul, FolderConfig, FileConfig};
201 |
202 | #[tokio::main]
203 | async fn main() {
204 | let mut app = Graphul::new();
205 |
206 | app.static_files("/", "templates", FolderConfig {
207 | // single page application
208 | spa: false,
209 | // it support gzip, brotli and deflate
210 | compress: true,
211 | // Set a specific read buffer chunk size.
212 | // The default capacity is 64kb.
213 | chunk_size: None,
214 | // If the requested path is a directory append `index.html`.
215 | // This is useful for static sites.
216 | index: true,
217 | // fallback - This file will be called if there is no file at the path of the request.
218 | not_found: Some("templates/404.html"), // or None
219 | });
220 |
221 | app.static_file("/path", "templates/about.html", FileConfig {
222 | // it support gzip, brotli and deflate
223 | compress: true,
224 | chunk_size: Some(65536) // buffer capacity 64KiB
225 | });
226 |
227 | app.run("127.0.0.1:8000").await;
228 | }
229 | ```
230 |
231 | ## 📖 Groups
232 |
233 |
234 | ```rust
235 | use graphul::{
236 | extract::{Path, Json},
237 | Graphul,
238 | http::{ Methods, StatusCode }, IntoResponse
239 | };
240 |
241 | use serde_json::json;
242 |
243 | async fn index() -> &'static str {
244 | "index handler"
245 | }
246 |
247 | async fn name(Path(name): Path) -> impl IntoResponse {
248 | let user = json!({
249 | "response": format!("my name is {}", name)
250 | });
251 | (StatusCode::CREATED, Json(user)).into_response()
252 | }
253 |
254 | #[tokio::main]
255 | async fn main() {
256 | let mut app = Graphul::new();
257 |
258 | // GROUP /api
259 | let mut api = app.group("api");
260 |
261 | // GROUP /api/user
262 | let mut user = api.group("user");
263 |
264 | // GET POST PUT DELETE ... /api/user
265 | user.resource("/", Article);
266 |
267 | // GET /api/user/samuel
268 | user.get("/:name", name);
269 |
270 | // GROUP /api/post
271 | let mut post = api.group("post");
272 |
273 | // GET /api/post
274 | post.get("/", index);
275 |
276 | // GET /api/post/all
277 | post.get("/all", || async move {
278 | Json(json!({"message": "hello world!"}))
279 | });
280 |
281 | app.run("127.0.0.1:8000").await;
282 | }
283 | ```
284 |
285 | ## 📖 Share state
286 |
287 | ```rust
288 | use graphul::{http::Methods, extract::State, Graphul};
289 |
290 | #[derive(Clone)]
291 | struct AppState {
292 | data: String
293 | }
294 |
295 | #[tokio::main]
296 | async fn main() {
297 | let state = AppState { data: "Hello, World 👋!".to_string() };
298 | let mut app = Graphul::share_state(state);
299 |
300 | app.get("/", |State(state): State| async {
301 | state.data
302 | });
303 |
304 | app.run("127.0.0.1:8000").await;
305 | }
306 | ```
307 |
308 | ## 📖 Share state with Resource
309 |
310 | ```rust
311 | use graphul::{
312 | async_trait,
313 | http::{resource::Resource, response::Response, StatusCode},
314 | Context, Graphul, IntoResponse,
315 | };
316 | use serde_json::json;
317 |
318 | struct Article;
319 |
320 | #[derive(Clone)]
321 | struct AppState {
322 | data: Vec<&'static str>,
323 | }
324 |
325 | #[async_trait]
326 | impl Resource for Article {
327 |
328 | async fn get(ctx: Context) -> Response {
329 | let article = ctx.state();
330 |
331 | let posts = json!({
332 | "posts": article.data,
333 | });
334 | (StatusCode::OK, ctx.json(posts)).into_response()
335 | }
336 |
337 | // you can use post, put, delete, head, patch and trace
338 |
339 | }
340 |
341 | #[tokio::main]
342 | async fn main() {
343 | let state = AppState {
344 | data: vec!["Article 1", "Article 2", "Article 3"],
345 | };
346 | let mut app = Graphul::share_state(state);
347 |
348 | app.resource("/article", Article);
349 |
350 | app.run("127.0.0.1:8000").await;
351 | }
352 | ```
353 |
354 | ## 📖 Middleware
355 |
356 | - [Example using tracing](https://github.com/graphul-rs/graphul/tree/main/examples/tracing-middleware)
357 |
358 | ```rust
359 | use graphul::{
360 | Req,
361 | middleware::{self, Next},
362 | http::{response::Response,Methods},
363 | Graphul
364 | };
365 |
366 | async fn my_middleware( request: Req, next: Next ) -> Response {
367 |
368 | // your logic
369 |
370 | next.run(request).await
371 | }
372 |
373 | #[tokio::main]
374 | async fn main() {
375 | let mut app = Graphul::new();
376 |
377 | app.get("/", || async {
378 | "hello world!"
379 | });
380 | app.middleware(middleware::from_fn(my_middleware));
381 |
382 | app.run("127.0.0.1:8000").await;
383 | }
384 | ```
385 |
386 | ## 📖 Routers
387 |
388 | - [Example Multiple Routers](https://github.com/graphul-rs/graphul/tree/main/examples/multiple-routers)
389 |
390 | ```rust
391 | use graphul::{http::Methods, Graphul};
392 |
393 | #[tokio::main]
394 | async fn main() {
395 | let mut app = Graphul::new();
396 |
397 | app.get("/", || async { "Home" });
398 |
399 | // you can use: Graphul::post, Graphul::put, Graphul::delete, Graphul::patch
400 | let route_get = Graphul::get("/hello", || async { "Hello, World 👋!" });
401 |
402 | // you can also use the `route` variable to add the route to the app
403 | app.add_router(route_get);
404 |
405 | app.run("127.0.0.1:8000").await;
406 | ```
407 |
408 | #### 💡 Graphul::router
409 |
410 | ```rust
411 | use graphul::{
412 | Req,
413 | middleware::{self, Next},
414 | http::{response::Response,Methods},
415 | Graphul
416 | };
417 |
418 | async fn my_router() -> Graphul {
419 | let mut router = Graphul::router();
420 |
421 | router.get("/hi", || async {
422 | "Hey! :)"
423 | });
424 | // this middleware will be available only on this router
425 | router.middleware(middleware::from_fn(my_middleware));
426 |
427 | router
428 | }
429 |
430 | #[tokio::main]
431 | async fn main() {
432 | let mut app = Graphul::new();
433 |
434 | app.get("/", || async {
435 | "hello world!"
436 | });
437 |
438 | app.add_router(my_router().await);
439 |
440 | app.run("127.0.0.1:8000").await;
441 | }
442 | ```
443 |
444 | ## 📖 Templates
445 |
446 | ```rust
447 | use graphul::{
448 | http::Methods,
449 | Context, Graphul, template::HtmlTemplate,
450 | };
451 | use askama::Template;
452 |
453 | #[derive(Template)]
454 | #[template(path = "hello.html")]
455 | struct HelloTemplate {
456 | name: String,
457 | }
458 |
459 | #[tokio::main]
460 | async fn main() {
461 | let mut app = Graphul::new();
462 |
463 | app.get("/:name", |c: Context| async move {
464 | let template = HelloTemplate { name: c.params("name") };
465 | HtmlTemplate(template)
466 | });
467 |
468 | app.run("127.0.0.1:8000").await;
469 | }
470 | ```
471 |
472 | ## License
473 |
474 | This project is licensed under the [MIT license](https://github.com/graphul-rs/graphul/blob/main/LICENSE).
475 |
476 | ### Contribution
477 |
478 | Unless you explicitly state otherwise, any contribution intentionally submitted
479 | for inclusion in `Graphul` by you, shall be licensed as MIT, without any
480 | additional terms or conditions.
481 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //pub mod types;
2 |
3 | //use types::*;
4 |
5 | mod app;
6 | mod color;
7 | mod fs;
8 | pub mod http;
9 | mod listen;
10 | pub mod middleware;
11 | pub mod template;
12 |
13 | use std::convert::Infallible;
14 | use std::io;
15 | use std::net::SocketAddr;
16 |
17 | pub use async_trait::async_trait;
18 |
19 | pub use axum::extract;
20 | use axum::handler::Handler;
21 | pub use axum::response::IntoResponse;
22 | use axum::routing::{delete, get, get_service, head, options, patch, post, put, trace, Route};
23 | use axum::Router;
24 |
25 | pub use http::request::Context;
26 | pub use http::request::ContextPart;
27 | use http::resource::Resource;
28 | use http::StatusCode;
29 | use hyper::service::Service;
30 | use hyper::Request;
31 | use tower_http::services::{ServeDir, ServeFile};
32 | use tower_http::set_status::SetStatus;
33 | use tower_layer::Layer;
34 |
35 | use tower::ServiceExt;
36 |
37 | pub type FolderConfig = fs::FolderConfig;
38 | pub type FileConfig = fs::FileConfig;
39 |
40 | pub type Body = axum::body::Body;
41 |
42 | pub type Req = axum::http::Request;
43 |
44 | pub struct Group<'a, S = ()> {
45 | app: &'a mut Graphul,
46 | prefix: String,
47 | }
48 |
49 | impl<'a, S> Group<'a, S>
50 | where
51 | S: Clone + Send + Sync + 'static,
52 | {
53 | pub fn resource(&mut self, path: &str, res: impl Resource + 'static) {
54 | let route_name = self.get_route_name(path);
55 | self.app.resource(route_name.as_str(), res);
56 | }
57 | }
58 |
59 | impl<'a, S> http::Methods for Group<'a, S>
60 | where
61 | S: Clone + Send + Sync + 'static,
62 | {
63 | fn post(&mut self, path: &str, handler: H)
64 | where
65 | H: Handler,
66 | T: 'static,
67 | {
68 | let route_name = self.get_route_name(path);
69 | self.app.post(route_name.as_str(), handler);
70 | }
71 |
72 | fn get(&mut self, path: &str, handler: H)
73 | where
74 | H: Handler,
75 | T: 'static,
76 | {
77 | let route_name = self.get_route_name(path);
78 | self.app.get(route_name.as_str(), handler);
79 | }
80 | fn put(&mut self, path: &str, handler: H)
81 | where
82 | H: Handler,
83 | T: 'static,
84 | {
85 | let route_name = self.get_route_name(path);
86 | self.app.put(route_name.as_str(), handler);
87 | }
88 |
89 | fn delete(&mut self, path: &str, handler: H)
90 | where
91 | H: Handler,
92 | T: 'static,
93 | {
94 | let route_name = self.get_route_name(path);
95 | self.app.delete(route_name.as_str(), handler);
96 | }
97 | fn head(&mut self, path: &str, handler: H)
98 | where
99 | H: Handler,
100 | T: 'static,
101 | {
102 | let route_name = self.get_route_name(path);
103 | self.app.head(route_name.as_str(), handler);
104 | }
105 |
106 | fn options(&mut self, path: &str, handler: H)
107 | where
108 | H: Handler,
109 | T: 'static,
110 | {
111 | let route_name = self.get_route_name(path);
112 | self.app.options(route_name.as_str(), handler);
113 | }
114 | fn patch(&mut self, path: &str, handler: H)
115 | where
116 | H: Handler,
117 | T: 'static,
118 | {
119 | let route_name = self.get_route_name(path);
120 | self.app.patch(route_name.as_str(), handler);
121 | }
122 |
123 | fn trace(&mut self, path: &str, handler: H)
124 | where
125 | H: Handler,
126 | T: 'static,
127 | {
128 | let route_name = self.get_route_name(path);
129 | self.app.trace(route_name.as_str(), handler);
130 | }
131 | }
132 |
133 | impl<'a, S> Group<'a, S>
134 | where
135 | S: Clone + Send + Sync + 'static,
136 | {
137 | fn new(app: &'a mut Graphul, prefix: &str) -> Self {
138 | Group {
139 | app,
140 | prefix: prefix.to_string(),
141 | }
142 | }
143 |
144 | fn get_route_name(&self, name: &str) -> String {
145 | if name == "/" {
146 | return self.prefix.clone();
147 | }
148 | format!("{}{}", self.prefix, name)
149 | }
150 |
151 | pub fn group(&mut self, name: &str) -> Group {
152 | self.app
153 | .group(format!("/{}/{}", self.prefix, name).as_str())
154 | }
155 | }
156 |
157 | #[derive(Debug)]
158 | pub struct Graphul {
159 | routes: Router,
160 | count_routes: usize,
161 | route_list: Vec,
162 | state: S,
163 | }
164 |
165 | impl Graphul
166 | where
167 | S: Clone + Send + Sync + 'static,
168 | {
169 | pub fn resource + 'static>(&mut self, path: &str, _res: T) {
170 | // get
171 | self.increase_route_counter(path.into());
172 | self.routes = self.routes.clone().route(path, get(T::get));
173 |
174 | // post
175 | self.increase_route_counter(path.into());
176 | self.routes = self.routes.clone().route(path, post(T::post));
177 |
178 | // put
179 | self.increase_route_counter(path.into());
180 | self.routes = self.routes.clone().route(path, put(T::put));
181 |
182 | // delete
183 | self.increase_route_counter(path.into());
184 | self.routes = self.routes.clone().route(path, delete(T::delete));
185 |
186 | // patch
187 | self.increase_route_counter(path.into());
188 | self.routes = self.routes.clone().route(path, patch(T::patch));
189 |
190 | // options
191 | self.increase_route_counter(path.into());
192 | self.routes = self.routes.clone().route(path, options(T::options));
193 |
194 | // trace
195 | self.increase_route_counter(path.into());
196 | self.routes = self.routes.clone().route(path, trace(T::trace));
197 |
198 | // head
199 | self.increase_route_counter(path.into());
200 | self.routes = self.routes.clone().route(path, head(T::head));
201 | }
202 | }
203 |
204 | impl http::Methods for Graphul
205 | where
206 | S: Clone + Send + Sync + 'static,
207 | {
208 | fn get(&mut self, path: &str, handler: H)
209 | where
210 | H: Handler,
211 | T: 'static,
212 | {
213 | self.increase_route_counter(format!("GET {path}"));
214 | self.routes = self.routes.clone().route(path, get(handler));
215 | }
216 | fn post(&mut self, path: &str, handler: H)
217 | where
218 | H: Handler,
219 | T: 'static,
220 | {
221 | self.increase_route_counter(format!("POST {path}"));
222 | self.routes = self.routes.clone().route(path, post(handler));
223 | }
224 | fn put(&mut self, path: &str, handler: H)
225 | where
226 | H: Handler,
227 | T: 'static,
228 | {
229 | self.increase_route_counter(format!("PUT {path}"));
230 | self.routes = self.routes.clone().route(path, put(handler));
231 | }
232 | fn delete(&mut self, path: &str, handler: H)
233 | where
234 | H: Handler,
235 | T: 'static,
236 | {
237 | self.increase_route_counter(format!("DELETE {path}"));
238 | self.routes = self.routes.clone().route(path, delete(handler));
239 | }
240 | fn head(&mut self, path: &str, handler: H)
241 | where
242 | H: Handler,
243 | T: 'static,
244 | {
245 | self.increase_route_counter(format!("HEAD {path}"));
246 | self.routes = self.routes.clone().route(path, head(handler));
247 | }
248 | fn options(&mut self, path: &str, handler: H)
249 | where
250 | H: Handler,
251 | T: 'static,
252 | {
253 | self.increase_route_counter(format!("OPTIONS {path}"));
254 | self.routes = self.routes.clone().route(path, options(handler));
255 | }
256 | fn patch(&mut self, path: &str, handler: H)
257 | where
258 | H: Handler,
259 | T: 'static,
260 | {
261 | self.increase_route_counter(format!("PATCH {path}"));
262 | self.routes = self.routes.clone().route(path, patch(handler));
263 | }
264 | fn trace(&mut self, path: &str, handler: H)
265 | where
266 | H: Handler,
267 | T: 'static,
268 | {
269 | self.increase_route_counter(format!("TRACE {path}"));
270 | self.routes = self.routes.clone().route(path, trace(handler));
271 | }
272 | }
273 |
274 | impl Graphul<()> {
275 | pub fn new() -> Self {
276 | Self {
277 | routes: Router::new(),
278 | count_routes: 0,
279 | route_list: vec![],
280 | state: (),
281 | }
282 | }
283 |
284 | // new alias to create sub-routes
285 | pub fn router() -> Self {
286 | Self {
287 | routes: Router::new(),
288 | count_routes: 0,
289 | route_list: vec![],
290 | state: (),
291 | }
292 | }
293 |
294 | // Graphul::get("/", || async {});
295 | // let
296 |
297 | pub fn get(path: &str, handler: H) -> Graphul
298 | where
299 | H: Handler,
300 | T: 'static,
301 | {
302 | use http::Methods;
303 |
304 | let mut app = Graphul::new();
305 | app.get(path, handler);
306 | app
307 | }
308 |
309 | // Get with state
310 | // Graphul::Get(my_state, "/", || async {});
311 | pub fn state_get(state: S, path: &str, handler: H) -> Graphul
312 | where
313 | H: Handler,
314 | T: 'static,
315 | S: Clone + Send + Sync + 'static,
316 | {
317 | use http::Methods;
318 |
319 | let mut app: Graphul = Graphul::share_state(state);
320 | app.get(path, handler);
321 | app
322 | }
323 |
324 | // Post without state
325 | // Graphul::post("/", || async {});
326 | pub fn post(path: &str, handler: H) -> Graphul
327 | where
328 | H: Handler,
329 | T: 'static,
330 | {
331 | use http::Methods;
332 |
333 | let mut app = Graphul::new();
334 | app.post(path, handler);
335 | app
336 | }
337 |
338 | // Post with state
339 | // Graphul::Post(my_state, "/", || async {});
340 | pub fn state_post(state: S, path: &str, handler: H) -> Graphul
341 | where
342 | H: Handler,
343 | T: 'static,
344 | S: Clone + Send + Sync + 'static,
345 | {
346 | use http::Methods;
347 |
348 | let mut app: Graphul = Graphul::share_state(state);
349 | app.post(path, handler);
350 | app
351 | }
352 |
353 | // Put without state
354 | // Graphul::put("/", || async {});
355 | pub fn put(path: &str, handler: H) -> Graphul
356 | where
357 | H: Handler,
358 | T: 'static,
359 | {
360 | use http::Methods;
361 |
362 | let mut app = Graphul::new();
363 | app.put(path, handler);
364 | app
365 | }
366 |
367 | // Put with state
368 | // Graphul::Put(my_state, "/", || async {});
369 | pub fn state_put(state: S, path: &str, handler: H) -> Graphul
370 | where
371 | H: Handler,
372 | T: 'static,
373 | S: Clone + Send + Sync + 'static,
374 | {
375 | use http::Methods;
376 |
377 | let mut app: Graphul = Graphul::share_state(state);
378 | app.put(path, handler);
379 | app
380 | }
381 |
382 | // Delete without state
383 | // Graphul::delete("/", || async {});
384 | pub fn delete(path: &str, handler: H) -> Graphul
385 | where
386 | H: Handler,
387 | T: 'static,
388 | {
389 | use http::Methods;
390 |
391 | let mut app = Graphul::new();
392 | app.delete(path, handler);
393 | app
394 | }
395 |
396 | // Delete with state
397 | // Graphul::Delete(my_state, "/", || async {});
398 | pub fn state_delete(state: S, path: &str, handler: H) -> Graphul
399 | where
400 | H: Handler,
401 | T: 'static,
402 | S: Clone + Send + Sync + 'static,
403 | {
404 | use http::Methods;
405 |
406 | let mut app: Graphul = Graphul::share_state(state);
407 | app.delete(path, handler);
408 | app
409 | }
410 |
411 | // Patch without state
412 | // Graphul::patch("/", || async {});
413 | pub fn patch(path: &str, handler: H) -> Graphul
414 | where
415 | H: Handler,
416 | T: 'static,
417 | {
418 | use http::Methods;
419 |
420 | let mut app = Graphul::new();
421 | app.patch(path, handler);
422 | app
423 | }
424 |
425 | // Patch with state
426 | // Graphul::Patch(my_state, "/", || async {});
427 | pub fn state_patch(state: S, path: &str, handler: H) -> Graphul
428 | where
429 | H: Handler,
430 | T: 'static,
431 | S: Clone + Send + Sync + 'static,
432 | {
433 | use http::Methods;
434 |
435 | let mut app: Graphul = Graphul::share_state(state);
436 | app.patch(path, handler);
437 | app
438 | }
439 |
440 | // Options without state
441 | // Graphul::options("/", || async {});
442 | pub fn options(path: &str, handler: H) -> Graphul
443 | where
444 | H: Handler,
445 | T: 'static,
446 | {
447 | use http::Methods;
448 |
449 | let mut app = Graphul::new();
450 | app.options(path, handler);
451 | app
452 | }
453 |
454 | // Options with state
455 | // Graphul::Options(my_state, "/", || async {});
456 | pub fn state_options(state: S, path: &str, handler: H) -> Graphul
457 | where
458 | H: Handler,
459 | T: 'static,
460 | S: Clone + Send + Sync + 'static,
461 | {
462 | use http::Methods;
463 |
464 | let mut app: Graphul = Graphul::share_state(state);
465 | app.options(path, handler);
466 | app
467 | }
468 |
469 | // Trace without state
470 | // Graphul::trace("/", || async {});
471 | pub fn trace(path: &str, handler: H) -> Graphul
472 | where
473 | H: Handler,
474 | T: 'static,
475 | {
476 | use http::Methods;
477 |
478 | let mut app = Graphul::new();
479 | app.trace(path, handler);
480 | app
481 | }
482 |
483 | // Trace with state
484 | // Graphul::Trace(my_state, "/", || async {});
485 | pub fn state_trace(state: S, path: &str, handler: H) -> Graphul
486 | where
487 | H: Handler,
488 | T: 'static,
489 | S: Clone + Send + Sync + 'static,
490 | {
491 | use http::Methods;
492 |
493 | let mut app: Graphul = Graphul::share_state(state);
494 | app.trace(path, handler);
495 | app
496 | }
497 |
498 | // Head without state
499 | // Graphul::head("/", || async {});
500 | pub fn head(path: &str, handler: H) -> Graphul
501 | where
502 | H: Handler,
503 | T: 'static,
504 | {
505 | use http::Methods;
506 |
507 | let mut app = Graphul::new();
508 | app.head(path, handler);
509 | app
510 | }
511 |
512 | // Head with state
513 | // Graphul::Head(my_state, "/", || async {});
514 | pub fn state_head(state: S, path: &str, handler: H) -> Graphul
515 | where
516 | H: Handler,
517 | T: 'static,
518 | S: Clone + Send + Sync + 'static,
519 | {
520 | use http::Methods;
521 |
522 | let mut app: Graphul = Graphul::share_state(state);
523 | app.head(path, handler);
524 | app
525 | }
526 | }
527 |
528 | impl Default for Graphul<()> {
529 | fn default() -> Self {
530 | Self::new()
531 | }
532 | }
533 |
534 | impl Graphul
535 | where
536 | S: Clone + Send + Sync + 'static,
537 | {
538 | pub fn share_state(state: S) -> Self {
539 | Self {
540 | routes: Router::new(),
541 | count_routes: 0,
542 | route_list: vec![],
543 | state,
544 | }
545 | }
546 |
547 | pub fn routes(&self) -> Vec {
548 | self.route_list.clone()
549 | }
550 |
551 | fn increase_route_counter(&mut self, path: String) {
552 | self.count_routes += 1;
553 | self.route_list.push(path);
554 | }
555 |
556 | fn add_route_to_list(&mut self, paths: Vec) {
557 | for path in paths {
558 | self.increase_route_counter(path)
559 | }
560 | }
561 |
562 | pub fn merge(&mut self, route: R)
563 | where
564 | R: Into>,
565 | {
566 | self.routes = self.routes.clone().merge(route);
567 | }
568 |
569 | pub fn add_router(&mut self, route: Graphul) {
570 | self.merge(route.routes);
571 | self.add_route_to_list(route.route_list);
572 | }
573 |
574 | pub fn add_routers(&mut self, routes: Vec>) {
575 | for route in routes {
576 | self.merge(route.routes);
577 | self.add_route_to_list(route.route_list);
578 | }
579 | }
580 |
581 | pub fn set_server_file_config(
582 | &self,
583 | file_dir: String,
584 | compress: bool,
585 | chunk_size: Option,
586 | ) -> ServeFile {
587 | let mut serve_file = ServeFile::new(file_dir);
588 | if compress {
589 | serve_file = serve_file
590 | .precompressed_gzip()
591 | .precompressed_deflate()
592 | .precompressed_br()
593 | }
594 | if let Some(chunk_size) = chunk_size {
595 | serve_file = serve_file.with_buf_chunk_size(chunk_size)
596 | }
597 | serve_file
598 | }
599 |
600 | pub fn static_files(&mut self, path: &'static str, dir: &'static str, config: FolderConfig) {
601 | let mut serve_dir: ServeDir>;
602 | if config.spa {
603 | serve_dir = ServeDir::new(dir)
604 | .append_index_html_on_directories(config.index)
605 | .fallback(SetStatus::new(
606 | self.set_server_file_config(
607 | format!("{dir}/index.html"),
608 | config.compress,
609 | config.chunk_size,
610 | ),
611 | StatusCode::OK,
612 | ));
613 | } else {
614 | let mut not_found = "";
615 | if let Some(not_f) = config.not_found {
616 | not_found = not_f;
617 | }
618 | serve_dir = ServeDir::new(dir)
619 | .append_index_html_on_directories(config.index)
620 | .fallback(SetStatus::new(
621 | self.set_server_file_config(
622 | not_found.to_string(),
623 | config.compress,
624 | config.chunk_size,
625 | ),
626 | StatusCode::NOT_FOUND,
627 | ));
628 | }
629 | if config.compress {
630 | serve_dir = serve_dir
631 | .precompressed_gzip()
632 | .precompressed_deflate()
633 | .precompressed_br()
634 | }
635 | if let Some(chunk_size) = config.chunk_size {
636 | serve_dir = serve_dir.with_buf_chunk_size(chunk_size)
637 | }
638 | let serve_dir = get_service(serve_dir).handle_error(Graphul::::handle_error);
639 | self.routes = self.routes.clone().nest_service(path, serve_dir);
640 | }
641 |
642 | pub fn static_file(&mut self, path: &'static str, file: &'static str, config: FileConfig) {
643 | let serve_dir =
644 | self.set_server_file_config(file.to_string(), config.compress, config.chunk_size);
645 | self.routes = self.routes.clone().route(
646 | path,
647 | get(move |request: Request| async move {
648 | let serve_dir = get_service(serve_dir).handle_error(Graphul::::handle_error);
649 | serve_dir.oneshot(request).await
650 | }),
651 | );
652 | }
653 |
654 | async fn handle_error(_err: io::Error) -> impl IntoResponse {
655 | (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
656 | }
657 |
658 | pub fn middleware(&mut self, service: L)
659 | where
660 | L: Layer> + Clone + Send + 'static,
661 | L::Service: Service> + Clone + Send + 'static,
662 | >>::Response: IntoResponse + 'static,
663 | >>::Error: Into + 'static,
664 | >>::Future: Send + 'static,
665 | {
666 | self.routes = self.routes.clone().route_layer(service);
667 | }
668 |
669 | pub fn group(&mut self, name: &str) -> Group {
670 | Group::new(self, format!("/{name}").as_str())
671 | }
672 |
673 | async fn fallback(req: Req) -> (StatusCode, String) {
674 | (
675 | StatusCode::NOT_FOUND,
676 | format!("Cannot {} {}", req.method().as_str(), req.uri()),
677 | )
678 | }
679 |
680 | pub fn export_routes(self) -> Router {
681 | self.routes
682 | .with_state(self.state)
683 | .fallback(Graphul::::fallback)
684 | }
685 |
686 | pub async fn run(self, addr: &str) {
687 | let addr: SocketAddr = addr.parse().unwrap();
688 |
689 | listen::startup_message(&addr, false, self.count_routes);
690 |
691 | axum::Server::bind(&addr)
692 | .serve(
693 | self.routes
694 | .with_state(self.state)
695 | .fallback(Graphul::::fallback)
696 | .into_make_service_with_connect_info::(),
697 | )
698 | .await
699 | .unwrap();
700 | }
701 | }
702 |
--------------------------------------------------------------------------------