Skip to content

Commit

Permalink
Remove poem_openapi::response::StaticFileResponse and implement `Ap…
Browse files Browse the repository at this point in the history
…iResponse trait` for `poem::web::StaticFileResponse`
  • Loading branch information
sunli829 committed Jan 13, 2023
1 parent d5f1a65 commit 1a7dba2
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 111 deletions.
2 changes: 1 addition & 1 deletion poem-openapi-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "poem-openapi-derive"
version = "2.0.22"
version = "2.0.23"
authors = ["sunli <[email protected]>"]
edition = "2021"
description = "Macros for poem-openapi"
Expand Down
6 changes: 6 additions & 0 deletions poem-openapi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [2.0.23] 2022-01-13

- Add the missing feature `openapi-explorer` in `ui` mod [#480](https://github.com/poem-web/poem/pull/480)
- Add yaml support [#476](https://github.com/poem-web/poem/pull/476)
- Remove `poem_openapi::response::StaticFileResponse` and implement `ApiResponse trait` for `poem::web::StaticFileResponse`

# [2.0.22] 2023-01-11

- Add support for OpenAPI Explorer [#440](https://github.com/poem-web/poem/pull/440)
Expand Down
6 changes: 3 additions & 3 deletions poem-openapi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "poem-openapi"
version = "2.0.22"
version = "2.0.23"
authors = ["sunli <[email protected]>"]
edition = "2021"
description = "OpenAPI support for Poem."
Expand All @@ -23,14 +23,14 @@ static-files = ["poem/static-files"]
websocket = ["poem/websocket"]

[dependencies]
poem-openapi-derive = { path = "../poem-openapi-derive", version = "2.0.22" }
poem-openapi-derive = { path = "../poem-openapi-derive", version = "2.0.23" }
poem = { path = "../poem", version = "1.3.51", features = [
"multipart",
"tempfile",
"cookie",
"sse",
"xml",
"yaml"
"yaml",
] }

tokio = { version = "1.17.0", features = ["fs"] }
Expand Down
2 changes: 1 addition & 1 deletion poem-openapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pub mod param;
pub mod payload;
#[doc(hidden)]
pub mod registry;
pub mod response;
mod response;
pub mod types;
#[doc(hidden)]
pub mod validation;
Expand Down
3 changes: 0 additions & 3 deletions poem-openapi/src/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@
#[cfg(feature = "static-files")]
mod static_file;

#[cfg(feature = "static-files")]
pub use static_file::StaticFileResponse;
179 changes: 76 additions & 103 deletions poem-openapi/src/response/static_file.rs
Original file line number Diff line number Diff line change
@@ -1,112 +1,85 @@
use poem::{error::StaticFileError, Body};
use poem::{web::StaticFileResponse, Body};

use crate::{
payload::{Binary, PlainText},
payload::{Binary, Payload},
registry::{MetaHeader, MetaMediaType, MetaResponse, MetaResponses, Registry},
types::Type,
ApiResponse,
};

/// A static file response.
#[cfg_attr(docsrs, doc(cfg(feature = "static-files")))]
#[derive(ApiResponse)]
#[oai(internal)]
pub enum StaticFileResponse {
/// Ok
#[oai(status = 200)]
Ok(
Binary<Body>,
/// 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").
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag>
#[oai(header = "etag")]
Option<String>,
/// 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.
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified>
#[oai(header = "last-modified")]
Option<String>,
/// The Content-Type representation header is used to indicate the
/// original media type of the resource (prior to any content encoding
/// applied for sending).
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type>
#[oai(header = "content-type")]
Option<String>,
),
/// Not modified
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>
#[oai(status = 304)]
NotModified,
/// Bad request
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>
#[oai(status = 400)]
BadRequest,
/// Resource was not found
#[oai(status = 404)]
NotFound,
/// Precondition failed
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>
#[oai(status = 412)]
PreconditionFailed,
/// Range not satisfiable
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>
#[oai(status = 416)]
RangeNotSatisfiable(
/// The Content-Range response HTTP header indicates where in a full
/// body message a partial message belongs.
///
/// Reference: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range>
#[oai(header = "content-range")]
String,
),
/// Internal server error
#[oai(status = 500)]
InternalServerError(PlainText<String>),
}
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")."#;
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."#;
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)."#;

impl StaticFileResponse {
/// Create a static file response.
pub fn new(res: Result<poem::web::StaticFileResponse, StaticFileError>) -> Self {
res.into()
}
}

impl From<Result<poem::web::StaticFileResponse, StaticFileError>> for StaticFileResponse {
fn from(res: Result<poem::web::StaticFileResponse, StaticFileError>) -> Self {
match res {
Ok(poem::web::StaticFileResponse::Ok {
body,
etag,
last_modified,
content_type,
..
}) => StaticFileResponse::Ok(Binary(body), etag, last_modified, content_type),
Ok(poem::web::StaticFileResponse::NotModified) => StaticFileResponse::NotModified,
Err(
StaticFileError::MethodNotAllowed(_)
| StaticFileError::NotFound
| StaticFileError::InvalidPath
| StaticFileError::Forbidden(_),
) => StaticFileResponse::NotFound,
Err(StaticFileError::PreconditionFailed) => StaticFileResponse::PreconditionFailed,
Err(StaticFileError::RangeNotSatisfiable { size }) => {
StaticFileResponse::RangeNotSatisfiable(format!("*/{}", size))
}
Err(StaticFileError::Io(_)) => StaticFileResponse::BadRequest,
impl ApiResponse for StaticFileResponse {
fn meta() -> MetaResponses {
MetaResponses {
responses: vec![
MetaResponse {
description: "",
status: Some(200),
content: vec![MetaMediaType {
content_type: Binary::<Body>::CONTENT_TYPE,
schema: Binary::<Body>::schema_ref(),
}],
headers: vec![MetaHeader {
name: "etag".to_string(),
description: Some(ETAG_DESCRIPTION.to_string()),
required: false,
deprecated: false,
schema: String::schema_ref(),
}, MetaHeader {
name: "last-modified".to_string(),
description: Some(LAST_MODIFIED_DESCRIPTION.to_string()),
required: false,
deprecated: false,
schema: String::schema_ref(),
}, MetaHeader {
name: "content-type".to_string(),
description: Some(CONTENT_TYPE_DESCRIPTION.to_string()),
required: false,
deprecated: false,
schema: String::schema_ref(),
}],
},
MetaResponse {
description: "Not modified",
status: Some(304),
content: vec![],
headers: vec![],
},
MetaResponse {
description: "Bad request",
status: Some(400),
content: vec![],
headers: vec![],
},
MetaResponse {
description: "Resource was not found",
status: Some(404),
content: vec![],
headers: vec![],
},
MetaResponse {
description: "Precondition failed",
status: Some(412),
content: vec![],
headers: vec![],
},
MetaResponse {
description: "The Content-Range response HTTP header indicates where in a full body message a partial message belongs.",
status: Some(416),
content: vec![],
headers: vec![],
}, MetaResponse {
description: "Internal server error",
status: Some(500),
content: vec![],
headers: vec![],
},
],
}
}

fn register(_registry: &mut Registry) {}
}
64 changes: 64 additions & 0 deletions poem/src/web/static_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};

use bytes::Bytes;
use headers::{
ContentRange, ETag, HeaderMapExt, IfMatch, IfModifiedSince, IfNoneMatch, IfUnmodifiedSince,
Range,
Expand Down Expand Up @@ -42,6 +43,16 @@ pub enum StaticFileResponse {
NotModified,
}

impl StaticFileResponse {
/// Set the content type
pub fn with_content_type(mut self, ct: impl Into<String>) -> Self {
if let StaticFileResponse::Ok { content_type, .. } = &mut self {
*content_type = Some(ct.into());
}
self
}
}

impl IntoResponse for StaticFileResponse {
fn into_response(self) -> Response {
match self {
Expand Down Expand Up @@ -104,6 +115,59 @@ impl<'a> FromRequest<'a> for StaticFileRequest {
}

impl StaticFileRequest {
/// Create static file response.
///
/// `prefer_utf8` - Specifies whether text responses should signal a UTF-8
/// encoding.
pub fn create_response_from_data(
self,
data: impl AsRef<[u8]>,
) -> Result<StaticFileResponse, StaticFileError> {
let data = data.as_ref();

// content length
let mut content_length = data.len() as u64;
let mut content_range = None;

let body = if let Some((start, end)) = self.range.and_then(|range| range.iter().next()) {
let start = match start {
Bound::Included(n) => n,
Bound::Excluded(n) => n + 1,
Bound::Unbounded => 0,
};
let end = match end {
Bound::Included(n) => n + 1,
Bound::Excluded(n) => n,
Bound::Unbounded => content_length,
};
if end < start || end > content_length {
return Err(StaticFileError::RangeNotSatisfiable {
size: content_length,
});
}

if start != 0 || end != content_length {
content_range = Some((start..end, content_length));
}

content_length = end - start;
Body::from_bytes(Bytes::copy_from_slice(
&data[start as usize..(start + content_length) as usize],
))
} else {
Body::from_bytes(Bytes::copy_from_slice(data))
};

Ok(StaticFileResponse::Ok {
body,
content_length,
content_type: None,
etag: None,
last_modified: None,
content_range,
})
}

/// Create static file response.
///
/// `prefer_utf8` - Specifies whether text responses should signal a UTF-8
Expand Down

0 comments on commit 1a7dba2

Please sign in to comment.