Cargo.toml
````toml [dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
rustgram = "0.1" ````
````rust use rustgram::{r, Router, Request,Response}; use std::net::SocketAddr;
async fn notfoundhandler(req: Request) -> Response { return hyper::Response::builder() .status(StatusCode::NOTFOUND) .body("Not found".into()) .unwrap(); }
pub async fn testhandler(req: Request) -> String { format!("test called") }
async fn main() { let mut router = Router::new(crate::notfoundhandler); router.get("/", r(testhandler)); router.get("/api", r(testhandler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//start the app
rustgram::start(router, addr).await;
} ````
The middleware stack is build like a service call stack.
The Order of the middleware stack is reverse to the applied order.
````rust use rustgram::service::{Service, ServiceTransform}; use rustgram::{Request, Response};
//define a middleware service
pub struct Middleware
{
inner: S, //space the inner service to call it later
}
impl Service
where
S: Service
fn call(&self, req: Request) -> Self::Future
{
// before the request handler from the router is called
self.inner.call(req) //call the next handler
// after the request handler is called with the response
}
}
//define a middleware transform pub struct MiddlewareTransform;
impl ServiceTransform for MiddlewareTransform
where
S: Service;
fn transform(&self, inner: S) -> Self::Service
{
Middleware {
inner,
}
}
}
//or define a middleware transform with a function
pub fn middleware_transform(inner: S) -> Middleware
{
Middleware {
inner,
}
}
async fn notfoundhandler(req: Request) -> Response { return hyper::Response::builder() .status(StatusCode::NOTFOUND) .body("Not found".into()) .unwrap(); }
pub async fn testhandler(req: Request) -> String { format!("test called") }
//Apply a middleware to a route after the r function
async fn main() { let mut router = Router::new(crate::notfoundhandler); router.get("/", r(testhandler) .add(middlewaretransform) );
router.get("/api", r(test_handler)
.add(MiddlewareTransform)
);
//apply multiple middleware to a route
router.get("/multiple", r(test_handler)
.add(MiddlewareTransform) //then this at last
.add(middleware_transform) //then this ^
.add(middleware_transform) //then this ^
.add(middleware_transform) //then this ^
.add(middleware_transform) //then this ^
.add(middleware_transform) //this is called first ^
);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//start the app
rustgram::start(router, addr).await;
} ````
If work must be done before the req handling or after the response, a Box Future is needed
When calling async actions before the response, here Arc is needed to avoid lifetime issues:
````rust use std::future::Future; use std::pin::Pin; use std::sync::Arc;
use rustgram::service::{Service, ServiceTransform}; use rustgram::{Request, Response};
pub struct Middleware
{
inner: Arc, //use Arc here to avoid lifetime issues
}
impl Service
where
S: Service
fn call(&self, req: Request) -> Self::Future
{
//must clone the service to call it in the async move block. (we are cloning the arc ref, to avoid lifetime issues)
let next = self.inner.clone();
Box::pin(async move {
//do async fn before req
next.call(req).await
//do async fn after req
})
}
}
pub fn mw_transform(inner: S) -> Middleware
{
Middleware {
inner: Arc::new(inner), //use Arc here!
}
}
````
Only after response async action:
````rust use rustgram::service::{Service, ServiceTransform}; use rustgram::{Request, Response};
pub struct Middleware
{
inner: Arc, //use Arc here to avoid lifetime issues
}
impl Service
where
S: Service
fn call(&self, req: Request) -> Self::Future
{
//no Arc cloning needed because we move the feature into the async block
//but change the req is not possible here
let res = self.inner.call(req);
Box::pin(async move {
let res = res.await;
//do async fn after req
res
})
}
}
pub fn mw_transform(inner: S) -> Middleware
{
Middleware {
inner, //no Arc here!
}
}
````
The router only uses Service traits. For normal functions and closure, this is already implemented.
The functions don't need to return a Hyper Response, but their return gets converted into hyper response.
Supported returns are:
- Hyper Response
- String
- &'static str
- Result
The GramStdHttpErr gets converted into a hyper response.
The E where E impl GramHttpErr, will convert via the GramHttpErr trait.
````rust use hyper::StatusCode; use rustgram::{GramHttpErr, Response, Request};
pub struct HttpErr { httpstatuscode: u16, apierrorcode: u32, msg: &'static str, }
impl HttpErr { pub fn new(httpstatuscode: u16, apierrorcode: u32, msg: &'static str) -> Self { Self { httpstatuscode, apierrorcode, msg } } }
impl GramHttpErr
//the msg for the end user
let msg = format!(
"{{\"status\": {}, \"error_message\": \"{}\"}}",
self.api_error_code, self.msg
);
hyper::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(hyper::Body::from(msg))
.unwrap()
}
}
//example usage:
pub async fn testhandlererr(_req: Request) -> Result
````shell
cargo new route_builder ````
In the Cargo-toml file:
toml
rustgram = { version = "0.1", features = ["route_builder"] }
Open the main function in src/main.rs
````rust use rustgram::route_parser;
fn main() { //input path: from the root of the current working directory // output path: into the app crate (created via cargo new app) routeparser::start( "routes.yml".tostring(), "app/src/routes.rs".to_string() ); } ````
Create the route file.
````yaml
basehandler: testhandler
basemw: testmw
handler404: crate::notfound_handler
prefix: "/"
routes: - get: p: "" # must match the basehandler namespace s: testhandler::testhandler # a put route with middleware - put: p: "" s: testhandler::testhandler mw: - mw1transform - mwtransform # a group of routes. # a prefix (p) for all routes and middleware (mw) like routes - group: p: admin mw: - mw1transform - mwtransform # the routes to this group gr: - get: p: "" s: testhandlerdb::testhandlerdbtojson - get: p: "/user/:id" s: testhandler::testhandler # for this route, mw is called first, then mw1, mw2 and mw3 - put: p: "/manymw" s: testhandler::testhandler mw: - mw3transform - mw2transform - group: p: nested mw: - mw1transform - mwtransform gr: # define a new group inside the group routes - group: p: "/management" mw: - mw1transform - mwtransform gr: - put: p: "/put" s: testhandler::testhandler mw: - mw5transform - mw4transform ````
This file is parsed to this:
````rust /**
Please do not modify this file. Any changes will be overridden by the next route build. Use the returned router instead */ use rustgram::{r, Router};
use crate::testhandler::*; use crate::testmw::*;
pub(crate) fn routes() -> Router { let mut router = Router::new(crate::notfoundhandler); router.get("/", r(testhandler::testhandler));
router.put(
"/",
r(test_handler::test_handler)
.add(mw1_transform)
.add(mw_transform),
);
router.get(
"/admin",
r(test_handler_db::test_handler_db_to_json)
.add(mw1_transform)
.add(mw_transform),
);
router.get(
"/admin/user/:id",
r(test_handler::test_handler)
.add(mw1_transform)
.add(mw_transform),
);
router.put(
"/admin/many_mw",
r(test_handler::test_handler)
.add(mw3_transform)
.add(mw2_transform)
.add(mw1_transform)
.add(mw_transform),
);
router.put(
"/nested/management/put",
r(test_handler::test_handler)
.add(mw5_transform)
.add(mw4_transform)
.add(mw3_transform)
.add(mw2_transform)
.add(mw1_transform)
.add(mw_transform),
);
router
} ````