Implementation of firmware for a sprinklers system using async Rust, Tokio, Actix, MQTT and more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

291 lines
7.7 KiB

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<Output> = Pin<Box<dyn Future<Output = Output>>>;
#[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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u16(*self as u16)
}
}
impl<'de> Deserialize<'de> for ErrorCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let prim = u16::deserialize(deserializer)?;
ErrorCode::from_u16(prim)
.ok_or_else(|| <D::Error as serde::de::Error>::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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cause: Option<String>,
}
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<eyre::Report> 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<M>(code: ErrorCode, message: M, name: Option<String>, cause: Option<String>) -> Self
where
M: ToString,
{
Self {
code,
message: message.to_string(),
name,
cause,
}
}
pub fn simple<M>(code: ErrorCode, message: M) -> Self
where
M: ToString,
{
Self::new(code, message, None, None)
}
pub fn with_name<M, N>(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<M, C>(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<M, N, C>(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<M>(message: M) -> Self
where
M: ToString,
{
ResponseMessage {
message: message.to_string(),
}
}
}
impl From<String> for ResponseMessage {
fn from(message: String) -> Self {
ResponseMessage { message }
}
}
pub type ResponseValue = serde_json::Value;
type RequestResult<Ok = ResponseValue> = Result<Ok, RequestError>;
type RequestFuture<Ok = ResponseValue> = BoxFuture<RequestResult<Ok>>;
trait IRequest {
type Response: Serialize;
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response>;
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<Response>(RequestFuture<Response>)
where
Response: Serialize;
impl<Response> Future for ErasedRequestFuture<Response>
where
Response: Serialize,
{
type Output = RequestResult;
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
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<RequestResult> for Response {
fn from(res: RequestResult) -> Self {
match res {
Ok(value) => Response::Success(value),
Err(error) => Response::Error(error),
}
}
}
impl From<RequestError> for Response {
fn from(error: RequestError) -> Self {
Response::Error(error)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WithRequestId<T> {
pub rid: i32,
#[serde(flatten)]
pub rest: T,
}
pub type ResponseWithId = WithRequestId<Response>;
#[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<Output = Response> {
self.exec(ctx).map(Response::from)
}
}