use serde_json::Value as J;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnvelopeFailureCode {
MissingVersion,
MalformedVersion,
UnsupportedMajor,
UnsupportedMinor,
}
impl EnvelopeFailureCode {
pub fn as_str(self) -> &'static str {
match self {
EnvelopeFailureCode::MissingVersion => "MISSING_VERSION",
EnvelopeFailureCode::MalformedVersion => "MALFORMED_VERSION",
EnvelopeFailureCode::UnsupportedMajor => "UNSUPPORTED_MAJOR",
EnvelopeFailureCode::UnsupportedMinor => "UNSUPPORTED_MINOR",
}
}
}
#[derive(Debug, Clone)]
pub struct EnvelopeFailure {
pub code: EnvelopeFailureCode,
pub message: String,
}
impl std::fmt::Display for EnvelopeFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code.as_str(), self.message)
}
}
impl std::error::Error for EnvelopeFailure {}
fn fail<T>(code: EnvelopeFailureCode, message: impl Into<String>) -> Result<T, EnvelopeFailure> {
Err(EnvelopeFailure {
code,
message: message.into(),
})
}
fn parse_version(version: &str, label: &str) -> Result<(u64, u64), EnvelopeFailure> {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() != 2 {
return fail(
EnvelopeFailureCode::MalformedVersion,
format!("{label}: version '{version}' must be '<major>.<minor>'"),
);
}
let is_int = |s: &str| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit());
if !is_int(parts[0]) || !is_int(parts[1]) {
return fail(
EnvelopeFailureCode::MalformedVersion,
format!("{label}: version '{version}' must be two integers '<major>.<minor>'"),
);
}
Ok((parts[0].parse().unwrap(), parts[1].parse().unwrap()))
}
pub fn validate_envelope(
envelope: &J,
supported: &str,
field: Option<&str>,
label: Option<&str>,
) -> Result<(), EnvelopeFailure> {
let field = field.unwrap_or("specVersion");
let label = label.unwrap_or("envelope");
let obj = match envelope {
J::Object(o) => o,
_ => {
return fail(
EnvelopeFailureCode::MissingVersion,
format!("{label}: not an object"),
)
}
};
let raw = match obj.get(field) {
Some(J::String(s)) => s,
_ => {
return fail(
EnvelopeFailureCode::MissingVersion,
format!("{label}: missing string '{field}'"),
)
}
};
let (gmaj, gmin) = parse_version(raw, label)?;
let (wmaj, wmin) = parse_version(supported, "supported")?;
if gmaj != wmaj {
return fail(
EnvelopeFailureCode::UnsupportedMajor,
format!("{label}: version {raw} has major {gmaj} but runtime supports major {wmaj}"),
);
}
if gmin > wmin {
return fail(
EnvelopeFailureCode::UnsupportedMinor,
format!("{label}: version {raw} has minor {gmin} > supported minor {wmin}"),
);
}
Ok(())
}