use std::fmt::Write as _;
use super::activity_model::ResolvedActivity;
pub(crate) const GENERATED_ACTIVITY_HEADER: &str =
"//// Generated by aion generate — do not edit; regenerate from the activity declarations.";
pub(crate) fn emit_wrappers_module(package_name: &str, activities: &[ResolvedActivity]) -> String {
let mut out = String::new();
let _ = writeln!(out, "{GENERATED_ACTIVITY_HEADER}");
out.push_str("////\n");
let _ = writeln!(
out,
"//// Typed activity wrappers for the `{package_name}` workflow project, derived"
);
out.push_str("//// from the declarations its `manifest()` returns.\n");
out.push('\n');
out.push_str("import aion/activity\n");
let _ = writeln!(out, "import {package_name}_activities as activities");
let _ = writeln!(out, "import {package_name}_codecs as codecs");
let _ = writeln!(out, "import {package_name}_io as io");
for activity in activities {
let name = &activity.declaration.name;
let input_type = &activity.input.gleam_type;
let output_type = &activity.output.gleam_type;
let input_prefix = &activity.input.fn_prefix;
let output_prefix = &activity.output.fn_prefix;
let _ = write!(
out,
"\npub fn {name}_activity(\n input: io.{input_type},\n\
) -> activity.Activity(io.{input_type}, io.{output_type}) {{\n \
activity.new(\n \"{name}\",\n input,\n \
codecs.{input_prefix}_codec(),\n codecs.{output_prefix}_codec(),\n \
activities.{name},\n )\n}}\n"
);
}
out
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::emit_wrappers_module;
use crate::codegen::activity_model::{ResolvedActivity, ResolvedType};
use crate::codegen::declaration::{ActivityDeclaration, Tier};
use crate::codegen::model::{BoundaryType, GleamType};
fn boundary() -> BoundaryType {
BoundaryType {
file: PathBuf::from("schemas/placeholder.json"),
stem: "placeholder".to_owned(),
root: GleamType::Named {
type_name: "Placeholder".to_owned(),
fn_prefix: "placeholder".to_owned(),
},
defs: Vec::new(),
}
}
fn declaration(name: &str, tier: Tier, input: &str, output: &str) -> ActivityDeclaration {
ActivityDeclaration {
name: name.to_owned(),
tier,
input_type: input.to_owned(),
output_type: output.to_owned(),
}
}
fn resolved<'a>(
declaration: &'a ActivityDeclaration,
boundary: &'a BoundaryType,
) -> ResolvedActivity<'a> {
let prefix = |ty: &str| ty.to_lowercase();
ResolvedActivity {
declaration,
input: ResolvedType {
gleam_type: declaration.input_type.clone(),
fn_prefix: prefix(&declaration.input_type),
boundary,
},
output: ResolvedType {
gleam_type: declaration.output_type.clone(),
fn_prefix: prefix(&declaration.output_type),
boundary,
},
}
}
#[test]
fn wrappers_module_emits_one_constructor_per_declaration_in_order() {
let art = boundary();
let d1 = declaration("reserve_inventory", Tier::RemotePython, "Order", "Receipt");
let d2 = declaration("ship_order", Tier::InVm, "Order", "Shipment");
let activities = [resolved(&d1, &art), resolved(&d2, &art)];
let module = emit_wrappers_module("demo", &activities);
assert!(module.starts_with(super::GENERATED_ACTIVITY_HEADER));
assert!(module.contains("import demo_activities as activities\n"));
assert!(module.contains("import demo_codecs as codecs\n"));
assert!(module.contains("import demo_io as io\n"));
let first = module.find("reserve_inventory_activity");
let second = module.find("ship_order_activity");
assert!(first.is_some() && second.is_some());
assert!(first < second, "declaration order preserved");
assert!(module.contains(
"pub fn reserve_inventory_activity(\n input: io.Order,\n\
) -> activity.Activity(io.Order, io.Receipt) {\n activity.new(\n \
\"reserve_inventory\",\n input,\n codecs.order_codec(),\n \
codecs.receipt_codec(),\n activities.reserve_inventory,\n )\n}\n"
));
}
}