use std::fmt;
use crate::setup_core::ReasonCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum WireReason {
AdaptationBindingMissing,
AdaptationBindingMismatch,
DigestMismatch,
LimitExceeded,
LinkNotAllowed,
PathDuplicate,
PathEscapesTarget,
PathNotRelative,
SpecialFileNotAllowed,
UnsupportedBundleFormat,
UnsupportedComponentKind,
UnsupportedNativeSurface,
UnsupportedProtocolVersion,
UnsupportedOperation,
ProjectionProfileMismatch,
UnsupportedPermissionProfile,
UnsupportedPlatform,
UnsupportedArchitecture,
RecoveryRequired,
Stale,
ProviderUnavailable,
}
impl WireReason {
pub const ALL: &'static [Self] = &[
Self::AdaptationBindingMissing,
Self::AdaptationBindingMismatch,
Self::DigestMismatch,
Self::LimitExceeded,
Self::LinkNotAllowed,
Self::PathDuplicate,
Self::PathEscapesTarget,
Self::PathNotRelative,
Self::SpecialFileNotAllowed,
Self::UnsupportedBundleFormat,
Self::UnsupportedComponentKind,
Self::UnsupportedNativeSurface,
Self::UnsupportedProtocolVersion,
Self::UnsupportedOperation,
Self::ProjectionProfileMismatch,
Self::UnsupportedPermissionProfile,
Self::UnsupportedPlatform,
Self::UnsupportedArchitecture,
Self::RecoveryRequired,
Self::Stale,
Self::ProviderUnavailable,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::AdaptationBindingMissing => "adaptation_binding_missing",
Self::AdaptationBindingMismatch => "adaptation_binding_mismatch",
Self::DigestMismatch => "digest_mismatch",
Self::LimitExceeded => "limit_exceeded",
Self::LinkNotAllowed => "link_not_allowed",
Self::PathDuplicate => "path_duplicate",
Self::PathEscapesTarget => "path_escapes_target",
Self::PathNotRelative => "path_not_relative",
Self::SpecialFileNotAllowed => "special_file_not_allowed",
Self::UnsupportedBundleFormat => "unsupported_bundle_format",
Self::UnsupportedComponentKind => "unsupported_component_kind",
Self::UnsupportedNativeSurface => "unsupported_native_surface",
Self::UnsupportedProtocolVersion => "unsupported_protocol_version",
Self::UnsupportedOperation => "unsupported_operation",
Self::UnsupportedPermissionProfile => "unsupported_permission_profile",
Self::ProjectionProfileMismatch => "projection_profile_mismatch",
Self::UnsupportedPlatform => "unsupported_platform",
Self::UnsupportedArchitecture => "unsupported_architecture",
Self::RecoveryRequired => "recovery_required",
Self::Stale => "stale",
Self::ProviderUnavailable => "provider_unavailable",
}
}
#[must_use]
pub fn parse(text: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|reason| reason.as_str() == text)
}
}
impl fmt::Display for WireReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<ReasonCode> for WireReason {
fn from(reason: ReasonCode) -> Self {
match reason {
ReasonCode::UnsupportedOperation => Self::UnsupportedOperation,
ReasonCode::UnsupportedComponentKind => Self::UnsupportedComponentKind,
ReasonCode::UnsupportedNativeSurface => Self::UnsupportedNativeSurface,
ReasonCode::UnsupportedBundleFormat => Self::UnsupportedBundleFormat,
ReasonCode::UnsupportedProtocolVersion => Self::UnsupportedProtocolVersion,
ReasonCode::ProjectionProfileMismatch => Self::ProjectionProfileMismatch,
ReasonCode::UnsupportedPermissionProfile => Self::UnsupportedPermissionProfile,
ReasonCode::UnsupportedPlatform => Self::UnsupportedPlatform,
ReasonCode::UnsupportedArchitecture => Self::UnsupportedArchitecture,
ReasonCode::RecoveryRequired => Self::RecoveryRequired,
ReasonCode::Stale => Self::Stale,
ReasonCode::IntegrityMismatch => Self::DigestMismatch,
ReasonCode::InvalidTarget
| ReasonCode::LockUnavailable
| ReasonCode::StateUnavailable => Self::ProviderUnavailable,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic)]
use super::*;
use crate::provider_v3::vocabulary::kit::{json, strings};
fn expected_reasons(group: &str) -> Vec<String> {
json("conformance-cases.json")[group]
.as_array()
.unwrap_or_else(|| panic!("{group} is not an array"))
.iter()
.map(|case| case["expected_reason"].as_str().unwrap().to_owned())
.collect()
}
#[test]
fn every_bundle_rejection_the_kit_names_has_a_wire_reason() {
for reason in expected_reasons("bundle_rejections") {
assert!(
WireReason::parse(&reason).is_some(),
"no wire reason for {reason}"
);
}
}
#[test]
fn every_capability_rejection_the_kit_names_has_a_wire_reason() {
for reason in expected_reasons("capability_rejections") {
assert!(
WireReason::parse(&reason).is_some(),
"no wire reason for {reason}"
);
}
}
#[test]
fn every_unsupported_reason_in_the_manifest_has_a_wire_reason() {
for reason in strings(&json("manifest.json"), "unsupported_reasons") {
assert!(
WireReason::parse(&reason).is_some(),
"no wire reason for {reason}"
);
}
}
#[test]
fn the_two_prose_reasons_are_present_and_not_confused_with_a_list_one() {
assert_eq!(
WireReason::parse("recovery_required"),
Some(WireReason::RecoveryRequired)
);
assert_eq!(WireReason::parse("stale"), Some(WireReason::Stale));
}
#[test]
fn spellings_are_unique_so_two_reasons_never_read_as_one() {
let mut seen: Vec<&str> = WireReason::ALL.iter().map(|r| r.as_str()).collect();
let total = seen.len();
seen.sort_unstable();
seen.dedup();
assert_eq!(seen.len(), total);
}
#[test]
fn a_kernel_lock_failure_is_not_dressed_up_as_a_contract_refusal() {
assert_eq!(
WireReason::from(ReasonCode::LockUnavailable),
WireReason::ProviderUnavailable
);
assert_eq!(
WireReason::from(ReasonCode::InvalidTarget),
WireReason::ProviderUnavailable
);
assert_eq!(
WireReason::from(ReasonCode::StateUnavailable),
WireReason::ProviderUnavailable
);
}
#[test]
fn the_kernel_reasons_that_are_contract_reasons_keep_their_spelling() {
assert_eq!(
WireReason::from(ReasonCode::RecoveryRequired).as_str(),
"recovery_required"
);
assert_eq!(WireReason::from(ReasonCode::Stale).as_str(), "stale");
assert_eq!(
WireReason::from(ReasonCode::UnsupportedPlatform).as_str(),
"unsupported_platform"
);
}
}