use std::fmt::{self, Write as _};
use serde_json::Value;
use crate::observe::HttpMethod;
use crate::path::JsonPath;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Side {
Engenho,
K3s,
}
impl Side {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Engenho => "engenho",
Self::K3s => "k3s",
}
}
}
impl fmt::Display for Side {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Gvr {
pub group: String,
pub version: String,
pub resource: String,
}
impl fmt::Display for Gvr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.group.is_empty() {
write!(f, "{}/{}", self.version, self.resource)
} else {
write!(f, "{}/{}/{}", self.group, self.version, self.resource)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Verb {
Get,
List,
Create,
Update,
Patch,
Delete,
Watch,
DeleteCollection,
}
impl Verb {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Get => "get",
Self::List => "list",
Self::Create => "create",
Self::Update => "update",
Self::Patch => "patch",
Self::Delete => "delete",
Self::Watch => "watch",
Self::DeleteCollection => "deletecollection",
}
}
}
impl fmt::Display for Verb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatusCause {
pub field: String,
pub reason: String,
pub message: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Severity {
Hard,
Cosmetic,
}
impl Severity {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Hard => "HARD",
Self::Cosmetic => "cosmetic",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Divergence {
StatusCodeDiff {
op: String,
method: HttpMethod,
engenho: u16,
k3s: u16,
},
MissingResource { path: JsonPath, present_on: Side },
FieldDiff {
path: JsonPath,
engenho: Value,
k3s: Value,
severity: Severity,
},
MissingDefault {
path: JsonPath,
k3s_value: Value,
severity: Severity,
},
ExtraField {
path: JsonPath,
side: Side,
value: Value,
severity: Severity,
},
MissingVerb {
gvr: Gvr,
verb: Verb,
present_on: Side,
},
MissingSubresource {
gvr: Gvr,
sub: String,
present_on: Side,
},
ValidationShapeDiff {
engenho: StatusCause,
k3s: StatusCause,
},
DiscoveryDiff {
group: String,
version: String,
detail: String,
},
WatchEventDiff { index: usize, detail: String },
}
impl Divergence {
#[must_use]
pub fn severity(&self) -> Severity {
match self {
Self::FieldDiff { severity, .. }
| Self::MissingDefault { severity, .. }
| Self::ExtraField { severity, .. } => *severity,
_ => Severity::Hard,
}
}
#[must_use]
pub fn owning_crate(&self) -> &'static str {
match self {
Self::StatusCodeDiff { .. } | Self::MissingVerb { .. } => "engenho-apiserver",
Self::MissingResource { .. } => "engenho-apiserver / engenho-controllers",
Self::MissingDefault { .. } => {
"engenho-apiserver (handler defaulting) / engenho-controllers"
}
Self::ExtraField { .. } => "engenho-apiserver (handler stamping)",
Self::FieldDiff { .. } => "engenho-apiserver / engenho-store",
Self::MissingSubresource { .. } => "engenho-apiserver (router subresources)",
Self::ValidationShapeDiff { .. } => "engenho-apiserver (admission/validation)",
Self::DiscoveryDiff { .. } => "engenho-apiserver (discovery.rs)",
Self::WatchEventDiff { .. } => "engenho-store (watch) / engenho-apiserver",
}
}
#[must_use]
pub fn reroot(self, focus: &JsonPath) -> Self {
match self {
Self::MissingResource { path, present_on } => Self::MissingResource {
path: focus.concat(&path),
present_on,
},
Self::FieldDiff {
path,
engenho,
k3s,
severity,
} => Self::FieldDiff {
path: focus.concat(&path),
engenho,
k3s,
severity,
},
Self::MissingDefault {
path,
k3s_value,
severity,
} => Self::MissingDefault {
path: focus.concat(&path),
k3s_value,
severity,
},
Self::ExtraField {
path,
side,
value,
severity,
} => Self::ExtraField {
path: focus.concat(&path),
side,
value,
severity,
},
other => other,
}
}
#[must_use]
pub fn signature(&self) -> String {
let mut s = String::new();
let _ = match self {
Self::StatusCodeDiff { op, method, .. } => {
write!(s, "StatusCodeDiff:{op}:{}", method.label())
}
Self::MissingResource { path, present_on } => {
write!(s, "MissingResource:{path}:{present_on}")
}
Self::FieldDiff { path, .. } => write!(s, "FieldDiff:{path}"),
Self::MissingDefault { path, .. } => write!(s, "MissingDefault:{path}"),
Self::ExtraField { path, side, .. } => write!(s, "ExtraField:{path}:{side}"),
Self::MissingVerb {
gvr,
verb,
present_on,
} => write!(s, "MissingVerb:{gvr}:{verb}:{present_on}"),
Self::MissingSubresource {
gvr,
sub,
present_on,
} => write!(s, "MissingSubresource:{gvr}:{sub}:{present_on}"),
Self::ValidationShapeDiff { .. } => write!(s, "ValidationShapeDiff"),
Self::DiscoveryDiff {
group,
version,
detail,
} => write!(s, "DiscoveryDiff:{group}/{version}:{detail}"),
Self::WatchEventDiff { index, .. } => write!(s, "WatchEventDiff:{index}"),
};
s
}
}
impl fmt::Display for Divergence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sev = self.severity().label();
match self {
Self::StatusCodeDiff {
op,
method,
engenho,
k3s,
} => write!(
f,
"[{sev}] StatusCodeDiff {op} ({}): engenho={engenho} k3s={k3s}",
method.label()
),
Self::MissingResource { path, present_on } => {
write!(
f,
"[{sev}] MissingResource {path}: present only on {present_on}"
)
}
Self::FieldDiff {
path, engenho, k3s, ..
} => write!(f, "[{sev}] FieldDiff {path}: engenho={engenho} k3s={k3s}"),
Self::MissingDefault {
path, k3s_value, ..
} => write!(
f,
"[{sev}] MissingDefault {path}: k3s sets {k3s_value}, engenho unset"
),
Self::ExtraField {
path, side, value, ..
} => write!(f, "[{sev}] ExtraField {path}: {side} adds {value}"),
Self::MissingVerb {
gvr,
verb,
present_on,
} => write!(
f,
"[{sev}] MissingVerb {gvr} verb={verb}: present only on {present_on}"
),
Self::MissingSubresource {
gvr,
sub,
present_on,
} => write!(
f,
"[{sev}] MissingSubresource {gvr}/{sub}: present only on {present_on}"
),
Self::ValidationShapeDiff { engenho, k3s } => write!(
f,
"[{sev}] ValidationShapeDiff: engenho={engenho:?} k3s={k3s:?}"
),
Self::DiscoveryDiff {
group,
version,
detail,
} => write!(f, "[{sev}] DiscoveryDiff {group}/{version}: {detail}"),
Self::WatchEventDiff { index, detail } => {
write!(f, "[{sev}] WatchEventDiff #{index}: {detail}")
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParityWitness(());
impl ParityWitness {
pub(crate) fn new() -> Self {
Self(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Verdict {
Parity(ParityWitness),
Divergent(Vec<Divergence>),
ReferenceUnreachable,
}
impl Verdict {
#[must_use]
pub fn is_parity(&self) -> bool {
matches!(self, Self::Parity(_))
}
#[must_use]
pub fn divergences(&self) -> &[Divergence] {
match self {
Self::Divergent(d) => d,
_ => &[],
}
}
}