use std::fs;
use std::path::Path;
use anyhow::Result;
use chrono::Local;
#[derive(Debug, Clone)]
pub struct CrateGenerationParams<'a> {
pub output: &'a Path,
pub package: &'a str,
pub version: &'a str,
pub canonical_url: &'a str,
pub author: &'a str,
pub description: &'a str,
pub command_invoked: &'a str,
pub crate_name: Option<&'a str>,
}
#[derive(Debug, Clone)]
pub struct CrateStatistics {
pub num_structs: usize,
pub num_enums: usize,
pub total_types: usize,
pub canonical_url: String,
}
pub fn generate_crate_structure(params: CrateGenerationParams) -> Result<()> {
let src_dir = params.output.join("src");
fs::create_dir_all(&src_dir)?;
let resource_dir = src_dir.join("resources");
let datatypes_dir = src_dir.join("datatypes");
let extensions_dir = src_dir.join("extensions");
let primitives_dir = src_dir.join("primitives");
let traits_dir = src_dir.join("traits");
let bindings_dir = src_dir.join("bindings");
let profiles_dir = src_dir.join("profiles");
fs::create_dir_all(&resource_dir)?;
fs::create_dir_all(&datatypes_dir)?;
fs::create_dir_all(&extensions_dir)?;
fs::create_dir_all(&primitives_dir)?;
fs::create_dir_all(&traits_dir)?;
fs::create_dir_all(&bindings_dir)?;
fs::create_dir_all(&profiles_dir)?;
let stats = generate_crate_statistics_from_organized_dirs(
&resource_dir,
&datatypes_dir,
&extensions_dir,
&primitives_dir,
)?;
let cargo_toml_content = generate_cargo_toml(
params.package,
params.version,
params.output,
params.crate_name,
);
let cargo_toml_path = params.output.join("Cargo.toml");
fs::write(&cargo_toml_path, cargo_toml_content)?;
let lib_rs_content = generate_lib_rs_idiomatic();
let lib_rs_path = src_dir.join("lib.rs");
fs::write(&lib_rs_path, lib_rs_content)?;
let macros_content = include_str!("../macros.rs");
let macros_path = src_dir.join("macros.rs");
fs::write(¯os_path, macros_content)?;
let validation_content =
crate::generators::ValidationTraitGenerator::generate_validation_module();
let validation_path = src_dir.join("validation.rs");
fs::write(&validation_path, validation_content)?;
let prelude_content = generate_prelude_module();
let prelude_path = src_dir.join("prelude.rs");
fs::write(&prelude_path, prelude_content)?;
generate_module_files(
&resource_dir,
&datatypes_dir,
&extensions_dir,
&primitives_dir,
&traits_dir,
&bindings_dir,
&profiles_dir,
)?;
let readme_content = generate_readme_md(
params.package,
params.version,
params.canonical_url,
params.author,
params.description,
params.command_invoked,
&stats,
params.crate_name,
);
let readme_path = params.output.join("README.md");
fs::write(&readme_path, readme_content)?;
Ok(())
}
fn generate_cargo_toml(
package: &str,
version: &str,
output_dir: &Path,
crate_name_override: Option<&str>,
) -> String {
let derived = package.replace(['.', '-'], "_");
let crate_name = crate_name_override.unwrap_or(&derived);
let lib_name = crate_name.replace('-', "_");
let rh_foundation_path = foundation_relative_path(output_dir);
let rh_foundation_version = env!("CARGO_PKG_VERSION");
let release_keyword = package
.split('.')
.find(|seg| {
seg.len() >= 2 && seg.starts_with('r') && seg[1..].chars().all(|c| c.is_ascii_digit())
})
.map(|seg| format!("\"fhir-{seg}\", "))
.unwrap_or_default();
format!(
r#"[package]
name = "{crate_name}"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
rust-version.workspace = true
description = "Generated FHIR types from {package} package version {version}"
authors.workspace = true
keywords = ["fhir", {release_keyword}"healthcare", "hl7", "types"]
categories = ["api-bindings", "data-structures"]
readme = "README.md"
[dependencies]
serde = {{ version = "1.0", features = ["derive"] }}
serde_json = "1.0"
phf = {{ version = "0.11", features = ["macros"] }}
once_cell = "1.19"
rh-foundation = {{ path = "{rh_foundation_path}", version = "{rh_foundation_version}" }}
[lib]
name = "{lib_name}"
path = "src/lib.rs"
[lints]
workspace = true
"#
)
}
fn foundation_relative_path(output_dir: &Path) -> String {
let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
return "../rh-foundation".to_string();
};
let Some(workspace_root) = Path::new(&manifest_dir).parent().and_then(|p| p.parent()) else {
return "../rh-foundation".to_string();
};
let foundation = workspace_root.join("crates/rh-foundation");
let (Ok(foundation), Ok(output)) = (foundation.canonicalize(), output_dir.canonicalize())
else {
return "../rh-foundation".to_string();
};
let from: Vec<_> = output.components().collect();
let to: Vec<_> = foundation.components().collect();
let common = from
.iter()
.zip(to.iter())
.take_while(|(a, b)| a == b)
.count();
let mut parts: Vec<String> = vec!["..".to_string(); from.len() - common];
parts.extend(
to[common..]
.iter()
.map(|c| c.as_os_str().to_string_lossy().into_owned()),
);
if parts.is_empty() {
".".to_string()
} else {
parts.join("/")
}
}
fn generate_lib_rs_idiomatic() -> String {
let lib_content = r#"//! Generated FHIR Rust bindings
//!
//! This crate contains Rust types and traits for FHIR resources and data types.
//! It includes macros for primitive field generation and maintains FHIR compliance.
// Allow clippy lint for derivable Default implementations
//
// TODO: Future optimization - derive Default when possible instead of manual impl
//
// Currently, we generate explicit Default implementations for all structs.
// Many of these could use #[derive(Default)] instead, which would be more idiomatic.
//
// Pros of deriving Default:
// - More idiomatic Rust code
// - Less generated code (no manual impl blocks)
// - Clearer intent (all fields use Default::default())
//
// Cons of current approach (manual impl):
// - Clippy warns about 1,100+ derivable implementations
// - More verbose generated code
//
// Pros of current approach:
// - Explicit and predictable behavior
// - Handles mixed initialization patterns consistently
// - Simpler code generation logic
//
// To implement derive-based approach would require:
// 1. Analyze all field types to ensure they implement Default
// 2. Detect required fields with non-Default initializations (String::new(), Vec::new(), etc.)
// 3. Add "Default" to struct derives only when ALL fields can use Default::default()
// 4. Skip manual impl generation for those structs
//
#![allow(clippy::derivable_impls)]
pub mod macros;
pub mod metadata;
pub mod primitives;
pub mod datatypes;
pub mod extensions;
pub mod resources;
pub mod profiles;
pub mod traits;
pub mod bindings;
pub mod validation;
pub mod prelude;
pub use serde::{Deserialize, Serialize};
"#;
lib_content.to_string()
}
pub fn generate_module_files(
resource_dir: &Path,
datatypes_dir: &Path,
extensions_dir: &Path,
primitives_dir: &Path,
traits_dir: &Path,
bindings_dir: &Path,
profiles_dir: &Path,
) -> Result<()> {
let resource_mod_content = generate_mod_rs_for_directory(resource_dir, "FHIR resource types")?;
fs::write(resource_dir.join("mod.rs"), resource_mod_content)?;
let datatypes_mod_content = generate_mod_rs_for_directory(datatypes_dir, "FHIR data types")?;
fs::write(datatypes_dir.join("mod.rs"), datatypes_mod_content)?;
let extensions_mod_content =
generate_mod_rs_for_directory(extensions_dir, "FHIR extension types")?;
fs::write(extensions_dir.join("mod.rs"), extensions_mod_content)?;
let primitives_mod_content =
generate_mod_rs_for_directory(primitives_dir, "FHIR primitive types")?;
fs::write(primitives_dir.join("mod.rs"), primitives_mod_content)?;
let traits_mod_content = generate_mod_rs_for_directory(
traits_dir,
"FHIR trait definitions for resources and profiles",
)?;
fs::write(traits_dir.join("mod.rs"), traits_mod_content)?;
let bindings_mod_content =
generate_mod_rs_for_directory(bindings_dir, "FHIR ValueSet bindings and enums")?;
fs::write(bindings_dir.join("mod.rs"), bindings_mod_content)?;
let profiles_mod_content =
generate_mod_rs_for_directory(profiles_dir, "FHIR profiles derived from core resources")?;
fs::write(profiles_dir.join("mod.rs"), profiles_mod_content)?;
Ok(())
}
fn generate_mod_rs_for_directory(dir: &Path, description: &str) -> Result<String> {
let mut content = String::new();
content.push_str(&format!("//! {description}\n\n"));
let mut rs_files = Vec::new();
if dir.exists() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if stem != "mod" {
rs_files.push(stem.to_string());
}
}
}
}
}
rs_files.sort();
for module_name in &rs_files {
content.push_str(&format!("pub mod {module_name};\n"));
}
Ok(content)
}
fn generate_crate_statistics_from_organized_dirs(
resource_dir: &Path,
datatypes_dir: &Path,
extensions_dir: &Path,
primitives_dir: &Path,
) -> Result<CrateStatistics> {
let mut num_structs = 0;
for dir in [resource_dir, datatypes_dir, extensions_dir, primitives_dir] {
if dir.exists() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if stem != "mod" {
num_structs += 1;
if let Ok(content) = fs::read_to_string(&path) {
num_structs +=
content.matches("pub struct ").count().saturating_sub(1);
}
}
}
}
}
}
}
Ok(CrateStatistics {
num_structs,
num_enums: 0,
total_types: num_structs,
canonical_url: "Unknown".to_string(),
})
}
#[allow(clippy::too_many_arguments)]
fn generate_readme_md(
package: &str,
version: &str,
canonical_url: &str,
author: &str,
description: &str,
command_invoked: &str,
stats: &CrateStatistics,
crate_name_override: Option<&str>,
) -> String {
let derived = package.replace(['.', '-'], "_");
let crate_name = crate_name_override.unwrap_or(&derived).replace('-', "_");
let mut content = String::new();
content.push_str(&format!("# {crate_name}\n\n"));
content.push_str(&format!("**Generated FHIR Types for {package}**\n\n"));
content.push_str(&format!("This crate contains automatically generated Rust types for FHIR (Fast Healthcare Interoperability Resources) based on the `{package}` package.\n\n"));
content.push_str("## Important Notice\n\n");
content
.push_str("**This crate was automatically generated using the RH codegen CLI tool.**\n\n");
content.push_str(&format!(
"- **Generator command**:\n```bash\n{command_invoked}\n```\n\n"
));
content.push_str(&format!("- **Generation timestamp**: {}\n\n", Local::now()));
content.push_str("## Package Information\n\n");
content.push_str(&format!("* **Package Name** {package}\n"));
content.push_str(&format!("* **Package Author** {author}\n"));
content.push_str(&format!("* **Version** {version}\n"));
content.push_str(&format!("* **Canonical URL** `{canonical_url}`\n\n"));
content.push_str(&format!(
"**Statistics: {} structs, {} enums, {} total types**\n\n",
stats.num_structs, stats.num_enums, stats.total_types
));
content.push_str(&format!("## Description\n\n{description}"));
content.push_str("\n\n");
content.push_str("## Features\n\n");
content.push_str(
"- **Complete FHIR type definitions** - All resources, datatypes, and primitives\n",
);
content.push_str(
"- **Serde serialization** - Built-in JSON serialization/deserialization support\n",
);
content.push_str(
"- **Type metadata** - Compile-time metadata for field types and path resolution\n",
);
content.push_str(
"- **Idiomatic Rust** - Clean, organized module structure with proper naming conventions\n",
);
content.push_str("- **Zero-cost abstractions** - PHF (perfect hash function) maps for O(1) metadata lookups\n\n");
content.push_str("## Usage\n\n");
content.push_str("Add this crate to your `Cargo.toml`:\n\n");
content.push_str("```toml\n");
content.push_str("[dependencies]\n");
content.push_str(&format!("{crate_name} = \"0.1.0\"\n"));
content.push_str("```\n\n");
content.push_str("### Deserializing FHIR Resources\n\n");
content.push_str("```rust\n");
content.push_str(&format!("use {crate_name}::resources::patient::Patient;\n"));
content.push_str("use serde_json;\n\n");
content.push_str("let json_data = r#\"{\\\"resourceType\\\": \\\"Patient\\\", \\\"id\\\": \\\"example\\\"}\"#;\n");
content.push_str("let patient: Patient = serde_json::from_str(json_data)?;\n\n");
content.push_str("println!(\"Patient ID: {}\", patient.id.unwrap_or_default());\n");
content.push_str("```\n\n");
content.push_str("### Creating Resources Programmatically\n\n");
content.push_str("This crate provides two idiomatic ways to work with FHIR resources using builder traits:\n\n");
content.push_str("#### Option 1: Resource Module with Re-exported Traits (Recommended)\n\n");
content.push_str("Each resource module re-exports its associated traits for convenience:\n\n");
content.push_str("```rust\n");
content.push_str("// Import resource with its traits - all in one place!\n");
content.push_str(&format!(
"use {crate_name}::resources::patient::{{Patient, PatientMutators}};\n"
));
content.push_str(&format!(
"use {crate_name}::prelude::*; // Gets base traits (ResourceMutators, etc.)\n"
));
content.push_str(&format!(
"use {crate_name}::datatypes::human_name::HumanName;\n\n"
));
content.push_str("// Build a patient using the builder pattern\n");
content.push_str("let patient = <Patient as PatientMutators>::new()\n");
content.push_str(" .set_id(\"patient-123\".to_string())\n");
content.push_str(" .set_active(true)\n");
content.push_str(" .add_name(HumanName {\n");
content.push_str(" family: Some(\"Doe\".to_string()),\n");
content.push_str(" given: vec![\"John\".to_string()],\n");
content.push_str(" ..Default::default()\n");
content.push_str(" })\n");
content.push_str(" .set_gender(Some(AdministrativeGender::Male))\n");
content.push_str(" .set_birth_date(\"1990-01-15\".to_string());\n");
content.push_str("```\n\n");
content.push_str("#### Option 2: Prelude Module\n\n");
content.push_str("For common base traits, use the prelude module:\n\n");
content.push_str("```rust\n");
content.push_str(&format!(
"use {crate_name}::prelude::*; // ValidatableResource, ResourceMutators, etc.\n"
));
content.push_str(&format!(
"use {crate_name}::resources::patient::{{Patient, PatientMutators}};\n\n"
));
content.push_str("let patient = <Patient as PatientMutators>::new()\n");
content.push_str(" .set_id(\"example\".to_string());\n");
content.push_str("```\n\n");
content.push_str("The prelude includes:\n");
content.push_str("- `ValidatableResource` - Access invariants and validation rules\n");
content.push_str("- `ResourceMutators` - Builder methods for all resources\n");
content.push_str("- `DomainResourceMutators` - Builder methods for domain resources\n\n");
content.push_str("#### Direct Struct Construction\n\n");
content.push_str("You can also construct resources directly:\n\n");
content.push_str("```rust\n");
content.push_str(&format!("use {crate_name}::resources::patient::Patient;\n"));
content.push_str(&format!(
"use {crate_name}::datatypes::human_name::HumanName;\n"
));
content.push_str(&format!(
"use {crate_name}::bindings::administrative_gender::AdministrativeGender;\n\n"
));
content.push_str("let patient = Patient {\n");
content.push_str(" id: Some(\"patient-123\".to_string()),\n");
content.push_str(" active: Some(true),\n");
content.push_str(" name: vec![HumanName {\n");
content.push_str(" family: Some(\"Doe\".to_string()),\n");
content.push_str(" given: vec![\"John\".to_string()],\n");
content.push_str(" ..Default::default()\n");
content.push_str(" }],\n");
content.push_str(" gender: Some(AdministrativeGender::Male),\n");
content.push_str(" birth_date: Some(\"1990-01-15\".to_string()),\n");
content.push_str(" ..Default::default()\n");
content.push_str("};\n");
content.push_str("```\n\n");
content.push_str("### Using Type Metadata\n\n");
content.push_str("This crate includes compile-time metadata for all FHIR types, enabling runtime type introspection and path resolution:\n\n");
content.push_str("```rust\n");
content.push_str(&format!("use {crate_name}::metadata::{{resolve_path, get_field_info, FhirFieldType, FhirPrimitiveType}};\n\n"));
content.push_str("// Resolve nested paths to their FHIR types\n");
content.push_str("if let Some(field_type) = resolve_path(\"Patient.birthDate\") {\n");
content.push_str(" match field_type {\n");
content.push_str(" FhirFieldType::Primitive(FhirPrimitiveType::Date) => {\n");
content.push_str(" println!(\"birthDate is a FHIR date type\");\n");
content.push_str(" }\n");
content.push_str(" _ => {}\n");
content.push_str(" }\n");
content.push_str("}\n\n");
content.push_str("// Resolve complex nested paths\n");
content.push_str("if let Some(field_type) = resolve_path(\"Patient.name.given\") {\n");
content.push_str(" match field_type {\n");
content.push_str(" FhirFieldType::Primitive(FhirPrimitiveType::String) => {\n");
content.push_str(" println!(\"name.given is a string array\");\n");
content.push_str(" }\n");
content.push_str(" _ => {}\n");
content.push_str(" }\n");
content.push_str("}\n\n");
content.push_str("// Get field information directly\n");
content.push_str("if let Some(field_info) = get_field_info(\"Patient\", \"active\") {\n");
content.push_str(" println!(\"Min cardinality: {}\", field_info.min);\n");
content.push_str(" println!(\"Max cardinality: {:?}\", field_info.max);\n");
content.push_str(" println!(\"Is choice type: {}\", field_info.is_choice_type);\n");
content.push_str("}\n");
content.push_str("```\n\n");
content.push_str("The metadata system enables:\n");
content.push_str("- **Path resolution** - Navigate nested paths like `Patient.name.given`\n");
content.push_str("- **Type introspection** - Determine field types at runtime\n");
content.push_str("- **Cardinality information** - Min/max occurrence constraints\n");
content.push_str("- **Choice type detection** - Identify polymorphic fields\n");
content
.push_str("- **Zero runtime cost** - All lookups use compile-time perfect hash maps\n\n");
content.push_str("## Structure\n\n");
content.push_str("This crate organizes FHIR types into logical modules:\n\n");
content.push_str("- **resources/** - All FHIR resources (Patient, Observation, etc.)\n");
content.push_str("- **profiles/** - FHIR profiles (Vitalsigns, BodyHeight, etc.)\n");
content.push_str(
"- **datatypes/** - Complex and primitive datatypes (HumanName, Address, etc.)\n",
);
content.push_str("- **bindings/** - ValueSet enumerations (AdministrativeGender, etc.)\n");
content.push_str("- **primitives/** - Base primitive types (DateType, DateTimeType, etc.)\n");
content.push_str("- **traits/** - Mutator, accessor, and existence traits for all types\n");
content.push_str(
"- **prelude.rs** - Commonly used traits (ValidatableResource, ResourceMutators, etc.)\n",
);
content.push_str("- **metadata/** - Type metadata split by category (resources, datatypes, primitives) for faster incremental compilation\n\n");
content.push_str("## Regenerating This Crate\n\n");
content.push_str("To regenerate this crate with updated FHIR definitions:\n\n");
content.push_str("```bash\n");
content.push_str(command_invoked);
content.push_str("\n```\n\n");
content.push_str("## License\n\n");
content.push_str("This generated crate is provided under MIT OR Apache-2.0 license.\n\n");
content.push_str("## Related Links\n\n");
content.push_str("- [FHIR Specification](https://hl7.org/fhir/)\n");
content.push_str("- [FHIR Package Registry](https://packages.fhir.org/)\n");
content.push_str("- [RH Project](https://github.com/reasonhealth/rh)\n\n");
content.push_str("---\n\n");
content.push_str(&format!(
"*Generated by RH codegen tool at {}*\n",
Local::now()
));
content
}
fn generate_prelude_module() -> String {
r#"//! Prelude module - commonly used traits for convenience
//!
//! This module re-exports the most commonly used traits for working with
//! FHIR resources. Import this module to avoid having to import individual
//! traits from the `traits` module.
//!
//! # Example
//!
//! ```ignore
//! use hl7_fhir_r4_core::prelude::*;
//! use hl7_fhir_r4_core::resources::patient::Patient;
//!
//! // All mutator traits are now in scope
//! let patient = <Patient as PatientMutators>::new()
//! .set_id("example".to_string())
//! .set_active(true);
//! ```
// Resource mutator traits - for building resources with method chaining
pub use crate::traits::resource::ResourceMutators;
pub use crate::traits::domain_resource::DomainResourceMutators;
// Note: Individual resource mutator traits (PatientMutators, ObservationMutators, etc.)
// are re-exported from their respective resource modules for convenience.
// For example: use hl7_fhir_r4_core::resources::patient::PatientMutators;
// Validation trait
pub use crate::validation::ValidatableResource;
"#
.to_string()
}
pub fn parse_package_metadata(package_json_path: &Path) -> Result<(String, String, String)> {
let package_json_content = fs::read_to_string(package_json_path)?;
let package_json: serde_json::Value = serde_json::from_str(&package_json_content)?;
let canonical = package_json
.get("canonical")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string();
let author = package_json
.get("author")
.and_then(|v| v.as_str())
.unwrap_or("FHIR Code Generator")
.to_string();
let description = package_json
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("Generated FHIR types crate.")
.to_string();
Ok((canonical, author, description))
}
impl<'a> crate::generators::file_generator::FileGenerator<'a> {
pub fn generate_complete_crate<P: AsRef<Path>>(
&self,
output_dir: P,
crate_name: &str,
_structures: &[crate::fhir_types::StructureDefinition],
) -> crate::CodegenResult<()> {
let output_dir = output_dir.as_ref();
let src_dir = output_dir.join("src");
fs::create_dir_all(&src_dir)?;
let primitives_dir = src_dir.join("primitives");
let datatypes_dir = src_dir.join("datatypes");
let extensions_dir = src_dir.join("extensions");
let resource_dir = src_dir.join("resource");
let traits_dir = src_dir.join("traits");
fs::create_dir_all(&primitives_dir)?;
fs::create_dir_all(&datatypes_dir)?;
fs::create_dir_all(&extensions_dir)?;
fs::create_dir_all(&resource_dir)?;
fs::create_dir_all(&traits_dir)?;
self.generate_lib_file(src_dir.join("lib.rs"))?;
self.generate_macros_file(src_dir.join("macros.rs"))?;
self.generate_combined_primitives_file(&[], primitives_dir.join("mod.rs"))?;
let cargo_toml_path = output_dir.join("Cargo.toml");
if !cargo_toml_path.exists() {
self.generate_cargo_toml(&cargo_toml_path, crate_name)?;
}
self.generate_module_file(&datatypes_dir, &[])?;
self.generate_module_file(&extensions_dir, &[])?;
self.generate_module_file(&resource_dir, &[])?;
self.generate_module_file(&traits_dir, &[])?;
Ok(())
}
pub(crate) fn generate_cargo_toml<P: AsRef<Path>>(
&self,
cargo_path: P,
crate_name: &str,
) -> crate::CodegenResult<()> {
let cargo_content = format!(
r#"[package]
name = "{crate_name}"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = {{ version = "1.0", features = ["derive"] }}
serde_json = "1.0"
"#
);
fs::write(cargo_path, cargo_content)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cargo_toml_uses_relative_foundation_path_for_sibling_crates() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let output = Path::new(&manifest_dir)
.parent()
.unwrap()
.join("rh-hl7_fhir_r4_core");
let toml = generate_cargo_toml(
"hl7.fhir.r4.core",
"4.0.1",
&output,
Some("rh-hl7-fhir-r4-core"),
);
assert!(toml.contains(r#"rh-foundation = { path = "../rh-foundation""#));
}
#[test]
fn cargo_toml_never_contains_absolute_paths() {
let toml = generate_cargo_toml(
"hl7.fhir.r5.core",
"5.0.0",
Path::new("/nonexistent/output/dir"),
Some("rh-hl7-fhir-r5-core"),
);
assert!(toml.contains(r#"path = "../rh-foundation""#));
for line in toml.lines() {
assert!(
!line.contains("path = \"/"),
"absolute path emitted: {line}"
);
}
}
#[test]
fn cargo_toml_inherits_workspace_metadata() {
let toml = generate_cargo_toml("hl7.fhir.r4.core", "4.0.1", Path::new("."), None);
for key in [
"version.workspace = true",
"edition.workspace = true",
"license.workspace = true",
"repository.workspace = true",
"rust-version.workspace = true",
"authors.workspace = true",
] {
assert!(toml.contains(key), "missing workspace inheritance: {key}");
}
assert!(toml.contains(r#""fhir-r4""#));
}
}