runique 2.1.11

A Django-inspired web framework for Rust with ORM, templates, and comprehensive security middleware
Documentation
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
//! Centralized Runique log configuration — levels by category, `runique_log!` macro, `dev()` helper.
use std::sync::{Arc, Mutex};
use tracing::Level;
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};

/// Form pipeline tracing — each field covers one stage of `#[form]` processing.
#[derive(Debug, Clone, Default)]
pub struct FormTracing {
    /// Field-level events: type resolution, coercion, missing/extra fields.
    pub field: Option<Level>,
    /// `set_value()` calls: raw input → typed value assignment.
    pub set_value: Option<Level>,
    /// `validate()` results: per-field errors, required/length/format checks.
    pub validate: Option<Level>,
    /// HTML render events: widget selection, context injection.
    pub render: Option<Level>,
    /// `finalize()` per field: password hashing, file move to MEDIA_ROOT.
    pub finalize: Option<Level>,
}

impl FormTracing {
    pub fn new() -> Self {
        Self::default()
    }
    #[must_use]
    pub fn field(mut self, level: Level) -> Self {
        self.field = Some(level);
        self
    }
    #[must_use]
    pub fn set_value(mut self, level: Level) -> Self {
        self.set_value = Some(level);
        self
    }
    #[must_use]
    pub fn validate(mut self, level: Level) -> Self {
        self.validate = Some(level);
        self
    }
    #[must_use]
    pub fn render(mut self, level: Level) -> Self {
        self.render = Some(level);
        self
    }
    #[must_use]
    pub fn finalize(mut self, level: Level) -> Self {
        self.finalize = Some(level);
        self
    }
    pub fn dev(self) -> Self {
        self.field(Level::DEBUG)
            .set_value(Level::DEBUG)
            .validate(Level::DEBUG)
            .render(Level::DEBUG)
            .finalize(Level::DEBUG)
    }
}

/// Builder startup tracing — one-time events during `build()`.
#[derive(Debug, Clone, Default)]
pub struct BuilderTracing {
    /// Template loading: nb internal + user templates registered in Tera.
    pub templates: Option<Level>,
    /// Admin registry: nb resources registered at startup.
    pub registry: Option<Level>,
    /// Middleware stack: each slot name + number assigned at startup.
    pub middleware: Option<Level>,
    /// Static files: static_url + path + media_url + path.
    pub statics: Option<Level>,
    /// URL routes: nb named routes in registry after `add_urls()`.
    pub routes: Option<Level>,
}

impl BuilderTracing {
    pub fn new() -> Self {
        Self::default()
    }
    #[must_use]
    pub fn templates(mut self, level: Level) -> Self {
        self.templates = Some(level);
        self
    }
    #[must_use]
    pub fn registry(mut self, level: Level) -> Self {
        self.registry = Some(level);
        self
    }
    #[must_use]
    pub fn middleware(mut self, level: Level) -> Self {
        self.middleware = Some(level);
        self
    }
    #[must_use]
    pub fn statics(mut self, level: Level) -> Self {
        self.statics = Some(level);
        self
    }
    #[must_use]
    pub fn routes(mut self, level: Level) -> Self {
        self.routes = Some(level);
        self
    }
    pub fn dev(self) -> Self {
        self.templates(Level::DEBUG)
            .registry(Level::DEBUG)
            .middleware(Level::DEBUG)
            .statics(Level::DEBUG)
            .routes(Level::DEBUG)
    }
}

/// Auth tracing — session lifecycle events.
#[derive(Debug, Clone, Default)]
pub struct AuthTracing {
    /// User login: session creation, group loading, DB persistence, exclusive flag.
    pub login: Option<Level>,
    /// Password reset flow: token generated, email sent, token validated/consumed, password updated.
    pub reset: Option<Level>,
}

impl AuthTracing {
    pub fn new() -> Self {
        Self::default()
    }
    #[must_use]
    pub fn login(mut self, level: Level) -> Self {
        self.login = Some(level);
        self
    }
    #[must_use]
    pub fn reset(mut self, level: Level) -> Self {
        self.reset = Some(level);
        self
    }
    pub fn dev(self) -> Self {
        self.login(Level::DEBUG).reset(Level::DEBUG)
    }
}

/// Mailer tracing — email dispatch events.
#[derive(Debug, Clone, Default)]
pub struct MailerTracing {
    /// `Email::send()`: backend used, recipient, subject, result (ok/err).
    pub send: Option<Level>,
}

impl MailerTracing {
    pub fn new() -> Self {
        Self::default()
    }
    #[must_use]
    pub fn send(mut self, level: Level) -> Self {
        self.send = Some(level);
        self
    }
    pub fn dev(self) -> Self {
        self.send(Level::DEBUG)
    }
}

/// Admin panel tracing — per-operation granularity.
#[derive(Debug, Clone, Default)]
pub struct AdminTracing {
    /// Auth checks: login, permission gate, write-access guard.
    pub auth: Option<Level>,
    /// CRUD handlers: detail, create, edit, delete — request + outcome.
    pub crud: Option<Level>,
    /// List view: pagination, ordering, column resolution.
    pub list: Option<Level>,
    /// Bulk operations: group_action, group_set, bulk_delete.
    pub bulk: Option<Level>,
}

impl AdminTracing {
    pub fn new() -> Self {
        Self::default()
    }
    #[must_use]
    pub fn auth(mut self, level: Level) -> Self {
        self.auth = Some(level);
        self
    }
    #[must_use]
    pub fn crud(mut self, level: Level) -> Self {
        self.crud = Some(level);
        self
    }
    #[must_use]
    pub fn list(mut self, level: Level) -> Self {
        self.list = Some(level);
        self
    }
    #[must_use]
    pub fn bulk(mut self, level: Level) -> Self {
        self.bulk = Some(level);
        self
    }
    pub fn dev(self) -> Self {
        self.auth(Level::DEBUG)
            .crud(Level::DEBUG)
            .list(Level::DEBUG)
            .bulk(Level::DEBUG)
    }
}

/// Unified Runique log configuration.
///
/// Controls both the global tracing subscriber level and internal framework
/// categories.
///
/// # Exemple
/// ```rust,ignore
/// RuniqueApp::builder(config)
///     .with_log(|l| l
///         .subscriber_level("info")   // optional — default: debug/warn according to DEBUG env
///         .csrf(Level::WARN)
///         .session(Level::INFO)
///         .forms(|f| f.validate(Level::DEBUG))
///         .admin(|a| a.crud(Level::INFO))
///     )
/// ```
#[derive(Debug, Clone, Default)]
pub struct RuniqueLog {
    /// Tracing subscriber level. `RUST_LOG` takes priority if defined.
    /// Default: `"debug"` if `DEBUG=true`, else `"warn"`.
    subscriber_level: Option<String>,

    /// Detects a `csrf_token` in a GET URL (silent cleanup).
    pub csrf: Option<Level>,
    /// Traces session invalidation during exclusive login.
    pub exclusive_login: Option<Level>,
    /// Reports `filter_fn` failure in admin list view.
    pub filter_fn: Option<Level>,
    /// Reports admin roles registry access errors.
    pub roles: Option<Level>,
    /// Warns if `password_init()` is called multiple times.
    pub password_init: Option<Level>,
    /// Session store traces: memory watermarks, large records, cleanup errors.
    pub session: Option<Level>,
    /// DB connection info (connecting / connected successfully).
    pub db: Option<Level>,
    /// Host header validation rejections (HTTP/2 `:authority` fallback included).
    pub host_validation: Option<Level>,
    /// ACME/TLS lifecycle events: cert loaded, renewed, binding port 443.
    pub acme: Option<Level>,

    /// Form pipeline tracing (`#[form]` fields, validation, render, finalize).
    pub forms: Option<FormTracing>,
    /// Admin panel tracing (auth, CRUD, list, bulk).
    pub admin: Option<AdminTracing>,
    /// Auth tracing (login session lifecycle).
    pub auth: Option<AuthTracing>,
    /// Mailer tracing (email dispatch results).
    pub mailer: Option<MailerTracing>,
    /// Builder startup tracing (templates, registry, middleware slots).
    pub builder: Option<BuilderTracing>,
    /// Rate limiter: requests blocked (ip, retry_after).
    pub rate_limit: Option<Level>,
}

impl RuniqueLog {
    pub fn new() -> Self {
        Self::default()
    }

    /// Overrides the tracing subscriber level.
    /// `RUST_LOG` always has priority over this value.
    #[must_use]
    pub fn subscriber_level(mut self, level: impl Into<String>) -> Self {
        self.subscriber_level = Some(level.into());
        self
    }

    /// Initializes the global tracing subscriber.
    /// Called automatically by `build()` — no effect if already initialized.
    pub fn init_subscriber(&self) {
        let default = self.subscriber_level.as_deref().unwrap_or_else(|| {
            if crate::utils::env::is_debug() {
                "debug"
            } else {
                "warn"
            }
        });

        let filter =
            std::env::var("RUST_LOG").map_or_else(|_| EnvFilter::new(default), EnvFilter::new);

        tracing_subscriber::fmt()
            .with_env_filter(filter)
            .with_span_events(FmtSpan::CLOSE)
            .try_init()
            .ok();
    }
    #[must_use]
    pub fn csrf(mut self, level: Level) -> Self {
        self.csrf = Some(level);
        self
    }
    #[must_use]
    pub fn exclusive_login(mut self, level: Level) -> Self {
        self.exclusive_login = Some(level);
        self
    }
    #[must_use]
    pub fn filter_fn(mut self, level: Level) -> Self {
        self.filter_fn = Some(level);
        self
    }
    #[must_use]
    pub fn roles(mut self, level: Level) -> Self {
        self.roles = Some(level);
        self
    }
    #[must_use]
    pub fn password_init(mut self, level: Level) -> Self {
        self.password_init = Some(level);
        self
    }
    #[must_use]
    pub fn session(mut self, level: Level) -> Self {
        self.session = Some(level);
        self
    }
    #[must_use]
    pub fn db(mut self, level: Level) -> Self {
        self.db = Some(level);
        self
    }
    #[must_use]
    pub fn host_validation(mut self, level: Level) -> Self {
        self.host_validation = Some(level);
        self
    }
    #[must_use]
    pub fn acme(mut self, level: Level) -> Self {
        self.acme = Some(level);
        self
    }

    /// Configures form pipeline tracing.
    ///
    /// ```rust,ignore
    /// .with_log(|l| l.forms(|f| f.validate(Level::DEBUG).set_value(Level::TRACE)))
    /// ```
    #[must_use]
    pub fn forms(mut self, f: impl FnOnce(FormTracing) -> FormTracing) -> Self {
        self.forms = Some(f(self.forms.take().unwrap_or_default()));
        self
    }

    /// Configures admin panel tracing.
    ///
    /// ```rust,ignore
    /// .with_log(|l| l.admin(|a| a.crud(Level::INFO).auth(Level::WARN)))
    /// ```
    #[must_use]
    pub fn admin(mut self, f: impl FnOnce(AdminTracing) -> AdminTracing) -> Self {
        self.admin = Some(f(self.admin.take().unwrap_or_default()));
        self
    }

    /// Configures auth session tracing.
    ///
    /// ```rust,ignore
    /// .with_log(|l| l.auth(|a| a.login(Level::INFO)))
    /// ```
    #[must_use]
    pub fn auth(mut self, f: impl FnOnce(AuthTracing) -> AuthTracing) -> Self {
        self.auth = Some(f(self.auth.take().unwrap_or_default()));
        self
    }

    /// Configures mailer dispatch tracing.
    ///
    /// ```rust,ignore
    /// .with_log(|l| l.mailer(|m| m.send(Level::INFO)))
    /// ```
    #[must_use]
    pub fn mailer(mut self, f: impl FnOnce(MailerTracing) -> MailerTracing) -> Self {
        self.mailer = Some(f(self.mailer.take().unwrap_or_default()));
        self
    }

    /// Rate limiter: logs blocked requests (ip, retry_after_secs).
    #[must_use]
    pub fn rate_limit(mut self, level: Level) -> Self {
        self.rate_limit = Some(level);
        self
    }

    /// Configures builder startup tracing.
    ///
    /// ```rust,ignore
    /// .with_log(|l| l.builder(|b| b.templates(Level::DEBUG).middleware(Level::DEBUG)))
    /// ```
    #[must_use]
    pub fn builder(mut self, f: impl FnOnce(BuilderTracing) -> BuilderTracing) -> Self {
        self.builder = Some(f(self.builder.take().unwrap_or_default()));
        self
    }

    /// Enables all categories at `DEBUG` level.
    ///
    /// No effect if `DEBUG` is not `true` or `1` in the environment —
    /// can be used unconditionally in `.with_log()`.
    ///
    /// ```rust,ignore
    /// .with_log(|l| l.dev())
    /// // or with override
    /// .with_log(|l| l.dev().db(Level::INFO))
    /// ```
    #[must_use]
    pub fn dev(self) -> Self {
        if !crate::utils::env::is_debug() {
            return self;
        }
        self.csrf(Level::DEBUG)
            .exclusive_login(Level::DEBUG)
            .filter_fn(Level::DEBUG)
            .roles(Level::DEBUG)
            .password_init(Level::DEBUG)
            .session(Level::DEBUG)
            .db(Level::DEBUG)
            .host_validation(Level::DEBUG)
            .acme(Level::DEBUG)
            .rate_limit(Level::DEBUG)
            .forms(|f| f.dev())
            .admin(|a| a.dev())
            .auth(|a| a.dev())
            .mailer(|m| m.dev())
            .builder(|b| b.dev())
    }
}

static LOG_CONFIG: Mutex<Option<Arc<RuniqueLog>>> = Mutex::new(None);

/// Initializes the log configuration — called once during `build()`.
/// Silent no-op if already initialized.
pub fn log_init(config: RuniqueLog) {
    if let Ok(mut guard) = LOG_CONFIG.lock()
        && guard.is_none()
    {
        *guard = Some(Arc::new(config));
    }
}

/// Returns the active log configuration.
/// Returns an empty config (all disabled) if `log_init` hasn't been called.
pub fn get_log() -> Arc<RuniqueLog> {
    LOG_CONFIG
        .lock()
        .ok()
        .and_then(|g| g.as_ref().cloned())
        .unwrap_or_default()
}

/// Resets the log configuration. Only call from tests.
pub fn reset_log_for_test() {
    if let Ok(mut guard) = LOG_CONFIG.lock() {
        *guard = None;
    }
}

/// Emits a tracing event at the configured dynamic level.
///
/// # Exemple
/// ```rust,ignore
/// if let Some(level) = get_log().csrf {
///     runique_log!(level, path = %path, "csrf_token detected in a GET URL");
/// }
/// ```
#[macro_export]
macro_rules! runique_log {
    ($level:expr, $($args:tt)*) => {
        match $level {
            ::tracing::Level::ERROR => ::tracing::error!($($args)*),
            ::tracing::Level::WARN  => ::tracing::warn!($($args)*),
            ::tracing::Level::INFO  => ::tracing::info!($($args)*),
            ::tracing::Level::DEBUG => ::tracing::debug!($($args)*),
            _                       => ::tracing::trace!($($args)*),
        }
    };
}