1use crate::http_client::HttpClient;
7use crate::Bucket;
8use chrono::{DateTime, Utc};
9use http::Method;
10use reduct_base::error::ReductError;
11use reduct_base::msg::entry_api::QueryEntry;
12use reduct_base::msg::query_link_api::{QueryLinkCreateRequest, QueryLinkCreateResponse};
13use std::sync::Arc;
14
15pub struct CreateQueryLinkBuilder {
16 request: QueryLinkCreateRequest,
17 file_name: Option<String>,
18 http_client: Arc<HttpClient>,
19}
20
21impl CreateQueryLinkBuilder {
22 pub(crate) fn new(bucket: String, entry: String, http_client: Arc<HttpClient>) -> Self {
23 Self {
24 request: QueryLinkCreateRequest {
25 bucket,
26 entry,
27 expire_at: Utc::now() + chrono::Duration::hours(24),
28 ..Default::default()
29 },
30 file_name: None,
31 http_client,
32 }
33 }
34
35 pub fn expire_at(mut self, expire_at: DateTime<Utc>) -> Self {
37 self.request.expire_at = expire_at;
38 self
39 }
40
41 pub fn index(mut self, index: u64) -> Self {
43 self.request.index = Some(index);
44 self
45 }
46
47 pub fn query(mut self, query: QueryEntry) -> Self {
49 self.request.query = query;
50 self
51 }
52
53 pub fn file_name(mut self, file_name: &str) -> Self {
55 self.file_name = Some(file_name.to_string());
56 self
57 }
58
59 pub fn base_url(mut self, base_url: &str) -> Self {
61 self.request.base_url = Some(base_url.to_string());
62 self
63 }
64
65 pub async fn send(self) -> Result<String, ReductError> {
67 let file_name = self.file_name.unwrap_or(format!(
68 "{}_{}.bin",
69 self.request.entry,
70 self.request.index.unwrap_or(0)
71 ));
72 let response: QueryLinkCreateResponse = self
73 .http_client
74 .send_and_receive_json(
75 Method::POST,
76 &format!("/links/{}", file_name),
77 Some(self.request),
78 )
79 .await?;
80 Ok(response.link)
81 }
82}
83
84impl Bucket {
85 pub fn create_query_link(&self, entry: &str) -> CreateQueryLinkBuilder {
95 CreateQueryLinkBuilder::new(
96 self.name.clone(),
97 entry.to_string(),
98 self.http_client.clone(),
99 )
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use crate::bucket::tests::bucket;
106 use crate::Bucket;
107 use reduct_base::msg::entry_api::QueryEntry;
108 use rstest::rstest;
109
110 #[cfg(feature = "test-api-117")]
111 #[rstest]
112 #[tokio::test]
113 async fn test_link_creation(#[future] bucket: Bucket) {
114 let bucket: Bucket = bucket.await;
115 let link = bucket
116 .create_query_link("entry-1")
117 .expire_at(chrono::Utc::now() + chrono::Duration::hours(1))
118 .send()
119 .await
120 .unwrap();
121
122 let body = reqwest::get(&link).await.unwrap().text().await.unwrap();
123 assert_eq!(body, "Hey entry-1!");
124 }
125
126 #[cfg(feature = "test-api-117")]
127 #[rstest]
128 #[tokio::test]
129 async fn test_link_creation_with_query(#[future] bucket: Bucket) {
130 let bucket: Bucket = bucket.await;
131 let link = bucket
132 .create_query_link("entry-1")
133 .query(QueryEntry {
134 start: Some(0),
135 ..Default::default()
136 })
137 .send()
138 .await
139 .unwrap();
140 let body = reqwest::get(&link).await.unwrap().text().await.unwrap();
141 assert_eq!(body, "Hey entry-1!");
142 }
143
144 #[cfg(feature = "test-api-117")]
145 #[rstest]
146 #[tokio::test]
147 async fn test_link_creation_with_index(#[future] bucket: Bucket) {
148 let bucket: Bucket = bucket.await;
149 let link = bucket
150 .create_query_link("entry-1")
151 .index(1)
152 .send()
153 .await
154 .unwrap();
155 let body = reqwest::get(&link).await.unwrap().text().await.unwrap();
156 assert_eq!(body, r#"{"detail": "Record number out of range"}"#)
157 }
158
159 #[cfg(feature = "test-api-117")]
160 #[rstest]
161 #[tokio::test]
162 async fn test_link_creation_expired(#[future] bucket: Bucket) {
163 let bucket: Bucket = bucket.await;
164 let link = bucket
165 .create_query_link("entry-1")
166 .expire_at(chrono::Utc::now() - chrono::Duration::hours(1))
167 .send()
168 .await
169 .unwrap();
170 let response = reqwest::get(&link).await.unwrap();
171 assert_eq!(response.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY);
172 }
173
174 #[cfg(feature = "test-api-117")]
175 #[rstest]
176 #[tokio::test]
177 async fn test_link_creation_file_name(#[future] bucket: Bucket) {
178 let bucket: Bucket = bucket.await;
179 let link = bucket
180 .create_query_link("entry-1")
181 .file_name("my-link.bin")
182 .send()
183 .await
184 .unwrap();
185 assert!(link.contains("links/my-link.bin?"));
186 }
187}