Skip to main content

product_os_router/
lib.rs

1//! # Product OS Router
2//!
3//! A fully-featured HTTP router built on top of [Axum](https://github.com/tokio-rs/axum) and [Tower](https://github.com/tower-rs/tower),
4//! providing convenient helper methods for creating HTTP, HTTPS, WebSocket, and Server-Sent Events (SSE) servers.
5//!
6//! ## Features
7//!
8//! - **Easy Routing**: Simple, chainable API for defining routes
9//! - **HTTP Methods**: Support for GET, POST, PUT, PATCH, DELETE, and more
10//! - **State Management**: Both stateful and stateless routing options
11//! - **WebSockets**: Built-in WebSocket support (with `ws` feature)
12//! - **Server-Sent Events**: SSE support (with `sse` feature)
13//! - **CORS**: Cross-Origin Resource Sharing support (with `cors` feature)
14//! - **Middleware**: Custom middleware support (with `middleware` feature)
15//! - **Default Headers**: Apply default headers to all responses
16//! - **Protocol Upgrade**: HTTP to HTTPS automatic redirection
17//! - **Path Parameters**: Express-style path parameter syntax (`:param`, `*wildcard`)
18//! - **no_std Support**: Can be used in no_std environments with alloc
19//!
20//! ## Quick Start
21//!
22//! ```rust
23//! use product_os_router::{ProductOSRouter, IntoResponse};
24//!
25//! async fn hello_world() -> &'static str {
26//!     "Hello, World!"
27//! }
28//!
29//! # async fn example() {
30//! let mut router = ProductOSRouter::new();
31//! router.add_get_no_state("/", hello_world);
32//!
33//! let app = router.get_router();
34//! // Use app with your server (e.g., axum::Server)
35//! # }
36//! ```
37//!
38//! ## Path Parameter Syntax
39//!
40//! The router automatically converts Express-style path parameters to Axum format:
41//! - `:param` becomes `{param}` (single segment)
42//! - `*wildcard` becomes `{wildcard}` (catch-all)
43//!
44//! ```rust
45//! # use product_os_router::{ProductOSRouter, Path};
46//! async fn user_handler(Path(id): Path<u32>) -> String {
47//!     format!("User ID: {}", id)
48//! }
49//!
50//! # async fn example() {
51//! let mut router = ProductOSRouter::new();
52//! router.add_get_no_state("/users/:id", user_handler);  // Converted to /users/{id}
53//! # }
54//! ```
55//!
56//! ## With State
57//!
58//! ```rust
59//! # use product_os_router::{ProductOSRouter, State};
60//! #[derive(Clone)]
61//! struct AppState {
62//!     counter: u32,
63//! }
64//!
65//! async fn handler(State(state): State<AppState>) -> String {
66//!     format!("Counter: {}", state.counter)
67//! }
68//!
69//! # async fn example() {
70//! let state = AppState { counter: 0 };
71//! let mut router = ProductOSRouter::new_with_state(state);
72//! router.add_get("/", handler);
73//! # }
74//! ```
75//!
76//! ## Feature Flags
77//!
78//! - `default`: Includes `framework_axum` and `std` features
79//! - `framework_axum`: Axum framework (existing, stable)
80//! - `framework_flow`: Flow framework (new, high-performance)
81//! - `std`: Standard library support
82//! - `core`: Core routing functionality
83//! - `ws`: WebSocket support
84//! - `sse`: Server-Sent Events support
85//! - `cors`: CORS middleware support
86//! - `sessions`: Session management support
87//! - `middleware`: Custom middleware traits
88//! - `debug`: Debug handler macros
89
90#![no_std]
91extern crate no_std_compat as std;
92
93use std::prelude::v1::*;
94
95// ============================================================================
96// Compile-Time Feature Validation
97// ============================================================================
98
99// Ensure at least one framework is enabled
100#[cfg(not(any(feature = "framework_axum", feature = "framework_flow")))]
101compile_error!(
102    "At least one framework feature must be enabled: 'framework_axum' or 'framework_flow'"
103);
104
105// Prevent using both frameworks simultaneously
106#[cfg(all(feature = "framework_axum", feature = "framework_flow"))]
107compile_error!(
108    "Both 'framework_axum' and 'framework_flow' features cannot be enabled simultaneously. \
109     \n\nChoose ONE framework:\
110     \n  1. Axum (default):  --features framework_axum\
111     \n  2. Flow (new):      --no-default-features --features framework_flow,std"
112);
113
114// ============================================================================
115// Module Imports
116// ============================================================================
117
118// Conditional module imports based on feature flags
119#[cfg(feature = "framework_axum")]
120mod axum_impl;
121
122#[cfg(feature = "framework_flow")]
123pub mod flow_impl;
124
125// BTreeMap needed by both frameworks
126#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
127use std::collections::BTreeMap;
128
129#[cfg(any(feature = "framework_axum", feature = "core"))]
130pub use product_os_http::{
131    header::{HeaderMap, HeaderName, HeaderValue},
132    request::Parts as RequestParts,
133    response::Parts as ResponseParts,
134    Request, Response, StatusCode,
135};
136
137#[cfg(feature = "core")]
138pub use product_os_http_body::{BodyBytes, Bytes};
139
140#[cfg(feature = "middleware")]
141pub use product_os_http_body::*;
142
143#[cfg(feature = "framework_axum")]
144pub use axum::{
145    body::{Body, HttpBody},
146    extract::{
147        ws::{Message, WebSocket, WebSocketUpgrade},
148        ConnectInfo, FromRef, FromRequest, FromRequestParts, Path, Query, State,
149    },
150    handler::Handler,
151    http::Uri,
152    middleware::{from_fn, from_fn_with_state, Next},
153    response::{IntoResponse, Redirect},
154    routing::{any, delete, get, head, options, patch, post, put, trace, MethodRouter},
155    BoxError, Form, Json, Router,
156};
157
158#[cfg(all(feature = "sse", feature = "framework_axum"))]
159pub use axum::response::sse::{Event, Sse};
160
161#[cfg(feature = "framework_axum")]
162pub use crate::extractors::RequestMethod;
163
164// When only Flow is enabled (without axum compat), re-export Flow-native types.
165#[cfg(all(
166    feature = "framework_flow",
167    not(feature = "framework_axum"),
168    not(feature = "flow_axum_compat")
169))]
170pub use flow_impl::core::body::Body;
171#[cfg(all(
172    feature = "framework_flow",
173    not(feature = "framework_axum"),
174    not(feature = "flow_axum_compat")
175))]
176pub use flow_impl::core::Handler;
177#[cfg(all(
178    feature = "framework_flow",
179    not(feature = "framework_axum"),
180    not(feature = "flow_axum_compat")
181))]
182pub use flow_impl::core::{FromRequest, FromRequestParts, IntoResponse};
183#[cfg(all(
184    feature = "framework_flow",
185    not(feature = "framework_axum"),
186    not(feature = "flow_axum_compat")
187))]
188pub use flow_impl::extractors::form::StreamingForm as Form;
189#[cfg(all(
190    feature = "framework_flow",
191    not(feature = "framework_axum"),
192    not(feature = "flow_axum_compat")
193))]
194pub use flow_impl::extractors::json::StreamingJson as Json;
195#[cfg(all(
196    feature = "framework_flow",
197    not(feature = "framework_axum"),
198    not(feature = "flow_axum_compat")
199))]
200pub use flow_impl::extractors::path::Path;
201#[cfg(all(
202    feature = "framework_flow",
203    not(feature = "framework_axum"),
204    not(feature = "flow_axum_compat")
205))]
206pub use flow_impl::extractors::query::Query;
207#[cfg(all(
208    feature = "framework_flow",
209    not(feature = "framework_axum"),
210    not(feature = "flow_axum_compat")
211))]
212pub use flow_impl::extractors::state::State;
213#[cfg(all(
214    feature = "framework_flow",
215    not(feature = "framework_axum"),
216    not(feature = "flow_axum_compat")
217))]
218pub use flow_impl::router::NextService;
219#[cfg(all(
220    feature = "framework_flow",
221    not(feature = "framework_axum"),
222    not(feature = "flow_axum_compat")
223))]
224pub use product_os_http::uri::Uri;
225#[cfg(all(
226    feature = "framework_flow",
227    not(feature = "framework_axum"),
228    not(feature = "flow_axum_compat")
229))]
230pub use product_os_http_body::Body as HttpBody;
231
232#[cfg(all(
233    feature = "framework_flow",
234    not(feature = "framework_axum"),
235    not(feature = "flow_axum_compat")
236))]
237pub type BoxError = std::boxed::Box<dyn core::error::Error + Send + Sync>;
238
239#[cfg(all(
240    feature = "sse",
241    feature = "framework_flow",
242    not(feature = "framework_axum"),
243    not(feature = "flow_axum_compat")
244))]
245pub use flow_impl::sse::{Event, KeepAlive, Sse};
246
247#[cfg(all(
248    feature = "ws",
249    feature = "framework_flow",
250    not(feature = "framework_axum"),
251    not(feature = "flow_axum_compat")
252))]
253pub use flow_impl::ws::{Message, WebSocket};
254
255#[cfg(any(feature = "framework_axum", feature = "core"))]
256pub use product_os_http::uri::Scheme;
257
258// When flow_axum_compat is enabled, re-export from Axum for full compatibility.
259// Path, Query, and State use Flow-native extractors since the Flow router
260// populates extensions differently than Axum's internal machinery.
261#[cfg(all(feature = "flow_axum_compat", not(feature = "framework_axum")))]
262pub use axum::{
263    body::{Body, HttpBody},
264    extract::{
265        ws::{Message, WebSocket, WebSocketUpgrade},
266        ConnectInfo, FromRef, FromRequest, FromRequestParts, State,
267    },
268    http::Uri,
269    response::{IntoResponse, Redirect},
270    BoxError, Form, Json,
271};
272
273// Path and Query use Flow-native extractors under flow_axum_compat because
274// the Flow router populates PathParams in extensions, not Axum's internal UrlParams.
275#[cfg(all(feature = "flow_axum_compat", not(feature = "framework_axum")))]
276pub use flow_impl::extractors::path::Path;
277#[cfg(all(feature = "flow_axum_compat", not(feature = "framework_axum")))]
278pub use flow_impl::extractors::query::Query;
279
280#[cfg(all(
281    feature = "sse",
282    feature = "flow_axum_compat",
283    not(feature = "framework_axum")
284))]
285pub use axum::response::sse::{Event, Sse};
286
287#[cfg(all(feature = "flow_axum_compat", not(feature = "framework_axum")))]
288pub use flow_impl::core::Handler;
289
290#[cfg(all(feature = "flow_axum_compat", not(feature = "framework_axum")))]
291pub use flow_impl::router::NextService;
292
293#[cfg(all(feature = "flow_axum_compat", not(feature = "framework_axum")))]
294pub use axum::routing::MethodRouter;
295
296#[cfg(all(
297    feature = "debug",
298    any(feature = "framework_axum", feature = "flow_axum_compat")
299))]
300pub use axum_macros::debug_handler;
301
302#[cfg(feature = "core")]
303pub use tower::{
304    make::future::SharedFuture, make::AsService, make::IntoService, make::MakeConnection,
305    make::Shared, util::service_fn, util::ServiceFn, Layer, MakeService, Service, ServiceBuilder,
306    ServiceExt,
307};
308
309#[cfg(feature = "core")]
310pub use product_os_request::Method;
311
312#[cfg(feature = "middleware")]
313pub mod middleware;
314
315#[cfg(feature = "framework_axum")]
316mod default_headers;
317#[cfg(feature = "core")]
318pub mod dual_protocol;
319#[cfg(feature = "framework_axum")]
320mod extractors;
321#[cfg(feature = "framework_axum")]
322mod service_wrapper;
323
324#[cfg(feature = "framework_axum")]
325pub use crate::default_headers::DefaultHeadersLayer;
326#[cfg(feature = "core")]
327pub use crate::dual_protocol::{Protocol, UpgradeHttpLayer};
328
329#[cfg(feature = "middleware")]
330pub use crate::middleware::*;
331
332#[cfg(feature = "framework_flow")]
333pub use flow_impl::extractors::headers::Headers;
334
335#[cfg(all(feature = "framework_axum", not(feature = "framework_flow")))]
336mod axum_request_id;
337#[cfg(all(feature = "framework_axum", not(feature = "framework_flow")))]
338mod axum_trace_context;
339
340#[cfg(all(feature = "framework_axum", not(feature = "framework_flow")))]
341pub use axum_request_id::RequestId;
342#[cfg(all(feature = "framework_axum", not(feature = "framework_flow")))]
343pub use axum_trace_context::TraceContext;
344
345#[cfg(feature = "framework_flow")]
346pub use flow_impl::extractors::trace_context::TraceContext;
347
348#[cfg(all(
349    feature = "framework_flow",
350    not(feature = "framework_axum"),
351    not(feature = "flow_axum_compat")
352))]
353pub use flow_impl::extractors::context::{ClientAddr, ConnectInfo, RequestId};
354
355#[cfg(all(feature = "framework_flow", feature = "flow_axum_compat"))]
356pub use flow_impl::extractors::context::{ClientAddr, RequestId};
357
358/// Error types for router operations
359///
360/// Provides semantic error types for different HTTP error scenarios.
361/// Each variant maps to an appropriate HTTP status code.
362///
363/// # Examples
364///
365/// ```rust
366/// use product_os_router::{ProductOSRouterError, StatusCode};
367///
368/// let error = ProductOSRouterError::Authentication("Invalid token".to_string());
369/// let response = ProductOSRouterError::handle_error(&error);
370/// assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
371/// ```
372use core::fmt;
373
374#[derive(Debug)]
375pub enum ProductOSRouterError {
376    /// Invalid header error (400 Bad Request)
377    Headers(String),
378    /// Invalid query parameter error (400 Bad Request)
379    Query(String),
380    /// Invalid request body error (400 Bad Request)
381    Body(String),
382    /// Authentication failure (401 Unauthorized)
383    Authentication(String),
384    /// Authorization failure (403 Forbidden)
385    Authorization(String),
386    /// Processing timeout or failure (504 Gateway Timeout)
387    Process(String),
388    /// Service unavailable (503 Service Unavailable)
389    Unavailable(String),
390}
391
392impl fmt::Display for ProductOSRouterError {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        match self {
395            ProductOSRouterError::Headers(m) => write!(f, "header error: {m}"),
396            ProductOSRouterError::Query(m) => write!(f, "query error: {m}"),
397            ProductOSRouterError::Body(m) => write!(f, "body error: {m}"),
398            ProductOSRouterError::Authentication(m) => write!(f, "authentication error: {m}"),
399            ProductOSRouterError::Authorization(m) => write!(f, "authorization error: {m}"),
400            ProductOSRouterError::Process(m) => write!(f, "process error: {m}"),
401            ProductOSRouterError::Unavailable(m) => write!(f, "unavailable: {m}"),
402        }
403    }
404}
405
406#[cfg(feature = "std")]
407extern crate std as _real_std;
408
409#[cfg(feature = "std")]
410impl _real_std::error::Error for ProductOSRouterError {}
411
412impl ProductOSRouterError {
413    /// Extract the error message from any variant.
414    pub fn message(&self) -> &str {
415        match self {
416            ProductOSRouterError::Headers(m) => m,
417            ProductOSRouterError::Query(m) => m,
418            ProductOSRouterError::Body(m) => m,
419            ProductOSRouterError::Authentication(m) => m,
420            ProductOSRouterError::Authorization(m) => m,
421            ProductOSRouterError::Process(m) => m,
422            ProductOSRouterError::Unavailable(m) => m,
423        }
424    }
425}
426
427#[cfg(feature = "core")]
428impl ProductOSRouterError {
429    /// Return the appropriate HTTP status code for this error variant.
430    pub fn status_code(&self) -> StatusCode {
431        match self {
432            ProductOSRouterError::Headers(_) => StatusCode::BAD_REQUEST,
433            ProductOSRouterError::Query(_) => StatusCode::BAD_REQUEST,
434            ProductOSRouterError::Body(_) => StatusCode::BAD_REQUEST,
435            ProductOSRouterError::Authentication(_) => StatusCode::UNAUTHORIZED,
436            ProductOSRouterError::Authorization(_) => StatusCode::FORBIDDEN,
437            ProductOSRouterError::Process(_) => StatusCode::GATEWAY_TIMEOUT,
438            ProductOSRouterError::Unavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
439        }
440    }
441}
442
443#[cfg(feature = "std")]
444impl ProductOSRouterError {
445    /// Build a JSON error response body from a message and status code.
446    ///
447    /// Quotes in the error message are replaced with single quotes to
448    /// ensure valid JSON output.
449    fn build_error_response(message: &str, status_code: &StatusCode) -> Response<Body> {
450        let safe_message = message.replace("\"", "'");
451        let status_code_u16 = status_code.as_u16();
452
453        Response::builder()
454            .status(status_code_u16)
455            .header("content-type", "application/json")
456            .body(Body::from(format!("{{\"error\": \"{safe_message}\"}}")))
457            .unwrap_or_else(|_| {
458                Response::builder()
459                    .status(StatusCode::INTERNAL_SERVER_ERROR.as_u16())
460                    .body(Body::from("{\"error\": \"internal error\"}"))
461                    .expect("fallback response must be valid")
462            })
463    }
464
465    /// Create an HTTP response from an error with a specific status code.
466    ///
467    /// Consumes the error and converts it into a JSON response with the error
468    /// message. Quotes in the error message are replaced with single quotes.
469    ///
470    /// # Arguments
471    ///
472    /// * `error` - The error to convert (consumed)
473    /// * `status_code` - The HTTP status code to use
474    ///
475    /// # Returns
476    ///
477    /// An HTTP response with JSON body containing the error message
478    ///
479    /// # Examples
480    ///
481    /// ```rust
482    /// use product_os_router::{ProductOSRouterError, StatusCode};
483    ///
484    /// let error = ProductOSRouterError::Headers("Invalid header".to_string());
485    /// let response = ProductOSRouterError::error_response(error, &StatusCode::BAD_REQUEST);
486    /// ```
487    pub fn error_response(error: ProductOSRouterError, status_code: &StatusCode) -> Response<Body> {
488        Self::build_error_response(error.message(), status_code)
489    }
490
491    /// Handle an error and return an appropriate HTTP response.
492    ///
493    /// Automatically determines the appropriate HTTP status code based on the
494    /// error variant and returns a JSON response with the error message.
495    ///
496    /// # Arguments
497    ///
498    /// * `error` - The error to handle
499    ///
500    /// # Returns
501    ///
502    /// An HTTP response with the appropriate status code and error message
503    ///
504    /// # Examples
505    ///
506    /// ```rust
507    /// use product_os_router::ProductOSRouterError;
508    ///
509    /// let error = ProductOSRouterError::Authentication("Login required".to_string());
510    /// let response = ProductOSRouterError::handle_error(&error);
511    /// ```
512    pub fn handle_error(error: &ProductOSRouterError) -> Response<Body> {
513        Self::build_error_response(error.message(), &error.status_code())
514    }
515}
516
517/// Main router enum for Product OS Router
518///
519/// Dispatches to either Axum or Flow implementation based on feature flags.
520/// Provides a high-level API for defining routes, handlers, and middleware.
521/// Supports both stateful and stateless routing.
522///
523/// # Type Parameters
524///
525/// * `S` - The state type (defaults to `()` for stateless routing)
526///
527/// # Examples
528///
529/// ## Stateless Router
530///
531/// ```rust
532/// use product_os_router::ProductOSRouter;
533///
534/// async fn handler() -> &'static str {
535///     "Hello!"
536/// }
537///
538/// let mut router = ProductOSRouter::new();
539/// router.add_get_no_state("/", handler);
540/// ```
541///
542/// ## Stateful Router
543///
544/// ```rust
545/// use product_os_router::{ProductOSRouter, State};
546///
547/// #[derive(Clone)]
548/// struct AppState {
549///     message: String,
550/// }
551///
552/// async fn handler(State(state): State<AppState>) -> String {
553///     state.message
554/// }
555///
556/// let state = AppState { message: "Hello!".to_string() };
557/// let mut router = ProductOSRouter::new_with_state(state);
558/// router.add_get("/", handler);
559/// ```
560#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
561pub enum ProductOSRouter<S = ()> {
562    #[cfg(feature = "framework_axum")]
563    Axum(axum_impl::AxumRouter<S>),
564
565    #[cfg(feature = "framework_flow")]
566    Flow(flow_impl::Router<S>),
567}
568
569#[cfg(feature = "framework_axum")]
570impl<S: Clone + Send + Sync + 'static> Clone for ProductOSRouter<S> {
571    fn clone(&self) -> Self {
572        match self {
573            Self::Axum(r) => Self::Axum(r.clone()),
574        }
575    }
576}
577
578#[cfg(feature = "framework_flow")]
579impl<S: Clone + Send + Sync + 'static> Clone for ProductOSRouter<S> {
580    fn clone(&self) -> Self {
581        match self {
582            Self::Flow(r) => Self::Flow(r.clone()),
583        }
584    }
585}
586
587#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
588impl<S: Clone + Send + Sync + 'static> ProductOSRouter<S> {
589    /// Create a new router with the given shared state.
590    pub fn new_with_state(state: S) -> Self {
591        #[cfg(feature = "framework_axum")]
592        return Self::Axum(axum_impl::AxumRouter::new_with_state(state));
593
594        #[cfg(feature = "framework_flow")]
595        return Self::Flow(flow_impl::Router::new_with_state(state));
596    }
597}
598
599#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
600impl ProductOSRouter<()> {
601    /// Create a new stateless router
602    ///
603    /// Creates a router without any shared state. Use [`new_with_state`](Self::new_with_state)
604    /// if you need to share state between handlers.
605    ///
606    /// # Examples
607    ///
608    /// ```rust
609    /// use product_os_router::ProductOSRouter;
610    ///
611    /// let router = ProductOSRouter::new();
612    /// ```
613    pub fn new() -> Self {
614        ProductOSRouter::new_with_state(())
615    }
616}
617
618#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
619impl Default for ProductOSRouter<()> {
620    fn default() -> Self {
621        Self::new()
622    }
623}
624
625#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
626impl ProductOSRouter<()> {
627    /// Convert path parameters from Express-style to Axum format
628    pub fn param_to_field(path: &str) -> String {
629        #[cfg(feature = "framework_axum")]
630        {
631            axum_impl::AxumRouter::<()>::param_to_field(path)
632        }
633        #[cfg(all(feature = "framework_flow", not(feature = "framework_axum")))]
634        {
635            flow_impl::Router::<()>::param_to_field(path)
636        }
637    }
638
639    /// Returns a 501 Not Implemented JSON response
640    pub fn not_implemented() -> Response<Body> {
641        Response::builder()
642            .status(StatusCode::NOT_IMPLEMENTED.as_u16())
643            .header("content-type", "application/json")
644            .body(Body::from("{}"))
645            .unwrap()
646    }
647
648    /// Returns a 404 Not Found JSON response
649    #[deprecated(
650        since = "0.0.41",
651        note = "Use not_implemented() which correctly returns 501, or build a 404 response explicitly"
652    )]
653    pub fn not_implemented_legacy() -> Response<Body> {
654        Response::builder()
655            .status(StatusCode::NOT_FOUND.as_u16())
656            .header("content-type", "application/json")
657            .body(Body::from("{}"))
658            .unwrap()
659    }
660
661    /// Add a GET route handler without state
662    pub fn add_get_no_state<H, T>(&mut self, path: &str, handler: H)
663    where
664        H: Handler<T, ()> + Send + Sync + 'static,
665        T: 'static,
666    {
667        match self {
668            #[cfg(feature = "framework_axum")]
669            Self::Axum(r) => r.add_get_no_state(path, handler),
670            #[cfg(feature = "framework_flow")]
671            Self::Flow(r) => r.add_get_no_state(path, handler),
672        }
673    }
674
675    /// Add a POST route handler without state
676    pub fn add_post_no_state<H, T>(&mut self, path: &str, handler: H)
677    where
678        H: Handler<T, ()> + Send + Sync + 'static,
679        T: 'static,
680    {
681        match self {
682            #[cfg(feature = "framework_axum")]
683            Self::Axum(r) => r.add_post_no_state(path, handler),
684            #[cfg(feature = "framework_flow")]
685            Self::Flow(r) => r.add_post_no_state(path, handler),
686        }
687    }
688
689    /// Add a handler for the given HTTP method without state
690    pub fn add_handler_no_state<H, T>(&mut self, path: &str, method: Method, handler: H)
691    where
692        H: Handler<T, ()> + Send + Sync + 'static,
693        T: 'static,
694    {
695        match self {
696            #[cfg(feature = "framework_axum")]
697            Self::Axum(r) => r.add_handler_no_state(path, method, handler),
698            #[cfg(feature = "framework_flow")]
699            Self::Flow(r) => r.add_handler_no_state(path, method, handler),
700        }
701    }
702
703    /// Add a default header without state
704    pub fn add_default_header_no_state(&mut self, header_name: &str, header_value: &str) {
705        match self {
706            #[cfg(feature = "framework_axum")]
707            Self::Axum(r) => r.add_default_header_no_state(header_name, header_value),
708            #[cfg(feature = "framework_flow")]
709            Self::Flow(r) => r.add_default_header_no_state(header_name, header_value),
710        }
711    }
712
713    /// Set a fallback handler without state
714    pub fn set_fallback_handler_no_state<H, T>(&mut self, handler: H)
715    where
716        H: Handler<T, ()> + Send + Sync + 'static,
717        T: 'static,
718    {
719        match self {
720            #[cfg(feature = "framework_axum")]
721            Self::Axum(r) => r.set_fallback_handler_no_state(handler),
722            #[cfg(feature = "framework_flow")]
723            Self::Flow(r) => r.set_fallback_handler_no_state(handler),
724        }
725    }
726
727    /// Add a CORS handler without state
728    #[cfg(feature = "cors")]
729    pub fn add_cors_handler_no_state<H, T>(&mut self, path: &str, method: Method, handler: H)
730    where
731        H: Handler<T, ()> + Send + Sync + 'static,
732        T: 'static,
733    {
734        match self {
735            #[cfg(feature = "framework_axum")]
736            Self::Axum(r) => r.add_cors_handler_no_state(path, method, handler),
737            #[cfg(feature = "framework_flow")]
738            Self::Flow(r) => r.add_cors_handler(path, method, handler),
739        }
740    }
741
742    /// Add an SSE handler without state
743    #[cfg(feature = "sse")]
744    pub fn add_sse_handler_no_state<H, T>(&mut self, path: &str, handler: H)
745    where
746        H: Handler<T, ()> + Send + Sync + 'static,
747        T: 'static,
748    {
749        match self {
750            #[cfg(feature = "framework_axum")]
751            Self::Axum(r) => r.add_sse_handler_no_state(path, handler),
752            #[cfg(feature = "framework_flow")]
753            Self::Flow(r) => r.add_sse_handler(path, handler),
754        }
755    }
756
757    /// Add a WebSocket handler without state (Axum / flow_axum_compat)
758    #[cfg(all(
759        feature = "ws",
760        any(feature = "framework_axum", feature = "flow_axum_compat")
761    ))]
762    pub fn add_ws_handler_no_state<H, T>(&mut self, path: &str, handler: H)
763    where
764        H: Handler<T, ()> + Send + Sync + 'static,
765        T: 'static,
766    {
767        match self {
768            #[cfg(feature = "framework_axum")]
769            Self::Axum(r) => r.add_ws_handler_no_state(path, handler),
770            #[cfg(feature = "framework_flow")]
771            Self::Flow(r) => r.add_ws_handler(path, handler),
772        }
773    }
774
775    #[cfg(all(
776        feature = "ws",
777        feature = "framework_flow",
778        not(feature = "framework_axum"),
779        not(feature = "flow_axum_compat")
780    ))]
781    pub fn add_ws_handler_no_state<F, Fut>(&mut self, path: &str, handler: F)
782    where
783        F: Fn(flow_impl::ws::WebSocket) -> Fut + Send + Sync + Clone + 'static,
784        Fut: std::future::Future<Output = ()> + Send + 'static,
785    {
786        match self {
787            Self::Flow(r) => r.add_ws_handler(path, handler),
788        }
789    }
790
791    /// Add multiple handlers without state
792    pub fn add_handlers_no_state<H, T>(&mut self, path: &str, handlers: BTreeMap<Method, H>)
793    where
794        H: Handler<T, ()> + Send + Sync + 'static,
795        T: 'static,
796    {
797        match self {
798            #[cfg(feature = "framework_axum")]
799            Self::Axum(r) => r.add_handlers_no_state(path, handlers),
800            #[cfg(feature = "framework_flow")]
801            Self::Flow(r) => r.add_handlers_no_state(path, handlers),
802        }
803    }
804
805    /// Add multiple CORS-enabled handlers without state
806    #[cfg(feature = "cors")]
807    pub fn add_cors_handlers_no_state<H, T>(&mut self, path: &str, handlers: BTreeMap<Method, H>)
808    where
809        H: Handler<T, ()> + Send + Sync + 'static,
810        T: 'static,
811    {
812        match self {
813            #[cfg(feature = "framework_axum")]
814            Self::Axum(r) => r.add_cors_handlers_no_state(path, handlers),
815            #[cfg(feature = "framework_flow")]
816            Self::Flow(r) => r.add_cors_handlers_no_state(path, handlers),
817        }
818    }
819
820    /// Add a CORS middleware without state
821    #[cfg(feature = "cors")]
822    pub fn add_cors_middleware_no_state(&mut self) {
823        match self {
824            #[cfg(feature = "framework_axum")]
825            Self::Axum(r) => r.add_cors_middleware_no_state(),
826            #[cfg(feature = "framework_flow")]
827            Self::Flow(r) => r.add_cors_middleware(),
828        }
829    }
830}
831
832#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
833impl<S> ProductOSRouter<S>
834where
835    S: Clone + Send + Sync + 'static,
836{
837    /// Return a clone of the router.
838    ///
839    /// For both Axum and Flow, returns `ProductOSRouter<S>`.
840    /// Use `into_axum_router()` when you need the raw `axum::Router`.
841    pub fn get_router(&self) -> ProductOSRouter<S> {
842        self.clone()
843    }
844
845    /// Extract the inner `axum::Router`, consuming the wrapper.
846    ///
847    /// This is needed when handing the router to axum-specific server functions
848    /// that expect a raw `axum::Router`.
849    #[cfg(feature = "framework_axum")]
850    pub fn into_axum_router(self) -> Router {
851        match self {
852            Self::Axum(r) => r.get_router(),
853        }
854    }
855
856    /// Returns a clone of the shared state stored in the router.
857    #[cfg(feature = "framework_flow")]
858    pub fn get_state(&self) -> S {
859        match self {
860            Self::Flow(r) => r.get_state(),
861        }
862    }
863
864    /// Add a GET route handler
865    pub fn add_get<H, T>(&mut self, path: &str, handler: H)
866    where
867        H: Handler<T, S> + Send + Sync + 'static,
868        T: 'static,
869    {
870        match self {
871            #[cfg(feature = "framework_axum")]
872            Self::Axum(r) => r.add_get(path, handler),
873            #[cfg(feature = "framework_flow")]
874            Self::Flow(r) => r.add_get(path, handler),
875        }
876    }
877
878    /// Add a POST route handler
879    pub fn add_post<H, T>(&mut self, path: &str, handler: H)
880    where
881        H: Handler<T, S> + Send + Sync + 'static,
882        T: 'static,
883    {
884        match self {
885            #[cfg(feature = "framework_axum")]
886            Self::Axum(r) => r.add_post(path, handler),
887            #[cfg(feature = "framework_flow")]
888            Self::Flow(r) => r.add_post(path, handler),
889        }
890    }
891
892    /// Add a handler for the given HTTP method and path
893    pub fn add_handler<H, T>(&mut self, path: &str, method: Method, handler: H)
894    where
895        H: Handler<T, S> + Send + Sync + 'static,
896        T: 'static,
897    {
898        match self {
899            #[cfg(feature = "framework_axum")]
900            Self::Axum(r) => r.add_handler(path, method, handler),
901            #[cfg(feature = "framework_flow")]
902            Self::Flow(r) => r.add_handler(path, method, handler),
903        }
904    }
905
906    /// Add a default header
907    pub fn add_default_header(&mut self, header_name: &str, header_value: &str) {
908        match self {
909            #[cfg(feature = "framework_axum")]
910            Self::Axum(r) => r.add_default_header(header_name, header_value),
911            #[cfg(feature = "framework_flow")]
912            Self::Flow(r) => r.add_default_header(header_name, header_value),
913        }
914    }
915
916    /// Add CORS middleware
917    #[cfg(feature = "cors")]
918    pub fn add_cors_middleware(&mut self) {
919        match self {
920            #[cfg(feature = "framework_axum")]
921            Self::Axum(r) => r.add_cors_middleware(),
922            #[cfg(feature = "framework_flow")]
923            Self::Flow(r) => r.add_cors_middleware(),
924        }
925    }
926
927    /// Add multiple HTTP method handlers at a single path
928    pub fn add_handlers<H, T>(&mut self, path: &str, handlers: BTreeMap<Method, H>)
929    where
930        H: Handler<T, S> + Send + Sync + 'static,
931        T: 'static,
932    {
933        match self {
934            #[cfg(feature = "framework_axum")]
935            Self::Axum(r) => r.add_handlers(path, handlers),
936            #[cfg(feature = "framework_flow")]
937            Self::Flow(r) => r.add_handlers(path, handlers),
938        }
939    }
940
941    /// Add CORS-enabled handlers for different methods on the same path
942    #[cfg(feature = "cors")]
943    pub fn add_cors_handlers<H, T>(&mut self, path: &str, handlers: BTreeMap<Method, H>)
944    where
945        H: Handler<T, S> + Send + Sync + 'static,
946        T: 'static,
947    {
948        match self {
949            #[cfg(feature = "framework_axum")]
950            Self::Axum(r) => r.add_cors_handlers(path, handlers),
951            #[cfg(feature = "framework_flow")]
952            Self::Flow(r) => r.add_cors_handlers(path, handlers),
953        }
954    }
955
956    /// Add a CORS handler for a specific HTTP method and path
957    #[cfg(feature = "cors")]
958    pub fn add_cors_handler<H, T>(&mut self, path: &str, method: Method, handler: H)
959    where
960        H: Handler<T, S> + Send + Sync + 'static,
961        T: 'static,
962    {
963        match self {
964            #[cfg(feature = "framework_axum")]
965            Self::Axum(r) => r.add_cors_handler(path, method, handler),
966            #[cfg(feature = "framework_flow")]
967            Self::Flow(r) => r.add_cors_handler(path, method, handler),
968        }
969    }
970
971    /// Add a WebSocket handler for the specified path (Axum / flow_axum_compat)
972    #[cfg(all(
973        feature = "ws",
974        any(feature = "framework_axum", feature = "flow_axum_compat")
975    ))]
976    pub fn add_ws_handler<H, T>(&mut self, path: &str, handler: H)
977    where
978        H: Handler<T, S> + Send + Sync + 'static,
979        T: 'static,
980    {
981        match self {
982            #[cfg(feature = "framework_axum")]
983            Self::Axum(r) => r.add_ws_handler(path, handler),
984            #[cfg(feature = "framework_flow")]
985            Self::Flow(r) => r.add_ws_handler(path, handler),
986        }
987    }
988
989    /// Add a native Flow WebSocket handler for the specified path.
990    ///
991    /// The handler receives a `WebSocket` after the HTTP upgrade has been
992    /// performed by the server layer.
993    #[cfg(all(
994        feature = "ws",
995        feature = "framework_flow",
996        not(feature = "framework_axum"),
997        not(feature = "flow_axum_compat")
998    ))]
999    pub fn add_ws_handler<F, Fut>(&mut self, path: &str, handler: F)
1000    where
1001        F: Fn(flow_impl::ws::WebSocket) -> Fut + Send + Sync + Clone + 'static,
1002        Fut: std::future::Future<Output = ()> + Send + 'static,
1003    {
1004        match self {
1005            Self::Flow(r) => r.add_ws_handler(path, handler),
1006        }
1007    }
1008
1009    /// Look up a native WebSocket handler for the given path.
1010    #[cfg(all(
1011        feature = "ws",
1012        feature = "framework_flow",
1013        not(feature = "framework_axum"),
1014        not(feature = "flow_axum_compat")
1015    ))]
1016    pub fn lookup_ws_handler(
1017        &mut self,
1018        path: &str,
1019    ) -> Option<flow_impl::router::WsHandlerCallback> {
1020        match self {
1021            Self::Flow(r) => r.lookup_ws_handler(path),
1022        }
1023    }
1024
1025    /// Add an SSE handler for the specified path
1026    #[cfg(feature = "sse")]
1027    pub fn add_sse_handler<H, T>(&mut self, path: &str, handler: H)
1028    where
1029        H: Handler<T, S> + Send + Sync + 'static,
1030        T: 'static,
1031    {
1032        match self {
1033            #[cfg(feature = "framework_axum")]
1034            Self::Axum(r) => r.add_sse_handler(path, handler),
1035            #[cfg(feature = "framework_flow")]
1036            Self::Flow(r) => r.add_sse_handler(path, handler),
1037        }
1038    }
1039
1040    /// Add a route with an Axum `MethodRouter`.
1041    #[cfg(feature = "framework_axum")]
1042    pub fn add_route(&mut self, path: &str, method_router: axum::routing::MethodRouter<S>)
1043    where
1044        S: Clone + Send + Sync + 'static,
1045    {
1046        match self {
1047            Self::Axum(r) => r.add_route(path, method_router),
1048        }
1049    }
1050
1051    /// Set the fallback service for unmatched routes (Axum `MethodRouter`).
1052    #[cfg(feature = "framework_axum")]
1053    pub fn set_fallback(&mut self, service_handler: axum::routing::MethodRouter<S>)
1054    where
1055        S: Clone + Send + Sync + 'static,
1056    {
1057        match self {
1058            Self::Axum(r) => r.set_fallback(service_handler),
1059        }
1060    }
1061
1062    /// Set the fallback handler for unmatched routes.
1063    pub fn set_fallback_handler<H, T>(&mut self, handler: H)
1064    where
1065        H: Handler<T, S>,
1066        T: 'static,
1067    {
1068        match self {
1069            #[cfg(feature = "framework_axum")]
1070            Self::Axum(r) => r.set_fallback_handler(handler),
1071            #[cfg(feature = "framework_flow")]
1072            Self::Flow(r) => r.set_fallback_handler(handler),
1073        }
1074    }
1075
1076    /// Add an Axum-compatible Tower middleware layer.
1077    #[cfg(feature = "framework_axum")]
1078    pub fn add_middleware<L, ResBody>(&mut self, middleware: L)
1079    where
1080        L: tower::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
1081        L::Service: tower::Service<Request<Body>, Response = Response<ResBody>>
1082            + Clone
1083            + Send
1084            + Sync
1085            + 'static,
1086        <L::Service as tower::Service<Request<Body>>>::Future: Send,
1087        <L::Service as tower::Service<Request<Body>>>::Error: Into<axum::BoxError>,
1088        ResBody: axum::body::HttpBody<Data = product_os_http_body::Bytes> + Send + 'static,
1089        ResBody::Error: Into<axum::BoxError>,
1090    {
1091        match self {
1092            Self::Axum(r) => r.add_middleware(middleware),
1093        }
1094    }
1095
1096    /// Add a Tower middleware layer for the Flow framework.
1097    ///
1098    /// Uses body-erasing to convert arbitrary `Response<ResBody>` into
1099    /// `Response<Body>`, enabling standard Tower layers (CSRF, compression, etc.)
1100    /// to be used with Flow's middleware pipeline.
1101    #[cfg(all(feature = "framework_flow", feature = "flow_axum_compat"))]
1102    pub fn add_middleware<L, ResBody>(&mut self, middleware: L)
1103    where
1104        L: tower::Layer<flow_impl::router::NextService<S>> + Clone + Send + Sync + 'static,
1105        L::Service: tower::Service<Request<Body>, Response = product_os_http::Response<ResBody>>
1106            + Clone
1107            + Send
1108            + Sync
1109            + 'static,
1110        <L::Service as tower::Service<Request<Body>>>::Future: Send + 'static,
1111        <L::Service as tower::Service<Request<Body>>>::Error: Into<axum::BoxError> + Send + 'static,
1112        ResBody: axum::body::HttpBody<Data = product_os_http_body::Bytes> + Send + 'static,
1113        ResBody::Error: Into<axum::BoxError>,
1114    {
1115        match self {
1116            Self::Flow(r) => r.add_middleware_erased(middleware),
1117        }
1118    }
1119
1120    /// Add a Tower middleware layer for pure Flow (without `flow_axum_compat`).
1121    ///
1122    /// The layer wraps `NextService<S>` and must produce
1123    /// `Response<Body>` with `Error = Infallible`.
1124    #[cfg(all(
1125        feature = "framework_flow",
1126        not(feature = "flow_axum_compat"),
1127        not(feature = "framework_axum")
1128    ))]
1129    pub fn add_middleware<L>(&mut self, middleware: L)
1130    where
1131        L: tower::Layer<flow_impl::router::NextService<S>> + Clone + Send + Sync + 'static,
1132        L::Service: tower::Service<
1133                Request<Body>,
1134                Response = flow_impl::core::body::Response,
1135                Error = core::convert::Infallible,
1136            > + Clone
1137            + Send
1138            + Sync
1139            + 'static,
1140        <L::Service as tower::Service<Request<Body>>>::Future: Send + 'static,
1141    {
1142        match self {
1143            Self::Flow(r) => r.add_middleware(middleware),
1144        }
1145    }
1146}
1147
1148#[cfg(any(feature = "framework_axum", feature = "framework_flow"))]
1149impl<S: Clone + Send + Sync + 'static> ProductOSRouter<S> {
1150    /// Inserts a value into request extensions for all subsequent handlers.
1151    pub fn add_extension<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
1152        #[cfg(feature = "framework_axum")]
1153        {
1154            match self {
1155                Self::Axum(r) => {
1156                    let router = r.take_router().layer(axum::Extension(value));
1157                    *r.get_router_mut() = router;
1158                }
1159            }
1160        }
1161        #[cfg(all(feature = "framework_flow", not(feature = "framework_axum")))]
1162        {
1163            match self {
1164                Self::Flow(r) => {
1165                    r.add_extension(value);
1166                }
1167            }
1168        }
1169    }
1170}
1171
1172// Delegate tower::Service to the inner router variant.
1173// This enables `ProductOSRouter` to be used directly with hyper.
1174#[cfg(feature = "framework_flow")]
1175impl<S> tower::Service<Request<Body>> for ProductOSRouter<S>
1176where
1177    S: Clone + Send + Sync + 'static,
1178{
1179    type Response = flow_impl::core::body::Response;
1180    type Error = core::convert::Infallible;
1181    type Future = core::pin::Pin<
1182        Box<dyn core::future::Future<Output = Result<Self::Response, Self::Error>> + Send>,
1183    >;
1184
1185    fn poll_ready(
1186        &mut self,
1187        cx: &mut core::task::Context<'_>,
1188    ) -> core::task::Poll<Result<(), Self::Error>> {
1189        match self {
1190            Self::Flow(r) => tower::Service::poll_ready(r, cx),
1191        }
1192    }
1193
1194    fn call(&mut self, req: Request<Body>) -> Self::Future {
1195        match self {
1196            Self::Flow(r) => tower::Service::call(r, req),
1197        }
1198    }
1199}
1200
1201#[cfg(feature = "std")]
1202mod extension_impl {
1203    use crate::{Body, FromRequestParts, Request};
1204    use std::ops::{Deref, DerefMut};
1205    use std::prelude::v1::*;
1206
1207    /// Extractor that gets a value from request extensions.
1208    ///
1209    /// This is commonly used with middleware that inserts data into extensions.
1210    /// In axum 0.7+, prefer using `State` for application-wide state.
1211    ///
1212    /// # Example
1213    ///
1214    /// ```ignore
1215    /// use product_os_router::{Extension, ServiceBuilder, MethodRouter};
1216    /// use std::sync::Arc;
1217    ///
1218    /// async fn handler(Extension(value): Extension<Arc<String>>) -> String {
1219    ///     (*value).clone()
1220    /// }
1221    ///
1222    /// let shared_data = Arc::new("Hello".to_string());
1223    /// let route = MethodRouter::new()
1224    ///     .get(handler)
1225    ///     .layer(ServiceBuilder::new().layer(tower::layer::util::AddExtensionLayer::new(shared_data)));
1226    /// ```
1227    #[derive(Debug, Clone, Copy, Default)]
1228    pub struct Extension<T>(pub T);
1229
1230    impl<T> Deref for Extension<T> {
1231        type Target = T;
1232
1233        fn deref(&self) -> &Self::Target {
1234            &self.0
1235        }
1236    }
1237
1238    impl<T> DerefMut for Extension<T> {
1239        fn deref_mut(&mut self) -> &mut Self::Target {
1240            &mut self.0
1241        }
1242    }
1243
1244    impl<T, S> FromRequestParts<S> for Extension<T>
1245    where
1246        T: Clone + Send + Sync + 'static,
1247        S: Send + Sync,
1248    {
1249        type Rejection = (product_os_http::StatusCode, &'static str);
1250
1251        fn from_request_parts(
1252            parts: &mut product_os_http::request::Parts,
1253            _state: &S,
1254        ) -> impl core::future::Future<Output = Result<Self, Self::Rejection>> + Send {
1255            let value = parts.extensions.get::<T>().cloned().map(Extension).ok_or((
1256                product_os_http::StatusCode::INTERNAL_SERVER_ERROR,
1257                "Extension not found in request",
1258            ));
1259
1260            async move { value }
1261        }
1262    }
1263
1264    pub fn extension_layer<T>(
1265        value: T,
1266    ) -> tower::util::MapRequestLayer<impl Fn(Request<Body>) -> Request<Body> + Clone>
1267    where
1268        T: Clone + Send + Sync + 'static,
1269    {
1270        tower::util::MapRequestLayer::new(move |mut req: Request<Body>| {
1271            req.extensions_mut().insert(value.clone());
1272            req
1273        })
1274    }
1275}
1276
1277#[cfg(feature = "std")]
1278pub use extension_impl::{extension_layer, Extension};