use crate::{DIDWebVHError, parameters::Parameters};
use affinidi_data_integrity::{DataIntegrityProof, SignedDocument, SigningDocument};
use multibase::Base;
use multihash::Multihash;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json_canonicalizer::to_string;
use sha2::{Digest, Sha256};
use std::{collections::HashMap, fs::OpenOptions, io::Write};
use tracing::debug;
pub mod create;
pub mod read;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LogEntry {
pub version_id: String,
pub version_time: String,
pub parameters: Parameters,
pub state: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<DataIntegrityProof>,
}
impl LogEntry {
pub fn save_to_file(&self, file_path: &str) -> Result<(), DIDWebVHError> {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(file_path)
.map_err(|e| {
DIDWebVHError::LogEntryError(format!("Couldn't open file {}: {}", file_path, e))
})?;
file.write_all(
serde_json::to_string(self)
.map_err(|e| {
DIDWebVHError::LogEntryError(format!(
"Couldn't serialize LogEntry to JSON. Reason: {}",
e
))
})?
.as_bytes(),
)
.map_err(|e| {
DIDWebVHError::LogEntryError(format!(
"Couldn't append LogEntry to file({}). Reason: {}",
file_path, e
))
})?;
file.write_all("\n".as_bytes()).map_err(|e| {
DIDWebVHError::LogEntryError(format!(
"Couldn't append LogEntry to file({}). Reason: {}",
file_path, e
))
})?;
Ok(())
}
fn generate_scid(&self) -> Result<String, DIDWebVHError> {
self.generate_log_entry_hash().map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't generate SCID from preliminary LogEntry. Reason: {}",
e
))
})
}
pub fn generate_log_entry_hash(&self) -> Result<String, DIDWebVHError> {
let jcs = to_string(self).map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't generate JCS from LogEntry. Reason: {}",
e
))
})?;
debug!("JCS for LogEntry hash: {}", jcs);
let hash_encoded = Multihash::<32>::wrap(0x12, Sha256::digest(jcs.as_bytes()).as_slice())
.map_err(|e| {
DIDWebVHError::SCIDError(format!(
"Couldn't create multihash encoding for LogEntry. Reason: {}",
e
))
})?;
Ok(multibase::encode(Base::Base58Btc, hash_encoded.to_bytes()))
}
}
impl TryFrom<&LogEntry> for SigningDocument {
type Error = DIDWebVHError;
fn try_from(log_entry: &LogEntry) -> Result<Self, Self::Error> {
let mut signing = SigningDocument {
extra: HashMap::new(),
proof: None,
};
signing.extra.insert(
"versionId".to_string(),
log_entry.version_id.to_owned().into(),
);
signing.extra.insert(
"versionTime".to_string(),
log_entry.version_time.to_owned().into(),
);
signing.extra.insert(
"parameters".to_string(),
serde_json::to_value(&log_entry.parameters).map_err(|e| {
DIDWebVHError::ParametersError(format!(
"Couldn't serialize Paramaters to JSON Value: {}",
e
))
})?,
);
signing
.extra
.insert("state".to_string(), log_entry.state.clone());
if let Some(proof) = &log_entry.proof {
signing.extra.insert(
"proof".to_string(),
serde_json::to_value(proof).map_err(|e| {
DIDWebVHError::LogEntryError(format!(
"Couldn't serialize Data Integrity Proof to JSON Value: {}",
e
))
})?,
);
}
Ok(signing)
}
}
impl TryFrom<&LogEntry> for SignedDocument {
type Error = DIDWebVHError;
fn try_from(log_entry: &LogEntry) -> Result<Self, Self::Error> {
let mut signing = SignedDocument {
extra: HashMap::new(),
proof: log_entry.proof.clone(),
};
signing.extra.insert(
"versionId".to_string(),
log_entry.version_id.to_owned().into(),
);
signing.extra.insert(
"versionTime".to_string(),
log_entry.version_time.to_owned().into(),
);
signing.extra.insert(
"parameters".to_string(),
serde_json::to_value(&log_entry.parameters).map_err(|e| {
DIDWebVHError::ParametersError(format!(
"Couldn't serialize Paramaters to JSON Value: {}",
e
))
})?,
);
signing
.extra
.insert("state".to_string(), log_entry.state.clone());
Ok(signing)
}
}