use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use super::{ChartSpec, validate_text};
use crate::error::{ChartError, ChartWarning};
pub(crate) const MAX_PARTIES: usize = 8;
pub(crate) const MAX_SEATS: u32 = 800;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ParliamentSpec {
pub parties: Vec<PartySpec>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub majority: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub coalition: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PartySpec {
pub label: String,
pub seats: u32,
}
impl ParliamentSpec {
pub(crate) fn total(&self) -> u32 {
self.parties.iter().map(|party| party.seats).sum()
}
pub(crate) fn majority_of(&self) -> u32 {
self.total() / 2 + 1
}
pub(crate) fn in_coalition(&self, party: usize) -> bool {
self.coalition.contains(&self.parties[party].label)
}
pub(crate) fn coalition_seats(&self) -> u32 {
(0..self.parties.len())
.filter(|party| self.in_coalition(*party))
.map(|party| self.parties[party].seats)
.sum()
}
}
impl ChartSpec {
pub(super) fn validate_parliament(&self) -> Result<Vec<ChartWarning>, ChartError> {
for (field, present) in [
("/data", !self.data.is_empty()),
("/categories", !self.categories.is_empty()),
("/series", !self.series.is_empty()),
("/zoomSteps", !self.zoom_steps.is_empty()),
("/panes", !self.panes.is_empty()),
("/references", !self.references.is_empty()),
("/timeAxis", !self.time_axis.is_default()),
(
"/valueAxis",
self.value_axis != super::ValueAxisSpec::default(),
),
] {
if present {
return Err(ChartError::new(
"option_not_supported",
field,
"a parliament is drawn from parties; remove this field",
));
}
}
let Some(parliament) = &self.parliament else {
return Err(ChartError::new(
"missing_parliament",
"/parliament",
"a parliament requires a parliament block",
));
};
if parliament.parties.is_empty() {
return Err(ChartError::new(
"empty_data",
"/parliament/parties",
"provide at least one party",
));
}
if parliament.parties.len() > MAX_PARTIES {
return Err(ChartError::new(
"too_many_series",
"/parliament/parties",
format!("at most {MAX_PARTIES} parties are supported"),
));
}
let mut labels = BTreeSet::new();
for (index, party) in parliament.parties.iter().enumerate() {
let path = format!("/parliament/parties/{index}");
validate_text(&party.label, &format!("{path}/label"), 100)?;
if !labels.insert(party.label.as_str()) {
return Err(ChartError::new(
"duplicate_label",
format!("{path}/label"),
"party labels must be unique",
));
}
if party.seats == 0 {
return Err(ChartError::new(
"invalid_value",
format!("{path}/seats"),
"a party has at least one seat",
));
}
}
if parliament.total() > MAX_SEATS {
return Err(ChartError::new(
"too_many_data_points",
"/parliament/parties",
format!("at most {MAX_SEATS} seats are supported"),
));
}
for (index, label) in parliament.coalition.iter().enumerate() {
if !labels.contains(label.as_str()) {
return Err(ChartError::new(
"unknown_node",
format!("/parliament/coalition/{index}"),
format!("no party is labeled \"{label}\""),
));
}
}
Ok(Vec::new())
}
}