main
fn encode_hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
let _ = write!(out, "{byte:02x}");
}
out
}
fn decode_hex(text: &str) -> Result<Vec<u8>, String> {
if !text.len().is_multiple_of(2) {
return Err("hex string must have an even number of digits".to_string());
}
let mut out = Vec::with_capacity(text.len() / 2);
let raw = text.as_bytes();
for pair in raw.chunks(2) {
let hi = (pair[0] as char)
.to_digit(16)
.ok_or_else(|| format!("invalid hex digit `{}`", pair[0] as char))?;
let lo = (pair[1] as char)
.to_digit(16)
.ok_or_else(|| format!("invalid hex digit `{}`", pair[1] as char))?;
out.push(((hi << 4) | lo) as u8);
}
Ok(out)
}
fn timestamp_to_text(value: &prost_types::Timestamp) -> Option<String> {
chrono::DateTime::from_timestamp(value.seconds, value.nanos as u32).map(|dt| dt.to_rfc3339())
}
fn timestamp_from_text(text: &str) -> Result<prost_types::Timestamp, String> {
let parsed = chrono::DateTime::parse_from_rfc3339(text).map_err(|err| err.to_string())?;
let utc = parsed.with_timezone(&chrono::Utc);
Ok(prost_types::Timestamp {
seconds: utc.timestamp(),
nanos: utc.timestamp_subsec_nanos() as i32,
})
}
pub mod bytes {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&super::encode_hex(value))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let text = String::deserialize(deserializer)?;
super::decode_hex(&text).map_err(serde::de::Error::custom)
}
}
pub mod bytes_list {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(value: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error> {
let texts: Vec<String> = value.iter().map(|item| super::encode_hex(item)).collect();
texts.serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<Vec<u8>>, D::Error> {
let texts = Vec::<String>::deserialize(deserializer)?;
texts
.into_iter()
.map(|text| super::decode_hex(&text).map_err(serde::de::Error::custom))
.collect()
}
}
pub mod timestamp_opt {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(
value: &Option<prost_types::Timestamp>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value.as_ref().and_then(super::timestamp_to_text) {
Some(text) => serializer.serialize_some(&text),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<prost_types::Timestamp>, D::Error> {
let text = Option::<String>::deserialize(deserializer)?;
match text {
Some(text) => super::timestamp_from_text(&text)
.map(Some)
.map_err(serde::de::Error::custom),
None => Ok(None),
}
}
}