use std::collections::HashSet;
use std::fs;
use std::path::Path;
use anyhow::{Context, Result};
use camino::Utf8Path;
use uniffi_bindgen::interface::{AsType, ComponentInterface, Type, UniffiTraitMethods};
use super::types::*;
pub(super) fn parse_metadata(
source: &Path,
crate_name: Option<&str>,
library_mode: bool,
) -> Result<BindingsMetadata> {
if source.extension().and_then(|e| e.to_str()) != Some("udl") {
if !library_mode {
anyhow::bail!(
"source '{}' is not a UDL file and library mode is not enabled",
source.display()
);
}
let source_str = source
.to_str()
.ok_or_else(|| anyhow::anyhow!("library source path must be valid UTF-8"))?;
let source_utf8 = Utf8Path::new(source_str);
let cis = uniffi_bindgen::library_mode::find_cis(
source_utf8,
&uniffi_bindgen::EmptyCrateConfigSupplier,
)
.with_context(|| format!("failed to parse library metadata: {}", source.display()))?;
let ci = if let Some(crate_name) = crate_name {
cis.into_iter()
.find(|ci| ci.crate_name() == crate_name)
.ok_or_else(|| {
anyhow::anyhow!("crate '{crate_name}' not found in library metadata")
})?
} else {
cis.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("no UniFFI components found in library metadata"))?
};
let local_crate = ci.crate_name().to_string();
return component_interface_to_metadata(ci, &local_crate);
}
let udl = fs::read_to_string(source)
.with_context(|| format!("failed to read UDL: {}", source.display()))?;
let ci = ComponentInterface::from_webidl(&udl, LOCAL_CRATE_SENTINEL)
.with_context(|| format!("failed to parse UDL: {}", source.display()))?;
component_interface_to_metadata(ci, LOCAL_CRATE_SENTINEL)
}
fn component_interface_to_metadata(
ci: ComponentInterface,
local_crate: &str,
) -> Result<BindingsMetadata> {
let functions = ci
.function_definitions()
.iter()
.map(|f| FnDef {
name: f.name().to_string(),
args: f
.arguments()
.into_iter()
.map(|a| ArgDef {
name: a.name().to_string(),
type_: a.as_type(),
default: a.default_value().cloned(),
})
.collect(),
return_type: f.return_type().cloned(),
throws_type: f.throws_type().cloned(),
is_async: f.is_async(),
docstring: f.docstring().map(ToOwned::to_owned),
})
.collect();
let (errors, enums) = parse_enums(&ci);
let records = ci
.record_definitions()
.iter()
.map(|r| RecordDef {
name: r.name().to_string(),
fields: r
.fields()
.iter()
.map(|f| FieldDef {
name: f.name().to_string(),
type_: f.as_type(),
docstring: f.docstring().map(ToOwned::to_owned),
default: f.default_value().cloned(),
})
.collect(),
docstring: r.docstring().map(ToOwned::to_owned),
methods: parse_methods(r.methods()),
constructors: parse_constructors(r.constructors()),
traits: extract_traits(&r.uniffi_trait_methods()),
})
.collect();
let objects = ci
.object_definitions()
.iter()
.map(|o| ObjectDef {
name: o.name().to_string(),
is_error: ci.is_name_used_as_error(o.name()),
is_trait: o.is_trait_interface(),
constructors: o
.constructors()
.iter()
.map(|c| CtorDef {
name: c.name().to_string(),
args: c
.arguments()
.into_iter()
.map(|a| ArgDef {
name: a.name().to_string(),
type_: a.as_type(),
default: a.default_value().cloned(),
})
.collect(),
throws_type: c.throws_type().cloned(),
is_async: c.is_async(),
docstring: c.docstring().map(ToOwned::to_owned),
})
.collect(),
methods: {
let ms: Vec<_> = o.methods().into_iter().cloned().collect();
parse_methods(&ms)
},
docstring: o.docstring().map(ToOwned::to_owned),
traits: extract_traits(&o.uniffi_trait_methods()),
})
.collect();
let callback_interfaces = ci
.callback_interface_definitions()
.iter()
.map(|cb| CallbackInterfaceDef {
name: cb.name().to_string(),
methods: cb
.methods()
.iter()
.map(|m| CallbackMethodDef {
name: m.name().to_string(),
args: m
.arguments()
.into_iter()
.map(|a| ArgDef {
name: a.name().to_string(),
type_: a.as_type(),
default: a.default_value().cloned(),
})
.collect(),
return_type: m.return_type().cloned(),
is_async: m.is_async(),
docstring: m.docstring().map(ToOwned::to_owned),
})
.collect(),
docstring: cb.docstring().map(ToOwned::to_owned),
})
.collect();
let mut seen_custom: HashSet<String> = HashSet::new();
let mut custom_types: Vec<CustomTypeDef> = Vec::new();
for t in ci.iter_local_types() {
if let Type::Custom {
name,
builtin,
module_path,
} = t
{
if seen_custom.insert(name.clone()) {
custom_types.push(CustomTypeDef {
name: name.clone(),
builtin: *builtin.clone(),
module_path: module_path.clone(),
});
}
}
}
custom_types.sort_by(|a, b| a.name.cmp(&b.name));
Ok(BindingsMetadata {
namespace: ci.namespace().to_string(),
namespace_docstring: ci.namespace_docstring().map(ToOwned::to_owned),
local_crate: local_crate.to_string(),
ffi_namespace: ci.crate_name().to_string(),
functions,
errors,
enums,
records,
objects,
custom_types,
callback_interfaces,
})
}
pub(super) fn parse_methods(methods: &[uniffi_bindgen::interface::Method]) -> Vec<MethodDef> {
methods
.iter()
.map(|m| MethodDef {
name: m.name().to_string(),
args: m
.arguments()
.into_iter()
.map(|a| ArgDef {
name: a.name().to_string(),
type_: a.as_type(),
default: a.default_value().cloned(),
})
.collect(),
return_type: m.return_type().cloned(),
throws_type: m.throws_type().cloned(),
is_async: m.is_async(),
docstring: m.docstring().map(ToOwned::to_owned),
})
.collect()
}
pub(super) fn parse_constructors(
constructors: &[uniffi_bindgen::interface::Constructor],
) -> Vec<CtorDef> {
constructors
.iter()
.map(|c| CtorDef {
name: c.name().to_string(),
args: c
.arguments()
.into_iter()
.map(|a| ArgDef {
name: a.name().to_string(),
type_: a.as_type(),
default: a.default_value().cloned(),
})
.collect(),
throws_type: c.throws_type().cloned(),
is_async: c.is_async(),
docstring: c.docstring().map(ToOwned::to_owned),
})
.collect()
}
fn parse_enums(ci: &ComponentInterface) -> (Vec<ErrorDef>, Vec<EnumDef>) {
let mut errors = Vec::new();
let mut enums = Vec::new();
for e in ci.enum_definitions() {
let has_discr = e.variant_discr_type().is_some();
let variants: Vec<VariantDef> = e
.variants()
.iter()
.enumerate()
.map(|(i, v)| VariantDef {
name: v.name().to_string(),
fields: v
.fields()
.iter()
.map(|f| FieldDef {
name: f.name().to_string(),
type_: f.as_type(),
docstring: f.docstring().map(ToOwned::to_owned),
default: None,
})
.collect(),
docstring: v.docstring().map(ToOwned::to_owned),
discr: if has_discr {
e.variant_discr(i).ok()
} else {
None
},
})
.collect();
let methods = parse_methods(e.methods());
let constructors = parse_constructors(e.constructors());
let traits = extract_traits(&e.uniffi_trait_methods());
if ci.is_name_used_as_error(e.name()) {
errors.push(ErrorDef {
name: e.name().to_string(),
variants,
is_flat: e.is_flat(),
is_non_exhaustive: e.is_non_exhaustive(),
docstring: e.docstring().map(ToOwned::to_owned),
methods,
constructors,
});
} else {
enums.push(EnumDef {
name: e.name().to_string(),
variants,
is_flat: e.is_flat(),
is_non_exhaustive: e.is_non_exhaustive(),
docstring: e.docstring().map(ToOwned::to_owned),
methods,
constructors,
traits,
});
}
}
(errors, enums)
}
fn extract_traits(utm: &UniffiTraitMethods) -> SynthesisedTraits {
SynthesisedTraits {
display: utm.display_fmt.as_ref().map(|m| m.name().to_string()),
debug: utm.debug_fmt.as_ref().map(|m| m.name().to_string()),
eq: utm.eq_eq.as_ref().map(|m| m.name().to_string()),
hash: utm.hash_hash.as_ref().map(|m| m.name().to_string()),
ord: utm.ord_cmp.as_ref().map(|m| m.name().to_string()),
}
}
pub(super) fn namespace_from_source(source: &Path) -> Result<String> {
source
.file_stem()
.and_then(|s| s.to_str())
.map(ToOwned::to_owned)
.ok_or_else(|| anyhow::anyhow!("source path must have a valid UTF-8 file stem"))
}
pub(super) fn parse_wasm_source(
source: &Path,
crate_name: Option<&str>,
) -> Result<BindingsMetadata> {
let items = super::wasm_metadata::extract_from_wasm(source)
.with_context(|| format!("failed to extract metadata from: {}", source.display()))?;
let mut groups = uniffi_meta::create_metadata_groups(&items);
uniffi_meta::group_metadata(&mut groups, items).context("failed to group WASM metadata")?;
let group = if let Some(crate_name) = crate_name {
groups
.remove(crate_name)
.ok_or_else(|| anyhow::anyhow!("crate '{crate_name}' not found in WASM metadata"))?
} else {
groups
.into_values()
.next()
.ok_or_else(|| anyhow::anyhow!("no UniFFI components found in WASM metadata"))?
};
let local_crate = group.namespace.crate_name.clone();
let mut ci = ComponentInterface::new(&local_crate);
ci.add_metadata(group)
.context("failed to build ComponentInterface from WASM metadata")?;
component_interface_to_metadata(ci, &local_crate)
}