1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
//! This crate aims to simplify interacting with the Google Cloud Storage JSON API. Use it until //! Google releases a Cloud Storage Client Library for Rust. Shoutout to //! [MyEmma](https://myemma.io/) for funding this free and open source project. //! //! Google Cloud Storage is a product by Google that allows for cheap file storage, with a //! relatively sophisticated API. The idea is that storage happens in `Bucket`s, which are //! filesystems with a globally unique name. You can make as many of these `Bucket`s as you like! //! //! This project talks to Google using a `Service Account`. A service account is an account that you //! must create in the [cloud storage console](https://console.cloud.google.com/). When the account //! is created, you can download the file `service-account-********.json`. Store this file somewhere //! on your machine, and place the path to this file in the environment parameter `SERVICE_ACCOUNT`. //! Environment parameters declared in the `.env` file are also registered. The service account can //! then be granted `Roles` in the cloud storage console. The roles required for this project to //! function are `Service Account Token Creator` and `Storage Object Admin`. //! //! # Quickstart //! Add the following line to your `Cargo.toml` //! ```toml //! [dependencies] //! cloud-storage = "0.3" //! ``` //! The two most important concepts are [Buckets](bucket/struct.Bucket.html), which represent //! file systems, and [Objects](object/struct.Object.html), which represent files. //! //! ## Examples: //! Creating a new Bucket in Google Cloud Storage: //! ```rust //! # use cloud_storage::{Bucket, NewBucket}; //! let bucket = Bucket::create(&NewBucket { //! name: "doctest-bucket".to_string(), //! ..Default::default() //! }).unwrap(); //! # bucket.delete(); //! ``` //! Connecting to an existing Bucket in Google Cloud Storage: //! ```no_run //! # use cloud_storage::Bucket; //! let bucket = Bucket::read("mybucket").unwrap(); //! ``` //! Read a file from disk and store it on googles server: //! ```rust,no_run //! # use cloud_storage::Object; //! # use std::fs::File; //! # use std::io::Read; //! let mut bytes: Vec<u8> = Vec::new(); //! for byte in File::open("myfile.txt").unwrap().bytes() { //! bytes.push(byte.unwrap()) //! } //! Object::create("mybucket", &bytes, "myfile.txt", "text/plain"); //! ``` //! Renaming/moving a file //! ```rust,no_run //! # use cloud_storage::Object; //! let mut object = Object::read("mybucket", "myfile").unwrap(); //! object.name = "mybetterfile".to_string(); //! object.update().unwrap(); //! ``` //! Removing a file //! ```rust,no_run //! # use cloud_storage::Object; //! let object = Object::read("mybucket", "myfile").unwrap(); //! object.delete(); //! ``` #![forbid(unsafe_code, missing_docs)] /// Contains objects as represented by Google, to be used for serialization and deserialization. mod error; mod resources; mod token; pub use crate::error::*; use crate::resources::service_account::ServiceAccount; pub use crate::resources::{ bucket::{Bucket, NewBucket}, object::Object, *, }; use crate::token::Token; use std::sync::Mutex; lazy_static::lazy_static! { /// Static `Token` struct that caches static ref TOKEN_CACHE: Mutex<Token> = Mutex::new(Token::new( "https://www.googleapis.com/auth/devstorage.full_control", )); static ref IAM_TOKEN_CACHE: Mutex<Token> = Mutex::new(Token::new( "https://www.googleapis.com/auth/iam" )); /// The struct is the parsed service account json file. It is publicly exported to enable easier /// debugging of which service account is currently used. It is of the type /// [ServiceAccount](service_account/struct.ServiceAccount.html). pub static ref SERVICE_ACCOUNT: ServiceAccount = ServiceAccount::get(); } const BASE_URL: &'static str = "https://www.googleapis.com/storage/v1"; fn get_headers() -> Result<reqwest::header::HeaderMap, Error> { let mut result = reqwest::header::HeaderMap::new(); let mut guard = TOKEN_CACHE.lock().unwrap(); let token = guard.get()?; result.insert( reqwest::header::AUTHORIZATION, format!("Bearer {}", token).parse().unwrap(), ); Ok(result) } fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error> where T: std::str::FromStr, T::Err: std::fmt::Display, D: serde::Deserializer<'de>, { use serde::de::Deserialize; let s = String::deserialize(deserializer)?; T::from_str(&s).map_err(serde::de::Error::custom) } fn from_str_opt<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error> where T: std::str::FromStr, T::Err: std::fmt::Display, D: serde::Deserializer<'de>, { let s: Result<serde_json::Value, _> = serde::Deserialize::deserialize(deserializer); println!("{:?}", s); match s { Ok(serde_json::Value::String(s)) => T::from_str(&s) .map_err(serde::de::Error::custom) .map(Option::from), Ok(serde_json::Value::Number(num)) => T::from_str(&num.to_string()) .map_err(serde::de::Error::custom) .map(Option::from), Ok(_value) => Err(serde::de::Error::custom("Incorrect type")), Err(_) => Ok(None), } } #[cfg(test)] fn read_test_bucket() -> Bucket { dotenv::dotenv().ok(); let name = std::env::var("TEST_BUCKET").unwrap(); match Bucket::read(&name) { Ok(bucket) => bucket, Err(_not_found) => Bucket::create(&NewBucket { name, ..Default::default() }) .unwrap(), } } // since all tests run in parallel, we need to make sure we do not create multiple buckets with // the same name in each test. #[cfg(test)] fn create_test_bucket(name: &str) -> Bucket { dotenv::dotenv().ok(); let base_name = std::env::var("TEST_BUCKET").unwrap(); let name = format!("{}-{}", base_name, name); let new_bucket = NewBucket { name, ..Default::default() }; match dbg!(Bucket::create(&new_bucket)) { Ok(bucket) => bucket, Err(_alread_exists) => dbg!(Bucket::read(&new_bucket.name)).unwrap(), } }