rh_foundation/snapshot/
sd_loader.rs1use std::path::{Path, PathBuf};
2
3use tracing::{debug, info, warn};
4
5use crate::snapshot::error::{SnapshotError, SnapshotResult};
6use crate::snapshot::sd_load_support::{
7 ensure_directory, json_files_in_directory, package_directory, read_file,
8 try_parse_structure_definition, validate_structure_definition,
9};
10use crate::snapshot::types::StructureDefinition;
11
12pub struct StructureDefinitionLoader;
13
14impl StructureDefinitionLoader {
15 pub fn load_from_file(path: &Path) -> SnapshotResult<StructureDefinition> {
16 debug!("Loading StructureDefinition from file: {}", path.display());
17
18 let content = read_file(path)?;
19 let structure_definition: StructureDefinition =
20 serde_json::from_str(&content).map_err(SnapshotError::SerializationError)?;
21
22 Self::validate_structure_definition(&structure_definition)?;
23
24 info!(
25 "Loaded StructureDefinition: {} ({})",
26 structure_definition.name, structure_definition.url
27 );
28
29 Ok(structure_definition)
30 }
31
32 pub fn load_from_directory(dir: &Path) -> SnapshotResult<Vec<StructureDefinition>> {
33 info!(
34 "Loading StructureDefinitions from directory: {}",
35 dir.display()
36 );
37
38 ensure_directory(dir)?;
39
40 let mut structure_definitions = Vec::new();
41 for path in json_files_in_directory(dir)? {
42 match Self::try_load_structure_definition(&path) {
43 Ok(Some(structure_definition)) => structure_definitions.push(structure_definition),
44 Ok(None) => {
45 debug!("Skipping non-StructureDefinition file: {}", path.display());
46 }
47 Err(error) => {
48 warn!(
49 "Failed to load StructureDefinition from {}: {}",
50 path.display(),
51 error
52 );
53 }
54 }
55 }
56
57 info!(
58 "Loaded {} StructureDefinitions from {}",
59 structure_definitions.len(),
60 dir.display()
61 );
62
63 Ok(structure_definitions)
64 }
65
66 pub fn load_from_package(
67 package_name: &str,
68 version: &str,
69 packages_dir: &Path,
70 ) -> SnapshotResult<Vec<StructureDefinition>> {
71 info!(
72 "Loading StructureDefinitions from package {}@{}",
73 package_name, version
74 );
75
76 let package_dir = Self::get_package_directory(packages_dir, package_name, version);
77
78 if !package_dir.exists() {
79 return Err(SnapshotError::Other(format!(
80 "Package not found: {}@{} at {}",
81 package_name,
82 version,
83 package_dir.display()
84 )));
85 }
86
87 let package_subdir = package_dir.join("package");
88 let dir_to_scan = if package_subdir.exists() && package_subdir.is_dir() {
89 package_subdir
90 } else {
91 package_dir
92 };
93
94 Self::load_from_directory(&dir_to_scan)
95 }
96
97 fn try_load_structure_definition(path: &Path) -> SnapshotResult<Option<StructureDefinition>> {
98 let content = read_file(path)?;
99 try_parse_structure_definition(&content)
100 }
101
102 fn validate_structure_definition(sd: &StructureDefinition) -> SnapshotResult<()> {
103 validate_structure_definition(sd)
104 }
105
106 fn get_package_directory(packages_dir: &Path, package_name: &str, version: &str) -> PathBuf {
107 package_directory(packages_dir, package_name, version)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use std::fs;
115 use std::io::Write;
116 use tempfile::TempDir;
117
118 fn create_test_structure_definition() -> String {
119 r#"{
120 "resourceType": "StructureDefinition",
121 "url": "http://example.org/StructureDefinition/test-patient",
122 "name": "TestPatient",
123 "type": "Patient",
124 "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
125 "differential": {
126 "element": [
127 {
128 "path": "Patient",
129 "min": 0,
130 "max": "*"
131 }
132 ]
133 }
134 }"#
135 .to_string()
136 }
137
138 fn create_invalid_structure_definition() -> String {
139 r#"{
140 "resourceType": "StructureDefinition",
141 "url": "",
142 "name": "TestPatient",
143 "type": "Patient"
144 }"#
145 .to_string()
146 }
147
148 fn create_non_structure_definition() -> String {
149 r#"{
150 "resourceType": "Patient",
151 "id": "example"
152 }"#
153 .to_string()
154 }
155
156 #[test]
157 fn test_load_from_file_success() {
158 let temp_dir = TempDir::new().unwrap();
159 let file_path = temp_dir.path().join("test.json");
160
161 let mut file = fs::File::create(&file_path).unwrap();
162 file.write_all(create_test_structure_definition().as_bytes())
163 .unwrap();
164
165 let result = StructureDefinitionLoader::load_from_file(&file_path);
166 assert!(result.is_ok());
167
168 let sd = result.unwrap();
169 assert_eq!(sd.name, "TestPatient");
170 assert_eq!(
171 sd.url,
172 "http://example.org/StructureDefinition/test-patient"
173 );
174 assert_eq!(sd.type_, "Patient");
175 }
176
177 #[test]
178 fn test_load_from_file_missing_url() {
179 let temp_dir = TempDir::new().unwrap();
180 let file_path = temp_dir.path().join("invalid.json");
181
182 let mut file = fs::File::create(&file_path).unwrap();
183 file.write_all(create_invalid_structure_definition().as_bytes())
184 .unwrap();
185
186 let result = StructureDefinitionLoader::load_from_file(&file_path);
187 assert!(result.is_err());
188 assert!(matches!(
189 result.unwrap_err(),
190 SnapshotError::InvalidStructureDefinition(_)
191 ));
192 }
193
194 #[test]
195 fn test_load_from_directory() {
196 let temp_dir = TempDir::new().unwrap();
197
198 let file1 = temp_dir.path().join("sd1.json");
199 fs::write(&file1, create_test_structure_definition()).unwrap();
200
201 let file2 = temp_dir.path().join("patient.json");
202 fs::write(&file2, create_non_structure_definition()).unwrap();
203
204 let file3 = temp_dir.path().join("readme.txt");
205 fs::write(&file3, "not json").unwrap();
206
207 let result = StructureDefinitionLoader::load_from_directory(temp_dir.path());
208 assert!(result.is_ok());
209
210 let sds = result.unwrap();
211 assert_eq!(sds.len(), 1);
212 assert_eq!(sds[0].name, "TestPatient");
213 }
214
215 #[test]
216 fn test_validate_structure_definition() {
217 let valid_sd = StructureDefinition {
218 url: "http://example.org/test".to_string(),
219 name: "Test".to_string(),
220 type_: "Patient".to_string(),
221 base_definition: None,
222 differential: None,
223 snapshot: None,
224 };
225
226 assert!(StructureDefinitionLoader::validate_structure_definition(&valid_sd).is_ok());
227
228 let invalid_sd = StructureDefinition {
229 url: "".to_string(),
230 name: "Test".to_string(),
231 type_: "Patient".to_string(),
232 base_definition: None,
233 differential: None,
234 snapshot: None,
235 };
236
237 assert!(StructureDefinitionLoader::validate_structure_definition(&invalid_sd).is_err());
238 }
239}