use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, ToSchema)]
pub struct Posture {
pub signed: bool,
pub encrypted: bool,
}
impl Posture {
pub const OPEN: Posture = Posture {
signed: false,
encrypted: false,
};
pub const fn new(signed: bool, encrypted: bool) -> Self {
Self { signed, encrypted }
}
pub const fn level(self) -> PostureLevel {
match (self.signed, self.encrypted) {
(false, _) => PostureLevel::Open,
(true, false) => PostureLevel::Authenticated,
(true, true) => PostureLevel::Confidential,
}
}
pub const fn is_secure(self) -> bool {
self.signed
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum PostureLevel {
Open,
Authenticated,
Confidential,
}
impl PostureLevel {
pub const fn as_wire(self) -> &'static str {
match self {
PostureLevel::Open => "open",
PostureLevel::Authenticated => "authenticated",
PostureLevel::Confidential => "confidential",
}
}
pub fn from_wire(s: &str) -> Option<Self> {
match s {
"open" => Some(PostureLevel::Open),
"authenticated" => Some(PostureLevel::Authenticated),
"confidential" => Some(PostureLevel::Confidential),
_ => None,
}
}
pub const fn to_posture(self) -> Posture {
match self {
PostureLevel::Open => Posture::OPEN,
PostureLevel::Authenticated => Posture::new(true, false),
PostureLevel::Confidential => Posture::new(true, true),
}
}
}
impl From<Posture> for PostureLevel {
fn from(p: Posture) -> Self {
p.level()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_is_neither_and_not_secure() {
assert_eq!(Posture::OPEN, Posture::new(false, false));
assert_eq!(Posture::OPEN.level(), PostureLevel::Open);
assert!(!Posture::OPEN.is_secure());
}
#[test]
fn default_posture_is_open() {
assert_eq!(Posture::default(), Posture::OPEN);
}
#[test]
fn signed_only_is_authenticated_and_secure() {
let p = Posture::new(true, false);
assert_eq!(p.level(), PostureLevel::Authenticated);
assert!(p.is_secure());
}
#[test]
fn signed_and_encrypted_is_confidential() {
let p = Posture::new(true, true);
assert_eq!(p.level(), PostureLevel::Confidential);
assert!(p.is_secure());
}
#[test]
fn encrypted_without_signed_degrades_to_open() {
let p = Posture::new(false, true);
assert_eq!(p.level(), PostureLevel::Open);
assert!(!p.is_secure());
}
#[test]
fn level_ordering_is_a_graduated_ladder() {
assert!(PostureLevel::Open < PostureLevel::Authenticated);
assert!(PostureLevel::Authenticated < PostureLevel::Confidential);
}
#[test]
fn from_posture_for_level() {
assert_eq!(
PostureLevel::from(Posture::new(true, false)),
PostureLevel::Authenticated
);
}
#[test]
fn posture_serde_round_trip() {
let p = Posture::new(true, false);
let json = serde_json::to_string(&p).unwrap();
assert_eq!(json, r#"{"signed":true,"encrypted":false}"#);
let back: Posture = serde_json::from_str(&json).unwrap();
assert_eq!(back, p);
}
#[test]
fn posture_level_serializes_snake_case() {
assert_eq!(
serde_json::to_string(&PostureLevel::Authenticated).unwrap(),
r#""authenticated""#
);
assert_eq!(
serde_json::to_string(&PostureLevel::Confidential).unwrap(),
r#""confidential""#
);
let back: PostureLevel = serde_json::from_str(r#""open""#).unwrap();
assert_eq!(back, PostureLevel::Open);
}
#[test]
fn as_wire_matches_serde_snake_case() {
for level in [
PostureLevel::Open,
PostureLevel::Authenticated,
PostureLevel::Confidential,
] {
let serde = serde_json::to_string(&level).unwrap();
assert_eq!(format!("\"{}\"", level.as_wire()), serde);
}
}
#[test]
fn from_wire_round_trips_as_wire() {
for level in [
PostureLevel::Open,
PostureLevel::Authenticated,
PostureLevel::Confidential,
] {
assert_eq!(PostureLevel::from_wire(level.as_wire()), Some(level));
}
assert_eq!(PostureLevel::from_wire("bogus"), None);
assert_eq!(PostureLevel::from_wire("Authenticated"), None); }
#[test]
fn to_posture_is_inverse_of_level() {
for posture in [
Posture::OPEN,
Posture::new(true, false),
Posture::new(true, true),
] {
assert_eq!(posture.level().to_posture(), posture);
}
}
}