use crate::cli::pipeline::version_core::read_version;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::ApiSurface;
use anyhow::Context as _;
use std::path::Path;
use tracing::info;
pub(super) fn extract_raw(config: &ResolvedCrateConfig, _config_path: &Path) -> anyhow::Result<ApiSurface> {
info!("Extracting API surface from Rust source...");
let version = read_version(&config.version_from)?;
let workspace_root = config.workspace_root.as_deref();
let default_name = &config.name;
let mut groups: std::collections::BTreeMap<String, Vec<&Path>> = std::collections::BTreeMap::new();
if !config.source_crates.is_empty() {
for sc in &config.source_crates {
let crate_name = sc.name.replace('-', "_");
for source in &sc.sources {
groups.entry(crate_name.clone()).or_default().push(source.as_path());
}
}
} else {
for source in &config.sources {
let crate_name = derive_crate_name_from_path(source, default_name);
groups.entry(crate_name).or_default().push(source.as_path());
}
}
let mut merged = ApiSurface {
crate_name: default_name.to_string(),
version: version.clone(),
..ApiSurface::default()
};
for (crate_name, sources) in &groups {
let api = crate::extract::extractor::extract(sources, crate_name, &version, workspace_root)
.with_context(|| format!("failed to extract API surface from crate {crate_name}"))?;
merged.types.extend(api.types);
merged.functions.extend(api.functions);
merged.enums.extend(api.enums);
merged.errors.extend(api.errors);
merged.excluded_type_paths.extend(api.excluded_type_paths);
merged.excluded_trait_names.extend(api.excluded_trait_names);
merged.unsupported_public_items.extend(api.unsupported_public_items);
}
let return_type_names: ahash::AHashSet<String> = merged
.functions
.iter()
.filter_map(|f| match &f.return_type {
crate::core::ir::TypeRef::Named(name) => Some(name.clone()),
_ => None,
})
.collect();
for typ in &mut merged.types {
if return_type_names.contains(&typ.name) {
typ.is_return_type = true;
}
}
Ok(merged)
}
fn derive_crate_name_from_path(path: &Path, default: &str) -> String {
let path_str = path.to_string_lossy();
if let Some(after_crates) = path_str.split("crates/").nth(1) {
if let Some(name) = after_crates.split('/').next() {
if path_str.contains(&format!("crates/{name}/src/")) {
return name.replace('-', "_");
}
}
}
default.to_string()
}