use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::string::String;
use std::vec::Vec;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CanonicalError {
SerializationFailed(String),
NonDeterministic(String),
}
impl fmt::Display for CanonicalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CanonicalError::SerializationFailed(s) => {
write!(f, "Serialization failed: {}", s)
}
CanonicalError::NonDeterministic(s) => {
write!(f, "Non-deterministic content: {}", s)
}
}
}
}
pub type CanonicalResult<T> = core::result::Result<T, CanonicalError>;
#[derive(Default)]
pub struct CanonicalJson;
impl CanonicalJson {
pub fn serialize<T>(value: &T) -> CanonicalResult<String>
where
T: serde::Serialize,
{
serde_json::to_string(value)
.map_err(|e| CanonicalError::SerializationFailed(e.to_string()))
}
pub fn serialize_bytes<T>(value: &T) -> CanonicalResult<Vec<u8>>
where
T: serde::Serialize,
{
serde_json::to_vec(value)
.map_err(|e| CanonicalError::SerializationFailed(e.to_string()))
}
pub fn deserialize<'de, T>(s: &'de str) -> CanonicalResult<T>
where
T: serde::Deserialize<'de>,
{
serde_json::from_str(s)
.map_err(|e| CanonicalError::SerializationFailed(e.to_string()))
}
pub fn deserialize_bytes<'de, T>(bytes: &'de [u8]) -> CanonicalResult<T>
where
T: serde::Deserialize<'de>,
{
serde_json::from_slice(bytes)
.map_err(|e| CanonicalError::SerializationFailed(e.to_string()))
}
}
pub trait StableSerialize: serde::Serialize {
fn to_canonical_json(&self) -> CanonicalResult<String>
where
Self: Sized,
{
CanonicalJson::serialize(self)
}
fn to_canonical_json_bytes(&self) -> CanonicalResult<Vec<u8>>
where
Self: Sized,
{
CanonicalJson::serialize_bytes(self)
}
}
impl<T: serde::Serialize> StableSerialize for T {}
pub type StableMap<K, V> = BTreeMap<K, V>;
pub type StableSet<T> = BTreeSet<T>;
#[cfg(test)]
mod tests {
use super::*;
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct TestStruct {
a: u32,
b: String,
c: Vec<u32>,
}
#[test]
fn test_canonical_json_stable() {
let val = TestStruct {
a: 1,
b: "test".to_string(),
c: vec![2, 3, 1],
};
let json1 = CanonicalJson::serialize(&val).unwrap();
let json2 = CanonicalJson::serialize(&val).unwrap();
assert_eq!(json1, json2);
}
#[test]
fn test_canonical_roundtrip() {
let original = TestStruct {
a: 42,
b: "hello".to_string(),
c: vec![1, 2, 3],
};
let json = CanonicalJson::serialize(&original).unwrap();
let deserialized: TestStruct = CanonicalJson::deserialize(&json).unwrap();
assert_eq!(original, deserialized);
}
}