1#![doc = include_str!("../README.md")]
2
3extern crate alloc;
4
5use alloc::collections::BTreeMap;
6
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Catalog {
13 pub version: u32,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub title: Option<String>,
16 pub schemas: Vec<SchemaEntry>,
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
18 pub groups: Vec<CatalogGroup>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct CatalogGroup {
27 pub name: String,
28 pub description: String,
29 pub schemas: Vec<String>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct SchemaEntry {
36 pub name: String,
37 pub description: String,
38 pub url: String,
39 #[serde(default, rename = "fileMatch", skip_serializing_if = "Vec::is_empty")]
40 pub file_match: Vec<String>,
41 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
42 pub versions: BTreeMap<String, String>,
43}
44
45pub fn parse_catalog(json: &str) -> Result<Catalog, serde_json::Error> {
51 serde_json::from_str(json)
52}
53
54pub fn parse_catalog_value(value: serde_json::Value) -> Result<Catalog, serde_json::Error> {
60 serde_json::from_value(value)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn round_trip_catalog() {
69 let catalog = Catalog {
70 version: 1,
71 title: None,
72 schemas: vec![SchemaEntry {
73 name: "Test Schema".into(),
74 description: "A test schema".into(),
75 url: "https://example.com/test.json".into(),
76 file_match: vec!["*.test.json".into()],
77 versions: BTreeMap::new(),
78 }],
79 groups: vec![],
80 };
81 let json = serde_json::to_string_pretty(&catalog).expect("serialize");
82 let parsed: Catalog = serde_json::from_str(&json).expect("deserialize");
83 assert_eq!(parsed.version, 1);
84 assert_eq!(parsed.schemas.len(), 1);
85 assert_eq!(parsed.schemas[0].name, "Test Schema");
86 assert_eq!(parsed.schemas[0].file_match, vec!["*.test.json"]);
87 }
88
89 #[test]
90 fn parse_catalog_from_json_string() {
91 let json = r#"{"version":1,"schemas":[{"name":"test","description":"desc","url":"https://example.com/s.json","fileMatch":["*.json"]}]}"#;
92 let catalog = parse_catalog(json).expect("parse");
93 assert_eq!(catalog.schemas.len(), 1);
94 assert_eq!(catalog.schemas[0].name, "test");
95 assert_eq!(catalog.schemas[0].file_match, vec!["*.json"]);
96 }
97
98 #[test]
99 fn empty_file_match_omitted_in_serialization() {
100 let entry = SchemaEntry {
101 name: "No Match".into(),
102 description: "desc".into(),
103 url: "https://example.com/no.json".into(),
104 file_match: vec![],
105 versions: BTreeMap::new(),
106 };
107 let json = serde_json::to_string(&entry).expect("serialize");
108 assert!(!json.contains("fileMatch"));
109 assert!(!json.contains("versions"));
110 }
111
112 #[test]
113 fn deserialize_with_versions() {
114 let json = r#"{
115 "version": 1,
116 "schemas": [{
117 "name": "test",
118 "description": "desc",
119 "url": "https://example.com/s.json",
120 "versions": {"draft-07": "https://example.com/draft07.json"}
121 }]
122 }"#;
123 let catalog = parse_catalog(json).expect("parse");
124 assert_eq!(
125 catalog.schemas[0].versions.get("draft-07"),
126 Some(&"https://example.com/draft07.json".to_string())
127 );
128 }
129}