use crate::{ model::{SectionId, Sections}, section_runner::SectionRunner, }; use eyre::WrapErr; use futures_util::FutureExt; use num_derive::FromPrimitive; use serde::{Deserialize, Serialize}; use std::{fmt, future::Future, pin::Pin, time::Duration}; pub struct RequestContext { pub sections: Sections, pub section_runner: SectionRunner, } type BoxFuture = Pin>>; pub type ResponseValue = serde_json::Value; #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] #[repr(u16)] pub enum ErrorCode { BadRequest = 100, NotSpecified = 101, Parse = 102, Range = 103, InvalidData = 104, BadToken = 105, Unauthorized = 106, NoPermission = 107, NotFound = 109, // NotUnique = 110, Internal = 200, NotImplemented = 201, Timeout = 300, // ServerDisconnected = 301, // BrokerDisconnected = 302, } mod ser { use super::ErrorCode; use num_traits::FromPrimitive; use serde::{Deserialize, Deserializer, Serialize, Serializer}; impl Serialize for ErrorCode { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_u16(*self as u16) } } impl<'de> Deserialize<'de> for ErrorCode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let prim = u16::deserialize(deserializer)?; ErrorCode::from_u16(prim) .ok_or_else(|| ::custom("invalid ErrorCode")) } } } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "result")] pub struct RequestError { code: ErrorCode, message: String, #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] cause: Option, } impl fmt::Display for RequestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "request error (code {:?}", self.code)?; if let Some(name) = &self.name { write!(f, "on {}", name)?; } write!(f, "): {}", self.message)?; if let Some(cause) = &self.cause { write!(f, ", caused by {}", cause)?; } Ok(()) } } impl std::error::Error for RequestError {} impl From for RequestError { fn from(report: eyre::Report) -> Self { let mut chain = report.chain(); let message = match chain.next() { Some(a) => a.to_string(), None => "unknown error".to_string(), }; let cause = chain.fold(None, |cause, err| match cause { Some(cause) => Some(format!("{}: {}", cause, err)), None => Some(err.to_string()), }); RequestError::new(ErrorCode::Internal, message, None, cause) } } #[allow(dead_code)] impl RequestError { pub fn new(code: ErrorCode, message: M, name: Option, cause: Option) -> Self where M: ToString, { Self { code, message: message.to_string(), name, cause, } } pub fn simple(code: ErrorCode, message: M) -> Self where M: ToString, { Self::new(code, message, None, None) } pub fn with_name(code: ErrorCode, message: M, name: N) -> Self where M: ToString, N: ToString, { Self::new(code, message, Some(name.to_string()), None) } pub fn with_cause(code: ErrorCode, message: M, cause: C) -> Self where M: ToString, C: ToString, { Self::new(code, message, None, Some(cause.to_string())) } pub fn with_name_and_cause(code: ErrorCode, message: M, name: N, cause: C) -> Self where M: ToString, N: ToString, C: ToString, { Self::new( code, message, Some(name.to_string()), Some(cause.to_string()), ) } } type RequestResult = Result; type RequestFuture = BoxFuture; trait IRequest { fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture; } mod run_section { use super::*; use crate::section_runner::SectionRunHandle; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RequestData { pub section_id: SectionId, #[serde(with = "crate::serde::duration")] pub duration: Duration, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ResponseData { pub message: String, pub run_id: SectionRunHandle, } impl IRequest for RequestData { fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture { let mut section_runner = ctx.section_runner.clone(); let section = ctx.sections.get(&self.section_id).cloned(); let duration = self.duration; Box::pin(async move { let section = section.ok_or_else(|| { RequestError::with_name(ErrorCode::NotFound, "section not found", "section") })?; let handle = section_runner .queue_run(section.clone(), duration) .await .wrap_err("could not queue run")?; let res = ResponseData { message: format!("running section '{}' for {:?}", §ion.name, duration), run_id: handle, }; let res_value = serde_json::to_value(res).wrap_err("could not serialize response")?; Ok(res_value) }) } } } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum Request { RunSection(run_section::RequestData), } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase", tag = "result")] pub enum Response { Success(ResponseValue), Error(RequestError), } impl From for Response { fn from(res: RequestResult) -> Self { match res { Ok(value) => Response::Success(value), Err(error) => Response::Error(error), } } } impl From for Response { fn from(error: RequestError) -> Self { Response::Error(error) } } impl IRequest for Request { fn exec(&mut self, ctx: &mut RequestContext) -> BoxFuture { match self { Request::RunSection(req) => req.exec(ctx), } } } impl Request { pub fn execute(&mut self, ctx: &mut RequestContext) -> impl Future { self.exec(ctx).map(Response::from) } } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct WithRequestId { pub rid: i32, #[serde(flatten)] pub rest: T, } pub type ResponseWithId = WithRequestId;