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
//! Body of the generated `pub mod axum { ... }` — shared types
//! (selection / list / fetch query DTOs), per-procedure + per-model
//! axum handlers, the `model_router`/`procedure_router`/`router` fns,
//! plus the RPC sub-module when `transport rpc`.
use quote::quote;
use crate::axum::generate_axum_shared_support;
use super::collect::ServerCollected;
pub(super) fn build_axum_module(c: &ServerCollected) -> proc_macro2::TokenStream {
let procedure_transport_constants = &c.procedure_transport_constants;
let model_transport_constants = &c.model_transport_constants;
let route_transport_entries = &c.route_transport_entries;
let op_descriptor_entries = &c.op_descriptor_entries;
let procedure_axum_handler_defs = &c.procedure_axum_handler_defs;
let model_axum_handler_defs = &c.model_axum_handler_defs;
let procedure_axum_routes = &c.procedure_axum_routes;
let model_axum_routes = &c.model_axum_routes;
let axum_shared_support = generate_axum_shared_support();
let rpc_module = super::rpc_module::build_rpc_module(c.is_rpc, &c.rpc_dispatch_arms);
let dtos = super::axum_dtos::build_axum_dtos();
quote! {
pub mod axum {
use ::cratestack::AuthProvider;
use ::cratestack::CoolError;
use ::cratestack::HttpTransport;
use ::cratestack::axum;
use ::cratestack::axum::body::Bytes;
use ::cratestack::axum::extract::{Path, RawQuery, State};
use ::cratestack::axum::http::HeaderMap;
use ::cratestack::axum::response::Response;
#[derive(Clone)]
pub struct ProcedureRouterState<R, C, Auth> {
pub db: super::Cratestack,
pub registry: R,
pub codec: C,
pub auth_provider: Auth,
}
#[derive(Clone)]
pub struct ModelRouterState<C, Auth> {
pub db: super::Cratestack,
pub codec: C,
pub auth_provider: Auth,
}
/// The four request components that make up a canonical signed
/// request. On `transport rest` these are the REST method/path/
/// query/body; on `transport rpc` they are the ACTUAL rpc request
/// (`POST /rpc/<op_id>`, no query, the raw frame bytes). A handler's
/// `_dispatch` fn takes one of these so signature verification and
/// tracing share a single source of truth that matches the client
/// byte-for-byte.
// `pub(super)` (not private): the gRPC service module
// (`super::grpc`, a sibling of this `axum` module, emitted only
// for `transport grpc` schemas under the `grpc` Cargo feature —
// see `crates/cratestack-macros/src/include/server/grpc/`)
// constructs `CanonicalRequest` and calls the `_dispatch` fns
// below directly, so gRPC method bodies delegate to the exact
// same dispatch functions REST/RPC already call — "no second
// dispatch path" (ticket #171 AC). `pub(super)` keeps them
// unreachable from outside the generated `cratestack_schema`
// module entirely (schema authors never see these).
pub(super) struct CanonicalRequest<'a> {
pub(super) method: &'a str,
pub(super) path: &'a str,
pub(super) query: Option<&'a str>,
pub(super) body: &'a [u8],
}
pub(super) fn request_context<'a>(
method: &'a str,
path: &'a str,
query: Option<&'a str>,
headers: &'a HeaderMap,
body: &'a [u8],
) -> ::cratestack::RequestContext<'a> {
::cratestack::RequestContext {
method,
path,
query,
headers,
body,
}
}
#dtos
#(#procedure_transport_constants)*
#(#model_transport_constants)*
#axum_shared_support
pub const ROUTE_TRANSPORTS: &[::cratestack::RouteTransportDescriptor] = &[
#(#route_transport_entries,)*
];
/// RPC op descriptors. Populated only when the schema declares
/// `transport rpc`; empty otherwise. The two slices
/// (`ROUTE_TRANSPORTS` and `OPS`) are never both non-empty for a
/// given schema — see `docs/design/rpc-transport.md`.
pub const OPS: &[::cratestack::OpDescriptor] = &[
#(#op_descriptor_entries,)*
];
#(#procedure_axum_handler_defs)*
#(#model_axum_handler_defs)*
pub fn model_router<C, Auth>(
db: super::Cratestack,
codec: C,
auth_provider: Auth,
) -> axum::Router
where
C: HttpTransport,
Auth: AuthProvider,
{
let state = ModelRouterState {
db,
codec,
auth_provider,
};
axum::Router::new()
#(#model_axum_routes)*
.layer(::cratestack::axum::middleware::from_fn_with_state(
super::SCHEMA_SHA256,
::cratestack::schema_fingerprint::warn_on_schema_mismatch,
))
.with_state(state)
}
pub fn procedure_router<R, C, Auth>(
db: super::Cratestack,
registry: R,
codec: C,
auth_provider: Auth,
) -> axum::Router
where
R: super::procedures::ProcedureRegistry,
C: HttpTransport,
Auth: AuthProvider,
{
let state = ProcedureRouterState {
db,
registry,
codec,
auth_provider,
};
axum::Router::new()
#(#procedure_axum_routes)*
.layer(::cratestack::axum::middleware::from_fn_with_state(
super::SCHEMA_SHA256,
::cratestack::schema_fingerprint::warn_on_schema_mismatch,
))
.with_state(state)
}
pub fn router<R, C, Auth>(
db: super::Cratestack,
registry: R,
codec: C,
auth_provider: Auth,
) -> axum::Router
where
R: super::procedures::ProcedureRegistry,
C: HttpTransport,
Auth: AuthProvider,
{
model_router(db.clone(), codec.clone(), auth_provider.clone())
.merge(procedure_router(db, registry, codec, auth_provider))
}
#rpc_module
}
}
}