use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Catalog {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub fields: Vec<CatalogField>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CatalogField {
pub name: String,
#[serde(rename = "type")]
pub field_type: CatalogFieldType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CatalogFieldType {
String,
Number,
Boolean,
Time,
Object,
Array,
#[serde(other)]
Unknown,
}
impl CatalogFieldType {
pub fn as_str(self) -> &'static str {
match self {
Self::String => "string",
Self::Number => "number",
Self::Boolean => "boolean",
Self::Time => "time",
Self::Object => "object",
Self::Array => "array",
Self::Unknown => "unknown",
}
}
}
impl Catalog {
pub fn normalized(&self) -> Self {
let mut sorted = self.clone();
sorted.fields.sort_by(|a, b| a.name.cmp(&b.name));
sorted
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_yaml_roundtrip() {
let cat = Catalog {
name: "cardiology".into(),
description: Some("Cardiology catalog".into()),
fields: vec![
CatalogField {
name: "condition_id".into(),
field_type: CatalogFieldType::String,
},
CatalogField {
name: "display_order".into(),
field_type: CatalogFieldType::Number,
},
],
};
let yaml = serde_norway::to_string(&cat).unwrap();
let parsed: Catalog = serde_norway::from_str(&yaml).unwrap();
assert_eq!(cat, parsed);
}
#[test]
fn catalog_field_type_serializes_snake_case() {
let yaml = serde_norway::to_string(&CatalogFieldType::Boolean).unwrap();
assert_eq!(yaml.trim(), "boolean");
}
#[test]
fn unknown_field_type_deserializes_without_failure() {
let yaml = "name: future\nfields:\n - name: x\n type: hyperlink\n";
let cat: Catalog = serde_norway::from_str(yaml).unwrap();
assert_eq!(cat.fields[0].field_type, CatalogFieldType::Unknown);
assert_eq!(cat.fields[0].field_type.as_str(), "unknown");
}
#[test]
fn unknown_field_type_does_not_break_known_fields() {
let yaml = "\
name: mixed
fields:
- name: id
type: string
- name: fancy
type: quantum_entanglement
- name: score
type: number
";
let cat: Catalog = serde_norway::from_str(yaml).unwrap();
assert_eq!(cat.fields.len(), 3);
assert_eq!(cat.fields[0].field_type, CatalogFieldType::String);
assert_eq!(cat.fields[1].field_type, CatalogFieldType::Unknown);
assert_eq!(cat.fields[2].field_type, CatalogFieldType::Number);
}
#[test]
fn description_omitted_when_none() {
let cat = Catalog {
name: "x".into(),
description: None,
fields: vec![],
};
let yaml = serde_norway::to_string(&cat).unwrap();
assert!(!yaml.contains("description"));
}
#[test]
fn normalized_sorts_fields_by_name() {
let cat = Catalog {
name: "x".into(),
description: None,
fields: vec![
CatalogField {
name: "z".into(),
field_type: CatalogFieldType::String,
},
CatalogField {
name: "a".into(),
field_type: CatalogFieldType::String,
},
],
};
let n = cat.normalized();
assert_eq!(n.fields[0].name, "a");
assert_eq!(n.fields[1].name, "z");
}
}