1use serde::Serialize;
4
5use crate::clients::base::{
6 delete_resource, get_raw, get_resource, get_resource_required, head_exists, put_raw,
7 update_resource, ResourceContext,
8};
9use crate::common::{
10 create_hmac_signature, encode_path_segment, sign_storage_content, QueryParams,
11};
12use crate::error::ApifyClientResult;
13use crate::http_client::{HttpClient, HttpMethod, HttpRequest};
14use crate::models::{KeyValueStore, KeyValueStoreKeysPage, KeyValueStoreRecord};
15
16#[derive(Debug, Default, Clone)]
18pub struct ListKeysOptions {
19 pub limit: Option<i64>,
21 pub exclusive_start_key: Option<String>,
23 pub prefix: Option<String>,
25 pub collection: Option<String>,
27 pub signature: Option<String>,
29}
30
31#[derive(Debug, Default, Clone)]
36pub struct GetRecordsOptions {
37 pub collection: Option<String>,
39 pub prefix: Option<String>,
41 pub signature: Option<String>,
43}
44
45#[derive(Debug, Default, Clone)]
50pub struct GetRecordOptions {
51 pub attachment: Option<bool>,
54 pub signature: Option<String>,
56}
57
58#[derive(Debug, Clone)]
60pub struct KeyValueStoreClient {
61 ctx: ResourceContext,
62}
63
64impl KeyValueStoreClient {
65 pub(crate) fn new(http: HttpClient, base_url: &str, resource_path: &str, id: &str) -> Self {
66 Self {
67 ctx: ResourceContext::single(http, base_url, resource_path, id),
68 }
69 }
70
71 pub(crate) fn nested(http: HttpClient, base_url: &str, sub_path: &str) -> Self {
73 Self {
74 ctx: ResourceContext::collection(http, base_url, sub_path),
75 }
76 }
77
78 pub(crate) fn with_public_base(mut self, public_base_url: &str) -> Self {
80 self.ctx = self.ctx.with_public_origin(public_base_url);
81 self
82 }
83
84 pub async fn get(&self) -> ApifyClientResult<Option<KeyValueStore>> {
86 get_resource(&self.ctx, None, &QueryParams::new()).await
87 }
88
89 pub async fn update<T: Serialize>(&self, new_fields: &T) -> ApifyClientResult<KeyValueStore> {
91 update_resource(&self.ctx, None, new_fields).await
92 }
93
94 pub async fn delete(&self) -> ApifyClientResult<()> {
96 delete_resource(&self.ctx, None).await
97 }
98
99 pub async fn list_keys(
101 &self,
102 options: ListKeysOptions,
103 ) -> ApifyClientResult<KeyValueStoreKeysPage> {
104 let mut params = QueryParams::new();
105 params
106 .add_int("limit", options.limit)
107 .add_str("exclusiveStartKey", options.exclusive_start_key)
108 .add_str("prefix", options.prefix)
109 .add_str("collection", options.collection)
110 .add_str("signature", options.signature);
111 get_resource_required(&self.ctx, Some("keys"), ¶ms).await
112 }
113
114 pub async fn get_records(&self, options: GetRecordsOptions) -> ApifyClientResult<Vec<u8>> {
121 let mut params = QueryParams::new();
122 params
123 .add_str("collection", options.collection)
124 .add_str("prefix", options.prefix)
125 .add_str("signature", options.signature);
126 let url = self
127 .ctx
128 .merged_params(¶ms)
129 .apply_to_url(&self.ctx.url(Some("records")));
130 let response = self
131 .ctx
132 .http
133 .call(HttpRequest {
134 method: HttpMethod::Get,
135 url,
136 headers: Default::default(),
137 body: None,
138 timeout: crate::clients::base::DEFAULT_REQUEST_TIMEOUT,
139 })
140 .await?;
141 Ok(response.body)
142 }
143
144 pub async fn record_exists(&self, key: &str) -> ApifyClientResult<bool> {
146 head_exists(
147 &self.ctx,
148 Some(&format!("records/{}", encode_path_segment(key))),
149 &QueryParams::new(),
150 )
151 .await
152 }
153
154 pub async fn get_record(&self, key: &str) -> ApifyClientResult<Option<KeyValueStoreRecord>> {
160 self.get_record_with_options(key, GetRecordOptions::default())
161 .await
162 }
163
164 pub async fn get_record_with_options(
170 &self,
171 key: &str,
172 options: GetRecordOptions,
173 ) -> ApifyClientResult<Option<KeyValueStoreRecord>> {
174 let mut params = QueryParams::new();
175 params
177 .add_bool("attachment", Some(options.attachment.unwrap_or(true)))
178 .add_str("signature", options.signature);
179 let response = get_raw(
180 &self.ctx,
181 Some(&format!("records/{}", encode_path_segment(key))),
182 ¶ms,
183 )
184 .await?;
185 Ok(response.map(|r| {
186 let content_type = r.header("content-type").map(|s| s.to_string());
187 KeyValueStoreRecord {
188 key: key.to_string(),
189 value: r.body,
190 content_type,
191 }
192 }))
193 }
194
195 pub async fn set_record_raw(
197 &self,
198 key: &str,
199 value: Vec<u8>,
200 content_type: &str,
201 ) -> ApifyClientResult<()> {
202 put_raw(
203 &self.ctx,
204 Some(&format!("records/{}", encode_path_segment(key))),
205 &QueryParams::new(),
206 value,
207 content_type,
208 )
209 .await
210 }
211
212 pub async fn set_record_json<T: Serialize>(
214 &self,
215 key: &str,
216 value: &T,
217 ) -> ApifyClientResult<()> {
218 let bytes = serde_json::to_vec(value)?;
219 self.set_record_raw(key, bytes, "application/json; charset=utf-8")
220 .await
221 }
222
223 pub async fn get_record_public_url(&self, key: &str) -> ApifyClientResult<String> {
230 let mut params = QueryParams::new();
231 if let Some(store) = self.get().await? {
232 if let Some(secret) = store
233 .extra
234 .get("urlSigningSecretKey")
235 .and_then(|v| v.as_str())
236 {
237 params.add_str("signature", Some(create_hmac_signature(secret, key)));
238 }
239 }
240 Ok(params.apply_to_url(
241 &self
242 .ctx
243 .public_url(Some(&format!("records/{}", encode_path_segment(key)))),
244 ))
245 }
246
247 pub async fn create_keys_public_url(
253 &self,
254 expires_in_secs: Option<i64>,
255 ) -> ApifyClientResult<String> {
256 let mut params = QueryParams::new();
257 if let Some(store) = self.get().await? {
258 if let Some(secret) = store
259 .extra
260 .get("urlSigningSecretKey")
261 .and_then(|v| v.as_str())
262 {
263 let signature = sign_storage_content(secret, &store.id, expires_in_secs);
264 params.add_str("signature", Some(signature));
265 }
266 }
267 Ok(params.apply_to_url(&self.ctx.public_url(Some("keys"))))
268 }
269
270 pub async fn delete_record(&self, key: &str) -> ApifyClientResult<()> {
272 let url = self
273 .ctx
274 .url(Some(&format!("records/{}", encode_path_segment(key))));
275 self.ctx
276 .http
277 .call(HttpRequest {
278 method: HttpMethod::Delete,
279 url,
280 headers: Default::default(),
281 body: None,
282 timeout: crate::clients::base::DEFAULT_REQUEST_TIMEOUT,
283 })
284 .await?;
285 Ok(())
286 }
287}