armature-framework 0.2.2

A modern, type-safe HTTP framework for Rust inspired by Angular and NestJS. Features dependency injection, decorators, middleware, authentication (JWT/OAuth2/SAML), validation, OpenAPI/Swagger, caching, job queues, and observability.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# Armature Macros Guide

Comprehensive guide to all macros available in the Armature framework.

## Table of Contents

- [Overview]#overview
- [Macro Crates]#macro-crates
- [Route Decorators]#route-decorators
- [Response Macros]#response-macros
- [Validation Macros]#validation-macros
- [Parameter Extraction]#parameter-extraction
- [Error Handling]#error-handling
- [Model Macros]#model-macros
- [Test Helpers]#test-helpers
- [Best Practices]#best-practices

## Overview

Armature provides three macro crates:

- **`armature-macro`** - Procedural attribute macros (decorators)
- **`armature-macros`** - Declarative macros for common patterns
- **`armature-macros-utils`** - Utility procedural macros

## Macro Crates

### armature-macro (Proc Macros)

Attribute macros for decorating routes, controllers, and modules.

```toml
[dependencies]
armature-macro = { path = "../armature-macro" }
```

### armature-macros (Declarative)

Pattern-based macros for quick responses, validation, and utilities.

```toml
[dependencies]
armature-macros = { path = "../armature-macros" }
```

### armature-macros-utils (Proc Macros)

Additional utility procedural macros for responses and models.

```toml
[dependencies]
armature-macros-utils = { path = "../armature-macros-utils" }
```

## Route Decorators

### HTTP Method Decorators

From `armature-macro`:

```rust
use armature_macro::{get, post, put, delete, patch, controller};

#[controller("/api/users")]
pub struct UserController;

impl UserController {
    #[get("/:id")]
    async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
        let id = path_param!(req, "id")?;
        ok_json!({ "id": id })
    }

    #[post("/")]
    async fn create_user(req: HttpRequest) -> Result<HttpResponse, Error> {
        created_json!({ "message": "User created" })
    }

    #[put("/:id")]
    async fn update_user(req: HttpRequest) -> Result<HttpResponse, Error> {
        ok_json!({ "message": "User updated" })
    }

    #[delete("/:id")]
    async fn delete_user(req: HttpRequest) -> Result<HttpResponse, Error> {
        HttpResponse::no_content()
    }
}
```

### Additional Route Decorators

```rust
// Timeout decorator
#[timeout(5)]  // 5 seconds
#[get("/slow")]
async fn slow_endpoint(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Handler
}

// Body size limit
#[body_limit("10mb")]
#[post("/upload")]
async fn upload(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Handler
}

// Cache decorator
#[cache(ttl = 300)]
#[get("/expensive")]
async fn expensive_operation(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Result is cached for 5 minutes
}
```

## Response Macros

### JSON Responses

From `armature-macros`:

```rust
use armature_macros::*;

// 200 OK JSON response
#[get("/users")]
async fn list_users(req: HttpRequest) -> Result<HttpResponse, Error> {
    let users = vec!["Alice", "Bob", "Charlie"];
    ok_json!({ "users": users })
}

// 201 Created JSON response
#[post("/users")]
async fn create_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    let id = 123;
    created_json!({ "id": id, "message": "User created" })
}

// Custom status JSON response
#[get("/status")]
async fn status(req: HttpRequest) -> Result<HttpResponse, Error> {
    json_response!(202, { "status": "processing" })
}
```

### Error Responses

```rust
// 400 Bad Request
#[post("/users")]
async fn create_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    if name.is_empty() {
        return bad_request!("Name is required");
    }
    ok_json!({ "id": 1 })
}

// 404 Not Found
#[get("/users/:id")]
async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    let id: i64 = path_param!(req, "id")?;

    match find_user(id).await {
        Some(user) => ok_json!(user),
        None => not_found!("User {} not found", id),
    }
}

// 500 Internal Server Error
#[get("/data")]
async fn get_data(req: HttpRequest) -> Result<HttpResponse, Error> {
    match load_data().await {
        Ok(data) => ok_json!(data),
        Err(e) => internal_error!("Failed to load data: {}", e),
    }
}
```

### Other Response Types

```rust
use armature_macros_utils::{html, text, redirect};

// HTML response
#[get("/page")]
async fn page(req: HttpRequest) -> Result<HttpResponse, Error> {
    html!("<h1>Welcome</h1><p>Hello, world!</p>")
}

// Plain text response
#[get("/text")]
async fn plain_text(req: HttpRequest) -> Result<HttpResponse, Error> {
    text!("Hello, world!")
}

// Redirect response
#[get("/old-url")]
async fn redirect_old(req: HttpRequest) -> Result<HttpResponse, Error> {
    redirect!("/new-url")
}
```

## Validation Macros

### Field Validation

```rust
use armature_macros::*;

#[post("/users")]
async fn create_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    let name: String = extract_field(&req, "name")?;
    let email: String = extract_field(&req, "email")?;
    let age: u32 = extract_field(&req, "age")?;

    // Validate required fields
    validate_required!(name);
    validate_required!(email);

    // Validate email format
    validate_email!(email);

    // Validate age
    validate!(age >= 18);

    created_json!({ "message": "User created" })
}
```

### Guard Macro

```rust
use armature_macros::guard;

#[get("/admin")]
async fn admin_panel(req: HttpRequest) -> Result<HttpResponse, Error> {
    let user = get_current_user(&req).await?;

    // Guard condition - returns 403 if false
    guard!(user.is_admin(), "Admin access required");

    ok_json!({ "message": "Welcome, admin!" })
}
```

## Parameter Extraction

### Single Path Parameter

```rust
use armature_macros::path_param;

#[get("/users/:id")]
async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Extract and parse in one line
    let id: i64 = path_param!(req, "id")?;

    ok_json!({ "id": id })
}
```

### Multiple Path Parameters

```rust
use armature_macros::path_params;

#[get("/users/:user_id/posts/:post_id")]
async fn get_post(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Extract multiple parameters at once
    let (user_id, post_id) = path_params!(
        req,
        "user_id": i64,
        "post_id": i64
    )?;

    ok_json!({ "user_id": user_id, "post_id": post_id })
}
```

### Query Parameters

```rust
use armature_macros::query_param;

#[get("/search")]
async fn search(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Extract with default value
    let page: u32 = query_param!(req, "page").unwrap_or(1);
    let limit: u32 = query_param!(req, "limit").unwrap_or(20);
    let query: String = query_param!(req, "q").unwrap_or_default();

    ok_json!({ "page": page, "limit": limit, "query": query })
}
```

### Headers

```rust
use armature_macros::header;

#[get("/protected")]
async fn protected(req: HttpRequest) -> Result<HttpResponse, Error> {
    // Extract header (returns error if missing)
    let auth: &String = header!(req, "Authorization")?;

    // Or with default
    let content_type = header!(req, "Content-Type").unwrap_or(&"text/plain".to_string());

    ok_json!({ "message": "Authorized" })
}
```

## Error Handling

### Log and Return Error

```rust
use armature_macros::log_error;

#[get("/data")]
async fn get_data(req: HttpRequest) -> Result<HttpResponse, Error> {
    match database.query().await {
        Ok(data) => ok_json!(data),
        Err(e) => log_error!("Database query failed: {}", e),
    }
}
```

## Model Macros

### Model Derive

From `armature-macros-utils`:

```rust
use armature_macros_utils::{Model, ApiModel, Resource};
use serde::{Serialize, Deserialize};

// Basic model with common traits
#[derive(Model, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub email: String,
}

// API model with field control
#[derive(ApiModel, Serialize, Deserialize)]
pub struct UserResponse {
    pub id: i64,
    pub name: String,
    #[api(skip)]  // Exclude from API response
    pub password_hash: String,
}

// Resource model for database operations
#[derive(Resource, Serialize, Deserialize)]
#[resource(table = "users")]
pub struct UserEntity {
    #[resource(primary_key)]
    pub id: i64,
    pub name: String,
    pub created_at: String,
}
```

## Test Helpers

### Test Request Creation

From `armature-macros-utils`:

```rust
use armature_macros_utils::{test_request, assert_json, assert_status};

#[tokio::test]
async fn test_get_user() {
    let req = test_request!(GET "/users/1");
    let resp = handler(req).await.unwrap();

    assert_status!(resp, 200);
    assert_json!(resp, { "id": 1, "name": "Alice" });
}

#[tokio::test]
async fn test_create_user() {
    let req = test_request!(
        POST "/users",
        json!({ "name": "Bob", "email": "bob@example.com" })
    );

    let resp = handler(req).await.unwrap();
    assert_status!(resp, 201);
}
```

## Best Practices

### Combining Macros

```rust
use armature_macro::{get, post, timeout, body_limit};
use armature_macros::*;

#[controller("/api/users")]
pub struct UserController;

impl UserController {
    // Combine multiple decorators
    #[timeout(30)]
    #[body_limit("5mb")]
    #[post("/")]
    async fn create_user(req: HttpRequest) -> Result<HttpResponse, Error> {
        // Extract and validate
        let name: String = path_param!(req, "name")?;
        let email: String = path_param!(req, "email")?;

        validate_required!(name);
        validate_email!(email);

        // Create user
        let user = User { id: 1, name, email };

        // Return created response
        created_json!({ "user": user })
    }

    #[get("/:id")]
    async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
        let id: i64 = path_param!(req, "id")?;

        match find_user(id).await {
            Some(user) => ok_json!(user),
            None => not_found!("User {} not found", id),
        }
    }
}
```

### Clean Handler Code

**Before macros:**

```rust
async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    let id = req.path_params.get("id")
        .ok_or_else(|| Error::BadRequest("Missing id parameter".to_string()))?
        .parse::<i64>()
        .map_err(|e| Error::BadRequest(format!("Invalid id: {}", e)))?;

    match db.find_user(id).await {
        Ok(Some(user)) => {
            let json = serde_json::to_string(&user)
                .map_err(|e| Error::Serialization(e.to_string()))?;
            let mut response = HttpResponse::ok();
            response.body = json.into_bytes();
            response.headers.insert("Content-Type".to_string(), "application/json".to_string());
            Ok(response)
        }
        Ok(None) => {
            let error = serde_json::json!({
                "error": format!("User {} not found", id),
                "status": 404
            });
            HttpResponse::not_found().with_json(&error)
        }
        Err(e) => {
            Err(Error::InternalServerError(e.to_string()))
        }
    }
}
```

**After macros:**

```rust
async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
    let id: i64 = path_param!(req, "id")?;

    match db.find_user(id).await {
        Ok(Some(user)) => ok_json!(user),
        Ok(None) => not_found!("User {} not found", id),
        Err(e) => log_error!("Database error: {}", e),
    }
}
```

**Result:** 15 lines → 9 lines (40% reduction) with better readability!

## Summary

### Available Macros

| Category | Macro | Purpose |
|----------|-------|---------|
| **Routes** | `#[get]`, `#[post]`, `#[put]`, `#[delete]`, `#[patch]` | HTTP method routing |
| **Routes** | `#[timeout]`, `#[body_limit]`, `#[cache]` | Route configuration |
| **Responses** | `ok_json!()`, `created_json!()` | Quick JSON responses |
| **Responses** | `json_response!()`, `html!()`, `text!()` | Custom responses |
| **Errors** | `bad_request!()`, `not_found!()`, `internal_error!()` | Error responses |
| **Params** | `path_param!()`, `query_param!()`, `header!()` | Extract parameters |
| **Params** | `path_params!()` | Extract multiple params |
| **Validation** | `validate!()`, `validate_required!()` | Field validation |
| **Validation** | `validate_email!()`, `guard!()` | Specific validators |
| **Utilities** | `json_object!{}`, `paginated_response!()` | Helpers |
| **Errors** | `log_error!()`, `validation_error!()` | Error handling |
| **Models** | `#[derive(Model)]`, `#[derive(ApiModel)]` | Model generation |
| **Testing** | `test_request!()`, `assert_json!()` | Test helpers |

### Benefits

- **Reduced Boilerplate** - 30-50% less code
-**Type Safety** - Compile-time validation
-**Consistency** - Uniform error handling
-**Readability** - Clear, expressive code
-**Maintainability** - Easier to refactor
-**Performance** - Zero runtime overhead

### When to Use Macros

**Use macros for:**
- ✓ Repetitive patterns
- ✓ Type-safe abstractions
- ✓ Quick prototyping
- ✓ Consistent error handling

**Don't use macros for:**
- ✗ Complex business logic
- ✗ One-off operations
- ✗ When clarity suffers