1use reqwest::header::HeaderMap;
2use std::{collections::HashMap, str::FromStr};
3use std::path::PathBuf;
4use crate::services::AppwriteException;
5
6#[derive(Clone)]
7pub struct Client {
8 endpoint: url::Url,
9 headers: HeaderMap,
10 client: reqwest::blocking::Client,
11}
12
13pub static CHUNK_SIZE: u64 = 5*1024*1024; #[derive(Clone, Deserialize, Serialize, Debug)]
16#[serde(untagged)]
17pub enum ParamType {
18 Bool(bool),
19 Number(i64),
20 String(String),
21 Array(Vec<ParamType>),
22 FilePath(PathBuf),
23 Object(HashMap<String, ParamType>),
24 Float(f64),
25 StreamData(Vec<u8>, String),
26 OptionalBool(Option<bool>),
27 OptionalNumber(Option<i64>),
28 OptionalArray(Option<Vec<ParamType>>),
29 OptionalFilePath(Option<PathBuf>),
30 OptionalObject(Option<HashMap<String, ParamType>>),
31 OptionalFloat(Option<f64>),
32 DocumentList(crate::models::DocumentList),
33 CollectionList(crate::models::CollectionList),
34 IndexList(crate::models::IndexList),
35 UserList(crate::models::UserList),
36 SessionList(crate::models::SessionList),
37 LogList(crate::models::LogList),
38 FileList(crate::models::FileList),
39 BucketList(crate::models::BucketList),
40 TeamList(crate::models::TeamList),
41 MembershipList(crate::models::MembershipList),
42 FunctionList(crate::models::FunctionList),
43 RuntimeList(crate::models::RuntimeList),
44 DeploymentList(crate::models::DeploymentList),
45 ExecutionList(crate::models::ExecutionList),
46 ProjectList(crate::models::ProjectList),
47 WebhookList(crate::models::WebhookList),
48 KeyList(crate::models::KeyList),
49 PlatformList(crate::models::PlatformList),
50 DomainList(crate::models::DomainList),
51 CountryList(crate::models::CountryList),
52 ContinentList(crate::models::ContinentList),
53 LanguageList(crate::models::LanguageList),
54 CurrencyList(crate::models::CurrencyList),
55 PhoneList(crate::models::PhoneList),
56 MetricList(crate::models::MetricList),
57 Collection(crate::models::Collection),
58 AttributeList(crate::models::AttributeList),
59 AttributeString(crate::models::AttributeString),
60 AttributeInteger(crate::models::AttributeInteger),
61 AttributeFloat(crate::models::AttributeFloat),
62 AttributeBoolean(crate::models::AttributeBoolean),
63 AttributeEmail(crate::models::AttributeEmail),
64 AttributeEnum(crate::models::AttributeEnum),
65 AttributeIp(crate::models::AttributeIp),
66 AttributeUrl(crate::models::AttributeUrl),
67 Index(crate::models::Index),
68 Document(crate::models::Document),
69 Log(crate::models::Log),
70 User(crate::models::User),
71 Preferences(crate::models::Preferences),
72 Session(crate::models::Session),
73 Token(crate::models::Token),
74 Jwt(crate::models::Jwt),
75 Locale(crate::models::Locale),
76 File(crate::models::File),
77 Bucket(crate::models::Bucket),
78 Team(crate::models::Team),
79 Membership(crate::models::Membership),
80 Function(crate::models::Function),
81 Runtime(crate::models::Runtime),
82 Deployment(crate::models::Deployment),
83 Execution(crate::models::Execution),
84 Project(crate::models::Project),
85 Webhook(crate::models::Webhook),
86 Key(crate::models::Key),
87 Domain(crate::models::Domain),
88 Platform(crate::models::Platform),
89 Country(crate::models::Country),
90 Continent(crate::models::Continent),
91 Language(crate::models::Language),
92 Currency(crate::models::Currency),
93 Phone(crate::models::Phone),
94 HealthAntivirus(crate::models::HealthAntivirus),
95 HealthQueue(crate::models::HealthQueue),
96 HealthStatus(crate::models::HealthStatus),
97 HealthTime(crate::models::HealthTime),
98 Metric(crate::models::Metric),
99 UsageDatabase(crate::models::UsageDatabase),
100 UsageCollection(crate::models::UsageCollection),
101 UsageUsers(crate::models::UsageUsers),
102 UsageStorage(crate::models::UsageStorage),
103 UsageBuckets(crate::models::UsageBuckets),
104 UsageFunctions(crate::models::UsageFunctions),
105 UsageProject(crate::models::UsageProject),
106 }
107
108fn handleOptional(param: ParamType) -> Option<ParamType> {
110 match param {
111 ParamType::OptionalBool(value) => match value {
112 Some(data) => Some(ParamType::Bool(data)),
113 None => None
114 }
115 ParamType::OptionalNumber(value) => match value {
116 Some(data) => Some(ParamType::Number(data)),
117 None => None
118 }
119 ParamType::OptionalArray(value) => match value {
120 Some(data) => Some(ParamType::Array(data)),
121 None => None
122 }
123 ParamType::OptionalFilePath(value) => match value {
124 Some(data) => Some(ParamType::FilePath(data)),
125 None => None
126 }
127 ParamType::OptionalObject(value) => match value {
128 Some(data) => Some(ParamType::Object(data)),
129 None => None
130 }
131 ParamType::OptionalFloat(value) => match value {
132 Some(data) => Some(ParamType::Float(data)),
133 None => None
134 }
135 _ => Some(param)
136 }
137}
138
139impl Client {
154 pub fn new() -> Self {
155 let mut new_headers = HeaderMap::new();
156
157 new_headers.insert("x-sdk-version", "appwrite:rust:0.0.2".parse().unwrap());
158 new_headers.insert("user-agent", format!("{}-rust-{}", std::env::consts::OS, "0.0.2").parse().unwrap());
159 new_headers.insert("X-Appwrite-Response-Format", "0.7.0".parse().unwrap());
160
161 Self {
162 endpoint: "https://HOSTNAME/v1".parse().unwrap(),
163 headers: new_headers,
164 client: reqwest::blocking::Client::builder()
165 .build().unwrap(),
166 }
167 }
168
169 pub fn add_header(&mut self, key: String, value: String) {
170 self.headers.append(
171 reqwest::header::HeaderName::from_str(&key).unwrap(),
172 (&value.to_lowercase()).parse().unwrap(),
173 );
174 }
175
176 pub fn add_self_signed(&mut self, value: bool) {
177 self.client = reqwest::blocking::Client::builder().danger_accept_invalid_certs(value).build().unwrap();
178 }
179
180 pub fn set_project(&mut self, value: &str) {
182 self.add_header("X-Appwrite-Project".to_string(), value.to_string())
183 }
184
185 pub fn set_key(&mut self, value: &str) {
187 self.add_header("X-Appwrite-Key".to_string(), value.to_string())
188 }
189
190 pub fn set_jwt(&mut self, value: &str) {
192 self.add_header("X-Appwrite-JWT".to_string(), value.to_string())
193 }
194
195 pub fn set_locale(&mut self, value: &str) {
196 self.add_header("X-Appwrite-Locale".to_string(), value.to_string())
197 }
198
199 pub fn set_mode(&mut self, value: &str) {
200 self.add_header("X-Appwrite-Mode".to_string(), value.to_string())
201 }
202
203
204 pub fn set_endpoint(&mut self, endpoint: &str) {
205 self.endpoint = endpoint.parse().unwrap()
206 }
207
208 pub fn call(
209 self,
210 method: &str,
211 path: &str,
212 headers: Option<HashMap<String, String>>,
213 params: Option<HashMap<String, ParamType>>,
214 ) -> Result<reqwest::blocking::Response, AppwriteException> {
215 let mut content_type: String = "application/json".to_string();
218
219 let request_headers: HeaderMap = match headers {
220 Some(data) => {
221 let mut headers = self.headers.clone();
222
223 for (k, v) in data {
224 if k == "content-type" {
225 content_type = v.to_string()
226 } else {
227 headers.append(
228 reqwest::header::HeaderName::from_lowercase(k.as_bytes()).unwrap(),
229 (&v.to_lowercase()).parse().unwrap(),
230 );
231 }
232 }
233
234 headers
235 }
236 None => self.headers.clone(),
237 };
238
239 let method_type = match method {
241 "GET" => reqwest::Method::GET,
242 "POST" => reqwest::Method::POST,
243 "OPTIONS" => reqwest::Method::OPTIONS,
244 "PUT" => reqwest::Method::PUT,
245 "DELETE" => reqwest::Method::DELETE,
246 "HEAD" => reqwest::Method::HEAD,
247 "PATCH" => reqwest::Method::PATCH,
248 _ => reqwest::Method::GET,
249 };
250
251 let mut request = self
252 .client
253 .request(method_type, self.endpoint.join(&format!("{}{}", "v1", path)).unwrap());
254
255 match params {
256 Some(data) => {
257 let flattened_data = flatten(FlattenType::Normal(data.clone()), None);
258
259 let mut buffer: Vec<(String, ParamType)> = Vec::new();
263 for (k, v) in flattened_data {
264 match handleOptional(v) {
265 Some(data) => buffer.push((k, data)),
266 None => {}
267 }
268 }
269 let flattened_data = buffer;
270
271 if content_type.starts_with("multipart/form-data") {
273 let mut form = reqwest::blocking::multipart::Form::new();
274
275 for (k, v) in flattened_data.clone() {
276 match v {
277 ParamType::Bool(data) => {
278 form = form.text(k, data.to_string());
279 }
280 ParamType::String(data) => form = form.text(k, data),
281 ParamType::FilePath(data) => form = form.file(k, data).unwrap(),
282 ParamType::Number(data) => form = form.text(k, data.to_string()),
283 ParamType::Float(data) => form = form.text(k, data.to_string()),
284 ParamType::StreamData(data, filename) => form = form.part(k, reqwest::blocking::multipart::Part::bytes(data).file_name(filename)),
285 ParamType::Array(_data) => {
287 },
289 ParamType::Object(_data) => {
290 },
292 _ => {}
293 }
294 }
295 request = request.multipart(form);
296 }
297
298 if content_type.starts_with("application/json") && method != "GET" {
299 request = request.json(&data);
300 }
301
302 if method == "GET" {
303 request = request.query(&queryize_data(flatten(FlattenType::Normal(data), None)));
304 }
305 }
306 None => {}
307 }
308
309 request = request.headers(request_headers);
310
311 match request.send() {
312 Ok(data) => {
313 if data.status().is_success() {
314 Ok(data)
315 } else {
316 let dataString = match data.text() {
317 Ok(data) => {data},
318 Err(err) => {
319 return Err(AppwriteException::new(format!("A error occoured. ERR: {}, This could either be a connection error or an internal Appwrite error. Please check your Appwrite instance logs. ", err), 0, "".to_string()))
321 }
322 };
323
324 Err(match serde_json::from_str(&dataString) {
326 Ok(data) => data,
327 Err(_err) => {
328 AppwriteException::new(format!("{}", dataString), 0, "".to_string())
329 }
330 })
331 }
332 },
333 Err(err) => {
334 Err(AppwriteException::new(format!("{}", err), 0, "".to_string()))
336 },
337 }
338 }
339}
340
341enum FlattenType {
342 Normal(HashMap<String, ParamType>),
343 Nested(Vec<ParamType>),
344}
345
346fn queryize_data(data: Vec<(String, ParamType)>) -> Vec<(String, String)> {
347 let mut output: Vec<(String, String)> = Default::default();
348
349 for (k, v) in data {
350 match v {
351 ParamType::Bool(value) => output.push((k, value.to_string())),
352 ParamType::String(value) => output.push((k, value)),
353 ParamType::Number(value) => output.push((k, value.to_string())),
354 _ => {}
355 }
356 }
357
358 output
359}
360
361fn flatten(data: FlattenType, prefix: Option<String>) -> Vec<(String, ParamType)> {
362 let mut output: Vec<(String, ParamType)> = Default::default();
363
364 match data {
365 FlattenType::Normal(data) => {
366 for (k, v) in data {
367 let final_key = match &prefix {
368 Some(current_prefix) => format!("{}[{}]", current_prefix, k),
369 None => k,
370 };
371
372 match v {
373 ParamType::Array(value) => {
374 output.append(&mut flatten(FlattenType::Nested(value), Some(final_key)));
375 }
376 ParamType::Object(value) => {
377 output.extend(flatten(FlattenType::Normal(value), Some(final_key)).into_iter())
378 },
379 value => {
380 output.push((final_key, value));
381 }
382 }
383 }
384 }
385
386 FlattenType::Nested(data) => {
387 for (k, v) in data.iter().enumerate() {
388 let final_key = match &prefix {
389 Some(current_prefix) => format!("{}[{}]", current_prefix, k),
390 None => k.to_string(),
391 };
392
393 match v {
394 ParamType::Array(value) => {
395 flatten(FlattenType::Nested(value.to_owned()), Some(final_key))
396 .append(&mut output);
397 }
398 value => {
399 output.push((final_key, value.to_owned()));
400 }
401 }
402 }
403 }
404 }
405
406 output
407}