serverust_cli/
templates.rs1const 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}