1use serde::Deserialize;
19
20use crate::error::CoreError;
21
22#[derive(Debug, Clone, Deserialize)]
27pub struct IncludeSpec {
28 #[serde(default)]
31 pub path: Option<String>,
32
33 #[serde(default)]
36 pub pkg: Option<String>,
37
38 #[serde(default)]
43 pub prefix: Option<String>,
44}
45
46impl IncludeSpec {
47 pub fn validate(&self) -> Result<(), CoreError> {
49 match (&self.path, &self.pkg) {
50 (None, None) => Err(CoreError::ValidationError {
51 reason: "Include spec must have either 'path' or 'pkg'".into(),
52 }),
53 (Some(_), Some(_)) => Err(CoreError::ValidationError {
54 reason: "Include spec cannot have both 'path' and 'pkg'".into(),
55 }),
56 _ => Ok(()),
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::serde_yaml;
65
66 #[test]
67 fn test_include_spec_parse_minimal() {
68 let yaml = r#"
69path: ./lib/tasks.nika.yaml
70"#;
71 let spec: IncludeSpec = serde_yaml::from_str(yaml).unwrap();
72 assert_eq!(spec.path, Some("./lib/tasks.nika.yaml".to_string()));
73 assert!(spec.pkg.is_none());
74 assert!(spec.prefix.is_none());
75 assert!(spec.validate().is_ok());
76 }
77
78 #[test]
79 fn test_include_spec_parse_with_prefix() {
80 let yaml = r#"
81path: ./lib/seo-tasks.nika.yaml
82prefix: seo_
83"#;
84 let spec: IncludeSpec = serde_yaml::from_str(yaml).unwrap();
85 assert_eq!(spec.path, Some("./lib/seo-tasks.nika.yaml".to_string()));
86 assert_eq!(spec.prefix, Some("seo_".to_string()));
87 assert!(spec.validate().is_ok());
88 }
89
90 #[test]
91 fn test_include_spec_parse_array() {
92 let yaml = r#"
93- path: ./lib/seo.nika.yaml
94 prefix: seo_
95- path: ./lib/common.nika.yaml
96"#;
97 let specs: Vec<IncludeSpec> = serde_yaml::from_str(yaml).unwrap();
98 assert_eq!(specs.len(), 2);
99 assert_eq!(specs[0].prefix, Some("seo_".to_string()));
100 assert!(specs[1].prefix.is_none());
101 }
102
103 #[test]
104 fn test_include_spec_empty_prefix() {
105 let yaml = r#"
106path: ./lib/tasks.nika.yaml
107prefix: ""
108"#;
109 let spec: IncludeSpec = serde_yaml::from_str(yaml).unwrap();
110 assert_eq!(spec.prefix, Some(String::new()));
111 }
112
113 #[test]
115 fn test_include_spec_parse_pkg() {
116 let yaml = r#"
117pkg: "@workflows/seo-audit"
118prefix: seo_
119"#;
120 let spec: IncludeSpec = serde_yaml::from_str(yaml).unwrap();
121 assert_eq!(spec.pkg, Some("@workflows/seo-audit".to_string()));
122 assert!(spec.path.is_none());
123 assert_eq!(spec.prefix, Some("seo_".to_string()));
124 assert!(spec.validate().is_ok());
125 }
126
127 #[test]
128 fn test_include_spec_validate_missing_both() {
129 let spec = IncludeSpec {
130 path: None,
131 pkg: None,
132 prefix: None,
133 };
134 assert!(spec.validate().is_err());
135 }
136
137 #[test]
138 fn test_include_spec_validate_both_present() {
139 let spec = IncludeSpec {
140 path: Some("./lib/tasks.yaml".to_string()),
141 pkg: Some("@workflows/seo".to_string()),
142 prefix: None,
143 };
144 assert!(spec.validate().is_err());
145 }
146}