use aide::openapi::{Parameter, PathItem, ReferenceOr};
use cot::html::Html;
use cot::json::Json;
use cot::openapi::{AsApiRoute, NoApi, RouteContext};
use cot::request::extractors::{Path, UrlQuery};
use cot::response::{IntoResponse, Response};
use cot::router::method::openapi::{ApiMethodRouter, api_get, api_post};
use cot::router::{Route, Router};
use cot::test::TestRequestBuilder;
use cot::{RequestHandler, StatusCode};
use schemars::SchemaGenerator;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Deserialize, Serialize, schemars::JsonSchema)]
struct TestRequest {
field1: String,
field2: i32,
optional_field: Option<bool>,
}
#[derive(Serialize, schemars::JsonSchema)]
struct TestResponse {
result: String,
}
async fn test_handler() -> cot::Result<Response> {
Html::new("test").into_response()
}
async fn test_json_handler(Json(req): Json<TestRequest>) -> Json<TestResponse> {
Json(TestResponse {
result: format!("Got: {}, {}", req.field1, req.field2),
})
}
async fn test_path_handler(Path(id): Path<i32>) -> cot::Result<Response> {
Html::new(format!("ID: {id}")).into_response()
}
async fn test_query_handler(UrlQuery(query): UrlQuery<TestRequest>) -> cot::Result<Response> {
Html::new(format!("Query: {}, {}", query.field1, query.field2)).into_response()
}
#[cot::test]
async fn api_route_integration() {
let router = Router::with_urls([
Route::with_api_handler("/test", api_get(test_handler)),
Route::with_api_handler("/json", api_post(test_json_handler)),
Route::with_api_handler("/path/{id}", api_get(test_path_handler)),
Route::with_api_handler_and_name("/query", api_get(test_query_handler), "query"),
]);
let aide::openapi::OpenApi {
paths: Some(aide::openapi::Paths {
paths: api_spec, ..
}),
..
} = router.as_api()
else {
panic!("Expected OpenAPI data");
};
assert!(api_spec.contains_key("/test"));
assert!(api_spec.contains_key("/json"));
assert!(api_spec.contains_key("/path/{id}"));
assert!(api_spec.contains_key("/query"));
assert!(matches!(
api_spec.get("/test"),
Some(ReferenceOr::Item(PathItem { get: Some(_), .. }))
));
assert!(matches!(
api_spec.get("/json"),
Some(ReferenceOr::Item(PathItem { post: Some(_), .. }))
));
if let Some(ReferenceOr::Item(PathItem {
get: Some(operation),
..
})) = api_spec.get("/path/{id}")
{
assert_eq!(operation.parameters.len(), 1);
if let ReferenceOr::Item(Parameter::Path { parameter_data, .. }) = &operation.parameters[0]
{
assert_eq!(parameter_data.name, "id");
} else {
panic!("Expected path parameter");
}
} else {
panic!("Expected GET operation for /path/{{id}}");
}
if let Some(ReferenceOr::Item(PathItem {
get: Some(operation),
..
})) = api_spec.get("/query")
{
assert_eq!(operation.parameters.len(), 3);
for param in &operation.parameters {
assert!(matches!(param, ReferenceOr::Item(Parameter::Query { .. })));
}
} else {
panic!("Expected GET operation for /query");
}
let request = TestRequestBuilder::get("/test").build();
let response = router.handle(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.into_body().into_bytes().await.unwrap(), "test");
let request = TestRequestBuilder::get("/path/123").build();
let response = router.handle(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.into_body().into_bytes().await.unwrap(), "ID: 123");
}
#[test]
fn api_router_nested() {
let router = Router::with_urls([Route::with_api_handler(
"/test",
ApiMethodRouter::new().get(test_handler),
)]);
let nested_router = Router::with_urls(vec![Route::with_router("/b", router)]);
let root_router = Router::with_urls(vec![Route::with_router("/a", nested_router)]);
if let aide::openapi::OpenApi {
paths: Some(aide::openapi::Paths {
paths: api_spec, ..
}),
..
} = root_router.as_api()
{
assert!(matches!(
api_spec.get("/a/b/test"),
Some(ReferenceOr::Item(PathItem { get: Some(_), .. }))
));
} else {
panic!("Expected OpenAPI data");
}
}
#[test]
fn api_router_cycle() {
#[derive(Deserialize, Serialize, schemars::JsonSchema)]
struct NestedData {
nested: Option<Box<Self>>,
}
async fn test_handler(_data: Json<NestedData>) -> cot::Result<Response> {
unimplemented!()
}
let router = Router::with_urls([Route::with_api_handler(
"/test",
ApiMethodRouter::new().get(test_handler),
)]);
let openapi = router.as_api();
let schemas = openapi.components.unwrap().schemas;
let nested_schema = schemas.get("NestedData").unwrap();
let nested_object = nested_schema.json_schema.as_object().unwrap();
let reference = nested_object
.get("properties")
.unwrap()
.get("nested")
.unwrap()
.get("anyOf")
.unwrap();
let ref_obj = Value::Object(Map::from_iter([(
"$ref".to_string(),
Value::String("#/components/schemas/NestedData".to_string()),
)]));
let ref_arr = reference.as_array().unwrap();
assert_eq!(ref_arr[0], ref_obj);
}
#[test]
fn api_method_router() {
let router = ApiMethodRouter::new()
.get(test_handler)
.post(test_json_handler);
let route_context = RouteContext::new();
let mut schema_generator = SchemaGenerator::default();
let path_item = router.as_api_route(&route_context, &mut schema_generator);
assert!(path_item.get.is_some());
assert!(path_item.post.is_some());
assert!(path_item.put.is_none());
assert!(path_item.delete.is_none());
assert!(path_item.options.is_none());
assert!(path_item.head.is_none());
assert!(path_item.patch.is_none());
assert!(path_item.trace.is_none());
if let Some(operation) = path_item.post {
assert!(operation.request_body.is_some());
}
}
#[cot::test]
async fn no_api_in_method_router() {
let router = ApiMethodRouter::new()
.get(test_handler)
.post(NoApi(test_json_handler));
let route_context = RouteContext::new();
let mut schema_generator = SchemaGenerator::default();
let path_item = router.as_api_route(&route_context, &mut schema_generator);
assert!(path_item.get.is_some());
assert!(path_item.post.is_none());
let request = TestRequestBuilder::post("/test")
.json(&TestRequest {
field1: "test".to_string(),
field2: 42,
optional_field: Some(true),
})
.build();
let response = router.handle(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
#[cot::test]
async fn no_api_in_params() {
async fn noapi_handler(
NoApi(Path(id)): NoApi<Path<i32>>,
NoApi(Json(req)): NoApi<Json<TestRequest>>,
) -> cot::Result<Response> {
Html::new(format!("Got: {id}, {}, {}", req.field1, req.field2)).into_response()
}
let router = Router::with_urls([Route::with_api_handler(
"/test/{id}",
api_post(noapi_handler),
)]);
let request = TestRequestBuilder::post("/test/123")
.json(&TestRequest {
field1: "test".to_string(),
field2: 42,
optional_field: Some(true),
})
.build();
let response = router.handle(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().into_bytes().await.unwrap();
assert_eq!(body, "Got: 123, test, 42");
}