Skip to main content

shaperail_codegen/
parser.rs

1use shaperail_core::ResourceDefinition;
2
3/// Errors that can occur during YAML parsing.
4#[derive(Debug, thiserror::Error)]
5pub enum ParseError {
6    #[error("{0}")]
7    Yaml(#[from] serde_yaml::Error),
8
9    #[error("{0}")]
10    Io(#[from] std::io::Error),
11
12    #[error("{0}")]
13    ConfigInterpolation(String),
14}
15
16/// Parse a YAML string into a `ResourceDefinition`.
17pub fn parse_resource(yaml: &str) -> Result<ResourceDefinition, ParseError> {
18    let resource: ResourceDefinition = serde_yaml::from_str(yaml)?;
19    Ok(resource)
20}
21
22/// Parse a resource YAML file from disk.
23pub fn parse_resource_file(path: &std::path::Path) -> Result<ResourceDefinition, ParseError> {
24    let content = std::fs::read_to_string(path)?;
25    parse_resource(&content)
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn parse_minimal_resource() {
34        let yaml = r#"
35resource: tags
36version: 1
37schema:
38  id: { type: uuid, primary: true, generated: true }
39  name: { type: string, required: true }
40"#;
41        let rd = parse_resource(yaml).unwrap();
42        assert_eq!(rd.resource, "tags");
43        assert_eq!(rd.version, 1);
44        assert_eq!(rd.schema.len(), 2);
45        assert!(rd.endpoints.is_none());
46        assert!(rd.relations.is_none());
47        assert!(rd.indexes.is_none());
48    }
49
50    #[test]
51    fn parse_full_users_resource() {
52        let yaml = include_str!("../../resources/users.yaml");
53        let rd = parse_resource(yaml).unwrap();
54        assert_eq!(rd.resource, "users");
55        assert_eq!(rd.version, 1);
56        assert_eq!(rd.schema.len(), 9);
57        assert!(rd.endpoints.is_some());
58        assert!(rd.relations.is_some());
59        assert!(rd.indexes.is_some());
60    }
61
62    #[test]
63    fn parse_error_invalid_yaml() {
64        let yaml = "not: [valid: yaml: here";
65        let err = parse_resource(yaml).unwrap_err();
66        assert!(matches!(err, ParseError::Yaml(_)));
67    }
68
69    #[test]
70    fn parse_error_missing_resource_key() {
71        let yaml = r#"
72version: 1
73schema:
74  id: { type: uuid }
75"#;
76        let err = parse_resource(yaml).unwrap_err();
77        assert!(err.to_string().contains("missing field"));
78    }
79
80    #[test]
81    fn parse_error_unknown_top_level_key() {
82        let yaml = r#"
83resource: tags
84version: 1
85schema:
86  id: { type: uuid, primary: true, generated: true }
87unknown: true
88"#;
89        let err = parse_resource(yaml).unwrap_err();
90        assert!(err.to_string().contains("unknown field"));
91        assert!(err.to_string().contains("unknown"));
92    }
93
94    #[test]
95    fn parse_error_unknown_field_key() {
96        let yaml = r#"
97resource: tags
98version: 1
99schema:
100  id: { type: uuid, primary: true, generated: true, searchable: true }
101"#;
102        let err = parse_resource(yaml).unwrap_err();
103        assert!(err.to_string().contains("unknown field"));
104        assert!(err.to_string().contains("searchable"));
105    }
106
107    #[test]
108    fn parse_resource_with_db_key() {
109        let yaml = r#"
110resource: events
111version: 1
112db: analytics
113schema:
114  id: { type: uuid, primary: true, generated: true }
115  name: { type: string, required: true }
116"#;
117        let rd = parse_resource(yaml).unwrap();
118        assert_eq!(rd.resource, "events");
119        assert_eq!(rd.db.as_deref(), Some("analytics"));
120    }
121}