use serde_json::Value;
pub const ID_KEYS: &[&str] = &["fragment_id", "content_hash", "memory_id", "fragment_ids"];
pub fn stringify_id_fields(value: &mut Value) {
match value {
Value::Object(map) => {
for (key, entry) in map.iter_mut() {
if ID_KEYS.contains(&key.as_str()) {
stringify_ids_in(entry);
} else {
stringify_id_fields(entry);
}
}
}
Value::Array(items) => items.iter_mut().for_each(stringify_id_fields),
_ => {}
}
}
fn stringify_ids_in(value: &mut Value) {
match value {
Value::Number(number) => {
if let Some(id) = number.as_u64() {
*value = Value::String(id.to_string());
}
}
Value::Array(items) => items.iter_mut().for_each(stringify_ids_in),
_ => {}
}
}
pub fn parse_id_fields(value: &mut Value) -> Result<(), String> {
match value {
Value::Object(map) => {
for (key, entry) in map.iter_mut() {
if ID_KEYS.contains(&key.as_str()) {
parse_ids_in(entry)?;
} else {
parse_id_fields(entry)?;
}
}
}
Value::Array(items) => {
for item in items {
parse_id_fields(item)?;
}
}
_ => {}
}
Ok(())
}
fn parse_ids_in(value: &mut Value) -> Result<(), String> {
match value {
Value::String(text) => {
*value = Value::Number(parse_u64(text)?.into());
}
Value::Array(items) => {
for item in items {
parse_ids_in(item)?;
}
}
_ => {}
}
Ok(())
}
pub fn parse_fragment_id_strings(request: &mut Value) -> Result<(), String> {
let Some(fragments) = request.get_mut("fragments").and_then(Value::as_array_mut) else {
return Ok(());
};
for fragment in fragments {
let Some(id) = fragment.get_mut("id") else {
continue;
};
if let Value::String(text) = id {
*id = Value::Number(parse_u64(text)?.into());
}
}
Ok(())
}
pub(crate) fn parse_u64(text: &str) -> Result<u64, String> {
text.parse()
.map_err(|_| format!("invalid id '{text}' (expected a decimal u64 string)"))
}
pub(crate) fn deserialize_optional_id<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
use serde::Deserialize;
let expected = "expected a u64 number or a decimal u64 string";
Option::<Value>::deserialize(deserializer)?
.map(|value| match value {
Value::Number(number) => number
.as_u64()
.ok_or_else(|| Error::custom(format!("invalid id {number} ({expected})"))),
Value::String(text) => parse_u64(&text).map_err(Error::custom),
other => Err(Error::custom(format!("invalid id {other} ({expected})"))),
})
.transpose()
}
#[cfg(test)]
#[path = "wire_tests.rs"]
mod tests;