1use crate::wasm_tools::ExIm;
2use concepts::{
3 FnName, FunctionExtension, FunctionMetadata, IfcFqnName, PackageExtension, PackageIfcFns,
4 PkgFqn,
5};
6use const_format::formatcp;
7use hashbrown::HashMap;
8use id_arena::Arena;
9use indexmap::IndexMap;
10use semver::{BuildMetadata, Prerelease, Version};
11use std::{fmt::Display, path::PathBuf};
12use tracing::{error, warn};
13use wit_component::WitPrinter;
14use wit_parser::{
15 Function, FunctionKind, Handle, Interface, InterfaceId, PackageId, PackageName, Param, Resolve,
16 Span, Stability, Type, TypeDef, TypeDefKind, TypeOwner, UnresolvedPackageGroup, World,
17 WorldItem, WorldKey,
18};
19
20const OBELISK_TYPES_VERSION_MAJOR: u64 = 5;
21const OBELISK_TYPES_VERSION_MINOR: u64 = 0;
22const OBELISK_TYPES_VERSION_PATCH: u64 = 0;
23const OBELISK_TYPES_VERSION: &str = formatcp!(
24 "{OBELISK_TYPES_VERSION_MAJOR}.{OBELISK_TYPES_VERSION_MINOR}.{OBELISK_TYPES_VERSION_PATCH}"
25);
26const OBELISK_TYPES_PACKAGE_NAME: &str = formatcp!("obelisk:types@{OBELISK_TYPES_VERSION}");
27
28pub const WIT_OBELISK_LOG_PACKAGE: [&str; 3] = [
29 "obelisk_log@1.0.0",
30 "obelisk_log@1.0.0.wit",
31 include_str!(concat!(
32 env!("CARGO_MANIFEST_DIR"),
33 "/wit/obelisk_log@1.0.0/obelisk_log@1.0.0.wit"
34 )),
35];
36const WIT_OBELISK_TYPES_PACKAGE_CONTENT: &str = include_str!(concat!(
37 env!("CARGO_MANIFEST_DIR"),
38 "/wit/obelisk_types@5.0.0/obelisk_types@5.0.0.wit"
39));
40pub const WIT_OBELISK_TYPES_PACKAGE: [&str; 3] = [
41 "obelisk_types@5.0.0",
42 "obelisk_types@5.0.0.wit",
43 WIT_OBELISK_TYPES_PACKAGE_CONTENT,
44];
45pub const WIT_OBELISK_WORKFLOW_PACKAGE: [&str; 3] = [
46 "obelisk_workflow@6.0.0",
47 "obelisk_workflow@6.0.0.wit",
48 include_str!(concat!(
49 env!("CARGO_MANIFEST_DIR"),
50 "/wit/obelisk_workflow@6.0.0/obelisk_workflow@6.0.0.wit"
51 )),
52];
53
54pub const WIT_OBELISK_WEBHOOK_PACKAGE: [&str; 3] = [
55 "obelisk_webhook@6.0.0",
56 "obelisk_webhook@6.0.0.wit",
57 include_str!(concat!(
58 env!("CARGO_MANIFEST_DIR"),
59 "/wit/obelisk_webhook@6.0.0/obelisk_webhook@6.0.0.wit"
60 )),
61];
62
63pub(crate) fn wit(resolve: &Resolve, main_package: PackageId) -> Result<String, anyhow::Error> {
64 let ids = packages_except_main(resolve, main_package, false);
66 let mut printer = WitPrinter::new(OutputToString::default());
67 printer.print(resolve, main_package, &ids)?;
68 let wit = printer.output.to_string();
69 Ok(wit)
70}
71
72#[expect(
73 clippy::needless_pass_by_value,
74 reason = "original resolve is consumed; the enriched resolve is returned"
75)]
76pub(crate) fn rebuild_resolve(
77 exim: &ExIm,
78 resolve: Resolve,
79 main_package: PackageId,
80) -> Result<(Resolve, PackageId), anyhow::Error> {
81 let ids = packages_except_main(&resolve, main_package, true);
82 let mut printer = WitPrinter::new(OutputToString::default());
83 printer.print(&resolve, main_package, &ids)?;
84 let wit = printer.output.to_string();
85
86 let (mut resolve, main_pkg_id) = {
87 let wit = replace_obelisk_types(&wit);
88 let group = UnresolvedPackageGroup::parse(PathBuf::new(), &wit)?;
89 let mut resolve = Resolve::new();
90 let main_pkg_id = resolve.push_group(group)?;
91 (resolve, main_pkg_id)
92 };
93 let world_id = resolve
94 .select_world(&[main_pkg_id], None)
95 .expect("default world must be found");
96 let added_interfaces = add_extended_interfaces(exim.get_exports_hierarchy_ext(), &mut resolve)?;
97 resolve
98 .worlds
99 .get_mut(world_id)
100 .expect("id belongs to this resolve")
101 .exports
102 .extend(added_interfaces.into_iter().map(|ifc_id| {
103 (
104 WorldKey::Interface(ifc_id),
105 WorldItem::Interface {
106 id: ifc_id,
107 stability: Stability::Unknown,
108 span: Span::default(),
109 },
110 )
111 }));
112
113 Ok((resolve, main_pkg_id))
114}
115
116fn add_extended_interfaces(
117 exports_hierarchy_ext: &[PackageIfcFns],
118 resolve: &mut Resolve,
119) -> Result<Vec<InterfaceId>, semver::Error> {
120 let mut added_interfaces = Vec::new();
121 let obelisk_types_package_name = PackageName {
124 namespace: "obelisk".to_string(),
125 name: "types".to_string(),
126 version: Some(Version {
127 major: OBELISK_TYPES_VERSION_MAJOR,
128 minor: OBELISK_TYPES_VERSION_MINOR,
129 patch: OBELISK_TYPES_VERSION_PATCH,
130 pre: Prerelease::EMPTY,
131 build: BuildMetadata::EMPTY,
132 }),
133 };
134 let obelisk_types_pkg_id =
135 if let Some(id) = resolve.package_names.get(&obelisk_types_package_name) {
136 *id
137 } else {
138 let pkg = wit_parser::Package {
139 name: obelisk_types_package_name,
140 docs: wit_parser::Docs::default(),
141 interfaces: IndexMap::default(),
142 worlds: IndexMap::default(),
143 };
144 let package_name = pkg.name.clone();
145 let ext_pkg_id = resolve.packages.alloc(pkg);
146 resolve.package_names.insert(package_name, ext_pkg_id);
147 ext_pkg_id
148 };
149 let time_ifc_id = *resolve.packages[obelisk_types_pkg_id]
151 .interfaces
152 .get("time")
153 .expect("`time` interface was added");
154 let time_ifc = &resolve.interfaces[time_ifc_id];
155
156 let (execution_ifc_id, execution_ifc) = find_interface(
157 &IfcFqnName::from_parts("obelisk", "types", "execution", Some(OBELISK_TYPES_VERSION)),
158 resolve,
159 &resolve.interfaces,
160 )
161 .expect(formatcp!(
162 "{OBELISK_TYPES_PACKAGE_NAME} with interface `execution` must be found"
163 ));
164
165 let (join_set_ifc_id, join_set_ifc) = find_interface(
166 &IfcFqnName::from_parts("obelisk", "types", "join-set", Some(OBELISK_TYPES_VERSION)),
167 resolve,
168 &resolve.interfaces,
169 )
170 .expect(formatcp!(
171 "{OBELISK_TYPES_PACKAGE_NAME} with interface `join-set` must be found"
172 ));
173
174 let type_id_execution_id = {
176 let actual_type_id = *execution_ifc
177 .types
178 .get("execution-id")
179 .expect("`execution-id` must exist");
180 resolve.types.alloc(TypeDef {
182 name: None,
183 kind: TypeDefKind::Type(Type::Id(actual_type_id)),
184 owner: TypeOwner::Interface(execution_ifc_id),
185 docs: wit_parser::Docs::default(),
186 stability: wit_parser::Stability::default(),
187 span: Span::default(),
188 })
189 };
190 let (type_id_join_set_id, type_id_join_set_id_borrow_handle) = {
192 let actual_type_id = *join_set_ifc
193 .types
194 .get("join-set")
195 .expect("`join-set` must exist");
196 let type_id_join_set_id = resolve.types.alloc(TypeDef {
198 name: Some("join-set".to_string()),
199 kind: TypeDefKind::Type(Type::Id(actual_type_id)),
200 owner: TypeOwner::Interface(join_set_ifc_id),
201 docs: wit_parser::Docs::default(),
202 stability: wit_parser::Stability::default(),
203 span: Span::default(),
204 });
205 let type_id_join_set_id_borrow_handle = resolve.types.alloc(TypeDef {
207 name: None,
208 kind: TypeDefKind::Handle(Handle::Borrow(type_id_join_set_id)),
209 owner: TypeOwner::Interface(join_set_ifc_id),
210 docs: wit_parser::Docs::default(),
211 stability: wit_parser::Stability::default(),
212 span: Span::default(),
213 });
214 (type_id_join_set_id, type_id_join_set_id_borrow_handle)
215 };
216 let type_id_await_next_extension_error = {
218 let actual_type_id = *execution_ifc
219 .types
220 .get("await-next-extension-error")
221 .expect("`await-next-extension-error` must exist");
222 resolve.types.alloc(TypeDef {
224 name: None,
225 kind: TypeDefKind::Type(Type::Id(actual_type_id)),
226 owner: TypeOwner::Interface(execution_ifc_id),
227 docs: wit_parser::Docs::default(),
228 stability: wit_parser::Stability::default(),
229 span: Span::default(),
230 })
231 };
232 let type_id_get_extension_error = {
234 let actual_type_id = *execution_ifc
235 .types
236 .get("get-extension-error")
237 .expect("`get-extension-error` must exist");
238 resolve.types.alloc(TypeDef {
240 name: None,
241 kind: TypeDefKind::Type(Type::Id(actual_type_id)),
242 owner: TypeOwner::Interface(execution_ifc_id),
243 docs: wit_parser::Docs::default(),
244 stability: wit_parser::Stability::default(),
245 span: Span::default(),
246 })
247 };
248 let type_id_stub_error = {
250 let actual_type_id = *execution_ifc
251 .types
252 .get("stub-error")
253 .expect("`stub-error` must exist");
254 resolve.types.alloc(TypeDef {
256 name: None,
257 kind: TypeDefKind::Type(Type::Id(actual_type_id)),
258 owner: TypeOwner::Interface(execution_ifc_id),
259 docs: wit_parser::Docs::default(),
260 stability: wit_parser::Stability::default(),
261 span: Span::default(),
262 })
263 };
264 let type_id_await_next_err_part = type_id_await_next_extension_error;
265 let type_id_schedule_at = {
267 let actual_type_id = *time_ifc
268 .types
269 .get("schedule-at")
270 .expect("`schedule-at` must exist");
271 resolve.types.alloc(TypeDef {
273 name: None,
274 kind: TypeDefKind::Type(Type::Id(actual_type_id)),
275 owner: TypeOwner::Interface(execution_ifc_id),
276 docs: wit_parser::Docs::default(),
277 stability: wit_parser::Stability::default(),
278 span: Span::default(),
279 })
280 };
281
282 for (pkg_fqn, ifc_to_fns) in get_ext_pkg_to_ifc_to_details_map(exports_hierarchy_ext) {
283 let (orig_pkg_fqn, pkg_ext) = pkg_fqn
284 .split_ext()
285 .expect("`get_pkg_to_ifc_to_details_map` filtered by ext packages");
286 let pkg_id = get_or_create_package(pkg_fqn, resolve)?;
287 let (orig_pkg_id, _) = resolve
288 .packages
289 .iter()
290 .find(|(_, found_pkg)| {
291 from_wit_package_name_to_pkg_fqn(&found_pkg.name) == orig_pkg_fqn
292 })
293 .unwrap_or_else(|| {
294 panic!("original package must be not found in resolve: {orig_pkg_fqn}")
295 });
296
297 for (ifc_fqn, fns) in ifc_to_fns {
298 let orig_pkg = resolve.packages.get(orig_pkg_id).expect("id is fresh");
299
300 let orig_ifc_id = *orig_pkg
301 .interfaces
302 .get(ifc_fqn.ifc_name())
303 .unwrap_or_else(|| {
304 panic!("interface must be found in original resolve: {ifc_fqn}")
305 });
306 let orig_ifc = resolve
307 .interfaces
308 .get(orig_ifc_id)
309 .expect("orig_ifc obtained from orig_resolve");
310
311 let mut types = copy_or_refer_original_types(orig_ifc_id, orig_ifc, &mut resolve.types);
312 match pkg_ext {
313 PackageExtension::ObeliskExt => {
314 types.insert("execution-id".to_string(), type_id_execution_id);
315 types.insert("join-set".to_string(), type_id_join_set_id);
316 types.insert(
317 "await-next-extension-error".to_string(),
318 type_id_await_next_extension_error,
319 );
320 types.insert(
321 "get-extension-error".to_string(),
322 type_id_get_extension_error,
323 );
324 }
325 PackageExtension::ObeliskSchedule => {
326 types.insert("execution-id".to_string(), type_id_execution_id);
327 types.insert("schedule-at".to_string(), type_id_schedule_at);
328 }
329 PackageExtension::ObeliskStub => {
330 types.insert("execution-id".to_string(), type_id_execution_id);
331 types.insert("stub-error".to_string(), type_id_stub_error);
332 }
333 }
334
335 let mut ifc = Interface {
336 name: Some(ifc_fqn.ifc_name().to_string()),
337 types,
338 functions: IndexMap::default(),
339 docs: wit_parser::Docs::default(),
340 stability: Stability::default(),
341 package: Some(pkg_id),
342 span: Span::default(),
343 clone_of: None,
344 };
345 for (fn_name, fn_meta) in fns {
346 let (prefix, fn_ext) = fn_meta.split_extension().expect("filtered by ext package");
347 let original_fn = orig_ifc.functions.get(prefix).unwrap_or_else(|| {
348 panic!("original function {prefix} must be found based on {fn_meta:?}")
349 });
350 let (params, result) = match fn_ext {
351 FunctionExtension::Submit => {
352 assert_eq!(pkg_ext, PackageExtension::ObeliskExt);
354 let mut params = vec![Param {
355 name: generate_param_name("join-set", &original_fn.params),
356 ty: Type::Id(type_id_join_set_id_borrow_handle),
357 span: Span::default(),
358 }];
359 params.extend_from_slice(&original_fn.params);
360
361 (params, Some(Type::Id(type_id_execution_id)))
362 }
363 FunctionExtension::AwaitNext => {
364 assert_eq!(pkg_ext, PackageExtension::ObeliskExt);
369 let params = vec![Param {
370 name: "join-set".to_string(),
371 ty: Type::Id(type_id_join_set_id_borrow_handle),
372 span: Span::default(),
373 }];
374 let result = {
375 let type_id_result = resolve.types.alloc(TypeDef {
376 name: None,
377 kind: TypeDefKind::Result(wit_parser::Result_ {
378 ok: original_fn.result,
379 err: Some(Type::Id(type_id_await_next_err_part)),
380 }),
381 owner: TypeOwner::None,
382 docs: wit_parser::Docs::default(),
383 stability: wit_parser::Stability::default(),
384 span: Span::default(),
385 });
386 Some(Type::Id(type_id_result))
387 };
388 (params, result)
389 }
390 FunctionExtension::Schedule => {
391 assert_eq!(pkg_ext, PackageExtension::ObeliskSchedule);
393 let schedule_at_param_name =
394 generate_param_name("schedule-at", &original_fn.params);
395 let mut params = vec![Param {
396 name: schedule_at_param_name.clone(),
397 ty: Type::Id(type_id_schedule_at),
398 span: Span::default(),
399 }];
400 params.extend_from_slice(&original_fn.params);
401 let result = Some(Type::Id(type_id_execution_id));
402 (params, result)
403 }
404 FunctionExtension::Stub => {
405 assert_eq!(pkg_ext, PackageExtension::ObeliskStub);
407 let mut params = vec![Param {
408 name: "execution-id".to_string(),
409 ty: Type::Id(type_id_execution_id),
410 span: Span::default(),
411 }];
412 let Some(return_type) = &original_fn.result else {
413 unreachable!(
414 "return types of exported functions are validated in ExImLite"
415 )
416 };
417 let return_type_id = Type::Id(resolve.types.alloc(TypeDef {
418 name: None,
419 kind: TypeDefKind::Type(*return_type),
420 owner: TypeOwner::None,
421 docs: wit_parser::Docs::default(),
422 stability: wit_parser::Stability::default(),
423 span: Span::default(),
424 }));
425 params.push(Param {
426 name: "execution-result".to_string(),
427 ty: return_type_id,
428 span: Span::default(),
429 });
430
431 let result = {
432 let type_id_result = resolve.types.alloc(TypeDef {
433 name: None,
434 kind: TypeDefKind::Result(wit_parser::Result_ {
435 ok: None,
436 err: Some(Type::Id(type_id_stub_error)),
437 }),
438 owner: TypeOwner::None,
439 docs: wit_parser::Docs::default(),
440 stability: wit_parser::Stability::default(),
441 span: Span::default(),
442 });
443 Some(Type::Id(type_id_result))
444 };
445 (params, result)
446 }
447 FunctionExtension::Get => {
448 assert_eq!(pkg_ext, PackageExtension::ObeliskExt);
450 let params = vec![Param {
451 name: "execution-id".to_string(),
452 ty: Type::Id(type_id_execution_id),
453 span: Span::default(),
454 }];
455 let result = Some(Type::Id(resolve.types.alloc(TypeDef {
456 name: None,
457 kind: TypeDefKind::Result(wit_parser::Result_ {
458 ok: original_fn.result,
459 err: Some(Type::Id(type_id_get_extension_error)),
460 }),
461 owner: TypeOwner::None,
462 docs: wit_parser::Docs::default(),
463 stability: wit_parser::Stability::default(),
464 span: Span::default(),
465 })));
466 (params, result)
467 }
468 };
469 let wit_fun = Function {
470 name: fn_name.to_string(),
471 kind: FunctionKind::Freestanding,
472 params,
473 result,
474 docs: wit_parser::Docs::default(),
475 stability: Stability::default(),
476 span: Span::default(),
477 };
478 ifc.functions.insert(fn_name.to_string(), wit_fun);
479 }
480 let ifc_id = resolve.interfaces.alloc(ifc);
482 resolve
483 .packages
484 .get_mut(pkg_id)
485 .expect("found or inserted already")
486 .interfaces
487 .insert(ifc_fqn.ifc_name().to_string(), ifc_id);
488
489 added_interfaces.push(ifc_id);
491 }
492 }
493 Ok(added_interfaces)
494}
495
496pub(crate) fn packages_except_main(
497 resolve: &Resolve,
498 main_package: PackageId,
499 sorted: bool,
500) -> Vec<PackageId> {
501 let mut packages = resolve
502 .packages
503 .iter()
504 .map(|(id, _)| id)
505 .filter(|id| *id != main_package)
507 .collect::<Vec<_>>();
508 if sorted {
509 packages.sort();
510 }
511 packages
512}
513
514fn replace_obelisk_types(wit: &str) -> String {
516 let types_nesting = {
518 let mut types_nesting = WIT_OBELISK_TYPES_PACKAGE_CONTENT.replacen(';', "{", 1);
519 types_nesting.push('}');
520 types_nesting
521 };
522 const TYPES_NESTED_PACKAGE_FIRST_LINE: &str =
523 formatcp!("package {OBELISK_TYPES_PACKAGE_NAME} {{");
524 let wit = remove_nested_package(wit, TYPES_NESTED_PACKAGE_FIRST_LINE);
525 let wit = format!("{wit}\n{types_nesting}");
526 wit
527}
528
529fn generate_param_name(param_name: &str, params: &[Param]) -> String {
530 let orig_param_names: hashbrown::HashSet<&str> = params
531 .iter()
532 .map(|Param { name, .. }| name.as_str())
533 .collect();
534 if orig_param_names.contains(param_name) {
535 for my_char in 'a'..='z' {
536 let name = format!("{param_name}-{my_char}");
537 if !orig_param_names.contains(name.as_str()) {
538 return name;
539 }
540 }
541 warn!("Parameter name `{param_name}` collides with other params {orig_param_names:?}");
542 }
543 param_name.to_string()
544}
545
546fn copy_or_refer_original_types(
547 orig_ifc_id: InterfaceId,
548 orig_ifc: &Interface,
549 resolve_types: &mut Arena<TypeDef>,
550) -> IndexMap<String, id_arena::Id<TypeDef>> {
551 let mut target_types = IndexMap::new();
552 for (name, orig_type_id) in &orig_ifc.types {
554 let type_def = resolve_types
555 .get(*orig_type_id)
556 .expect("type def must be found in resolve");
557
558 let allocated_type_id = match type_def.kind {
559 TypeDefKind::Type(_) => resolve_types.alloc(type_def.clone()),
560 _ => {
561 resolve_types.alloc(TypeDef {
563 name: None,
564 kind: TypeDefKind::Type(Type::Id(*orig_type_id)),
565 owner: TypeOwner::Interface(orig_ifc_id),
566 docs: wit_parser::Docs::default(),
567 stability: wit_parser::Stability::default(),
568 span: Span::default(),
569 })
570 }
571 };
572 target_types.insert(name.clone(), allocated_type_id);
573 }
574 target_types
575}
576
577fn get_or_create_package(
578 pkg_fqn: PkgFqn,
579 resolve: &mut Resolve,
580) -> Result<PackageId, semver::Error> {
581 if let Some((pkg_id, _)) = resolve
582 .packages
583 .iter()
584 .find(|(_, found_pkg)| from_wit_package_name_to_pkg_fqn(&found_pkg.name) == pkg_fqn)
585 {
586 Ok(pkg_id)
587 } else {
588 let pkg = wit_parser::Package {
589 name: from_pkg_fqn_to_wit_package_name(pkg_fqn)?,
590 docs: wit_parser::Docs::default(),
591 interfaces: IndexMap::default(),
592 worlds: IndexMap::default(),
593 };
594 let package_name = pkg.name.clone();
595 let pkg_id = resolve.packages.alloc(pkg);
596 resolve.package_names.insert(package_name, pkg_id);
597 Ok(pkg_id)
598 }
599}
600
601fn get_ext_pkg_to_ifc_to_details_map(
602 exports: &[PackageIfcFns],
603) -> IndexMap<PkgFqn, IndexMap<IfcFqnName, IndexMap<FnName, FunctionMetadata>>> {
604 let mut pkg_to_ifc_to_details_map: IndexMap<PkgFqn, IndexMap<IfcFqnName, IndexMap<FnName, _>>> =
607 IndexMap::new();
608 for pkg_ifc_fns in exports {
609 if pkg_ifc_fns.ifc_fqn.pkg_fqn_name().is_extension() {
610 let inner_map = pkg_to_ifc_to_details_map
611 .entry(pkg_ifc_fns.ifc_fqn.pkg_fqn_name())
612 .or_default();
613 inner_map.insert(pkg_ifc_fns.ifc_fqn.clone(), pkg_ifc_fns.fns.clone());
614 }
615 }
616 pkg_to_ifc_to_details_map
617}
618
619fn find_interface<'a>(
620 ifc_fqn: &IfcFqnName,
621 resolve: &'_ Resolve,
622 interfaces: &'a Arena<Interface>,
623) -> Option<(InterfaceId, &'a Interface)> {
624 let pkg_id = *resolve
625 .package_names
626 .get(&try_from_ifc_fqn_name(ifc_fqn).ok()?)?;
627 let ifc_id = *resolve.packages[pkg_id]
628 .interfaces
629 .get(ifc_fqn.ifc_name())?;
630 interfaces.get(ifc_id).map(|ifc| (ifc_id, ifc))
631}
632
633fn remove_nested_package(wit_string: &str, nested_package_to_remove: &str) -> String {
634 let Some(nested_package_start) = wit_string.find(nested_package_to_remove) else {
636 return wit_string.to_string();
637 };
638
639 let Some(open_brace_index) = wit_string[nested_package_start..]
641 .find('{')
642 .map(|idx| nested_package_start + idx)
643 else {
644 panic!("nested namespace must contain '{{'");
645 };
646
647 let mut brace_count = 1;
649 let mut current_index = open_brace_index + 1;
650
651 while current_index < wit_string.len() && brace_count > 0 {
652 match wit_string.chars().nth(current_index) {
653 Some('{') => brace_count += 1,
654 Some('}') => brace_count -= 1,
655 _ => {}
656 }
657 current_index += 1;
658 }
659
660 if brace_count != 0 {
662 warn!("Cannot remove the nested package {nested_package_to_remove}");
663 return wit_string.to_string();
664 }
665
666 format!(
668 "{}\n{}",
669 wit_string[..nested_package_start].trim(),
670 wit_string[current_index..].trim()
671 )
672}
673
674fn try_from_ifc_fqn_name(ifc_fqn: &IfcFqnName) -> Result<PackageName, anyhow::Error> {
675 Ok(PackageName {
676 namespace: ifc_fqn.namespace().to_string(),
677 name: ifc_fqn.package_name().to_string(),
678 version: ifc_fqn
679 .version()
680 .map(semver::Version::parse)
681 .transpose()
682 .inspect_err(|err| {
683 error!(
684 "cannot parse the version `{:?}` - {err:?}",
685 ifc_fqn.version()
686 );
687 })?,
688 })
689}
690
691pub(crate) fn from_wit_package_name_to_pkg_fqn(package_name: &PackageName) -> PkgFqn {
692 PkgFqn {
693 namespace: package_name.namespace.clone(),
694 package_name: package_name.name.clone(),
695 version: package_name.version.as_ref().map(ToString::to_string),
696 }
697}
698
699fn from_pkg_fqn_to_wit_package_name(pkg_fqn: PkgFqn) -> Result<PackageName, semver::Error> {
700 Ok(PackageName {
701 namespace: pkg_fqn.namespace,
702 name: pkg_fqn.package_name,
703 version: pkg_fqn
704 .version
705 .as_ref()
706 .map(|v| v.parse())
707 .transpose()
708 .inspect_err(|err| error!("Cannot convert version {:?} - {err:?}", pkg_fqn.version))?,
709 })
710}
711
712pub fn build_wit_deps_map(
715 all_exports: &[PackageIfcFns],
716) -> Result<HashMap<PkgFqn, String >, anyhow::Error> {
717 let mut result = HashMap::new();
718
719 let mut primary_pkgs: IndexMap<PkgFqn, Vec<&PackageIfcFns>> = IndexMap::new();
721 for pkg_ifc_fns in all_exports.iter().filter(|e| !e.extension) {
722 primary_pkgs
723 .entry(pkg_ifc_fns.ifc_fqn.pkg_fqn_name())
724 .or_default()
725 .push(pkg_ifc_fns);
726 }
727
728 let has_any_extension = all_exports.iter().any(|e| e.extension);
729
730 for (pkg_fqn, ifc_fns_list) in &primary_pkgs {
731 let (primary_resolve, primary_pkg_id) = build_primary_resolve(pkg_fqn, ifc_fns_list, None)?;
733 let primary_wit = {
734 let mut printer = WitPrinter::new(OutputToString::default());
735 printer.print(&primary_resolve, primary_pkg_id, &[])?;
736 printer.output.to_string()
737 };
738 result.insert(pkg_fqn.clone(), primary_wit.clone());
739
740 let ext_for_pkg: Vec<PackageIfcFns> = all_exports
742 .iter()
743 .filter(|e| {
744 e.extension
745 && e.ifc_fqn
746 .pkg_fqn_name()
747 .split_ext()
748 .map(|(orig, _)| orig == *pkg_fqn)
749 .unwrap_or(false)
750 })
751 .cloned()
752 .collect();
753
754 if !ext_for_pkg.is_empty() {
755 let wit_with_types = replace_obelisk_types(&primary_wit);
758 let group = UnresolvedPackageGroup::parse(PathBuf::new(), &wit_with_types)?;
759 let mut resolve_with_types = Resolve::new();
760 resolve_with_types.push_group(group)?;
761
762 add_extended_interfaces(&ext_for_pkg, &mut resolve_with_types)?;
763
764 for (ext_pkg_id, ext_pkg) in &resolve_with_types.packages {
766 let ext_pkg_fqn = from_wit_package_name_to_pkg_fqn(&ext_pkg.name);
767 if ext_pkg_fqn.is_extension() {
768 let mut printer = WitPrinter::new(OutputToString::default());
769 printer.print(&resolve_with_types, ext_pkg_id, &[])?;
770 result.insert(ext_pkg_fqn, printer.output.to_string());
771 }
772 }
773 }
774 }
775
776 if has_any_extension {
778 let types_pkg_fqn = PkgFqn {
779 namespace: "obelisk".to_string(),
780 package_name: "types".to_string(),
781 version: Some(OBELISK_TYPES_VERSION.to_string()),
782 };
783 result.insert(types_pkg_fqn, WIT_OBELISK_TYPES_PACKAGE_CONTENT.to_string());
784 }
785
786 Ok(result)
787}
788
789pub(crate) fn build_primary_resolve(
795 pkg_fqn: &PkgFqn,
796 ifc_fns_list: &[&PackageIfcFns],
797 world_name: Option<&str>,
798) -> Result<(Resolve, PackageId), anyhow::Error> {
799 use crate::wit_builder::allocate_type;
800
801 let mut resolve = Resolve::new();
802
803 let pkg = wit_parser::Package {
804 name: from_pkg_fqn_to_wit_package_name(pkg_fqn.clone())?,
805 docs: wit_parser::Docs::default(),
806 interfaces: IndexMap::default(),
807 worlds: IndexMap::default(),
808 };
809 let pkg_id = resolve.packages.alloc(pkg);
810 resolve
811 .package_names
812 .insert(resolve.packages[pkg_id].name.clone(), pkg_id);
813
814 let mut ifc_name_to_id: HashMap<String, InterfaceId> = HashMap::new();
817
818 for &pkg_ifc_fns in ifc_fns_list {
819 let ifc_name = pkg_ifc_fns.ifc_fqn.ifc_name().to_string();
820 let ifc_id = if let Some(&existing_id) = ifc_name_to_id.get(&ifc_name) {
821 existing_id
822 } else {
823 let iface = Interface {
824 name: Some(ifc_name.clone()),
825 types: IndexMap::new(),
826 functions: IndexMap::new(),
827 docs: wit_parser::Docs::default(),
828 stability: Stability::default(),
829 package: Some(pkg_id),
830 span: Span::default(),
831 clone_of: None,
832 };
833 let new_id = resolve.interfaces.alloc(iface);
834 resolve
835 .packages
836 .get_mut(pkg_id)
837 .unwrap()
838 .interfaces
839 .insert(ifc_name.clone(), new_id);
840 ifc_name_to_id.insert(ifc_name, new_id);
841 new_id
842 };
843
844 let mut dedup = HashMap::new();
845
846 for (fn_name, fn_metadata) in &pkg_ifc_fns.fns {
847 let wit_params: Vec<Param> = fn_metadata
848 .parameter_types
849 .iter()
850 .map(|p| {
851 let ty = allocate_type(&mut resolve, ifc_id, &p.type_wrapper, &mut dedup);
852 Param {
853 name: p.name.as_ref().to_string(),
854 ty,
855 span: Span::default(),
856 }
857 })
858 .collect();
859
860 let return_tw = fn_metadata.return_type.type_wrapper();
861 let result_type = allocate_type(&mut resolve, ifc_id, &return_tw, &mut dedup);
862
863 let wit_fn = Function {
864 name: fn_name.to_string(),
865 kind: FunctionKind::Freestanding,
866 params: wit_params,
867 result: Some(result_type),
868 docs: wit_parser::Docs::default(),
869 stability: Stability::default(),
870 span: Span::default(),
871 };
872 resolve
873 .interfaces
874 .get_mut(ifc_id)
875 .unwrap()
876 .functions
877 .insert(fn_name.to_string(), wit_fn);
878 }
879
880 let mut types = IndexMap::new();
882 for (type_id, type_def) in &resolve.types {
883 if type_def.owner == TypeOwner::Interface(ifc_id)
884 && let Some(name) = &type_def.name
885 {
886 types.insert(name.clone(), type_id);
887 }
888 }
889 resolve.interfaces.get_mut(ifc_id).unwrap().types = types;
890 }
891
892 let main_pkg_id = if let Some(world_name) = world_name {
893 let world_exports: IndexMap<WorldKey, WorldItem> = resolve.packages[pkg_id]
894 .interfaces
895 .values()
896 .map(|&ifc_id| {
897 (
898 WorldKey::Interface(ifc_id),
899 WorldItem::Interface {
900 id: ifc_id,
901 stability: Stability::Unknown,
902 span: Span::default(),
903 },
904 )
905 })
906 .collect();
907 let root_pkg = wit_parser::Package {
910 name: PackageName {
911 namespace: "root".to_string(),
912 name: "component".to_string(),
913 version: None,
914 },
915 docs: wit_parser::Docs::default(),
916 interfaces: IndexMap::default(),
917 worlds: IndexMap::default(),
918 };
919 let root_pkg_id = resolve.packages.alloc(root_pkg);
920 resolve
921 .package_names
922 .insert(resolve.packages[root_pkg_id].name.clone(), root_pkg_id);
923 let world = World {
924 name: world_name.to_string(),
925 docs: wit_parser::Docs::default(),
926 imports: IndexMap::default(),
927 exports: world_exports,
928 package: Some(root_pkg_id),
929 span: Span::default(),
930 includes: vec![],
931 stability: Stability::Unknown,
932 };
933 let world_id = resolve.worlds.alloc(world);
934 resolve
935 .packages
936 .get_mut(root_pkg_id)
937 .unwrap()
938 .worlds
939 .insert(world_name.to_string(), world_id);
940 root_pkg_id
941 } else {
942 pkg_id
943 };
944
945 Ok((resolve, main_pkg_id))
946}
947
948#[derive(Default)]
950pub struct OutputToString {
951 indent: usize,
952 output: String,
953 needs_indent: bool,
955}
956
957impl wit_component::Output for OutputToString {
958 fn push_str(&mut self, src: &str) {
959 self.output.push_str(src);
960 }
961
962 fn indent_if_needed(&mut self) -> bool {
963 if self.needs_indent {
964 for _ in 0..self.indent {
965 self.output.push_str(" ");
967 }
968 self.needs_indent = false;
969 true
970 } else {
971 false
972 }
973 }
974
975 fn indent_start(&mut self) {
976 assert!(
977 !self.needs_indent,
978 "`indent_start` is never called after newline"
979 );
980 self.output.push_str(" {");
981 self.indent += 1;
982 self.newline();
983 }
984
985 fn indent_end(&mut self) {
986 self.indent = self.indent.saturating_sub(1);
991 self.indent_if_needed();
992 self.output.push('}');
993 self.newline();
994 }
995
996 fn newline(&mut self) {
997 self.output.push('\n');
998 self.needs_indent = true;
999 }
1000}
1001impl Display for OutputToString {
1002 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1003 self.output.fmt(f)
1004 }
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009 use crate::{wasm_tools::WasmComponent, wasm_tools::strip_wasm_hash, wit::OutputToString};
1010 use concepts::ComponentType;
1011 use rstest::rstest;
1012 use std::path::PathBuf;
1013 use wit_component::WitPrinter;
1014 use wit_parser::{Resolve, UnresolvedPackageGroup};
1015
1016 #[rstest]
1017 #[case(
1018 test_programs_fibo_workflow_builder::TEST_PROGRAMS_FIBO_WORKFLOW,
1019 ComponentType::Workflow
1020 )]
1021 #[case(
1022 test_programs_fibo_activity_builder::TEST_PROGRAMS_FIBO_ACTIVITY,
1023 ComponentType::Activity
1024 )]
1025 #[case(
1026 test_programs_fibo_webhook_builder::TEST_PROGRAMS_FIBO_WEBHOOK,
1027 ComponentType::WebhookEndpoint
1028 )]
1029 #[case(
1030 test_programs_http_get_activity_builder::TEST_PROGRAMS_HTTP_GET_ACTIVITY,
1031 ComponentType::Activity
1032 )]
1033 #[case(
1034 test_programs_http_get_workflow_builder::TEST_PROGRAMS_HTTP_GET_WORKFLOW,
1035 ComponentType::Workflow
1036 )]
1037 #[case(
1038 test_programs_stub_activity_builder::TEST_PROGRAMS_STUB_ACTIVITY,
1039 ComponentType::ActivityStub
1040 )]
1041 #[case(
1042 test_programs_stub_workflow_builder::TEST_PROGRAMS_STUB_WORKFLOW,
1043 ComponentType::Workflow
1044 )]
1045 #[case(
1046 test_programs_sleep_activity_builder::TEST_PROGRAMS_SLEEP_ACTIVITY,
1047 ComponentType::Workflow
1048 )]
1049 #[case(
1050 test_programs_sleep_workflow_builder::TEST_PROGRAMS_SLEEP_WORKFLOW,
1051 ComponentType::Workflow
1052 )]
1053 fn wit_should_contain_extensions(
1054 #[case] wasm_path: &'static str,
1055 #[case] component_type: ComponentType,
1056 ) {
1057 test_utils::set_up();
1058
1059 let component = WasmComponent::new(wasm_path, component_type).unwrap();
1060 let wasm_path = PathBuf::from(wasm_path);
1061 let wasm_file = strip_wasm_hash(&wasm_path.file_name().unwrap().to_string_lossy());
1062 let wit = component.wit();
1063 let group = UnresolvedPackageGroup::parse(PathBuf::new(), &wit).unwrap();
1065 let mut resolve = Resolve::new();
1066 let main_id = resolve.push_group(group).unwrap();
1067 let ids = resolve
1068 .packages
1069 .iter()
1070 .map(|(id, _)| id)
1071 .filter(|id| *id != main_id)
1073 .collect::<Vec<_>>();
1074 let mut printer = WitPrinter::new(OutputToString::default());
1075 printer.print(&resolve, main_id, &ids).unwrap(); insta::with_settings!({sort_maps => true, snapshot_suffix => format!("{wasm_file}_wit")}, {insta::assert_snapshot!(wit)});
1078 }
1079}