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}