mirror of
https://github.com/poem-web/poem.git
synced 2026-01-25 04:18:25 +00:00
[poem-openapi] Rework OpenAPI macro
This commit is contained in:
@@ -2,27 +2,43 @@
|
||||
|
||||
The following defines some API operations to add, delete, modify and query the `pet` table.
|
||||
|
||||
`add_pet` and `update_pet` are used to add and update the `Pet` object. **This is the basic type we defined before.
|
||||
The basic type cannot be directly used as the request content. You need to use a `Payload` type to wrap it**, In this way,
|
||||
you can determine what the requested `Content-Type` is. In the following example, we use `payload::Json` to wrap it,
|
||||
indicating that the `Content-Type` of these two API requests is `application/json`.
|
||||
A method represents an API operation. Use the `path` and `method` attributes to specify the path and HTTP method of the operation.
|
||||
|
||||
`find_pet_by_id` and `find_pets_by_status` are used to find the `Pet` object, and their response is also a `Pet` object,
|
||||
which also needs to be wrapped with the `Payload` type.
|
||||
There can be multiple parameters for each API operation, and the following types can be used:
|
||||
|
||||
We can use `#[oai(name = "...", in = "...")]` to decorate a function parameter to specify the source of this value.
|
||||
The `in` attribute can be `query`, ` path`, `header` and `cookie`. The `id` parameter of `delete_pet` is parsed from the
|
||||
path, and the parameters of `find_pet_by_id` and `find_pets_by_status` are parsed from the Url query string. If the
|
||||
parameter type is not `Option<T>`, it means that this parameter is not an optional parameter, and a `400 Bad Request` error
|
||||
will be returned when the parsing fails.
|
||||
- **poem_openapi::param::Query** represents this parameter is parsed from the query string
|
||||
|
||||
You can define multiple function parameters, but there can only be one `Payload` type as the request content, or multiple
|
||||
basic types as the request parameters.
|
||||
- **poem_openapi::param::Header** represents this parameter is parsed from the request headers
|
||||
|
||||
- **poem_openapi::param::Path** represents this parameter is parsed from the URI path
|
||||
|
||||
- **poem_openapi::param::Cookie** represents this parameter is parsed from the cookie
|
||||
|
||||
- **poem_openapi::param::CookiePrivate** represents this parameter is parsed from the private cookie
|
||||
|
||||
- **poem_openapi::param::CookieSigned** represents this parameter is parsed from the signed cookie
|
||||
|
||||
- **poem_openapi::payload::Binary** represents a binary request payload
|
||||
|
||||
- **poem_openapi::payload::Json** represents a request payload encoded with JSON
|
||||
|
||||
- **poem_openapi::payload::PlainText** represents a utf8 string request payload
|
||||
|
||||
- **ApiRequest** parse the request payload generated by `ApiRequest` macro
|
||||
|
||||
- **SecurityScheme** parse the security scheme generated by `SecurityScheme` macro
|
||||
|
||||
- **Result<T, ParseRequestError>** used to capture parsing errors
|
||||
|
||||
- **PoemExtractor** used poem's extractors
|
||||
|
||||
The return value can be any type that implements `poem::IntoResponse`.
|
||||
|
||||
```rust
|
||||
use poem_api::{
|
||||
OpenApi,
|
||||
poem_api::payload::Json,
|
||||
OpenApi,
|
||||
poem_api::payload::Json,
|
||||
param::{Path, Query},
|
||||
};
|
||||
use poem::Result;
|
||||
|
||||
@@ -44,19 +60,19 @@ impl Api {
|
||||
|
||||
/// Delete a pet
|
||||
#[oai(path = "/pet/:pet_id", method = "delete")]
|
||||
async fn delete_pet(&self, #[oai(name = "pet_id", in = "path")] id: u64) -> Result<()> {
|
||||
async fn delete_pet(&self, #[oai(name = "pet_id", in = "path")] id: Path<u64>) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Query pet by id
|
||||
#[oai(path = "/pet", method = "get")]
|
||||
async fn find_pet_by_id(&self, #[oai(name = "status", in = "query")] id: u64) -> Result<Json<Pet>> {
|
||||
async fn find_pet_by_id(&self, id: Query<u64>) -> Result<Json<Pet>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Query pets by status
|
||||
#[oai(path = "/pet/findByStatus", method = "get")]
|
||||
async fn find_pets_by_status(&self, #[oai(name = "status", in = "query")] status: Status) -> Result<Json<Vec<Pet>>> {
|
||||
async fn find_pets_by_status(&self, status: Query<Status>) -> Result<Json<Vec<Pet>>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ impl Api {
|
||||
#[oai(path = "/repo", method = "get")]
|
||||
async fn repo_list(
|
||||
&self,
|
||||
#[oai(auth("GithubScope::PublicRepo"))] auth: GithubAuthorization,
|
||||
#[oai(scope("GithubScope::PublicRepo"))] auth: GithubAuthorization,
|
||||
) -> Result<PlainText<String>> {
|
||||
// Use the token in GithubAuthorization to obtain all public repositories from Github.
|
||||
todo!()
|
||||
|
||||
@@ -2,18 +2,43 @@
|
||||
|
||||
下面定义一组API对宠物表进行增删改查的操作。
|
||||
|
||||
`add_pet`和`update_pet`用于添加和更新`Pet`对象,**这是我们在之前定义的基本类型,基本类型不能直接作为请求内容,需要使用一个`Payload`类型来包装它**,这样就可以确定内容的`Content-Type`。在下面的例子中,我们使用`payload::Json`来包装它,表示这两个API请求内容的`Content-Type`为`application/json`。
|
||||
一个方法代表一个API操作,必须使用`path`和`method`属性指定操作的路径和方法。
|
||||
|
||||
`find_pet_by_id`和`find_pets_by_status`用于查找`Pet`对象,它们的响应也是一个`Pet`对象,同样需要使用`Payload`类型来包装。
|
||||
方法的参数可以有多个,可以使用以下类型:
|
||||
|
||||
我们可以用`#[oai(name = "...", in = "...")]`来修饰一个函数参数用于指定此参数值的来源,`in`的值可以是`query`, `path`, `header`, `cookie`四种类型。`delete_pet`的`id`参数从路径中提取,`find_pet_by_id`和`find_pets_by_status`的参数从Query中获取。如果参数类型不是`Option<T>`,那么表示这个参数不是一个可选参数,提取失败时会返回`400 Bad Request`错误。
|
||||
- **poem_openapi::param::Query** 表示参数来自查询字符串
|
||||
|
||||
你可以定义多个函数参数,但只能有一个`Payload`类型作为请求内容,或者多个基本类型作为请求的参数。
|
||||
- **poem_openapi::param::Header** 表示参数来自请求头
|
||||
|
||||
- **poem_openapi::param::Path** 表示参数来自请求路径
|
||||
|
||||
- **poem_openapi::param::Cookie** 表示参数来自Cookie
|
||||
|
||||
- **poem_openapi::param::CookiePrivate** 表示参数来自加密的Cookie
|
||||
|
||||
- **poem_openapi::param::CookieSigned** 表示参数来自签名后的Cookie
|
||||
|
||||
- **poem_openapi::payload::Binary** 表示请求内容是二进制数据
|
||||
|
||||
- **poem_openapi::payload::Json** 表示请求内容用Json编码
|
||||
|
||||
- **poem_openapi::payload::PlainText** 表示请求内容是UTF8文本
|
||||
|
||||
- **ApiRequest** 使用`ApiRequest`宏生成的请求体
|
||||
|
||||
- **SecurityScheme** 使用`SecurityScheme`宏生成认证方法
|
||||
|
||||
- **Result<T, ParseRequestError>** 用于捕获解析的错误
|
||||
|
||||
- **PoemExtractor** 使用Poem的提取器
|
||||
|
||||
返回值可以是任意实现了`poem::IntoResponse`的类型。
|
||||
|
||||
```rust
|
||||
use poem_api::{
|
||||
OpenApi,
|
||||
poem_api::payload::Json,
|
||||
OpenApi,
|
||||
poem_api::payload::Json,
|
||||
param::{Path, Query},
|
||||
};
|
||||
use poem::Result;
|
||||
|
||||
@@ -34,20 +59,20 @@ impl Api {
|
||||
}
|
||||
|
||||
/// 删除一个Pet
|
||||
#[oai(path = "/pet/:pet_id", method = "delete")]
|
||||
async fn delete_pet(&self, #[oai(name = "pet_id", in = "path")] id: u64) -> Result<()> {
|
||||
#[oai(path = "/pet/:id", method = "delete")]
|
||||
async fn delete_pet(&self, id: Path<u64>) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 根据ID查询Pet
|
||||
#[oai(path = "/pet", method = "get")]
|
||||
async fn find_pet_by_id(&self, #[oai(name = "status", in = "query")] id: u64) -> Result<Json<Pet>> {
|
||||
async fn find_pet_by_id(&self, id: Query<u64>) -> Result<Json<Pet>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 根据状态查询Pet
|
||||
#[oai(path = "/pet/findByStatus", method = "get")]
|
||||
async fn find_pets_by_status(&self, #[oai(name = "status", in = "query")] status: Status) -> Result<Json<Vec<Pet>>> {
|
||||
async fn find_pets_by_status(&self, status: Query<Status>) -> Result<Json<Vec<Pet>>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Api {
|
||||
#[oai(path = "/repo", method = "get")]
|
||||
async fn repo_list(
|
||||
&self,
|
||||
#[oai(auth("GithubScope::PublicRepo"))] auth: GithubAuthorization,
|
||||
#[oai(scope("GithubScope::PublicRepo"))] auth: GithubAuthorization,
|
||||
) -> Result<PlainText<String>> {
|
||||
// 使用GithubAuthorization得到的token向Github获取所有公共仓库信息。
|
||||
todo!()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use poem::{listener::TcpListener, Request, Result, Route};
|
||||
use poem_openapi::{auth::ApiKey, payload::PlainText, OpenApi, OpenApiService, SecurityScheme};
|
||||
use poem_openapi::{
|
||||
auth::ApiKey, param::Query, payload::PlainText, OpenApi, OpenApiService, SecurityScheme,
|
||||
};
|
||||
|
||||
struct User {
|
||||
username: String,
|
||||
@@ -24,20 +26,17 @@ async fn api_checker(_: &Request, api_key: ApiKey) -> Option<User> {
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
#[allow(unused_variables)]
|
||||
impl Api {
|
||||
/// This is just a demo, so you can log in with any username and password.
|
||||
#[oai(path = "/login", method = "get")]
|
||||
async fn login(
|
||||
&self,
|
||||
#[oai(name = "user", in = "query")] user: String,
|
||||
#[oai(name = "password", in = "query")] _password: String,
|
||||
) -> PlainText<String> {
|
||||
PlainText(format!("key:{}", user))
|
||||
async fn login(&self, user: Query<String>, password: Query<String>) -> PlainText<String> {
|
||||
PlainText(format!("key:{}", user.0))
|
||||
}
|
||||
|
||||
/// This API returns the currently logged in user.
|
||||
#[oai(path = "/hello", method = "get")]
|
||||
async fn hello(&self, #[oai(auth)] auth: MyApiKeyAuthorization) -> PlainText<String> {
|
||||
async fn hello(&self, auth: MyApiKeyAuthorization) -> PlainText<String> {
|
||||
PlainText(auth.0.username)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,7 @@ struct Api;
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/basic", method = "get")]
|
||||
async fn auth_basic(
|
||||
&self,
|
||||
#[oai(auth)] auth: MyBasicAuthorization,
|
||||
) -> Result<PlainText<String>> {
|
||||
async fn auth_basic(&self, auth: MyBasicAuthorization) -> Result<PlainText<String>> {
|
||||
if auth.0.username != "test" || auth.0.password != "123456" {
|
||||
return Err(Error::new(StatusCode::UNAUTHORIZED));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl Api {
|
||||
#[oai(path = "/repositories", method = "get")]
|
||||
async fn repositories(
|
||||
&self,
|
||||
#[oai(auth("GithubScopes::PublicRepo"))] auth: GithubAuthorization,
|
||||
#[oai(scope = "GithubScopes::PublicRepo")] auth: GithubAuthorization,
|
||||
) -> Result<PlainText<String>> {
|
||||
let client = reqwest::Client::new();
|
||||
let text = client
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
use poem::{listener::TcpListener, Route};
|
||||
use poem_openapi::{payload::PlainText, OpenApi, OpenApiService};
|
||||
use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService};
|
||||
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/hello", method = "get")]
|
||||
async fn index(
|
||||
&self,
|
||||
#[oai(name = "name", in = "query")] name: Option<String>,
|
||||
) -> PlainText<String> {
|
||||
match name {
|
||||
async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
|
||||
match name.0 {
|
||||
Some(name) => PlainText(format!("hello, {}!", name)),
|
||||
None => PlainText("hello!".to_string()),
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use poem::{listener::TcpListener, web::Data, EndpointExt, Route};
|
||||
use poem_openapi::{payload::PlainText, OpenApi, OpenApiService};
|
||||
use poem_openapi::{payload::PlainText, OpenApi, OpenApiService, PoemExtractor};
|
||||
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/hello", method = "get")]
|
||||
async fn index(&self, #[oai(extract)] data: Data<&i32>) -> PlainText<String> {
|
||||
PlainText(format!("{}", data.0))
|
||||
async fn index(&self, data: PoemExtractor<Data<&i32>>) -> PlainText<String> {
|
||||
PlainText(format!("{}", data.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use poem::{error::BadRequest, listener::TcpListener, Result, Route};
|
||||
use poem_openapi::{
|
||||
param::Path,
|
||||
payload::{Binary, Json},
|
||||
types::multipart::Upload,
|
||||
ApiResponse, Multipart, Object, OpenApi, OpenApiService,
|
||||
@@ -67,7 +68,7 @@ impl Api {
|
||||
|
||||
/// Get file
|
||||
#[oai(path = "/files/:id", method = "get")]
|
||||
async fn get(&self, #[oai(name = "id", in = "path")] id: u64) -> GetFileResponse {
|
||||
async fn get(&self, id: Path<u64>) -> GetFileResponse {
|
||||
let status = self.status.lock().await;
|
||||
match status.files.get(&id) {
|
||||
Some(file) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use poem::{listener::TcpListener, Route};
|
||||
use poem_openapi::{
|
||||
payload::Json, types::Password, ApiResponse, Object, OpenApi, OpenApiService, Tags,
|
||||
param::Path, payload::Json, types::Password, ApiResponse, Object, OpenApi, OpenApiService, Tags,
|
||||
};
|
||||
use slab::Slab;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -88,12 +88,9 @@ impl Api {
|
||||
|
||||
/// Find user by id
|
||||
#[oai(path = "/users/:user_id", method = "get", tag = "ApiTags::User")]
|
||||
async fn find_user(
|
||||
&self,
|
||||
#[oai(name = "user_id", in = "path")] user_id: i64,
|
||||
) -> FindUserResponse {
|
||||
async fn find_user(&self, user_id: Path<i64>) -> FindUserResponse {
|
||||
let users = self.users.lock().await;
|
||||
match users.get(user_id as usize) {
|
||||
match users.get(user_id.0 as usize) {
|
||||
Some(user) => FindUserResponse::Ok(Json(user.clone())),
|
||||
None => FindUserResponse::NotFound,
|
||||
}
|
||||
@@ -101,12 +98,9 @@ impl Api {
|
||||
|
||||
/// Delete user by id
|
||||
#[oai(path = "/users/:user_id", method = "delete", tag = "ApiTags::User")]
|
||||
async fn delete_user(
|
||||
&self,
|
||||
#[oai(name = "user_id", in = "path")] user_id: i64,
|
||||
) -> DeleteUserResponse {
|
||||
async fn delete_user(&self, user_id: Path<i64>) -> DeleteUserResponse {
|
||||
let mut users = self.users.lock().await;
|
||||
let user_id = user_id as usize;
|
||||
let user_id = user_id.0 as usize;
|
||||
if users.contains(user_id) {
|
||||
users.remove(user_id);
|
||||
DeleteUserResponse::Ok
|
||||
@@ -117,13 +111,9 @@ impl Api {
|
||||
|
||||
/// Update user by id
|
||||
#[oai(path = "/users/:user_id", method = "put", tag = "ApiTags::User")]
|
||||
async fn put_user(
|
||||
&self,
|
||||
#[oai(name = "user_id", in = "path")] user_id: i64,
|
||||
update: Json<UpdateUser>,
|
||||
) -> UpdateUserResponse {
|
||||
async fn put_user(&self, user_id: Path<i64>, update: Json<UpdateUser>) -> UpdateUserResponse {
|
||||
let mut users = self.users.lock().await;
|
||||
match users.get_mut(user_id as usize) {
|
||||
match users.get_mut(user_id.0 as usize) {
|
||||
Some(user) => {
|
||||
if let Some(name) = update.0.name {
|
||||
user.name = name;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use darling::{util::SpannedValue, FromMeta};
|
||||
use http::header::HeaderName;
|
||||
use indexmap::IndexMap;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
AttributeArgs, Error, FnArg, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta, NestedMeta, Path,
|
||||
ext::IdentExt, AttributeArgs, Error, FnArg, ImplItem, ImplItemMethod, ItemImpl, Pat, Path,
|
||||
ReturnType,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common_args::{APIMethod, DefaultValue, ParamIn},
|
||||
common_args::{APIMethod, DefaultValue},
|
||||
error::GeneratorResult,
|
||||
utils::{
|
||||
convert_oai_path, get_crate_name, get_summary_and_description, optional_literal,
|
||||
@@ -42,51 +41,11 @@ struct APIOperation {
|
||||
operation_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Auth {
|
||||
scopes: Vec<Path>,
|
||||
}
|
||||
|
||||
impl FromMeta for Auth {
|
||||
fn from_meta(item: &Meta) -> darling::Result<Self> {
|
||||
match item {
|
||||
Meta::Path(_) => Ok(Default::default()),
|
||||
Meta::List(ls) => {
|
||||
let mut scopes = Vec::new();
|
||||
for item in &ls.nested {
|
||||
if let NestedMeta::Lit(Lit::Str(s)) = item {
|
||||
let path = syn::parse_str::<Path>(&s.value())?;
|
||||
scopes.push(path);
|
||||
} else {
|
||||
return Err(
|
||||
darling::Error::custom("Incorrect scope definitions.").with_span(item)
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Self { scopes })
|
||||
}
|
||||
Meta::NameValue(_) => Err(darling::Error::custom(
|
||||
"Incorrect scope definitions. #[oai(auth(\"read\", \"write\"))]",
|
||||
)
|
||||
.with_span(item)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromMeta, Default)]
|
||||
struct APIOperationParam {
|
||||
// for parameter
|
||||
#[darling(default)]
|
||||
name: Option<String>,
|
||||
#[darling(default, rename = "in")]
|
||||
param_in: Option<ParamIn>,
|
||||
#[darling(default)]
|
||||
private: bool,
|
||||
#[darling(default)]
|
||||
signed: bool,
|
||||
#[darling(default)]
|
||||
extract: bool,
|
||||
#[darling(default)]
|
||||
auth: Option<Auth>,
|
||||
#[darling(default)]
|
||||
desc: Option<String>,
|
||||
#[darling(default)]
|
||||
@@ -95,6 +54,10 @@ struct APIOperationParam {
|
||||
default: Option<DefaultValue>,
|
||||
#[darling(default)]
|
||||
validator: Option<Validators>,
|
||||
|
||||
// for oauth
|
||||
#[darling(multiple, default, rename = "scope")]
|
||||
scopes: Vec<Path>,
|
||||
}
|
||||
|
||||
struct Context {
|
||||
@@ -107,15 +70,11 @@ pub(crate) fn generate(
|
||||
args: AttributeArgs,
|
||||
mut item_impl: ItemImpl,
|
||||
) -> GeneratorResult<TokenStream> {
|
||||
let APIArgs {
|
||||
internal,
|
||||
prefix_path,
|
||||
common_tags,
|
||||
} = match APIArgs::from_list(&args) {
|
||||
let api_args = match APIArgs::from_list(&args) {
|
||||
Ok(args) => args,
|
||||
Err(err) => return Ok(err.write_errors()),
|
||||
};
|
||||
let crate_name = get_crate_name(internal);
|
||||
let crate_name = get_crate_name(api_args.internal);
|
||||
let ident = item_impl.self_ty.clone();
|
||||
let mut ctx = Context {
|
||||
add_routes: Default::default(),
|
||||
@@ -132,14 +91,7 @@ pub(crate) fn generate(
|
||||
);
|
||||
}
|
||||
|
||||
generate_operation(
|
||||
&mut ctx,
|
||||
&crate_name,
|
||||
&prefix_path,
|
||||
&common_tags,
|
||||
operation_args,
|
||||
method,
|
||||
)?;
|
||||
generate_operation(&mut ctx, &crate_name, &api_args, operation_args, method)?;
|
||||
remove_oai_attrs(&mut method.attrs);
|
||||
}
|
||||
}
|
||||
@@ -170,7 +122,7 @@ pub(crate) fn generate(
|
||||
|
||||
for (path, add_route) in add_routes {
|
||||
routes.push(quote! {
|
||||
at(#path, #crate_name::poem::RouteMethod::new()#(.#add_route)*)
|
||||
at(#path, #crate_name::__private::poem::RouteMethod::new()#(.#add_route)*)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,7 +143,7 @@ pub(crate) fn generate(
|
||||
#(#register_items)*
|
||||
}
|
||||
|
||||
fn add_routes(self, route: #crate_name::poem::Route) -> #crate_name::poem::Route {
|
||||
fn add_routes(self, route: #crate_name::__private::poem::Route) -> #crate_name::__private::poem::Route {
|
||||
let api_obj = ::std::sync::Arc::new(self);
|
||||
route #(.#routes)*
|
||||
}
|
||||
@@ -204,8 +156,7 @@ pub(crate) fn generate(
|
||||
fn generate_operation(
|
||||
ctx: &mut Context,
|
||||
crate_name: &TokenStream,
|
||||
prefix_path: &Option<SpannedValue<String>>,
|
||||
common_tags: &[Path],
|
||||
api_args: &APIArgs,
|
||||
args: APIOperation,
|
||||
item_method: &mut ImplItemMethod,
|
||||
) -> GeneratorResult<()> {
|
||||
@@ -222,9 +173,9 @@ fn generate_operation(
|
||||
let (summary, description) = get_summary_and_description(&item_method.attrs)?;
|
||||
let summary = optional_literal(&summary);
|
||||
let description = optional_literal(&description);
|
||||
let tags = common_tags.iter().chain(&tags);
|
||||
let tags = api_args.common_tags.iter().chain(&tags);
|
||||
|
||||
let (oai_path, new_path, path_vars) = convert_oai_path(&path, prefix_path)?;
|
||||
let (oai_path, new_path) = convert_oai_path(&path, &api_args.prefix_path)?;
|
||||
|
||||
if item_method.sig.inputs.is_empty() {
|
||||
return Err(Error::new_spanned(
|
||||
@@ -257,253 +208,135 @@ fn generate_operation(
|
||||
|
||||
let mut parse_args = Vec::new();
|
||||
let mut use_args = Vec::new();
|
||||
let mut has_request_payload = false;
|
||||
let mut request_meta = quote!(::std::option::Option::None);
|
||||
let mut request_meta = Vec::new();
|
||||
let mut params_meta = Vec::new();
|
||||
let mut security = quote!(::std::vec![]);
|
||||
let mut security = Vec::new();
|
||||
|
||||
for i in 1..item_method.sig.inputs.len() {
|
||||
let arg = &mut item_method.sig.inputs[i];
|
||||
let pat = match arg {
|
||||
FnArg::Typed(pat) => pat,
|
||||
let (arg_ident, arg_ty, operation_param) = match arg {
|
||||
FnArg::Typed(pat) => {
|
||||
if let Pat::Ident(ident) = &*pat.pat {
|
||||
let ident = ident.ident.clone();
|
||||
let operation_param =
|
||||
parse_oai_attrs::<APIOperationParam>(&pat.attrs)?.unwrap_or_default();
|
||||
remove_oai_attrs(&mut pat.attrs);
|
||||
(ident, pat.ty.clone(), operation_param)
|
||||
} else {
|
||||
return Err(Error::new_spanned(pat, "Invalid param definition.").into());
|
||||
}
|
||||
}
|
||||
FnArg::Receiver(_) => {
|
||||
return Err(Error::new_spanned(item_method, "Invalid method definition.").into());
|
||||
}
|
||||
};
|
||||
let pname = format_ident!("p{}", i);
|
||||
let arg_ty = &pat.ty;
|
||||
let param_name = operation_param
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| arg_ident.unraw().to_string());
|
||||
use_args.push(pname.clone());
|
||||
|
||||
let operation_param = parse_oai_attrs::<APIOperationParam>(&pat.attrs)?;
|
||||
remove_oai_attrs(&mut pat.attrs);
|
||||
// register
|
||||
ctx.register_items.push(quote! {
|
||||
<#arg_ty as #crate_name::ApiExtractor>::register(registry);
|
||||
});
|
||||
|
||||
match operation_param {
|
||||
// is poem extractor
|
||||
Some(operation_param) if operation_param.extract => {
|
||||
parse_args.push(quote! {
|
||||
let #pname = match <#arg_ty as #crate_name::poem::FromRequest>::from_request(&request, &mut body)
|
||||
.await
|
||||
.map_err(|err| #crate_name::ParseRequestError::Extractor(#crate_name::poem::IntoResponse::into_response(err)))
|
||||
{
|
||||
::std::result::Result::Ok(value) => value,
|
||||
::std::result::Result::Err(err) if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER => {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::poem::IntoResponse::into_response(resp);
|
||||
},
|
||||
::std::result::Result::Err(err) => return #crate_name::poem::IntoResponse::into_response(err),
|
||||
};
|
||||
});
|
||||
use_args.push(pname);
|
||||
// default value for parameter
|
||||
let default_value = match &operation_param.default {
|
||||
Some(DefaultValue::Default) => {
|
||||
quote!(::std::option::Option::Some(<#arg_ty as ::std::default::Default>::default))
|
||||
}
|
||||
|
||||
// is authorization extractor
|
||||
Some(operation_param) if operation_param.auth.is_some() => {
|
||||
let auth = operation_param.auth.as_ref().unwrap();
|
||||
parse_args.push(quote! {
|
||||
let #pname = match <#arg_ty as #crate_name::SecurityScheme>::from_request(&request, &query.0).await {
|
||||
::std::result::Result::Ok(value) => value,
|
||||
::std::result::Result::Err(err) if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER => {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::poem::IntoResponse::into_response(resp);
|
||||
},
|
||||
::std::result::Result::Err(err) => return #crate_name::poem::IntoResponse::into_response(err),
|
||||
};
|
||||
});
|
||||
use_args.push(pname);
|
||||
|
||||
let scopes = &auth.scopes;
|
||||
security = quote!(::std::vec![::std::collections::HashMap::from([
|
||||
(<#arg_ty as #crate_name::SecurityScheme>::NAME, ::std::vec![#(#crate_name::OAuthScopes::name(&#scopes)),*])
|
||||
])]);
|
||||
ctx.register_items
|
||||
.push(quote!(<#arg_ty as #crate_name::SecurityScheme>::register(registry);));
|
||||
Some(DefaultValue::Function(func_name)) => {
|
||||
quote!(::std::option::Option::Some(#func_name))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
let param_meta_default = match &operation_param.default {
|
||||
Some(DefaultValue::Default) => {
|
||||
quote!(::std::option::Option::Some(#crate_name::types::ToJSON::to_json(&<#arg_ty as ::std::default::Default>::default())))
|
||||
}
|
||||
Some(DefaultValue::Function(func_name)) => {
|
||||
quote!(::std::option::Option::Some(#crate_name::types::ToJSON::to_json(&#func_name())))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
|
||||
// is parameter
|
||||
Some(operation_param) => {
|
||||
let param_oai_typename = match &operation_param.name {
|
||||
Some(name) => name.clone(),
|
||||
None => {
|
||||
return Err(Error::new_spanned(
|
||||
arg,
|
||||
r#"Missing a name. #[oai(name = "...")]"#,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
let param_in = match operation_param.param_in {
|
||||
Some(param_in) => param_in,
|
||||
None => {
|
||||
return Err(Error::new_spanned(
|
||||
arg,
|
||||
r#"Missing a input type. #[oai(in = "...")]"#,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
if param_in == ParamIn::Path && !path_vars.contains(&*param_oai_typename) {
|
||||
return Err(Error::new_spanned(
|
||||
arg,
|
||||
format!(
|
||||
"The parameter `{}` is not defined in the path.",
|
||||
param_oai_typename
|
||||
),
|
||||
)
|
||||
.into());
|
||||
} else if param_in == ParamIn::Header
|
||||
&& HeaderName::try_from(¶m_oai_typename).is_err()
|
||||
{
|
||||
return Err(Error::new_spanned(
|
||||
arg,
|
||||
format!(
|
||||
"The parameter name `{}` is not a valid header name.",
|
||||
param_oai_typename
|
||||
),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let meta_in = {
|
||||
let meta_ty = match param_in {
|
||||
ParamIn::Path => quote!(Path),
|
||||
ParamIn::Query => quote!(Query),
|
||||
ParamIn::Header => quote!(Header),
|
||||
ParamIn::Cookie if operation_param.private => quote!(CookiePrivate),
|
||||
ParamIn::Cookie if operation_param.signed => quote!(CookieSigned),
|
||||
ParamIn::Cookie => quote!(Cookie),
|
||||
};
|
||||
quote!(#crate_name::registry::MetaParamIn::#meta_ty)
|
||||
};
|
||||
let validators = operation_param.validator.unwrap_or_default();
|
||||
let validators_checker =
|
||||
validators.create_param_checker(crate_name, &res_ty, ¶m_oai_typename)?;
|
||||
let validators_update_meta = validators.create_update_meta(crate_name)?;
|
||||
|
||||
match &operation_param.default {
|
||||
Some(default_value) => {
|
||||
let default_value = match default_value {
|
||||
DefaultValue::Default => {
|
||||
quote!(<#arg_ty as ::std::default::Default>::default())
|
||||
}
|
||||
DefaultValue::Function(func_name) => quote!(#func_name()),
|
||||
};
|
||||
|
||||
parse_args.push(quote! {
|
||||
let #pname = {
|
||||
let value = #crate_name::param::get(#param_oai_typename, #meta_in, &request, &query.0);
|
||||
let value = value.as_deref();
|
||||
match value {
|
||||
Some(value) => {
|
||||
match #crate_name::types::ParseFromParameter::parse_from_parameter(Some(value))
|
||||
.map_err(|err| #crate_name::ParseRequestError::ParseParam {
|
||||
name: #param_oai_typename,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
{
|
||||
::std::result::Result::Ok(value) => {
|
||||
#validators_checker
|
||||
value
|
||||
},
|
||||
::std::result::Result::Err(err) if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER => {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::poem::IntoResponse::into_response(resp);
|
||||
},
|
||||
::std::result::Result::Err(err) => return #crate_name::poem::IntoResponse::into_response(err),
|
||||
}
|
||||
}
|
||||
None => #default_value,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
None => {
|
||||
parse_args.push(quote! {
|
||||
let #pname = {
|
||||
let value = #crate_name::param::get(#param_oai_typename, #meta_in, &request, &query.0);
|
||||
match #crate_name::types::ParseFromParameter::parse_from_parameter(value.as_deref())
|
||||
.map_err(|err| #crate_name::ParseRequestError::ParseParam {
|
||||
name: #param_oai_typename,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
{
|
||||
::std::result::Result::Ok(value) => {
|
||||
#validators_checker
|
||||
value
|
||||
},
|
||||
::std::result::Result::Err(err) if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER => {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::poem::IntoResponse::into_response(resp);
|
||||
},
|
||||
::std::result::Result::Err(err) => return #crate_name::poem::IntoResponse::into_response(err),
|
||||
}
|
||||
};
|
||||
});
|
||||
// validator
|
||||
let validator = operation_param.validator.clone().unwrap_or_default();
|
||||
let param_checker = validator.create_param_checker(crate_name, &res_ty, ¶m_name)?.map(|stream| {
|
||||
quote! {
|
||||
if <#arg_ty as #crate_name::ApiExtractor>::TYPE == #crate_name::ApiExtractorType::Parameter {
|
||||
if let ::std::option::Option::Some(value) = #crate_name::ApiExtractor::param_raw_type(&#pname) {
|
||||
#stream
|
||||
}
|
||||
}
|
||||
}
|
||||
}).unwrap_or_default();
|
||||
let validators_update_meta = validator
|
||||
.create_update_meta(crate_name)?
|
||||
.unwrap_or_default();
|
||||
|
||||
let meta_arg_default = match &operation_param.default {
|
||||
Some(DefaultValue::Default) => quote! {
|
||||
::std::option::Option::Some(#crate_name::types::ToJSON::to_json(&<#arg_ty as ::std::default::Default>::default()))
|
||||
},
|
||||
Some(DefaultValue::Function(func_name)) => quote! {
|
||||
::std::option::Option::Some(#crate_name::types::ToJSON::to_json(&#func_name()))
|
||||
},
|
||||
None => quote!(::std::option::Option::None),
|
||||
// do extract
|
||||
parse_args.push(quote! {
|
||||
let mut param_opts = #crate_name::ExtractParamOptions {
|
||||
name: #param_name,
|
||||
default_value: #default_value,
|
||||
};
|
||||
|
||||
let #pname = match <#arg_ty as #crate_name::ApiExtractor>::from_request(&request, &mut body, param_opts).await {
|
||||
::std::result::Result::Ok(value) => value,
|
||||
::std::result::Result::Err(err) if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER => {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::__private::poem::IntoResponse::into_response(resp);
|
||||
}
|
||||
::std::result::Result::Err(err) => return #crate_name::__private::poem::IntoResponse::into_response(err),
|
||||
};
|
||||
#param_checker
|
||||
});
|
||||
|
||||
// param meta
|
||||
let param_desc = optional_literal(&operation_param.desc);
|
||||
let deprecated = operation_param.deprecated;
|
||||
params_meta.push(quote! {
|
||||
if <#arg_ty as #crate_name::ApiExtractor>::TYPE == #crate_name::ApiExtractorType::Parameter {
|
||||
let mut schema = <#arg_ty as #crate_name::ApiExtractor>::param_schema_ref().unwrap();
|
||||
|
||||
let mut patch_schema = {
|
||||
let mut schema = #crate_name::registry::MetaSchema::ANY;
|
||||
schema.default = #param_meta_default;
|
||||
#validators_update_meta
|
||||
schema
|
||||
};
|
||||
|
||||
use_args.push(pname);
|
||||
|
||||
let desc = optional_literal(&operation_param.desc);
|
||||
let deprecated = operation_param.deprecated;
|
||||
params_meta.push(quote! {
|
||||
#[allow(unused_mut)]
|
||||
#crate_name::registry::MetaOperationParam {
|
||||
name: #param_oai_typename,
|
||||
schema: {
|
||||
<#arg_ty as #crate_name::types::Type>::schema_ref().merge({
|
||||
let mut schema = #crate_name::registry::MetaSchema::ANY;
|
||||
schema.default = #meta_arg_default;
|
||||
#validators_update_meta
|
||||
schema
|
||||
})
|
||||
},
|
||||
in_type: #meta_in,
|
||||
description: #desc,
|
||||
required: <#arg_ty as #crate_name::types::Type>::IS_REQUIRED,
|
||||
deprecated: #deprecated,
|
||||
}
|
||||
});
|
||||
ctx.register_items
|
||||
.push(quote!(<#arg_ty as #crate_name::types::Type>::register(registry);));
|
||||
let meta_param = #crate_name::registry::MetaOperationParam {
|
||||
name: #param_name,
|
||||
schema: schema.merge(patch_schema),
|
||||
in_type: <#arg_ty as #crate_name::ApiExtractor>::param_in().unwrap(),
|
||||
description: #param_desc,
|
||||
required: <#arg_ty as #crate_name::ApiExtractor>::PARAM_IS_REQUIRED,
|
||||
deprecated: #deprecated,
|
||||
};
|
||||
params.push(meta_param);
|
||||
}
|
||||
});
|
||||
|
||||
// is request body
|
||||
None => {
|
||||
if has_request_payload {
|
||||
return Err(
|
||||
Error::new_spanned(arg, "Only one request payload is allowed.").into(),
|
||||
);
|
||||
}
|
||||
|
||||
parse_args.push(quote! {
|
||||
let #pname = match <#arg_ty as #crate_name::ApiRequest>::from_request(&request, &mut body).await {
|
||||
::std::result::Result::Ok(value) => value,
|
||||
::std::result::Result::Err(err) if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER => {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::poem::IntoResponse::into_response(resp);
|
||||
},
|
||||
::std::result::Result::Err(err) => return #crate_name::poem::IntoResponse::into_response(err),
|
||||
};
|
||||
});
|
||||
use_args.push(pname);
|
||||
|
||||
has_request_payload = true;
|
||||
request_meta = quote!(::std::option::Option::Some(<#arg_ty as #crate_name::ApiRequest>::meta()));
|
||||
ctx.register_items
|
||||
.push(quote!(<#arg_ty as #crate_name::ApiRequest>::register(registry);));
|
||||
// request object
|
||||
request_meta.push(quote! {
|
||||
if <#arg_ty as #crate_name::ApiExtractor>::TYPE == #crate_name::ApiExtractorType::RequestObject {
|
||||
request = <#arg_ty as #crate_name::ApiExtractor>::request_meta();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// security
|
||||
let scopes = &operation_param.scopes;
|
||||
security.push(quote! {
|
||||
if <#arg_ty as #crate_name::ApiExtractor>::TYPE == #crate_name::ApiExtractorType::SecurityScheme {
|
||||
security = ::std::vec![<::std::collections::HashMap<&'static str, ::std::vec::Vec<&'static str>> as ::std::convert::From<_>>::from([
|
||||
(<#arg_ty as #crate_name::ApiExtractor>::security_scheme().unwrap(), ::std::vec![#(#crate_name::OAuthScopes::name(&#scopes)),*])
|
||||
])];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctx.register_items
|
||||
@@ -516,16 +349,15 @@ fn generate_operation(
|
||||
});
|
||||
|
||||
ctx.add_routes.entry(new_path).or_default().push(quote! {
|
||||
method(#crate_name::poem::http::Method::#http_method, {
|
||||
method(#crate_name::__private::poem::http::Method::#http_method, {
|
||||
let api_obj = ::std::clone::Clone::clone(&api_obj);
|
||||
let ep = #crate_name::poem::endpoint::make(move |request| {
|
||||
let ep = #crate_name::__private::poem::endpoint::make(move |request| {
|
||||
let api_obj = ::std::clone::Clone::clone(&api_obj);
|
||||
async move {
|
||||
let (request, mut body) = request.split();
|
||||
let query = <#crate_name::poem::web::Query::<::std::collections::HashMap<::std::string::String, ::std::string::String>> as #crate_name::poem::FromRequest>::from_request(&request, &mut body).await.unwrap_or_default();
|
||||
#(#parse_args)*
|
||||
let resp = api_obj.#fn_ident(#(#use_args),*).await;
|
||||
#crate_name::poem::IntoResponse::into_response(resp)
|
||||
#crate_name::__private::poem::IntoResponse::into_response(resp)
|
||||
}
|
||||
});
|
||||
#transform
|
||||
@@ -544,14 +376,26 @@ fn generate_operation(
|
||||
ctx.operations.entry(oai_path).or_default().push(quote! {
|
||||
#crate_name::registry::MetaOperation {
|
||||
tags: ::std::vec![#(#tag_names),*],
|
||||
method: #crate_name::poem::http::Method::#http_method,
|
||||
method: #crate_name::__private::poem::http::Method::#http_method,
|
||||
summary: #summary,
|
||||
description: #description,
|
||||
params: ::std::vec![#(#params_meta),*],
|
||||
request: #request_meta,
|
||||
params: {
|
||||
let mut params = ::std::vec::Vec::new();
|
||||
#(#params_meta)*
|
||||
params
|
||||
},
|
||||
request: {
|
||||
let mut request = ::std::option::Option::None;
|
||||
#(#request_meta)*
|
||||
request
|
||||
},
|
||||
responses: <#res_ty as #crate_name::ApiResponse>::meta(),
|
||||
deprecated: #deprecated,
|
||||
security: #security,
|
||||
security: {
|
||||
let mut security = ::std::vec![];
|
||||
#(#security)*
|
||||
security
|
||||
},
|
||||
operation_id: #operation_id,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -105,9 +105,9 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #crate_name::types::ParseFromJSON for #ident {
|
||||
fn parse_from_json(value: #crate_name::serde_json::Value) -> #crate_name::types::ParseResult<Self> {
|
||||
fn parse_from_json(value: #crate_name::__private::serde_json::Value) -> #crate_name::types::ParseResult<Self> {
|
||||
match &value {
|
||||
#crate_name::serde_json::Value::String(item) => match item.as_str() {
|
||||
#crate_name::__private::serde_json::Value::String(item) => match item.as_str() {
|
||||
#(#item_to_ident,)*
|
||||
_ => ::std::result::Result::Err(#crate_name::types::ParseError::expected_type(value)),
|
||||
}
|
||||
@@ -129,17 +129,17 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #crate_name::types::ToJSON for #ident {
|
||||
fn to_json(&self) -> #crate_name::serde_json::Value {
|
||||
fn to_json(&self) -> #crate_name::__private::serde_json::Value {
|
||||
let name = match self {
|
||||
#(#ident_to_item),*
|
||||
};
|
||||
#crate_name::serde_json::Value::String(::std::string::ToString::to_string(name))
|
||||
#crate_name::__private::serde_json::Value::String(::std::string::ToString::to_string(name))
|
||||
}
|
||||
}
|
||||
|
||||
#[#crate_name::poem::async_trait]
|
||||
#[#crate_name::__private::poem::async_trait]
|
||||
impl #crate_name::types::ParseFromMultipartField for #ident {
|
||||
async fn parse_from_multipart(field: ::std::option::Option<#crate_name::poem::web::Field>) -> #crate_name::types::ParseResult<Self> {
|
||||
async fn parse_from_multipart(field: ::std::option::Option<#crate_name::__private::poem::web::Field>) -> #crate_name::types::ParseResult<Self> {
|
||||
use poem_openapi::types::ParseFromParameter;
|
||||
match field {
|
||||
::std::option::Option::Some(field) => {
|
||||
|
||||
@@ -90,18 +90,21 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
|
||||
fields.push(field_ident);
|
||||
|
||||
let parse_err = quote! {{
|
||||
let resp = #crate_name::__private::poem::Response::builder()
|
||||
.status(#crate_name::__private::poem::http::StatusCode::BAD_REQUEST)
|
||||
.body(::std::format!("failed to parse field `{}`: {}", #field_name, err.into_message()));
|
||||
#crate_name::ParseRequestError::ParseRequestBody(resp)
|
||||
}};
|
||||
|
||||
deserialize_fields.push(quote! {
|
||||
if field.name() == ::std::option::Option::Some(#field_name) {
|
||||
#field_ident = match #field_ident {
|
||||
::std::option::Option::Some(value) => {
|
||||
::std::option::Option::Some(<#field_ty as #crate_name::types::ParseFromMultipartField>::parse_from_repeated_field(value, field).await.map_err(|err|
|
||||
#crate_name::ParseRequestError::ParseRequestBody { reason: ::std::format!("failed to parse field `{}`: {}", #field_name, err.into_message()) }
|
||||
)?)
|
||||
::std::option::Option::Some(<#field_ty as #crate_name::types::ParseFromMultipartField>::parse_from_repeated_field(value, field).await.map_err(|err| #parse_err )?)
|
||||
}
|
||||
::std::option::Option::None => {
|
||||
::std::option::Option::Some(<#field_ty as #crate_name::types::ParseFromMultipartField>::parse_from_multipart(::std::option::Option::Some(field)).await.map_err(|err|
|
||||
#crate_name::ParseRequestError::ParseRequestBody { reason: ::std::format!("failed to parse field `{}`: {}", #field_name, err.into_message()) }
|
||||
)?)
|
||||
::std::option::Option::Some(<#field_ty as #crate_name::types::ParseFromMultipartField>::parse_from_multipart(::std::option::Option::Some(field)).await.map_err(|err| #parse_err )?)
|
||||
}
|
||||
};
|
||||
continue;
|
||||
@@ -136,7 +139,11 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
},
|
||||
::std::option::Option::None => {
|
||||
<#field_ty as #crate_name::types::ParseFromMultipartField>::parse_from_multipart(::std::option::Option::None).await.map_err(|_|
|
||||
#crate_name::ParseRequestError::ParseRequestBody { reason: ::std::format!("field `{}` is required", #field_name) }
|
||||
#crate_name::ParseRequestError::ParseRequestBody(
|
||||
#crate_name::__private::poem::Response::builder()
|
||||
.status(#crate_name::__private::poem::http::StatusCode::BAD_REQUEST)
|
||||
.body(::std::format!("field `{}` is required", #field_name))
|
||||
)
|
||||
)?
|
||||
}
|
||||
};
|
||||
@@ -208,27 +215,18 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
#[#crate_name::poem::async_trait]
|
||||
#[#crate_name::__private::poem::async_trait]
|
||||
impl #impl_generics #crate_name::payload::ParsePayload for #ident #ty_generics #where_clause {
|
||||
async fn from_request(request: &#crate_name::poem::Request, body: &mut #crate_name::poem::RequestBody) -> ::std::result::Result<Self, #crate_name::ParseRequestError> {
|
||||
if body.is_some() {
|
||||
let mut multipart = <#crate_name::poem::web::Multipart as #crate_name::poem::FromRequest>::from_request(request, body).await.map_err(|err| #crate_name::ParseRequestError::ParseRequestBody {
|
||||
reason: ::std::string::ToString::to_string(::std::convert::Into::<#crate_name::poem::Error>::into(err).reason().unwrap_or_default()),
|
||||
})?;
|
||||
#(#skip_fields)*
|
||||
#(let mut #fields = ::std::option::Option::None;)*
|
||||
while let ::std::option::Option::Some(field) = multipart.next_field().await.map_err(|err| #crate_name::ParseRequestError::ParseRequestBody {
|
||||
reason: ::std::string::ToString::to_string(::std::convert::Into::<#crate_name::poem::Error>::into(err).reason().unwrap_or_default()),
|
||||
})? {
|
||||
#(#deserialize_fields)*
|
||||
}
|
||||
#(#deserialize_none)*
|
||||
::std::result::Result::Ok(Self { #(#fields,)* #(#skip_idents),* })
|
||||
} else {
|
||||
::std::result::Result::Err(#crate_name::ParseRequestError::ParseRequestBody {
|
||||
reason: ::std::convert::Into::into("expect request body"),
|
||||
})
|
||||
async fn from_request(request: &#crate_name::__private::poem::Request, body: &mut #crate_name::__private::poem::RequestBody) -> ::std::result::Result<Self, #crate_name::ParseRequestError> {
|
||||
let mut multipart = <#crate_name::__private::poem::web::Multipart as #crate_name::__private::poem::FromRequest>::from_request(request, body).await
|
||||
.map_err(|err| #crate_name::ParseRequestError::ParseRequestBody(#crate_name::__private::poem::IntoResponse::into_response(err)))?;
|
||||
#(#skip_fields)*
|
||||
#(let mut #fields = ::std::option::Option::None;)*
|
||||
while let ::std::option::Option::Some(field) = multipart.next_field().await.map_err(|err| #crate_name::ParseRequestError::ParseRequestBody(#crate_name::__private::poem::IntoResponse::into_response(err)))? {
|
||||
#(#deserialize_fields)*
|
||||
}
|
||||
#(#deserialize_none)*
|
||||
::std::result::Result::Ok(Self { #(#fields,)* #(#skip_idents),* })
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
#[allow(non_snake_case)]
|
||||
let #field_ident: #field_ty = {
|
||||
match obj.get(#field_name).cloned().unwrap_or_default() {
|
||||
#crate_name::serde_json::Value::Null => #default_value,
|
||||
#crate_name::__private::serde_json::Value::Null => #default_value,
|
||||
value => {
|
||||
let value = #crate_name::types::ParseFromJSON::parse_from_json(value).map_err(#crate_name::types::ParseError::propagate)?;
|
||||
#validators_checker
|
||||
@@ -290,9 +290,9 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #impl_generics #crate_name::types::ParseFromJSON for #ident #ty_generics #where_clause {
|
||||
fn parse_from_json(value: #crate_name::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> {
|
||||
fn parse_from_json(value: #crate_name::__private::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> {
|
||||
match value {
|
||||
#crate_name::serde_json::Value::Object(obj) => {
|
||||
#crate_name::__private::serde_json::Value::Object(obj) => {
|
||||
#(#deserialize_fields)*
|
||||
::std::result::Result::Ok(Self { #(#fields),* })
|
||||
}
|
||||
@@ -302,23 +302,23 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #impl_generics #crate_name::types::ToJSON for #ident #ty_generics #where_clause {
|
||||
fn to_json(&self) -> #crate_name::serde_json::Value {
|
||||
let mut object = ::#crate_name::serde_json::Map::new();
|
||||
fn to_json(&self) -> #crate_name::__private::serde_json::Value {
|
||||
let mut object = ::#crate_name::__private::serde_json::Map::new();
|
||||
#(#serialize_fields)*
|
||||
#crate_name::serde_json::Value::Object(object)
|
||||
#crate_name::__private::serde_json::Value::Object(object)
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #crate_name::serde::Serialize for #ident #ty_generics #where_clause {
|
||||
fn serialize<S: #crate_name::serde::Serializer>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
impl #impl_generics #crate_name::__private::serde::Serialize for #ident #ty_generics #where_clause {
|
||||
fn serialize<S: #crate_name::__private::serde::Serializer>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
#crate_name::types::ToJSON::to_json(self).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl #de_impl_generics #crate_name::serde::Deserialize<'de> for #ident #ty_generics #where_clause {
|
||||
fn deserialize<D: #crate_name::serde::Deserializer<'de>>(deserializer: D) -> ::std::result::Result<Self, D::Error> {
|
||||
let value: #crate_name::serde_json::Value = #crate_name::serde::de::Deserialize::deserialize(deserializer)?;
|
||||
#crate_name::types::ParseFromJSON::parse_from_json(value).map_err(|err| #crate_name::serde::de::Error::custom(err.into_message()))
|
||||
impl #de_impl_generics #crate_name::__private::serde::Deserialize<'de> for #ident #ty_generics #where_clause {
|
||||
fn deserialize<D: #crate_name::__private::serde::Deserializer<'de>>(deserializer: D) -> ::std::result::Result<Self, D::Error> {
|
||||
let value: #crate_name::__private::serde_json::Value = #crate_name::__private::serde::de::Deserialize::deserialize(deserializer)?;
|
||||
#crate_name::types::ParseFromJSON::parse_from_json(value).map_err(|err| #crate_name::__private::serde::de::Error::custom(err.into_message()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,9 +332,9 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
registry.create_schema(name, |registry| #meta);
|
||||
}
|
||||
|
||||
fn __internal_parse_from_json(value: #crate_name::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> where Self: #crate_name::types::Type {
|
||||
fn __internal_parse_from_json(value: #crate_name::__private::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> where Self: #crate_name::types::Type {
|
||||
match value {
|
||||
#crate_name::serde_json::Value::Object(obj) => {
|
||||
#crate_name::__private::serde_json::Value::Object(obj) => {
|
||||
#(#deserialize_fields)*
|
||||
::std::result::Result::Ok(Self { #(#fields),* })
|
||||
}
|
||||
@@ -342,10 +342,10 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
fn __internal_to_json(&self) -> #crate_name::serde_json::Value where Self: #crate_name::types::Type {
|
||||
fn __internal_to_json(&self) -> #crate_name::__private::serde_json::Value where Self: #crate_name::types::Type {
|
||||
let mut object = ::serde_json::Map::new();
|
||||
#(#serialize_fields)*
|
||||
#crate_name::serde_json::Value::Object(object)
|
||||
#crate_name::__private::serde_json::Value::Object(object)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -379,27 +379,27 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #crate_name::types::ParseFromJSON for #concrete_type {
|
||||
fn parse_from_json(value: #crate_name::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> {
|
||||
fn parse_from_json(value: #crate_name::__private::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> {
|
||||
Self::__internal_parse_from_json(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl #crate_name::types::ToJSON for #concrete_type {
|
||||
fn to_json(&self) -> #crate_name::serde_json::Value {
|
||||
fn to_json(&self) -> #crate_name::__private::serde_json::Value {
|
||||
Self::__internal_to_json(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl #crate_name::serde::Serialize for #concrete_type {
|
||||
fn serialize<S: #crate_name::serde::Serializer>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
impl #crate_name::__private::serde::Serialize for #concrete_type {
|
||||
fn serialize<S: #crate_name::__private::serde::Serializer>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
#crate_name::types::ToJSON::to_json(self).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> #crate_name::serde::Deserialize<'de> for #concrete_type {
|
||||
fn deserialize<D: #crate_name::serde::Deserializer<'de>>(deserializer: D) -> ::std::result::Result<Self, D::Error> {
|
||||
let value: #crate_name::serde_json::Value = #crate_name::serde::de::Deserialize::deserialize(deserializer)?;
|
||||
#crate_name::types::ParseFromJSON::parse_from_json(value).map_err(|err| #crate_name::serde::de::Error::custom(err.into_message()))
|
||||
impl<'de> #crate_name::__private::serde::Deserialize<'de> for #concrete_type {
|
||||
fn deserialize<D: #crate_name::__private::serde::Deserializer<'de>>(deserializer: D) -> ::std::result::Result<Self, D::Error> {
|
||||
let value: #crate_name::__private::serde_json::Value = #crate_name::__private::serde::de::Deserialize::deserialize(deserializer)?;
|
||||
#crate_name::types::ParseFromJSON::parse_from_json(value).map_err(|err| #crate_name::__private::serde::de::Error::custom(err.into_message()))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -126,7 +126,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #crate_name::types::ParseFromJSON for #ident {
|
||||
fn parse_from_json(value: #crate_name::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> {
|
||||
fn parse_from_json(value: #crate_name::__private::serde_json::Value) -> ::std::result::Result<Self, #crate_name::types::ParseError<Self>> {
|
||||
match value.as_object().and_then(|obj| obj.get(#property_name)) {
|
||||
#(#from_json,)*
|
||||
_ => ::std::result::Result::Err(#crate_name::types::ParseError::expected_type(value)),
|
||||
@@ -135,23 +135,23 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
}
|
||||
|
||||
impl #crate_name::types::ToJSON for #ident {
|
||||
fn to_json(&self) -> #crate_name::serde_json::Value {
|
||||
fn to_json(&self) -> #crate_name::__private::serde_json::Value {
|
||||
match self {
|
||||
#(#to_json),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #crate_name::serde::Serialize for #ident {
|
||||
fn serialize<S: #crate_name::serde::Serializer>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
impl #crate_name::__private::serde::Serialize for #ident {
|
||||
fn serialize<S: #crate_name::__private::serde::Serializer>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
#crate_name::types::ToJSON::to_json(self).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> #crate_name::serde::Deserialize<'de> for #ident {
|
||||
fn deserialize<D: #crate_name::serde::Deserializer<'de>>(deserializer: D) -> ::std::result::Result<Self, D::Error> {
|
||||
let value: #crate_name::serde_json::Value = #crate_name::serde::de::Deserialize::deserialize(deserializer)?;
|
||||
#crate_name::types::ParseFromJSON::parse_from_json(value).map_err(|err| #crate_name::serde::de::Error::custom(err.into_message()))
|
||||
impl<'de> #crate_name::__private::serde::Deserialize<'de> for #ident {
|
||||
fn deserialize<D: #crate_name::__private::serde::Deserializer<'de>>(deserializer: D) -> ::std::result::Result<Self, D::Error> {
|
||||
let value: #crate_name::__private::serde_json::Value = #crate_name::__private::serde::de::Deserialize::deserialize(deserializer)?;
|
||||
#crate_name::types::ParseFromJSON::parse_from_json(value).map_err(|err| #crate_name::__private::serde::de::Error::custom(err.into_message()))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use darling::{
|
||||
ast::{Data, Fields},
|
||||
util::Ignored,
|
||||
@@ -49,6 +51,17 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
let mut content = Vec::new();
|
||||
let mut schemas = Vec::new();
|
||||
|
||||
let impl_generics = {
|
||||
let mut s = quote!(#impl_generics).to_string();
|
||||
match s.find('<') {
|
||||
Some(pos) => {
|
||||
s.insert_str(pos + 1, "'__request,");
|
||||
TokenStream::from_str(&s).unwrap()
|
||||
}
|
||||
_ => quote!(<'__request>),
|
||||
}
|
||||
};
|
||||
|
||||
for variant in e {
|
||||
let item_ident = &variant.ident;
|
||||
|
||||
@@ -81,21 +94,30 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
|
||||
let expanded = {
|
||||
quote! {
|
||||
#[#crate_name::poem::async_trait]
|
||||
impl #impl_generics #crate_name::ApiRequest for #ident #ty_generics #where_clause {
|
||||
fn meta() -> #crate_name::registry::MetaRequest {
|
||||
#crate_name::registry::MetaRequest {
|
||||
description: #description,
|
||||
content: ::std::vec![#(#content),*],
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
#[#crate_name::__private::poem::async_trait]
|
||||
impl #impl_generics #crate_name::ApiExtractor<'__request> for #ident #ty_generics #where_clause {
|
||||
const TYPE: #crate_name::ApiExtractorType = #crate_name::ApiExtractorType::RequestObject;
|
||||
|
||||
type ParamType = ();
|
||||
type ParamRawType = ();
|
||||
|
||||
fn register(registry: &mut #crate_name::registry::Registry) {
|
||||
#(<#schemas as #crate_name::payload::Payload>::register(registry);)*
|
||||
}
|
||||
|
||||
async fn from_request(request: &#crate_name::poem::Request, body: &mut #crate_name::poem::RequestBody) -> ::std::result::Result<Self, #crate_name::ParseRequestError> {
|
||||
fn request_meta() -> ::std::option::Option<#crate_name::registry::MetaRequest> {
|
||||
::std::option::Option::Some(#crate_name::registry::MetaRequest {
|
||||
description: #description,
|
||||
content: ::std::vec![#(#content),*],
|
||||
required: true,
|
||||
})
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'__request #crate_name::__private::poem::Request,
|
||||
body: &mut #crate_name::__private::poem::RequestBody,
|
||||
_param_opts: #crate_name::ExtractParamOptions<Self::ParamType>,
|
||||
) -> ::std::result::Result<Self, #crate_name::ParseRequestError> {
|
||||
let content_type = request.content_type();
|
||||
match content_type {
|
||||
#(#from_requests)*
|
||||
|
||||
@@ -105,7 +105,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
let payload_ty = &values[1].ty;
|
||||
into_responses.push(quote! {
|
||||
#ident::#item_ident(status, payload, #(#match_headers),*) => {
|
||||
let mut resp = #crate_name::poem::IntoResponse::into_response(payload);
|
||||
let mut resp = #crate_name::__private::poem::IntoResponse::into_response(payload);
|
||||
resp.set_status(status);
|
||||
#(#with_headers)*
|
||||
resp
|
||||
@@ -131,8 +131,8 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
let status = get_status(variant.ident.span(), variant.status)?;
|
||||
into_responses.push(quote! {
|
||||
#ident::#item_ident(payload, #(#match_headers),*) => {
|
||||
let mut resp = #crate_name::poem::IntoResponse::into_response(payload);
|
||||
resp.set_status(#crate_name::poem::http::StatusCode::from_u16(#status).unwrap());
|
||||
let mut resp = #crate_name::__private::poem::IntoResponse::into_response(payload);
|
||||
resp.set_status(#crate_name::__private::poem::http::StatusCode::from_u16(#status).unwrap());
|
||||
#(#with_headers)*
|
||||
resp
|
||||
}
|
||||
@@ -156,9 +156,9 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
let status = get_status(variant.ident.span(), variant.status)?;
|
||||
into_responses.push(quote! {
|
||||
#ident::#item_ident => {
|
||||
let status = #crate_name::poem::http::StatusCode::from_u16(#status).unwrap();
|
||||
let status = #crate_name::__private::poem::http::StatusCode::from_u16(#status).unwrap();
|
||||
#[allow(unused_mut)]
|
||||
let mut resp = #crate_name::poem::IntoResponse::into_response(status);
|
||||
let mut resp = #crate_name::__private::poem::IntoResponse::into_response(status);
|
||||
#(#with_headers)*
|
||||
resp
|
||||
}
|
||||
@@ -178,9 +178,9 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
let status = get_status(variant.ident.span(), variant.status)?;
|
||||
into_responses.push(quote! {
|
||||
#ident::#item_ident(#(#match_headers),*) => {
|
||||
let status = #crate_name::poem::http::StatusCode::from_u16(#status).unwrap();
|
||||
let status = #crate_name::__private::poem::http::StatusCode::from_u16(#status).unwrap();
|
||||
#[allow(unused_mut)]
|
||||
let mut resp = #crate_name::poem::IntoResponse::into_response(status);
|
||||
let mut resp = #crate_name::__private::poem::IntoResponse::into_response(status);
|
||||
#(#with_headers)*
|
||||
resp
|
||||
}
|
||||
@@ -224,8 +224,8 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
|
||||
let expanded = {
|
||||
quote! {
|
||||
impl #impl_generics #crate_name::poem::IntoResponse for #ident #ty_generics #where_clause {
|
||||
fn into_response(self) -> #crate_name::poem::Response {
|
||||
impl #impl_generics #crate_name::__private::poem::IntoResponse for #ident #ty_generics #where_clause {
|
||||
fn into_response(self) -> #crate_name::__private::poem::Response {
|
||||
match self {
|
||||
#(#into_responses)*
|
||||
}
|
||||
|
||||
@@ -283,6 +283,7 @@ impl SecuritySchemeArgs {
|
||||
fn generate_register_security_scheme(
|
||||
&self,
|
||||
crate_name: &TokenStream,
|
||||
name: &str,
|
||||
) -> GeneratorResult<TokenStream> {
|
||||
let description = get_description(&self.attrs)?;
|
||||
let description = optional_literal(&description);
|
||||
@@ -312,7 +313,7 @@ impl SecuritySchemeArgs {
|
||||
let ts = match self.ty {
|
||||
AuthType::ApiKey => {
|
||||
quote! {
|
||||
registry.create_security_scheme(Self::NAME, #crate_name::registry::MetaSecurityScheme {
|
||||
registry.create_security_scheme(#name, #crate_name::registry::MetaSecurityScheme {
|
||||
ty: "apiKey",
|
||||
description: #description,
|
||||
name: #key_name,
|
||||
@@ -326,7 +327,7 @@ impl SecuritySchemeArgs {
|
||||
}
|
||||
AuthType::Basic => {
|
||||
quote! {
|
||||
registry.create_security_scheme(Self::NAME, #crate_name::registry::MetaSecurityScheme {
|
||||
registry.create_security_scheme(#name, #crate_name::registry::MetaSecurityScheme {
|
||||
ty: "http",
|
||||
description: #description,
|
||||
name: ::std::option::Option::None,
|
||||
@@ -340,7 +341,7 @@ impl SecuritySchemeArgs {
|
||||
}
|
||||
AuthType::Bearer => {
|
||||
quote! {
|
||||
registry.create_security_scheme(Self::NAME, #crate_name::registry::MetaSecurityScheme {
|
||||
registry.create_security_scheme(#name, #crate_name::registry::MetaSecurityScheme {
|
||||
ty: "http",
|
||||
description: #description,
|
||||
name: ::std::option::Option::None,
|
||||
@@ -355,7 +356,7 @@ impl SecuritySchemeArgs {
|
||||
AuthType::OAuth2 => {
|
||||
let flows = self.flows.as_ref().unwrap().generate_meta(crate_name)?;
|
||||
quote! {
|
||||
registry.create_security_scheme(Self::NAME, #crate_name::registry::MetaSecurityScheme {
|
||||
registry.create_security_scheme(#name, #crate_name::registry::MetaSecurityScheme {
|
||||
ty: "oauth2",
|
||||
description: #description,
|
||||
name: ::std::option::Option::None,
|
||||
@@ -369,7 +370,7 @@ impl SecuritySchemeArgs {
|
||||
}
|
||||
AuthType::OpenIdConnect => {
|
||||
quote! {
|
||||
registry.create_security_scheme(Self::NAME, #crate_name::registry::MetaSecurityScheme {
|
||||
registry.create_security_scheme(#name, #crate_name::registry::MetaSecurityScheme {
|
||||
ty: "openIdConnect",
|
||||
description: #description,
|
||||
name: ::std::option::Option::None,
|
||||
@@ -441,7 +442,8 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
.into());
|
||||
}
|
||||
|
||||
let register_security_scheme = args.generate_register_security_scheme(&crate_name)?;
|
||||
let register_security_scheme =
|
||||
args.generate_register_security_scheme(&crate_name, &oai_typename)?;
|
||||
let from_request = args.generate_from_request(&crate_name);
|
||||
let checker = match &args.checker {
|
||||
Some(name) => match syn::parse_str::<Path>(name) {
|
||||
@@ -456,15 +458,27 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
#[#crate_name::poem::async_trait]
|
||||
impl #crate_name::SecurityScheme for #ident {
|
||||
const NAME: &'static str = #oai_typename;
|
||||
#[#crate_name::__private::poem::async_trait]
|
||||
impl<'a> #crate_name::ApiExtractor<'a> for #ident {
|
||||
const TYPE: #crate_name::ApiExtractorType = #crate_name::ApiExtractorType::SecurityScheme;
|
||||
|
||||
type ParamType = ();
|
||||
type ParamRawType = ();
|
||||
|
||||
fn register(registry: &mut #crate_name::registry::Registry) {
|
||||
#register_security_scheme
|
||||
}
|
||||
|
||||
async fn from_request(req: &#crate_name::poem::Request, query: &::std::collections::HashMap<::std::string::String, ::std::string::String>) -> ::std::result::Result<Self, #crate_name::ParseRequestError> {
|
||||
fn security_scheme() -> ::std::option::Option<&'static str> {
|
||||
::std::option::Option::Some(#oai_typename)
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
req: &'a #crate_name::__private::poem::Request,
|
||||
body: &mut #crate_name::__private::poem::RequestBody,
|
||||
_param_opts: #crate_name::ExtractParamOptions<Self::ParamType>,
|
||||
) -> ::std::result::Result<Self, #crate_name::ParseRequestError> {
|
||||
let query = req.extensions().get::<#crate_name::__private::UrlQuery>().unwrap();
|
||||
let output = #from_request?;
|
||||
#checker
|
||||
::std::result::Result::Ok(Self(output))
|
||||
|
||||
@@ -94,20 +94,19 @@ pub(crate) fn parse_oai_attrs<T: FromMeta>(attrs: &[Attribute]) -> GeneratorResu
|
||||
pub(crate) fn convert_oai_path<'a, 'b: 'a>(
|
||||
path: &'a SpannedValue<String>,
|
||||
prefix_path: &'b Option<SpannedValue<String>>,
|
||||
) -> Result<(String, String, HashSet<&'a str>)> {
|
||||
) -> Result<(String, String)> {
|
||||
if !path.starts_with('/') {
|
||||
return Err(Error::new(path.span(), "The path must start with '/'."));
|
||||
}
|
||||
|
||||
let mut vars = HashSet::new();
|
||||
let mut oai_path = String::new();
|
||||
let mut new_path = String::new();
|
||||
|
||||
if let Some(prefix_path) = prefix_path {
|
||||
handle_path(prefix_path, &mut vars, &mut oai_path, &mut new_path)?;
|
||||
handle_path(prefix_path, &mut oai_path, &mut new_path)?;
|
||||
}
|
||||
|
||||
handle_path(path, &mut vars, &mut oai_path, &mut new_path)?;
|
||||
handle_path(path, &mut oai_path, &mut new_path)?;
|
||||
|
||||
if oai_path.is_empty() {
|
||||
oai_path += "/";
|
||||
@@ -117,15 +116,16 @@ pub(crate) fn convert_oai_path<'a, 'b: 'a>(
|
||||
new_path += "/";
|
||||
}
|
||||
|
||||
Ok((oai_path, new_path, vars))
|
||||
Ok((oai_path, new_path))
|
||||
}
|
||||
|
||||
fn handle_path<'a>(
|
||||
path: &'a SpannedValue<String>,
|
||||
vars: &mut HashSet<&'a str>,
|
||||
oai_path: &mut String,
|
||||
new_path: &mut String,
|
||||
) -> Result<()> {
|
||||
let mut vars = HashSet::new();
|
||||
|
||||
for s in path.split('/') {
|
||||
if s.is_empty() {
|
||||
continue;
|
||||
|
||||
@@ -155,18 +155,16 @@ impl Validators {
|
||||
Ok(Some(quote! {
|
||||
#(
|
||||
let validator = #validators;
|
||||
if let ::std::option::Option::Some(value) = #crate_name::types::Type::as_raw_value(&value) {
|
||||
if !#crate_name::validation::Validator::check(&validator, value) {
|
||||
let err = #crate_name::ParseRequestError::ParseParam {
|
||||
name: #arg_name,
|
||||
reason: ::std::format!("verification failed. {}", validator),
|
||||
};
|
||||
if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::poem::IntoResponse::into_response(resp);
|
||||
} else {
|
||||
return #crate_name::poem::IntoResponse::into_response(err);
|
||||
}
|
||||
if !#crate_name::validation::Validator::check(&validator, value) {
|
||||
let err = #crate_name::ParseRequestError::ParseParam {
|
||||
name: #arg_name,
|
||||
reason: ::std::format!("verification failed. {}", validator),
|
||||
};
|
||||
if <#res_ty as #crate_name::ApiResponse>::BAD_REQUEST_HANDLER {
|
||||
let resp = <#res_ty as #crate_name::ApiResponse>::from_parse_request_error(err);
|
||||
return #crate_name::__private::poem::IntoResponse::into_response(resp);
|
||||
} else {
|
||||
return #crate_name::__private::poem::IntoResponse::into_response(err);
|
||||
}
|
||||
}
|
||||
)*
|
||||
@@ -189,9 +187,11 @@ impl Validators {
|
||||
let validator = #validators;
|
||||
if let ::std::option::Option::Some(value) = #crate_name::types::Type::as_raw_value(&value) {
|
||||
if !#crate_name::validation::Validator::check(&validator, value) {
|
||||
return Err(#crate_name::ParseRequestError::ParseRequestBody {
|
||||
reason: ::std::format!("field `{}` verification failed. {}", #field_name, validator),
|
||||
});
|
||||
return Err(#crate_name::ParseRequestError::ParseRequestBody(
|
||||
#crate_name::__private::poem::Response::builder()
|
||||
.status(#crate_name::__private::poem::http::StatusCode::BAD_REQUEST)
|
||||
.body(::std::format!("field `{}` verification failed. {}", #field_name, validator))
|
||||
));
|
||||
}
|
||||
}
|
||||
)*
|
||||
@@ -204,9 +204,11 @@ impl Validators {
|
||||
for item in value {
|
||||
if let ::std::option::Option::Some(item) = #crate_name::types::Type::as_raw_value(item) {
|
||||
if !#crate_name::validation::Validator::check(&validator, item) {
|
||||
return Err(#crate_name::ParseRequestError::ParseRequestBody {
|
||||
reason: ::std::format!("field `{}` verification failed. {}", #field_name, validator),
|
||||
});
|
||||
return Err(#crate_name::ParseRequestError::ParseRequestBody(
|
||||
#crate_name::__private::poem::Response::builder()
|
||||
.status(#crate_name::__private::poem::http::StatusCode::BAD_REQUEST)
|
||||
.body(::std::format!("field `{}` verification failed. {}", #field_name, validator))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
# Unreleased
|
||||
|
||||
- Add `list` attribute to the validator.
|
||||
- Rework `OpenAPI` macro.
|
||||
|
||||
# [1.0.28] 2021-11-17
|
||||
|
||||
|
||||
@@ -61,18 +61,15 @@ This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in
|
||||
|
||||
```rust
|
||||
use poem::{listener::TcpListener, Route};
|
||||
use poem_openapi::{payload::PlainText, OpenApi, OpenApiService};
|
||||
use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService};
|
||||
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/hello", method = "get")]
|
||||
async fn index(
|
||||
&self,
|
||||
#[oai(name = "name", in = "query")] name: Option<String>,
|
||||
) -> PlainText<String> {
|
||||
match name {
|
||||
async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
|
||||
match name.0 {
|
||||
Some(name) => PlainText(format!("hello, {}!", name)),
|
||||
None => PlainText("hello!".to_string()),
|
||||
}
|
||||
@@ -82,10 +79,9 @@ impl Api {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
let listener = TcpListener::bind("127.0.0.1:3000");
|
||||
let api_service = OpenApiService::new(Api)
|
||||
.title("Hello World")
|
||||
.server("http://localhost:3000/api");
|
||||
let ui = api_service.swagger_ui("http://localhost:3000");
|
||||
let api_service =
|
||||
OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api");
|
||||
let ui = api_service.swagger_ui();
|
||||
|
||||
poem::Server::new(listener)
|
||||
.await?
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use poem::Request;
|
||||
|
||||
use crate::{auth::ApiKeyAuthorization, registry::MetaParamIn, ParseRequestError};
|
||||
use crate::{auth::ApiKeyAuthorization, base::UrlQuery, registry::MetaParamIn, ParseRequestError};
|
||||
|
||||
/// Used to extract the Api Key from the request.
|
||||
pub struct ApiKey {
|
||||
@@ -13,7 +11,7 @@ pub struct ApiKey {
|
||||
impl ApiKeyAuthorization for ApiKey {
|
||||
fn from_request(
|
||||
req: &Request,
|
||||
query: &HashMap<String, String>,
|
||||
query: &UrlQuery,
|
||||
name: &str,
|
||||
in_type: MetaParamIn,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
|
||||
@@ -4,14 +4,10 @@ mod api_key;
|
||||
mod basic;
|
||||
mod bearer;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use api_key::ApiKey;
|
||||
pub use basic::Basic;
|
||||
pub use bearer::Bearer;
|
||||
use poem::Request;
|
||||
|
||||
use crate::{registry::MetaParamIn, ParseRequestError};
|
||||
pub use self::{api_key::ApiKey, basic::Basic, bearer::Bearer};
|
||||
use crate::{base::UrlQuery, registry::MetaParamIn, ParseRequestError};
|
||||
|
||||
/// Represents a basic authorization extractor.
|
||||
pub trait BasicAuthorization: Sized {
|
||||
@@ -30,7 +26,7 @@ pub trait ApiKeyAuthorization: Sized {
|
||||
/// Extract from the HTTP request.
|
||||
fn from_request(
|
||||
req: &Request,
|
||||
query: &HashMap<String, String>,
|
||||
query: &UrlQuery,
|
||||
name: &str,
|
||||
in_type: MetaParamIn,
|
||||
) -> Result<Self, ParseRequestError>;
|
||||
|
||||
@@ -1,54 +1,143 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use mime::Mime;
|
||||
use poem::{IntoResponse, Request, RequestBody, Result, Route};
|
||||
use poem::{FromRequest, IntoResponse, Request, RequestBody, Result, Route};
|
||||
|
||||
use crate::{
|
||||
payload::{ParsePayload, Payload},
|
||||
registry::{
|
||||
MetaApi, MetaMediaType, MetaOAuthScope, MetaRequest, MetaResponse, MetaResponses, Registry,
|
||||
MetaApi, MetaMediaType, MetaOAuthScope, MetaParamIn, MetaRequest, MetaResponse,
|
||||
MetaResponses, MetaSchemaRef, Registry,
|
||||
},
|
||||
ParseRequestError,
|
||||
};
|
||||
|
||||
/// Represents a OpenAPI request object.
|
||||
///
|
||||
/// Reference: <https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#requestBodyObject>
|
||||
/// API extractor types.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ApiExtractorType {
|
||||
/// A request object.
|
||||
RequestObject,
|
||||
|
||||
/// A request parameter.
|
||||
Parameter,
|
||||
|
||||
/// A security scheme.
|
||||
SecurityScheme,
|
||||
|
||||
/// A poem extractor.
|
||||
PoemExtractor,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct UrlQuery(pub BTreeMap<String, String>);
|
||||
|
||||
impl Deref for UrlQuery {
|
||||
type Target = BTreeMap<String, String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for the parameter extractor.
|
||||
pub struct ExtractParamOptions<T> {
|
||||
/// The name of this parameter.
|
||||
pub name: &'static str,
|
||||
|
||||
/// The default value of this parameter.
|
||||
pub default_value: Option<fn() -> T>,
|
||||
}
|
||||
|
||||
impl<T> Default for ExtractParamOptions<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "",
|
||||
default_value: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a OpenAPI extractor.
|
||||
#[poem::async_trait]
|
||||
pub trait ApiRequest: Sized {
|
||||
/// Gets metadata of this request.
|
||||
fn meta() -> MetaRequest;
|
||||
#[allow(unused_variables)]
|
||||
pub trait ApiExtractor<'a>: Sized {
|
||||
/// The type of API extractor.
|
||||
const TYPE: ApiExtractorType;
|
||||
|
||||
/// Register the schema contained in this request object to the registry.
|
||||
fn register(registry: &mut Registry);
|
||||
/// If it is `true`, it means that this parameter is required.
|
||||
const PARAM_IS_REQUIRED: bool = false;
|
||||
|
||||
/// Parse the request object from the HTTP request.
|
||||
/// The parameter type.
|
||||
type ParamType;
|
||||
|
||||
/// The raw parameter type for validators.
|
||||
type ParamRawType;
|
||||
|
||||
/// Register related types to registry.
|
||||
fn register(registry: &mut Registry) {}
|
||||
|
||||
/// Returns name of security scheme if this extractor is security scheme.
|
||||
fn security_scheme() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the location of the parameter if this extractor is parameter.
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the schema of the parameter if this extractor is parameter.
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `MetaRequest` if this extractor is request object.
|
||||
fn request_meta() -> Option<MetaRequest> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a reference to the raw type of this parameter.
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse from the HTTP request.
|
||||
async fn from_request(
|
||||
request: &Request,
|
||||
request: &'a Request,
|
||||
body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError>;
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<T: Payload + ParsePayload> ApiRequest for T {
|
||||
fn meta() -> MetaRequest {
|
||||
MetaRequest {
|
||||
impl<'a, T: Payload + ParsePayload> ApiExtractor<'a> for T {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::RequestObject;
|
||||
|
||||
type ParamType = ();
|
||||
type ParamRawType = ();
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn request_meta() -> Option<MetaRequest> {
|
||||
Some(MetaRequest {
|
||||
description: None,
|
||||
content: vec![MetaMediaType {
|
||||
content_type: T::CONTENT_TYPE,
|
||||
schema: T::schema_ref(),
|
||||
}],
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
})
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &Request,
|
||||
request: &'a Request,
|
||||
body: &mut RequestBody,
|
||||
_param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
match request.content_type() {
|
||||
Some(content_type) => {
|
||||
@@ -74,6 +163,85 @@ impl<T: Payload + ParsePayload> ApiRequest for T {
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ApiExtractor<'a>> ApiExtractor<'a> for Result<T, ParseRequestError> {
|
||||
const TYPE: ApiExtractorType = T::TYPE;
|
||||
const PARAM_IS_REQUIRED: bool = T::PARAM_IS_REQUIRED;
|
||||
type ParamType = T::ParamType;
|
||||
type ParamRawType = T::ParamRawType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn security_scheme() -> Option<&'static str> {
|
||||
T::security_scheme()
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
T::param_in()
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
T::param_schema_ref()
|
||||
}
|
||||
|
||||
fn request_meta() -> Option<MetaRequest> {
|
||||
T::request_meta()
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
match self {
|
||||
Ok(value) => value.param_raw_type(),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
Ok(T::from_request(request, body, param_opts).await)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a poem extractor.
|
||||
pub struct PoemExtractor<T>(pub T);
|
||||
|
||||
impl<T> Deref for PoemExtractor<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for PoemExtractor<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: FromRequest<'a>> ApiExtractor<'a> for PoemExtractor<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::PoemExtractor;
|
||||
|
||||
type ParamType = ();
|
||||
type ParamRawType = ();
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
body: &mut RequestBody,
|
||||
_param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
match T::from_request(request, body).await {
|
||||
Ok(value) => Ok(Self(value)),
|
||||
Err(err) => Err(ParseRequestError::Extractor(err.into_response())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a OpenAPI responses object.
|
||||
///
|
||||
/// Reference: <https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#responsesObject>
|
||||
@@ -129,22 +297,6 @@ pub trait Tags {
|
||||
fn name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Represents a OpenAPI security scheme.
|
||||
#[poem::async_trait]
|
||||
pub trait SecurityScheme: Sized {
|
||||
/// The name of security scheme.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Register this security scheme type to registry.
|
||||
fn register(registry: &mut Registry);
|
||||
|
||||
/// Parse authorization information from request.
|
||||
async fn from_request(
|
||||
req: &Request,
|
||||
query: &HashMap<String, String>,
|
||||
) -> Result<Self, ParseRequestError>;
|
||||
}
|
||||
|
||||
/// Represents a OAuth scopes.
|
||||
pub trait OAuthScopes {
|
||||
/// Gets metadata of this object.
|
||||
@@ -154,22 +306,6 @@ pub trait OAuthScopes {
|
||||
fn name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<T: SecurityScheme> SecurityScheme for Option<T> {
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
req: &Request,
|
||||
query: &HashMap<String, String>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
Ok(T::from_request(req, query).await.ok())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a OpenAPI object.
|
||||
pub trait OpenApi: Sized {
|
||||
/// Gets metadata of this API object.
|
||||
|
||||
@@ -13,15 +13,15 @@ Define a OpenAPI payload.
|
||||
| skip | Skip this field | bool | Y |
|
||||
| rename | Rename the field | string | Y |
|
||||
| default | Default value | bool,string | Y |
|
||||
| multiple_of | The value of "multiple_of" MUST be a number, strictly greater than 0. A numeric instance is only valid if division by this value results in an integer. | number | Y |
|
||||
| maximum | The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. If `exclusive` is `true` and instance is less than the provided value, or else if the instance is less than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| minimum | The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. If `exclusive` is `true` and instance is greater than the provided value, or else if the instance is greater than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| max_length | The value of "max_length" MUST be a non-negative integer. A string instance is valid against this validator if its length is less than, or equal to, the value. | usize | Y |
|
||||
| min_length | The value of "min_length" MUST be a non-negative integer. The value of this validator MUST be an integer. This integer MUST be greater than, or equal to, 0.| usize | Y |
|
||||
| pattern | The value of "pattern" MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. A string instance is considered valid if the regular expression matches the instance successfully. | string | Y |
|
||||
| max_items | The value of "max_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is less than, or equal to, the value of this validator. | usize | Y |
|
||||
| min_items | The value of "min_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is greater than, or equal to, the value of this validator. | usize | Y |
|
||||
| unique_items | The value of "unique_items" MUST be an boolean. If this value is `false`, the instance validates successfully. If this value is `true`, the instance validates successfully if all of its elements are unique. | bool | Y |
|
||||
| validator.multiple_of | The value of "multiple_of" MUST be a number, strictly greater than 0. A numeric instance is only valid if division by this value results in an integer. | number | Y |
|
||||
| validator.maximum | The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. If `exclusive` is `true` and instance is less than the provided value, or else if the instance is less than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| validator.minimum | The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. If `exclusive` is `true` and instance is greater than the provided value, or else if the instance is greater than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| validator.max_length | The value of "max_length" MUST be a non-negative integer. A string instance is valid against this validator if its length is less than, or equal to, the value. | usize | Y |
|
||||
| validator.min_length | The value of "min_length" MUST be a non-negative integer. The value of this validator MUST be an integer. This integer MUST be greater than, or equal to, 0.| usize | Y |
|
||||
| validator.pattern | The value of "pattern" MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. A string instance is considered valid if the regular expression matches the instance successfully. | string | Y |
|
||||
| validator.max_items | The value of "max_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is less than, or equal to, the value of this validator. | usize | Y |
|
||||
| validator.min_items | The value of "min_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is greater than, or equal to, the value of this validator. | usize | Y |
|
||||
| validator.unique_items | The value of "unique_items" MUST be an boolean. If this value is `false`, the instance validates successfully. If this value is `true`, the instance validates successfully if all of its elements are unique. | bool | Y |
|
||||
|
||||
Example
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ Define a OpenAPI object
|
||||
| default | Default value | bool,string | Y |
|
||||
| read_only | set field openapi readOnly property, field readOnly property = args.read_only_all \|\| field.read_only | bool | Y |
|
||||
| write_only | set field openapi writeOnly property, field writeOnly property = args.write_only_all \|\| field.write_only | bool | Y |
|
||||
| multiple_of | The value of "multiple_of" MUST be a number, strictly greater than 0. A numeric instance is only valid if division by this value results in an integer. | number | Y |
|
||||
| maximum | The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. If `exclusive` is `true` and instance is less than the provided value, or else if the instance is less than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| minimum | The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. If `exclusive` is `true` and instance is greater than the provided value, or else if the instance is greater than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| max_length | The value of "max_length" MUST be a non-negative integer. A string instance is valid against this validator if its length is less than, or equal to, the value. | usize | Y |
|
||||
| min_length | The value of "min_length" MUST be a non-negative integer. The value of this validator MUST be an integer. This integer MUST be greater than, or equal to, 0.| usize | Y |
|
||||
| pattern | The value of "pattern" MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. A string instance is considered valid if the regular expression matches the instance successfully. | string | Y |
|
||||
| max_items | The value of "max_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is less than, or equal to, the value of this validator. | usize | Y |
|
||||
| min_items | The value of "min_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is greater than, or equal to, the value of this validator. | usize | Y |
|
||||
| unique_items | The value of "unique_items" MUST be an boolean. If this value is `false`, the instance validates successfully. If this value is `true`, the instance validates successfully if all of its elements are unique. | bool | Y |
|
||||
| validator.multiple_of | The value of "multiple_of" MUST be a number, strictly greater than 0. A numeric instance is only valid if division by this value results in an integer. | number | Y |
|
||||
| validator.maximum | The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. If `exclusive` is `true` and instance is less than the provided value, or else if the instance is less than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| validator.minimum | The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. If `exclusive` is `true` and instance is greater than the provided value, or else if the instance is greater than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| validator.max_length | The value of "max_length" MUST be a non-negative integer. A string instance is valid against this validator if its length is less than, or equal to, the value. | usize | Y |
|
||||
| validator.min_length | The value of "min_length" MUST be a non-negative integer. The value of this validator MUST be an integer. This integer MUST be greater than, or equal to, 0.| usize | Y |
|
||||
| validator.pattern | The value of "pattern" MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. A string instance is considered valid if the regular expression matches the instance successfully. | string | Y |
|
||||
| validator.max_items | The value of "max_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is less than, or equal to, the value of this validator. | usize | Y |
|
||||
| validator.min_items | The value of "min_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is greater than, or equal to, the value of this validator. | usize | Y |
|
||||
| validator.unique_items | The value of "unique_items" MUST be an boolean. If this value is `false`, the instance validates successfully. If this value is `true`, the instance validates successfully if all of its elements are unique. | bool | Y |
|
||||
|
||||
# Examples
|
||||
|
||||
|
||||
@@ -15,29 +15,25 @@ Define a OpenAPI.
|
||||
|
||||
| Attribute | description | Type | Optional |
|
||||
|---------------|---------------------------|----------|----------|
|
||||
| name | Parameter name. When this value is set, it means this is an OpenAPI parameter type. | string | Y |
|
||||
| in | Where to parse the parameter. The possible values are "query", "path", "header", "cookie". | string | Y |
|
||||
| private | It means that the value of this cookie is encrypted. | bool | Y |
|
||||
| signed | It means that the value of this cookie is signed. | bool | Y |
|
||||
| extract | It means this parameter is a Poem extractor. | bool | Y |
|
||||
| auth | It means this parameter is a authorization extractor. | bool | Y |
|
||||
| name | Parameter name | string | Y |
|
||||
| desc | Argument description | string | Y |
|
||||
| deprecated | Argument deprecated | bool | Y |
|
||||
| default | Default value | bool,string | Y |
|
||||
| multiple_of | The value of "multiple_of" MUST be a number, strictly greater than 0. A numeric instance is only valid if division by this value results in an integer. | number | Y |
|
||||
| maximum | The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. If `exclusive` is `true` and instance is less than the provided value, or else if the instance is less than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| minimum | The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. If `exclusive` is `true` and instance is greater than the provided value, or else if the instance is greater than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| max_length | The value of "max_length" MUST be a non-negative integer. A string instance is valid against this validator if its length is less than, or equal to, the value. | usize | Y |
|
||||
| min_length | The value of "min_length" MUST be a non-negative integer. The value of this validator MUST be an integer. This integer MUST be greater than, or equal to, 0.| usize | Y |
|
||||
| pattern | The value of "pattern" MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. A string instance is considered valid if the regular expression matches the instance successfully. | string | Y |
|
||||
| max_items | The value of "max_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is less than, or equal to, the value of this validator. | usize | Y |
|
||||
| min_items | The value of "min_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is greater than, or equal to, the value of this validator. | usize | Y |
|
||||
| unique_items | The value of "unique_items" MUST be an boolean. If this value is `false`, the instance validates successfully. If this value is `true`, the instance validates successfully if all of its elements are unique. | bool | Y |
|
||||
| default | Default value | bool,string | Y |
|
||||
| validator.multiple_of | The value of "multiple_of" MUST be a number, strictly greater than 0. A numeric instance is only valid if division by this value results in an integer. | number | Y |
|
||||
| validator.maximum | The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. If `exclusive` is `true` and instance is less than the provided value, or else if the instance is less than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| validator.minimum | The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. If `exclusive` is `true` and instance is greater than the provided value, or else if the instance is greater than or exactly equal to the provided value. | { value: `<number>`, exclusive: `<bool>`} | Y |
|
||||
| validator.max_length | The value of "max_length" MUST be a non-negative integer. A string instance is valid against this validator if its length is less than, or equal to, the value. | usize | Y |
|
||||
| validator.min_length | The value of "min_length" MUST be a non-negative integer. The value of this validator MUST be an integer. This integer MUST be greater than, or equal to, 0.| usize | Y |
|
||||
| validator.pattern | The value of "pattern" MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. A string instance is considered valid if the regular expression matches the instance successfully. | string | Y |
|
||||
| validator.max_items | The value of "max_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is less than, or equal to, the value of this validator. | usize | Y |
|
||||
| validator.min_items | The value of "min_items" MUST be an integer. This integer MUST be greater than, or equal to, 0. An array instance is valid if its size is greater than, or equal to, the value of this validator. | usize | Y |
|
||||
| validator.unique_items | The value of "unique_items" MUST be an boolean. If this value is `false`, the instance validates successfully. If this value is `true`, the instance validates successfully if all of its elements are unique. | bool | Y |
|
||||
|
||||
# Examples
|
||||
|
||||
```rust
|
||||
use poem_openapi::{
|
||||
param::Header,
|
||||
payload::{Json, PlainText},
|
||||
ApiRequest, Object, OpenApi, ApiResponse,
|
||||
};
|
||||
@@ -74,7 +70,7 @@ impl PetApi {
|
||||
#[oai(path = "/pet", method = "post")]
|
||||
async fn create_pet(
|
||||
&self,
|
||||
#[oai(name = "TOKEN", in = "header")] token: String,
|
||||
#[oai(name = "TOKEN")] token: Header<String>,
|
||||
req: CreatePetRequest
|
||||
) -> CreatePetResponse {
|
||||
todo!()
|
||||
|
||||
@@ -15,11 +15,8 @@ pub enum ParseRequestError {
|
||||
},
|
||||
|
||||
/// Failed to parse a request body.
|
||||
#[error("Failed to parse a request body: {reason}")]
|
||||
ParseRequestBody {
|
||||
/// The reason for the error.
|
||||
reason: String,
|
||||
},
|
||||
#[error("Failed to parse a request body")]
|
||||
ParseRequestBody(Response),
|
||||
|
||||
/// The `Content-Type` requested by the client is not supported.
|
||||
#[error("The `Content-Type` requested by the client is not supported: {content_type}")]
|
||||
@@ -44,17 +41,16 @@ pub enum ParseRequestError {
|
||||
impl IntoResponse for ParseRequestError {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
ParseRequestError::ParseParam { .. } | ParseRequestError::ParseRequestBody { .. } => {
|
||||
self.to_string()
|
||||
.with_status(StatusCode::BAD_REQUEST)
|
||||
.into_response()
|
||||
}
|
||||
ParseRequestError::ParseParam { .. } => self
|
||||
.to_string()
|
||||
.with_status(StatusCode::BAD_REQUEST)
|
||||
.into_response(),
|
||||
ParseRequestError::ContentTypeNotSupported { .. }
|
||||
| ParseRequestError::ExpectContentType => self
|
||||
.to_string()
|
||||
.with_status(StatusCode::METHOD_NOT_ALLOWED)
|
||||
.into_response(),
|
||||
ParseRequestError::Extractor(resp) => resp,
|
||||
ParseRequestError::ParseRequestBody(resp) | ParseRequestError::Extractor(resp) => resp,
|
||||
ParseRequestError::Authorization => self
|
||||
.to_string()
|
||||
.with_status(StatusCode::UNAUTHORIZED)
|
||||
|
||||
@@ -34,18 +34,15 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use poem::{listener::TcpListener, Route};
|
||||
//! use poem_openapi::{payload::PlainText, OpenApi, OpenApiService};
|
||||
//! use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService};
|
||||
//!
|
||||
//! struct Api;
|
||||
//!
|
||||
//! #[OpenApi]
|
||||
//! impl Api {
|
||||
//! #[oai(path = "/hello", method = "get")]
|
||||
//! async fn index(
|
||||
//! &self,
|
||||
//! #[oai(name = "name", in = "query")] name: Option<String>,
|
||||
//! ) -> PlainText<String> {
|
||||
//! match name {
|
||||
//! async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
|
||||
//! match name.0 {
|
||||
//! Some(name) => PlainText(format!("hello, {}!", name)),
|
||||
//! None => PlainText("hello!".to_string()),
|
||||
//! }
|
||||
@@ -88,26 +85,26 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod auth;
|
||||
mod base;
|
||||
mod error;
|
||||
mod openapi;
|
||||
#[doc(hidden)]
|
||||
pub mod param;
|
||||
pub mod payload;
|
||||
#[doc(hidden)]
|
||||
pub mod registry;
|
||||
pub mod types;
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "swagger-ui")]
|
||||
pub mod ui;
|
||||
#[doc(hidden)]
|
||||
pub mod validation;
|
||||
|
||||
pub use base::{ApiRequest, ApiResponse, CombinedAPI, OAuthScopes, OpenApi, SecurityScheme, Tags};
|
||||
mod base;
|
||||
mod error;
|
||||
mod openapi;
|
||||
#[cfg(feature = "swagger-ui")]
|
||||
mod ui;
|
||||
|
||||
pub use base::{
|
||||
ApiExtractor, ApiExtractorType, ApiResponse, CombinedAPI, ExtractParamOptions, OAuthScopes,
|
||||
OpenApi, PoemExtractor, Tags,
|
||||
};
|
||||
pub use error::ParseRequestError;
|
||||
pub use openapi::OpenApiService;
|
||||
#[doc(hidden)]
|
||||
pub use poem;
|
||||
#[doc = include_str!("docs/request.md")]
|
||||
pub use poem_openapi_derive::ApiRequest;
|
||||
#[doc = include_str!("docs/response.md")]
|
||||
@@ -128,7 +125,12 @@ pub use poem_openapi_derive::OpenApi;
|
||||
pub use poem_openapi_derive::SecurityScheme;
|
||||
#[doc = include_str!("docs/tags.md")]
|
||||
pub use poem_openapi_derive::Tags;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use serde;
|
||||
#[doc(hidden)]
|
||||
pub use serde_json;
|
||||
pub mod __private {
|
||||
pub use poem;
|
||||
pub use serde;
|
||||
pub use serde_json;
|
||||
|
||||
pub use crate::base::UrlQuery;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use poem::{
|
||||
endpoint::{make_sync, BoxEndpoint},
|
||||
middleware::CookieJarManager,
|
||||
web::cookie::CookieKey,
|
||||
Endpoint, EndpointExt, IntoEndpoint, Response, Route,
|
||||
Endpoint, EndpointExt, FromRequest, IntoEndpoint, IntoResponse, Request, Response, Route,
|
||||
};
|
||||
|
||||
#[cfg(feature = "swagger-ui")]
|
||||
use crate::ui::create_ui_endpoint;
|
||||
use crate::{
|
||||
poem::middleware::CookieJarManager,
|
||||
base::UrlQuery,
|
||||
registry::{Document, MetaInfo, MetaServer, Registry},
|
||||
OpenApi,
|
||||
};
|
||||
@@ -123,16 +126,29 @@ impl<T: OpenApi> IntoEndpoint for OpenApiService<T> {
|
||||
type Endpoint = BoxEndpoint<'static, Response>;
|
||||
|
||||
fn into_endpoint(self) -> Self::Endpoint {
|
||||
async fn extract_query(next: impl Endpoint, mut req: Request) -> impl IntoResponse {
|
||||
let query: poem::web::Query<BTreeMap<String, String>> =
|
||||
FromRequest::from_request(&req, &mut Default::default())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
req.extensions_mut().insert(UrlQuery(query.0));
|
||||
next.call(req).await
|
||||
}
|
||||
|
||||
match self.cookie_key {
|
||||
Some(key) => self
|
||||
.api
|
||||
.add_routes(Route::new())
|
||||
.with(CookieJarManager::with_key(key))
|
||||
.around(extract_query)
|
||||
.map_to_response()
|
||||
.boxed(),
|
||||
None => self
|
||||
.api
|
||||
.add_routes(Route::new())
|
||||
.with(CookieJarManager::new())
|
||||
.around(extract_query)
|
||||
.map_to_response()
|
||||
.boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
209
poem-openapi/src/param/cookie.rs
Normal file
209
poem-openapi/src/param/cookie.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use poem::{Request, RequestBody};
|
||||
|
||||
use crate::{
|
||||
registry::{MetaParamIn, MetaSchemaRef, Registry},
|
||||
types::ParseFromParameter,
|
||||
ApiExtractor, ApiExtractorType, ExtractParamOptions, ParseRequestError,
|
||||
};
|
||||
|
||||
/// Represents the parameters passed by the cookie.
|
||||
pub struct Cookie<T>(pub T);
|
||||
|
||||
impl<T> Deref for Cookie<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Cookie<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ParseFromParameter> ApiExtractor<'a> for Cookie<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::Parameter;
|
||||
const PARAM_IS_REQUIRED: bool = T::IS_REQUIRED;
|
||||
|
||||
type ParamType = T;
|
||||
type ParamRawType = T::RawValueType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
Some(MetaParamIn::Cookie)
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
Some(T::schema_ref())
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
self.0.as_raw_value()
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
_body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = request
|
||||
.cookie()
|
||||
.get(param_opts.name)
|
||||
.as_ref()
|
||||
.map(|cookie| cookie.value_str().to_string());
|
||||
let value = match (value, ¶m_opts.default_value) {
|
||||
(Some(value), _) => Some(value),
|
||||
(None, Some(default_value)) => return Ok(Self(default_value())),
|
||||
(None, _) => None,
|
||||
};
|
||||
|
||||
ParseFromParameter::parse_from_parameter(value.as_deref())
|
||||
.map(Self)
|
||||
.map_err(|err| ParseRequestError::ParseParam {
|
||||
name: param_opts.name,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the parameters passed by the private cookie.
|
||||
pub struct CookiePrivate<T>(pub T);
|
||||
|
||||
impl<T> Deref for CookiePrivate<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for CookiePrivate<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ParseFromParameter> ApiExtractor<'a> for CookiePrivate<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::Parameter;
|
||||
const PARAM_IS_REQUIRED: bool = T::IS_REQUIRED;
|
||||
|
||||
type ParamType = T;
|
||||
type ParamRawType = T::RawValueType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
Some(MetaParamIn::Cookie)
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
Some(T::schema_ref())
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
self.0.as_raw_value()
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
_body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = request
|
||||
.cookie()
|
||||
.private()
|
||||
.get(param_opts.name)
|
||||
.as_ref()
|
||||
.map(|cookie| cookie.value_str().to_string());
|
||||
let value = match (value, ¶m_opts.default_value) {
|
||||
(Some(value), _) => Some(value),
|
||||
(None, Some(default_value)) => return Ok(Self(default_value())),
|
||||
(None, _) => None,
|
||||
};
|
||||
|
||||
ParseFromParameter::parse_from_parameter(value.as_deref())
|
||||
.map(Self)
|
||||
.map_err(|err| ParseRequestError::ParseParam {
|
||||
name: param_opts.name,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the parameters passed by the signed cookie.
|
||||
pub struct CookieSigned<T>(pub T);
|
||||
|
||||
impl<T> Deref for CookieSigned<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for CookieSigned<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ParseFromParameter> ApiExtractor<'a> for CookieSigned<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::Parameter;
|
||||
const PARAM_IS_REQUIRED: bool = T::IS_REQUIRED;
|
||||
|
||||
type ParamType = T;
|
||||
type ParamRawType = T::RawValueType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
Some(MetaParamIn::Cookie)
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
Some(T::schema_ref())
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
self.0.as_raw_value()
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
_body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = request
|
||||
.cookie()
|
||||
.signed()
|
||||
.get(param_opts.name)
|
||||
.as_ref()
|
||||
.map(|cookie| cookie.value_str().to_string());
|
||||
let value = match (value, ¶m_opts.default_value) {
|
||||
(Some(value), _) => Some(value),
|
||||
(None, Some(default_value)) => return Ok(Self(default_value())),
|
||||
(None, _) => None,
|
||||
};
|
||||
|
||||
ParseFromParameter::parse_from_parameter(value.as_deref())
|
||||
.map(Self)
|
||||
.map_err(|err| ParseRequestError::ParseParam {
|
||||
name: param_opts.name,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
}
|
||||
}
|
||||
74
poem-openapi/src/param/header.rs
Normal file
74
poem-openapi/src/param/header.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use poem::{Request, RequestBody};
|
||||
|
||||
use crate::{
|
||||
registry::{MetaParamIn, MetaSchemaRef, Registry},
|
||||
types::ParseFromParameter,
|
||||
ApiExtractor, ApiExtractorType, ExtractParamOptions, ParseRequestError,
|
||||
};
|
||||
|
||||
/// Represents the parameters passed by the request header.
|
||||
pub struct Header<T>(pub T);
|
||||
|
||||
impl<T> Deref for Header<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Header<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ParseFromParameter> ApiExtractor<'a> for Header<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::Parameter;
|
||||
const PARAM_IS_REQUIRED: bool = T::IS_REQUIRED;
|
||||
|
||||
type ParamType = T;
|
||||
type ParamRawType = T::RawValueType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
Some(MetaParamIn::Header)
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
Some(T::schema_ref())
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
self.0.as_raw_value()
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
_body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = request
|
||||
.headers()
|
||||
.get(param_opts.name)
|
||||
.and_then(|value| value.to_str().ok());
|
||||
let value = match (value, ¶m_opts.default_value) {
|
||||
(Some(value), _) => Some(value),
|
||||
(None, Some(default_value)) => return Ok(Self(default_value())),
|
||||
(None, _) => None,
|
||||
};
|
||||
|
||||
ParseFromParameter::parse_from_parameter(value)
|
||||
.map(Self)
|
||||
.map_err(|err| ParseRequestError::ParseParam {
|
||||
name: param_opts.name,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,10 @@
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
//! Parameter types for the API operation.
|
||||
mod cookie;
|
||||
mod header;
|
||||
mod path;
|
||||
mod query;
|
||||
|
||||
use crate::{poem::Request, registry::MetaParamIn};
|
||||
|
||||
pub fn get<'a>(
|
||||
name: &str,
|
||||
in_type: MetaParamIn,
|
||||
request: &'a Request,
|
||||
query: &'a HashMap<String, String>,
|
||||
) -> Option<Cow<'a, str>> {
|
||||
match in_type {
|
||||
MetaParamIn::Query => query.get(name).map(|s| s.as_str()).map(Cow::Borrowed),
|
||||
MetaParamIn::Header => request
|
||||
.headers()
|
||||
.get(name)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(Cow::Borrowed),
|
||||
MetaParamIn::Path => request.path_param(name).map(Cow::Borrowed),
|
||||
MetaParamIn::Cookie => request
|
||||
.cookie()
|
||||
.get(name)
|
||||
.as_ref()
|
||||
.map(|cookie| cookie.value_str().to_string())
|
||||
.map(Cow::Owned),
|
||||
MetaParamIn::CookiePrivate => request
|
||||
.cookie()
|
||||
.private()
|
||||
.get(name)
|
||||
.as_ref()
|
||||
.map(|cookie| cookie.value_str().to_string())
|
||||
.map(Cow::Owned),
|
||||
MetaParamIn::CookieSigned => request
|
||||
.cookie()
|
||||
.signed()
|
||||
.get(name)
|
||||
.as_ref()
|
||||
.map(|cookie| cookie.value_str().to_string())
|
||||
.map(Cow::Owned),
|
||||
}
|
||||
}
|
||||
pub use cookie::{Cookie, CookiePrivate, CookieSigned};
|
||||
pub use header::Header;
|
||||
pub use path::Path;
|
||||
pub use query::Query;
|
||||
|
||||
73
poem-openapi/src/param/path.rs
Normal file
73
poem-openapi/src/param/path.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use poem::{Request, RequestBody};
|
||||
|
||||
use crate::{
|
||||
registry::{MetaParamIn, MetaSchemaRef, Registry},
|
||||
types::ParseFromParameter,
|
||||
ApiExtractor, ApiExtractorType, ExtractParamOptions, ParseRequestError,
|
||||
};
|
||||
|
||||
/// Represents the parameters passed by the URI path.
|
||||
pub struct Path<T>(pub T);
|
||||
|
||||
impl<T> Deref for Path<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Path<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ParseFromParameter> ApiExtractor<'a> for Path<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::Parameter;
|
||||
const PARAM_IS_REQUIRED: bool = T::IS_REQUIRED;
|
||||
|
||||
type ParamType = T;
|
||||
type ParamRawType = T::RawValueType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
Some(MetaParamIn::Path)
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
Some(T::schema_ref())
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
self.0.as_raw_value()
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
_body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = match (
|
||||
request.path_param(param_opts.name),
|
||||
¶m_opts.default_value,
|
||||
) {
|
||||
(Some(value), _) => Some(value),
|
||||
(None, Some(default_value)) => return Ok(Self(default_value())),
|
||||
(None, _) => None,
|
||||
};
|
||||
|
||||
ParseFromParameter::parse_from_parameter(value)
|
||||
.map(Self)
|
||||
.map_err(|err| ParseRequestError::ParseParam {
|
||||
name: param_opts.name,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
}
|
||||
}
|
||||
75
poem-openapi/src/param/query.rs
Normal file
75
poem-openapi/src/param/query.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use poem::{Request, RequestBody};
|
||||
|
||||
use crate::{
|
||||
base::UrlQuery,
|
||||
registry::{MetaParamIn, MetaSchemaRef, Registry},
|
||||
types::ParseFromParameter,
|
||||
ApiExtractor, ApiExtractorType, ExtractParamOptions, ParseRequestError,
|
||||
};
|
||||
|
||||
/// Represents the parameters passed by the query string.
|
||||
pub struct Query<T>(pub T);
|
||||
|
||||
impl<T> Deref for Query<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Query<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[poem::async_trait]
|
||||
impl<'a, T: ParseFromParameter> ApiExtractor<'a> for Query<T> {
|
||||
const TYPE: ApiExtractorType = ApiExtractorType::Parameter;
|
||||
const PARAM_IS_REQUIRED: bool = T::IS_REQUIRED;
|
||||
|
||||
type ParamType = T;
|
||||
type ParamRawType = T::RawValueType;
|
||||
|
||||
fn register(registry: &mut Registry) {
|
||||
T::register(registry);
|
||||
}
|
||||
|
||||
fn param_in() -> Option<MetaParamIn> {
|
||||
Some(MetaParamIn::Query)
|
||||
}
|
||||
|
||||
fn param_schema_ref() -> Option<MetaSchemaRef> {
|
||||
Some(T::schema_ref())
|
||||
}
|
||||
|
||||
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
|
||||
self.0.as_raw_value()
|
||||
}
|
||||
|
||||
async fn from_request(
|
||||
request: &'a Request,
|
||||
_body: &mut RequestBody,
|
||||
param_opts: ExtractParamOptions<Self::ParamType>,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = request
|
||||
.extensions()
|
||||
.get::<UrlQuery>()
|
||||
.and_then(|query| query.0.get(param_opts.name).map(|s| s.as_str()));
|
||||
let value = match (value, ¶m_opts.default_value) {
|
||||
(Some(value), _) => Some(value),
|
||||
(None, Some(default_value)) => return Ok(Self(default_value())),
|
||||
(None, _) => None,
|
||||
};
|
||||
|
||||
ParseFromParameter::parse_from_parameter(value)
|
||||
.map(Self)
|
||||
.map_err(|err| ParseRequestError::ParseParam {
|
||||
name: param_opts.name,
|
||||
reason: err.into_message(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ use poem::{FromRequest, IntoResponse, Request, RequestBody, Response};
|
||||
|
||||
use crate::{
|
||||
payload::{ParsePayload, Payload},
|
||||
poem::Error,
|
||||
registry::{MetaMediaType, MetaResponse, MetaResponses, MetaSchema, MetaSchemaRef, Registry},
|
||||
ApiResponse, ParseRequestError,
|
||||
};
|
||||
@@ -29,14 +28,7 @@ impl ParsePayload for Binary<Vec<u8>> {
|
||||
body: &mut RequestBody,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
Ok(Self(<Vec<u8>>::from_request(request, body).await.map_err(
|
||||
|err| {
|
||||
ParseRequestError::ParseRequestBody {
|
||||
reason: Into::<Error>::into(err)
|
||||
.reason()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
}
|
||||
},
|
||||
|err| ParseRequestError::ParseRequestBody(err.into_response()),
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use poem::{Error, FromRequest, IntoResponse, Request, RequestBody, Response};
|
||||
use poem::{http::StatusCode, FromRequest, IntoResponse, Request, RequestBody, Response};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
@@ -33,16 +33,14 @@ impl<T: ParseFromJSON> ParsePayload for Json<T> {
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
let value = poem::web::Json::<Value>::from_request(request, body)
|
||||
.await
|
||||
.map_err(|err| ParseRequestError::ParseRequestBody {
|
||||
reason: Into::<Error>::into(err)
|
||||
.reason()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
})?;
|
||||
let value =
|
||||
T::parse_from_json(value.0).map_err(|err| ParseRequestError::ParseRequestBody {
|
||||
reason: err.into_message(),
|
||||
})?;
|
||||
.map_err(|err| ParseRequestError::ParseRequestBody(err.into_response()))?;
|
||||
let value = T::parse_from_json(value.0).map_err(|err| {
|
||||
ParseRequestError::ParseRequestBody(
|
||||
Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(err.into_message()),
|
||||
)
|
||||
})?;
|
||||
Ok(Self(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use poem::{FromRequest, IntoResponse, Request, RequestBody, Response};
|
||||
|
||||
use crate::{
|
||||
payload::{ParsePayload, Payload},
|
||||
poem::Error,
|
||||
registry::{MetaMediaType, MetaResponse, MetaResponses, MetaSchemaRef, Registry},
|
||||
types::Type,
|
||||
ApiResponse, ParseRequestError,
|
||||
@@ -27,14 +26,7 @@ impl ParsePayload for PlainText<String> {
|
||||
body: &mut RequestBody,
|
||||
) -> Result<Self, ParseRequestError> {
|
||||
Ok(Self(String::from_request(request, body).await.map_err(
|
||||
|err| {
|
||||
ParseRequestError::ParseRequestBody {
|
||||
reason: Into::<Error>::into(err)
|
||||
.reason()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
}
|
||||
},
|
||||
|err| ParseRequestError::ParseRequestBody(err.into_response()),
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
2
poem-openapi/src/types/external/bool.rs
vendored
2
poem-openapi/src/types/external/bool.rs
vendored
@@ -1,9 +1,9 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use poem::web::Field;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
poem::web::Field,
|
||||
registry::{MetaSchema, MetaSchemaRef},
|
||||
types::{
|
||||
ParseError, ParseFromJSON, ParseFromMultipartField, ParseFromParameter, ParseResult,
|
||||
|
||||
2
poem-openapi/src/types/external/datetime.rs
vendored
2
poem-openapi/src/types/external/datetime.rs
vendored
@@ -1,10 +1,10 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use poem::web::Field;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
poem::web::Field,
|
||||
registry::{MetaSchema, MetaSchemaRef},
|
||||
types::{
|
||||
ParseError, ParseFromJSON, ParseFromMultipartField, ParseFromParameter, ParseResult,
|
||||
|
||||
5
poem-openapi/src/types/external/vec.rs
vendored
5
poem-openapi/src/types/external/vec.rs
vendored
@@ -1,9 +1,10 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use poem::web::Field as PoemField;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
poem::web::Field as PoemField,
|
||||
registry::{MetaSchema, MetaSchemaRef, Registry},
|
||||
serde_json::Value,
|
||||
types::{ParseError, ParseFromJSON, ParseFromMultipartField, ParseResult, ToJSON, Type},
|
||||
};
|
||||
|
||||
|
||||
@@ -54,35 +54,26 @@ pub trait Type: Send + Sync {
|
||||
}
|
||||
|
||||
/// Represents a type that can parsing from JSON.
|
||||
pub trait ParseFromJSON: Type {
|
||||
pub trait ParseFromJSON: Sized + Type {
|
||||
/// Parse from [`serde_json::Value`].
|
||||
fn parse_from_json(value: Value) -> ParseResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn parse_from_json(value: Value) -> ParseResult<Self>;
|
||||
}
|
||||
|
||||
/// Represents a type that can parsing from parameter. (header, query, path,
|
||||
/// cookie)
|
||||
pub trait ParseFromParameter: Type {
|
||||
pub trait ParseFromParameter: Sized + Type {
|
||||
/// Parse from parameter.
|
||||
fn parse_from_parameter(value: Option<&str>) -> ParseResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn parse_from_parameter(value: Option<&str>) -> ParseResult<Self>;
|
||||
}
|
||||
|
||||
/// Represents a type that can parsing from multipart.
|
||||
#[poem::async_trait]
|
||||
pub trait ParseFromMultipartField: Type {
|
||||
pub trait ParseFromMultipartField: Sized + Type {
|
||||
/// Parse from multipart field.
|
||||
async fn parse_from_multipart(field: Option<PoemField>) -> ParseResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn parse_from_multipart(field: Option<PoemField>) -> ParseResult<Self>;
|
||||
|
||||
/// Parse from repeated multipart field.
|
||||
async fn parse_from_repeated_field(self, _field: PoemField) -> ParseResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
async fn parse_from_repeated_field(self, _field: PoemField) -> ParseResult<Self> {
|
||||
Err(ParseError::<Self>::custom("repeated field"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use askama::Template;
|
||||
use poem::{endpoint::make_sync, web::Html};
|
||||
|
||||
use crate::poem::Endpoint;
|
||||
use poem::{endpoint::make_sync, web::Html, Endpoint};
|
||||
|
||||
const SWAGGER_UI_JS: &str = include_str!("swagger-ui-bundle.js");
|
||||
const SWAGGER_UI_CSS: &str = include_str!("swagger-ui.css");
|
||||
|
||||
@@ -4,10 +4,11 @@ use poem::{
|
||||
Endpoint, EndpointExt, IntoEndpoint,
|
||||
};
|
||||
use poem_openapi::{
|
||||
param::Query,
|
||||
payload::{Binary, Json, PlainText},
|
||||
registry::{MetaApi, MetaSchema},
|
||||
types::Type,
|
||||
ApiRequest, ApiResponse, OpenApi, OpenApiService, ParseRequestError, Tags,
|
||||
ApiRequest, ApiResponse, OpenApi, OpenApiService, ParseRequestError, PoemExtractor, Tags,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -265,13 +266,13 @@ async fn response() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "code", in = "query")] code: u16) -> MyResponse {
|
||||
match code {
|
||||
async fn test(&self, code: Query<u16>) -> MyResponse {
|
||||
match code.0 {
|
||||
200 => MyResponse::Ok,
|
||||
409 => MyResponse::AlreadyExists(Json(code)),
|
||||
409 => MyResponse::AlreadyExists(Json(code.0)),
|
||||
_ => MyResponse::Default(
|
||||
StatusCode::from_u16(code).unwrap(),
|
||||
PlainText(format!("code: {}", code)),
|
||||
StatusCode::from_u16(code.0).unwrap(),
|
||||
PlainText(format!("code: {}", code.0)),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -365,8 +366,8 @@ async fn bad_request_handler() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "code", in = "query")] code: u16) -> MyResponse {
|
||||
MyResponse::Ok(PlainText(format!("code: {}", code)))
|
||||
async fn test(&self, code: Query<u16>) -> MyResponse {
|
||||
MyResponse::Ok(PlainText(format!("code: {}", code.0)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,9 +425,9 @@ async fn bad_request_handler_for_validator() {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(
|
||||
&self,
|
||||
#[oai(name = "code", in = "query", validator(maximum(value = "100")))] code: u16,
|
||||
#[oai(validator(maximum(value = "100")))] code: Query<u16>,
|
||||
) -> MyResponse {
|
||||
MyResponse::Ok(PlainText(format!("code: {}", code)))
|
||||
MyResponse::Ok(PlainText(format!("code: {}", code.0)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,8 +468,8 @@ async fn poem_extract() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(extract)] data: Data<&i32>) {
|
||||
assert_eq!(*data.0, 100);
|
||||
async fn test(&self, data: PoemExtractor<Data<&i32>>) {
|
||||
assert_eq!(*data.0 .0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,9 +65,9 @@ impl Api {
|
||||
#[allow(unused_variables)]
|
||||
async fn create_user(
|
||||
&self,
|
||||
#[oai(name = "key", in = "query", desc = "api key")] key: ::std::string::String,
|
||||
#[oai(name = "X-API-TOKEN", in = "header", deprecated)] api_token: ::std::option::Option<
|
||||
::std::string::String,
|
||||
#[oai(desc = "api key")] key: poem_openapi::param::Query<::std::string::String>,
|
||||
#[oai(name = "X-API-TOKEN", deprecated)] api_token: poem_openapi::param::Header<
|
||||
::std::option::Option<::std::string::String>,
|
||||
>,
|
||||
req: CreateUserRequest,
|
||||
) -> CreateUserResponse {
|
||||
|
||||
@@ -103,9 +103,14 @@ async fn required_fields() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
if !matches!(err,ParseRequestError::ParseRequestBody { reason } if reason == "field `file` is required")
|
||||
{
|
||||
panic!();
|
||||
match err {
|
||||
ParseRequestError::ParseRequestBody(resp) => {
|
||||
assert_eq!(
|
||||
resp.into_body().into_string().await.unwrap(),
|
||||
"field `file` is required"
|
||||
);
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,9 +273,14 @@ async fn validator() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
if !matches!(err, ParseRequestError::ParseRequestBody{reason } if reason == r#"field `value` verification failed. maximum(32, exclusive: false)"#)
|
||||
{
|
||||
panic!();
|
||||
match err {
|
||||
ParseRequestError::ParseRequestBody(resp) => {
|
||||
assert_eq!(
|
||||
resp.into_body().into_string().await.unwrap(),
|
||||
r#"field `value` verification failed. maximum(32, exclusive: false)"#
|
||||
);
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,9 +428,14 @@ async fn repeated_error() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
if !matches!(err, ParseRequestError::ParseRequestBody { reason } if reason == "failed to parse field `value`: failed to parse \"string\": repeated field")
|
||||
{
|
||||
panic!();
|
||||
match err {
|
||||
ParseRequestError::ParseRequestBody(resp) => {
|
||||
assert_eq!(
|
||||
resp.into_body().into_string().await.unwrap(),
|
||||
"failed to parse field `value`: failed to parse \"string\": repeated field"
|
||||
);
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ use poem::{
|
||||
Endpoint, IntoEndpoint, Request,
|
||||
};
|
||||
use poem_openapi::{
|
||||
param::{Cookie as ParamCookie, CookiePrivate, CookieSigned, Header, Path, Query},
|
||||
payload::Json,
|
||||
registry::{MetaApi, MetaParamIn, MetaSchema, MetaSchemaRef},
|
||||
types::Type,
|
||||
OpenApi, OpenApiService,
|
||||
OpenApi, OpenApiService, ParseRequestError,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -21,8 +23,8 @@ async fn param_name() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/abc", method = "get")]
|
||||
async fn test(&self, #[oai(name = "a", in = "query")] a: i32) {
|
||||
assert_eq!(a, 10);
|
||||
async fn test(&self, a: Query<i32>) {
|
||||
assert_eq!(a.0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +50,8 @@ async fn query() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "query")] v: i32) {
|
||||
assert_eq!(v, 10);
|
||||
async fn test(&self, v: Query<i32>) {
|
||||
assert_eq!(v.0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +76,8 @@ async fn query_default() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "query", default = "default_i32")] v: i32) {
|
||||
assert_eq!(v, 999);
|
||||
async fn test(&self, #[oai(default = "default_i32")] v: Query<i32>) {
|
||||
assert_eq!(v.0, 999);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +108,8 @@ async fn header() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "header")] v: i32) {
|
||||
assert_eq!(v, 10);
|
||||
async fn test(&self, v: Header<i32>) {
|
||||
assert_eq!(v.0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +125,8 @@ async fn header_default() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "header", default = "default_i32")] v: i32) {
|
||||
assert_eq!(v, 999);
|
||||
async fn test(&self, #[oai(default = "default_i32")] v: Header<i32>) {
|
||||
assert_eq!(v.0, 999);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +142,8 @@ async fn path() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/k/:v", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "path")] v: i32) {
|
||||
assert_eq!(v, 10);
|
||||
async fn test(&self, v: Path<i32>) {
|
||||
assert_eq!(v.0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,15 +161,10 @@ async fn cookie() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(
|
||||
&self,
|
||||
#[oai(name = "v1", in = "cookie")] v1: i32,
|
||||
#[oai(name = "v2", in = "cookie", private)] v2: i32,
|
||||
#[oai(name = "v3", in = "cookie", signed)] v3: i32,
|
||||
) {
|
||||
assert_eq!(v1, 10);
|
||||
assert_eq!(v2, 100);
|
||||
assert_eq!(v3, 1000);
|
||||
async fn test(&self, v1: ParamCookie<i32>, v2: CookiePrivate<i32>, v3: CookieSigned<i32>) {
|
||||
assert_eq!(v1.0, 10);
|
||||
assert_eq!(v2.0, 100);
|
||||
assert_eq!(v3.0, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,8 +201,8 @@ async fn cookie_default() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "cookie", default = "default_i32")] v: i32) {
|
||||
assert_eq!(v, 999);
|
||||
async fn test(&self, #[oai(default = "default_i32")] v: ParamCookie<i32>) {
|
||||
assert_eq!(v.0, 999);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,14 +216,15 @@ async fn deprecated() {
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
#[allow(unused_variables)]
|
||||
impl Api {
|
||||
#[oai(path = "/a", method = "get")]
|
||||
async fn a(&self, #[oai(name = "v", in = "query", deprecated)] _v: i32) {
|
||||
async fn a(&self, #[oai(deprecated)] v: Query<i32>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[oai(path = "/b", method = "get")]
|
||||
async fn b(&self, #[oai(name = "v", in = "query")] _v: i32) {
|
||||
async fn b(&self, v: Query<i32>) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -245,9 +243,10 @@ async fn desc() {
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
#[allow(unused_variables)]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, #[oai(name = "v", in = "query", desc = "ABC")] _v: i32) {
|
||||
async fn test(&self, #[oai(desc = "ABC")] v: Query<i32>) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -271,11 +270,8 @@ async fn default_opt() {
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(
|
||||
&self,
|
||||
#[oai(name = "v", in = "query", default = "default_value")] v: Option<i32>,
|
||||
) {
|
||||
assert_eq!(v, Some(88));
|
||||
async fn test(&self, #[oai(default = "default_value")] v: Query<Option<i32>>) {
|
||||
assert_eq!(v.0, Some(88));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,3 +294,33 @@ async fn default_opt() {
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn extract_result() {
|
||||
struct Api;
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(&self, v: Result<Query<i32>, ParseRequestError>) -> Json<i32> {
|
||||
match v {
|
||||
Ok(Query(v)) => Json(v),
|
||||
Err(_) => Json(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let meta: MetaApi = Api::meta().remove(0);
|
||||
assert_eq!(
|
||||
meta.paths[0].operations[0].params[0].in_type,
|
||||
MetaParamIn::Query
|
||||
);
|
||||
assert_eq!(meta.paths[0].operations[0].params[0].name, "v");
|
||||
|
||||
let api = OpenApiService::new(Api, "test", "1.0").into_endpoint();
|
||||
let resp = api
|
||||
.call(Request::builder().uri(Uri::from_static("/?v=a")).finish())
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.into_body().into_string().await.unwrap(), "0");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use poem_openapi::{
|
||||
payload::{Json, PlainText},
|
||||
registry::{MetaMediaType, MetaRequest, MetaSchema, MetaSchemaRef},
|
||||
types::ParseFromJSON,
|
||||
ApiRequest, Object,
|
||||
ApiExtractor, ApiRequest, Object,
|
||||
};
|
||||
|
||||
#[derive(Debug, Object, Eq, PartialEq)]
|
||||
@@ -23,7 +23,7 @@ enum MyRequest {
|
||||
#[test]
|
||||
fn meta() {
|
||||
assert_eq!(
|
||||
MyRequest::meta(),
|
||||
MyRequest::request_meta().unwrap(),
|
||||
MetaRequest {
|
||||
description: Some("MyRequest\n\nABC"),
|
||||
content: vec![
|
||||
@@ -54,7 +54,9 @@ async fn from_request() {
|
||||
);
|
||||
let (request, mut body) = request.split();
|
||||
assert_eq!(
|
||||
MyRequest::from_request(&request, &mut body).await.unwrap(),
|
||||
MyRequest::from_request(&request, &mut body, Default::default())
|
||||
.await
|
||||
.unwrap(),
|
||||
MyRequest::CreateByJson(Json(CreateUser {
|
||||
user: "sunli".to_string(),
|
||||
password: "123456".to_string()
|
||||
@@ -66,7 +68,9 @@ async fn from_request() {
|
||||
.body("abcdef".to_string());
|
||||
let (request, mut body) = request.split();
|
||||
assert_eq!(
|
||||
MyRequest::from_request(&request, &mut body).await.unwrap(),
|
||||
MyRequest::from_request(&request, &mut body, Default::default())
|
||||
.await
|
||||
.unwrap(),
|
||||
MyRequest::CreateByPlainText(PlainText("abcdef".to_string()))
|
||||
);
|
||||
}
|
||||
@@ -83,7 +87,7 @@ async fn generic() {
|
||||
.body(serde_json::to_vec(&serde_json::json!("hello")).unwrap());
|
||||
|
||||
assert_eq!(
|
||||
MyRequest::<String>::meta(),
|
||||
MyRequest::<String>::request_meta().unwrap(),
|
||||
MetaRequest {
|
||||
description: None,
|
||||
content: vec![MetaMediaType {
|
||||
@@ -96,7 +100,7 @@ async fn generic() {
|
||||
|
||||
let (request, mut body) = request.split();
|
||||
assert_eq!(
|
||||
MyRequest::<String>::from_request(&request, &mut body)
|
||||
MyRequest::<String>::from_request(&request, &mut body, Default::default())
|
||||
.await
|
||||
.unwrap(),
|
||||
MyRequest::CreateByJson(Json("hello".to_string()))
|
||||
|
||||
@@ -2,7 +2,7 @@ mod request;
|
||||
|
||||
use poem::{
|
||||
http::{HeaderValue, StatusCode},
|
||||
IntoResponse,
|
||||
IntoResponse, Response,
|
||||
};
|
||||
use poem_openapi::{
|
||||
payload::{Json, PlainText},
|
||||
@@ -216,9 +216,9 @@ async fn bad_request_handler() {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
CustomApiResponse::from_parse_request_error(ParseRequestError::ParseRequestBody {
|
||||
reason: "error".to_string(),
|
||||
}),
|
||||
CustomApiResponse::from_parse_request_error(ParseRequestError::ParseRequestBody(
|
||||
Response::default()
|
||||
)),
|
||||
CustomApiResponse::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use poem_openapi::{
|
||||
auth::{ApiKey, Basic, Bearer},
|
||||
payload::PlainText,
|
||||
registry::{MetaOAuthFlow, MetaOAuthFlows, MetaOAuthScope, MetaSecurityScheme, Registry},
|
||||
OAuthScopes, OpenApi, OpenApiService, SecurityScheme,
|
||||
ApiExtractor, OAuthScopes, OpenApi, OpenApiService, SecurityScheme,
|
||||
};
|
||||
use typed_headers::{http::StatusCode, Token68};
|
||||
|
||||
@@ -17,7 +17,7 @@ fn rename() {
|
||||
#[oai(rename = "ABC", type = "basic")]
|
||||
struct MySecurityScheme(Basic);
|
||||
|
||||
assert_eq!(MySecurityScheme::NAME, "ABC");
|
||||
assert_eq!(MySecurityScheme::security_scheme().unwrap(), "ABC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -26,7 +26,10 @@ fn default_rename() {
|
||||
#[oai(type = "basic")]
|
||||
struct MySecurityScheme(Basic);
|
||||
|
||||
assert_eq!(MySecurityScheme::NAME, "my_security_scheme");
|
||||
assert_eq!(
|
||||
MySecurityScheme::security_scheme().unwrap(),
|
||||
"my_security_scheme"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -97,7 +100,7 @@ async fn basic_auth() {
|
||||
#[OpenApi]
|
||||
impl MyApi {
|
||||
#[oai(path = "/test", method = "get")]
|
||||
async fn test(&self, #[oai(auth)] auth: MySecurityScheme) -> PlainText<String> {
|
||||
async fn test(&self, auth: MySecurityScheme) -> PlainText<String> {
|
||||
PlainText(format!("{}/{}", auth.0.username, auth.0.password))
|
||||
}
|
||||
}
|
||||
@@ -147,7 +150,7 @@ async fn bearer_auth() {
|
||||
#[OpenApi]
|
||||
impl MyApi {
|
||||
#[oai(path = "/test", method = "get")]
|
||||
async fn test(&self, #[oai(auth)] auth: MySecurityScheme) -> PlainText<String> {
|
||||
async fn test(&self, auth: MySecurityScheme) -> PlainText<String> {
|
||||
PlainText(auth.0.token)
|
||||
}
|
||||
}
|
||||
@@ -243,26 +246,17 @@ async fn api_key_auth() {
|
||||
#[OpenApi]
|
||||
impl MyApi {
|
||||
#[oai(path = "/header", method = "get")]
|
||||
async fn test_in_header(
|
||||
&self,
|
||||
#[oai(auth)] auth: MySecuritySchemeInHeader,
|
||||
) -> PlainText<String> {
|
||||
async fn test_in_header(&self, auth: MySecuritySchemeInHeader) -> PlainText<String> {
|
||||
PlainText(auth.0.key)
|
||||
}
|
||||
|
||||
#[oai(path = "/query", method = "get")]
|
||||
async fn test_in_query(
|
||||
&self,
|
||||
#[oai(auth)] auth: MySecuritySchemeInQuery,
|
||||
) -> PlainText<String> {
|
||||
async fn test_in_query(&self, auth: MySecuritySchemeInQuery) -> PlainText<String> {
|
||||
PlainText(auth.0.key)
|
||||
}
|
||||
|
||||
#[oai(path = "/cookie", method = "get")]
|
||||
async fn test_in_cookie(
|
||||
&self,
|
||||
#[oai(auth)] auth: MySecuritySchemeInCookie,
|
||||
) -> PlainText<String> {
|
||||
async fn test_in_cookie(&self, auth: MySecuritySchemeInCookie) -> PlainText<String> {
|
||||
PlainText(auth.0.key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use poem::{
|
||||
Endpoint, IntoEndpoint, Request,
|
||||
};
|
||||
use poem_openapi::{
|
||||
param::Query,
|
||||
payload::Payload,
|
||||
registry::{MetaApi, MetaSchema, Registry},
|
||||
types::{multipart::JsonField, ParseFromJSON, Type},
|
||||
@@ -273,7 +274,7 @@ async fn param_validator() {
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn test(
|
||||
&self,
|
||||
#[oai(name = "v", in = "query", validator(maximum(value = "100", exclusive)))] _v: i32,
|
||||
#[oai(name = "v", validator(maximum(value = "100", exclusive)))] _v: Query<i32>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user