Skip to main content

serverust_cli/
templates.rs

1//! Templates de texto para o scaffolding.
2//!
3//! Cada função devolve uma `String` pronta para ser escrita em disco. Os
4//! templates são intencionalmente simples (sem engine externo) para manter o
5//! binário enxuto e fácil de manter — substituições são feitas via `replace`.
6
7const NAME_PLACEHOLDER: &str = "{{NAME}}";
8const TYPE_PLACEHOLDER: &str = "{{TYPE}}";
9
10pub fn project_cargo_toml(name: &str) -> String {
11    format!(
12        r#"[package]
13name = "{name}"
14version = "0.1.0"
15edition = "2024"
16
17[dependencies]
18serverust-core = {{ path = "../serverust-core" }}
19serverust-lambda = {{ path = "../serverust-lambda" }}
20serverust-macros = {{ path = "../serverust-macros" }}
21tokio = {{ version = "1", features = ["macros", "rt-multi-thread"] }}
22serde = {{ version = "1", features = ["derive"] }}
23"#
24    )
25}
26
27pub fn project_serverust_toml(name: &str) -> String {
28    format!(
29        r#"# serverust.toml — configuração do projeto "{name}"
30# Perfis: default, dev, staging, prod
31# Selecione com SERVERUST_PROFILE=prod ou ServerustConfig::load_for_profile("prod")
32# Override por env: SERVERUST_SERVER__PORT=8080
33
34[default.server]
35host = "127.0.0.1"
36port = 3000
37
38[default.lambda]
39memory_size = 128
40timeout_seconds = 30
41
42[default.telemetry]
43log_level = "info"
44format = "json"
45
46[default.openapi]
47title = "{name}"
48version = "0.1.0"
49docs_path = "/docs"
50redoc_path = "/redoc"
51
52[dev.server]
53port = 3001
54
55[prod.server]
56host = "0.0.0.0"
57port = 8080
58"#
59    )
60}
61
62pub fn project_main_rs() -> String {
63    r#"use serverust_core::App;
64use serverust_lambda::AppRuntime;
65
66#[tokio::main]
67async fn main() -> Result<(), Box<dyn std::error::Error>> {
68    let app = App::new();
69    app.run().await?;
70    Ok(())
71}
72"#
73    .to_string()
74}
75
76pub fn project_modules_mod_rs() -> String {
77    "// declare seus módulos aqui: `pub mod users;`\n".to_string()
78}
79
80pub fn project_shared_mod_rs() -> String {
81    "// componentes compartilhados: guards, pipes, interceptors, filters\n".to_string()
82}
83
84pub fn controller(name: &str) -> String {
85    let type_name = pascal_case(name);
86    template_with_name_type(
87        r#"use serverust_core::extract::Path;
88use serverust_macros::get;
89
90#[get("/{{NAME}}/{id}")]
91pub async fn show_{{NAME}}(Path(id): Path<u64>) -> String {
92    format!("{{TYPE}}::show id={id}")
93}
94"#,
95        name,
96        &type_name,
97    )
98}
99
100pub fn service(name: &str) -> String {
101    let type_name = pascal_case(name);
102    template_with_name_type(
103        r#"use serverust_macros::injectable;
104
105#[injectable]
106pub struct {{TYPE}}Service;
107
108impl {{TYPE}}Service {
109    pub fn new() -> Self {
110        Self
111    }
112}
113
114impl Default for {{TYPE}}Service {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119"#,
120        name,
121        &type_name,
122    )
123}
124
125pub fn dto(name: &str) -> String {
126    let type_name = pascal_case(name);
127    template_with_name_type(
128        r#"use serde::{Deserialize, Serialize};
129use validator::Validate;
130
131#[derive(Debug, Deserialize, Serialize, Validate)]
132pub struct Create{{TYPE}}Dto {
133    #[validate(length(min = 1))]
134    pub name: String,
135}
136"#,
137        name,
138        &type_name,
139    )
140}
141
142pub fn module_mod_rs(name: &str) -> String {
143    format!(
144        "#[path = \"{name}.controller.rs\"]\npub mod controller;\n\
145         #[path = \"{name}.service.rs\"]\npub mod service;\n"
146    )
147}
148
149pub fn resource_mod_rs(name: &str) -> String {
150    format!(
151        "{base}#[path = \"{name}.dto.rs\"]\npub mod dto;\n",
152        base = module_mod_rs(name),
153    )
154}
155
156pub fn pipe(name: &str) -> String {
157    let type_name = pascal_case(name);
158    template_with_name_type(
159        r#"use serverust_core::pipeline::Pipe;
160
161pub struct {{TYPE}}Pipe;
162
163impl Pipe<String> for {{TYPE}}Pipe {
164    type Output = String;
165
166    fn transform(input: String) -> Result<Self::Output, axum::response::Response> {
167        Ok(input)
168    }
169}
170"#,
171        name,
172        &type_name,
173    )
174}
175
176pub fn guard(name: &str) -> String {
177    let type_name = pascal_case(name);
178    template_with_name_type(
179        r#"use axum::http::request::Parts;
180use axum::response::Response;
181use serverust_core::pipeline::Guard;
182
183pub struct {{TYPE}}Guard;
184
185impl Guard for {{TYPE}}Guard {
186    async fn check(_parts: &Parts) -> Result<(), Response> {
187        Ok(())
188    }
189}
190"#,
191        name,
192        &type_name,
193    )
194}
195
196pub fn interceptor(name: &str) -> String {
197    let type_name = pascal_case(name);
198    template_with_name_type(
199        r#"use axum::extract::Request;
200use axum::middleware::Next;
201use axum::response::Response;
202use serverust_core::pipeline::Interceptor;
203
204pub struct {{TYPE}}Interceptor;
205
206impl Interceptor for {{TYPE}}Interceptor {
207    async fn intercept(&self, req: Request, next: Next) -> Response {
208        next.run(req).await
209    }
210}
211"#,
212        name,
213        &type_name,
214    )
215}
216
217pub fn filter(name: &str) -> String {
218    let type_name = pascal_case(name);
219    template_with_name_type(
220        r#"use axum::response::{IntoResponse, Response};
221
222/// Filter (mapeador de erros) inspirado em ExceptionFilter do NestJS.
223pub struct {{TYPE}}Filter;
224
225impl {{TYPE}}Filter {
226    pub fn handle<E: std::error::Error>(err: E) -> Response {
227        (axum::http::StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response()
228    }
229}
230"#,
231        name,
232        &type_name,
233    )
234}
235
236fn template_with_name_type(template: &str, name: &str, type_name: &str) -> String {
237    template
238        .replace(NAME_PLACEHOLDER, name)
239        .replace(TYPE_PLACEHOLDER, type_name)
240}
241
242fn pascal_case(s: &str) -> String {
243    let mut out = String::with_capacity(s.len());
244    let mut up = true;
245    for ch in s.chars() {
246        if ch == '_' || ch == '-' {
247            up = true;
248            continue;
249        }
250        if up {
251            out.extend(ch.to_uppercase());
252            up = false;
253        } else {
254            out.push(ch);
255        }
256    }
257    out
258}