use crate::{Error, Ipld, Result};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
pub trait Codec: Send + Sync {
fn encode(&self, data: &Ipld) -> Result<Vec<u8>>;
fn decode(&self, bytes: &[u8]) -> Result<Ipld>;
fn code(&self) -> u64;
fn name(&self) -> &str;
}
#[derive(Debug, Clone, Default)]
pub struct DagCborCodec;
impl Codec for DagCborCodec {
fn encode(&self, data: &Ipld) -> Result<Vec<u8>> {
data.to_dag_cbor()
}
fn decode(&self, bytes: &[u8]) -> Result<Ipld> {
Ipld::from_dag_cbor(bytes)
}
fn code(&self) -> u64 {
crate::cid::codec::DAG_CBOR
}
fn name(&self) -> &str {
"dag-cbor"
}
}
#[derive(Debug, Clone, Default)]
pub struct DagJsonCodec;
impl Codec for DagJsonCodec {
fn encode(&self, data: &Ipld) -> Result<Vec<u8>> {
let json_str = data.to_dag_json()?;
Ok(json_str.into_bytes())
}
fn decode(&self, bytes: &[u8]) -> Result<Ipld> {
let json_str = std::str::from_utf8(bytes)
.map_err(|e| Error::Deserialization(format!("Invalid UTF-8: {}", e)))?;
Ipld::from_dag_json(json_str)
}
fn code(&self) -> u64 {
crate::cid::codec::DAG_JSON
}
fn name(&self) -> &str {
"dag-json"
}
}
#[derive(Debug, Clone, Default)]
pub struct RawCodec;
impl Codec for RawCodec {
fn encode(&self, data: &Ipld) -> Result<Vec<u8>> {
match data {
Ipld::Bytes(bytes) => Ok(bytes.clone()),
_ => Err(Error::Serialization(
"RAW codec requires Ipld::Bytes".to_string(),
)),
}
}
fn decode(&self, bytes: &[u8]) -> Result<Ipld> {
Ok(Ipld::Bytes(bytes.to_vec()))
}
fn code(&self) -> u64 {
crate::cid::codec::RAW
}
fn name(&self) -> &str {
"raw"
}
}
#[derive(Clone)]
pub struct CodecRegistry {
codecs: Arc<RwLock<HashMap<u64, Arc<dyn Codec>>>>,
}
impl Default for CodecRegistry {
fn default() -> Self {
Self::new()
}
}
impl CodecRegistry {
pub fn new() -> Self {
let registry = Self {
codecs: Arc::new(RwLock::new(HashMap::new())),
};
registry.register(Arc::new(RawCodec));
registry.register(Arc::new(DagCborCodec));
registry.register(Arc::new(DagJsonCodec));
registry
}
pub fn register(&self, codec: Arc<dyn Codec>) {
let code = codec.code();
let mut codecs = self.codecs.write().unwrap_or_else(|e| e.into_inner());
codecs.insert(code, codec);
}
pub fn get(&self, code: u64) -> Option<Arc<dyn Codec>> {
let codecs = self.codecs.read().unwrap_or_else(|e| e.into_inner());
codecs.get(&code).cloned()
}
pub fn has_codec(&self, code: u64) -> bool {
let codecs = self.codecs.read().unwrap_or_else(|e| e.into_inner());
codecs.contains_key(&code)
}
pub fn list_codecs(&self) -> Vec<u64> {
let codecs = self.codecs.read().unwrap_or_else(|e| e.into_inner());
codecs.keys().copied().collect()
}
pub fn get_name(&self, code: u64) -> Option<String> {
self.get(code).map(|c| c.name().to_string())
}
pub fn encode(&self, codec_code: u64, data: &Ipld) -> Result<Vec<u8>> {
let codec = self.get(codec_code).ok_or_else(|| {
Error::Serialization(format!("Codec 0x{:x} not registered", codec_code))
})?;
codec.encode(data)
}
pub fn decode(&self, codec_code: u64, bytes: &[u8]) -> Result<Ipld> {
let codec = self.get(codec_code).ok_or_else(|| {
Error::Deserialization(format!("Codec 0x{:x} not registered", codec_code))
})?;
codec.decode(bytes)
}
}
static GLOBAL_CODEC_REGISTRY: std::sync::OnceLock<CodecRegistry> = std::sync::OnceLock::new();
pub fn global_codec_registry() -> &'static CodecRegistry {
GLOBAL_CODEC_REGISTRY.get_or_init(CodecRegistry::new)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cid::codec;
use std::collections::BTreeMap;
#[test]
fn test_raw_codec() {
let codec = RawCodec;
let data = Ipld::Bytes(b"hello".to_vec());
let encoded = codec.encode(&data).unwrap();
assert_eq!(encoded, b"hello");
let decoded = codec.decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_raw_codec_requires_bytes() {
let codec = RawCodec;
let data = Ipld::String("hello".to_string());
assert!(codec.encode(&data).is_err());
}
#[test]
fn test_dag_cbor_codec() {
let codec = DagCborCodec;
let mut map = BTreeMap::new();
map.insert("key".to_string(), Ipld::Integer(42));
let data = Ipld::Map(map);
let encoded = codec.encode(&data).unwrap();
let decoded = codec.decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_dag_json_codec() {
let codec = DagJsonCodec;
let data = Ipld::String("hello".to_string());
let encoded = codec.encode(&data).unwrap();
let decoded = codec.decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_codec_registry() {
let registry = CodecRegistry::new();
assert!(registry.has_codec(codec::RAW));
assert!(registry.has_codec(codec::DAG_CBOR));
assert!(registry.has_codec(codec::DAG_JSON));
let cbor_codec = registry.get(codec::DAG_CBOR).unwrap();
assert_eq!(cbor_codec.code(), codec::DAG_CBOR);
assert_eq!(cbor_codec.name(), "dag-cbor");
}
#[test]
fn test_registry_encode_decode() {
let registry = CodecRegistry::new();
let data = Ipld::Integer(12345);
let encoded = registry.encode(codec::DAG_CBOR, &data).unwrap();
let decoded = registry.decode(codec::DAG_CBOR, &encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_registry_list_codecs() {
let registry = CodecRegistry::new();
let codecs = registry.list_codecs();
assert!(codecs.contains(&codec::RAW));
assert!(codecs.contains(&codec::DAG_CBOR));
assert!(codecs.contains(&codec::DAG_JSON));
assert_eq!(codecs.len(), 3);
}
#[test]
fn test_registry_get_name() {
let registry = CodecRegistry::new();
assert_eq!(registry.get_name(codec::RAW).unwrap(), "raw");
assert_eq!(registry.get_name(codec::DAG_CBOR).unwrap(), "dag-cbor");
assert_eq!(registry.get_name(codec::DAG_JSON).unwrap(), "dag-json");
assert!(registry.get_name(0x9999).is_none());
}
#[test]
fn test_unregistered_codec_error() {
let registry = CodecRegistry::new();
let data = Ipld::Integer(42);
let result = registry.encode(0x9999, &data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not registered"));
}
#[test]
fn test_global_registry() {
let registry = global_codec_registry();
assert!(registry.has_codec(codec::RAW));
assert!(registry.has_codec(codec::DAG_CBOR));
assert!(registry.has_codec(codec::DAG_JSON));
}
#[test]
fn test_codec_replacement() {
let registry = CodecRegistry::new();
registry.register(Arc::new(RawCodec));
assert!(registry.has_codec(codec::RAW));
}
#[test]
fn test_codec_codes() {
assert_eq!(RawCodec.code(), codec::RAW);
assert_eq!(DagCborCodec.code(), codec::DAG_CBOR);
assert_eq!(DagJsonCodec.code(), codec::DAG_JSON);
}
#[test]
fn test_codec_names() {
assert_eq!(RawCodec.name(), "raw");
assert_eq!(DagCborCodec.name(), "dag-cbor");
assert_eq!(DagJsonCodec.name(), "dag-json");
}
}