Skip to content

Commit 1a7dba2

Browse files
committed
Remove poem_openapi::response::StaticFileResponse and implement ApiResponse trait for poem::web::StaticFileResponse
1 parent d5f1a65 commit 1a7dba2

File tree

7 files changed

+151
-111
lines changed

7 files changed

+151
-111
lines changed

poem-openapi-derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "poem-openapi-derive"
3-
version = "2.0.22"
3+
version = "2.0.23"
44
authors = ["sunli <[email protected]>"]
55
edition = "2021"
66
description = "Macros for poem-openapi"

poem-openapi/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
# [2.0.23] 2022-01-13
8+
9+
- Add the missing feature `openapi-explorer` in `ui` mod [#480](https://github.com/poem-web/poem/pull/480)
10+
- Add yaml support [#476](https://github.com/poem-web/poem/pull/476)
11+
- Remove `poem_openapi::response::StaticFileResponse` and implement `ApiResponse trait` for `poem::web::StaticFileResponse`
12+
713
# [2.0.22] 2023-01-11
814

915
- Add support for OpenAPI Explorer [#440](https://github.com/poem-web/poem/pull/440)

poem-openapi/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "poem-openapi"
3-
version = "2.0.22"
3+
version = "2.0.23"
44
authors = ["sunli <[email protected]>"]
55
edition = "2021"
66
description = "OpenAPI support for Poem."
@@ -23,14 +23,14 @@ static-files = ["poem/static-files"]
2323
websocket = ["poem/websocket"]
2424

2525
[dependencies]
26-
poem-openapi-derive = { path = "../poem-openapi-derive", version = "2.0.22" }
26+
poem-openapi-derive = { path = "../poem-openapi-derive", version = "2.0.23" }
2727
poem = { path = "../poem", version = "1.3.51", features = [
2828
"multipart",
2929
"tempfile",
3030
"cookie",
3131
"sse",
3232
"xml",
33-
"yaml"
33+
"yaml",
3434
] }
3535

3636
tokio = { version = "1.17.0", features = ["fs"] }

poem-openapi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ pub mod param;
124124
pub mod payload;
125125
#[doc(hidden)]
126126
pub mod registry;
127-
pub mod response;
127+
mod response;
128128
pub mod types;
129129
#[doc(hidden)]
130130
pub mod validation;

poem-openapi/src/response/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,3 @@
22
33
#[cfg(feature = "static-files")]
44
mod static_file;
5-
6-
#[cfg(feature = "static-files")]
7-
pub use static_file::StaticFileResponse;
Lines changed: 76 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,85 @@
1-
use poem::{error::StaticFileError, Body};
1+
use poem::{web::StaticFileResponse, Body};
22

33
use crate::{
4-
payload::{Binary, PlainText},
4+
payload::{Binary, Payload},
5+
registry::{MetaHeader, MetaMediaType, MetaResponse, MetaResponses, Registry},
6+
types::Type,
57
ApiResponse,
68
};
79

8-
/// A static file response.
9-
#[cfg_attr(docsrs, doc(cfg(feature = "static-files")))]
10-
#[derive(ApiResponse)]
11-
#[oai(internal)]
12-
pub enum StaticFileResponse {
13-
/// Ok
14-
#[oai(status = 200)]
15-
Ok(
16-
Binary<Body>,
17-
/// The ETag (or entity tag) HTTP response header is an identifier for a
18-
/// specific version of a resource. It lets caches be more efficient and
19-
/// save bandwidth, as a web server does not need to resend a full
20-
/// response if the content was not changed. Additionally, etags help to
21-
/// prevent simultaneous updates of a resource from overwriting each
22-
/// other ("mid-air collisions").
23-
///
24-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag>
25-
#[oai(header = "etag")]
26-
Option<String>,
27-
/// The Last-Modified response HTTP header contains a date and time when
28-
/// the origin server believes the resource was last modified. It is
29-
/// used as a validator to determine if the resource is the same as the
30-
/// previously stored one. Less accurate than an ETag header, it is a
31-
/// fallback mechanism. Conditional requests containing
32-
/// If-Modified-Since or If-Unmodified-Since headers make use of this
33-
/// field.
34-
///
35-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified>
36-
#[oai(header = "last-modified")]
37-
Option<String>,
38-
/// The Content-Type representation header is used to indicate the
39-
/// original media type of the resource (prior to any content encoding
40-
/// applied for sending).
41-
///
42-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type>
43-
#[oai(header = "content-type")]
44-
Option<String>,
45-
),
46-
/// Not modified
47-
///
48-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>
49-
#[oai(status = 304)]
50-
NotModified,
51-
/// Bad request
52-
///
53-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>
54-
#[oai(status = 400)]
55-
BadRequest,
56-
/// Resource was not found
57-
#[oai(status = 404)]
58-
NotFound,
59-
/// Precondition failed
60-
///
61-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>
62-
#[oai(status = 412)]
63-
PreconditionFailed,
64-
/// Range not satisfiable
65-
///
66-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>
67-
#[oai(status = 416)]
68-
RangeNotSatisfiable(
69-
/// The Content-Range response HTTP header indicates where in a full
70-
/// body message a partial message belongs.
71-
///
72-
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range>
73-
#[oai(header = "content-range")]
74-
String,
75-
),
76-
/// Internal server error
77-
#[oai(status = 500)]
78-
InternalServerError(PlainText<String>),
79-
}
10+
const ETAG_DESCRIPTION: &str = r#"The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource. It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content was not changed. Additionally, etags help to prevent simultaneous updates of a resource from overwriting each other ("mid-air collisions")."#;
11+
const LAST_MODIFIED_DESCRIPTION: &str = r#"The Last-Modified response HTTP header contains a date and time when the origin server believes the resource was last modified. It is used as a validator to determine if the resource is the same as the previously stored one. Less accurate than an ETag header, it is a fallback mechanism. Conditional requests containing If-Modified-Since or If-Unmodified-Since headers make use of this field."#;
12+
const CONTENT_TYPE_DESCRIPTION: &str = r#"The Content-Type representation header is used to indicate the original media type of the resource (prior to any content encoding applied for sending)."#;
8013

81-
impl StaticFileResponse {
82-
/// Create a static file response.
83-
pub fn new(res: Result<poem::web::StaticFileResponse, StaticFileError>) -> Self {
84-
res.into()
85-
}
86-
}
87-
88-
impl From<Result<poem::web::StaticFileResponse, StaticFileError>> for StaticFileResponse {
89-
fn from(res: Result<poem::web::StaticFileResponse, StaticFileError>) -> Self {
90-
match res {
91-
Ok(poem::web::StaticFileResponse::Ok {
92-
body,
93-
etag,
94-
last_modified,
95-
content_type,
96-
..
97-
}) => StaticFileResponse::Ok(Binary(body), etag, last_modified, content_type),
98-
Ok(poem::web::StaticFileResponse::NotModified) => StaticFileResponse::NotModified,
99-
Err(
100-
StaticFileError::MethodNotAllowed(_)
101-
| StaticFileError::NotFound
102-
| StaticFileError::InvalidPath
103-
| StaticFileError::Forbidden(_),
104-
) => StaticFileResponse::NotFound,
105-
Err(StaticFileError::PreconditionFailed) => StaticFileResponse::PreconditionFailed,
106-
Err(StaticFileError::RangeNotSatisfiable { size }) => {
107-
StaticFileResponse::RangeNotSatisfiable(format!("*/{}", size))
108-
}
109-
Err(StaticFileError::Io(_)) => StaticFileResponse::BadRequest,
14+
impl ApiResponse for StaticFileResponse {
15+
fn meta() -> MetaResponses {
16+
MetaResponses {
17+
responses: vec![
18+
MetaResponse {
19+
description: "",
20+
status: Some(200),
21+
content: vec![MetaMediaType {
22+
content_type: Binary::<Body>::CONTENT_TYPE,
23+
schema: Binary::<Body>::schema_ref(),
24+
}],
25+
headers: vec![MetaHeader {
26+
name: "etag".to_string(),
27+
description: Some(ETAG_DESCRIPTION.to_string()),
28+
required: false,
29+
deprecated: false,
30+
schema: String::schema_ref(),
31+
}, MetaHeader {
32+
name: "last-modified".to_string(),
33+
description: Some(LAST_MODIFIED_DESCRIPTION.to_string()),
34+
required: false,
35+
deprecated: false,
36+
schema: String::schema_ref(),
37+
}, MetaHeader {
38+
name: "content-type".to_string(),
39+
description: Some(CONTENT_TYPE_DESCRIPTION.to_string()),
40+
required: false,
41+
deprecated: false,
42+
schema: String::schema_ref(),
43+
}],
44+
},
45+
MetaResponse {
46+
description: "Not modified",
47+
status: Some(304),
48+
content: vec![],
49+
headers: vec![],
50+
},
51+
MetaResponse {
52+
description: "Bad request",
53+
status: Some(400),
54+
content: vec![],
55+
headers: vec![],
56+
},
57+
MetaResponse {
58+
description: "Resource was not found",
59+
status: Some(404),
60+
content: vec![],
61+
headers: vec![],
62+
},
63+
MetaResponse {
64+
description: "Precondition failed",
65+
status: Some(412),
66+
content: vec![],
67+
headers: vec![],
68+
},
69+
MetaResponse {
70+
description: "The Content-Range response HTTP header indicates where in a full body message a partial message belongs.",
71+
status: Some(416),
72+
content: vec![],
73+
headers: vec![],
74+
}, MetaResponse {
75+
description: "Internal server error",
76+
status: Some(500),
77+
content: vec![],
78+
headers: vec![],
79+
},
80+
],
11081
}
11182
}
83+
84+
fn register(_registry: &mut Registry) {}
11285
}

poem/src/web/static_file.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{
77
time::{SystemTime, UNIX_EPOCH},
88
};
99

10+
use bytes::Bytes;
1011
use headers::{
1112
ContentRange, ETag, HeaderMapExt, IfMatch, IfModifiedSince, IfNoneMatch, IfUnmodifiedSince,
1213
Range,
@@ -42,6 +43,16 @@ pub enum StaticFileResponse {
4243
NotModified,
4344
}
4445

46+
impl StaticFileResponse {
47+
/// Set the content type
48+
pub fn with_content_type(mut self, ct: impl Into<String>) -> Self {
49+
if let StaticFileResponse::Ok { content_type, .. } = &mut self {
50+
*content_type = Some(ct.into());
51+
}
52+
self
53+
}
54+
}
55+
4556
impl IntoResponse for StaticFileResponse {
4657
fn into_response(self) -> Response {
4758
match self {
@@ -104,6 +115,59 @@ impl<'a> FromRequest<'a> for StaticFileRequest {
104115
}
105116

106117
impl StaticFileRequest {
118+
/// Create static file response.
119+
///
120+
/// `prefer_utf8` - Specifies whether text responses should signal a UTF-8
121+
/// encoding.
122+
pub fn create_response_from_data(
123+
self,
124+
data: impl AsRef<[u8]>,
125+
) -> Result<StaticFileResponse, StaticFileError> {
126+
let data = data.as_ref();
127+
128+
// content length
129+
let mut content_length = data.len() as u64;
130+
let mut content_range = None;
131+
132+
let body = if let Some((start, end)) = self.range.and_then(|range| range.iter().next()) {
133+
let start = match start {
134+
Bound::Included(n) => n,
135+
Bound::Excluded(n) => n + 1,
136+
Bound::Unbounded => 0,
137+
};
138+
let end = match end {
139+
Bound::Included(n) => n + 1,
140+
Bound::Excluded(n) => n,
141+
Bound::Unbounded => content_length,
142+
};
143+
if end < start || end > content_length {
144+
return Err(StaticFileError::RangeNotSatisfiable {
145+
size: content_length,
146+
});
147+
}
148+
149+
if start != 0 || end != content_length {
150+
content_range = Some((start..end, content_length));
151+
}
152+
153+
content_length = end - start;
154+
Body::from_bytes(Bytes::copy_from_slice(
155+
&data[start as usize..(start + content_length) as usize],
156+
))
157+
} else {
158+
Body::from_bytes(Bytes::copy_from_slice(data))
159+
};
160+
161+
Ok(StaticFileResponse::Ok {
162+
body,
163+
content_length,
164+
content_type: None,
165+
etag: None,
166+
last_modified: None,
167+
content_range,
168+
})
169+
}
170+
107171
/// Create static file response.
108172
///
109173
/// `prefer_utf8` - Specifies whether text responses should signal a UTF-8

0 commit comments

Comments
 (0)