#![forbid(unsafe_code)]
#![deny(missing_docs)]
use serde::{de::DeserializeOwned, Serialize};
use std::io::Read;
use thiserror::Error;
pub mod binary;
pub use binary::{
decode_frame, decode_varint_u64, encode_frame, encode_varint_u64, BinaryCodecError, Decoder,
Encoder, T_BYTES, T_CID32, T_PUBKEY32, T_SIG64, T_STR, T_U64,
};
#[derive(Debug, Error)]
pub enum AtomicCodecError {
#[error("serde: {0}")]
Serde(#[from] serde_json::Error),
#[error("canon: {0}")]
Canon(String),
#[error("yaml: {0}")]
Yaml(String),
}
pub fn to_canon_vec<T: Serialize>(v: &T) -> Result<Vec<u8>, AtomicCodecError> {
let val = serde_json::to_value(v)?;
json_atomic::canonize(&val).map_err(|e| AtomicCodecError::Canon(e.to_string()))
}
pub fn from_canon_slice<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, AtomicCodecError> {
Ok(serde_json::from_slice(bytes)?)
}
pub fn from_json_str_canon(s: &str) -> Result<Vec<u8>, AtomicCodecError> {
let v: serde_json::Value = serde_json::from_str(s)?;
json_atomic::canonize(&v).map_err(|e| AtomicCodecError::Canon(e.to_string()))
}
pub fn to_cid_hex<T: Serialize>(v: &T) -> Result<String, AtomicCodecError> {
let b = to_canon_vec(v)?;
Ok(blake3::hash(&b).to_hex().to_string())
}
pub struct Canonical<T> {
value: T,
bytes: Vec<u8>,
}
impl<T> Canonical<T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(value: T) -> Result<Self, AtomicCodecError> {
let bytes = to_canon_vec(&value)?;
Ok(Self { value, bytes })
}
pub fn from_reader<R: Read>(mut r: R) -> Result<Self, AtomicCodecError> {
let mut s = String::new();
r.read_to_string(&mut s)
.map_err(|e| AtomicCodecError::Canon(e.to_string()))?;
let v: serde_json::Value = serde_json::from_str(&s)?;
let bytes =
json_atomic::canonize(&v).map_err(|e| AtomicCodecError::Canon(e.to_string()))?;
Ok(Self {
value: serde_json::from_value(v)?,
bytes,
})
}
pub const fn value(&self) -> &T {
&self.value
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
#[must_use]
pub fn is_canonical(s: &str) -> bool {
serde_json::from_str::<serde_json::Value>(s)
.ok()
.and_then(|v| json_atomic::canonize(&v).ok())
.and_then(|b| String::from_utf8(b).ok())
.is_some_and(|canon| canon == s.trim())
}
pub fn yaml_to_canon_vec(yaml: &str) -> Result<Vec<u8>, AtomicCodecError> {
let v: serde_json::Value = match serde_yaml::from_str::<serde_yaml::Value>(yaml) {
Ok(doc) => serde_json::to_value(doc).map_err(|e| AtomicCodecError::Yaml(e.to_string()))?,
Err(e) => return Err(AtomicCodecError::Yaml(e.to_string())),
};
json_atomic::canonize(&v).map_err(|e| AtomicCodecError::Canon(e.to_string()))
}