use std::borrow::Cow;
use std::num::NonZeroU32;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::list::NonEmpty;
use crate::names::{EnvName, Name, SnakeName, StepId};
use crate::step::Step;
use crate::template::Template;
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Park {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pause: Option<Vec<Step>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shutdown: Option<Vec<Step>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Provision {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub all: Vec<Step>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub machine: Vec<Step>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Lifecycle {
pub up: NonEmpty<Step>,
pub down: NonEmpty<Step>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub park: Option<Park>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub resume: Vec<Step>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provision: Option<Provision>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seal: Option<Seal>,
pub status: Step,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Seal {
pub param: EnvName,
pub steps: NonEmpty<Step>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ReadyGrammar {
#[serde(rename = "isoloom-ready/1")]
V1,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum Condition {
StepOk {
step: StepId,
#[serde(default, skip_serializing_if = "Option::is_none")]
means: Option<String>,
},
MachinesRunning {
machines: Vec<Name>,
},
ReadyFile {
host: Template,
user: Template,
path: Template,
#[serde(default, skip_serializing_if = "Option::is_none")]
log: Option<Template>,
grammar: ReadyGrammar,
#[serde(default, skip_serializing_if = "Option::is_none")]
via: Option<Template>,
timeout_s: NonZeroU32,
#[serde(default, skip_serializing_if = "Option::is_none")]
unreachable_timeout_s: Option<NonZeroU32>,
interval_s: NonZeroU32,
},
Tcp {
address: Template,
port: Template,
timeout_s: NonZeroU32,
},
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Retries(u8);
impl Retries {
pub fn new(count: u8) -> Result<Retries> {
if count <= 3 {
Ok(Retries(count))
} else {
Err(Error::OutOfRange {
what: "a retry count",
value: count.into(),
min: 0,
max: 3,
})
}
}
#[must_use]
pub fn get(self) -> u8 {
self.0
}
}
impl TryFrom<u8> for Retries {
type Error = Error;
fn try_from(count: u8) -> Result<Retries> {
Retries::new(count)
}
}
impl From<Retries> for u8 {
fn from(r: Retries) -> u8 {
r.0
}
}
impl Serialize for Retries {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_u8(self.0)
}
}
impl<'de> Deserialize<'de> for Retries {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Retries, D::Error> {
Retries::new(u8::deserialize(d)?).map_err(serde::de::Error::custom)
}
}
impl JsonSchema for Retries {
fn schema_name() -> Cow<'static, str> {
"Retries".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({ "type": "integer", "minimum": 0, "maximum": 3 })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Recovery {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub stale_markers: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub steps: Vec<Step>,
#[serde(default)]
pub retries: Retries,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub apply_retries: Option<Retries>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExpiryUnit {
UnixSeconds,
MinutesFromNow,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Expiry {
pub tf_var: SnakeName,
pub unit: ExpiryUnit,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_minutes: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_retry_count_is_0_to_3() {
assert_eq!(Retries::new(3).map(Retries::get), Ok(3));
assert_eq!(Retries::try_from(2).map(u8::from), Ok(2));
assert_eq!(Retries::default().get(), 0);
assert_eq!(Retries::new(4).unwrap_err().code(), "out_of_range");
assert!(serde_json::from_str::<Retries>("4").is_err());
assert_eq!(serde_json::from_str::<Retries>("2").map(Retries::get).ok(), Some(2));
assert_eq!(serde_json::to_string(&Retries::new(2).unwrap()).unwrap(), "2");
}
}