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