jsonbank/
lib.rs

1//! # JsonBank SDK for Rust
2//! Links: [github](https://github.com/jsonbankio/rust-sdk) | [crates.io](https://crates.io/crates/jsonbank) | [docs.rs](https://docs.rs/jsonbank/latest/jsonbank)
3//!
4//! ## Initialization
5//! The JsonBank struct can be initialized with or without api keys.
6//! Api Keys are only required when you want to access **protected/private** documents.
7//!
8//! ### Without Api Keys
9//!
10//! Using this [json file from jsonbank](https://api.jsonbank.io/f/jsonbank/sdk-test/index.json)
11//! ```
12//! use jsonbank::{JsonBank, JsonValue};
13//!
14//! fn main() {
15//!     let jsb = JsonBank::new_without_config();
16//!
17//!     // get public content
18//!     let data: JsonValue = jsb.get_content("jsonbank/sdk-test/index").unwrap();
19//!     assert_eq!(data["author"], "jsonbank")
20//! }
21//! ```
22//!
23//! ### With Api Keys
24//! To get your api keys, visit [jsonbank.io](https://jsonbank.io) and create an account.
25//! ```no_run
26//! use jsonbank::{JsonBank, InitConfig, Keys};
27//!
28//! fn main() {
29//!     let mut  jsb = JsonBank::new(InitConfig {
30//!         host: None, // use default host
31//!         keys: Some(Keys {
32//!             public: Some("Your public key".to_string()),
33//!             private: Some("Your private key".to_string()),
34//!         }),
35//!     });
36//!
37//!     // authenticate the api keys (optional)
38//!     if !jsb.authenticate().is_ok() {
39//!         panic!("Authentication failed");
40//!      }
41//! }
42//! ```
43//!
44//! ## Usage
45//! Basic examples to get you started.
46//!
47//! Using these json files:
48//! - [Object Json File](https://jsonbank.io/f/jsonbank/sdk-test/index.json)
49//! - [Array Json File](https://jsonbank.io/gh/jsonbankio/documentation/github-test-array.json) (From github)
50//!
51//! ```
52//! use jsonbank::{JsonBank, JsonValue, JsonObject, JsonArray};
53//!
54//! fn main() {
55//!     /// initialize jsonbank
56//!     let jsb = JsonBank::new_without_config();
57//!
58//!     /// Get json object
59//!     let data: JsonObject = jsb.get_content("jsonbank/sdk-test/index.json").unwrap();
60//!     assert_eq!(data["author"], "jsonbank");
61//!
62//!     /// Get json array
63//!     let data: JsonArray = jsb.get_github_content("jsonbankio/documentation/github-test-array.json").unwrap();
64//!     assert_eq!(data[0], 1);
65//!     assert_eq!(data[1], "MultiType Array");
66//!     assert_eq!(data[2].as_object().unwrap()["name"], "github-test-array.json");
67//!
68//!     /// Get json value (when you don't know the exact type)
69//!     let data: JsonValue = jsb.get_content("jsonbank/sdk-test/index.json").unwrap();
70//!     if data.is_object() {
71//!        assert_eq!(data["author"], "jsonbank");
72//!     } else {
73//!        panic!("Expected json object");
74//!     }
75//! }
76//!```
77//! ## Extra Info
78//! The struct [JsonBank](struct.JsonBank.html) is well documented, so you can check the docs for more info.
79//!
80
81
82extern crate reqwest;
83extern crate serde;
84extern crate serde_json;
85
86
87mod functions;
88/// Package structs
89pub mod structs;
90/// Package error module
91pub mod error;
92
93
94use serde::{de::DeserializeOwned};
95use serde_json::{Value};
96use std::collections::HashMap;
97use std::fs;
98use std::path::{PathBuf};
99use reqwest::blocking::Response;
100use error::*;
101use functions::*;
102use structs::*;
103
104
105/// The keyword `jsonbank`
106pub const JSONBANK: &str = "jsonbank";
107/// The keyword `jsonbankio`
108pub const JSONBANK_IO: &str = "jsonbankio";
109/// The default host
110pub const DEFAULT_HOST: &str = "https://api.jsonbank.io";
111
112/// An alias for `serde_json::Value`
113// so adding serde_json as a dependency is not necessary
114pub type JsonValue = Value;
115
116/// An alias for `HashMap<String, JsonValue>`
117pub type JsonObject = HashMap<String, JsonValue>;
118
119/// An alias for `Vec<JsonValue>`
120pub type JsonArray = Vec<JsonValue>;
121
122/// Holds the public and private keys
123pub struct Keys {
124    pub public: Option<String>,
125    pub private: Option<String>,
126}
127
128/// The configuration struct
129pub struct Config {
130    pub host: String,
131    keys: Option<Keys>, // Keys
132}
133
134/// Minimal Config struct needed to initialize.
135pub struct InitConfig {
136    pub host: Option<String>,
137    pub keys: Option<Keys>,
138}
139
140// Endpoints struct - Endpoints
141struct Endpoints {
142    v1: String,
143    public: String,
144}
145
146/// JsonBank SDK Instance
147pub struct JsonBank {
148    /// Instance Config
149    pub config: Config,
150    // Endpoints
151    endpoints: Endpoints,
152    // Authenticated data
153    authenticated_data: Option<AuthenticatedData>,
154}
155
156
157// Implementing JsonBank
158impl JsonBank {
159    // Checks if a key is provided either public or private
160    fn has_key(&self, key: &str) -> bool {
161        // check if keys are provided
162        if self.config.keys.is_none() {
163            return false;
164        }
165
166        // get keys
167        let keys = self.config.keys.as_ref().unwrap();
168
169        // check if key is provided
170        if key == "public" {
171            return keys.public.is_some();
172        } else if key == "private" {
173            return keys.private.is_some();
174        }
175
176        false
177    }
178
179    // Returns a public or private key depending on the key parameter
180    fn get_key(&self, key: &str) -> String {
181        // check if keys are provided
182        if self.config.keys.is_none() {
183            return "".to_string();
184        }
185
186        // get keys
187        let keys = self.config.keys.as_ref().unwrap();
188
189        // check if key is provided
190        if key == "public" {
191            return keys.public.as_ref().unwrap().to_string();
192        } else if key == "private" {
193            return keys.private.as_ref().unwrap().to_string();
194        }
195
196        "".to_string()
197    }
198
199    // Make Endpoints
200    fn make_endpoints(host: &String) -> Endpoints {
201        Endpoints {
202            v1: format!("{}/v1", host),
203            public: host.to_string(),
204        }
205    }
206
207    /// Initialize JsonBank SDK Instance
208    /// # Arguments
209    /// * `conf` - The minimal config needed to initialize
210    /// # Example
211    /// ```
212    /// # use jsonbank::{JsonBank, InitConfig, Keys};
213    /// let jsb = JsonBank::new(InitConfig {
214    ///     host: None, // use default host
215    ///     keys: Some(Keys {
216    ///     public: Some("Your public key".to_string()),
217    ///     private: Some("Your private key".to_string()),
218    ///     }),
219    /// });
220    /// ```
221    pub fn new(conf: InitConfig) -> Self {
222        let host = conf.host.unwrap_or(DEFAULT_HOST.to_string());
223
224        // build config
225        let config = Config {
226            host: host.to_string(),
227            keys: conf.keys,
228        };
229
230        // set endpoints
231        let endpoints = Self::make_endpoints(&host);
232
233        // return JsonBank struct
234        JsonBank { config, endpoints, authenticated_data: None }
235    }
236
237    /// Initialize JsonBank SDK Instance without config
238    /// # Example
239    /// ```
240    /// # use jsonbank::JsonBank;
241    /// let jsb = JsonBank::new_without_config();
242    /// ```
243    pub fn new_without_config() -> Self {
244        Self::new(InitConfig {
245            host: None,
246            keys: None,
247        })
248    }
249}
250
251// Instance Implementation
252impl JsonBank {
253    // Format public url
254    fn public_url(&self, paths: Vec<&str>) -> String {
255        // add paths to public endpoint
256        format!("{}/{}", self.endpoints.public, paths.join("/"))
257    }
258
259    // format v1 url
260    fn v1_url(&self, paths: Vec<&str>) -> String {
261        // add paths to v1 endpoint
262        format!("{}/{}", self.endpoints.v1, paths.join("/"))
263    }
264
265    // process_response_error - Processes response error
266    fn process_response_error<T: DeserializeOwned>(&self, res: Response) -> Result<T, JsbError> {
267        let code = res.status().to_string();
268        let data: JsonObject = match res.json() {
269            Ok(text) => text,
270            Err(err) => {
271                return Err(JsbError::from_any(&err, None));
272            }
273        };
274
275        // get error object from data
276        let error = match data["error"].as_object() {
277            Some(err) => err,
278            None => {
279                return Err(JsbError {
280                    code,
281                    message: "Unknown error".to_string(),
282                });
283            }
284        };
285
286
287        Err(JsbError {
288            code: error["code"].as_str().unwrap().to_string(),
289            message: error["message"].as_str().unwrap().to_string(),
290        })
291    }
292
293    // process_response - Processes response
294    fn process_response<T: DeserializeOwned>(&self, res: Response) -> Result<T, JsbError> {
295        // Check if the response is successful
296        if res.status().is_success() {
297            let response_text: T = match res.json() {
298                Ok(text) => text,
299                Err(err) => {
300                    return Err(JsbError::from_any(&err, None));
301                }
302            };
303
304            Ok(response_text)
305        } else {
306            self.process_response_error(res)
307        }
308    }
309
310    // process_response_as_string - Processes response as text
311    fn process_response_as_string(&self, res: Response) -> Result<String, JsbError> {
312        // Check if the response is successful
313        if res.status().is_success() {
314            let response_text: String = match res.text() {
315                Ok(text) => text,
316                Err(err) => {
317                    return Err(JsbError::from_any(&err, None));
318                }
319            };
320
321            Ok(response_text)
322        } else {
323            self.process_response_error(res)
324        }
325    }
326
327    // make_request - Makes request
328    fn make_request(&self, method: &str, url: String, body: Option<JsonObject>, require_pub_key: bool, require_prv_key: bool) -> Result<Response, JsbError> {
329        // build request
330        let client = reqwest::blocking::Client::new();
331        // add json header
332        let mut headers = reqwest::header::HeaderMap::new();
333        headers.insert("Content-Type", "application/json".parse().unwrap());
334
335        // check if public key is required and not provided
336        if require_pub_key {
337            if self.has_key("public") {
338                // add public key to headers as `jsb-pub-key`
339                headers.insert("jsb-pub-key", self.get_key("public").parse().unwrap());
340            } else {
341                return Err(JsbError {
342                    code: "bad_request".to_string(),
343                    message: "Public key is not set".to_string(),
344                });
345            }
346        }
347
348        // check if private key is required and not provided
349        if require_prv_key {
350            if self.has_key("private") {
351                // add private key to headers as `jsb-private-key`
352                headers.insert("jsb-prv-key", self.get_key("private").parse().unwrap());
353            } else {
354                return Err(JsbError {
355                    code: "bad_request".to_string(),
356                    message: "Private key is not set".to_string(),
357                });
358            }
359        }
360
361        // send request
362        match {
363            match method {
364                "POST" => client.post(&url).json(&body.unwrap_or(HashMap::new())),
365                "DELETE" => client.delete(&url),
366                _ => client.get(&url).query(&body.unwrap_or(HashMap::new())),
367            }.headers(headers).send()
368        } {
369            Ok(res) => Ok(res),
370            Err(err) => Err(JsbError::from_any(&err, None))
371        }
372    }
373
374    // send_get_request - Sends get request
375    // This function sends the http request using reqwest
376    fn send_request<T: DeserializeOwned>(&self, method: &str, url: String, body: Option<JsonObject>, require_pub_key: bool, require_prv_key: bool) -> Result<T, JsbError> {
377        // make request
378        let res = match self.make_request(method, url, body, require_pub_key, require_prv_key) {
379            Ok(res) => res,
380            Err(err) => {
381                return Err(err);
382            }
383        };
384
385        // process response
386        self.process_response(res)
387    }
388
389    // send_request_as_string - Sends request and returns response as text
390    fn send_request_as_string(&self, method: &str, url: String, body: Option<JsonObject>, require_pub_key: bool, require_prv_key: bool) -> Result<String, JsbError> {
391        // make request
392        let res = match self.make_request(method, url, body, require_pub_key, require_prv_key) {
393            Ok(res) => res,
394            Err(err) => {
395                return Err(err);
396            }
397        };
398
399        // process response
400        self.process_response_as_string(res)
401    }
402
403    // public_request - Sends get request to public endpoint
404    fn public_request<T: DeserializeOwned>(&self, url: Vec<&str>) -> Result<T, JsbError> {
405        self.send_request("GET", self.public_url(url), None, false, false)
406    }
407
408    // public_request_as_string - Sends get request to public endpoint and returns response as text
409    fn public_request_as_string(&self, url: Vec<&str>) -> Result<String, JsbError> {
410        self.send_request_as_string("GET", self.public_url(url), None, false, false)
411    }
412
413    // read_request - Sends get request to auth required endpoints using public key
414    fn read_request<T: DeserializeOwned>(&self, url: Vec<&str>, query: Option<JsonObject>) -> Result<T, JsbError> {
415        self.send_request("GET", self.v1_url(url), query, true, false)
416    }
417
418    // read_request_as_string - Sends get request to auth required endpoints using public key and returns response as text
419    fn read_request_as_string(&self, url: Vec<&str>, query: Option<JsonObject>) -> Result<String, JsbError> {
420        self.send_request_as_string("GET", self.v1_url(url), query, true, false)
421    }
422
423    // read_post_request - Sends post request to auth required endpoints using public key
424    fn read_post_request<T: DeserializeOwned>(&self, url: Vec<&str>, body: Option<JsonObject>) -> Result<T, JsbError> {
425        self.send_request("POST", self.v1_url(url), body, true, false)
426    }
427
428    // write_request - Sends post request to auth required endpoints using private key
429    fn write_request<T: DeserializeOwned>(&self, url: Vec<&str>, body: Option<JsonObject>) -> Result<T, JsbError> {
430        self.send_request("POST", self.v1_url(url), body, false, true)
431    }
432
433    // delete_request - Sends delete request to auth required endpoints using private key
434    fn delete_request<T: DeserializeOwned>(&self, url: Vec<&str>) -> Result<T, JsbError> {
435        self.send_request("DELETE", self.v1_url(url), None, false, true)
436    }
437
438    /// Sets host, this is useful when you want to use your own jsonbank server (Not currently supported)
439    ///
440    /// # Example:
441    /// ```
442    /// # use jsonbank::JsonBank;
443    /// let mut jsb = JsonBank::new_without_config();
444    /// jsb.set_host("https://api.jsonbank.io");
445    /// ```
446    pub fn set_host(&mut self, host: &str) {
447        self.config.host = host.to_string();
448        // update endpoints
449        self.endpoints = Self::make_endpoints(&self.config.host);
450    }
451
452    /// Get public content meta from jsonbank
453    /// # Example:
454    /// Using this [json object file from jsonbank](https://api.jsonbank.io/f/jsonbank/sdk-test/index.json)
455    /// ```
456    /// # use jsonbank::JsonBank;
457    /// let jsb = JsonBank::new_without_config();
458    /// let meta = jsb.get_document_meta("jsonbank/sdk-test/index").unwrap();
459    /// // print document id
460    /// println!("{}", meta.id);
461    /// ```
462    pub fn get_document_meta(&self, id_or_path: &str) -> Result<DocumentMeta, JsbError> {
463        match self.public_request::<JsonObject>(vec!["meta/f", id_or_path]) {
464            Ok(res) => {
465                // convert to DocumentMeta
466                Ok(json_object_to_document_meta(&res))
467            }
468            Err(err) => Err(err),
469        }
470    }
471
472    /// Get public content from jsonbank
473    /// # Example:
474    /// ```no_run
475    /// use jsonbank::{JsonObject, JsonArray, JsonValue};
476    /// # use jsonbank::JsonBank;
477    /// # let jsb = JsonBank::new_without_config();
478    /// // get object content
479    /// let data: JsonObject =  jsb.get_content("id_or_path").unwrap();
480    /// println!("{:?}", data);
481    ///
482    /// // get array content
483    /// let data: JsonArray =  jsb.get_content("id_or_path").unwrap();
484    ///
485    /// // get any JsonValue content
486    /// let data: JsonValue =  jsb.get_content("id_or_path").unwrap();
487    /// ```
488    pub fn get_content<T: DeserializeOwned>(&self, id_or_path: &str) -> Result<T, JsbError> {
489        self.public_request::<T>(vec!["f", id_or_path])
490    }
491
492    /// Get public content as string from jsonbank
493    /// # Example:
494    /// ```no_run
495    /// # use jsonbank::JsonBank;
496    /// # let jsb = JsonBank::new_without_config();
497    /// let data: String =  jsb.get_content_as_string("id_or_path").unwrap();
498    /// println!("{}", data);
499    /// ```
500    pub fn get_content_as_string(&self, id_or_path: &str) -> Result<String, JsbError> {
501        self.public_request_as_string(vec!["f", id_or_path])
502    }
503
504    /// Grab a public json file from Github.
505    /// This will read from the `default` branch of the repo.
506    ///
507    /// # Example:
508    /// Using this [json object file from github](https://jsonbank.io/gh/jsonbankio/jsonbank-js/package.json)
509    /// ```
510    /// # use jsonbank::{JsonBank, JsonValue};
511    /// # let jsb = JsonBank::new_without_config();
512    /// let content: JsonValue = jsb.get_github_content("jsonbankio/jsonbank-js/package.json").unwrap();
513    /// assert_eq!(content["name"], "jsonbank");
514    /// assert_eq!(content["author"], "jsonbankio");
515    /// ```
516    pub fn get_github_content<T: DeserializeOwned>(&self, path: &str) -> Result<T, JsbError> {
517        self.public_request(vec!["gh", path])
518    }
519
520    /// Grab a public json file from Github as a string.
521    /// Same as `get_github_content` but returns a string instead of a deserialized object.
522    ///
523    /// # Example:
524    /// Using this [json object file from github](https://jsonbank.io/gh/jsonbankio/jsonbank-js/package.json)
525    /// ```
526    /// # use jsonbank::JsonBank;
527    /// # let jsb = JsonBank::new_without_config();
528    /// let content: String = jsb.get_github_content_as_string("jsonbankio/jsonbank-js/package.json").unwrap();
529    /// // string contains prepublishOnly
530    /// assert!(content.contains("prepublishOnly"));
531    /// ```
532    pub fn get_github_content_as_string(&self, path: &str) -> Result<String, JsbError> {
533        self.public_request_as_string(vec!["gh", path])
534    }
535}
536
537
538// Auth Implementation
539impl JsonBank {
540    /// Authenticate user using current api key
541    pub fn authenticate(&mut self) -> Result<AuthenticatedData, JsbError> {
542        match self.read_post_request::<JsonObject>(vec!["authenticate"], None) {
543            Ok(res) => {
544                // convert to AuthenticatedData
545                let data = AuthenticatedData {
546                    authenticated: res["authenticated"].as_bool().unwrap(),
547                    username: res["username"].as_str().unwrap().to_string(),
548                    api_key: AuthenticatedKey {
549                        title: res["apiKey"]["title"].as_str().unwrap().to_string(),
550                        projects: res["apiKey"]["projects"].as_array().unwrap().iter().map(|x| x.as_str().unwrap().to_string()).collect(),
551                    },
552                };
553
554                // set authenticated data
555                self.authenticated_data = Some(data.clone());
556
557                Ok(data)
558            }
559            Err(err) => Err(err),
560        }
561    }
562
563    /// Get username of authenticated user
564    ///
565    /// **Note:** [authenticate](#method.authenticate) must be called before calling this method.
566    /// Otherwise it will return an error.
567    pub fn get_username(&self) -> Result<String, JsbError> {
568        match &self.authenticated_data {
569            Some(data) => Ok(data.username.clone()),
570            None => Err(JsbError {
571                code: "not_authenticated".to_string(),
572                message: "User is not authenticated".to_string(),
573            }),
574        }
575    }
576
577    /// Check if user is authenticated
578    pub fn is_authenticated(&self) -> bool {
579        match &self.authenticated_data {
580            Some(data) => data.authenticated,
581            None => false,
582        }
583    }
584
585    /// Get content meta of a document owned by authenticated user
586    ///
587    /// **Note:** This does not return the content of the document.
588    pub fn get_own_document_meta(&self, id_or_path: &str) -> Result<DocumentMeta, JsbError> {
589        match self.read_request::<JsonObject>(vec!["meta/file", id_or_path], None) {
590            Ok(res) => {
591                // convert to DocumentMeta
592                Ok(json_object_to_document_meta(&res))
593            }
594            Err(err) => Err(err),
595        }
596    }
597
598
599    /// Get json content of a document owned by authenticated user
600    /// # Example:
601    /// ```no_run
602    /// use jsonbank::{JsonObject, JsonArray, JsonValue};
603    /// # use jsonbank::JsonBank;
604    /// # let jsb = JsonBank::new_without_config();
605    /// // get object content
606    /// let data: JsonObject =  jsb.get_own_content("id_or_path").unwrap();
607    /// println!("{:?}", data);
608    ///
609    /// // get array content
610    /// let data: JsonArray =  jsb.get_own_content("id_or_path").unwrap();
611    /// println!("{:?}", data);
612    ///
613    /// // get any JsonValue content
614    /// let data: JsonValue =  jsb.get_own_content("id_or_path").unwrap();
615    /// println!("{:?}", data);
616    /// ```
617    pub fn get_own_content<T: DeserializeOwned>(&self, id_or_path: &str) -> Result<T, JsbError> {
618        self.read_request(vec!["file", id_or_path], None)
619    }
620
621    /// Get content of a document owned by authenticated user as json string
622    /// /// # Example:
623    /// ```no_run
624    /// # use jsonbank::JsonBank;
625    /// # let jsb = JsonBank::new_without_config();
626    /// let data: String =  jsb.get_own_content_as_string("id_or_path").unwrap();
627    /// println!("{}", data);
628    /// ```
629    pub fn get_own_content_as_string(&self, id_or_path: &str) -> Result<String, JsbError> {
630        self.read_request_as_string(vec!["file", id_or_path], None)
631    }
632
633    /// Check if user has document.
634    /// This method will try to get document meta and if it throws the `notFound` error it will return false.
635    pub fn has_own_document(&self, id_or_path: &str) -> Result<bool, JsbError> {
636        match self.get_own_document_meta(id_or_path) {
637            Ok(_) => Ok(true),
638            Err(err) => {
639                if err.code == "notFound" {
640                    Ok(false)
641                } else {
642                    Err(JsbError::from_any(&err, None))
643                }
644            }
645        }
646    }
647
648    /// Create a document.
649    /// # Example:
650    /// ```no_run
651    /// # use jsonbank::JsonBank;
652    /// use jsonbank::structs::CreateDocumentBody;
653    /// # let jsb = JsonBank::new_without_config();
654    /// let new_doc = jsb.create_document(CreateDocumentBody {
655    ///     name: "test.json".to_string(),
656    ///     project: "test".to_string(),
657    ///     content: "[2, 4, 6]".to_string(),
658    ///     folder: None,
659    ///  }).unwrap();
660    ///
661    /// assert_eq!(new_doc.name, "test.json");
662    /// assert_eq!(new_doc.project, "test");
663    /// ```
664    pub fn create_document(&self, content: CreateDocumentBody) -> Result<NewDocument, JsbError> {
665
666        // check if content.project is set
667        if content.project.is_empty() {
668            return Err(JsbError {
669                code: "bad_request".to_string(),
670                message: "Project required".to_string(),
671            });
672        }
673
674        // check if content.name is set
675        if content.name.is_empty() {
676            return Err(JsbError {
677                code: "bad_request".to_string(),
678                message: "Name required".to_string(),
679            });
680        }
681
682        // check if content.content is set
683        if content.content.is_empty() {
684            return Err(JsbError {
685                code: "bad_request".to_string(),
686                message: "Content required".to_string(),
687            });
688        }
689
690        // check if content.content is a valid json
691        if !is_valid_json(&content.content) {
692            return Err(err_invalid_json());
693        }
694
695        // convert content to hashmap
696        let mut body: JsonObject = HashMap::from([
697            ("name".to_string(), JsonValue::String(content.name)),
698            ("project".to_string(), JsonValue::String(content.project.clone())),
699            ("content".to_string(), JsonValue::String(content.content)),
700        ]);
701
702        // add folder if set
703        if content.folder.is_some() {
704            body.insert("folder".to_string(), JsonValue::String(content.folder.unwrap()));
705        }
706
707
708        // send request
709        let url = vec!["project", &content.project, "document"];
710        match self.write_request::<JsonObject>(url, Some(body)) {
711            Ok(res) => {
712                // convert to NewDocument
713                Ok(NewDocument {
714                    id: res["id"].as_str().unwrap().to_string(),
715                    name: res["name"].as_str().unwrap().to_string(),
716                    path: res["path"].as_str().unwrap().to_string(),
717                    project: res["project"].as_str().unwrap().to_string(),
718                    created_at: res["createdAt"].as_str().unwrap().to_string(),
719                    exists: false,
720                })
721            }
722            Err(err) => Err(err),
723        }
724    }
725
726    /// Create a document if it does not exist
727    ///
728    /// First, it will try to create the document, if it fails and document error code is `name.exists` it will try to get the document
729    /// and return it
730    /// # Example:
731    /// ```no_run
732    /// # use jsonbank::JsonBank;
733    /// use jsonbank::structs::CreateDocumentBody;
734    /// # let jsb = JsonBank::new_without_config();
735    /// let new_doc = jsb.create_document_if_not_exists(CreateDocumentBody {
736    ///     name: "test.json".to_string(),
737    ///     project: "test".to_string(),
738    ///     content: "[2, 4, 6]".to_string(),
739    ///     folder: None,
740    ///  }).unwrap();
741    ///
742    /// assert_eq!(new_doc.name, "test.json");
743    /// assert_eq!(new_doc.project, "test");
744    ///
745    /// if new_doc.exists {
746    ///    println!("Document already exists");
747    /// } else {
748    ///    println!("Document created");
749    /// }
750    /// ```
751    pub fn create_document_if_not_exists(&self, content: CreateDocumentBody) -> Result<NewDocument, JsbError> {
752        match self.create_document(content.clone()) {
753            Ok(res) => Ok(res),
754            Err(err) => {
755                // check if error code is name.exists
756                if err.code == "name.exists" {
757                    let doc_path = make_document_path(&content);
758                    // get document
759                    match self.get_own_document_meta(doc_path.as_str()) {
760                        Ok(res) => Ok(NewDocument {
761                            id: res.id,
762                            name: content.name,
763                            path: res.path,
764                            project: res.project,
765                            created_at: res.created_at,
766                            exists: true,
767                        }),
768                        Err(err) => Err(err),
769                    }
770                } else {
771                    Err(err)
772                }
773            }
774        }
775    }
776
777
778    /// Update a document that belongs to the authenticated user.
779    /// # Example:
780    /// ```no_run
781    /// # use jsonbank::JsonBank;
782    /// # let jsb = JsonBank::new_without_config();
783    /// let res = jsb.update_own_document("id_or_path", "[new_json_content]".to_string()).unwrap();
784    /// // check if document was updated
785    /// assert_eq!(res.changed, true);
786    /// ```
787    pub fn update_own_document(&self, id_or_path: &str, content: String) -> Result<UpdatedDocument, JsbError> {
788        // check if content is a valid json
789        if !is_valid_json(&content) {
790            return Err(err_invalid_json());
791        }
792
793        // create body
794        let body = JsonObject::from([
795            ("content".to_string(), JsonValue::String(content)),
796        ]);
797
798        // send request
799        let url = vec!["file", id_or_path];
800
801        match self.write_request::<JsonObject>(url, Some(body)) {
802            Ok(res) => {
803                // convert to UpdatedDocument
804                Ok(UpdatedDocument {
805                    changed: res["changed"].as_bool().unwrap_or(false),
806                })
807            }
808            Err(err) => Err(err),
809        }
810    }
811
812
813    /// Upload a json document
814    /// This method will read the file contents and  send it to jsonbank using the [create_document](#createdocument)
815    /// # Example:
816    /// ```no_run
817    /// # use jsonbank::JsonBank;
818    /// use jsonbank::structs::UploadDocumentBody;
819    /// # let jsb = JsonBank::new_without_config();
820    /// let res = jsb.upload_document(UploadDocumentBody {
821    ///     file_path: "upload.json".to_string(),
822    ///     project: "test".to_string(),
823    ///     name: None,
824    ///     folder: None,
825    ///  }).unwrap();
826    ///
827    /// assert_eq!(res.project, "test");
828    /// // both paths should be the same
829    /// assert_eq!(res.name, "upload.json");
830    /// ```
831    pub fn upload_document(&self, doc: UploadDocumentBody) -> Result<NewDocument, JsbError> {
832        // project is required
833        if doc.project.is_empty() {
834            return Err(JsbError {
835                code: "bad_request".to_string(),
836                message: "Project required".to_string(),
837            });
838        }
839
840        let file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(doc.file_path);
841
842        // check if file exists using os
843        if !file_path.exists() {
844            return Err(JsbError {
845                code: "file_not_found".to_string(),
846                message: "File does not exist".to_string(),
847            });
848        }
849
850        // read file
851        let file_content = match fs::read_to_string(file_path.clone()) {
852            Ok(res) => res,
853            Err(err) => {
854                return Err(JsbError {
855                    code: "invalid_file".to_string(),
856                    message: err.to_string(),
857                });
858            }
859        };
860
861        // check if file is valid json
862        if !is_valid_json(&file_content) {
863            return Err(err_invalid_json());
864        }
865
866        // set name if not set
867        let name = if doc.name.is_none() {
868            file_path.file_name()
869                .unwrap()
870                .to_str()
871                .unwrap()
872                .to_string()
873        } else {
874            doc.name.unwrap()
875        };
876
877        self.create_document(CreateDocumentBody {
878            name,
879            project: doc.project,
880            content: file_content,
881            folder: doc.folder,
882        })
883    }
884
885    /// Delete a document
886    /// # Example:
887    /// ```no_run
888    /// # use jsonbank::JsonBank;
889    /// # let jsb = JsonBank::new_without_config();
890    /// let res = jsb.delete_document("id_or_path").unwrap();
891    /// // check if document was deleted
892    /// assert_eq!(res.deleted, true);
893    /// ```
894    pub fn delete_document(&self, id_or_path: &str) -> Result<DeletedDocument, JsbError> {
895        match self.delete_request::<JsonObject>(vec!["file", id_or_path]) {
896            Ok(res) => {
897                // convert to DeletedDocument
898                Ok(DeletedDocument {
899                    deleted: res["deleted"].as_bool().unwrap_or(false),
900                })
901            }
902            Err(err) => {
903                // if error code is `notFound` return DeletedDocument with deleted = false
904                if err.code == "notFound" {
905                    Ok(DeletedDocument { deleted: false })
906                } else {
907                    Err(err)
908                }
909            }
910        }
911    }
912
913    /// Create a folder
914    /// # Example:
915    /// ```no_run
916    /// # use jsonbank::JsonBank;
917    /// use jsonbank::structs::CreateFolderBody;
918    /// # let jsb = JsonBank::new_without_config();
919    ///
920    /// let res = jsb.create_folder(CreateFolderBody {
921    ///     name: "folder_name".to_string(),
922    ///     project: "project".to_string(),
923    ///     // Parent folder is optional
924    ///     folder: None,
925    /// }).unwrap();
926    ///
927    /// assert_eq!(res.name, "folder_name");
928    /// assert_eq!(res.project, "project");
929    /// ```
930    pub fn create_folder(&self, data: CreateFolderBody) -> Result<Folder, JsbError> {
931        // project is required
932        if data.project.is_empty() {
933            return Err(JsbError {
934                code: "bad_request".to_string(),
935                message: "Project required".to_string(),
936            });
937        }
938
939        // name is required
940        if data.name.is_empty() {
941            return Err(JsbError {
942                code: "bad_request".to_string(),
943                message: "Name required".to_string(),
944            });
945        }
946
947        // create body
948        let body = JsonObject::from([
949            ("name".to_string(), JsonValue::String(data.name)),
950            ("project".to_string(), JsonValue::String(data.project.clone())),
951        ]);
952
953        // send request
954        let url = vec!["project", &data.project, "folder"];
955        match self.write_request::<JsonObject>(url, Some(body)) {
956            Ok(res) => {
957                // convert to NewFolder
958                Ok(json_object_to_folder(&res))
959            }
960            Err(err) => Err(err),
961        }
962    }
963
964    //  private _get_folder - get a folder
965    fn ___get_folder(&self, id_or_path: &str, include_stats: bool) -> Result<Folder, JsbError> {
966        // create query
967        let query = if include_stats {
968            Some(JsonObject::from([
969                ("stats".to_string(), JsonValue::Bool(true)),
970            ]))
971        } else {
972            None
973        };
974
975        match self.read_request::<JsonObject>(vec!["folder", id_or_path], query) {
976            Ok(res) => {
977                // convert to Folder
978                Ok(json_object_to_folder(&res))
979            }
980            Err(err) => Err(err),
981        }
982    }
983
984    /// Get a folder
985    /// # Example:
986    /// ```no_run
987    /// # use jsonbank::JsonBank;
988    /// # let jsb = JsonBank::new_without_config();
989    /// let res = jsb.get_folder("id_or_path").unwrap();
990    /// println!("Folder name: {}", res.name);
991    /// ```
992    pub fn get_folder(&self, id_or_path: &str) -> Result<Folder, JsbError> {
993        self.___get_folder(id_or_path, false)
994    }
995
996    /// Get a folder with statistics count
997    /// # Example:
998    /// ```no_run
999    /// # use jsonbank::JsonBank;
1000    /// # let jsb = JsonBank::new_without_config();
1001    /// let res = jsb.get_folder_with_stats("id_or_path").unwrap();
1002    /// println!("Folder name: {}", res.name);
1003    /// let stats = res.stats.unwrap();
1004    /// println!("Folder documents count: {}", stats.documents);
1005    /// println!("Folder folders count: {}", stats.folders);
1006    /// ```
1007    pub fn get_folder_with_stats(&self, id_or_path: &str) -> Result<Folder, JsbError> {
1008        self.___get_folder(id_or_path, true)
1009    }
1010
1011    /// Create a folder if it does not exist
1012    ///
1013    /// First, it will try to create the folder, if it fails and folder error code is `name.exists` it will try to get the folder
1014    /// and return it.
1015    /// # Example:
1016    /// ```no_run
1017    /// # use jsonbank::JsonBank;
1018    /// use jsonbank::structs::CreateFolderBody;
1019    /// # let jsb = JsonBank::new_without_config();
1020    /// let (res, exists) = jsb.create_folder_if_not_exists(CreateFolderBody {
1021    ///    name: "folder_name".to_string(),
1022    ///     project: "project".to_string(),
1023    ///  // Parent folder is optional
1024    ///     folder: None,
1025    /// }).unwrap();
1026    ///
1027    /// // check if folder was created
1028    /// if exists {
1029    ///    println!("Folder was created");
1030    /// } else {
1031    ///   println!("Folder already exists");
1032    /// }
1033    ///
1034    /// assert_eq!(res.name, "folder_name");
1035    /// assert_eq!(res.project, "project");
1036    /// ```
1037    pub fn create_folder_if_not_exists(&self, data: CreateFolderBody) -> Result<(Folder, bool), JsbError> {
1038        match self.create_folder(data.clone()) {
1039            Ok(res) => Ok((res, false)),
1040            Err(err) => {
1041                // check if error code is name.exists
1042                if err.code == "name.exists" {
1043                    let folder_path = make_folder_path(&data);
1044                    // get folder
1045                    match self.get_folder(folder_path.as_str()) {
1046                        Ok(res) => Ok((res, true)),
1047                        Err(err) => Err(err),
1048                    }
1049                } else {
1050                    Err(err)
1051                }
1052            }
1053        }
1054    }
1055}