Merge feat/http-to-openapi: to_openapi gateway projection (5-endpoint OpenAPI doc, ADR-042/045)
Implements to_openapi(registry) -> OpenAPISpec in src/adapters/to_openapi.rs — pure projection generating fixed 5-endpoint gateway doc (/search, /schema, /call, /batch, /subscribe) with info.version = 1.0.0 (ADR-045). /call responses carry protocol-level errors + operation-level errors mapped by http_status (ADR-023). Per-caller operation surface NOT preloaded (discovered via /search, ADR-042). /subscribe response is text/event-stream. Wired GET /openapi.json in adapter.rs. 16 new tests.
This commit is contained in:
@@ -16,7 +16,10 @@ pub mod from_mcp;
|
|||||||
#[cfg(feature = "mcp")]
|
#[cfg(feature = "mcp")]
|
||||||
pub mod to_mcp;
|
pub mod to_mcp;
|
||||||
|
|
||||||
|
pub mod to_openapi;
|
||||||
|
|
||||||
pub use from_openapi::{FromOpenAPI, HttpAuthScheme, HttpServiceConfig, OpenAPISpec};
|
pub use from_openapi::{FromOpenAPI, HttpAuthScheme, HttpServiceConfig, OpenAPISpec};
|
||||||
|
pub use to_openapi::to_openapi;
|
||||||
|
|
||||||
#[cfg(feature = "mcp")]
|
#[cfg(feature = "mcp")]
|
||||||
pub use from_mcp::FromMCP;
|
pub use from_mcp::FromMCP;
|
||||||
|
|||||||
1052
crates/alknet-http/src/adapters/to_openapi.rs
Normal file
1052
crates/alknet-http/src/adapters/to_openapi.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@
|
|||||||
//! custom routes + decoy fallback) and drives hyper's HTTP/1.1 or HTTP/2
|
//! custom routes + decoy fallback) and drives hyper's HTTP/1.1 or HTTP/2
|
||||||
//! connection driver over a single QUIC bidirectional stream. The 5 gateway
|
//! connection driver over a single QUIC bidirectional stream. The 5 gateway
|
||||||
//! endpoints (`/search`/`/schema`/`/call`/`/batch`/`/subscribe`) are wired in
|
//! endpoints (`/search`/`/schema`/`/call`/`/batch`/`/subscribe`) are wired in
|
||||||
//! from `gateway_routes`; `/openapi.json`, the MCP route, and the WS upgrade
|
//! from `gateway_routes`; `/openapi.json` serves the `to_openapi` projection
|
||||||
//! handler remain placeholder 501 handlers pending their respective tasks.
|
//! of the registry.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -14,6 +14,7 @@ use std::pin::Pin;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use axum::extract::State;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::middleware::from_fn_with_state;
|
use axum::middleware::from_fn_with_state;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
@@ -35,6 +36,7 @@ use super::gateway_routes;
|
|||||||
use super::healthz::healthz;
|
use super::healthz::healthz;
|
||||||
#[cfg(feature = "mcp")]
|
#[cfg(feature = "mcp")]
|
||||||
use crate::adapters::to_mcp_service;
|
use crate::adapters::to_mcp_service;
|
||||||
|
use crate::adapters::to_openapi;
|
||||||
#[cfg(feature = "mcp")]
|
#[cfg(feature = "mcp")]
|
||||||
use crate::gateway::GatewayDispatch;
|
use crate::gateway::GatewayDispatch;
|
||||||
use crate::websocket::upgrade::ws_upgrade_handler;
|
use crate::websocket::upgrade::ws_upgrade_handler;
|
||||||
@@ -183,7 +185,7 @@ fn build_router(state: RouterState, extra_routes: Option<Router>) -> Router {
|
|||||||
|
|
||||||
let default: Router<RouterState> = Router::new()
|
let default: Router<RouterState> = Router::new()
|
||||||
.merge(gateway_routes::gateway_router())
|
.merge(gateway_routes::gateway_router())
|
||||||
.route("/openapi.json", get(not_implemented))
|
.route("/openapi.json", get(openapi_json_handler))
|
||||||
.route(WS_UPGRADE_PATH, get(ws_upgrade_handler))
|
.route(WS_UPGRADE_PATH, get(ws_upgrade_handler))
|
||||||
.route_layer(from_fn_with_state(
|
.route_layer(from_fn_with_state(
|
||||||
auth_state.clone(),
|
auth_state.clone(),
|
||||||
@@ -204,8 +206,16 @@ fn build_router(state: RouterState, extra_routes: Option<Router>) -> Router {
|
|||||||
with_extras.with_state(state)
|
with_extras.with_state(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn not_implemented() -> impl IntoResponse {
|
async fn openapi_json_handler(State(registry): State<Arc<OperationRegistry>>) -> impl IntoResponse {
|
||||||
(StatusCode::NOT_IMPLEMENTED, "501 Not Implemented")
|
let spec = to_openapi(®istry);
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(
|
||||||
|
axum::http::header::CONTENT_TYPE,
|
||||||
|
axum::http::HeaderValue::from_static("application/json"),
|
||||||
|
)],
|
||||||
|
axum::Json(spec.raw),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -684,4 +694,23 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(response.contains("location: https://example.com"));
|
assert!(response.contains("location: https://example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn openapi_json_route_serves_gateway_spec() {
|
||||||
|
let adapter = HttpAdapter::new(provider(), empty_registry());
|
||||||
|
let request = b"GET /openapi.json HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
|
||||||
|
let response = serve_and_read(adapter, request).await;
|
||||||
|
assert!(
|
||||||
|
response.starts_with("HTTP/1.1 200"),
|
||||||
|
"expected 200 for /openapi.json, got: {response}"
|
||||||
|
);
|
||||||
|
assert!(response.contains("\"openapi\""));
|
||||||
|
assert!(response.contains("\"/search\""));
|
||||||
|
assert!(response.contains("\"/schema\""));
|
||||||
|
assert!(response.contains("\"/call\""));
|
||||||
|
assert!(response.contains("\"/batch\""));
|
||||||
|
assert!(response.contains("\"/subscribe\""));
|
||||||
|
assert!(response.contains("\"1.0.0\""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user