use crate::wasm_tools::ExIm;
use concepts::{
FnName, FunctionExtension, FunctionMetadata, IfcFqnName, PackageExtension, PackageIfcFns,
PkgFqn,
};
use const_format::formatcp;
use hashbrown::HashMap;
use id_arena::Arena;
use indexmap::IndexMap;
use semver::{BuildMetadata, Prerelease, Version};
use std::{fmt::Display, path::PathBuf};
use tracing::{error, warn};
use wit_component::WitPrinter;
use wit_parser::{
Function, FunctionKind, Handle, Interface, InterfaceId, PackageId, PackageName, Param, Resolve,
Span, Stability, Type, TypeDef, TypeDefKind, TypeOwner, UnresolvedPackageGroup, World,
WorldItem, WorldKey,
};
const OBELISK_TYPES_VERSION_MAJOR: u64 = 5;
const OBELISK_TYPES_VERSION_MINOR: u64 = 0;
const OBELISK_TYPES_VERSION_PATCH: u64 = 0;
const OBELISK_TYPES_VERSION: &str = formatcp!(
"{OBELISK_TYPES_VERSION_MAJOR}.{OBELISK_TYPES_VERSION_MINOR}.{OBELISK_TYPES_VERSION_PATCH}"
);
const OBELISK_TYPES_PACKAGE_NAME: &str = formatcp!("obelisk:types@{OBELISK_TYPES_VERSION}");
pub const WIT_OBELISK_LOG_PACKAGE: [&str; 3] = [
"obelisk_log@1.0.0",
"obelisk_log@1.0.0.wit",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/wit/obelisk_log@1.0.0/obelisk_log@1.0.0.wit"
)),
];
const WIT_OBELISK_TYPES_PACKAGE_CONTENT: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/wit/obelisk_types@5.0.0/obelisk_types@5.0.0.wit"
));
pub const WIT_OBELISK_TYPES_PACKAGE: [&str; 3] = [
"obelisk_types@5.0.0",
"obelisk_types@5.0.0.wit",
WIT_OBELISK_TYPES_PACKAGE_CONTENT,
];
pub const WIT_OBELISK_WORKFLOW_PACKAGE: [&str; 3] = [
"obelisk_workflow@6.0.0",
"obelisk_workflow@6.0.0.wit",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/wit/obelisk_workflow@6.0.0/obelisk_workflow@6.0.0.wit"
)),
];
pub const WIT_OBELISK_WEBHOOK_PACKAGE: [&str; 3] = [
"obelisk_webhook@6.0.0",
"obelisk_webhook@6.0.0.wit",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/wit/obelisk_webhook@6.0.0/obelisk_webhook@6.0.0.wit"
)),
];
pub(crate) fn wit(resolve: &Resolve, main_package: PackageId) -> Result<String, anyhow::Error> {
let ids = packages_except_main(resolve, main_package, false);
let mut printer = WitPrinter::new(OutputToString::default());
printer.print(resolve, main_package, &ids)?;
let wit = printer.output.to_string();
Ok(wit)
}
#[expect(
clippy::needless_pass_by_value,
reason = "original resolve is consumed; the enriched resolve is returned"
)]
pub(crate) fn rebuild_resolve(
exim: &ExIm,
resolve: Resolve,
main_package: PackageId,
) -> Result<(Resolve, PackageId), anyhow::Error> {
let ids = packages_except_main(&resolve, main_package, true);
let mut printer = WitPrinter::new(OutputToString::default());
printer.print(&resolve, main_package, &ids)?;
let wit = printer.output.to_string();
let (mut resolve, main_pkg_id) = {
let wit = replace_obelisk_types(&wit);
let group = UnresolvedPackageGroup::parse(PathBuf::new(), &wit)?;
let mut resolve = Resolve::new();
let main_pkg_id = resolve.push_group(group)?;
(resolve, main_pkg_id)
};
let world_id = resolve
.select_world(&[main_pkg_id], None)
.expect("default world must be found");
let added_interfaces = add_extended_interfaces(exim.get_exports_hierarchy_ext(), &mut resolve)?;
resolve
.worlds
.get_mut(world_id)
.expect("id belongs to this resolve")
.exports
.extend(added_interfaces.into_iter().map(|ifc_id| {
(
WorldKey::Interface(ifc_id),
WorldItem::Interface {
id: ifc_id,
stability: Stability::Unknown,
span: Span::default(),
},
)
}));
Ok((resolve, main_pkg_id))
}
fn add_extended_interfaces(
exports_hierarchy_ext: &[PackageIfcFns],
resolve: &mut Resolve,
) -> Result<Vec<InterfaceId>, semver::Error> {
let mut added_interfaces = Vec::new();
let obelisk_types_package_name = PackageName {
namespace: "obelisk".to_string(),
name: "types".to_string(),
version: Some(Version {
major: OBELISK_TYPES_VERSION_MAJOR,
minor: OBELISK_TYPES_VERSION_MINOR,
patch: OBELISK_TYPES_VERSION_PATCH,
pre: Prerelease::EMPTY,
build: BuildMetadata::EMPTY,
}),
};
let obelisk_types_pkg_id =
if let Some(id) = resolve.package_names.get(&obelisk_types_package_name) {
*id
} else {
let pkg = wit_parser::Package {
name: obelisk_types_package_name,
docs: wit_parser::Docs::default(),
interfaces: IndexMap::default(),
worlds: IndexMap::default(),
};
let package_name = pkg.name.clone();
let ext_pkg_id = resolve.packages.alloc(pkg);
resolve.package_names.insert(package_name, ext_pkg_id);
ext_pkg_id
};
let time_ifc_id = *resolve.packages[obelisk_types_pkg_id]
.interfaces
.get("time")
.expect("`time` interface was added");
let time_ifc = &resolve.interfaces[time_ifc_id];
let (execution_ifc_id, execution_ifc) = find_interface(
&IfcFqnName::from_parts("obelisk", "types", "execution", Some(OBELISK_TYPES_VERSION)),
resolve,
&resolve.interfaces,
)
.expect(formatcp!(
"{OBELISK_TYPES_PACKAGE_NAME} with interface `execution` must be found"
));
let (join_set_ifc_id, join_set_ifc) = find_interface(
&IfcFqnName::from_parts("obelisk", "types", "join-set", Some(OBELISK_TYPES_VERSION)),
resolve,
&resolve.interfaces,
)
.expect(formatcp!(
"{OBELISK_TYPES_PACKAGE_NAME} with interface `join-set` must be found"
));
let type_id_execution_id = {
let actual_type_id = *execution_ifc
.types
.get("execution-id")
.expect("`execution-id` must exist");
resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(Type::Id(actual_type_id)),
owner: TypeOwner::Interface(execution_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})
};
let (type_id_join_set_id, type_id_join_set_id_borrow_handle) = {
let actual_type_id = *join_set_ifc
.types
.get("join-set")
.expect("`join-set` must exist");
let type_id_join_set_id = resolve.types.alloc(TypeDef {
name: Some("join-set".to_string()),
kind: TypeDefKind::Type(Type::Id(actual_type_id)),
owner: TypeOwner::Interface(join_set_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
});
let type_id_join_set_id_borrow_handle = resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Handle(Handle::Borrow(type_id_join_set_id)),
owner: TypeOwner::Interface(join_set_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
});
(type_id_join_set_id, type_id_join_set_id_borrow_handle)
};
let type_id_await_next_extension_error = {
let actual_type_id = *execution_ifc
.types
.get("await-next-extension-error")
.expect("`await-next-extension-error` must exist");
resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(Type::Id(actual_type_id)),
owner: TypeOwner::Interface(execution_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})
};
let type_id_get_extension_error = {
let actual_type_id = *execution_ifc
.types
.get("get-extension-error")
.expect("`get-extension-error` must exist");
resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(Type::Id(actual_type_id)),
owner: TypeOwner::Interface(execution_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})
};
let type_id_stub_error = {
let actual_type_id = *execution_ifc
.types
.get("stub-error")
.expect("`stub-error` must exist");
resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(Type::Id(actual_type_id)),
owner: TypeOwner::Interface(execution_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})
};
let type_id_await_next_err_part = type_id_await_next_extension_error;
let type_id_schedule_at = {
let actual_type_id = *time_ifc
.types
.get("schedule-at")
.expect("`schedule-at` must exist");
resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(Type::Id(actual_type_id)),
owner: TypeOwner::Interface(execution_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})
};
for (pkg_fqn, ifc_to_fns) in get_ext_pkg_to_ifc_to_details_map(exports_hierarchy_ext) {
let (orig_pkg_fqn, pkg_ext) = pkg_fqn
.split_ext()
.expect("`get_pkg_to_ifc_to_details_map` filtered by ext packages");
let pkg_id = get_or_create_package(pkg_fqn, resolve)?;
let (orig_pkg_id, _) = resolve
.packages
.iter()
.find(|(_, found_pkg)| {
from_wit_package_name_to_pkg_fqn(&found_pkg.name) == orig_pkg_fqn
})
.unwrap_or_else(|| {
panic!("original package must be not found in resolve: {orig_pkg_fqn}")
});
for (ifc_fqn, fns) in ifc_to_fns {
let orig_pkg = resolve.packages.get(orig_pkg_id).expect("id is fresh");
let orig_ifc_id = *orig_pkg
.interfaces
.get(ifc_fqn.ifc_name())
.unwrap_or_else(|| {
panic!("interface must be found in original resolve: {ifc_fqn}")
});
let orig_ifc = resolve
.interfaces
.get(orig_ifc_id)
.expect("orig_ifc obtained from orig_resolve");
let mut types = copy_or_refer_original_types(orig_ifc_id, orig_ifc, &mut resolve.types);
match pkg_ext {
PackageExtension::ObeliskExt => {
types.insert("execution-id".to_string(), type_id_execution_id);
types.insert("join-set".to_string(), type_id_join_set_id);
types.insert(
"await-next-extension-error".to_string(),
type_id_await_next_extension_error,
);
types.insert(
"get-extension-error".to_string(),
type_id_get_extension_error,
);
}
PackageExtension::ObeliskSchedule => {
types.insert("execution-id".to_string(), type_id_execution_id);
types.insert("schedule-at".to_string(), type_id_schedule_at);
}
PackageExtension::ObeliskStub => {
types.insert("execution-id".to_string(), type_id_execution_id);
types.insert("stub-error".to_string(), type_id_stub_error);
}
}
let mut ifc = Interface {
name: Some(ifc_fqn.ifc_name().to_string()),
types,
functions: IndexMap::default(),
docs: wit_parser::Docs::default(),
stability: Stability::default(),
package: Some(pkg_id),
span: Span::default(),
clone_of: None,
};
for (fn_name, fn_meta) in fns {
let (prefix, fn_ext) = fn_meta.split_extension().expect("filtered by ext package");
let original_fn = orig_ifc.functions.get(prefix).unwrap_or_else(|| {
panic!("original function {prefix} must be found based on {fn_meta:?}")
});
let (params, result) = match fn_ext {
FunctionExtension::Submit => {
assert_eq!(pkg_ext, PackageExtension::ObeliskExt);
let mut params = vec![Param {
name: generate_param_name("join-set", &original_fn.params),
ty: Type::Id(type_id_join_set_id_borrow_handle),
span: Span::default(),
}];
params.extend_from_slice(&original_fn.params);
(params, Some(Type::Id(type_id_execution_id)))
}
FunctionExtension::AwaitNext => {
assert_eq!(pkg_ext, PackageExtension::ObeliskExt);
let params = vec![Param {
name: "join-set".to_string(),
ty: Type::Id(type_id_join_set_id_borrow_handle),
span: Span::default(),
}];
let result = {
let type_id_result = resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: original_fn.result,
err: Some(Type::Id(type_id_await_next_err_part)),
}),
owner: TypeOwner::None,
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
});
Some(Type::Id(type_id_result))
};
(params, result)
}
FunctionExtension::Schedule => {
assert_eq!(pkg_ext, PackageExtension::ObeliskSchedule);
let schedule_at_param_name =
generate_param_name("schedule-at", &original_fn.params);
let mut params = vec![Param {
name: schedule_at_param_name.clone(),
ty: Type::Id(type_id_schedule_at),
span: Span::default(),
}];
params.extend_from_slice(&original_fn.params);
let result = Some(Type::Id(type_id_execution_id));
(params, result)
}
FunctionExtension::Stub => {
assert_eq!(pkg_ext, PackageExtension::ObeliskStub);
let mut params = vec![Param {
name: "execution-id".to_string(),
ty: Type::Id(type_id_execution_id),
span: Span::default(),
}];
let Some(return_type) = &original_fn.result else {
unreachable!(
"return types of exported functions are validated in ExImLite"
)
};
let return_type_id = Type::Id(resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(*return_type),
owner: TypeOwner::None,
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
}));
params.push(Param {
name: "execution-result".to_string(),
ty: return_type_id,
span: Span::default(),
});
let result = {
let type_id_result = resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: None,
err: Some(Type::Id(type_id_stub_error)),
}),
owner: TypeOwner::None,
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
});
Some(Type::Id(type_id_result))
};
(params, result)
}
FunctionExtension::Get => {
assert_eq!(pkg_ext, PackageExtension::ObeliskExt);
let params = vec![Param {
name: "execution-id".to_string(),
ty: Type::Id(type_id_execution_id),
span: Span::default(),
}];
let result = Some(Type::Id(resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: original_fn.result,
err: Some(Type::Id(type_id_get_extension_error)),
}),
owner: TypeOwner::None,
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})));
(params, result)
}
};
let wit_fun = Function {
name: fn_name.to_string(),
kind: FunctionKind::Freestanding,
params,
result,
docs: wit_parser::Docs::default(),
stability: Stability::default(),
span: Span::default(),
};
ifc.functions.insert(fn_name.to_string(), wit_fun);
}
let ifc_id = resolve.interfaces.alloc(ifc);
resolve
.packages
.get_mut(pkg_id)
.expect("found or inserted already")
.interfaces
.insert(ifc_fqn.ifc_name().to_string(), ifc_id);
added_interfaces.push(ifc_id);
}
}
Ok(added_interfaces)
}
pub(crate) fn packages_except_main(
resolve: &Resolve,
main_package: PackageId,
sorted: bool,
) -> Vec<PackageId> {
let mut packages = resolve
.packages
.iter()
.map(|(id, _)| id)
.filter(|id| *id != main_package)
.collect::<Vec<_>>();
if sorted {
packages.sort();
}
packages
}
fn replace_obelisk_types(wit: &str) -> String {
let types_nesting = {
let mut types_nesting = WIT_OBELISK_TYPES_PACKAGE_CONTENT.replacen(';', "{", 1);
types_nesting.push('}');
types_nesting
};
const TYPES_NESTED_PACKAGE_FIRST_LINE: &str =
formatcp!("package {OBELISK_TYPES_PACKAGE_NAME} {{");
let wit = remove_nested_package(wit, TYPES_NESTED_PACKAGE_FIRST_LINE);
let wit = format!("{wit}\n{types_nesting}");
wit
}
fn generate_param_name(param_name: &str, params: &[Param]) -> String {
let orig_param_names: hashbrown::HashSet<&str> = params
.iter()
.map(|Param { name, .. }| name.as_str())
.collect();
if orig_param_names.contains(param_name) {
for my_char in 'a'..='z' {
let name = format!("{param_name}-{my_char}");
if !orig_param_names.contains(name.as_str()) {
return name;
}
}
warn!("Parameter name `{param_name}` collides with other params {orig_param_names:?}");
}
param_name.to_string()
}
fn copy_or_refer_original_types(
orig_ifc_id: InterfaceId,
orig_ifc: &Interface,
resolve_types: &mut Arena<TypeDef>,
) -> IndexMap<String, id_arena::Id<TypeDef>> {
let mut target_types = IndexMap::new();
for (name, orig_type_id) in &orig_ifc.types {
let type_def = resolve_types
.get(*orig_type_id)
.expect("type def must be found in resolve");
let allocated_type_id = match type_def.kind {
TypeDefKind::Type(_) => resolve_types.alloc(type_def.clone()),
_ => {
resolve_types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Type(Type::Id(*orig_type_id)),
owner: TypeOwner::Interface(orig_ifc_id),
docs: wit_parser::Docs::default(),
stability: wit_parser::Stability::default(),
span: Span::default(),
})
}
};
target_types.insert(name.clone(), allocated_type_id);
}
target_types
}
fn get_or_create_package(
pkg_fqn: PkgFqn,
resolve: &mut Resolve,
) -> Result<PackageId, semver::Error> {
if let Some((pkg_id, _)) = resolve
.packages
.iter()
.find(|(_, found_pkg)| from_wit_package_name_to_pkg_fqn(&found_pkg.name) == pkg_fqn)
{
Ok(pkg_id)
} else {
let pkg = wit_parser::Package {
name: from_pkg_fqn_to_wit_package_name(pkg_fqn)?,
docs: wit_parser::Docs::default(),
interfaces: IndexMap::default(),
worlds: IndexMap::default(),
};
let package_name = pkg.name.clone();
let pkg_id = resolve.packages.alloc(pkg);
resolve.package_names.insert(package_name, pkg_id);
Ok(pkg_id)
}
}
fn get_ext_pkg_to_ifc_to_details_map(
exports: &[PackageIfcFns],
) -> IndexMap<PkgFqn, IndexMap<IfcFqnName, IndexMap<FnName, FunctionMetadata>>> {
let mut pkg_to_ifc_to_details_map: IndexMap<PkgFqn, IndexMap<IfcFqnName, IndexMap<FnName, _>>> =
IndexMap::new();
for pkg_ifc_fns in exports {
if pkg_ifc_fns.ifc_fqn.pkg_fqn_name().is_extension() {
let inner_map = pkg_to_ifc_to_details_map
.entry(pkg_ifc_fns.ifc_fqn.pkg_fqn_name())
.or_default();
inner_map.insert(pkg_ifc_fns.ifc_fqn.clone(), pkg_ifc_fns.fns.clone());
}
}
pkg_to_ifc_to_details_map
}
fn find_interface<'a>(
ifc_fqn: &IfcFqnName,
resolve: &'_ Resolve,
interfaces: &'a Arena<Interface>,
) -> Option<(InterfaceId, &'a Interface)> {
let pkg_id = *resolve
.package_names
.get(&try_from_ifc_fqn_name(ifc_fqn).ok()?)?;
let ifc_id = *resolve.packages[pkg_id]
.interfaces
.get(ifc_fqn.ifc_name())?;
interfaces.get(ifc_id).map(|ifc| (ifc_id, ifc))
}
fn remove_nested_package(wit_string: &str, nested_package_to_remove: &str) -> String {
let Some(nested_package_start) = wit_string.find(nested_package_to_remove) else {
return wit_string.to_string();
};
let Some(open_brace_index) = wit_string[nested_package_start..]
.find('{')
.map(|idx| nested_package_start + idx)
else {
panic!("nested namespace must contain '{{'");
};
let mut brace_count = 1;
let mut current_index = open_brace_index + 1;
while current_index < wit_string.len() && brace_count > 0 {
match wit_string.chars().nth(current_index) {
Some('{') => brace_count += 1,
Some('}') => brace_count -= 1,
_ => {}
}
current_index += 1;
}
if brace_count != 0 {
warn!("Cannot remove the nested package {nested_package_to_remove}");
return wit_string.to_string();
}
format!(
"{}\n{}",
wit_string[..nested_package_start].trim(),
wit_string[current_index..].trim()
)
}
fn try_from_ifc_fqn_name(ifc_fqn: &IfcFqnName) -> Result<PackageName, anyhow::Error> {
Ok(PackageName {
namespace: ifc_fqn.namespace().to_string(),
name: ifc_fqn.package_name().to_string(),
version: ifc_fqn
.version()
.map(semver::Version::parse)
.transpose()
.inspect_err(|err| {
error!(
"cannot parse the version `{:?}` - {err:?}",
ifc_fqn.version()
);
})?,
})
}
pub(crate) fn from_wit_package_name_to_pkg_fqn(package_name: &PackageName) -> PkgFqn {
PkgFqn {
namespace: package_name.namespace.clone(),
package_name: package_name.name.clone(),
version: package_name.version.as_ref().map(ToString::to_string),
}
}
fn from_pkg_fqn_to_wit_package_name(pkg_fqn: PkgFqn) -> Result<PackageName, semver::Error> {
Ok(PackageName {
namespace: pkg_fqn.namespace,
name: pkg_fqn.package_name,
version: pkg_fqn
.version
.as_ref()
.map(|v| v.parse())
.transpose()
.inspect_err(|err| error!("Cannot convert version {:?} - {err:?}", pkg_fqn.version))?,
})
}
pub fn build_wit_deps_map(
all_exports: &[PackageIfcFns],
) -> Result<HashMap<PkgFqn, String >, anyhow::Error> {
let mut result = HashMap::new();
let mut primary_pkgs: IndexMap<PkgFqn, Vec<&PackageIfcFns>> = IndexMap::new();
for pkg_ifc_fns in all_exports.iter().filter(|e| !e.extension) {
primary_pkgs
.entry(pkg_ifc_fns.ifc_fqn.pkg_fqn_name())
.or_default()
.push(pkg_ifc_fns);
}
let has_any_extension = all_exports.iter().any(|e| e.extension);
for (pkg_fqn, ifc_fns_list) in &primary_pkgs {
let (primary_resolve, primary_pkg_id) = build_primary_resolve(pkg_fqn, ifc_fns_list, None)?;
let primary_wit = {
let mut printer = WitPrinter::new(OutputToString::default());
printer.print(&primary_resolve, primary_pkg_id, &[])?;
printer.output.to_string()
};
result.insert(pkg_fqn.clone(), primary_wit.clone());
let ext_for_pkg: Vec<PackageIfcFns> = all_exports
.iter()
.filter(|e| {
e.extension
&& e.ifc_fqn
.pkg_fqn_name()
.split_ext()
.map(|(orig, _)| orig == *pkg_fqn)
.unwrap_or(false)
})
.cloned()
.collect();
if !ext_for_pkg.is_empty() {
let wit_with_types = replace_obelisk_types(&primary_wit);
let group = UnresolvedPackageGroup::parse(PathBuf::new(), &wit_with_types)?;
let mut resolve_with_types = Resolve::new();
resolve_with_types.push_group(group)?;
add_extended_interfaces(&ext_for_pkg, &mut resolve_with_types)?;
for (ext_pkg_id, ext_pkg) in &resolve_with_types.packages {
let ext_pkg_fqn = from_wit_package_name_to_pkg_fqn(&ext_pkg.name);
if ext_pkg_fqn.is_extension() {
let mut printer = WitPrinter::new(OutputToString::default());
printer.print(&resolve_with_types, ext_pkg_id, &[])?;
result.insert(ext_pkg_fqn, printer.output.to_string());
}
}
}
}
if has_any_extension {
let types_pkg_fqn = PkgFqn {
namespace: "obelisk".to_string(),
package_name: "types".to_string(),
version: Some(OBELISK_TYPES_VERSION.to_string()),
};
result.insert(types_pkg_fqn, WIT_OBELISK_TYPES_PACKAGE_CONTENT.to_string());
}
Ok(result)
}
pub(crate) fn build_primary_resolve(
pkg_fqn: &PkgFqn,
ifc_fns_list: &[&PackageIfcFns],
world_name: Option<&str>,
) -> Result<(Resolve, PackageId), anyhow::Error> {
use crate::wit_builder::allocate_type;
let mut resolve = Resolve::new();
let pkg = wit_parser::Package {
name: from_pkg_fqn_to_wit_package_name(pkg_fqn.clone())?,
docs: wit_parser::Docs::default(),
interfaces: IndexMap::default(),
worlds: IndexMap::default(),
};
let pkg_id = resolve.packages.alloc(pkg);
resolve
.package_names
.insert(resolve.packages[pkg_id].name.clone(), pkg_id);
let mut ifc_name_to_id: HashMap<String, InterfaceId> = HashMap::new();
for &pkg_ifc_fns in ifc_fns_list {
let ifc_name = pkg_ifc_fns.ifc_fqn.ifc_name().to_string();
let ifc_id = if let Some(&existing_id) = ifc_name_to_id.get(&ifc_name) {
existing_id
} else {
let iface = Interface {
name: Some(ifc_name.clone()),
types: IndexMap::new(),
functions: IndexMap::new(),
docs: wit_parser::Docs::default(),
stability: Stability::default(),
package: Some(pkg_id),
span: Span::default(),
clone_of: None,
};
let new_id = resolve.interfaces.alloc(iface);
resolve
.packages
.get_mut(pkg_id)
.unwrap()
.interfaces
.insert(ifc_name.clone(), new_id);
ifc_name_to_id.insert(ifc_name, new_id);
new_id
};
let mut dedup = HashMap::new();
for (fn_name, fn_metadata) in &pkg_ifc_fns.fns {
let wit_params: Vec<Param> = fn_metadata
.parameter_types
.iter()
.map(|p| {
let ty = allocate_type(&mut resolve, ifc_id, &p.type_wrapper, &mut dedup);
Param {
name: p.name.as_ref().to_string(),
ty,
span: Span::default(),
}
})
.collect();
let return_tw = fn_metadata.return_type.type_wrapper();
let result_type = allocate_type(&mut resolve, ifc_id, &return_tw, &mut dedup);
let wit_fn = Function {
name: fn_name.to_string(),
kind: FunctionKind::Freestanding,
params: wit_params,
result: Some(result_type),
docs: wit_parser::Docs::default(),
stability: Stability::default(),
span: Span::default(),
};
resolve
.interfaces
.get_mut(ifc_id)
.unwrap()
.functions
.insert(fn_name.to_string(), wit_fn);
}
let mut types = IndexMap::new();
for (type_id, type_def) in &resolve.types {
if type_def.owner == TypeOwner::Interface(ifc_id)
&& let Some(name) = &type_def.name
{
types.insert(name.clone(), type_id);
}
}
resolve.interfaces.get_mut(ifc_id).unwrap().types = types;
}
let main_pkg_id = if let Some(world_name) = world_name {
let world_exports: IndexMap<WorldKey, WorldItem> = resolve.packages[pkg_id]
.interfaces
.values()
.map(|&ifc_id| {
(
WorldKey::Interface(ifc_id),
WorldItem::Interface {
id: ifc_id,
stability: Stability::Unknown,
span: Span::default(),
},
)
})
.collect();
let root_pkg = wit_parser::Package {
name: PackageName {
namespace: "root".to_string(),
name: "component".to_string(),
version: None,
},
docs: wit_parser::Docs::default(),
interfaces: IndexMap::default(),
worlds: IndexMap::default(),
};
let root_pkg_id = resolve.packages.alloc(root_pkg);
resolve
.package_names
.insert(resolve.packages[root_pkg_id].name.clone(), root_pkg_id);
let world = World {
name: world_name.to_string(),
docs: wit_parser::Docs::default(),
imports: IndexMap::default(),
exports: world_exports,
package: Some(root_pkg_id),
span: Span::default(),
includes: vec![],
stability: Stability::Unknown,
};
let world_id = resolve.worlds.alloc(world);
resolve
.packages
.get_mut(root_pkg_id)
.unwrap()
.worlds
.insert(world_name.to_string(), world_id);
root_pkg_id
} else {
pkg_id
};
Ok((resolve, main_pkg_id))
}
#[derive(Default)]
pub struct OutputToString {
indent: usize,
output: String,
needs_indent: bool,
}
impl wit_component::Output for OutputToString {
fn push_str(&mut self, src: &str) {
self.output.push_str(src);
}
fn indent_if_needed(&mut self) -> bool {
if self.needs_indent {
for _ in 0..self.indent {
self.output.push_str(" ");
}
self.needs_indent = false;
true
} else {
false
}
}
fn indent_start(&mut self) {
assert!(
!self.needs_indent,
"`indent_start` is never called after newline"
);
self.output.push_str(" {");
self.indent += 1;
self.newline();
}
fn indent_end(&mut self) {
self.indent = self.indent.saturating_sub(1);
self.indent_if_needed();
self.output.push('}');
self.newline();
}
fn newline(&mut self) {
self.output.push('\n');
self.needs_indent = true;
}
}
impl Display for OutputToString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.output.fmt(f)
}
}
#[cfg(test)]
mod tests {
use crate::{wasm_tools::WasmComponent, wasm_tools::strip_wasm_hash, wit::OutputToString};
use concepts::ComponentType;
use rstest::rstest;
use std::path::PathBuf;
use wit_component::WitPrinter;
use wit_parser::{Resolve, UnresolvedPackageGroup};
#[rstest]
#[case(
test_programs_fibo_workflow_builder::TEST_PROGRAMS_FIBO_WORKFLOW,
ComponentType::Workflow
)]
#[case(
test_programs_fibo_activity_builder::TEST_PROGRAMS_FIBO_ACTIVITY,
ComponentType::Activity
)]
#[case(
test_programs_fibo_webhook_builder::TEST_PROGRAMS_FIBO_WEBHOOK,
ComponentType::WebhookEndpoint
)]
#[case(
test_programs_http_get_activity_builder::TEST_PROGRAMS_HTTP_GET_ACTIVITY,
ComponentType::Activity
)]
#[case(
test_programs_http_get_workflow_builder::TEST_PROGRAMS_HTTP_GET_WORKFLOW,
ComponentType::Workflow
)]
#[case(
test_programs_stub_activity_builder::TEST_PROGRAMS_STUB_ACTIVITY,
ComponentType::ActivityStub
)]
#[case(
test_programs_stub_workflow_builder::TEST_PROGRAMS_STUB_WORKFLOW,
ComponentType::Workflow
)]
#[case(
test_programs_sleep_activity_builder::TEST_PROGRAMS_SLEEP_ACTIVITY,
ComponentType::Workflow
)]
#[case(
test_programs_sleep_workflow_builder::TEST_PROGRAMS_SLEEP_WORKFLOW,
ComponentType::Workflow
)]
fn wit_should_contain_extensions(
#[case] wasm_path: &'static str,
#[case] component_type: ComponentType,
) {
test_utils::set_up();
let component = WasmComponent::new(wasm_path, component_type).unwrap();
let wasm_path = PathBuf::from(wasm_path);
let wasm_file = strip_wasm_hash(&wasm_path.file_name().unwrap().to_string_lossy());
let wit = component.wit();
let group = UnresolvedPackageGroup::parse(PathBuf::new(), &wit).unwrap();
let mut resolve = Resolve::new();
let main_id = resolve.push_group(group).unwrap();
let ids = resolve
.packages
.iter()
.map(|(id, _)| id)
.filter(|id| *id != main_id)
.collect::<Vec<_>>();
let mut printer = WitPrinter::new(OutputToString::default());
printer.print(&resolve, main_id, &ids).unwrap(); insta::with_settings!({sort_maps => true, snapshot_suffix => format!("{wasm_file}_wit")}, {insta::assert_snapshot!(wit)});
}
}