use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SeasonalAdjustment {
SeasonallyAdjusted,
NotSeasonallyAdjusted,
SeasonallyAdjustedAnnualRate,
Other(String),
}
impl SeasonalAdjustment {
fn from_label(label: &str) -> Self {
match label {
"Seasonally Adjusted" => Self::SeasonallyAdjusted,
"Not Seasonally Adjusted" => Self::NotSeasonallyAdjusted,
"Seasonally Adjusted Annual Rate" => Self::SeasonallyAdjustedAnnualRate,
other => Self::Other(other.to_owned()),
}
}
pub fn label(&self) -> &str {
match self {
Self::SeasonallyAdjusted => "Seasonally Adjusted",
Self::NotSeasonallyAdjusted => "Not Seasonally Adjusted",
Self::SeasonallyAdjustedAnnualRate => "Seasonally Adjusted Annual Rate",
Self::Other(label) => label,
}
}
pub fn query_code(&self) -> &str {
match self {
Self::SeasonallyAdjusted => "SA",
Self::NotSeasonallyAdjusted => "NSA",
Self::SeasonallyAdjustedAnnualRate => "SAAR",
Self::Other(label) => label,
}
}
}
impl fmt::Display for SeasonalAdjustment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label())
}
}
impl<'de> Deserialize<'de> for SeasonalAdjustment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let label = String::deserialize(deserializer)?;
Ok(Self::from_label(&label))
}
}
impl Serialize for SeasonalAdjustment {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.label())
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for SeasonalAdjustment {
fn schema_name() -> std::borrow::Cow<'static, str> {
"SeasonalAdjustment".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
<String as schemars::JsonSchema>::json_schema(generator)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_labels_map_to_variants() {
assert_eq!(
serde_json::from_str::<SeasonalAdjustment>("\"Not Seasonally Adjusted\"").unwrap(),
SeasonalAdjustment::NotSeasonallyAdjusted
);
assert_eq!(
serde_json::from_str::<SeasonalAdjustment>("\"Seasonally Adjusted Annual Rate\"")
.unwrap(),
SeasonalAdjustment::SeasonallyAdjustedAnnualRate
);
}
#[test]
fn unknown_label_is_preserved_verbatim() {
assert_eq!(
serde_json::from_str::<SeasonalAdjustment>("\"Smoothed\"").unwrap(),
SeasonalAdjustment::Other("Smoothed".to_owned())
);
}
#[test]
fn serializes_to_its_label() {
assert_eq!(
serde_json::to_string(&SeasonalAdjustment::SeasonallyAdjustedAnnualRate).unwrap(),
"\"Seasonally Adjusted Annual Rate\""
);
}
#[test]
fn query_codes_match_geofred() {
assert_eq!(SeasonalAdjustment::SeasonallyAdjusted.query_code(), "SA");
assert_eq!(
SeasonalAdjustment::NotSeasonallyAdjusted.query_code(),
"NSA"
);
assert_eq!(
SeasonalAdjustment::SeasonallyAdjustedAnnualRate.query_code(),
"SAAR"
);
}
const KNOWN: &[SeasonalAdjustment] = &[
SeasonalAdjustment::SeasonallyAdjusted,
SeasonalAdjustment::NotSeasonallyAdjusted,
SeasonalAdjustment::SeasonallyAdjustedAnnualRate,
];
#[test]
fn every_known_variant_round_trips_through_serde() {
for variant in KNOWN {
let json = serde_json::to_string(variant).unwrap();
let back: SeasonalAdjustment = serde_json::from_str(&json).unwrap();
assert_eq!(
&back, variant,
"{variant:?} serialized to {json} but deserialized back to {back:?} — a \
named variant whose label is not wired into `from_label` is silently \
swallowed by `Other`, the exact bug ADR-0030 pins"
);
}
}
#[test]
fn known_list_is_exhaustive() {
fn account_for_every_variant(seasonal_adjustment: &SeasonalAdjustment) {
match seasonal_adjustment {
SeasonalAdjustment::SeasonallyAdjusted
| SeasonalAdjustment::NotSeasonallyAdjusted
| SeasonalAdjustment::SeasonallyAdjustedAnnualRate
| SeasonalAdjustment::Other(_) => {}
}
}
account_for_every_variant(&SeasonalAdjustment::SeasonallyAdjusted);
assert!(
KNOWN
.iter()
.all(|v| !matches!(v, SeasonalAdjustment::Other(_))),
"`KNOWN` must list only named variants, never `Other`"
);
}
}