use crate::types::Generation;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, de};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum FaultClass {
Transport,
Process,
Protocol,
Timeout,
Downstream,
Resource,
Replay,
Rollout,
AmbiguousOutcome,
Invariant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RecoveryHint {
KeepWorker,
ReplaceWorker,
RollForward,
AbortHost,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, JsonSchema)]
#[serde(transparent)]
pub struct FaultCode(String);
impl FaultCode {
pub fn try_new(code: impl Into<String>) -> Result<Self, crate::types::InvariantViolation> {
let code = code.into();
if code.is_empty()
|| !code
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte == b'_' || byte.is_ascii_digit())
{
return Err(crate::types::InvariantViolation::new(
"fault code must be non-empty lowercase ascii snake_case",
));
}
Ok(Self(code))
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl<'de> Deserialize<'de> for FaultCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let code = String::deserialize(deserializer)?;
Self::try_new(code).map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct Fault {
pub generation: Generation,
pub class: FaultClass,
pub code: FaultCode,
#[serde(skip_serializing_if = "Option::is_none")]
pub recovery_hint: Option<RecoveryHint>,
pub detail: String,
}
impl Fault {
#[must_use]
pub fn new(
generation: Generation,
class: FaultClass,
code: FaultCode,
recovery_hint: Option<RecoveryHint>,
detail: impl Into<String>,
) -> Self {
Self {
generation,
class,
code,
recovery_hint,
detail: detail.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::FaultCode;
#[test]
fn fault_code_rejects_non_snake_case() {
assert!(FaultCode::try_new("broken_pipe").is_ok());
assert!(FaultCode::try_new("BrokenPipe").is_err());
assert!(FaultCode::try_new("").is_err());
assert!(serde_json::from_str::<FaultCode>(r#""BrokenPipe""#).is_err());
assert!(serde_json::from_str::<FaultCode>(r#""broken_pipe""#).is_ok());
}
}