quicburn-shared 0.1.0

A blazing fast QUIC implementation in Rust
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
// crates/quicburn-shared/src/routes.rs

use std::collections::HashMap;

// ============================================================
// Route IDs
// ============================================================

/// Compute a route's wire id from its name.
///
/// Routes are identified by name (a string) everywhere in application code,
/// but the wire protocol needs a single byte per frame, so every route name
/// is hashed down to a `u32` here. This is the ONE place that hashing logic
/// lives — hand-written route registrations, the `#[route(...)]` macro, and
/// this registry all call this same function, so nothing can ever disagree
/// about what a name hashes to.
///
/// This hash is deliberately simple and NOT collision-resistant (XOR of the
/// first 8 bytes of the name). Two different names can hash to the same
/// byte. Collisions are caught at startup — by `RouteTable::register` in
/// quicburn-server, and by `RouteRegistry::register` below — which panic
/// rather than silently letting one route shadow another.
pub const fn route_id(name: &str) -> u32 {
    let bytes = name.as_bytes();
    let mut hash: u32 = 0x811c9dc5; // FNV-1a offset basis
    let mut i = 0;
    while i < bytes.len() && i < 32 {
        hash ^= bytes[i] as u32;
        hash = hash.wrapping_mul(0x01000193); // FNV-1a prime
        i += 1;
    }
    hash
}

// ============================================================
// Route Categories
// ============================================================

/// A route's category, used for grouping in introspection/docs (e.g. a
/// `/routes` debug endpoint, or CLI tooling listing what a server supports).
/// Core categories are named variants for nice `Display`; a third-party
/// crate that doesn't fit an existing category uses `Other`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteCategory {
    Basic,
    PubSub,
    Kv,
    File,
    Time,
    Health,
    Query,
    Crypto,
    Auth,
    Terminal,
    /// A category defined by a third-party crate — we can't add a variant
    /// here for every crate that might ever extend this server.
    Other(&'static str),
}

impl RouteCategory {
    pub fn name(self) -> &'static str {
        match self {
            Self::Basic => "Basic",
            Self::PubSub => "PubSub",
            Self::Kv => "KV Store",
            Self::File => "File Operations",
            Self::Time => "Time",
            Self::Health => "Health",
            Self::Query => "Query",
            Self::Crypto => "Crypto",
            Self::Auth => "Auth",
            Self::Terminal => "Terminal",
            Self::Other(name) => name,
        }
    }
}

// ============================================================
// Priority
// ============================================================

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Priority {
    Background,
    Low,
    #[default]
    Normal,
    High,
    Critical,
}

impl Priority {
    pub fn as_i32(self) -> i32 {
        match self {
            Self::Background => -10,
            Self::Low => -5,
            Self::Normal => 0,
            Self::High => 5,
            Self::Critical => 10,
        }
    }
}

// ============================================================
// Route Registry — metadata for introspection, NOT dispatch
// ============================================================
//
// This registry is descriptive only (for a `/routes` debug endpoint, CLI
// docs, etc). It has no connection to actual request handling — that's
// `quicburn_server::route_map::RouteTable`, which holds real handlers. They
// stay separate so this crate doesn't need to know about `AppState`,
// `Frame`, or anything server-specific.
//
// There's deliberately no global/static instance anymore. A `OnceLock`
// singleton can only be populated once, in whatever order crates happen to
// touch it first — which breaks the moment two crates both try to register
// routes into it. Instead, whoever assembles the server builds one
// explicitly, the same way `build_routes()` assembles the `RouteTable`.

#[derive(Debug, Clone)]
pub struct RouteInfo {
    pub id: u32,
    pub name: &'static str,
    pub category: RouteCategory,
    pub priority: Priority,
    pub requires_auth: bool,
    pub description: Option<&'static str>,
}

#[derive(Debug, Default, Clone)]
pub struct RouteRegistry {
    routes: HashMap<u32, RouteInfo>,
}

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

    /// Register a route's metadata. Panics on id collision — same
    /// fail-fast-at-startup philosophy as `RouteTable::register` in
    /// quicburn-server: silently shadowing one route's docs with another's
    /// is worse than a loud panic at boot.
    pub fn register(
        &mut self,
        name: &'static str,
        category: RouteCategory,
        priority: Priority,
        requires_auth: bool,
        description: Option<&'static str>,
    ) {
        let id: u32 = route_id(name);
        if let Some(existing) = self.routes.get(&id) {
            panic!(
                "route id collision in registry: \"{}\" and \"{}\" both hash to byte {} \
                 — rename one of them",
                existing.name, name, id
            );
        }
        self.routes.insert(
            id,
            RouteInfo { id, name, category, priority, requires_auth, description },
        );
    }

    pub fn get(&self, id: u32) -> Option<&RouteInfo> {
        self.routes.get(&id)
    }

    pub fn get_by_name(&self, name: &str) -> Option<&RouteInfo> {
        self.routes.get(&route_id(name))
    }

    pub fn iter(&self) -> impl Iterator<Item = &RouteInfo> {
        self.routes.values()
    }

    pub fn by_category(&self, category: RouteCategory) -> Vec<&RouteInfo> {
        self.routes
            .values()
            .filter(|info| info.category == category)
            .collect()
    }

    pub fn len(&self) -> usize {
        self.routes.len()
    }

    pub fn is_empty(&self) -> bool {
        self.routes.is_empty()
    }
}

// ============================================================
// Core route metadata (optional convenience)
// ============================================================

/// Metadata for the routes this crate ships out of the box. Opt-in — call
/// this yourself if you want built-ins to show up in your registry, same as
/// you call each `register_X_routes` yourself in `build_routes()`.
pub fn register_core_routes(registry: &mut RouteRegistry) {
    registry.register(
        "echo",
        RouteCategory::Basic,
        Priority::Normal,
        false,
        Some("Echo back the request"),
    );
    registry.register(
        "uppercase",
        RouteCategory::Basic,
        Priority::Normal,
        false,
        Some("Convert text to uppercase"),
    );
    registry.register(
        "reverse",
        RouteCategory::Basic,
        Priority::Normal,
        false,
        Some("Reverse the input string"),
    );

    registry.register(
        "publish",
        RouteCategory::PubSub,
        Priority::Normal,
        true,
        Some("Publish a message to a topic"),
    );
    registry.register(
        "subscribe",
        RouteCategory::PubSub,
        Priority::Normal,
        true,
        Some("Subscribe to a topic"),
    );

    registry.register(
        "kv_set",
        RouteCategory::Kv,
        Priority::Normal,
        true,
        Some("Set a key-value pair"),
    );
    registry.register(
        "kv_get",
        RouteCategory::Kv,
        Priority::Normal,
        true,
        Some("Get a value by key"),
    );
    registry.register(
        "kv_delete",
        RouteCategory::Kv,
        Priority::Normal,
        true,
        Some("Delete a key-value pair"),
    );

    registry.register(
        "file_upload",
        RouteCategory::File,
        Priority::Low,
        false,
        Some("Upload a file"),
    );
    registry.register(
        "file_download",
        RouteCategory::File,
        Priority::Low,
        false,
        Some("Download a file"),
    );
    registry.register(
        "file_list",
        RouteCategory::File,
        Priority::Low,
        false,
        Some("List files in directory"),
    );
    registry.register(
        "file_delete",
        RouteCategory::File,
        Priority::Low,
        false,
        Some("Delete a file"),
    );
    registry.register(
        "file_info",
        RouteCategory::File,
        Priority::Low,
        false,
        Some("Get file metadata"),
    );

    registry.register(
        "time",
        RouteCategory::Time,
        Priority::Normal,
        false,
        Some("Get current time"),
    );
    registry.register(
        "timestamp",
        RouteCategory::Time,
        Priority::Normal,
        false,
        Some("Get current timestamp"),
    );

    registry.register(
        "health",
        RouteCategory::Health,
        Priority::High,
        false,
        Some("Health check"),
    );
    registry.register(
        "status",
        RouteCategory::Health,
        Priority::High,
        false,
        Some("Detailed status"),
    );

    registry.register(
        "query_json",
        RouteCategory::Query,
        Priority::Normal,
        true,
        Some("Execute a JSON query"),
    );

    registry.register(
        "query_filtered",
        RouteCategory::Query,
        Priority::Normal,
        true,
        Some("Execute a filtered query"),
    );

    registry.register(
        "query",
        RouteCategory::Query,
        Priority::Normal,
        true,
        Some("Execute a query"),
    );

    registry.register(
        "hash",
        RouteCategory::Crypto,
        Priority::Normal,
        true,
        Some("Hash a value"),
    );
    registry.register(
        "hmac",
        RouteCategory::Crypto,
        Priority::Normal,
        true,
        Some("HMAC a value"),
    );

    registry.register(
        "auth",
        RouteCategory::Auth,
        Priority::Normal,
        false,
        Some("Authenticate a session"),
    );
    registry.register(
        "whoami",
        RouteCategory::Auth,
        Priority::Normal,
        true,
        Some("Get current user info"),
    );
    registry.register(
        "logout",
        RouteCategory::Auth,
        Priority::Normal,
        true,
        Some("Log out and invalidate the current session"),
    );
    registry.register(
        "refresh",
        RouteCategory::Auth,
        Priority::Normal,
        true,
        Some("Refresh the current session's expiry"),
    );

    registry.register(
        "admin_stats",
        RouteCategory::Auth,
        Priority::Normal,
        true,
        Some("Get admin statistics"),
    );

    registry.register(
        "term_attach",
        RouteCategory::Terminal,
        Priority::Normal,
        true,
        Some("Attach to a terminal session"),
    );
    registry.register(
        "term_resize",
        RouteCategory::Terminal,
        Priority::Normal,
        true,
        Some("Resize terminal window"),
    );
}