use std::collections::BTreeMap;
use serde::{Deserialize, Deserializer, Serialize};
use crate::{ReleaseElementId, ReleaseId, SeriesId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ReleaseTable {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub element_id: Option<ReleaseElementId>,
#[serde(
rename(serialize = "roots", deserialize = "elements"),
deserialize_with = "roots_from_map"
)]
pub roots: Vec<ReleaseTableElement>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ReleaseTableElement {
pub element_id: ReleaseElementId,
pub release_id: ReleaseId,
#[serde(default)]
pub parent_id: Option<ReleaseElementId>,
#[serde(default, deserialize_with = "optional_series_id")]
pub series_id: Option<SeriesId>,
#[serde(rename = "type")]
pub element_type: String,
pub name: String,
#[serde(default)]
pub line: Option<String>,
pub level: String,
#[serde(default)]
pub children: Vec<ReleaseTableElement>,
}
fn roots_from_map<'de, D>(deserializer: D) -> Result<Vec<ReleaseTableElement>, D::Error>
where
D: Deserializer<'de>,
{
let map: BTreeMap<String, ReleaseTableElement> = BTreeMap::deserialize(deserializer)?;
let mut roots: Vec<ReleaseTableElement> = map.into_values().collect();
roots.sort_by_key(|element| element.element_id);
Ok(roots)
}
fn optional_series_id<'de, D>(deserializer: D) -> Result<Option<SeriesId>, D::Error>
where
D: Deserializer<'de>,
{
let raw: Option<String> = Option::deserialize(deserializer)?;
Ok(raw.filter(|id| !id.is_empty()).map(SeriesId::new))
}
#[cfg(test)]
mod tests {
use super::*;
const TABLE_BODY: &str = r#"{
"name": null,
"element_id": null,
"release_id": "10",
"elements": {
"34483": {
"element_id": 34483, "release_id": 10, "parent_id": null,
"series_id": null, "type": "section", "name": "Monthly, SA",
"line": null, "level": "0",
"children": [
{
"element_id": 34484, "release_id": 10, "parent_id": 34483,
"series_id": "", "type": "series", "name": "All items",
"line": "1", "level": "1",
"children": [
{
"element_id": 34485, "release_id": 10, "parent_id": 34484,
"series_id": "CPIFABSL", "type": "series",
"name": "Food and beverages", "line": "2", "level": "2",
"children": []
}
]
}
]
}
}
}"#;
#[test]
fn deserializes_a_nested_table() {
let table: ReleaseTable = serde_json::from_str(TABLE_BODY).unwrap();
assert!(table.name.is_none());
assert!(table.element_id.is_none());
assert_eq!(table.roots.len(), 1);
let section = &table.roots[0];
assert_eq!(section.element_id, ReleaseElementId::new(34483));
assert_eq!(section.element_type, "section");
assert!(section.parent_id.is_none());
assert!(section.series_id.is_none()); assert_eq!(section.children.len(), 1);
let all_items = §ion.children[0];
assert_eq!(all_items.parent_id, Some(ReleaseElementId::new(34483)));
assert!(all_items.series_id.is_none()); assert_eq!(all_items.line.as_deref(), Some("1"));
let leaf = &all_items.children[0];
assert_eq!(leaf.series_id, Some(SeriesId::new("CPIFABSL")));
assert_eq!(leaf.element_type, "series");
assert!(leaf.children.is_empty());
}
#[test]
fn roots_are_ordered_by_element_id() {
let body = r#"{
"elements": {
"200": {"element_id":200,"release_id":1,"type":"table","name":"B","level":"0"},
"100": {"element_id":100,"release_id":1,"type":"table","name":"A","level":"0"}
}
}"#;
let table: ReleaseTable = serde_json::from_str(body).unwrap();
let ids: Vec<u32> = table.roots.iter().map(|e| e.element_id.get()).collect();
assert_eq!(ids, [100, 200]);
}
}