use std::fmt::Write as _;
use super::plan::ConnectionPlan;
use super::scaffold::{AionDependency, AwlWorkerScaffold};
use super::text::string_literal;
const AION_CRATES: [(&str, &str); 3] = [
(
"aion-worker",
"the worker SDK: the connection, the registry, and `register_activity_with_descriptor`",
),
(
"aion-package",
"`ActivityDescriptor`/`WorkerContract` — the advertisement types the server admits against",
),
(
"aion-awl",
"the AWL compiler. The embedded document is compiled ONCE at startup and every schema \
this worker advertises comes from the contract it returns, so it is a production \
dependency",
),
];
pub(super) fn emit(request: &AwlWorkerScaffold<'_>, plan: &ConnectionPlan) -> String {
let mut out = String::new();
let _ = writeln!(
out,
"# Generated by `aion awl scaffold` from `{}` — do not edit; regenerate from the\n\
# document.\n\
#\n\
# Serves the `{}` queue's {} action(s) across {} connection(s), one per node: the\n\
# server routes by (namespace, task_queue, node) and never by activity type, so a\n\
# process serving several nodes must dial once per node.\n\
#\n\
# Standalone (NOT a workspace member) so it builds from a plain `cargo build` in\n\
# this directory wherever it was scaffolded.",
request.document_name,
plan.task_queue,
plan.servable.len(),
plan.connections.len(),
);
let name = string_literal(request.crate_name);
let _ = writeln!(
out,
"\n[package]\n\
name = {name}\n\
version = \"0.1.0\"\n\
edition = \"2024\"\n\
publish = false\n\
\n\
[[bin]]\n\
name = {name}\n\
path = \"src/main.rs\"\n\
\n\
# An empty workspace table keeps this crate out of any workspace it is\n\
# scaffolded inside.\n\
[workspace]"
);
emit_lints(&mut out);
emit_dependencies(&mut out, request.dependencies);
out
}
fn emit_lints(out: &mut String) {
out.push_str(
"\n[lints.rust]\n\
unsafe_code = \"deny\"\n\
missing_docs = \"warn\"\n\
\n\
[lints.clippy]\n\
all = { level = \"deny\", priority = -1 }\n\
pedantic = { level = \"deny\", priority = -1 }\n\
unwrap_used = \"deny\"\n\
expect_used = \"deny\"\n\
panic = \"deny\"\n\
todo = \"deny\"\n",
);
}
fn emit_dependencies(out: &mut String, dependency: &AionDependency) {
out.push_str("\n[dependencies]\n");
for (crate_name, why) in AION_CRATES {
for line in wrapped_comment(why) {
let _ = writeln!(out, "# {line}");
}
match dependency {
AionDependency::Version(version) => {
let _ = writeln!(out, "{crate_name} = {}", string_literal(version));
}
AionDependency::Path(crates_directory) => {
let _ = writeln!(
out,
"{crate_name} = {{ path = {} }}",
string_literal(&format!("{crates_directory}/{crate_name}"))
);
}
}
}
out.push_str(
"# Top-level failure reporting for the binary; the declaration module keeps its own\n\
# typed errors.\n\
anyhow = \"1\"\n\
# The activity payloads: the types live in the AWL document, so the handlers take\n\
# and return JSON values rather than a second Rust rendering of the same shapes.\n\
serde_json = \"1\"\n\
thiserror = \"2\"\n\
tokio = { version = \"1\", features = [\"macros\", \"rt-multi-thread\"] }\n\
tracing = \"0.1\"\n\
# Without a subscriber the SDK's registration and reconnect logs go nowhere, and an\n\
# operator watching a worker that will not dial sees silence.\n\
tracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\n",
);
}
fn wrapped_comment(text: &str) -> Vec<String> {
const WIDTH: usize = 84;
let mut lines = Vec::new();
let mut current = String::new();
for word in text.split_whitespace() {
if !current.is_empty() && current.len() + 1 + word.len() > WIDTH {
lines.push(std::mem::take(&mut current));
}
if !current.is_empty() {
current.push(' ');
}
current.push_str(word);
}
if !current.is_empty() {
lines.push(current);
}
lines
}