use serde::{Deserialize, Serialize};
use skyzen::{
routing::{CreateRouteNode, Route, Router},
utils::Json,
OpenApi, Request, Responder, Response, StatusCode, ToSchema,
};
#[derive(Debug, Serialize, Deserialize, ToSchema)]
struct Widget {
id: i64,
label: String,
}
struct Outcome<T>(T);
impl<T: Responder> Responder for Outcome<T> {
type Error = T::Error;
fn respond_to(
self,
request: &Request,
response: &mut Response,
) -> core::result::Result<(), Self::Error> {
self.0.respond_to(request, response)
}
fn openapi() -> Option<Vec<skyzen::openapi::ResponseSchema>> {
T::openapi()
}
fn register_openapi_schemas(
defs: &mut std::collections::BTreeMap<String, skyzen::openapi::SchemaRef>,
) {
T::register_openapi_schemas(defs);
}
}
#[skyzen::openapi]
async fn direct() -> Json<Widget> {
Json(Widget {
id: 1,
label: "direct".to_owned(),
})
}
#[skyzen::openapi]
async fn wrapped() -> Outcome<Json<Widget>> {
Outcome(Json(Widget {
id: 2,
label: "wrapped".to_owned(),
}))
}
fn document() -> OpenApi {
Route::new(("/direct".at(direct), "/wrapped".at(wrapped)))
.build()
.openapi()
}
fn response_content(spec: &serde_json::Value, path: &str) -> serde_json::Value {
spec["paths"][path]["get"]["responses"]
.as_object()
.and_then(|responses| responses.values().next())
.and_then(|response| response.get("content"))
.cloned()
.unwrap_or(serde_json::Value::Null)
}
#[test]
fn a_responder_the_macro_cannot_destructure_still_documents_its_payload() {
let spec = serde_json::to_value(document().to_utoipa_spec()).expect("the spec serializes");
let wrapped = response_content(&spec, "/wrapped");
assert!(
!wrapped.is_null(),
"issue #18: the wrapped response documented no content at all: {spec:#}"
);
let schema = &wrapped["application/json"]["schema"];
assert!(
schema["properties"].get("label").is_some(),
"the wrapped response should describe the payload's fields: {schema:#}"
);
}
#[test]
fn the_wrapped_and_direct_responses_document_the_same_payload() {
let spec = serde_json::to_value(document().to_utoipa_spec()).expect("the spec serializes");
assert_eq!(
response_content(&spec, "/wrapped"),
response_content(&spec, "/direct"),
"a wrapper that forwards `openapi()` should document what it forwards to"
);
}
#[test]
fn the_payload_schema_reaches_the_components_map() {
let spec = serde_json::to_value(document().to_utoipa_spec()).expect("the spec serializes");
let widget = &spec["components"]["schemas"]["Widget"];
assert!(
widget.is_object(),
"the payload's own schema should be registered: {spec:#}"
);
assert!(
widget["properties"].get("label").is_some(),
"the registered schema should describe the payload's fields: {widget:#}"
);
}
#[skyzen::openapi]
async fn created() -> (StatusCode, Json<Widget>) {
(
StatusCode::CREATED,
Json(Widget {
id: 3,
label: "created".to_owned(),
}),
)
}
#[test]
fn a_tuple_responder_documents_its_payload_too() {
let router: Router = Route::new(("/created".at(created),)).build();
let spec = serde_json::to_value(router.openapi().to_utoipa_spec()).expect("serializes");
let content = response_content(&spec, "/created");
assert!(
content["application/json"]["schema"]["properties"]
.get("label")
.is_some(),
"{content:#}"
);
}