use poem::{
Endpoint, EndpointExt, Error,
http::{Method, StatusCode},
test::TestClient,
web::Data,
};
use poem_openapi::{
ApiRequest, ApiResponse, Object, OpenApi, OpenApiService, ParameterStyle, Tags,
param::{Path, Query},
payload::{Binary, Json, Payload, PlainText},
registry::{MetaApi, MetaExternalDocument, MetaOperation, MetaParamIn, MetaSchema, Registry},
types::Type,
};
#[tokio::test]
async fn path_and_method() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/abc", method = "post")]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(meta.paths[0].path, "/abc");
assert_eq!(meta.paths[0].operations[0].method, Method::POST);
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
cli.post("/abc").send().await.assert_status_is_ok();
}
#[test]
fn deprecated() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/abc", method = "get", deprecated)]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert!(meta.paths[0].operations[0].deprecated);
}
#[test]
fn tag() {
#[derive(Tags)]
enum MyTags {
UserOperations,
PetOperations,
}
struct Api;
#[OpenApi]
impl Api {
#[oai(
path = "/abc",
method = "get",
tag = "MyTags::UserOperations",
tag = "MyTags::PetOperations"
)]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(
meta.paths[0].operations[0].tags,
vec!["UserOperations", "PetOperations"]
);
}
#[tokio::test]
async fn common_tags() {
#[derive(Tags)]
enum MyTags {
UserOperations,
CommonOperations,
}
struct Api;
#[OpenApi(prefix_path = "/hello", tag = "MyTags::CommonOperations")]
impl Api {
#[oai(path = "/world", method = "get", tag = "MyTags::UserOperations")]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(meta.paths[0].path, "/hello/world");
assert_eq!(
meta.paths[0].operations[0].tags,
vec!["CommonOperations", "UserOperations"]
);
}
#[tokio::test]
async fn prefix_path() {
struct Api;
#[OpenApi(prefix_path = "/hello")]
impl Api {
#[oai(path = "/world", method = "get")]
async fn test(&self) {}
#[oai(path = "/", method = "get")]
async fn test1(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(meta.paths[0].path, "/hello/world");
assert_eq!(meta.paths[1].path, "/hello");
let ep = OpenApiService::new(Api, "test", "1.0");
TestClient::new(ep)
.get("/hello/world")
.send()
.await
.assert_status_is_ok();
let ep = OpenApiService::new(Api, "test", "1.0");
TestClient::new(ep)
.get("/hello")
.send()
.await
.assert_status_is_ok();
const PREFIX: &str = "/hello2";
struct Api2;
#[OpenApi(prefix_path = PREFIX)]
impl Api2 {
#[oai(path = "/world", method = "get")]
async fn test(&self) {}
#[oai(path = "/", method = "get")]
async fn test1(&self) {}
}
let meta: MetaApi = Api2::meta().remove(0);
assert_eq!(meta.paths[0].path, "/hello2/world");
assert_eq!(meta.paths[1].path, "/hello2");
let ep = OpenApiService::new(Api2, "test", "1.0");
TestClient::new(ep)
.get("/hello2/world")
.send()
.await
.assert_status_is_ok();
let ep = OpenApiService::new(Api2, "test", "1.0");
TestClient::new(ep)
.get("/hello2")
.send()
.await
.assert_status_is_ok();
struct Api3;
#[OpenApi(prefix_path = "/hello3/")]
impl Api3 {
#[oai(path = "/world", method = "get")]
async fn test(&self) {}
#[oai(path = "/", method = "get")]
async fn test1(&self) {}
}
let meta: MetaApi = Api3::meta().remove(0);
assert_eq!(meta.paths[0].path, "/hello3/world");
assert_eq!(meta.paths[1].path, "/hello3/");
let ep = OpenApiService::new(Api3, "test", "1.0");
TestClient::new(ep)
.get("/hello3/world")
.send()
.await
.assert_status_is_ok();
let ep = OpenApiService::new(Api3, "test", "1.0");
TestClient::new(ep)
.get("/hello3/")
.send()
.await
.assert_status_is_ok();
struct Api4;
#[OpenApi(prefix_path = "")]
impl Api4 {
#[oai(path = "/world", method = "get")]
async fn test(&self) {}
#[oai(path = "/", method = "get")]
async fn test1(&self) {}
}
let meta: MetaApi = Api4::meta().remove(0);
assert_eq!(meta.paths[0].path, "/world");
assert_eq!(meta.paths[1].path, "/");
let ep = OpenApiService::new(Api4, "test", "1.0");
TestClient::new(ep)
.get("/world")
.send()
.await
.assert_status_is_ok();
let ep = OpenApiService::new(Api4, "test", "1.0");
TestClient::new(ep)
.get("/")
.send()
.await
.assert_status_is_ok();
}
#[tokio::test]
async fn request() {
#[derive(ApiRequest)]
enum MyRequest {
Json(Json<i32>),
PlainText(PlainText<String>),
Binary(Binary<Vec<u8>>),
}
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "get")]
async fn test(&self, req: MyRequest) {
match req {
MyRequest::Json(value) => {
assert_eq!(value.0, 100);
}
MyRequest::PlainText(value) => {
assert_eq!(value.0, "abc");
}
MyRequest::Binary(value) => {
assert_eq!(value.0, vec![1, 2, 3]);
}
}
}
}
let meta: MetaApi = Api::meta().remove(0);
let meta_request = meta.paths[0].operations[0].request.as_ref().unwrap();
assert!(meta_request.required);
assert_eq!(meta_request.description, Some("Test request"));
assert_eq!(
meta_request.content[0].content_type,
"application/json; charset=utf-8"
);
assert_eq!(meta_request.content[0].schema, i32::schema_ref());
assert_eq!(
meta_request.content[1].content_type,
"text/plain; charset=utf-8"
);
assert_eq!(meta_request.content[1].schema, String::schema_ref());
assert_eq!(
meta_request.content[2].content_type,
"application/octet-stream"
);
assert_eq!(
meta_request.content[2].schema.unwrap_inline(),
&MetaSchema {
format: Some("binary"),
..MetaSchema::new("string")
}
);
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
cli.get("/")
.content_type("application/json")
.body("100")
.send()
.await
.assert_status_is_ok();
cli.get("/")
.content_type("application/json; x=10")
.body("100")
.send()
.await
.assert_status_is_ok();
cli.get("/")
.content_type("text/plain")
.body("abc")
.send()
.await
.assert_status_is_ok();
cli.get("/")
.content_type("application/octet-stream")
.body(vec![1, 2, 3])
.send()
.await
.assert_status_is_ok();
}
#[tokio::test]
async fn payload_request() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "post")]
async fn test(&self, req: Json<i32>) {
assert_eq!(req.0, 100);
}
}
let meta: MetaApi = Api::meta().remove(0);
let meta_request = meta.paths[0].operations[0].request.as_ref().unwrap();
assert!(meta_request.required);
assert_eq!(
meta_request.content[0].content_type,
"application/json; charset=utf-8"
);
assert_eq!(meta_request.content[0].schema, i32::schema_ref());
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
cli.post("/")
.content_type("application/json")
.body("100")
.send()
.await
.assert_status_is_ok();
cli.post("/")
.content_type("text/plain")
.body("100")
.send()
.await
.assert_status(StatusCode::UNSUPPORTED_MEDIA_TYPE);
}
#[tokio::test]
async fn response() {
const ALREADY_EXISTS_CODE: u16 = 409;
#[derive(ApiResponse)]
enum MyResponse {
#[oai(status = 200)]
Ok,
#[oai(status = ALREADY_EXISTS_CODE)]
AlreadyExists(Json<u16>),
Default(StatusCode, PlainText<String>),
}
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "get")]
async fn test(&self, code: Query<u16>) -> MyResponse {
match code.0 {
200 => MyResponse::Ok,
409 => MyResponse::AlreadyExists(Json(code.0)),
_ => MyResponse::Default(
StatusCode::from_u16(code.0).unwrap(),
PlainText(format!("code: {}", code.0)),
),
}
}
}
let meta: MetaApi = Api::meta().remove(0);
let meta_responses = &meta.paths[0].operations[0].responses;
assert_eq!(meta_responses.responses[0].description, "Ok");
assert_eq!(meta_responses.responses[0].status, Some(200));
assert!(meta_responses.responses[0].content.is_empty());
assert_eq!(meta_responses.responses[1].description, "Already exists");
assert_eq!(meta_responses.responses[1].status, Some(409));
assert_eq!(
meta_responses.responses[1].content[0].content_type,
"application/json; charset=utf-8"
);
assert_eq!(
meta_responses.responses[1].content[0].schema,
u16::schema_ref()
);
assert_eq!(meta_responses.responses[2].description, "Default");
assert_eq!(meta_responses.responses[2].status, None);
assert_eq!(
meta_responses.responses[2].content[0].content_type,
"text/plain; charset=utf-8"
);
assert_eq!(
meta_responses.responses[2].content[0].schema,
String::schema_ref()
);
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
let resp = cli.get("/").query("code", &200).send().await;
resp.assert_status_is_ok();
resp.assert_text("").await;
let resp = cli.get("/").query("code", &409).send().await;
resp.assert_status(StatusCode::CONFLICT);
resp.assert_content_type("application/json; charset=utf-8");
resp.assert_text("409").await;
let resp = cli.get("/").query("code", &404).send().await;
resp.assert_status(StatusCode::NOT_FOUND);
resp.assert_content_type("text/plain; charset=utf-8");
resp.assert_text("code: 404").await;
}
#[tokio::test]
async fn bad_request_handler() {
#[derive(ApiResponse)]
#[oai(bad_request_handler = "bad_request_handler")]
enum MyResponse {
#[oai(status = 200)]
Ok(PlainText<String>),
#[oai(status = 400)]
BadRequest(PlainText<String>),
}
fn bad_request_handler(err: Error) -> MyResponse {
MyResponse::BadRequest(PlainText(format!("!!! {err}")))
}
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "get")]
async fn test(&self, code: Query<u16>) -> MyResponse {
MyResponse::Ok(PlainText(format!("code: {}", code.0)))
}
}
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
let resp = cli.get("/").query("code", &200).send().await;
resp.assert_status_is_ok();
resp.assert_content_type("text/plain; charset=utf-8");
resp.assert_text("code: 200").await;
let resp = cli.get("/").send().await;
resp.assert_status(StatusCode::BAD_REQUEST);
resp.assert_content_type("text/plain; charset=utf-8");
resp.assert_text(
r#"!!! failed to parse parameter `code`: Type "integer(uint16)" expects an input value."#,
)
.await;
}
#[tokio::test]
async fn bad_request_handler_for_validator() {
#[derive(ApiResponse)]
#[oai(bad_request_handler = "bad_request_handler")]
enum MyResponse {
#[oai(status = 200)]
Ok(PlainText<String>),
#[oai(status = 400)]
BadRequest(PlainText<String>),
}
fn bad_request_handler(err: Error) -> MyResponse {
MyResponse::BadRequest(PlainText(format!("!!! {err}")))
}
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "get")]
async fn test(
&self,
#[oai(validator(maximum(value = "100")))] code: Query<u16>,
) -> MyResponse {
MyResponse::Ok(PlainText(format!("code: {}", code.0)))
}
}
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
let resp = cli.get("/").query("code", &50).send().await;
resp.assert_status_is_ok();
resp.assert_content_type("text/plain; charset=utf-8");
resp.assert_text("code: 50").await;
let resp = cli.get("/").query("code", &200).send().await;
resp.assert_status(StatusCode::BAD_REQUEST);
resp.assert_content_type("text/plain; charset=utf-8");
resp.assert_text(r#"!!! failed to parse parameter `code`: verification failed. maximum(100, exclusive: false)"#).await;
}
#[tokio::test]
async fn poem_extract() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/test1", method = "get")]
async fn test1(&self, data: Data<&i32>) {
assert_eq!(*data.0, 100);
}
#[oai(path = "/test2", method = "get")]
async fn test2(&self, Data(value): Data<&i32>) {
assert_eq!(*value, 100);
}
#[oai(path = "/test3/:user_id", method = "get")]
async fn test3(&self, Path(user_id): Path<i32>) {
assert_eq!(user_id, 7);
}
}
let ep = OpenApiService::new(Api, "test", "1.0").data(100i32);
let client = TestClient::new(ep);
client.get("/test1").send().await.assert_status_is_ok();
client.get("/test2").send().await.assert_status_is_ok();
client.get("/test3/7").send().await.assert_status_is_ok();
}
#[tokio::test]
async fn issue_948() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/hello/:another_param", method = "post")]
async fn another_param_handler(
&self,
Path(another_param): Path<String>,
) -> PlainText<String> {
PlainText(format!("POST /hello/{another_param}"))
}
#[oai(path = "/hello/:param", method = "get")]
async fn param_handler(&self, Path(param): Path<String>) -> PlainText<String> {
PlainText(format!("GET /hello/{param}"))
}
}
let meta = Api::meta().remove(0);
assert_eq!(meta.paths[0].path, "/hello/{another_param}");
assert_eq!(meta.paths[0].operations[0].method, Method::POST);
assert_eq!(meta.paths[1].path, "/hello/{param}");
assert_eq!(meta.paths[1].operations[0].method, Method::GET);
let ep = OpenApiService::new(Api, "test", "1.0");
let client = TestClient::new(ep);
let res = client.get("/hello/something").send().await;
res.assert_status_is_ok();
res.assert_text("GET /hello/something").await;
let res = client.post("/hello/another_something").send().await;
res.assert_status_is_ok();
res.assert_text("POST /hello/another_something").await;
}
#[tokio::test]
async fn returning_borrowed_value() {
struct Api {
value1: i32,
value2: String,
values: Vec<i32>,
}
#[OpenApi]
impl Api {
#[oai(path = "/value1", method = "get")]
async fn value1(&self) -> Json<&i32> {
Json(&self.value1)
}
#[oai(path = "/value2", method = "get")]
async fn value2(&self) -> Json<&String> {
Json(&self.value2)
}
#[oai(path = "/value3", method = "get")]
async fn value3<'a>(&self, data: Data<&'a i32>) -> Json<&'a i32> {
Json(&data)
}
#[oai(path = "/value4", method = "get")]
async fn value4<'a>(&self, Data(value): Data<&'a i32>) -> Json<&'a i32> {
Json(value)
}
#[oai(path = "/values", method = "get")]
async fn values(&self) -> Json<&[i32]> {
Json(&self.values)
}
}
let ep = OpenApiService::new(
Api {
value1: 999,
value2: "abc".to_string(),
values: vec![1, 2, 3, 4, 5],
},
"test",
"1.0",
)
.data(888i32);
let cli = TestClient::new(ep);
let resp = cli.get("/value1").send().await;
resp.assert_status_is_ok();
resp.assert_text("999").await;
let resp = cli.get("/value2").send().await;
resp.assert_status_is_ok();
resp.assert_text("\"abc\"").await;
let resp = cli.get("/value3").send().await;
resp.assert_status_is_ok();
resp.assert_text("888").await;
let resp = cli.get("/value4").send().await;
resp.assert_status_is_ok();
resp.assert_text("888").await;
let resp = cli.get("/values").send().await;
resp.assert_status_is_ok();
resp.assert_text("[1,2,3,4,5]").await;
}
#[tokio::test]
async fn external_docs() {
struct Api;
#[OpenApi]
impl Api {
#[oai(
path = "/",
method = "get",
external_docs = "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md"
)]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(
meta.paths[0].operations[0].external_docs,
Some(MetaExternalDocument {
url: "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md"
.to_string(),
description: None
})
);
}
#[tokio::test]
async fn generic() {
trait MyApiPort: Send + Sync + 'static {
fn test(&self) -> String;
}
struct MyApiA;
impl MyApiPort for MyApiA {
fn test(&self) -> String {
"test".to_string()
}
}
struct MyOpenApi<MyApi> {
api: MyApi,
}
#[OpenApi]
impl<MyApi: MyApiPort> MyOpenApi<MyApi> {
#[oai(path = "/some_call", method = "get")]
async fn some_call(&self) -> Json<String> {
Json(self.api.test())
}
}
let ep = OpenApiService::new(MyOpenApi { api: MyApiA }, "test", "1.0");
let cli = TestClient::new(ep);
let resp = cli.get("/some_call").send().await;
resp.assert_status_is_ok();
resp.assert_json("test").await;
}
#[tokio::test]
async fn extra_response_headers_on_operation() {
struct Api;
#[OpenApi]
impl Api {
#[oai(
path = "/",
method = "get",
response_header(name = "A1", ty = "String", description = "abc"),
response_header(name = "a2", ty = "i32", deprecated = true)
)]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
let header = &meta.paths[0].operations[0].responses.responses[0].headers[0];
assert_eq!(header.name, "A1");
assert_eq!(header.description.as_deref(), Some("abc"));
assert!(!header.deprecated);
assert_eq!(header.schema, String::schema_ref());
let header = &meta.paths[0].operations[0].responses.responses[0].headers[1];
assert_eq!(header.name, "A2");
assert_eq!(header.description, None);
assert!(header.deprecated);
assert_eq!(header.schema, i32::schema_ref());
}
#[tokio::test]
async fn extra_response_headers_on_api() {
struct Api;
#[OpenApi(
response_header(name = "A1", ty = "String", description = "abc"),
response_header(name = "a2", ty = "i32", deprecated = true)
)]
impl Api {
#[oai(path = "/", method = "get")]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
let header = &meta.paths[0].operations[0].responses.responses[0].headers[0];
assert_eq!(header.name, "A1");
assert_eq!(header.description.as_deref(), Some("abc"));
assert!(!header.deprecated);
assert_eq!(header.schema, String::schema_ref());
let header = &meta.paths[0].operations[0].responses.responses[0].headers[1];
assert_eq!(header.name, "A2");
assert_eq!(header.description, None);
assert!(header.deprecated);
assert_eq!(header.schema, i32::schema_ref());
}
#[tokio::test]
async fn extra_request_headers_on_operation() {
struct Api;
#[OpenApi]
impl Api {
#[oai(
path = "/",
method = "get",
request_header(name = "A1", ty = "String", description = "abc"),
request_header(name = "a2", ty = "i32", deprecated = true)
)]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
let params = &meta.paths[0].operations[0].params[0];
assert_eq!(params.name, "A1");
assert_eq!(params.schema, String::schema_ref());
assert_eq!(params.in_type, MetaParamIn::Header);
assert_eq!(params.description.as_deref(), Some("abc"));
assert!(params.required);
assert!(!params.deprecated);
let params = &meta.paths[0].operations[0].params[1];
assert_eq!(params.name, "A2");
assert_eq!(params.schema, i32::schema_ref());
assert_eq!(params.in_type, MetaParamIn::Header);
assert_eq!(params.description, None);
assert!(params.required);
assert!(params.deprecated);
}
#[tokio::test]
async fn extra_request_headers_on_api() {
struct Api;
#[OpenApi(
request_header(name = "A1", ty = "String", description = "abc"),
request_header(name = "a2", ty = "i32", deprecated = true)
)]
impl Api {
#[oai(path = "/", method = "get")]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
let params = &meta.paths[0].operations[0].params[0];
assert_eq!(params.name, "A1");
assert_eq!(params.schema, String::schema_ref());
assert_eq!(params.in_type, MetaParamIn::Header);
assert_eq!(params.description.as_deref(), Some("abc"));
assert!(params.required);
assert!(!params.deprecated);
let params = &meta.paths[0].operations[0].params[1];
assert_eq!(params.name, "A2");
assert_eq!(params.schema, i32::schema_ref());
assert_eq!(params.in_type, MetaParamIn::Header);
assert_eq!(params.description, None);
assert!(params.required);
assert!(params.deprecated);
}
#[tokio::test]
async fn multiple_methods() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/abc", method = "post", method = "put")]
async fn test(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(meta.paths[0].path, "/abc");
assert_eq!(meta.paths[0].operations[0].method, Method::POST);
assert_eq!(meta.paths[0].operations[1].method, Method::PUT);
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
cli.post("/abc").send().await.assert_status_is_ok();
cli.put("/abc").send().await.assert_status_is_ok();
}
#[tokio::test]
async fn actual_type() {
#[derive(Debug, Object)]
struct MyObj {
value: i32,
}
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/", method = "get", actual_type = "Json<MyObj>")]
async fn test(&self) -> Binary<Vec<u8>> {
Binary(b"{ \"value\": 100 }".to_vec())
}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(meta.paths[0].path, "/");
let operator = &meta.paths[0].operations[0];
let response = &operator.responses.responses[0];
assert_eq!(response.status, Some(200));
let media = &response.content[0];
assert_eq!(media.content_type, "application/json; charset=utf-8");
assert_eq!(media.schema, <Json<MyObj>>::schema_ref());
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
let resp = cli.get("/").send().await;
resp.assert_content_type("application/json; charset=utf-8");
resp.assert_json(&serde_json::json!({ "value": 100 })).await;
let mut registry = Registry::new();
Api::register(&mut registry);
let type_name: Vec<&String> = registry.schemas.keys().collect();
assert_eq!(&type_name, &["MyObj"]);
}
#[tokio::test]
async fn code_samples() {
const JS_SOURCE: &str = "
J
S
JavaScript
";
struct Api;
#[OpenApi]
impl Api {
#[oai(
path = "/test1",
method = "get",
code_sample(lang = "js", source = "JS_SOURCE")
)]
async fn test1(&self) {}
#[oai(
path = "/test2",
method = "get",
code_sample(lang = "rust", label = "Rust lang", source = "\"Rust Source\""),
code_sample(lang = "go", label = "Go", source = "\"Google Go\"")
)]
async fn test2(&self) {}
}
let meta: MetaApi = Api::meta().remove(0);
let path = &meta.paths[0];
let operator: &MetaOperation = &path.operations[0];
assert_eq!(path.path, "/test1");
let code_sample = &operator.code_samples[0];
assert_eq!(code_sample.lang, "js");
assert_eq!(code_sample.label, None);
assert_eq!(code_sample.source, JS_SOURCE);
let path = &meta.paths[1];
let operator: &MetaOperation = &path.operations[0];
assert_eq!(path.path, "/test2");
let code_sample = &operator.code_samples[0];
assert_eq!(code_sample.lang, "rust");
assert_eq!(code_sample.label, Some("Rust lang"));
assert_eq!(code_sample.source, "Rust Source");
let code_sample = &operator.code_samples[1];
assert_eq!(code_sample.lang, "go");
assert_eq!(code_sample.label, Some("Go"));
assert_eq!(code_sample.source, "Google Go");
}
#[tokio::test]
async fn hidden() {
#[derive(Debug, Object)]
struct MyObj1 {
value: i32,
}
#[derive(Debug, Object)]
struct MyObj2 {
value: i32,
}
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/api1", method = "get", hidden)]
async fn api1(&self, req: Json<MyObj1>) -> Json<i32> {
Json(req.0.value)
}
#[oai(path = "/api2", method = "get")]
async fn api2(&self, req: Json<MyObj2>) -> Json<i32> {
Json(req.0.value)
}
}
let meta: MetaApi = Api::meta().remove(0);
assert_eq!(meta.paths.len(), 1);
let path = &meta.paths[0];
assert_eq!(path.path, "/api2");
let mut registry = Registry::new();
Api::register(&mut registry);
assert!(!registry.schemas.contains_key("MyObj1"));
assert!(registry.schemas.contains_key("MyObj2"));
}
#[test]
fn issue_405() {
#[allow(dead_code)]
struct Api;
#[OpenApi]
impl Api {
#[oai(
path = "/hello",
method = "get",
operation_id = "hello",
transform = "my_transformer"
)]
#[allow(dead_code)]
async fn index(&self) -> PlainText<String> {
PlainText("hello, world!".to_string())
}
}
#[allow(dead_code)]
fn my_transformer(ep: impl Endpoint) -> impl Endpoint {
ep.map_to_response()
}
}
#[tokio::test]
async fn issue_489() {
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/hello", method = "get")]
async fn get_hello(&self) {}
#[oai(path = "/hello", method = "delete")]
async fn delete_hello(&self) {}
#[oai(path = "/goodbye", method = "get")]
async fn get_goodbye(&self) {}
}
let ep = OpenApiService::new(Api, "test", "1.0");
let cli = TestClient::new(ep);
cli.delete("/goodbye")
.send()
.await
.assert_status(StatusCode::METHOD_NOT_ALLOWED);
}
#[tokio::test]
async fn parameter_style_some() {
#[allow(dead_code)]
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/hello", method = "get")]
#[allow(dead_code)]
async fn index(
&self,
#[oai(style = "deep_object")] Query(input): Query<String>,
) -> PlainText<String> {
PlainText(format!("hello, world! {input}"))
}
}
assert_eq!(
Api::meta()[0].paths[0].operations[0].params[0].style,
Some(ParameterStyle::DeepObject)
);
let spec = OpenApiService::new(Api {}, "test", "1.0").spec();
assert!(spec.contains("\"style\": \"deepObject\""));
}
#[tokio::test]
async fn parameter_style_none() {
#[allow(dead_code)]
struct Api;
#[OpenApi]
impl Api {
#[oai(path = "/hello", method = "get")]
#[allow(dead_code)]
async fn index(&self, #[oai()] Query(input): Query<String>) -> PlainText<String> {
PlainText(format!("hello, world! {input}"))
}
}
assert_eq!(Api::meta()[0].paths[0].operations[0].params[0].style, None);
let spec = OpenApiService::new(Api {}, "test", "1.0").spec();
assert!(!spec.contains("\"style\"") && !spec.contains("\"style\": null"));
}