added mapped api response types
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing

This commit is contained in:
Jake Hillion 2022-07-24 21:58:13 +01:00
parent 896898a216
commit 3a44c299f3
5 changed files with 167 additions and 8 deletions

39
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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<Arc<State>>) {
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<Arc<State>>) -> tide::Result {
async fn index(req: tide::Request<Arc<State>>) -> ApiResult<Vec<Container>> {
use db::schema::containers::dsl::*;
use diesel::{QueryDsl, RunQueryDsl};
let mut connection = req.state().db.get().unwrap();
let items: Vec<Container> = containers
.limit(5)
.load::<Container>(&mut connection)
.expect("Error loading containers");
let items: Vec<Container> = containers.limit(5).load::<Container>(&mut connection)?;
Ok(tide::Body::from_json(&items).unwrap().into())
Ok(ApiSuccess::Ok(items))
}

118
src/api/error.rs Normal file
View File

@ -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<T> = Result<ApiSuccess<T>, ApiError>;
#[derive(Serialize, Error, Debug)]
pub enum ApiSuccess<T> {
Ok(T),
}
#[derive(Error, Debug)]
pub enum ApiError {
#[error("not found")]
NotFound,
#[error("database error: {0}")]
Database(DieselError),
}
impl From<DieselError> for ApiError {
fn from(err: DieselError) -> Self {
match err {
DieselError::NotFound => Self::NotFound,
e => Self::Database(e),
}
}
}
pub(super) struct ApiEndpoint<E>(pub(super) E);
#[async_trait]
impl<E, S, F, T> tide::Endpoint<S> for ApiEndpoint<E>
where
E: Fn(tide::Request<S>) -> F + Send + Sync + 'static,
S: Clone + Send + Sync + 'static,
F: Future<Output = ApiResult<T>> + Send + Sync + 'static,
T: Serialize,
{
async fn call(&self, req: tide::Request<S>) -> tide::Result {
Ok(ApiResultProxy::<T>::from(self.0(req).await).into())
}
}
struct ApiResultProxy<T>(pub Result<ApiSuccess<T>, ApiError>);
impl<T> From<ApiResult<T>> for ApiResultProxy<T> {
fn from(res: ApiResult<T>) -> Self {
Self(res)
}
}
#[derive(Serialize)]
enum ApiResponse<'a, T> {
#[serde(rename = "content")]
Content(&'a T),
#[serde(rename = "err")]
Err(&'a str),
}
impl<T: Serialize> From<ApiResultProxy<T>> for Response {
fn from(res: ApiResultProxy<T>) -> 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<T> ApiSuccess<T> {
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(),
)
}
}
}
}

View File

@ -1,4 +1,7 @@
mod containers;
mod error;
use error::{ApiEndpoint, ApiResult, ApiSuccess};
use std::sync::Arc;