use sprinklers_actors::{ProgramRunner, SectionRunner, StateManager}; use sprinklers_core::model::Sections; use futures_util::{ready, FutureExt}; use num_derive::FromPrimitive; use serde::{Deserialize, Serialize}; use std::{fmt, future::Future, pin::Pin, task::Poll}; mod programs; mod sections; pub struct RequestContext { pub sections: Sections, pub section_runner: SectionRunner, pub program_runner: ProgramRunner, pub state_manager: StateManager, } type BoxFuture = Pin>>; #[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, NoSuchSection = 120, NoSuchSectionRun = 121, NoSuchProgram = 122, 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()), ) } } #[derive(Debug, Serialize, Deserialize)] struct ResponseMessage { message: String, } impl ResponseMessage { fn new(message: M) -> Self where M: ToString, { ResponseMessage { message: message.to_string(), } } } impl From for ResponseMessage { fn from(message: String) -> Self { ResponseMessage { message } } } pub type ResponseValue = serde_json::Value; type RequestResult = Result; type RequestFuture = BoxFuture>; trait IRequest { type Response: Serialize; fn exec(self, ctx: &mut RequestContext) -> RequestFuture; fn exec_erased(self, ctx: &mut RequestContext) -> RequestFuture where Self::Response: 'static, Self: Sized, { // TODO: figure out how to get rid of this nested box Box::pin(ErasedRequestFuture(self.exec(ctx))) } } struct ErasedRequestFuture(RequestFuture) where Response: Serialize; impl Future for ErasedRequestFuture where Response: Serialize, { type Output = RequestResult; fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { use eyre::WrapErr; let response = ready!(self.as_mut().0.poll_unpin(cx)); Poll::Ready(response.and_then(|res| { serde_json::to_value(res) .wrap_err("could not serialize response") .map_err(RequestError::from) })) } } #[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) } } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct WithRequestId { pub rid: i32, #[serde(flatten)] pub rest: T, } pub type ResponseWithId = WithRequestId; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum Request { RunSection(sections::RunSectionRequest), CancelSection(sections::CancelSectionRequest), CancelSectionRunId(sections::CancelSectionRunIdRequest), PauseSectionRunner(sections::PauseSectionRunnerRequest), RunProgram(programs::RunProgramRequest), CancelProgram(programs::CancelProgramRequest), UpdateProgram(programs::UpdateProgramRequest), } impl IRequest for Request { type Response = ResponseValue; fn exec(self, ctx: &mut RequestContext) -> RequestFuture { match self { Request::RunSection(req) => req.exec_erased(ctx), Request::CancelSection(req) => req.exec_erased(ctx), Request::CancelSectionRunId(req) => req.exec_erased(ctx), Request::PauseSectionRunner(req) => req.exec_erased(ctx), Request::RunProgram(req) => req.exec_erased(ctx), Request::CancelProgram(req) => req.exec_erased(ctx), Request::UpdateProgram(req) => req.exec_erased(ctx), } } } impl Request { pub fn execute(self, ctx: &mut RequestContext) -> impl Future { self.exec(ctx).map(Response::from) } }