use fandhe_backend_core::Server;
use fandhe_backend_http::response::Response;
use fandhe_backend_plugin_openapi::OpenApiDoc;
use fandhe_backend_routes::Router;
fn custom_openapi_doc() -> Result<OpenApiDoc, fandhe_backend_plugin_openapi::OpenApiDocError> {
let json = r#"{
"openapi": "3.0.0",
"info": { "title": "todo-backend (custom)", "version": "1.0.0" },
"paths": {
"/todos": {
"get": {
"summary": "todo 一覧を返す",
"responses": { "200": { "description": "OK" } }
}
}
}
}"#;
OpenApiDoc::from_json(json)
}
fn build_router() -> Router {
Router::new().route("GET", "/todos", |_head, _body| {
Response::new(200, b"[]".to_vec()).with_content_type("application/json")
})
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> std::io::Result<()> {
let addr = std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3005".to_string());
let doc = custom_openapi_doc().expect("custom_openapi_doc は妥当な固定 JSON を返す");
let server = Server::new().handler(build_router()).openapi_with(doc);
println!("openapi_custom_doc: listening on http://{addr}");
let bound = server.bind(&addr).await?;
bound.run().await
}
#[cfg(test)]
mod tests {
use super::{build_router, custom_openapi_doc};
use fandhe_backend_core::{Handler, Server, handle_connection};
use fandhe_backend_http::request::RequestHead;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
struct NotCalledHandler;
impl Handler for NotCalledHandler {
fn handle(
&self,
_head: &RequestHead,
_body: &[u8],
) -> fandhe_backend_routes::HandlerFuture {
panic!("plugin::try_intercept が Some を返したのに既定 Handler が呼ばれた");
}
}
async fn roundtrip(server: &Server, request: &[u8]) -> Vec<u8> {
let (mut client, server_stream) = tokio::io::duplex(1 << 20);
client.write_all(request).await.unwrap();
client.shutdown().await.unwrap();
handle_connection(server, server_stream).await;
let mut out = Vec::new();
client.read_to_end(&mut out).await.unwrap();
out
}
#[tokio::test]
async fn serves_registered_custom_json() {
let doc = custom_openapi_doc().unwrap();
let server = Server::new().handler(NotCalledHandler).openapi_with(doc);
let response = roundtrip(
&server,
b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n",
)
.await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/json\r\n"));
assert!(response.contains("\"title\": \"todo-backend (custom)\""));
}
#[tokio::test]
async fn unregistered_yaml_falls_through_to_404() {
let doc = custom_openapi_doc().unwrap();
let server = Server::new().openapi_with(doc);
let response = roundtrip(
&server,
b"GET /openapi.yaml HTTP/1.1\r\nConnection: close\r\n\r\n",
)
.await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 404 Not Found\r\n"));
}
#[tokio::test]
async fn unrelated_path_reaches_default_handler() {
let doc = custom_openapi_doc().unwrap();
let server = Server::new().handler(build_router()).openapi_with(doc);
let response =
roundtrip(&server, b"GET /todos HTTP/1.1\r\nConnection: close\r\n\r\n").await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with("[]"));
}
}