vpr_core/paths/clinical/
letter.rs1use std::path::{Path, PathBuf};
31
32use crate::TimestampId;
33
34use super::common::CorrespondenceDir;
35
36#[derive(Debug, Clone, Copy)]
43pub struct LetterDir;
44
45impl LetterDir {
46 pub const NAME: &'static str = "letter";
47}
48
49#[derive(Debug, Clone, Copy)]
54pub struct CompositionYaml;
55
56impl CompositionYaml {
57 pub const NAME: &'static str = "composition.yaml";
58}
59
60#[derive(Debug, Clone, Copy)]
64pub struct BodyMd;
65
66impl BodyMd {
67 pub const NAME: &'static str = "body.md";
68}
69
70#[derive(Debug, Clone, Copy)]
75pub struct AttachmentsDir;
76
77impl AttachmentsDir {
78 pub const NAME: &'static str = "attachments";
79}
80
81#[derive(Debug, Clone)]
93pub struct LetterPaths {
94 relative_root: PathBuf,
95}
96
97impl LetterPaths {
98 pub fn new(letter_id: &TimestampId) -> Self {
107 Self {
108 relative_root: PathBuf::from(CorrespondenceDir::NAME)
109 .join(LetterDir::NAME)
110 .join(letter_id.to_string()),
111 }
112 }
113
114 pub fn dir(&self) -> &Path {
116 &self.relative_root
117 }
118
119 pub fn composition_yaml(&self) -> PathBuf {
121 self.relative_root.join(CompositionYaml::NAME)
122 }
123
124 pub fn body_md(&self) -> PathBuf {
126 self.relative_root.join(BodyMd::NAME)
127 }
128
129 pub fn attachments_dir(&self) -> PathBuf {
131 self.relative_root.join(AttachmentsDir::NAME)
132 }
133
134 pub fn attachment(&self, filename: &str) -> PathBuf {
142 self.attachments_dir().join(filename)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use std::str::FromStr;
150
151 #[test]
152 fn test_directory_constants() {
153 assert_eq!(CorrespondenceDir::NAME, "correspondence");
154 assert_eq!(LetterDir::NAME, "letter");
155 assert_eq!(CompositionYaml::NAME, "composition.yaml");
156 assert_eq!(BodyMd::NAME, "body.md");
157 assert_eq!(AttachmentsDir::NAME, "attachments");
158 }
159
160 #[test]
161 fn test_letter_paths_relative_paths() {
162 let timestamp_id =
163 TimestampId::from_str("20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000")
164 .expect("valid timestamp id");
165
166 let paths = LetterPaths::new(×tamp_id);
167
168 assert_eq!(
169 paths.dir(),
170 Path::new(
171 "correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000"
172 )
173 );
174
175 assert_eq!(
176 paths.composition_yaml(),
177 PathBuf::from("correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000/composition.yaml")
178 );
179
180 assert_eq!(
181 paths.body_md(),
182 PathBuf::from("correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000/body.md")
183 );
184
185 assert_eq!(
186 paths.attachments_dir(),
187 PathBuf::from("correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000/attachments")
188 );
189 }
190
191 #[test]
192 fn test_letter_paths_attachment_path() {
193 let timestamp_id =
194 TimestampId::from_str("20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000")
195 .expect("valid timestamp id");
196
197 let paths = LetterPaths::new(×tamp_id);
198
199 assert_eq!(
200 paths.attachment("letter.pdf"),
201 PathBuf::from("correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000/attachments/letter.pdf")
202 );
203
204 assert_eq!(
205 paths.attachment("scan.png"),
206 PathBuf::from("correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000/attachments/scan.png")
207 );
208 }
209
210 #[test]
211 fn test_different_timestamp_ids_produce_different_paths() {
212 let timestamp_id1 =
213 TimestampId::from_str("20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000")
214 .expect("valid timestamp id");
215 let timestamp_id2 =
216 TimestampId::from_str("20260115T093015.123Z-661f9511-f3ac-52e5-b827-557766551111")
217 .expect("valid timestamp id");
218
219 let paths1 = LetterPaths::new(×tamp_id1);
220 let paths2 = LetterPaths::new(×tamp_id2);
221
222 assert_ne!(paths1.dir(), paths2.dir());
223 assert_ne!(paths1.composition_yaml(), paths2.composition_yaml());
224 assert_ne!(paths1.body_md(), paths2.body_md());
225 }
226
227 #[test]
228 fn test_paths_is_relative_not_absolute() {
229 let timestamp_id =
230 TimestampId::from_str("20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000")
231 .expect("valid timestamp id");
232
233 let paths = LetterPaths::new(×tamp_id);
234
235 assert!(!paths.dir().to_str().unwrap().starts_with('/'));
237 assert!(!paths.composition_yaml().to_str().unwrap().starts_with('/'));
238 assert!(!paths.body_md().to_str().unwrap().starts_with('/'));
239 assert!(!paths.attachments_dir().to_str().unwrap().starts_with('/'));
240 }
241
242 #[test]
243 fn test_caller_can_join_with_patient_root() {
244 let timestamp_id =
245 TimestampId::from_str("20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000")
246 .expect("valid timestamp id");
247
248 let paths = LetterPaths::new(×tamp_id);
249 let patient_root =
250 Path::new("/patient_data/clinical/55/0e/550e8400e29b41d4a716446655440000");
251
252 assert_eq!(
254 patient_root.join(paths.dir()),
255 PathBuf::from("/patient_data/clinical/55/0e/550e8400e29b41d4a716446655440000/correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000")
256 );
257
258 assert_eq!(
259 patient_root.join(paths.composition_yaml()),
260 PathBuf::from("/patient_data/clinical/55/0e/550e8400e29b41d4a716446655440000/correspondence/letter/20260114T143522.045Z-550e8400-e29b-41d4-a716-446655440000/composition.yaml")
261 );
262 }
263}