use serde::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
use std::fmt;
use crate::resource::status::*;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ExecutionStatus {
pub code: StatusCode,
pub message: String,
pub elapsed: Option<u64>,
pub progress: Option<f32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "call_stack_repr"
)]
pub call_stack: Option<Vec<SourceLocation>>,
pub cause: Option<Cause>,
#[serde(default)]
pub elapsed_times: HashMap<String, u64>,
#[serde(default)]
pub extra: HashMap<String, String>,
pub instruction: Option<Instruction>,
pub source_location: Option<SourceLocation>,
#[serde(skip)]
_placeholder: (),
}
impl ExecutionStatus {
pub fn full_message(&self) -> String {
if let Some(ref cause) = self.cause {
format!("{} ({})", self.message, cause)
} else {
self.message.clone()
}
}
}
impl Status for ExecutionStatus {
fn code(&self) -> StatusCode {
self.code
}
fn message(&self) -> &str {
&self.message
}
fn elapsed(&self) -> Option<u64> {
self.elapsed
}
fn progress(&self) -> Option<f32> {
self.progress
}
}
pub(crate) mod call_stack_repr {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::result;
use super::*;
pub(crate) fn deserialize<'de, D>(
deserializer: D,
) -> result::Result<Option<Vec<SourceLocation>>, D::Error>
where
D: Deserializer<'de>,
{
#[allow(clippy::type_complexity)]
let raw: Option<Vec<(usize, (u64, u64), (u64, u64))>> =
Deserialize::deserialize(deserializer)?;
Ok(raw.map(|vec| {
vec.into_iter()
.map(|(origin, lines, columns)| SourceLocation {
origin,
columns,
lines,
_placeholder: (),
})
.collect()
}))
}
pub(crate) fn serialize<S>(
stack: &Option<Vec<SourceLocation>>,
serializer: S,
) -> result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let raw: Option<Vec<_>> = stack.as_ref().map(|vec| {
vec.iter()
.map(|sloc| (sloc.origin, sloc.lines, sloc.columns))
.collect()
});
raw.serialize(serializer)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SourceLocation {
pub columns: (u64, u64),
pub lines: (u64, u64),
pub origin: usize,
#[serde(skip)]
_placeholder: (),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Cause {
pub code: i64,
#[serde(default)]
pub extra: serde_json::Value,
pub http_status: Option<u16>,
#[serde(skip)]
_placeholder: (),
}
impl fmt::Display for Cause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "code: {}", self.code)?;
if let Some(http_status) = self.http_status {
write!(f, ", HTTP status: {}", http_status)?;
}
write!(f, ", extra: {}", self.extra,)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Instruction {
pub instruction: String,
pub source: SourceLocation,
#[serde(skip)]
_placeholder: (),
}
#[test]
fn deserialize_error_status() {
use serde_json;
let json = r#"{"call_stack": [[1, [109, 109], [14, 65]], [1, [109, 109], [15, 17]]], "code": -1, "elapsed": 62321, "elapsed_times": {"in-progress": 62265, "queued": 140, "started": 56}, "error": -8200, "instruction": {"instruction": "push-procedure", "source": {"columns": [14, 65], "lines": [109, 109], "origin": 1}}, "message": "Problem while executing script: 'get' expects 2 or 3 arguments, 4 given", "progress": 0.195, "source_location": {"columns": [0, 34], "lines": [97, 97], "origin": 1}}"#;
let _status: ExecutionStatus = serde_json::from_str(json).unwrap();
let json = r#"{"call_stack": [[1, [32, 47], [15, 1]]], "cause": {"code": -1206, "extra": {"all_fields": "Must be true or false", "fields": "Must be an object"}, "http_status": 400}, "code": -1, "elapsed": 8896, "elapsed_times": {"in-progress": 8834, "queued": 22, "started": 62}, "error": -8200, "instruction": {"instruction": "apply", "source": {"columns": [15, 1], "lines": [32, 47], "origin": 1}}, "message": "Problem while executing script: Error handling resource (Validation error)", "progress": 0.195, "source_location": {"columns": [15, 1], "lines": [32, 47], "origin": 1}}"#;
let status: ExecutionStatus = serde_json::from_str(json).unwrap();
assert_eq!(status.cause.unwrap().code, -1206);
}