bitwarden_vault/cipher/attachment_client/admin/
download_url.rs1use bitwarden_core::{ApiError, MissingFieldError};
2use bitwarden_error::bitwarden_error;
3use reqwest::StatusCode;
4use thiserror::Error;
5#[cfg(feature = "wasm")]
6use wasm_bindgen::prelude::wasm_bindgen;
7
8use crate::{AttachmentAdminClient, CipherId};
9
10#[allow(missing_docs)]
11#[bitwarden_error(flat)]
12#[derive(Debug, Error)]
13pub enum CipherAdminGetAttachmentDownloadUrlError {
14 #[error(transparent)]
15 Api(#[from] ApiError),
16 #[error(transparent)]
17 MissingField(#[from] MissingFieldError),
18 #[error("Attachment not found")]
19 NotFound,
20}
21
22impl<T> From<bitwarden_api_api::apis::Error<T>> for CipherAdminGetAttachmentDownloadUrlError {
23 fn from(value: bitwarden_api_api::apis::Error<T>) -> Self {
24 Self::Api(value.into())
25 }
26}
27
28#[cfg_attr(feature = "wasm", wasm_bindgen)]
29impl AttachmentAdminClient {
30 pub async fn get_attachment_download_url(
34 &self,
35 cipher_id: CipherId,
36 attachment_id: String,
37 ) -> Result<String, CipherAdminGetAttachmentDownloadUrlError> {
38 let response = self
39 .api_configurations
40 .api_client
41 .ciphers_api()
42 .get_attachment_data_admin(cipher_id.into(), &attachment_id)
43 .await
44 .map_err(|e| match e {
45 bitwarden_api_api::apis::Error::Response(content)
46 if content.status == StatusCode::NOT_FOUND =>
47 {
48 CipherAdminGetAttachmentDownloadUrlError::NotFound
49 }
50 other => other.into(),
51 })?;
52
53 response.url.ok_or_else(|| MissingFieldError("url").into())
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use std::sync::Arc;
60
61 use bitwarden_api_api::{apis::ApiClient, models::AttachmentResponseModel};
62 use bitwarden_core::client::ApiConfigurations;
63 use reqwest::StatusCode;
64
65 use super::*;
66
67 const TEST_CIPHER_ID: &str = "5faa9684-c793-4a2d-8a12-b33900187097";
68 const TEST_ATTACHMENT_ID: &str = "uf7bkexzag04d3cw04jsbqqkbpbwhxs0";
69 const TEST_API_URL: &str = "http://localhost:4000/attachments/test/api";
70
71 fn client_with_api(api_client: ApiClient) -> AttachmentAdminClient {
72 AttachmentAdminClient {
73 api_configurations: Arc::new(ApiConfigurations::from_api_client(api_client)),
74 }
75 }
76
77 #[tokio::test]
78 async fn returns_url_from_api_response() {
79 let api_client = ApiClient::new_mocked(|mock| {
80 mock.ciphers_api
81 .expect_get_attachment_data_admin()
82 .returning(|id, attachment_id| {
83 assert_eq!(&id.to_string(), TEST_CIPHER_ID);
84 assert_eq!(attachment_id, TEST_ATTACHMENT_ID);
85 Ok(AttachmentResponseModel {
86 id: Some(TEST_ATTACHMENT_ID.to_string()),
87 url: Some(TEST_API_URL.to_string()),
88 ..Default::default()
89 })
90 });
91 });
92
93 let client = client_with_api(api_client);
94 let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
95 let url = client
96 .get_attachment_download_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
97 .await
98 .unwrap();
99
100 assert_eq!(url, TEST_API_URL);
101 }
102
103 #[tokio::test]
104 async fn returns_missing_field_when_response_has_no_url() {
105 let api_client = ApiClient::new_mocked(|mock| {
106 mock.ciphers_api
107 .expect_get_attachment_data_admin()
108 .returning(|_id, _attachment_id| {
109 Ok(AttachmentResponseModel {
110 id: Some(TEST_ATTACHMENT_ID.to_string()),
111 url: None,
112 ..Default::default()
113 })
114 });
115 });
116
117 let client = client_with_api(api_client);
118 let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
119 let err = client
120 .get_attachment_download_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
121 .await
122 .unwrap_err();
123
124 assert!(matches!(
125 err,
126 CipherAdminGetAttachmentDownloadUrlError::MissingField(_)
127 ));
128 }
129
130 #[tokio::test]
131 async fn returns_not_found_on_404() {
132 let api_client = ApiClient::new_mocked(|mock| {
133 mock.ciphers_api
134 .expect_get_attachment_data_admin()
135 .returning(|_id, _attachment_id| {
136 Err(bitwarden_api_api::apis::Error::Response(
137 bitwarden_api_api::apis::ResponseContent {
138 status: StatusCode::NOT_FOUND,
139 message: String::new(),
140 },
141 ))
142 });
143 });
144
145 let client = client_with_api(api_client);
146 let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
147 let err = client
148 .get_attachment_download_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
149 .await
150 .unwrap_err();
151
152 assert!(matches!(
153 err,
154 CipherAdminGetAttachmentDownloadUrlError::NotFound
155 ));
156 }
157
158 #[tokio::test]
159 async fn propagates_non_404_api_errors() {
160 let api_client = ApiClient::new_mocked(|mock| {
161 mock.ciphers_api
162 .expect_get_attachment_data_admin()
163 .returning(|_id, _attachment_id| {
164 Err(bitwarden_api_api::apis::Error::Response(
165 bitwarden_api_api::apis::ResponseContent {
166 status: StatusCode::INTERNAL_SERVER_ERROR,
167 message: "bitwarden".to_string(),
168 },
169 ))
170 });
171 });
172
173 let client = client_with_api(api_client);
174 let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
175 let err = client
176 .get_attachment_download_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
177 .await
178 .unwrap_err();
179
180 assert!(matches!(
181 err,
182 CipherAdminGetAttachmentDownloadUrlError::Api(_)
183 ));
184 }
185}