use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use anyhow::{Context, Result};
use cviz::parse::component::parse_component;
use crate::compose::{build_graph_from_components, filename_from_path};
use crate::contract::ContractResult;
use crate::parse::config::parse_yaml;
use crate::split::split_out_composition;
use crate::wac::{generate_wac, GeneratedAdapter};
#[derive(Debug, Clone)]
pub struct SpliceRequest {
pub composition_wasm: PathBuf,
pub rules_yaml: String,
pub package_name: String,
pub splits_dir: PathBuf,
pub skip_type_check: bool,
}
#[derive(Debug, Clone)]
pub struct SpliceOutput {
pub wac: String,
pub wac_deps: BTreeMap<String, PathBuf>,
pub diagnostics: Vec<ContractResult>,
pub generated_adapters: Vec<GeneratedAdapter>,
}
impl SpliceOutput {
pub fn wac_compose_cmd(&self, wac_path: &str) -> String {
format_wac_compose_cmd(wac_path, &self.wac_deps)
}
}
#[derive(Debug, Clone)]
pub struct ComponentInput {
pub alias: Option<String>,
pub path: PathBuf,
}
#[derive(Debug, Clone)]
pub struct ComposeRequest {
pub components: Vec<ComponentInput>,
pub package_name: String,
}
#[derive(Debug, Clone)]
pub struct ComposeOutput {
pub wac: String,
pub wac_deps: BTreeMap<String, PathBuf>,
pub diagnostics: Vec<ContractResult>,
pub generated_adapters: Vec<GeneratedAdapter>,
}
impl ComposeOutput {
pub fn wac_compose_cmd(&self, wac_path: &str) -> String {
format_wac_compose_cmd(wac_path, &self.wac_deps)
}
}
pub fn splice(req: SpliceRequest) -> Result<SpliceOutput> {
let SpliceRequest {
composition_wasm,
rules_yaml,
package_name,
splits_dir,
skip_type_check,
} = req;
let cfg = parse_yaml(&rules_yaml).context("Failed to parse splice rules YAML")?;
let bytes = std::fs::read(&composition_wasm).with_context(|| {
format!(
"Failed to read composition wasm: {}",
composition_wasm.display()
)
})?;
let graph = parse_component(&bytes).with_context(|| {
format!(
"Failed to parse composition graph from: {}",
composition_wasm.display()
)
})?;
let splits_dir_str = splits_dir
.to_str()
.ok_or_else(|| {
anyhow::anyhow!(
"splits_dir contains non-UTF-8 bytes: {}",
splits_dir.display()
)
})?
.to_string();
let (splits_path, shim_comps) =
split_out_composition(&composition_wasm, &Some(splits_dir_str))?;
let out = generate_wac(shim_comps, &splits_path, &graph, &cfg, None, &package_name)?;
if !skip_type_check {
for diag in &out.diagnostics {
if let ContractResult::Error(msg) = diag {
anyhow::bail!("Contract type-check error: {msg}");
}
}
}
Ok(SpliceOutput {
wac: out.wac,
wac_deps: out.wac_deps,
diagnostics: out.diagnostics,
generated_adapters: out.generated_adapters,
})
}
pub fn compose(req: ComposeRequest) -> Result<ComposeOutput> {
let ComposeRequest {
components,
package_name,
} = req;
let mut resolved: Vec<(String, PathBuf, Vec<u8>)> = Vec::with_capacity(components.len());
for ComponentInput { alias, path } in &components {
let name = alias.clone().unwrap_or_else(|| filename_from_path(path));
let bytes = std::fs::read(path)
.with_context(|| format!("Failed to read Wasm component: {}", path.display()))?;
resolved.push((name, path.clone(), bytes));
}
{
let mut seen: HashMap<&str, &PathBuf> = HashMap::new();
for (name, path, _) in &resolved {
if let Some(prev) = seen.insert(name.as_str(), path) {
anyhow::bail!(
"Name conflict: '{}' and '{}' both resolve to the name '{}'.\n\
Use aliases to disambiguate, e.g.:\n\
\t{}0={} {}1={}",
prev.display(),
path.display(),
name,
name,
prev.display(),
name,
path.display(),
);
}
}
}
let (graph, node_paths) = build_graph_from_components(&resolved)?;
let out = generate_wac(
HashMap::new(),
"",
&graph,
&[],
Some(&node_paths),
&package_name,
)?;
Ok(ComposeOutput {
wac: out.wac,
wac_deps: out.wac_deps,
diagnostics: out.diagnostics,
generated_adapters: out.generated_adapters,
})
}
pub fn format_wac_compose_cmd(wac_path: &str, deps: &BTreeMap<String, PathBuf>) -> String {
let mut cmd = format!("wac compose {wac_path} ");
for (pkg_key, pkg_path) in deps {
cmd.push_str(&format!(
"\\\n --dep {pkg_key}=\"{}\" ",
pkg_path.display()
));
}
cmd
}