use std::collections::HashMap;
use crate::config::CodegenConfig;
use crate::fhir_types::StructureDefinition;
use crate::generators::DocumentationGenerator;
use crate::rust_types::{RustField, RustStruct, RustType, RustTypeAlias};
use crate::CodegenResult;
pub struct PrimitiveGenerator<'a> {
config: &'a CodegenConfig,
type_cache: &'a mut HashMap<String, RustStruct>,
}
impl<'a> PrimitiveGenerator<'a> {
pub fn new(config: &'a CodegenConfig, type_cache: &'a mut HashMap<String, RustStruct>) -> Self {
Self { config, type_cache }
}
pub fn generate_all_primitive_type_aliases(
&self,
primitive_structure_defs: &[StructureDefinition],
) -> CodegenResult<Vec<RustTypeAlias>> {
let mut type_aliases = Vec::new();
for structure_def in primitive_structure_defs {
let type_alias = self.generate_primitive_type_alias(structure_def)?;
type_aliases.push(type_alias);
}
Ok(type_aliases)
}
pub fn generate_primitive_type_alias(
&self,
structure_def: &StructureDefinition,
) -> CodegenResult<RustTypeAlias> {
let rust_primitive_type = Self::map_fhir_primitive_to_rust_type(&structure_def.name);
let type_alias_name = format!(
"{}Type",
Self::fhir_primitive_to_pascal_case(&structure_def.name)
);
let mut type_alias = RustTypeAlias::new(type_alias_name, rust_primitive_type);
let doc =
DocumentationGenerator::generate_primitive_type_alias_documentation(structure_def);
type_alias = type_alias.with_doc(doc);
Ok(type_alias)
}
fn fhir_primitive_to_pascal_case(name: &str) -> String {
match name {
"dateTime" => "DateTime".to_string(),
"positiveInt" => "PositiveInt".to_string(),
"unsignedInt" => "UnsignedInt".to_string(),
"base64Binary" => "Base64Binary".to_string(),
_ => {
let mut chars = name.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
}
}
pub fn generate_primitive_type_struct(
&mut self,
structure_def: &StructureDefinition,
mut rust_struct: RustStruct,
) -> CodegenResult<RustStruct> {
rust_struct.base_definition = None;
let rust_primitive_type = Self::map_fhir_primitive_to_rust_type(&structure_def.name);
let value_field = RustField::new("value".to_string(), rust_primitive_type);
rust_struct.add_field(value_field);
let struct_name = rust_struct.name.clone();
self.type_cache.insert(struct_name, rust_struct.clone());
Ok(rust_struct)
}
pub fn generate_primitive_element_struct(
&mut self,
structure_def: &StructureDefinition,
) -> CodegenResult<RustStruct> {
let element_struct_name = format!("_{}", structure_def.name);
if let Some(cached_struct) = self.type_cache.get(&element_struct_name) {
return Ok(cached_struct.clone());
}
let mut element_struct = RustStruct::new(element_struct_name.clone());
element_struct.doc_comment = Some(
DocumentationGenerator::generate_primitive_element_documentation(&structure_def.name),
);
let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
if self.config.with_serde {
derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
}
element_struct.derives = derives;
element_struct.base_definition = Some("Element".to_string());
self.type_cache
.insert(element_struct_name, element_struct.clone());
Ok(element_struct)
}
pub fn map_fhir_primitive_to_rust_type(primitive_name: &str) -> RustType {
match primitive_name {
"boolean" => RustType::Boolean,
"integer" | "positiveInt" | "unsignedInt" => RustType::Integer,
"decimal" => RustType::Float,
"string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical" | "oid"
| "uuid" | "base64Binary" | "xhtml" => RustType::String,
"date" | "dateTime" | "time" | "instant" => RustType::String, _ => RustType::String, }
}
pub fn is_fhir_primitive_type(name: &str) -> bool {
matches!(
name,
"boolean"
| "integer"
| "positiveInt"
| "unsignedInt"
| "decimal"
| "string"
| "code"
| "id"
| "markdown"
| "uri"
| "url"
| "canonical"
| "oid"
| "uuid"
| "base64Binary"
| "xhtml"
| "date"
| "dateTime"
| "time"
| "instant"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_map_fhir_primitive_to_rust_type() {
assert!(matches!(
PrimitiveGenerator::map_fhir_primitive_to_rust_type("boolean"),
RustType::Boolean
));
assert!(matches!(
PrimitiveGenerator::map_fhir_primitive_to_rust_type("integer"),
RustType::Integer
));
assert!(matches!(
PrimitiveGenerator::map_fhir_primitive_to_rust_type("decimal"),
RustType::Float
));
assert!(matches!(
PrimitiveGenerator::map_fhir_primitive_to_rust_type("string"),
RustType::String
));
assert!(matches!(
PrimitiveGenerator::map_fhir_primitive_to_rust_type("uri"),
RustType::String
));
assert!(matches!(
PrimitiveGenerator::map_fhir_primitive_to_rust_type("unknown"),
RustType::String
));
}
#[test]
fn test_is_fhir_primitive_type() {
assert!(PrimitiveGenerator::is_fhir_primitive_type("boolean"));
assert!(PrimitiveGenerator::is_fhir_primitive_type("string"));
assert!(PrimitiveGenerator::is_fhir_primitive_type("integer"));
assert!(PrimitiveGenerator::is_fhir_primitive_type("decimal"));
assert!(PrimitiveGenerator::is_fhir_primitive_type("uri"));
assert!(PrimitiveGenerator::is_fhir_primitive_type("dateTime"));
assert!(!PrimitiveGenerator::is_fhir_primitive_type("Patient"));
assert!(!PrimitiveGenerator::is_fhir_primitive_type("Identifier"));
assert!(!PrimitiveGenerator::is_fhir_primitive_type("unknown"));
}
#[test]
fn test_generate_primitive_type_alias() {
use crate::config::CodegenConfig;
let config = CodegenConfig::default();
let mut type_cache = HashMap::new();
let primitive_generator = PrimitiveGenerator::new(&config, &mut type_cache);
let structure_def = StructureDefinition {
resource_type: "StructureDefinition".to_string(),
id: "boolean".to_string(),
url: "http://hl7.org/fhir/StructureDefinition/boolean".to_string(),
name: "boolean".to_string(),
title: Some("boolean".to_string()),
status: "active".to_string(),
kind: "primitive-type".to_string(),
is_abstract: false,
description: Some("Value of 'true' or 'false'".to_string()),
purpose: None,
base_type: "boolean".to_string(),
base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
version: None,
differential: None,
snapshot: None,
};
let result = primitive_generator.generate_primitive_type_alias(&structure_def);
assert!(result.is_ok());
let type_alias = result.unwrap();
assert_eq!(type_alias.name, "BooleanType");
assert!(matches!(type_alias.target_type, RustType::Boolean));
}
#[test]
fn test_primitive_type_naming_convention() {
use crate::config::CodegenConfig;
let config = CodegenConfig::default();
let mut type_cache = HashMap::new();
let primitive_generator = PrimitiveGenerator::new(&config, &mut type_cache);
let test_cases = vec![
("string", "StringType"),
("integer", "IntegerType"),
("boolean", "BooleanType"),
("decimal", "DecimalType"),
("uri", "UriType"),
("dateTime", "DateTimeType"),
("time", "TimeType"),
("instant", "InstantType"),
("positiveInt", "PositiveIntType"),
];
for (input_name, expected_name) in test_cases {
let structure_def = StructureDefinition {
resource_type: "StructureDefinition".to_string(),
id: input_name.to_string(),
url: format!("http://hl7.org/fhir/StructureDefinition/{input_name}"),
name: input_name.to_string(),
title: Some(input_name.to_string()),
status: "active".to_string(),
kind: "primitive-type".to_string(),
is_abstract: false,
description: Some(format!("FHIR {input_name} primitive type")),
purpose: None,
base_type: input_name.to_string(),
base_definition: Some(
"http://hl7.org/fhir/StructureDefinition/Element".to_string(),
),
version: None,
differential: None,
snapshot: None,
};
let result = primitive_generator.generate_primitive_type_alias(&structure_def);
assert!(
result.is_ok(),
"Failed to generate type alias for {input_name}"
);
let type_alias = result.unwrap();
assert_eq!(
type_alias.name, expected_name,
"Expected {expected_name} for {input_name}, got {}",
type_alias.name
);
}
}
#[test]
fn test_generate_primitive_element_struct() {
use crate::config::CodegenConfig;
let config = CodegenConfig::default();
let mut type_cache = HashMap::new();
let mut primitive_generator = PrimitiveGenerator::new(&config, &mut type_cache);
let structure_def = StructureDefinition {
resource_type: "StructureDefinition".to_string(),
id: "string".to_string(),
url: "http://hl7.org/fhir/StructureDefinition/string".to_string(),
name: "string".to_string(),
title: Some("string".to_string()),
status: "active".to_string(),
kind: "primitive-type".to_string(),
is_abstract: false,
description: Some("A sequence of Unicode characters".to_string()),
purpose: None,
base_type: "string".to_string(),
base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
version: None,
differential: None,
snapshot: None,
};
let result = primitive_generator.generate_primitive_element_struct(&structure_def);
assert!(result.is_ok());
let element_struct = result.unwrap();
assert_eq!(element_struct.name, "_string");
assert_eq!(element_struct.base_definition, Some("Element".to_string()));
assert!(element_struct.derives.contains(&"Debug".to_string()));
assert!(element_struct.derives.contains(&"Clone".to_string()));
}
}