Skip to content

Commit fe96f29

Browse files
committed
Pass component registrar to MethodRouter
Add a component_registrar parameter to MethodRouter::insert_boxed_with_operation and store it for each handler. Update RustApi route registration to destructure Route and pass the component_registrar when inserting handlers. Add request/response test types, handler, and a test (test_auto_registers_openapi_components_for_body_refs) to verify OpenAPI components for request bodies/responses are auto-registered and no dangling $ref values remain. Also import serde::Serialize for the new response type.
1 parent 8030bb7 commit fe96f29

File tree

4 files changed

+84
-7
lines changed

4 files changed

+84
-7
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ members = [
1717
]
1818

1919
[workspace.package]
20-
version = "0.1.412"
20+
version = "0.1.416"
2121
edition = "2021"
2222
authors = ["RustAPI Contributors, Tuntii"]
2323
license = "MIT OR Apache-2.0"
@@ -141,3 +141,4 @@ strip = false
141141

142142

143143

144+

crates/rustapi-core/src/app.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,16 @@ impl RustApi {
491491
let mut by_path: BTreeMap<String, MethodRouter> = BTreeMap::new();
492492

493493
for route in routes {
494-
let method_enum = match route.method {
494+
let crate::handler::Route {
495+
path: route_path,
496+
method,
497+
handler,
498+
operation,
499+
component_registrar,
500+
..
501+
} = route;
502+
503+
let method_enum = match method {
495504
"GET" => http::Method::GET,
496505
"POST" => http::Method::POST,
497506
"PUT" => http::Method::PUT,
@@ -500,14 +509,19 @@ impl RustApi {
500509
_ => http::Method::GET,
501510
};
502511

503-
let path = if route.path.starts_with('/') {
504-
route.path.to_string()
512+
let path = if route_path.starts_with('/') {
513+
route_path.to_string()
505514
} else {
506-
format!("/{}", route.path)
515+
format!("/{}", route_path)
507516
};
508517

509518
let entry = by_path.entry(path).or_default();
510-
entry.insert_boxed_with_operation(method_enum, route.handler, route.operation);
519+
entry.insert_boxed_with_operation(
520+
method_enum,
521+
handler,
522+
operation,
523+
component_registrar,
524+
);
511525
}
512526

513527
#[cfg(feature = "tracing")]

crates/rustapi-core/src/router.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ impl MethodRouter {
190190
method: Method,
191191
handler: BoxedHandler,
192192
operation: Operation,
193+
component_registrar: fn(&mut rustapi_openapi::OpenApiSpec),
193194
) {
194195
if self.handlers.contains_key(&method) {
195196
panic!(
@@ -200,6 +201,7 @@ impl MethodRouter {
200201

201202
self.handlers.insert(method.clone(), handler);
202203
self.operations.insert(method, operation);
204+
self.component_registrars.push(component_registrar);
203205
}
204206

205207
/// Add a GET handler

crates/rustapi-rs/tests/auto_route.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rustapi_rs::collect_auto_routes;
22
use rustapi_rs::prelude::*;
33
use rustapi_rs::{get, post};
4-
use serde::Deserialize;
4+
use serde::{Deserialize, Serialize};
55

66
// Standard handler
77
#[get("/test-auto-rs")]
@@ -65,6 +65,25 @@ struct Pagination {
6565
page_size: Option<u32>,
6666
}
6767

68+
#[derive(Debug, Clone, Deserialize, Schema)]
69+
struct AutoCreatePin {
70+
title: String,
71+
}
72+
73+
#[derive(Debug, Clone, Serialize, Schema)]
74+
struct AutoCreatePinResponse {
75+
id: i64,
76+
title: String,
77+
}
78+
79+
#[post("/auto-create-pin")]
80+
async fn auto_create_pin(Json(body): Json<AutoCreatePin>) -> Created<AutoCreatePinResponse> {
81+
Created(AutoCreatePinResponse {
82+
id: 1,
83+
title: body.title,
84+
})
85+
}
86+
6887
#[get("/query")]
6988
async fn query_handler(Query(p): Query<Pagination>) -> &'static str {
7089
let _ = (&p.page, &p.page_size);
@@ -153,3 +172,44 @@ fn test_openapi_includes_query_params() {
153172
"OpenAPI should include query parameter 'page_size'"
154173
);
155174
}
175+
176+
#[test]
177+
fn test_auto_registers_openapi_components_for_body_refs() {
178+
use rustapi_openapi::schema::RustApiSchema;
179+
180+
let app = RustApi::auto();
181+
let spec = app.openapi_spec();
182+
183+
assert!(
184+
spec.validate_integrity().is_ok(),
185+
"auto route OpenAPI spec should not contain dangling $ref values"
186+
);
187+
188+
let components = spec.components.as_ref().expect("components should exist");
189+
let create_pin_name = <AutoCreatePin as RustApiSchema>::component_name().unwrap();
190+
let response_name = <AutoCreatePinResponse as RustApiSchema>::component_name().unwrap();
191+
192+
assert!(components.schemas.contains_key(create_pin_name));
193+
assert!(components.schemas.contains_key(response_name));
194+
195+
let path_item = spec
196+
.paths
197+
.get("/auto-create-pin")
198+
.expect("/auto-create-pin path should exist");
199+
let op = path_item
200+
.post
201+
.as_ref()
202+
.expect("POST /auto-create-pin should exist");
203+
let media_type = op
204+
.request_body
205+
.as_ref()
206+
.and_then(|body| body.content.get("application/json"))
207+
.expect("request body media type should exist");
208+
209+
match media_type.schema.as_ref().expect("schema should exist") {
210+
rustapi_openapi::SchemaRef::Ref { reference } => {
211+
assert_eq!(reference, "#/components/schemas/AutoCreatePin");
212+
}
213+
other => panic!("expected request body schema ref, got {other:?}"),
214+
}
215+
}

0 commit comments

Comments
 (0)