Alex Mikhalev
4 years ago
8 changed files with 359 additions and 30 deletions
@ -0,0 +1,261 @@
@@ -0,0 +1,261 @@
|
||||
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<Output> = Pin<Box<dyn Future<Output = Output>>>; |
||||
|
||||
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<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()), |
||||
) |
||||
} |
||||
} |
||||
|
||||
type RequestResult = Result<ResponseValue, RequestError>; |
||||
type RequestFuture = BoxFuture<RequestResult>; |
||||
|
||||
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<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) |
||||
} |
||||
} |
||||
|
||||
impl IRequest for Request { |
||||
fn exec(&mut self, ctx: &mut RequestContext) -> BoxFuture<RequestResult> { |
||||
match self { |
||||
Request::RunSection(req) => req.exec(ctx), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Request { |
||||
pub fn execute(&mut self, ctx: &mut RequestContext) -> impl Future<Output = Response> { |
||||
self.exec(ctx).map(Response::from) |
||||
} |
||||
} |
||||
|
||||
#[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>; |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer}; |
||||
|
||||
pub mod duration { |
||||
use super::*; |
||||
use std::time::Duration; |
||||
|
||||
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error> |
||||
where |
||||
S: Serializer, |
||||
{ |
||||
duration.as_secs_f64().serialize(serializer) |
||||
} |
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error> |
||||
where |
||||
D: Deserializer<'de>, |
||||
{ |
||||
let secs: f64 = Deserialize::deserialize(deserializer)?; |
||||
Ok(Duration::from_secs_f64(secs)) |
||||
} |
||||
} |
Loading…
Reference in new issue