@@ -16,8 +16,14 @@ pub mod openapi {
1616 pub use vespera_core:: openapi:: * ;
1717}
1818
19+ // Re-export OpenApi directly for convenience (used by merge feature)
20+ pub use vespera_core:: openapi:: OpenApi ;
21+
1922// Re-export macros from vespera_macro
20- pub use vespera_macro:: { Schema , route, vespera} ;
23+ pub use vespera_macro:: { Schema , export_app, route, vespera} ;
24+
25+ // Re-export serde_json for merge feature (runtime spec merging)
26+ pub use serde_json;
2127
2228// Re-export axum for convenience
2329pub mod axum {
@@ -27,3 +33,71 @@ pub mod axum {
2733pub mod axum_extra {
2834 pub use axum_extra:: * ;
2935}
36+
37+ /// A router wrapper that defers merging until `with_state()` is called.
38+ ///
39+ /// This is necessary because in Axum, routers can only be merged when they have
40+ /// the same state type. By deferring the merge, we ensure that:
41+ /// 1. The base router's `.with_state()` is called first, converting it to `Router<()>`
42+ /// 2. Then the child routers (also `Router<()>`) are merged
43+ ///
44+ /// This wrapper is returned by `vespera!()` when the `merge` parameter is used.
45+ pub struct VesperaRouter < S >
46+ where
47+ S : Clone + Send + Sync + ' static ,
48+ {
49+ base : axum:: Router < S > ,
50+ /// Routers to merge after `with_state()` is called
51+ merge_fns : Vec < fn ( ) -> axum:: Router < ( ) > > ,
52+ }
53+
54+ impl < S > VesperaRouter < S >
55+ where
56+ S : Clone + Send + Sync + ' static ,
57+ {
58+ /// Create a new VesperaRouter with a base router and routers to merge
59+ pub fn new ( base : axum:: Router < S > , merge_fns : Vec < fn ( ) -> axum:: Router < ( ) > > ) -> Self {
60+ Self { base, merge_fns }
61+ }
62+
63+ /// Provide the state for the router and merge all child routers.
64+ ///
65+ /// This is equivalent to calling `Router::with_state()` and then merging
66+ /// all the child routers.
67+ ///
68+ /// After calling `with_state()`, the router's state type becomes `()` because
69+ /// the state has been provided. Child routers (also `Router<()>`) can then be merged.
70+ pub fn with_state ( self , state : S ) -> axum:: Router < ( ) > {
71+ // First, apply the state to convert Router<S> to Router<()>
72+ let mut router: axum:: Router < ( ) > = self . base . with_state ( state) ;
73+
74+ // Then merge all child routers (they are Router<()> which can be merged
75+ // into Router<()> without issues)
76+ for merge_fn in self . merge_fns {
77+ router = router. merge ( merge_fn ( ) ) ;
78+ }
79+
80+ router
81+ }
82+
83+ /// Add a layer to the router.
84+ pub fn layer < L > ( self , layer : L ) -> Self
85+ where
86+ L : tower_layer:: Layer < axum:: routing:: Route > + Clone + Send + Sync + ' static ,
87+ L :: Service : tower_service:: Service < axum:: extract:: Request > + Clone + Send + Sync + ' static ,
88+ <L :: Service as tower_service:: Service < axum:: extract:: Request > >:: Response :
89+ axum:: response:: IntoResponse + ' static ,
90+ <L :: Service as tower_service:: Service < axum:: extract:: Request > >:: Error :
91+ Into < std:: convert:: Infallible > + ' static ,
92+ <L :: Service as tower_service:: Service < axum:: extract:: Request > >:: Future : Send + ' static ,
93+ {
94+ Self {
95+ base : self . base . layer ( layer) ,
96+ merge_fns : self . merge_fns ,
97+ }
98+ }
99+ }
100+
101+ // Re-export tower_layer and tower_service for the layer method
102+ pub use tower_layer;
103+ pub use tower_service;
0 commit comments