From 3a44c299f32331d49baac9b301daa9f75b62bfe1 Mon Sep 17 00:00:00 2001 From: Jake Hillion Date: Sun, 24 Jul 2022 21:58:13 +0100 Subject: [PATCH] added mapped api response types --- Cargo.lock | 39 ++++++++++++++ Cargo.toml | 2 + src/api/containers.rs | 13 ++--- src/api/error.rs | 118 ++++++++++++++++++++++++++++++++++++++++++ src/api/mod.rs | 3 ++ 5 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 src/api/error.rs diff --git a/Cargo.lock b/Cargo.lock index b9e3ef7..7ee6b24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -636,6 +636,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.21" @@ -643,6 +658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -651,6 +667,17 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -683,6 +710,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + [[package]] name = "futures-task" version = "0.3.21" @@ -695,9 +728,13 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite 0.2.9", "pin-utils", "slab", @@ -867,8 +904,10 @@ name = "inventory-system" version = "0.1.0" dependencies = [ "async-std", + "async-trait", "diesel", "diesel_migrations", + "futures", "log", "rust-embed", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9e4aa2c..3c05e9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,9 @@ edition = "2021" log = "0.4" thiserror = "1" +futures = "0.3.21" async-std = "1" +async-trait = "0.1.56" serde = { version = "1.0", features = ["derive"] } tide = "0.17.0-beta.1" diff --git a/src/api/containers.rs b/src/api/containers.rs index d138124..0f85031 100644 --- a/src/api/containers.rs +++ b/src/api/containers.rs @@ -1,4 +1,4 @@ -use super::State; +use super::{ApiEndpoint, ApiResult, ApiSuccess, State}; use crate::db; use std::sync::Arc; @@ -8,7 +8,7 @@ use serde::Serialize; use uuid::Uuid; pub fn setup_routes(mut route: tide::Route>) { - route.at("/").get(index); + route.at("/").get(ApiEndpoint(index)); } #[derive(Queryable, Serialize)] @@ -18,16 +18,13 @@ pub struct Container { pub name: String, } -async fn index(req: tide::Request>) -> tide::Result { +async fn index(req: tide::Request>) -> ApiResult> { use db::schema::containers::dsl::*; use diesel::{QueryDsl, RunQueryDsl}; let mut connection = req.state().db.get().unwrap(); - let items: Vec = containers - .limit(5) - .load::(&mut connection) - .expect("Error loading containers"); + let items: Vec = containers.limit(5).load::(&mut connection)?; - Ok(tide::Body::from_json(&items).unwrap().into()) + Ok(ApiSuccess::Ok(items)) } diff --git a/src/api/error.rs b/src/api/error.rs new file mode 100644 index 0000000..d02c025 --- /dev/null +++ b/src/api/error.rs @@ -0,0 +1,118 @@ +use log::error; + +use async_trait::async_trait; +use diesel::result::Error as DieselError; +use futures::future::Future; +use serde::Serialize; +use thiserror::Error; +use tide::{Body, Response, StatusCode}; + +pub type ApiResult = Result, ApiError>; + +#[derive(Serialize, Error, Debug)] +pub enum ApiSuccess { + Ok(T), +} + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("not found")] + NotFound, + + #[error("database error: {0}")] + Database(DieselError), +} + +impl From for ApiError { + fn from(err: DieselError) -> Self { + match err { + DieselError::NotFound => Self::NotFound, + e => Self::Database(e), + } + } +} + +pub(super) struct ApiEndpoint(pub(super) E); + +#[async_trait] +impl tide::Endpoint for ApiEndpoint +where + E: Fn(tide::Request) -> F + Send + Sync + 'static, + S: Clone + Send + Sync + 'static, + F: Future> + Send + Sync + 'static, + T: Serialize, +{ + async fn call(&self, req: tide::Request) -> tide::Result { + Ok(ApiResultProxy::::from(self.0(req).await).into()) + } +} + +struct ApiResultProxy(pub Result, ApiError>); + +impl From> for ApiResultProxy { + fn from(res: ApiResult) -> Self { + Self(res) + } +} + +#[derive(Serialize)] +enum ApiResponse<'a, T> { + #[serde(rename = "content")] + Content(&'a T), + + #[serde(rename = "err")] + Err(&'a str), +} + +impl From> for Response { + fn from(res: ApiResultProxy) -> Self { + match res.0 { + Ok(resp) => { + let (status_code, body) = resp.response(); + + Response::builder(status_code) + .body( + Body::from_json(&ApiResponse::Content(body)) + .expect("json serialisation should succeed"), + ) + .build() + } + + Err(err) => { + let (status_code, body) = err.response(); + + Response::builder(status_code) + .body( + Body::from_json(&ApiResponse::<()>::Err(&body)) + .expect("json serialisation should succeed"), + ) + .build() + } + } + } +} + +impl ApiSuccess { + fn response(&self) -> (StatusCode, &T) { + match self { + Self::Ok(b) => (StatusCode::Ok, b), + } + } +} + +impl ApiError { + fn response(&self) -> (StatusCode, String) { + match self { + Self::NotFound => (StatusCode::NotFound, "requested object not found".into()), + + e => { + error!("internal server error handling request: {}", e); + + ( + StatusCode::InternalServerError, + "internal server error".into(), + ) + } + } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index cef59ff..2c18ffa 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,7 @@ mod containers; +mod error; + +use error::{ApiEndpoint, ApiResult, ApiSuccess}; use std::sync::Arc;