1use std::collections::BTreeMap;
5
6use schemars::JsonSchema;
7use serde::{de::DeserializeOwned, Deserialize, Serialize};
8use serde_json::{Map, Value};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
12pub struct LossCount {
13 pub format: String,
15 pub kind: String,
17 pub count: usize,
19}
20
21#[derive(Debug, thiserror::Error)]
23pub enum NativeConvertError {
24 #[error("native record is missing a string id")]
26 MissingId,
27 #[error("native record did not serialize as an object")]
29 NonObject,
30 #[error("native record conversion failed: {0}")]
32 Serde(#[from] serde_json::Error),
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
37pub struct NativeRecord {
38 pub id: String,
40 #[serde(flatten)]
42 pub fields: Map<String, Value>,
43}
44
45#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
47pub struct NativeNamespace {
48 pub version: u32,
50 #[serde(default)]
52 pub arenas: BTreeMap<String, Vec<NativeRecord>>,
53}
54
55impl NativeNamespace {
56 pub fn set_arena<T: Serialize>(
58 &mut self,
59 name: impl Into<String>,
60 records: &[T],
61 ) -> Result<(), NativeConvertError> {
62 let mut converted = Vec::with_capacity(records.len());
63 for record in records {
64 let Value::Object(mut fields) = serde_json::to_value(record)? else {
65 return Err(NativeConvertError::NonObject);
66 };
67 let Some(Value::String(id)) = fields.remove("id") else {
68 return Err(NativeConvertError::MissingId);
69 };
70 converted.push(NativeRecord { id, fields });
71 }
72 converted.sort_by(|left, right| left.id.cmp(&right.id));
73 self.arenas.insert(name.into(), converted);
74 Ok(())
75 }
76
77 pub fn arena_as<T: DeserializeOwned>(&self, name: &str) -> Result<Vec<T>, NativeConvertError> {
79 self.arenas
80 .get(name)
81 .into_iter()
82 .flatten()
83 .map(|record| {
84 let mut fields = record.fields.clone();
85 fields.insert("id".into(), Value::String(record.id.clone()));
86 Ok(serde_json::from_value(Value::Object(fields))?)
87 })
88 .collect()
89 }
90}
91
92#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
94#[serde(transparent)]
95pub struct Native(pub BTreeMap<String, NativeNamespace>);
96
97impl Native {
98 pub fn namespace(&self, format: &str) -> Option<&NativeNamespace> {
100 self.0.get(format)
101 }
102
103 pub fn namespace_mut(&mut self, format: impl Into<String>) -> &mut NativeNamespace {
105 self.0.entry(format.into()).or_default()
106 }
107
108 pub(crate) fn finalize(&mut self) {
110 for namespace in self.0.values_mut() {
111 for records in namespace.arenas.values_mut() {
112 records.sort_by(|left, right| left.id.cmp(&right.id));
113 }
114 }
115 }
116
117 pub fn loss_counts(&self) -> Vec<LossCount> {
119 self.0
120 .iter()
121 .flat_map(|(format, namespace)| {
122 namespace
123 .arenas
124 .iter()
125 .filter(|(_, records)| !records.is_empty())
126 .map(move |(kind, records)| LossCount {
127 format: format.clone(),
128 kind: kind.clone(),
129 count: records.len(),
130 })
131 })
132 .collect()
133 }
134}