use std::collections::BTreeMap;
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct LossCount {
pub format: String,
pub kind: String,
pub count: usize,
}
#[derive(Debug, thiserror::Error)]
pub enum NativeConvertError {
#[error("native record is missing a string id")]
MissingId,
#[error("native record did not serialize as an object")]
NonObject,
#[error("native record conversion failed: {0}")]
Serde(#[from] serde_json::Error),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct NativeRecord {
pub id: String,
#[serde(flatten)]
pub fields: Map<String, Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct NativeNamespace {
pub version: u32,
#[serde(default)]
pub arenas: BTreeMap<String, Vec<NativeRecord>>,
}
impl NativeNamespace {
pub fn set_arena<T: Serialize>(
&mut self,
name: impl Into<String>,
records: &[T],
) -> Result<(), NativeConvertError> {
let mut converted = Vec::with_capacity(records.len());
for record in records {
let Value::Object(mut fields) = serde_json::to_value(record)? else {
return Err(NativeConvertError::NonObject);
};
let Some(Value::String(id)) = fields.remove("id") else {
return Err(NativeConvertError::MissingId);
};
converted.push(NativeRecord { id, fields });
}
converted.sort_by(|left, right| left.id.cmp(&right.id));
self.arenas.insert(name.into(), converted);
Ok(())
}
pub fn arena_as<T: DeserializeOwned>(&self, name: &str) -> Result<Vec<T>, NativeConvertError> {
self.arenas
.get(name)
.into_iter()
.flatten()
.map(|record| {
let mut fields = record.fields.clone();
fields.insert("id".into(), Value::String(record.id.clone()));
Ok(serde_json::from_value(Value::Object(fields))?)
})
.collect()
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct Native(pub BTreeMap<String, NativeNamespace>);
impl Native {
pub fn namespace(&self, format: &str) -> Option<&NativeNamespace> {
self.0.get(format)
}
pub fn namespace_mut(&mut self, format: impl Into<String>) -> &mut NativeNamespace {
self.0.entry(format.into()).or_default()
}
pub(crate) fn finalize(&mut self) {
for namespace in self.0.values_mut() {
for records in namespace.arenas.values_mut() {
records.sort_by(|left, right| left.id.cmp(&right.id));
}
}
}
pub fn loss_counts(&self) -> Vec<LossCount> {
self.0
.iter()
.flat_map(|(format, namespace)| {
namespace
.arenas
.iter()
.filter(|(_, records)| !records.is_empty())
.map(move |(kind, records)| LossCount {
format: format.clone(),
kind: kind.clone(),
count: records.len(),
})
})
.collect()
}
}