use crate::naming::Transport;
pub mod adapter;
pub mod auth;
pub mod feature;
pub mod hello;
pub mod migration;
pub mod resource;
pub mod shared;
pub mod standalone;
pub mod workspace;
pub fn crud_vars(crud_port: bool, transport: Transport) -> Vec<(&'static str, String)> {
if !crud_port {
return vec![
("op", "count".to_owned()),
("op_body", String::new()),
("op_value", "&self.svc.count()".to_owned()),
("op_description", "Count {{kebab}} items.".to_owned()),
];
}
let body = match transport {
Transport::Ws => concat!(
" // SECURITY: rows are ability-scoped, and this gateway is unguarded as\n",
" // scaffolded — with no ambient `Ability` every `Repo` read denies all\n",
" // rows. Bind #[use_guards(AuthnGuard, AuthzGuard)] on the struct and\n",
" // import AuthzWsModule in this adapter's module.rs (it brings\n",
" // `WsDataContext`, which installs the executor + ability per message),\n",
" // then replace the placeholder with `self.svc.list().await`.\n",
" let _ = &self.svc;",
),
Transport::Schedule => concat!(
" // A scheduled tick is system work: `DatabaseModule`'s JobContext\n",
" // installs the pool executor with no ability, so `Repo` runs unscoped.\n",
" // Replace the placeholder with the call this tick should make, e.g.\n",
" // `self.svc.list().await?`.\n",
" let _ = &self.svc;",
),
Transport::Mcp => concat!(
" // SECURITY: rows are ability-scoped. Bind the app's McpAbilityBridge as\n",
" // `dyn McpOperationGuard` and import AuthzMcpModule (it brings\n",
" // `McpDataContext`, which installs the executor + ability per tool call),\n",
" // then replace the placeholder with `self.svc.list().await` and mask the\n",
" // rows through `nest_rs_authz::masked_output_ambient`.\n",
" let _ = &self.svc;",
),
Transport::Http | Transport::Graphql | Transport::Queue => "",
};
let value = match transport {
Transport::Ws => "&Vec::<String>::new()",
_ => "\"[]\".to_string()",
};
vec![
("op", "list".to_owned()),
("op_body", body.to_owned()),
("op_value", value.to_owned()),
("op_description", "List {{kebab}} items.".to_owned()),
]
}
#[cfg(test)]
mod tests {
const SOURCES: &[&str] = &[
include_str!("adapter.rs"),
include_str!("auth.rs"),
include_str!("feature.rs"),
include_str!("hello.rs"),
include_str!("migration.rs"),
include_str!("resource.rs"),
include_str!("shared.rs"),
include_str!("standalone.rs"),
include_str!("workspace.rs"),
];
#[test]
fn no_scaffolded_log_is_emitted_without_a_structured_field() {
let bare: Vec<&str> = SOURCES
.iter()
.flat_map(|src| src.lines())
.filter(|line| line.contains("tracing::") && line.contains("!(target: \""))
.filter(|line| {
line.split_once("!(target: \"")
.and_then(|(_, rest)| rest.split_once('"'))
.is_some_and(|(_, after)| !after.split('"').next().unwrap_or("").contains('='))
})
.map(|line| line.trim())
.collect();
assert!(
bare.is_empty(),
"these scaffolded logs carry no structured field:\n{}",
bare.join("\n"),
);
}
}