use serde::{Deserialize, Serialize};
use crate::error::ContextError;
use crate::registry;
use crate::scope::ScopeGuard;
use crate::storage;
const WIRE_VERSION: u32 = 2;
#[derive(Serialize, Deserialize)]
struct WireContext {
version: u32,
entries: Vec<WireEntry>,
scope_chain: Vec<String>,
}
#[derive(Serialize, Deserialize)]
struct WireContextV1 {
version: u32,
entries: Vec<WireEntry>,
}
#[derive(Serialize, Deserialize)]
struct WireEntry {
key: String,
key_version: u32,
value: Vec<u8>,
}
pub fn serialize_context() -> Result<Vec<u8>, ContextError> {
let values = storage::collect_values();
let mut entries = Vec::new();
for (key, val) in &values {
let info = registry::get_serialization_info(key);
let is_local = val.is_local()
|| info.as_ref().map_or(false, |i| i.local_only);
if is_local {
continue;
}
let value_bytes = match info.as_ref().and_then(|i| i.serialize_fn.as_ref()) {
Some(custom_ser) => custom_ser(val.as_ref())?,
None => val.serialize_value()?,
};
let key_version = info.as_ref().map_or(1, |i| i.key_version);
entries.push(WireEntry {
key: key.to_string(),
key_version,
value: value_bytes,
});
}
let wire = WireContext {
version: WIRE_VERSION,
entries,
scope_chain: storage::collect_scope_chain(),
};
let bytes = bincode::serialize(&wire)
.map_err(|e| ContextError::SerializationFailed(e.to_string()))?;
crate::config::check_size(bytes.len())?;
Ok(bytes)
}
#[cfg(feature = "base64")]
pub fn serialize_context_string() -> Result<String, ContextError> {
use base64::Engine;
let bytes = serialize_context()?;
Ok(base64::engine::general_purpose::STANDARD.encode(&bytes))
}
pub fn deserialize_context(bytes: &[u8]) -> Result<ScopeGuard, ContextError> {
let (entries, scope_chain) = deserialize_wire(bytes)?;
let guard = storage::enter_scope();
if !scope_chain.is_empty() {
storage::set_remote_chain(scope_chain);
}
for entry in &entries {
let key_str = entry.key.as_str();
let restored = registry::with_registration(key_str, |reg| {
if reg.local_only || reg.deserializers.is_empty() {
return None; }
match reg.deserializers.get(&entry.key_version) {
Some(deser_fn) => {
let val = deser_fn(&entry.value);
Some((reg.key, val))
}
None => Some((reg.key, Err(ContextError::DeserializationFailed(format!(
"no deserializer for key '{}' wire version {} (registered versions: {:?})",
key_str,
entry.key_version,
reg.deserializers.keys().collect::<Vec<_>>()
)))))
}
});
match restored {
Some(Some((static_key, Ok(val)))) => {
storage::set_value(static_key, std::sync::Arc::from(val));
}
Some(Some((_, Err(e)))) => return Err(e),
Some(None) | None => {
}
}
}
Ok(guard)
}
fn deserialize_wire(bytes: &[u8]) -> Result<(Vec<WireEntry>, Vec<String>), ContextError> {
if let Ok(wire) = bincode::deserialize::<WireContext>(bytes) {
if wire.version == 2 {
return Ok((wire.entries, wire.scope_chain));
}
if wire.version > 2 {
return Err(ContextError::DeserializationFailed(format!(
"unsupported wire version: {} (this library supports versions 1 and 2)",
wire.version
)));
}
}
let wire: WireContextV1 = bincode::deserialize(bytes)
.map_err(|e| ContextError::DeserializationFailed(e.to_string()))?;
if wire.version != 1 {
return Err(ContextError::DeserializationFailed(format!(
"unsupported wire version: {} (expected 1 or 2)",
wire.version
)));
}
Ok((wire.entries, Vec::new()))
}
#[cfg(feature = "base64")]
pub fn deserialize_context_string(encoded: &str) -> Result<ScopeGuard, ContextError> {
use base64::Engine;
let bytes = base64::engine::general_purpose::STANDARD
.decode(encoded)
.map_err(|e| ContextError::DeserializationFailed(e.to_string()))?;
deserialize_context(&bytes)
}
pub fn make_wire_bytes(key: &str, key_version: u32, value_bytes: &[u8]) -> Vec<u8> {
make_wire_bytes_v(2, key, key_version, value_bytes)
}
pub fn make_wire_bytes_v(wire_version: u32, key: &str, key_version: u32, value_bytes: &[u8]) -> Vec<u8> {
if wire_version == 1 {
let wire = WireContextV1 {
version: 1,
entries: vec![WireEntry {
key: key.to_string(),
key_version,
value: value_bytes.to_vec(),
}],
};
bincode::serialize(&wire)
.expect("dcontext::make_wire_bytes_v: bincode serialization should not fail")
} else {
let wire = WireContext {
version: wire_version,
entries: vec![WireEntry {
key: key.to_string(),
key_version,
value: value_bytes.to_vec(),
}],
scope_chain: Vec::new(),
};
bincode::serialize(&wire)
.expect("dcontext::make_wire_bytes_v: bincode serialization should not fail")
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
pub use super::{make_wire_bytes, make_wire_bytes_v};
}