use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use super::NdfDocument;
use crate::NormaxisPdfError;
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NdfRecordStatus {
#[default]
Active,
Superseded,
Archived,
}
impl NdfRecordStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Active => "active",
Self::Superseded => "superseded",
Self::Archived => "archived",
}
}
}
impl std::fmt::Display for NdfRecordStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NdfRecord {
pub document_id: String,
pub title: String,
pub entity: String,
pub classification: String,
pub created_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_ref: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_type: Option<String>,
pub status: NdfRecordStatus,
pub integrity_hash: String,
pub payload: String,
}
impl NdfRecord {
pub fn from_ndf(ndf: &NdfDocument) -> crate::Result<Self> {
let status = if ndf.is_superseded() {
NdfRecordStatus::Superseded
} else {
NdfRecordStatus::Active
};
Ok(Self {
document_id: ndf.audit.document_id.clone(),
title: ndf.meta.title.clone(),
entity: ndf.meta.entity.clone(),
classification: ndf.meta.classification.clone(),
created_at: ndf.meta.created_at.clone(),
template_id: ndf.origin.ndt_template_id.clone(),
document_ref: ndf.meta.document_ref.clone(),
document_type: ndf.meta.document_type.clone(),
status,
integrity_hash: ndf.integrity.ndf_hash.clone(),
payload: ndf.to_canonical_json()?,
})
}
pub fn to_ndf(&self) -> crate::Result<NdfDocument> {
serde_json::from_str(&self.payload)
.map_err(|e| NormaxisPdfError::SerdeError(e.to_string()))
}
pub fn payload_sha256(&self) -> String {
let hash = Sha256::digest(self.payload.as_bytes());
format!("sha256:{}", hex::encode(hash))
}
pub fn summary(&self) -> NdfRecordSummary {
NdfRecordSummary {
document_id: self.document_id.clone(),
title: self.title.clone(),
entity: self.entity.clone(),
classification: self.classification.clone(),
created_at: self.created_at.clone(),
template_id: self.template_id.clone(),
document_ref: self.document_ref.clone(),
document_type: self.document_type.clone(),
status: self.status.clone(),
integrity_hash: self.integrity_hash.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NdfRecordSummary {
pub document_id: String,
pub title: String,
pub entity: String,
pub classification: String,
pub created_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_ref: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_type: Option<String>,
pub status: NdfRecordStatus,
pub integrity_hash: String,
}
#[derive(Debug, Clone, Default)]
pub struct NdfFilter {
pub entity: Option<String>,
pub classification: Option<String>,
pub template_id: Option<String>,
pub document_type: Option<String>,
pub status: Option<NdfRecordStatus>,
pub created_after: Option<String>,
pub created_before: Option<String>,
}
pub trait NdfRegistry {
type Error: std::error::Error + Send + Sync + 'static;
fn save(&mut self, ndf: &NdfDocument) -> Result<NdfRecord, Self::Error>;
fn load(&self, document_id: &str) -> Result<NdfDocument, Self::Error>;
fn record(&self, document_id: &str) -> Result<NdfRecord, Self::Error>;
fn list(&self, filter: NdfFilter) -> Result<Vec<NdfRecordSummary>, Self::Error>;
fn exists(&self, document_id: &str) -> Result<bool, Self::Error>;
fn archive(&mut self, document_id: &str) -> Result<(), Self::Error>;
}