fastapi_router/registry.rs
1//! Global route registry (link-section based).
2//!
3//! This provides a compile-time registry for routes generated by the
4//! `#[get]`, `#[post]`, etc. macros. Each macro expansion emits a static
5//! registration entry placed into a special linker section.
6//!
7//! At runtime, [`registered_routes`] scans that section and constructs the
8//! full list of routes by calling each registered constructor.
9
10use crate::Route;
11
12/// A route registration entry placed in a linker section.
13#[repr(C)]
14pub struct RouteRegistration {
15 constructor: Option<fn() -> Route>,
16}
17
18impl RouteRegistration {
19 /// Create a new registration entry for a route constructor.
20 #[must_use]
21 pub const fn new(constructor: fn() -> Route) -> Self {
22 Self {
23 constructor: Some(constructor),
24 }
25 }
26
27 /// Create an empty registration (used as a section anchor).
28 #[must_use]
29 pub const fn empty() -> Self {
30 Self { constructor: None }
31 }
32}
33
34/// Return all routes registered via the link-section table.
35#[must_use]
36#[allow(unsafe_code)]
37pub fn registered_routes() -> Vec<Route> {
38 // SAFETY: `registrations()` returns a valid slice of RouteRegistration entries
39 // from the linker section. The entries are placed there by route macros during
40 // compilation and are valid for the 'static lifetime. See `registrations()` for
41 // the detailed safety invariants.
42 let regs = unsafe { registrations() };
43 regs.iter()
44 .filter_map(|reg| reg.constructor)
45 .map(|ctor| ctor())
46 .collect()
47}
48
49#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
50#[allow(unsafe_code)]
51/// # Safety
52///
53/// This function must only be called when the following invariants hold:
54///
55/// 1. The linker has placed all `RouteRegistration` statics with
56/// `#[link_section = "fastapi_routes"]` into a contiguous memory region.
57/// 2. The `__start_fastapi_routes` and `__stop_fastapi_routes` symbols
58/// correctly bound this region (guaranteed by the linker for ELF targets).
59/// 3. All entries in the section are valid `RouteRegistration` structs
60/// (guaranteed by the route macros that create them).
61/// 4. `RouteRegistration` is `#[repr(C)]`, ensuring consistent memory layout.
62unsafe fn registrations() -> &'static [RouteRegistration] {
63 // SAFETY: These extern statics are provided by the linker and mark the start
64 // and end of the `fastapi_routes` section. This is a standard ELF linker feature.
65 unsafe extern "C" {
66 static __start_fastapi_routes: u8;
67 static __stop_fastapi_routes: u8;
68 }
69
70 // SAFETY: We cast the section bounds to RouteRegistration pointers.
71 // This is valid because:
72 // - All entries in this section are RouteRegistration (placed by route macros)
73 // - RouteRegistration is #[repr(C)] ensuring predictable layout
74 // - The linker guarantees __start and __stop bound the section correctly
75 let start = unsafe { &__start_fastapi_routes as *const u8 as *const RouteRegistration };
76 let stop = unsafe { &__stop_fastapi_routes as *const u8 as *const RouteRegistration };
77 let count = (stop as usize - start as usize) / std::mem::size_of::<RouteRegistration>();
78
79 // SAFETY: We create a slice from the contiguous section memory.
80 // - `start` points to valid RouteRegistration data (or is equal to stop if empty)
81 // - `count` is calculated from the section bounds, so we never read past the end
82 // - The data is 'static because it's in a linker section (program lifetime)
83 unsafe { std::slice::from_raw_parts(start, count) }
84}
85
86#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd")))]
87#[allow(unsafe_code)]
88/// # Safety
89///
90/// This is the fallback implementation for platforms without ELF linker section
91/// support. It returns an empty slice, which is always safe.
92///
93/// On unsupported platforms, route auto-discovery is disabled and routes must
94/// be registered manually.
95unsafe fn registrations() -> &'static [RouteRegistration] {
96 // SAFETY: An empty slice is always valid. No memory is accessed.
97 &[]
98}
99
100/// Anchor entry that ensures the `fastapi_routes` linker section exists.
101///
102/// This empty registration is placed in the section to guarantee it exists
103/// even when no routes are registered via macros. Without this, the linker
104/// might not create the section at all, and the `__start_fastapi_routes` and
105/// `__stop_fastapi_routes` symbols would be undefined.
106///
107/// # Safety
108///
109/// The `unsafe(link_section)` attribute places this static in a custom linker
110/// section. This is safe because:
111/// - `RouteRegistration` is `#[repr(C)]` with predictable layout
112/// - This is an empty registration (constructor is None), so it won't affect route discovery
113/// - The section is only read by `registrations()`, which handles all entries uniformly
114#[used]
115#[allow(unsafe_code)]
116#[cfg_attr(
117 any(target_os = "linux", target_os = "android", target_os = "freebsd"),
118 unsafe(link_section = "fastapi_routes")
119)]
120static __FASTAPI_ROUTE_REGISTRY_ANCHOR: RouteRegistration = RouteRegistration::empty();