Skip to content

Commit

Permalink
finished: Part VI - UrlMap CRUD API
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonkayZK committed Dec 16, 2021
1 parent cefc2ad commit d77521b
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -14,6 +14,7 @@ anyhow = "1.0.41"
config = { version = "0.11.0", features = ["json"] }
lazy_static = "1.4.0"
serde = { version = "1.0.126", features = ["derive"] }
serde_json = "1.0.64"
sqlx = { version = "0.5.5", features = ["runtime-tokio-rustls", "postgres", "migrate"] }
tokio = { version = "1.6.2", features = ["full"] }
async-trait = { version = "0.1.51" }
Expand Down
6 changes: 6 additions & 0 deletions src/dao/url_map_dao.rs
Expand Up @@ -12,6 +12,12 @@ pub struct UrlMap {
pub url: String,
}

impl UrlMap {
pub fn new(key: String, url: String) -> Self {
Self { key, url }
}
}

pub struct UrlMapDao {
db: Arc<DbHandle>,
receiver: Receiver<BaseMapperEnum<String, UrlMap>>,
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Expand Up @@ -11,6 +11,7 @@ use tracing_subscriber::FmtSubscriber;
mod configs;
mod dao;
mod server;
mod service;

#[tokio::main]
async fn main() -> Result<()> {
Expand Down
11 changes: 7 additions & 4 deletions src/server/router.rs
@@ -1,7 +1,9 @@
use crate::dao::url_map_dao::UrlMap;
use crate::service::url_maps_router;
use crate::BaseMapperEnum;
use anyhow::{Error, Result};
use hyper::{Body, Request, Response, StatusCode};
use routerify::{ext::RequestExt, Error, Middleware, RequestInfo, Router, RouterBuilder};
use routerify::{ext::RequestExt, Middleware, RequestInfo, Router, RouterBuilder};
use tokio::sync::mpsc::Sender;
use tracing::{error, info};

Expand Down Expand Up @@ -35,7 +37,7 @@ macro_rules! recv_failed {
};
}

async fn logger(req: Request<Body>) -> Result<Request<Body>, Error> {
async fn logger(req: Request<Body>) -> Result<Request<Body>> {
info!(
"{} {} {}",
req.remote_addr(),
Expand All @@ -53,11 +55,11 @@ async fn error_handler(err: routerify::RouteError, _: RequestInfo) -> Response<B
.unwrap()
}

async fn home_handler(_: Request<Body>) -> Result<Response<Body>, Error> {
async fn home_handler(_: Request<Body>) -> Result<Response<Body>> {
Ok(Response::new(Body::from("Url Mapper in Rust!")))
}

async fn redirect_handler(req: Request<Body>) -> Result<Response<Body>, Error> {
async fn redirect_handler(req: Request<Body>) -> Result<Response<Body>> {
let sender = req
.data::<Sender<BaseMapperEnum<String, UrlMap>>>()
.unwrap();
Expand Down Expand Up @@ -85,5 +87,6 @@ pub fn router() -> RouterBuilder<Body, Error> {
.middleware(Middleware::pre(logger))
.get("/", home_handler)
.get("/:key", redirect_handler)
.scope("/api", url_maps_router())
.err_handler_with_info(error_handler)
}
64 changes: 64 additions & 0 deletions src/service/mod.rs
@@ -0,0 +1,64 @@
use anyhow::Error;
use hyper::Body;
use routerify::Router;

mod url_maps;

#[macro_export]
macro_rules! json_response {
(body: $body:expr) => {
Response::builder()
.header(hyper::header::CONTENT_TYPE, "application/json")
.body(serde_json::to_string($body).unwrap().into())
.unwrap()
};
(status: $status:expr, body: $body:expr) => {
Response::builder()
.header(hyper::header::CONTENT_TYPE, "application/json")
.status($status)
.body(serde_json::to_string($body).unwrap().into())
.unwrap()
};
(error: $e:expr) => {
json_response!(
status: hyper::StatusCode::INTERNAL_SERVER_ERROR,
body: &serde_json::json!({
"error": $e.to_string(),
}).to_string())
};
}

#[macro_export]
macro_rules! sender_failed_json {
($m: expr, $f: tt) => {
match $m {
Ok(_) => {}
Err(e) => {
tracing::error!("Database Manager failed to get {}! error: {}", $f, e);
return Ok(json_response!(error: e));
}
}
};
}

#[macro_export]
macro_rules! recv_failed_json {
($m: expr, $status: expr) => {
match $m {
Ok(d) => d,
Err(e) => {
tracing::error!("Database Manager returned error: {}", e);
return Ok(json_response!(
status: $status,
body: &e.to_string()))
}
}
}
}

pub fn url_maps_router() -> Router<Body, Error> {
Router::builder()
.scope("/url_maps", url_maps::router())
.build()
.unwrap()
}
16 changes: 16 additions & 0 deletions src/service/url_maps/mod.rs
@@ -0,0 +1,16 @@
use anyhow::Error;
use hyper::Body;
use routerify::Router;

mod url_map_service;

pub fn router() -> Router<Body, Error> {
Router::builder()
.get("/:id", url_map_service::read_data_by_id)
.get("/", url_map_service::read_data_list)
.post("/", url_map_service::create_data)
.put("/:id", url_map_service::update_data)
.delete("/:id", url_map_service::delete_data_by_id)
.build()
.unwrap()
}
109 changes: 109 additions & 0 deletions src/service/url_maps/url_map_service.rs
@@ -0,0 +1,109 @@
use crate::dao::url_map_dao::UrlMap;
use crate::{json_response, recv_failed_json, sender_failed_json, BaseMapperEnum};
use anyhow::Result;
use hyper::{body::to_bytes, Body, Request, Response};
use routerify::ext::RequestExt;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::Sender;

pub async fn read_data_list(req: Request<Body>) -> Result<Response<Body>> {
let (tx, rx) = tokio::sync::oneshot::channel();
let sender = req
.data::<Sender<BaseMapperEnum<String, UrlMap>>>()
.unwrap();
sender_failed_json!(
sender.send(BaseMapperEnum::ReadDataList { resp: tx }).await,
"GetUrlMaps"
);
let url_maps = recv_failed_json!(rx.await.unwrap(), hyper::StatusCode::INTERNAL_SERVER_ERROR);
Ok(json_response!(body: &url_maps))
}

pub async fn read_data_by_id(req: Request<Body>) -> Result<Response<Body>> {
let sender = req
.data::<Sender<BaseMapperEnum<String, UrlMap>>>()
.unwrap();
let (tx, rx) = tokio::sync::oneshot::channel();
let id = req.param("id").unwrap();
sender_failed_json!(
sender
.send(BaseMapperEnum::ReadDataById {
id: id.into(),
resp: tx
})
.await,
"GetUrlMap"
);
let url_map = recv_failed_json!(rx.await.unwrap(), hyper::StatusCode::NOT_FOUND);
Ok(json_response!(body: &url_map))
}

pub async fn create_data(mut req: Request<Body>) -> Result<Response<Body>> {
let body = req.body_mut();
let url_map_bytes = to_bytes(body).await?;
let url_map = serde_json::from_slice::<UrlMap>(&url_map_bytes)?;
let (tx, rx) = tokio::sync::oneshot::channel();
let sender = req
.data::<Sender<BaseMapperEnum<String, UrlMap>>>()
.unwrap();
sender_failed_json!(
sender
.send(BaseMapperEnum::CreateData {
data: url_map,
resp: tx
})
.await,
"CreateUrlMap"
);
let url_map = recv_failed_json!(rx.await.unwrap(), hyper::StatusCode::UNPROCESSABLE_ENTITY);
Ok(json_response!(body: &url_map))
}

pub async fn update_data(mut req: Request<Body>) -> Result<Response<Body>> {
#[derive(Debug, Serialize, Deserialize)]
struct UrlMapUrl {
url: String,
}

let body = req.body_mut();
let url_map_url_bytes = to_bytes(body).await?;
let url_map_url = serde_json::from_slice::<UrlMapUrl>(&url_map_url_bytes)?;
let id = req.param("id").unwrap();
let url_map = UrlMap::new(id.into(), url_map_url.url);
let (tx, rx) = tokio::sync::oneshot::channel();
let sender = req
.data::<Sender<BaseMapperEnum<String, UrlMap>>>()
.unwrap();
sender_failed_json!(
sender
.send(BaseMapperEnum::UpdateData {
data: url_map,
resp: tx
})
.await,
"UpdateUrlMap"
);
let url_map = recv_failed_json!(rx.await.unwrap(), hyper::StatusCode::UNPROCESSABLE_ENTITY);
Ok(json_response!(body: &url_map))
}

pub async fn delete_data_by_id(req: Request<Body>) -> Result<Response<Body>> {
let id = req.param("id").unwrap();
let sender = req
.data::<Sender<BaseMapperEnum<String, UrlMap>>>()
.unwrap();
let (tx, rx) = tokio::sync::oneshot::channel();
sender_failed_json!(
sender
.send(BaseMapperEnum::DeleteDataById {
id: id.into(),
resp: tx
})
.await,
"DeleteUrlMap"
);
recv_failed_json!(rx.await.unwrap(), hyper::StatusCode::NOT_FOUND);
Ok(json_response!(body: &serde_json::json!({
"ok": "true"
}).to_string()))
}

0 comments on commit d77521b

Please sign in to comment.