use crate::{decode_str, encode_str, fingerprint_str, UniversalError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrappedField {
pub name: String,
pub d: String,
pub f: String,
}
impl WrappedField {
pub fn wrap(name: &str, value: &str) -> Self {
Self {
name: name.to_string(),
d: encode_str(value),
f: fingerprint_str(value),
}
}
pub fn verify(&self) -> Result<String, UniversalError> {
let decoded = decode_str(&self.d)?;
let actual_fp = fingerprint_str(&decoded);
if actual_fp != self.f {
return Err(UniversalError::IntegrityViolation {
expected: self.f.clone(),
actual: actual_fp,
});
}
Ok(decoded)
}
pub fn is_intact(&self) -> bool {
self.verify().is_ok()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UniversalStruct {
pub fields: Vec<WrappedField>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldVerifyResult {
pub name: String,
pub intact: bool,
pub value: Option<String>,
pub error: Option<String>,
}
impl std::fmt::Display for FieldVerifyResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.intact {
write!(f, "{}: Intact", self.name)
} else {
write!(
f,
"{}: Violated ({})",
self.name,
self.error.as_deref().unwrap_or("unknown")
)
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructVerifyResult {
pub all_intact: bool,
pub fields: Vec<FieldVerifyResult>,
pub violations: Vec<String>,
}
impl std::fmt::Display for StructVerifyResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.all_intact {
write!(f, "All {} fields intact", self.fields.len())
} else {
write!(f, "Violations in: {}", self.violations.join(", "))
}
}
}
impl UniversalStruct {
#[must_use]
pub fn wrap_fields(fields: &[(&str, &str)]) -> Self {
Self {
fields: fields
.iter()
.map(|(name, value)| WrappedField::wrap(name, value))
.collect(),
}
}
pub fn verify_all(&self) -> StructVerifyResult {
let mut all_intact = true;
let mut violations = Vec::new();
let fields = self
.fields
.iter()
.map(|f| match f.verify() {
Ok(value) => FieldVerifyResult {
name: f.name.clone(),
intact: true,
value: Some(value),
error: None,
},
Err(e) => {
all_intact = false;
violations.push(f.name.clone());
FieldVerifyResult {
name: f.name.clone(),
intact: false,
value: None,
error: Some(e.to_string()),
}
}
})
.collect();
StructVerifyResult {
all_intact,
fields,
violations,
}
}
pub fn get(&self, name: &str) -> Result<String, UniversalError> {
self.fields
.iter()
.find(|f| f.name == name)
.ok_or_else(|| {
UniversalError::MalformedEnvelope(format!("field '{}' not found", name))
})?
.verify()
}
pub fn to_map(&self) -> Result<HashMap<String, String>, UniversalError> {
let result = self.verify_all();
if !result.all_intact {
return Err(UniversalError::IntegrityViolation {
expected: "all fields intact".to_string(),
actual: format!("violations in: {}", result.violations.join(", ")),
});
}
Ok(result
.fields
.into_iter()
.filter_map(|f| f.value.map(|v| (f.name, v)))
.collect())
}
pub fn assert_intact(&self) {
let result = self.verify_all();
if !result.all_intact {
panic!(
"Entrouter Universal: field integrity violations in: {}",
result.violations.join(", ")
);
}
}
pub fn report(&self) -> String {
let result = self.verify_all();
let mut out = String::new();
out.push_str("━━━━ Entrouter Universal Field Report ━━━━\n");
out.push_str(&format!("All intact: {}\n\n", result.all_intact));
for field in &result.fields {
let status = if field.intact { "✅" } else { "❌ VIOLATED" };
out.push_str(&format!(
" {}: {} - {}\n",
field.name,
status,
field.value.as_deref().unwrap_or("-")
));
if let Some(err) = &field.error {
out.push_str(&format!(" Error: {}\n", err));
}
}
out.push_str("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
out
}
pub fn to_json(&self) -> Result<String, UniversalError> {
serde_json::to_string(self).map_err(|e| UniversalError::SerializationError(e.to_string()))
}
pub fn from_json(s: &str) -> Result<Self, UniversalError> {
serde_json::from_str(s).map_err(|e| UniversalError::SerializationError(e.to_string()))
}
}