1use std::collections::HashMap;
6
7use crate::config::CodegenConfig;
8use crate::fhir_types::StructureDefinition;
9use crate::generators::DocumentationGenerator;
10use crate::rust_types::{RustField, RustStruct, RustType, RustTypeAlias};
11use crate::CodegenResult;
12
13pub struct PrimitiveGenerator<'a> {
15 config: &'a CodegenConfig,
16 type_cache: &'a mut HashMap<String, RustStruct>,
17}
18
19impl<'a> PrimitiveGenerator<'a> {
20 pub fn new(config: &'a CodegenConfig, type_cache: &'a mut HashMap<String, RustStruct>) -> Self {
22 Self { config, type_cache }
23 }
24
25 pub fn generate_all_primitive_type_aliases(
27 &self,
28 primitive_structure_defs: &[StructureDefinition],
29 ) -> CodegenResult<Vec<RustTypeAlias>> {
30 let mut type_aliases = Vec::new();
31
32 for structure_def in primitive_structure_defs {
33 let type_alias = self.generate_primitive_type_alias(structure_def)?;
34 type_aliases.push(type_alias);
35 }
36
37 Ok(type_aliases)
38 }
39
40 pub fn generate_primitive_type_alias(
42 &self,
43 structure_def: &StructureDefinition,
44 ) -> CodegenResult<RustTypeAlias> {
45 let rust_primitive_type = Self::map_fhir_primitive_to_rust_type(&structure_def.name);
47
48 let type_alias_name = format!(
50 "{}Type",
51 Self::fhir_primitive_to_pascal_case(&structure_def.name)
52 );
53
54 let mut type_alias = RustTypeAlias::new(type_alias_name, rust_primitive_type);
56 let doc =
57 DocumentationGenerator::generate_primitive_type_alias_documentation(structure_def);
58 type_alias = type_alias.with_doc(doc);
59
60 Ok(type_alias)
61 }
62
63 fn fhir_primitive_to_pascal_case(name: &str) -> String {
65 match name {
66 "dateTime" => "DateTime".to_string(),
68 "positiveInt" => "PositiveInt".to_string(),
69 "unsignedInt" => "UnsignedInt".to_string(),
70 "base64Binary" => "Base64Binary".to_string(),
71 _ => {
73 let mut chars = name.chars();
74 match chars.next() {
75 None => String::new(),
76 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
77 }
78 }
79 }
80 }
81
82 pub fn generate_primitive_type_struct(
84 &mut self,
85 structure_def: &StructureDefinition,
86 mut rust_struct: RustStruct,
87 ) -> CodegenResult<RustStruct> {
88 rust_struct.base_definition = None;
90
91 let rust_primitive_type = Self::map_fhir_primitive_to_rust_type(&structure_def.name);
93
94 let value_field = RustField::new("value".to_string(), rust_primitive_type);
97 rust_struct.add_field(value_field);
98
99 let struct_name = rust_struct.name.clone();
101 self.type_cache.insert(struct_name, rust_struct.clone());
102
103 Ok(rust_struct)
104 }
105
106 pub fn generate_primitive_element_struct(
108 &mut self,
109 structure_def: &StructureDefinition,
110 ) -> CodegenResult<RustStruct> {
111 let element_struct_name = format!("_{}", structure_def.name);
113
114 if let Some(cached_struct) = self.type_cache.get(&element_struct_name) {
116 return Ok(cached_struct.clone());
117 }
118
119 let mut element_struct = RustStruct::new(element_struct_name.clone());
121
122 element_struct.doc_comment = Some(
124 DocumentationGenerator::generate_primitive_element_documentation(&structure_def.name),
125 );
126
127 let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
129 if self.config.with_serde {
130 derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
131 }
132 element_struct.derives = derives;
133
134 element_struct.base_definition = Some("Element".to_string());
136
137 self.type_cache
139 .insert(element_struct_name, element_struct.clone());
140
141 Ok(element_struct)
142 }
143
144 pub fn map_fhir_primitive_to_rust_type(primitive_name: &str) -> RustType {
146 match primitive_name {
147 "boolean" => RustType::Boolean,
148 "integer" | "positiveInt" | "unsignedInt" => RustType::Integer,
149 "decimal" => RustType::Float,
150 "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical" | "oid"
151 | "uuid" | "base64Binary" | "xhtml" => RustType::String,
152 "date" | "dateTime" | "time" | "instant" => RustType::String, _ => RustType::String, }
155 }
156
157 pub fn is_fhir_primitive_type(name: &str) -> bool {
159 matches!(
160 name,
161 "boolean"
162 | "integer"
163 | "positiveInt"
164 | "unsignedInt"
165 | "decimal"
166 | "string"
167 | "code"
168 | "id"
169 | "markdown"
170 | "uri"
171 | "url"
172 | "canonical"
173 | "oid"
174 | "uuid"
175 | "base64Binary"
176 | "xhtml"
177 | "date"
178 | "dateTime"
179 | "time"
180 | "instant"
181 )
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_map_fhir_primitive_to_rust_type() {
191 assert!(matches!(
192 PrimitiveGenerator::map_fhir_primitive_to_rust_type("boolean"),
193 RustType::Boolean
194 ));
195 assert!(matches!(
196 PrimitiveGenerator::map_fhir_primitive_to_rust_type("integer"),
197 RustType::Integer
198 ));
199 assert!(matches!(
200 PrimitiveGenerator::map_fhir_primitive_to_rust_type("decimal"),
201 RustType::Float
202 ));
203 assert!(matches!(
204 PrimitiveGenerator::map_fhir_primitive_to_rust_type("string"),
205 RustType::String
206 ));
207 assert!(matches!(
208 PrimitiveGenerator::map_fhir_primitive_to_rust_type("uri"),
209 RustType::String
210 ));
211 assert!(matches!(
212 PrimitiveGenerator::map_fhir_primitive_to_rust_type("unknown"),
213 RustType::String
214 ));
215 }
216
217 #[test]
218 fn test_is_fhir_primitive_type() {
219 assert!(PrimitiveGenerator::is_fhir_primitive_type("boolean"));
220 assert!(PrimitiveGenerator::is_fhir_primitive_type("string"));
221 assert!(PrimitiveGenerator::is_fhir_primitive_type("integer"));
222 assert!(PrimitiveGenerator::is_fhir_primitive_type("decimal"));
223 assert!(PrimitiveGenerator::is_fhir_primitive_type("uri"));
224 assert!(PrimitiveGenerator::is_fhir_primitive_type("dateTime"));
225
226 assert!(!PrimitiveGenerator::is_fhir_primitive_type("Patient"));
227 assert!(!PrimitiveGenerator::is_fhir_primitive_type("Identifier"));
228 assert!(!PrimitiveGenerator::is_fhir_primitive_type("unknown"));
229 }
230
231 #[test]
232 fn test_generate_primitive_type_alias() {
233 use crate::config::CodegenConfig;
234
235 let config = CodegenConfig::default();
236 let mut type_cache = HashMap::new();
237 let primitive_generator = PrimitiveGenerator::new(&config, &mut type_cache);
238
239 let structure_def = StructureDefinition {
240 resource_type: "StructureDefinition".to_string(),
241 id: "boolean".to_string(),
242 url: "http://hl7.org/fhir/StructureDefinition/boolean".to_string(),
243 name: "boolean".to_string(),
244 title: Some("boolean".to_string()),
245 status: "active".to_string(),
246 kind: "primitive-type".to_string(),
247 is_abstract: false,
248 description: Some("Value of 'true' or 'false'".to_string()),
249 purpose: None,
250 base_type: "boolean".to_string(),
251 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
252 version: None,
253 differential: None,
254 snapshot: None,
255 };
256
257 let result = primitive_generator.generate_primitive_type_alias(&structure_def);
258 assert!(result.is_ok());
259
260 let type_alias = result.unwrap();
261 assert_eq!(type_alias.name, "BooleanType");
262 assert!(matches!(type_alias.target_type, RustType::Boolean));
263 }
264
265 #[test]
266 fn test_primitive_type_naming_convention() {
267 use crate::config::CodegenConfig;
268
269 let config = CodegenConfig::default();
270 let mut type_cache = HashMap::new();
271 let primitive_generator = PrimitiveGenerator::new(&config, &mut type_cache);
272
273 let test_cases = vec![
275 ("string", "StringType"),
276 ("integer", "IntegerType"),
277 ("boolean", "BooleanType"),
278 ("decimal", "DecimalType"),
279 ("uri", "UriType"),
280 ("dateTime", "DateTimeType"),
281 ("time", "TimeType"),
282 ("instant", "InstantType"),
283 ("positiveInt", "PositiveIntType"),
284 ];
285
286 for (input_name, expected_name) in test_cases {
287 let structure_def = StructureDefinition {
288 resource_type: "StructureDefinition".to_string(),
289 id: input_name.to_string(),
290 url: format!("http://hl7.org/fhir/StructureDefinition/{input_name}"),
291 name: input_name.to_string(),
292 title: Some(input_name.to_string()),
293 status: "active".to_string(),
294 kind: "primitive-type".to_string(),
295 is_abstract: false,
296 description: Some(format!("FHIR {input_name} primitive type")),
297 purpose: None,
298 base_type: input_name.to_string(),
299 base_definition: Some(
300 "http://hl7.org/fhir/StructureDefinition/Element".to_string(),
301 ),
302 version: None,
303 differential: None,
304 snapshot: None,
305 };
306
307 let result = primitive_generator.generate_primitive_type_alias(&structure_def);
308 assert!(
309 result.is_ok(),
310 "Failed to generate type alias for {input_name}"
311 );
312
313 let type_alias = result.unwrap();
314 assert_eq!(
315 type_alias.name, expected_name,
316 "Expected {expected_name} for {input_name}, got {}",
317 type_alias.name
318 );
319 }
320 }
321
322 #[test]
323 fn test_generate_primitive_element_struct() {
324 use crate::config::CodegenConfig;
325
326 let config = CodegenConfig::default();
327 let mut type_cache = HashMap::new();
328 let mut primitive_generator = PrimitiveGenerator::new(&config, &mut type_cache);
329
330 let structure_def = StructureDefinition {
331 resource_type: "StructureDefinition".to_string(),
332 id: "string".to_string(),
333 url: "http://hl7.org/fhir/StructureDefinition/string".to_string(),
334 name: "string".to_string(),
335 title: Some("string".to_string()),
336 status: "active".to_string(),
337 kind: "primitive-type".to_string(),
338 is_abstract: false,
339 description: Some("A sequence of Unicode characters".to_string()),
340 purpose: None,
341 base_type: "string".to_string(),
342 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
343 version: None,
344 differential: None,
345 snapshot: None,
346 };
347
348 let result = primitive_generator.generate_primitive_element_struct(&structure_def);
349 assert!(result.is_ok());
350
351 let element_struct = result.unwrap();
352 assert_eq!(element_struct.name, "_string");
353 assert_eq!(element_struct.base_definition, Some("Element".to_string()));
354 assert!(element_struct.derives.contains(&"Debug".to_string()));
355 assert!(element_struct.derives.contains(&"Clone".to_string()));
356 }
357}