@su-record/vibe 0.4.6 โ 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vibe/rules/core/communication-guide.md +104 -0
- package/.vibe/rules/core/development-philosophy.md +53 -0
- package/.vibe/rules/core/quick-start.md +121 -0
- package/.vibe/rules/languages/dart-flutter.md +509 -0
- package/.vibe/rules/languages/go.md +396 -0
- package/.vibe/rules/languages/java-spring.md +586 -0
- package/.vibe/rules/languages/kotlin-android.md +491 -0
- package/.vibe/rules/languages/python-django.md +371 -0
- package/.vibe/rules/languages/python-fastapi.md +386 -0
- package/.vibe/rules/languages/rust.md +425 -0
- package/.vibe/rules/languages/swift-ios.md +516 -0
- package/.vibe/rules/languages/typescript-nextjs.md +441 -0
- package/.vibe/rules/languages/typescript-node.md +375 -0
- package/.vibe/rules/languages/typescript-react-native.md +446 -0
- package/.vibe/rules/languages/typescript-react.md +525 -0
- package/.vibe/rules/languages/typescript-vue.md +353 -0
- package/.vibe/rules/quality/bdd-contract-testing.md +388 -0
- package/.vibe/rules/quality/checklist.md +276 -0
- package/.vibe/rules/quality/testing-strategy.md +437 -0
- package/.vibe/rules/standards/anti-patterns.md +369 -0
- package/.vibe/rules/standards/code-structure.md +291 -0
- package/.vibe/rules/standards/complexity-metrics.md +312 -0
- package/.vibe/rules/standards/naming-conventions.md +198 -0
- package/.vibe/rules/tools/mcp-hi-ai-guide.md +665 -0
- package/.vibe/rules/tools/mcp-workflow.md +51 -0
- package/package.json +2 -2
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# ๐ฆ Rust ํ์ง ๊ท์น
|
|
2
|
+
|
|
3
|
+
## ํต์ฌ ์์น (core์์ ์์)
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
โ
๋จ์ผ ์ฑ
์ (SRP)
|
|
7
|
+
โ
์ค๋ณต ์ ๊ฑฐ (DRY)
|
|
8
|
+
โ
์ฌ์ฌ์ฉ์ฑ
|
|
9
|
+
โ
๋ฎ์ ๋ณต์ก๋
|
|
10
|
+
โ
ํจ์ โค 30์ค
|
|
11
|
+
โ
์ค์ฒฉ โค 3๋จ๊ณ
|
|
12
|
+
โ
Cyclomatic complexity โค 10
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Rust ํนํ ๊ท์น
|
|
16
|
+
|
|
17
|
+
### 1. ์๋ฌ ์ฒ๋ฆฌ (Result, Option)
|
|
18
|
+
|
|
19
|
+
```rust
|
|
20
|
+
// โ unwrap() ๋จ์ฉ
|
|
21
|
+
let content = fs::read_to_string("config.json").unwrap();
|
|
22
|
+
|
|
23
|
+
// โ
? ์ฐ์ฐ์์ ์ ์ ํ ์๋ฌ ์ฒ๋ฆฌ
|
|
24
|
+
fn read_config(path: &str) -> Result<Config, ConfigError> {
|
|
25
|
+
let content = fs::read_to_string(path)
|
|
26
|
+
.map_err(|e| ConfigError::IoError(e))?;
|
|
27
|
+
|
|
28
|
+
let config: Config = serde_json::from_str(&content)
|
|
29
|
+
.map_err(|e| ConfigError::ParseError(e))?;
|
|
30
|
+
|
|
31
|
+
Ok(config)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// โ
์ปค์คํ
์๋ฌ ํ์
(thiserror)
|
|
35
|
+
use thiserror::Error;
|
|
36
|
+
|
|
37
|
+
#[derive(Error, Debug)]
|
|
38
|
+
pub enum AppError {
|
|
39
|
+
#[error("์ค์ ํ์ผ์ ์ฝ์ ์ ์์ต๋๋ค: {0}")]
|
|
40
|
+
ConfigError(#[from] std::io::Error),
|
|
41
|
+
|
|
42
|
+
#[error("์๋ชป๋ ์์ฒญ์
๋๋ค: {0}")]
|
|
43
|
+
BadRequest(String),
|
|
44
|
+
|
|
45
|
+
#[error("๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: {resource} (ID: {id})")]
|
|
46
|
+
NotFound { resource: String, id: String },
|
|
47
|
+
|
|
48
|
+
#[error("๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๋ฅ: {0}")]
|
|
49
|
+
DatabaseError(#[from] sqlx::Error),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// โ
anyhow๋ก ๊ฐํธํ ์๋ฌ ์ฒ๋ฆฌ (์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ)
|
|
53
|
+
use anyhow::{Context, Result};
|
|
54
|
+
|
|
55
|
+
fn process_file(path: &str) -> Result<String> {
|
|
56
|
+
let content = fs::read_to_string(path)
|
|
57
|
+
.context(format!("ํ์ผ์ ์ฝ์ ์ ์์ต๋๋ค: {}", path))?;
|
|
58
|
+
|
|
59
|
+
Ok(content)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. ๊ตฌ์กฐ์ฒด์ ํธ๋ ์ดํธ
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
// โ
๊ตฌ์กฐ์ฒด ์ ์
|
|
67
|
+
use chrono::{DateTime, Utc};
|
|
68
|
+
use serde::{Deserialize, Serialize};
|
|
69
|
+
use uuid::Uuid;
|
|
70
|
+
|
|
71
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
72
|
+
pub struct User {
|
|
73
|
+
pub id: Uuid,
|
|
74
|
+
pub email: String,
|
|
75
|
+
pub name: String,
|
|
76
|
+
pub created_at: DateTime<Utc>,
|
|
77
|
+
pub updated_at: DateTime<Utc>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl User {
|
|
81
|
+
pub fn new(email: String, name: String) -> Self {
|
|
82
|
+
let now = Utc::now();
|
|
83
|
+
Self {
|
|
84
|
+
id: Uuid::new_v4(),
|
|
85
|
+
email,
|
|
86
|
+
name,
|
|
87
|
+
created_at: now,
|
|
88
|
+
updated_at: now,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// โ
ํธ๋ ์ดํธ ์ ์
|
|
94
|
+
#[async_trait]
|
|
95
|
+
pub trait UserRepository: Send + Sync {
|
|
96
|
+
async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, AppError>;
|
|
97
|
+
async fn find_by_email(&self, email: &str) -> Result<Option<User>, AppError>;
|
|
98
|
+
async fn create(&self, user: &User) -> Result<User, AppError>;
|
|
99
|
+
async fn update(&self, user: &User) -> Result<User, AppError>;
|
|
100
|
+
async fn delete(&self, id: Uuid) -> Result<(), AppError>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// โ
ํธ๋ ์ดํธ ๊ตฌํ
|
|
104
|
+
pub struct PostgresUserRepository {
|
|
105
|
+
pool: PgPool,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[async_trait]
|
|
109
|
+
impl UserRepository for PostgresUserRepository {
|
|
110
|
+
async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, AppError> {
|
|
111
|
+
let user = sqlx::query_as!(
|
|
112
|
+
User,
|
|
113
|
+
"SELECT * FROM users WHERE id = $1",
|
|
114
|
+
id
|
|
115
|
+
)
|
|
116
|
+
.fetch_optional(&self.pool)
|
|
117
|
+
.await?;
|
|
118
|
+
|
|
119
|
+
Ok(user)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ... ๋ค๋ฅธ ๋ฉ์๋ ๊ตฌํ
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 3. Actix-web / Axum ํธ๋ค๋ฌ
|
|
127
|
+
|
|
128
|
+
```rust
|
|
129
|
+
// โ
Axum ํธ๋ค๋ฌ
|
|
130
|
+
use axum::{
|
|
131
|
+
extract::{Path, State, Json},
|
|
132
|
+
http::StatusCode,
|
|
133
|
+
response::IntoResponse,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
pub async fn get_user(
|
|
137
|
+
State(repo): State<Arc<dyn UserRepository>>,
|
|
138
|
+
Path(id): Path<Uuid>,
|
|
139
|
+
) -> Result<Json<User>, AppError> {
|
|
140
|
+
let user = repo
|
|
141
|
+
.find_by_id(id)
|
|
142
|
+
.await?
|
|
143
|
+
.ok_or(AppError::NotFound {
|
|
144
|
+
resource: "์ฌ์ฉ์".to_string(),
|
|
145
|
+
id: id.to_string(),
|
|
146
|
+
})?;
|
|
147
|
+
|
|
148
|
+
Ok(Json(user))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub async fn create_user(
|
|
152
|
+
State(repo): State<Arc<dyn UserRepository>>,
|
|
153
|
+
Json(dto): Json<CreateUserDto>,
|
|
154
|
+
) -> Result<(StatusCode, Json<User>), AppError> {
|
|
155
|
+
let user = User::new(dto.email, dto.name);
|
|
156
|
+
let created = repo.create(&user).await?;
|
|
157
|
+
|
|
158
|
+
Ok((StatusCode::CREATED, Json(created)))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// โ
Actix-web ํธ๋ค๋ฌ
|
|
162
|
+
use actix_web::{web, HttpResponse, Result};
|
|
163
|
+
|
|
164
|
+
pub async fn get_user(
|
|
165
|
+
repo: web::Data<dyn UserRepository>,
|
|
166
|
+
path: web::Path<Uuid>,
|
|
167
|
+
) -> Result<HttpResponse, AppError> {
|
|
168
|
+
let id = path.into_inner();
|
|
169
|
+
let user = repo
|
|
170
|
+
.find_by_id(id)
|
|
171
|
+
.await?
|
|
172
|
+
.ok_or(AppError::NotFound {
|
|
173
|
+
resource: "์ฌ์ฉ์".to_string(),
|
|
174
|
+
id: id.to_string(),
|
|
175
|
+
})?;
|
|
176
|
+
|
|
177
|
+
Ok(HttpResponse::Ok().json(user))
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 4. ์์ ๊ถ๊ณผ ์๋ช
์ฃผ๊ธฐ
|
|
182
|
+
|
|
183
|
+
```rust
|
|
184
|
+
// โ ๋ถํ์ํ ํด๋ก
|
|
185
|
+
fn process(data: &Vec<String>) -> Vec<String> {
|
|
186
|
+
let cloned = data.clone(); // ๋ถํ์
|
|
187
|
+
cloned.iter().map(|s| s.to_uppercase()).collect()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// โ
์ฐธ์กฐ ํ์ฉ
|
|
191
|
+
fn process(data: &[String]) -> Vec<String> {
|
|
192
|
+
data.iter().map(|s| s.to_uppercase()).collect()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// โ
์๋ช
์ฃผ๊ธฐ ๋ช
์
|
|
196
|
+
pub struct UserService<'a> {
|
|
197
|
+
repo: &'a dyn UserRepository,
|
|
198
|
+
cache: &'a dyn CacheRepository,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl<'a> UserService<'a> {
|
|
202
|
+
pub fn new(
|
|
203
|
+
repo: &'a dyn UserRepository,
|
|
204
|
+
cache: &'a dyn CacheRepository,
|
|
205
|
+
) -> Self {
|
|
206
|
+
Self { repo, cache }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// โ
์์ ๊ถ ์ด์ vs ๋น๋ ค์ค๊ธฐ
|
|
211
|
+
fn take_ownership(s: String) { /* s์ ์์ ๊ถ์ ๊ฐ์ง */ }
|
|
212
|
+
fn borrow(s: &str) { /* s๋ฅผ ๋น๋ ค์ด */ }
|
|
213
|
+
fn borrow_mut(s: &mut String) { /* s๋ฅผ ๊ฐ๋ณ ๋น๋ ค์ด */ }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 5. ๋น๋๊ธฐ ์ฒ๋ฆฌ (Tokio)
|
|
217
|
+
|
|
218
|
+
```rust
|
|
219
|
+
// โ
๋น๋๊ธฐ ํจ์
|
|
220
|
+
use tokio::time::{sleep, Duration};
|
|
221
|
+
|
|
222
|
+
pub async fn fetch_with_retry<T, F, Fut>(
|
|
223
|
+
f: F,
|
|
224
|
+
max_retries: u32,
|
|
225
|
+
) -> Result<T, AppError>
|
|
226
|
+
where
|
|
227
|
+
F: Fn() -> Fut,
|
|
228
|
+
Fut: std::future::Future<Output = Result<T, AppError>>,
|
|
229
|
+
{
|
|
230
|
+
let mut attempts = 0;
|
|
231
|
+
|
|
232
|
+
loop {
|
|
233
|
+
match f().await {
|
|
234
|
+
Ok(result) => return Ok(result),
|
|
235
|
+
Err(e) if attempts < max_retries => {
|
|
236
|
+
attempts += 1;
|
|
237
|
+
let delay = Duration::from_millis(100 * 2_u64.pow(attempts));
|
|
238
|
+
sleep(delay).await;
|
|
239
|
+
}
|
|
240
|
+
Err(e) => return Err(e),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// โ
๋์ ์คํ
|
|
246
|
+
use futures::future::join_all;
|
|
247
|
+
|
|
248
|
+
pub async fn fetch_users(ids: Vec<Uuid>) -> Vec<Result<User, AppError>> {
|
|
249
|
+
let futures: Vec<_> = ids
|
|
250
|
+
.into_iter()
|
|
251
|
+
.map(|id| fetch_user(id))
|
|
252
|
+
.collect();
|
|
253
|
+
|
|
254
|
+
join_all(futures).await
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// โ
tokio::spawn์ผ๋ก ํ์คํฌ ์์ฑ
|
|
258
|
+
pub async fn background_job() {
|
|
259
|
+
tokio::spawn(async {
|
|
260
|
+
loop {
|
|
261
|
+
process_queue().await;
|
|
262
|
+
sleep(Duration::from_secs(60)).await;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 6. ํ
์คํธ
|
|
269
|
+
|
|
270
|
+
```rust
|
|
271
|
+
#[cfg(test)]
|
|
272
|
+
mod tests {
|
|
273
|
+
use super::*;
|
|
274
|
+
use mockall::predicate::*;
|
|
275
|
+
use mockall::mock;
|
|
276
|
+
|
|
277
|
+
// โ
Mock ์์ฑ
|
|
278
|
+
mock! {
|
|
279
|
+
pub UserRepo {}
|
|
280
|
+
|
|
281
|
+
#[async_trait]
|
|
282
|
+
impl UserRepository for UserRepo {
|
|
283
|
+
async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, AppError>;
|
|
284
|
+
async fn create(&self, user: &User) -> Result<User, AppError>;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// โ
๋จ์ ํ
์คํธ
|
|
289
|
+
#[tokio::test]
|
|
290
|
+
async fn test_get_user_success() {
|
|
291
|
+
let mut mock_repo = MockUserRepo::new();
|
|
292
|
+
let user_id = Uuid::new_v4();
|
|
293
|
+
let expected_user = User::new("test@example.com".into(), "ํ
์คํธ".into());
|
|
294
|
+
|
|
295
|
+
mock_repo
|
|
296
|
+
.expect_find_by_id()
|
|
297
|
+
.with(eq(user_id))
|
|
298
|
+
.returning(move |_| Ok(Some(expected_user.clone())));
|
|
299
|
+
|
|
300
|
+
let service = UserService::new(Arc::new(mock_repo));
|
|
301
|
+
let result = service.get_user(user_id).await;
|
|
302
|
+
|
|
303
|
+
assert!(result.is_ok());
|
|
304
|
+
assert_eq!(result.unwrap().email, "test@example.com");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// โ
์๋ฌ ์ผ์ด์ค ํ
์คํธ
|
|
308
|
+
#[tokio::test]
|
|
309
|
+
async fn test_get_user_not_found() {
|
|
310
|
+
let mut mock_repo = MockUserRepo::new();
|
|
311
|
+
let user_id = Uuid::new_v4();
|
|
312
|
+
|
|
313
|
+
mock_repo
|
|
314
|
+
.expect_find_by_id()
|
|
315
|
+
.returning(|_| Ok(None));
|
|
316
|
+
|
|
317
|
+
let service = UserService::new(Arc::new(mock_repo));
|
|
318
|
+
let result = service.get_user(user_id).await;
|
|
319
|
+
|
|
320
|
+
assert!(matches!(result, Err(AppError::NotFound { .. })));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 7. ์์กด์ฑ ์ฃผ์
|
|
326
|
+
|
|
327
|
+
```rust
|
|
328
|
+
// โ
์์ฑ์ ์ฃผ์
|
|
329
|
+
pub struct UserService {
|
|
330
|
+
repo: Arc<dyn UserRepository>,
|
|
331
|
+
cache: Arc<dyn CacheRepository>,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
impl UserService {
|
|
335
|
+
pub fn new(
|
|
336
|
+
repo: Arc<dyn UserRepository>,
|
|
337
|
+
cache: Arc<dyn CacheRepository>,
|
|
338
|
+
) -> Self {
|
|
339
|
+
Self { repo, cache }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// โ
Builder ํจํด
|
|
344
|
+
#[derive(Default)]
|
|
345
|
+
pub struct ServerBuilder {
|
|
346
|
+
port: Option<u16>,
|
|
347
|
+
host: Option<String>,
|
|
348
|
+
timeout: Option<Duration>,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
impl ServerBuilder {
|
|
352
|
+
pub fn new() -> Self {
|
|
353
|
+
Self::default()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
pub fn port(mut self, port: u16) -> Self {
|
|
357
|
+
self.port = Some(port);
|
|
358
|
+
self
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pub fn host(mut self, host: impl Into<String>) -> Self {
|
|
362
|
+
self.host = Some(host.into());
|
|
363
|
+
self
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
pub fn timeout(mut self, timeout: Duration) -> Self {
|
|
367
|
+
self.timeout = Some(timeout);
|
|
368
|
+
self
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
pub fn build(self) -> Server {
|
|
372
|
+
Server {
|
|
373
|
+
port: self.port.unwrap_or(8080),
|
|
374
|
+
host: self.host.unwrap_or_else(|| "127.0.0.1".into()),
|
|
375
|
+
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ์ฌ์ฉ
|
|
381
|
+
let server = ServerBuilder::new()
|
|
382
|
+
.port(3000)
|
|
383
|
+
.host("0.0.0.0")
|
|
384
|
+
.timeout(Duration::from_secs(60))
|
|
385
|
+
.build();
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## ํ์ผ ๊ตฌ์กฐ
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
project/
|
|
392
|
+
โโโ src/
|
|
393
|
+
โ โโโ main.rs # ์ํธ๋ฆฌํฌ์ธํธ
|
|
394
|
+
โ โโโ lib.rs # ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฃจํธ
|
|
395
|
+
โ โโโ config.rs # ์ค์
|
|
396
|
+
โ โโโ error.rs # ์๋ฌ ์ ์
|
|
397
|
+
โ โโโ domain/ # ๋๋ฉ์ธ ๋ชจ๋ธ
|
|
398
|
+
โ โ โโโ mod.rs
|
|
399
|
+
โ โ โโโ user.rs
|
|
400
|
+
โ โโโ handlers/ # HTTP ํธ๋ค๋ฌ
|
|
401
|
+
โ โ โโโ mod.rs
|
|
402
|
+
โ โ โโโ user.rs
|
|
403
|
+
โ โโโ services/ # ๋น์ฆ๋์ค ๋ก์ง
|
|
404
|
+
โ โ โโโ mod.rs
|
|
405
|
+
โ โ โโโ user.rs
|
|
406
|
+
โ โโโ repositories/ # ๋ฐ์ดํฐ ์ก์ธ์ค
|
|
407
|
+
โ โ โโโ mod.rs
|
|
408
|
+
โ โ โโโ user.rs
|
|
409
|
+
โ โโโ middleware/ # ๋ฏธ๋ค์จ์ด
|
|
410
|
+
โโโ tests/ # ํตํฉ ํ
์คํธ
|
|
411
|
+
โโโ migrations/ # DB ๋ง์ด๊ทธ๋ ์ด์
|
|
412
|
+
โโโ Cargo.toml
|
|
413
|
+
โโโ Cargo.lock
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## ์ฒดํฌ๋ฆฌ์คํธ
|
|
417
|
+
|
|
418
|
+
- [ ] unwrap()/expect() ์ต์ํ, ? ์ฐ์ฐ์ ํ์ฉ
|
|
419
|
+
- [ ] thiserror/anyhow๋ก ์๋ฌ ์ฒ๋ฆฌ
|
|
420
|
+
- [ ] ํธ๋ ์ดํธ๋ก ์ถ์ํ, ์์กด์ฑ ์ฃผ์
|
|
421
|
+
- [ ] Clone ์ต์ํ, ์ฐธ์กฐ ํ์ฉ
|
|
422
|
+
- [ ] async/await ์ ์ ํ ์ฌ์ฉ
|
|
423
|
+
- [ ] clippy ๊ฒฝ๊ณ ํด๊ฒฐ
|
|
424
|
+
- [ ] cargo fmt ์ ์ฉ
|
|
425
|
+
- [ ] #[cfg(test)] ๋ชจ๋๋ก ํ
์คํธ ์์ฑ
|