use std::path::PathBuf;
use super::cargo::{ensure_features_deps, ensure_workspace_deps, resource_deps};
use super::{finish, resolve_start, wire_into_app};
use crate::context::Context;
use crate::error::{CliError, CliResult};
use crate::naming::Names;
use crate::scaffold::{Renderer, Scaffold, ensure_decl};
use crate::templates::resource;
pub struct ResourceOptions {
pub name: String,
pub path: Option<PathBuf>,
pub dry_run: bool,
pub guarded: bool,
}
pub fn run(opts: ResourceOptions) -> CliResult<()> {
let ctx = Context::detect(&resolve_start(opts.path))?;
let ws = ctx.workspace.clone().ok_or(CliError::NotNestrsWorkspace)?;
crate::naming::validate_feature_name(&opts.name).map_err(CliError::InvalidFeatureName)?;
let names = Names::parse(&opts.name);
let root = ws.feature_root(&names.snake);
if root.exists() {
return Err(CliError::FeatureExists {
name: names.snake.clone(),
path: root,
});
}
let r = Renderer::new(&names);
let mut s = Scaffold::new();
s.create(root.join("entity.rs"), r.render(resource::ENTITY));
s.create(root.join("service.rs"), r.render(resource::SERVICE));
s.create(root.join("module.rs"), r.render(resource::MODULE));
s.create(root.join("mod.rs"), r.render(resource::MOD));
s.create(root.join("http/mod.rs"), r.render(resource::HTTP_MOD));
let (controller_tmpl, module_tmpl) = if opts.guarded {
(
resource::HTTP_CONTROLLER_GUARDED,
resource::HTTP_MODULE_GUARDED,
)
} else {
(resource::HTTP_CONTROLLER, resource::HTTP_MODULE)
};
s.create(root.join("http/module.rs"), r.render(module_tmpl));
s.create(root.join("http/controller.rs"), r.render(controller_tmpl));
s.edit(
ws.root.join("Cargo.toml"),
ensure_workspace_deps(resource_deps()),
);
s.edit(ws.features_cargo(), ensure_features_deps(resource_deps()));
s.edit(
ws.features_lib(),
ensure_decl(&format!("pub mod {};", names.snake)),
);
let wired_app = wire_into_app(
&ctx,
&mut s,
&format!("features::{}::{}", names.snake, names.http_module()),
&names.http_module(),
Some("DatabaseModule"),
);
finish(
s,
opts.dry_run,
&ws.root,
&format!("Created resource `{}`", names.snake),
)?;
print_next_steps(&ctx, &names, wired_app, opts.guarded);
Ok(())
}
fn print_next_steps(ctx: &Context, names: &Names, wired_app: Option<PathBuf>, guarded: bool) {
println!();
println!("Next steps:");
println!(" 1. Fill in `entity.rs` columns and add a migration.");
if wired_app.is_some() {
println!(
" 2. {} is wired into the current app.",
names.http_module()
);
} else if ctx.current_app.is_some() {
println!(
" 2. Add `DatabaseModule::for_root(None)` to this app, then import \
`features::{}::{}`.",
names.snake,
names.http_module()
);
} else {
println!(
" 2. Import `features::{}::{}` in an app that has `DatabaseModule`.",
names.snake,
names.http_module()
);
}
println!(" 3. Add transports: nestrs g graphql|ws {}", names.kebab);
if guarded {
println!(
" Guarded form: needs AuthGuard/AuthzGuard/AuthzHttpModule in the \
workspace, and ability rules for {} in your AppAbility.",
names.entity()
);
} else {
println!(
" Harden with #[crud] + guards: nestrs g resource {} --guarded",
names.kebab
);
}
}