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