poem_openapi/payload/
attachment.rs1use std::fmt::Write;
2
3use poem::{http::header::CONTENT_DISPOSITION, Body, IntoResponse, Response};
4
5use crate::{
6 payload::{Binary, Payload},
7 registry::{MetaHeader, MetaMediaType, MetaResponse, MetaResponses, MetaSchemaRef, Registry},
8 types::Type,
9 ApiResponse,
10};
11
12const CONTENT_DISPOSITION_DESC: &str = "Indicate if the content is expected to be displayed inline in the browser, that is, as a Web page or as part of a Web page, or as an attachment, that is downloaded and saved locally.";
13
14#[derive(Debug, Copy, Clone, Eq, PartialEq)]
16pub enum AttachmentType {
17 Inline,
19 Attachment,
22}
23
24impl AttachmentType {
25 #[inline]
26 fn as_str(&self) -> &'static str {
27 match self {
28 AttachmentType::Inline => "inline",
29 AttachmentType::Attachment => "attachment",
30 }
31 }
32}
33
34#[derive(Debug, Clone, Eq, PartialEq)]
36pub struct Attachment<T> {
37 data: Binary<T>,
38 ty: AttachmentType,
39 filename: Option<String>,
40}
41
42impl<T: Into<Body> + Send> Attachment<T> {
43 pub fn new(data: T) -> Self {
45 Self {
46 data: Binary(data),
47 ty: AttachmentType::Attachment,
48 filename: None,
49 }
50 }
51
52 #[must_use]
54 pub fn attachment_type(self, ty: AttachmentType) -> Self {
55 Self { ty, ..self }
56 }
57
58 #[must_use]
60 pub fn filename(self, filename: impl Into<String>) -> Self {
61 Self {
62 filename: Some(filename.into()),
63 ..self
64 }
65 }
66
67 fn content_disposition(&self) -> String {
68 let mut content_disposition = self.ty.as_str().to_string();
69
70 if let Some(legal_filename) = self.filename.as_ref().map(|filename| {
71 filename
72 .replace('\\', "\\\\")
73 .replace('\"', "\\\"")
74 .replace('\r', "\\\r")
75 .replace('\n', "\\\n")
76 }) {
77 _ = write!(content_disposition, "; filename=\"{legal_filename}\"");
78 }
79
80 content_disposition
81 }
82}
83
84impl<T: Into<Body> + Send> Payload for Attachment<T> {
85 const CONTENT_TYPE: &'static str = Binary::<T>::CONTENT_TYPE;
86
87 fn schema_ref() -> MetaSchemaRef {
88 Binary::<T>::schema_ref()
89 }
90}
91
92impl<T: Into<Body> + Send> IntoResponse for Attachment<T> {
93 fn into_response(self) -> Response {
94 let content_disposition = self.content_disposition();
95 self.data
96 .with_header(CONTENT_DISPOSITION, content_disposition)
97 .into_response()
98 }
99}
100
101impl<T: Into<Body> + Send> ApiResponse for Attachment<T> {
102 fn meta() -> MetaResponses {
103 MetaResponses {
104 responses: vec![MetaResponse {
105 description: "",
106 status: Some(200),
107 content: vec![MetaMediaType {
108 content_type: Self::CONTENT_TYPE,
109 schema: Self::schema_ref(),
110 }],
111 headers: vec![MetaHeader {
112 name: "Content-Disposition".to_string(),
113 description: Some(CONTENT_DISPOSITION_DESC.to_string()),
114 required: true,
115 deprecated: false,
116 schema: String::schema_ref(),
117 }],
118 }],
119 }
120 }
121
122 fn register(_registry: &mut Registry) {}
123}