From 97a38180df9ef66e30dc7bbc5eb44cecceacef24 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sun, 11 Jun 2017 18:14:54 +0200 Subject: [PATCH 01/54] Implemented create document --- examples/collection.rs | 5 ++-- src/azure/cosmos/client.rs | 53 ++++++++++++++++++++++++++++++++++-- src/azure/cosmos/document.rs | 45 ++++++++++++++++++++++++++++++ src/azure/cosmos/mod.rs | 1 + 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/azure/cosmos/document.rs diff --git a/examples/collection.rs b/examples/collection.rs index 250f14701..ff834e8fe 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -43,11 +43,10 @@ fn code() -> Result<(), Box> { // Each Cosmos' database contains zero or more collections. We can enumerate them using the // list_collection method. - for db in databases.iter() { - + for db in &databases { let collections = client.list_collections(db)?; println!("*** {} *** ({} collections)", db as &str, collections.len()); - for coll in collections.iter() { + for coll in &collections { // Collection does not implement Display but Deref to &str so this print works as // expected. println!("\t{}", coll as &str); diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index a12eff855..4e263a5d2 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -3,6 +3,7 @@ use azure::core::HTTPMethod; use azure::cosmos::database::Database; use azure::cosmos::collection::Collection; +use azure::cosmos::document::{IndexingDirective, DocumentAttributes}; use azure::core::errors::{AzureError, check_status_extract_body, check_status}; @@ -11,6 +12,8 @@ use azure::cosmos::request_response::{ListDatabasesResponse, CreateDatabaseReque use url; +use serde::Serialize; + use std::io::{Read, Cursor}; use crypto::hmac::Hmac; @@ -37,6 +40,8 @@ header! { (XMSVersion, "x-ms-version") => [String] } header! { (XMSDate, "x-ms-date") => [String] } header! { (Authorization, "Authorization") => [String] } header! { (OfferThroughput, "x-ms-offer-throughput") => [u64] } +header! { (DocumentIsUpsert, "x-ms-documentdb-is-upsert") => [bool] } +header! { (DocumentIndexingDirective, "x-ms-indexing-directive ") => [IndexingDirective] } define_encode_set! { pub COMPLETE_ENCODE_SET = [url::percent_encoding::USERINFO_ENCODE_SET] | { @@ -258,7 +263,7 @@ impl<'a> Client<'a> { pub fn create_collection(&self, database_name: &str, required_throughput: u64, - collection: &Collection) + collection: &str) -> Result { trace!("create_collection called"); @@ -317,7 +322,7 @@ impl<'a> Client<'a> { pub fn replace_collection(&self, database_name: &str, - collection: &Collection) + collection: &str) -> Result { trace!("replace_collection called"); @@ -344,6 +349,50 @@ impl<'a> Client<'a> { Ok(coll) } + + pub fn create_document(&self, + database: &str, + collection: &str, + is_upsert: bool, + indexing_directive: Option, + document: &T) + -> Result + where T: Serialize + { + trace!("create_document called(database == {}, collection == {}, is_upsert == {}", + database, + collection, + is_upsert); + + let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", + self.authorization_token.account(), + database, + collection))?; + + // Standard headers (auth and version) will be provied by perform_request + // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document + let mut headers = Headers::new(); + headers.set(DocumentIsUpsert(is_upsert)); + if let Some(id) = indexing_directive { + headers.set(DocumentIndexingDirective(id)); + } + + let document_serialized = serde_json::to_string(document)?; + trace!("document_serialized == {}", document_serialized); + + let mut curs = Cursor::new(&document_serialized); + + let mut resp = self.perform_request(&url, + HTTPMethod::Post, + Some((&mut curs, document_serialized.len() as u64)), + ResourceType::Documents, + Some(headers))?; + + let body = check_status_extract_body(&mut resp, StatusCode::Created)?; + let document_attributes: DocumentAttributes = serde_json::from_str(&body)?; + + Ok(document_attributes) + } } diff --git a/src/azure/cosmos/document.rs b/src/azure/cosmos/document.rs new file mode 100644 index 000000000..3bca68e46 --- /dev/null +++ b/src/azure/cosmos/document.rs @@ -0,0 +1,45 @@ +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] +pub enum IndexingDirective { + Include, + Exclude, +} + +impl Display for IndexingDirective { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + IndexingDirective::Include => write!(f, "Include"), + IndexingDirective::Exclude => write!(f, "Exclude"), + } + } +} + +impl FromStr for IndexingDirective { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "Include" => Ok(IndexingDirective::Include), + "Exclude" => Ok(IndexingDirective::Exclude), + _ => Err(format!("{} is not valid IndexingDirective value", s)), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct DocumentAttributes { + id: String, + #[serde(rename = "_rid")] + pub rid: String, + #[serde(rename = "_ts")] + pub ts: u64, + #[serde(rename = "_self")] + pub _self: String, + #[serde(rename = "_etag")] + pub etag: String, + #[serde(rename = "_attachments")] + pub attachments: String, +} diff --git a/src/azure/cosmos/mod.rs b/src/azure/cosmos/mod.rs index 07df4e9e1..6ca953769 100644 --- a/src/azure/cosmos/mod.rs +++ b/src/azure/cosmos/mod.rs @@ -5,3 +5,4 @@ pub mod database; mod request_response; pub mod collection; +pub mod document; From 8c707099ac264c8d2f05680e2684df74f60bdd16 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sun, 11 Jun 2017 18:38:01 +0200 Subject: [PATCH 02/54] Started a document example --- examples/document0.rs | 59 ++++++++++++++++++++++++++++++++++++++ src/azure/cosmos/client.rs | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 examples/document0.rs diff --git a/examples/document0.rs b/examples/document0.rs new file mode 100644 index 000000000..065681630 --- /dev/null +++ b/examples/document0.rs @@ -0,0 +1,59 @@ +extern crate azure_sdk_for_rust; + +use std::error::Error; + +use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; +use azure_sdk_for_rust::azure::cosmos::client::Client; + +use azure_sdk_for_rust::azure::cosmos; + +const DATABASE: &'static str = "azuresdktestdb"; +const COLLECTION: &'static str = "azuresdktc"; + + +fn main() { + code().unwrap(); +} + +fn code() -> Result<(), Box> { + let master_key = std::env::var("COSMOS_MASTER_KEY") + .expect("Set env variable COSMOS_MASTER_KEY first!"); + let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); + let authorization_token = AuthorizationToken::new(&account, TokenType::Master, master_key)?; + let client = Client::new(&authorization_token)?; + + client.create_database(DATABASE)?; + println!("database created"); + + let indexes = cosmos::collection::IncludedPathIndex { + kind: cosmos::collection::KeyKind::Hash, + data_type: cosmos::collection::DataType::String, + precision: Some(3), + }; + + let ip = cosmos::collection::IncludedPath { + path: "/*".to_owned(), + indexes: vec![indexes], + }; + + + let ip = cosmos::collection::IndexingPolicy { + automatic: true, + indexing_mode: cosmos::collection::IndexingMode::Consistent, + included_paths: vec![ip], + excluded_paths: vec![], + }; + + let coll = cosmos::collection::Collection::new(COLLECTION, ip); + + client.create_collection(DATABASE, 400, &coll)?; + println!("collection created"); + + + client.delete_collection(DATABASE, COLLECTION)?; + println!("collection deleted"); + client.delete_database(DATABASE)?; + println!("database deleted"); + + Ok(()) +} diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 4e263a5d2..cf8fac423 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -263,7 +263,7 @@ impl<'a> Client<'a> { pub fn create_collection(&self, database_name: &str, required_throughput: u64, - collection: &str) + collection: &Collection) -> Result { trace!("create_collection called"); From 7e39bd36bd8963371bb8a6eb2d5634289a6a478e Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 12 Jun 2017 22:26:34 +0200 Subject: [PATCH 03/54] Completed example of create_document --- examples/document0.rs | 85 ++++++++++++++++++++++++++++---------- src/azure/cosmos/client.rs | 2 +- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/examples/document0.rs b/examples/document0.rs index 065681630..44ede04f0 100644 --- a/examples/document0.rs +++ b/examples/document0.rs @@ -1,5 +1,10 @@ extern crate azure_sdk_for_rust; +extern crate chrono; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; use std::error::Error; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; @@ -7,6 +12,19 @@ use azure_sdk_for_rust::azure::cosmos::client::Client; use azure_sdk_for_rust::azure::cosmos; +//use chrono::{DateTime, UTC}; + +//use serde::Serialize; + +#[derive(Serialize, Deserialize, Debug)] +struct MySampleStruct<'a> { + id: &'a str, + a_string: &'a str, + a_number: u64, + a_timestamp: i64, +} + + const DATABASE: &'static str = "azuresdktestdb"; const COLLECTION: &'static str = "azuresdktc"; @@ -22,33 +40,56 @@ fn code() -> Result<(), Box> { let authorization_token = AuthorizationToken::new(&account, TokenType::Master, master_key)?; let client = Client::new(&authorization_token)?; - client.create_database(DATABASE)?; - println!("database created"); - - let indexes = cosmos::collection::IncludedPathIndex { - kind: cosmos::collection::KeyKind::Hash, - data_type: cosmos::collection::DataType::String, - precision: Some(3), + let database = { + let dbs = client.list_databases()?; + if let Some(db) = dbs.into_iter().find(|db| db.id == DATABASE) { + db + } else { + client.create_database(DATABASE)? + } }; - - let ip = cosmos::collection::IncludedPath { - path: "/*".to_owned(), - indexes: vec![indexes], + println!("database == {:?}", database); + + let coll = { + let colls = client.list_collections(&database)?; + if let Some(coll) = colls.into_iter().find(|coll| coll.id == COLLECTION) { + coll + } else { + + let indexes = cosmos::collection::IncludedPathIndex { + kind: cosmos::collection::KeyKind::Hash, + data_type: cosmos::collection::DataType::String, + precision: Some(3), + }; + + let ip = cosmos::collection::IncludedPath { + path: "/*".to_owned(), + indexes: vec![indexes], + }; + + + let ip = cosmos::collection::IndexingPolicy { + automatic: true, + indexing_mode: cosmos::collection::IndexingMode::Consistent, + included_paths: vec![ip], + excluded_paths: vec![], + }; + + let coll = cosmos::collection::Collection::new(COLLECTION, ip); + client.create_collection(DATABASE, 400, &coll)? + } }; + println!("collection == {:?}", coll); - - let ip = cosmos::collection::IndexingPolicy { - automatic: true, - indexing_mode: cosmos::collection::IndexingMode::Consistent, - included_paths: vec![ip], - excluded_paths: vec![], + let doc = MySampleStruct { + id: "unique_id1", + a_string: "Something here", + a_number: 100, + a_timestamp: chrono::UTC::now().timestamp(), }; - let coll = cosmos::collection::Collection::new(COLLECTION, ip); - - client.create_collection(DATABASE, 400, &coll)?; - println!("collection created"); - + let document_attributes = client.create_document(&database, &coll, false, None, &doc)?; + println!("document_attributes == {:?}", document_attributes); client.delete_collection(DATABASE, COLLECTION)?; println!("collection deleted"); diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index cf8fac423..989931760 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -364,7 +364,7 @@ impl<'a> Client<'a> { collection, is_upsert); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", + let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}/docs", self.authorization_token.account(), database, collection))?; From 785a8a60bc96e5ca7a3473bae6df1c9ddb526660 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 15 Jun 2017 16:04:37 +0200 Subject: [PATCH 04/54] Migrating (in progress) to Tokio --- Cargo.lock | 332 +++++++-- Cargo.toml | 34 +- src/azure/core/errors.rs | 7 +- src/azure/cosmos/client.rs | 145 ++-- src/azure/mod.rs | 4 +- src/azure/service_bus/event_hub/client.rs | 68 -- src/azure/service_bus/event_hub/mod.rs | 116 --- src/azure/service_bus/mod.rs | 1 - src/azure/storage/blob/lease_blob_options.rs | 20 - src/azure/storage/blob/list_blob_options.rs | 24 - src/azure/storage/blob/mod.rs | 670 ------------------ src/azure/storage/blob/put_block_options.rs | 14 - src/azure/storage/blob/put_options.rs | 12 - src/azure/storage/blob/put_page_options.rs | 12 - src/azure/storage/client.rs | 99 --- .../container/list_container_options.rs | 16 - src/azure/storage/container/mod.rs | 175 ----- src/azure/storage/mod.rs | 5 - src/azure/storage/rest_client.rs | 465 ------------ src/azure/storage/table/batch.rs | 132 ---- src/azure/storage/table/mod.rs | 228 ------ src/lib.rs | 6 +- 22 files changed, 364 insertions(+), 2221 deletions(-) delete mode 100644 src/azure/service_bus/event_hub/client.rs delete mode 100644 src/azure/service_bus/event_hub/mod.rs delete mode 100644 src/azure/service_bus/mod.rs delete mode 100644 src/azure/storage/blob/lease_blob_options.rs delete mode 100644 src/azure/storage/blob/list_blob_options.rs delete mode 100644 src/azure/storage/blob/mod.rs delete mode 100644 src/azure/storage/blob/put_block_options.rs delete mode 100644 src/azure/storage/blob/put_options.rs delete mode 100644 src/azure/storage/blob/put_page_options.rs delete mode 100644 src/azure/storage/client.rs delete mode 100644 src/azure/storage/container/list_container_options.rs delete mode 100644 src/azure/storage/container/mod.rs delete mode 100644 src/azure/storage/mod.rs delete mode 100644 src/azure/storage/rest_client.rs delete mode 100644 src/azure/storage/table/batch.rs delete mode 100644 src/azure/storage/table/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3b18965a7..fcfe7f7d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,11 +3,12 @@ name = "azure_sdk_for_rust" version = "0.3.1" dependencies = [ "RustyXML 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", @@ -15,7 +16,9 @@ dependencies = [ "serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -41,17 +44,13 @@ dependencies = [ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "antidote" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "base64" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -64,6 +63,20 @@ name = "byteorder" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bytes" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "chrono" version = "0.3.1" @@ -119,19 +132,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "gcc" -version = "0.3.50" +name = "futures" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "gdi32-sys" -version = "0.2.0" +name = "futures-cpupool" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gcc" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "httparse" version = "1.2.3" @@ -139,30 +157,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" -version = "0.10.12" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "hyper-native-tls" -version = "0.2.4" +name = "hyper-tls" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -175,6 +201,15 @@ dependencies = [ "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "iovec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.3.1" @@ -199,6 +234,11 @@ name = "lazy_static" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazycell" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.23" @@ -224,10 +264,37 @@ dependencies = [ [[package]] name = "mime" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -235,13 +302,25 @@ name = "native-tls" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "openssl 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "net2" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num" version = "0.1.39" @@ -284,28 +363,31 @@ dependencies = [ [[package]] name = "openssl" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl-sys" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", - "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "percent-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "pkg-config" version = "0.3.9" @@ -356,7 +438,7 @@ name = "rust-crypto" version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -368,6 +450,19 @@ name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "safemem" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "schannel" version = "0.1.5" @@ -382,6 +477,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scoped-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "secur32-sys" version = "0.2.0" @@ -411,6 +511,11 @@ dependencies = [ "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.8" @@ -446,6 +551,16 @@ dependencies = [ "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "slab" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.11.11" @@ -464,6 +579,11 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "take" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempdir" version = "0.3.5" @@ -502,21 +622,77 @@ dependencies = [ ] [[package]] -name = "traitobject" +name = "tokio" +version = "0.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tokio-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-proto" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-service" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "typeable" +name = "tokio-tls" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "unicase" -version = "1.4.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "version_check 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -547,20 +723,12 @@ dependencies = [ [[package]] name = "url" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "user32-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -573,11 +741,6 @@ name = "uuid" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "version_check" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "void" version = "1.0.2" @@ -593,14 +756,24 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum RustyXML 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9222d58bccd9e6e3b82098a2ec142ad34e5d433de986d46cec03ad3a2b5fd529" "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" -"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" -"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557" +"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" +"checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14" +"checksum cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c47d456a36ebf0536a6705c83c1cbbcb9255fbc1d905a6ded104f479268a29" "checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056" "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" @@ -608,29 +781,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" "checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" -"checksum gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)" = "5f837c392f2ea61cb1576eac188653df828c861b7137d74ea4a5caa89621f9e6" -"checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" +"checksum futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4b63a4792d4f8f686defe3b39b92127fea6344de5d38202b2ee5a11bbbf29d6a" +"checksum futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a283c84501e92cade5ea673a2a7ca44f71f209ccdd302a3e0896f50083d2c5ff" +"checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" -"checksum hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0f01e4a20f5dfa5278d7762b7bdb7cab96e24378b9eca3889fbd4b5e94dc7063" -"checksum hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72332e4a35d3059583623b50e98e491b78f8b96c5521fcb3f428167955aa56e8" +"checksum hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8590f308416a428dca05ca67020283105344e94059fd2f02cc72e9c913c30fb" +"checksum hyper-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6829541bb9b1716568b3cf353bbcf0566578874b0ffa147922eadc67c7aa3a6" "checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37" +"checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" +"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e" "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" -"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +"checksum mime 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c5ca99d8a021c1687882fd68dca26e601ceff5c26571c7cb41cf4ed60d57cb2d" +"checksum mio 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9e965267d4d58496fc4f740e9861118367f13570cadf66316ed2c3f2f14d87c7" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e94a2fc65a44729fe969cc973da87c1052ae3f000b2cb33029f14aeb85550d5" +"checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "checksum num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "2c3a3dc9f30bf824141521b30c908a859ab190b76e20435fcd89f35eb6583887" "checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" "checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" "checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6" "checksum num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e416ba127a4bb3ff398cb19546a8d0414f73352efe2857f4060d36f5fe5983a" -"checksum openssl 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b34cd77cf91301fff3123fbd46b065c3b728b17a392835de34c397315dce5586" -"checksum openssl-sys 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e035022a50faa380bd7ccdbd184d946ce539ebdb0a358780de92a995882af97a" +"checksum openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11ba043cb65fc9af71a431b8a36ffe8686cd4751cdf70a473ec1d01066ac7e41" +"checksum openssl-sys 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "236c718c2e2c2b58a546d86ffea5194400bb15dbe01ca85325ffd357b03cf66c" +"checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c36987d4978eb1be2e422b1e0423a557923a5c3e7e6f31d5699e9aafaefa469" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" @@ -640,32 +820,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum schannel 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4e45ac5e9e4698c1c138d2972bedcd90b81fe1efeba805449d2bdd54512de5f9" +"checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" "checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" "checksum security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "42ddf098d78d0b64564b23ee6345d07573e7d10e52ad86875d89ddf5f8378a02" "checksum security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "5bacdada57ea62022500c457c8571c17dfb5e6240b7c8eac5916ffa8c7138a55" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" "checksum serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f530d36fb84ec48fb7146936881f026cdbf4892028835fd9398475f82c1bb4" "checksum serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "10552fad5500771f3902d0c5ba187c5881942b811b7ba0d8fbbfbf84d80806d3" "checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a" "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" +"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" +"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" -"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" -"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +"checksum tokio 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad008a2129866117d3b0424c047f1302f3d8974ff58c6ade59ec14cd6c59fee2" +"checksum tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6a20ba4738d283cac7495ca36e045c80c2a8df3e05dd0909b17a06646af5a7ed" +"checksum tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c2c3ce9739f7387a0fa65b5421e81feae92e04d603f008898f4257790ce8c2db" +"checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" +"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +"checksum tokio-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "666266622d9a4d1974a0beda33d505999515b0c60edc0c3fda09784e56609a97" +"checksum unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e01da42520092d0cd2d6ac3ae69eb21a22ad43ff195676b86f8c37f487d6b80" "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a" "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" -"checksum url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2ba3456fbe5c0098cb877cf08b92b76c3e18e0be9e47c35b487220d377d24e" -"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" +"checksum url 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a69a2e36a5e5ed3f3063c8c64a3b028c4d50d689fa6c862abd7cfe65f882595c" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d0f5103675a280a926ec2f9b7bcc2ef49367df54e8c570c3311fec919f9a8b" -"checksum version_check 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2bb3950bf29e36796dea723df1747619dd331881aefef75b7cf1c58fdd738afe" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/Cargo.toml b/Cargo.toml index 349f23c26..bc0ab536d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,21 +16,25 @@ name = "main" doc = false [dependencies] -chrono = "0.3.1" -rust-crypto = "0.2.36" -quick-error = "1.2.0" -RustyXML = "0.1.1" -log = "0.3.8" -env_logger = "0.4.3" -uuid = "0.5.0" -time = "0.1.37" -url = "1.4.1" -base64 = "0.5.2" -serde = "1.0.8" -serde_json = "1.0.2" -serde_derive = "1.0.8" -hyper = "0.10.12" -hyper-native-tls = "0.2.4" +chrono = "*" +rust-crypto = "*" +quick-error = "*" +RustyXML = "*" +log = "*" +env_logger = "*" +uuid = "*" +time = "*" +url = "*" +base64 = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" +hyper = "*" + +tokio = "*" +tokio-core = "*" +futures = "*" +hyper-tls = "*" [features] test_e2e = [] diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 8f433a947..49efb7ac8 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -1,5 +1,5 @@ use hyper; -use hyper::status::StatusCode; +use hyper::StatusCode; use chrono; use std::io::Error as IOError; use std::io::Read; @@ -84,6 +84,11 @@ quick_error! { display("URL parse error: {}", err) cause(err) } + URIParseError(err: hyper::error::UriError) { + from() + display("URI parse error: {}", err) + cause(err) + } ChronoParserError(err: chrono::ParseError) { from() display("Chrono parser error: {}", err) diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 989931760..c376264ee 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -9,8 +9,7 @@ use azure::core::errors::{AzureError, check_status_extract_body, check_status}; use azure::cosmos::request_response::{ListDatabasesResponse, CreateDatabaseRequest, ListCollectionsResponse}; - -use url; +use std::str::FromStr; use serde::Serialize; @@ -24,13 +23,15 @@ use base64; use hyper; use serde_json; use hyper::header::{ContentLength, Headers}; -use hyper::status::StatusCode; -use hyper_native_tls; +use hyper::StatusCode; use chrono; +use url::percent_encoding; use url::percent_encoding::utf8_percent_encode; +use tokio_core; +use hyper_tls; const AZURE_VERSION: &'static str = "2017-02-22"; const VERSION: &'static str = "1.0"; @@ -44,7 +45,7 @@ header! { (DocumentIsUpsert, "x-ms-documentdb-is-upsert") => [bool] } header! { (DocumentIndexingDirective, "x-ms-indexing-directive ") => [IndexingDirective] } define_encode_set! { - pub COMPLETE_ENCODE_SET = [url::percent_encoding::USERINFO_ENCODE_SET] | { + pub COMPLETE_ENCODE_SET = [percent_encoding::USERINFO_ENCODE_SET] | { '+', '-', '&' } } @@ -58,19 +59,22 @@ pub enum ResourceType { } pub struct Client<'a> { - hyper_client: hyper::client::Client, + //hyper_client: hyper::Client, authorization_token: &'a AuthorizationToken<'a>, } impl<'a> Client<'a> { - pub fn new(authorization_token: &'a AuthorizationToken<'a>) - -> Result, hyper_native_tls::native_tls::Error> { - let ssl = hyper_native_tls::NativeTlsClient::new()?; - let connector = hyper::net::HttpsConnector::new(ssl); - let client = hyper::Client::with_connector(connector); - + pub fn new( + handle: &tokio_core::reactor::Handle, + authorization_token: &'a AuthorizationToken<'a>) + -> Result, ()>{ //hyper_tls::HttpsConnector::Error> { + + let client = hyper::Client::configure() + .connector(hyper_tls::HttpsConnector::new(4, handle)) + .build(handle)?; + Ok(Client { - hyper_client: client, + // hyper_client: client, authorization_token: authorization_token, }) } @@ -80,8 +84,8 @@ impl<'a> Client<'a> { } fn perform_request(&self, - url: &url::Url, - http_method: HTTPMethod, + uri: hyper::Uri, + http_method: hyper::Method, request_body: Option<(&mut Read, u64)>, resource_type: ResourceType, headers: Option) @@ -89,9 +93,7 @@ impl<'a> Client<'a> { let dt = chrono::UTC::now(); let time = format!("{}", dt.format(TIME_FORMAT)); - - // to do: calculate resource link - let resource_link = generate_resource_link(url); + let resource_link = generate_resource_link(&uri); let auth = generate_authorization(self.authorization_token, http_method, @@ -100,51 +102,46 @@ impl<'a> Client<'a> { &time); trace!("perform_request::auth == {:?}", auth); + let mut request = hyper::Request::new(http_method, uri); + // we need to add custom headers. If the caller has passed its collection of - // headers we will add to his. Otherwise we create one from scratch. - let mut headers = if let Some(h) = headers { - h - } else { - Headers::new() - }; + // headers we will add to his ones. Otherwise we create one from scratch. + if let Some(hs) = headers { + for h in hs.iter() { + request.headers_mut().set(h); + } + } if let Some((_, size)) = request_body { - headers.set(ContentLength(size)); + request.headers_mut().set(ContentLength(size)); } - headers.set(XMSDate(time)); - headers.set(XMSVersion(AZURE_VERSION.to_owned())); - headers.set(Authorization(auth)); - - trace!("perform_request::headers == {:?}", headers); + request.headers_mut().set(XMSDate(time)); + request.headers_mut().set(XMSVersion(AZURE_VERSION.to_owned())); + request.headers_mut().set(Authorization(auth)); - let mut builder = match http_method { - HTTPMethod::Get => self.hyper_client.get(&url.to_string()), - HTTPMethod::Put => self.hyper_client.put(&url.to_string()), - HTTPMethod::Post => self.hyper_client.post(&url.to_string()), - HTTPMethod::Delete => self.hyper_client.delete(&url.to_string()), - }; + trace!("perform_request::headers == {:?}", request.headers()); if let Some((mut rb, size)) = request_body { - let b = hyper::client::Body::SizedBody(rb, size); - builder = builder.body(b); + //let b = hyper::client::Body::SizedBody(rb, size); + request.set_body(request_body); } - let res = builder.headers(headers).send()?; + let future = self.hyper_client.request(request); - Ok(res) + Ok(future) } pub fn list_databases(&self) -> Result, AzureError> { trace!("list_databases called"); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs", self.authorization_token.account()))?; // No specific headers are required, list databases only needs standard headers // which will be provied by perform_request let mut resp = - self.perform_request(&url, HTTPMethod::Get, None, ResourceType::Databases, None)?; + self.perform_request(uri, hyper::Method::Get, None, ResourceType::Databases, None)?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let db: ListDatabasesResponse = serde_json::from_str(&body)?; @@ -156,7 +153,7 @@ impl<'a> Client<'a> { trace!("create_databases called (database_name == {})", database_name); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs", self.authorization_token.account()))?; // No specific headers are required, create databases only needs standard headers @@ -167,8 +164,8 @@ impl<'a> Client<'a> { let req = serde_json::to_string(&req)?; let mut curs = Cursor::new(&req); - let mut resp = self.perform_request(&url, - HTTPMethod::Post, + let mut resp = self.perform_request(uri, + hyper::Method::Post, Some((&mut curs, req.len() as u64)), ResourceType::Databases, None)?; @@ -182,14 +179,14 @@ impl<'a> Client<'a> { pub fn get_database(&self, database_name: &str) -> Result { trace!("get_database called (database_name == {})", database_name); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}", self.authorization_token.account(), database_name))?; // No specific headers are required, get database only needs standard headers // which will be provied by perform_request let mut resp = - self.perform_request(&url, HTTPMethod::Get, None, ResourceType::Databases, None)?; + self.perform_request(uri, hyper::Method::Get, None, ResourceType::Databases, None)?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let db: Database = serde_json::from_str(&body)?; @@ -201,14 +198,14 @@ impl<'a> Client<'a> { trace!("delete_database called (database_name == {})", database_name); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}", self.authorization_token.account(), database_name))?; // No specific headers are required, delete database only needs standard headers // which will be provied by perform_request - let mut resp = self.perform_request(&url, - HTTPMethod::Delete, + let mut resp = self.perform_request(uri, + hyper::Method::Delete, None, ResourceType::Databases, None)?; @@ -226,7 +223,7 @@ impl<'a> Client<'a> { database_name, collection_name); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", self.authorization_token.account(), database_name, collection_name))?; @@ -234,7 +231,7 @@ impl<'a> Client<'a> { // No specific headers are required, get database only needs standard headers // which will be provied by perform_request let mut resp = - self.perform_request(&url, HTTPMethod::Get, None, ResourceType::Collections, None)?; + self.perform_request(uri, hyper::Method::Get, None, ResourceType::Collections, None)?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let coll: Collection = serde_json::from_str(&body)?; @@ -245,14 +242,14 @@ impl<'a> Client<'a> { pub fn list_collections(&self, database_name: &str) -> Result, AzureError> { trace!("list_collections called"); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls", self.authorization_token.account(), database_name))?; // No specific headers are required, list collections only needs standard headers // which will be provied by perform_request let mut resp = - self.perform_request(&url, HTTPMethod::Get, None, ResourceType::Collections, None)?; + self.perform_request(uri, hyper::Method::Get, None, ResourceType::Collections, None)?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let colls: ListCollectionsResponse = serde_json::from_str(&body)?; @@ -267,7 +264,7 @@ impl<'a> Client<'a> { -> Result { trace!("create_collection called"); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls", self.authorization_token.account(), database_name))?; @@ -282,8 +279,8 @@ impl<'a> Client<'a> { let mut curs = Cursor::new(&collection_serialized); - let mut resp = self.perform_request(&url, - HTTPMethod::Post, + let mut resp = self.perform_request(uri, + hyper::Method::Post, Some((&mut curs, collection_serialized.len() as u64)), ResourceType::Collections, Some(headers))?; @@ -302,15 +299,15 @@ impl<'a> Client<'a> { database_name, collection_name); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", self.authorization_token.account(), database_name, collection_name))?; // No specific headers are required. // Standard headers (auth and version) will be provied by perform_request - let mut resp = self.perform_request(&url, - HTTPMethod::Delete, + let mut resp = self.perform_request(uri, + hyper::Method::Delete, None, ResourceType::Collections, None)?; @@ -326,7 +323,7 @@ impl<'a> Client<'a> { -> Result { trace!("replace_collection called"); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls", self.authorization_token.account(), database_name))?; @@ -338,8 +335,8 @@ impl<'a> Client<'a> { let mut curs = Cursor::new(&collection_serialized); - let mut resp = self.perform_request(&url, - HTTPMethod::Put, + let mut resp = self.perform_request(uri, + hyper::Method::Put, Some((&mut curs, collection_serialized.len() as u64)), ResourceType::Collections, None)?; @@ -364,7 +361,7 @@ impl<'a> Client<'a> { collection, is_upsert); - let url = url::Url::parse(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}/docs", + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}/docs", self.authorization_token.account(), database, collection))?; @@ -382,8 +379,8 @@ impl<'a> Client<'a> { let mut curs = Cursor::new(&document_serialized); - let mut resp = self.perform_request(&url, - HTTPMethod::Post, + let mut resp = self.perform_request(uri, + hyper::Method::Post, Some((&mut curs, document_serialized.len() as u64)), ResourceType::Documents, Some(headers))?; @@ -397,7 +394,7 @@ impl<'a> Client<'a> { fn generate_authorization(authorization_token: &AuthorizationToken, - http_method: HTTPMethod, + http_method: hyper::Method, resource_type: ResourceType, resource_link: &str, time: &str) @@ -429,7 +426,7 @@ fn encode_str_to_sign(str_to_sign: &str, authorization_token: &AuthorizationToke -fn string_to_sign(http_method: HTTPMethod, +fn string_to_sign(http_method: hyper::Method, rt: ResourceType, resource_link: &str, time: &str) @@ -441,10 +438,10 @@ fn string_to_sign(http_method: HTTPMethod, format!("{}\n{}\n{}\n{}\n\n", match http_method { - HTTPMethod::Get => "get", - HTTPMethod::Put => "put", - HTTPMethod::Post => "post", - HTTPMethod::Delete => "delete", + hyper::Method::Get => "get", + hyper::Method::Put => "put", + hyper::Method::Post => "post", + hyper::Method::Delete => "delete", }, match rt { ResourceType::Databases => "dbs", @@ -457,7 +454,7 @@ fn string_to_sign(http_method: HTTPMethod, } -fn generate_resource_link(u: &url::Url) -> &str { +fn generate_resource_link<'a>(u: &'a hyper::Uri) -> &'a str { static ENDING_STRINGS: &'static [&str] = &["/dbs", "/colls", "/docs"]; // store the element only if it does not end with dbs, colls or docs @@ -485,7 +482,7 @@ fn generate_resource_link(u: &url::Url) -> &str { mod tests { use azure::cosmos::client::*; use azure::cosmos::authorization_token; - use url::Url; + use uri::Url; #[test] fn string_to_sign_00() { diff --git a/src/azure/mod.rs b/src/azure/mod.rs index 8bb4fb19d..e7d374594 100644 --- a/src/azure/mod.rs +++ b/src/azure/mod.rs @@ -1,5 +1,5 @@ #[macro_use] pub mod core; -pub mod storage; -pub mod service_bus; +//pub mod storage; +//pub mod service_bus; pub mod cosmos; diff --git a/src/azure/service_bus/event_hub/client.rs b/src/azure/service_bus/event_hub/client.rs deleted file mode 100644 index 4048f3c5e..000000000 --- a/src/azure/service_bus/event_hub/client.rs +++ /dev/null @@ -1,68 +0,0 @@ -use azure::service_bus::event_hub::send_event; -use azure::core::errors::AzureError; - -use time::Duration; -use std::io::Read; - -use crypto::sha2::Sha256; -use crypto::hmac::Hmac; - -pub struct Client { - namespace: String, - event_hub: String, - policy_name: String, - hmac: Hmac, -} - -impl Client { - pub fn new(namespace: &str, event_hub: &str, policy_name: &str, key: &str) -> Client { - let mut v_hmac_key: Vec = Vec::new(); - v_hmac_key.extend(key.as_bytes()); - let hmac = Hmac::new(Sha256::new(), &v_hmac_key); - - Client { - namespace: namespace.to_owned(), - event_hub: event_hub.to_owned(), - policy_name: policy_name.to_owned(), - hmac: hmac, - } - } - - pub fn send_event(&mut self, - event_body: &mut (&mut Read, u64), - duration: Duration) - -> Result<(), AzureError> { - send_event(&self.namespace, - &self.event_hub, - &self.policy_name, - &mut self.hmac, - event_body, - duration) - } -} - -#[cfg(test)] -mod test { - #[allow(unused_imports)] - use super::Client; - - #[test] - pub fn client_ctor() { - Client::new("namespace", "event_hub", "policy", "key"); - } - - #[test] - pub fn client_enc() { - use crypto::mac::Mac; - use base64; - - let str_to_sign = "This must be secret!"; - - let mut c = Client::new("namespace", "event_hub", "policy", "key"); - - c.hmac.input(str_to_sign.as_bytes()); - let sig = base64::encode(c.hmac.result().code()); - - assert_eq!(sig, "2UNXaoPpeJBAhh6qxmTqXyNzTpOflGO6IhxegeUQBcU="); - } -} diff --git a/src/azure/service_bus/event_hub/mod.rs b/src/azure/service_bus/event_hub/mod.rs deleted file mode 100644 index 419ecda07..000000000 --- a/src/azure/service_bus/event_hub/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -use azure::core::errors::{AzureError, UnexpectedHTTPResult}; - -use hyper; -use hyper::net::HttpsConnector; -use hyper_native_tls::NativeTlsClient; -use hyper::header::{Headers, ContentLength}; -use hyper::status::StatusCode; - -use chrono; -use time::Duration; - -use std::ops::Add; -use base64; - -use url::percent_encoding::utf8_percent_encode; -use url::form_urlencoded::Serializer; - -use hyper::header::parsing::HTTP_VALUE; - -use crypto::sha2::Sha256; -use crypto::hmac::Hmac; -use crypto::mac::Mac; - -use url::Url; -use std::io::Read; - -mod client; -pub use self::client::Client; - -header! { (Authorization, "Authorization") => [String] } - -fn send_event(namespace: &str, - event_hub: &str, - policy_name: &str, - hmac: &mut Hmac, - event_body: &mut (&mut Read, u64), - duration: Duration) - -> Result<(), AzureError> { - - // prepare the url to call - let url = format!("https://{}.servicebus.windows.net/{}/messages", - namespace, - event_hub); - let url = try!(Url::parse(&url)); - debug!("url == {:?}", url); - - // create content - - // generate sas signature based on key name, key value, url and duration. - let sas = generate_signature(policy_name, hmac, &url.to_string(), duration); - debug!("sas == {}", sas); - - // add required headers (in this case just the Authorization and Content-Length). - let ssl = NativeTlsClient::new().unwrap(); - let connector = HttpsConnector::new(ssl); - let client = hyper::client::Client::with_connector(connector); - let mut headers = Headers::new(); - headers.set(Authorization(sas)); - headers.set(ContentLength(event_body.1)); - debug!("headers == {:?}", headers); - - let body = hyper::client::Body::SizedBody(event_body.0, event_body.1); - - // Post the request along with the headers and the body. - let mut response = try!(client.post(url).body(body).headers(headers).send()); - info!("response.status == {}", response.status); - debug!("response.headers == {:?}", response.headers); - - if response.status != StatusCode::Created { - debug!("response status unexpected, returning Err"); - let mut resp_s = String::new(); - try!(response.read_to_string(&mut resp_s)); - return Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new( - StatusCode::Created, - response.status, - &resp_s))); - } - - debug!("response status ok, returning Ok"); - Ok(()) -} - -fn generate_signature(policy_name: &str, - hmac: &mut Hmac, - url: &str, - ttl: Duration) - -> String { - let expiry = chrono::UTC::now().add(ttl).timestamp(); - debug!("expiry == {:?}", expiry); - - let url_encoded = utf8_percent_encode(url, HTTP_VALUE); - //debug!("url_encoded == {:?}", url_encoded); - - let str_to_sign = format!("{}\n{}", url_encoded, expiry); - debug!("str_to_sign == {:?}", str_to_sign); - - hmac.reset(); - hmac.input(str_to_sign.as_bytes()); - let sig = { - let sig = base64::encode(hmac.result().code()); - debug!("sig == {}", sig); - let mut ser = Serializer::new(String::new()); - ser.append_pair("sig", &sig); - let sig = ser.finish(); - debug!("sig == {}", sig); - sig - }; - - debug!("sig == {:?}", sig); - - format!("SharedAccessSignature sr={}&{}&se={}&skn={}", - &url_encoded, - sig, - expiry, - policy_name) -} diff --git a/src/azure/service_bus/mod.rs b/src/azure/service_bus/mod.rs deleted file mode 100644 index 351deea58..000000000 --- a/src/azure/service_bus/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod event_hub; diff --git a/src/azure/storage/blob/lease_blob_options.rs b/src/azure/storage/blob/lease_blob_options.rs deleted file mode 100644 index f2f21f4e5..000000000 --- a/src/azure/storage/blob/lease_blob_options.rs +++ /dev/null @@ -1,20 +0,0 @@ -use azure::core::lease::LeaseId; - -#[derive(Debug, Clone, PartialEq)] -pub struct LeaseBlobOptions { - pub lease_id: Option, - pub timeout: Option, - pub lease_break_period: Option, - pub lease_duration: Option, - pub proposed_lease_id: Option, - pub request_id: Option, -} - -pub const LEASE_BLOB_OPTIONS_DEFAULT: LeaseBlobOptions = LeaseBlobOptions { - lease_id: None, - timeout: None, - lease_break_period: None, - lease_duration: None, - proposed_lease_id: None, - request_id: None, -}; diff --git a/src/azure/storage/blob/list_blob_options.rs b/src/azure/storage/blob/list_blob_options.rs deleted file mode 100644 index c3bfd0310..000000000 --- a/src/azure/storage/blob/list_blob_options.rs +++ /dev/null @@ -1,24 +0,0 @@ -#[derive(Debug, Clone, PartialEq)] -pub struct ListBlobOptions { - pub max_results: u32, - pub include_snapshots: bool, - pub include_metadata: bool, - pub include_uncommittedblobs: bool, - pub include_copy: bool, - pub next_marker: Option, - pub prefix: Option, - pub timeout: Option, -} - -pub const LIST_BLOB_OPTIONS_DEFAULT: ListBlobOptions = ListBlobOptions { - max_results: 5000, - include_snapshots: false, - include_metadata: false, - include_uncommittedblobs: false, - include_copy: false, - next_marker: None, - prefix: None, - timeout: None, -}; - -impl ListBlobOptions {} diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs deleted file mode 100644 index fa254058f..000000000 --- a/src/azure/storage/blob/mod.rs +++ /dev/null @@ -1,670 +0,0 @@ -extern crate uuid; - -mod put_options; -pub use self::put_options::{PutOptions, PUT_OPTIONS_DEFAULT}; - -mod list_blob_options; -pub use self::list_blob_options::{ListBlobOptions, LIST_BLOB_OPTIONS_DEFAULT}; - -mod put_block_options; -pub use self::put_block_options::{PutBlockOptions, PUT_BLOCK_OPTIONS_DEFAULT}; - -mod put_page_options; -pub use self::put_page_options::{PutPageOptions, PUT_PAGE_OPTIONS_DEFAULT}; - -mod lease_blob_options; -pub use self::lease_blob_options::{LeaseBlobOptions, LEASE_BLOB_OPTIONS_DEFAULT}; - -use chrono::datetime::DateTime; -use chrono::UTC; - -use azure::core::lease::{LeaseId, LeaseStatus, LeaseState, LeaseDuration, LeaseAction}; -use azure::storage::client::Client; - -use azure::core; -use azure::storage::rest_client::{XMSRange, ContentMD5, XMSLeaseStatus, XMSLeaseDuration, - XMSLeaseState, XMSLeaseId, XMSRangeGetContentMD5, - XMSClientRequestId, XMSLeaseAction, XMSLeaseDurationSeconds, - XMSLeaseBreakPeriod, XMSProposedLeaseId, ETag}; - -use azure::core::parsing::{cast_must, cast_optional, from_azure_time, traverse}; - -use xml::Element; - -use std::str::FromStr; -use azure::core::enumerations; -use std::fmt; - -use std::io::Read; - -use azure::core::errors::{TraversingError, AzureError, check_status}; -use azure::core::parsing::FromStringOptional; - -use azure::core::range::Range; -use azure::core::ba512_range::BA512Range; -use azure::core::incompletevector::IncompleteVector; - -//use mime::Mime; - -use hyper::mime::Mime; - -use hyper::status::StatusCode; -use hyper::header::{Headers, ContentType, ContentLength, LastModified, ContentEncoding, - ContentLanguage}; - -use base64; - -use uuid::Uuid; - -create_enum!(BlobType, - (BlockBlob, "BlockBlob"), - (PageBlob, "PageBlob"), - (AppendBlob, "AppendBlob")); - -create_enum!(CopyStatus, - (Pending, "pending"), - (Success, "success"), - (Aborted, "aborted"), - (Failed, "failed")); - -create_enum!(PageWriteType, (Update, "update"), (Clear, "clear")); - -header! { (XMSBlobContentLength, "x-ms-blob-content-length") => [u64] } -header! { (XMSBlobSequenceNumber, "x-ms-blob-sequence-number") => [u64] } -header! { (XMSBlobType, "x-ms-blob-type") => [BlobType] } -header! { (XMSBlobContentDisposition, "x-ms-blob-content-disposition") => [String] } -header! { (XMSPageWrite, "x-ms-page-write") => [PageWriteType] } - -#[derive(Debug)] -pub struct Blob { - pub name: String, - pub container_name: String, - pub snapshot_time: Option>, - pub last_modified: DateTime, - pub etag: String, - pub content_length: u64, - pub content_type: Mime, - pub content_encoding: Option, - pub content_language: Option, - pub content_md5: Option, - pub cache_control: Option, - pub x_ms_blob_sequence_number: Option, - pub blob_type: BlobType, - pub lease_status: LeaseStatus, - pub lease_state: LeaseState, - pub lease_duration: Option, - pub copy_id: Option, - pub copy_status: Option, - pub copy_source: Option, - pub copy_progress: Option, - pub copy_completion: Option>, - pub copy_status_description: Option, -} - -impl Blob { - pub fn parse(elem: &Element, container_name: &str) -> Result { - let name = try!(cast_must::(elem, &["Name"])); - let snapshot_time = try!(cast_optional::>(elem, &["Snapshot"])); - let last_modified = try!(cast_must::>(elem, - &["Properties", "Last-Modified"])); - let etag = try!(cast_must::(elem, &["Properties", "Etag"])); - - let content_length = try!(cast_must::(elem, &["Properties", "Content-Length"])); - - let content_type = try!(cast_must::(elem, &["Properties", "Content-Type"])); - let content_encoding = try!(cast_optional::(elem, - &["Properties", "Content-Encoding"])); - let content_language = try!(cast_optional::(elem, - &["Properties", "Content-Language"])); - let content_md5 = try!(cast_optional::(elem, &["Properties", "Content-MD5"])); - let cache_control = try!(cast_optional::(elem, &["Properties", "Cache-Control"])); - let x_ms_blob_sequence_number = - try!(cast_optional::(elem, &["Properties", "x-ms-blob-sequence-number"])); - - let blob_type = try!(cast_must::(elem, &["Properties", "BlobType"])); - - let lease_status = try!(cast_must::(elem, &["Properties", "LeaseStatus"])); - let lease_state = try!(cast_must::(elem, &["Properties", "LeaseState"])); - let lease_duration = try!(cast_optional::(elem, - &["Properties", "LeaseDuration"])); - let copy_id = try!(cast_optional::(elem, &["Properties", "CopyId"])); - let copy_status = try!(cast_optional::(elem, &["Properties", "CopyStatus"])); - let copy_source = try!(cast_optional::(elem, &["Properties", "CopySource"])); - let copy_progress = try!(cast_optional::(elem, &["Properties", "CopyProgress"])); - let copy_completion = - try!(cast_optional::>(elem, &["Properties", "CopyCompletionTime"])); - let copy_status_description = - try!(cast_optional::(elem, &["Properties", "CopyStatusDescription"])); - - let mut cp_bytes: Option = None; - if let Some(txt) = copy_progress { - cp_bytes = Some(try!(txt.parse::())); - } - - let ctype = try!(content_type.parse::()); - - Ok(Blob { - name: name, - container_name: container_name.to_owned(), - snapshot_time: snapshot_time, - last_modified: last_modified, - etag: etag, - content_length: content_length, - content_type: ctype, - content_encoding: content_encoding, - content_language: content_language, - content_md5: content_md5, - cache_control: cache_control, - x_ms_blob_sequence_number: x_ms_blob_sequence_number, - blob_type: blob_type, - lease_status: lease_status, - lease_state: lease_state, - lease_duration: lease_duration, - copy_id: copy_id, - copy_status: copy_status, - copy_source: copy_source, - copy_progress: cp_bytes, - copy_completion: copy_completion, - copy_status_description: copy_status_description, - }) - } - - pub fn from_headers(blob_name: &str, - container_name: &str, - h: &Headers) - -> Result { - let content_type = match h.get::() { - Some(ct) => (ct as &Mime).clone(), - None => try!("application/octet-stream".parse::()), - }; - trace!("content_type == {:?}", content_type); - - let content_length = match h.get::() { - Some(cl) => *(cl as &u64), - None => return Err(AzureError::HeaderNotFound("Content-Length".to_owned())), - }; - trace!("content_length == {:?}", content_length); - - let last_modified = match h.get::() { - Some(lm) => { - try!(from_azure_time(&lm.to_string())) - //{let te: TraversingError= e.into(); te})) - } - None => return Err(AzureError::HeaderNotFound("Last-Modified".to_owned())), - }; - trace!("last_modified == {:?}", last_modified); - - let etag = match h.get::() { - Some(lm) => lm.to_string(), - None => return Err(AzureError::HeaderNotFound("ETag".to_owned())), - }; - trace!("etag == {:?}", etag); - - let x_ms_blob_sequence_number = match h.get::() { - Some(lm) => Some(*(lm as &u64)), - None => None, - }; - trace!("x_ms_blob_sequence_number == {:?}", - x_ms_blob_sequence_number); - - let blob_type = match h.get::() { - Some(lm) => try!((&lm.to_string()).parse::()), - None => return Err(AzureError::HeaderNotFound("x-ms-blob-type".to_owned())), - }; - trace!("blob_type == {:?}", blob_type); - - let content_encoding = match h.get::() { - Some(ce) => Some(ce.to_string()), - None => None, - }; - trace!("content_encoding == {:?}", content_encoding); - - let content_language = match h.get::() { - Some(cl) => Some(cl.to_string()), - None => None, - }; - trace!("content_language == {:?}", content_language); - - let content_md5 = match h.get::() { - Some(md5) => Some(md5.to_string()), - None => None, - }; - trace!("content_md5 == {:?}", content_md5); - - // TODO - // let cache_control = match h.get::() { - // Some(cc) => Some(cc.to_string()), - // None => None - // }; - // println!("cache_control == {:?}", cache_control); - - let lease_status = match h.get::() { - Some(ls) => try!(ls.to_string().parse::()), - None => return Err(AzureError::HeaderNotFound("x-ms-lease-status".to_owned())), - }; - trace!("lease_status == {:?}", lease_status); - - - let lease_state = match h.get::() { - Some(ls) => try!(ls.to_string().parse::()), - None => return Err(AzureError::HeaderNotFound("x-ms-lease-state".to_owned())), - }; - trace!("lease_state == {:?}", lease_state); - - - let lease_duration = match h.get::() { - Some(ls) => Some(try!(ls.to_string().parse::())), - None => None, - }; - trace!("lease_duration == {:?}", lease_duration); - - // TODO: get the remaining headers - // (https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx) - - Ok(Blob { - name: blob_name.to_owned(), - container_name: container_name.to_owned(), - snapshot_time: None, - last_modified: last_modified, - etag: etag, - content_length: content_length, - content_type: content_type, - content_encoding: content_encoding, - content_language: content_language, - content_md5: content_md5, - cache_control: None, // TODO - x_ms_blob_sequence_number: x_ms_blob_sequence_number, - blob_type: blob_type, - lease_status: lease_status, - lease_state: lease_state, - lease_duration: lease_duration, - copy_id: None, // TODO - copy_status: None, // TODO - copy_source: None, // TODO - copy_progress: None, // TODO - copy_completion: None, // TODO - copy_status_description: None, // TODO - }) - } - - pub fn list(c: &Client, - container_name: &str, - lbo: &ListBlobOptions) - -> Result, core::errors::AzureError> { - - let mut include = String::new(); - if lbo.include_snapshots { - include = include + "snapshots"; - } - if lbo.include_metadata { - if include.is_empty() { - include = include + ","; - } - include = include + "metadata"; - } - if lbo.include_uncommittedblobs { - if include.is_empty() { - include = include + ","; - } - include = include + "uncommittedblobs"; - } - if lbo.include_copy { - if include.is_empty() { - include = include + ","; - } - include = include + "copy"; - } - - let mut uri = format!("{}://{}.blob.core.windows.\ - net/{}?restype=container&comp=list&maxresults={}", - c.auth_scheme(), - c.account(), - container_name, - lbo.max_results); - - if !include.is_empty() { - uri = format!("{}&include={}", uri, include); - } - - if let Some(ref nm) = lbo.next_marker { - uri = format!("{}&marker={}", uri, nm); - } - - if let Some(ref pref) = lbo.prefix { - uri = format!("{}&prefix={}", uri, pref); - } - - if let Some(ref timeout) = lbo.timeout { - uri = format!("{}&timeout={}", uri, timeout); - } - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Get, &Headers::new(), None)); - - try!(check_status(&mut resp, StatusCode::Ok)); - - let mut resp_s = String::new(); - try!(resp.read_to_string(&mut resp_s)); - - trace!("resp_s == {:?}\n\n", resp_s); - - let sp = &resp_s; - let elem: Element = try!(sp.parse()); - - let next_marker = match try!(cast_optional::(&elem, &["NextMarker"])) { - Some(ref nm) if nm == "" => None, - Some(nm) => Some(nm), - None => None, - }; - - let mut v = Vec::new(); - for node_blob in try!(traverse(&elem, &["Blobs", "Blob"], true)) { - // println!("{:?}", blob); - v.push(try!(Blob::parse(node_blob, container_name))); - } - - Ok(IncompleteVector::::new(next_marker, v)) - } - - pub fn get(c: &Client, - container_name: &str, - blob_name: &str, - snapshot: Option<&DateTime>, - range: Option<&Range>, - lease_id: Option<&LeaseId>) - -> Result<(Blob, Box), core::errors::AzureError> { - let mut uri = format!("{}://{}.blob.core.windows.net/{}/{}", - c.auth_scheme(), - c.account(), - container_name, - blob_name); - - if let Some(snapshot) = snapshot { - uri = format!("{}?snapshot={}", uri, snapshot.to_rfc2822()); - } - - let uri = uri; - - trace!("uri == {:?}", uri); - - let mut headers = Headers::new(); - - if let Some(r) = range { - headers.set(XMSRange(*r)); - - // if range is < 4MB request md5 - if r.end - r.start <= 1024 * 1024 * 4 { - headers.set(XMSRangeGetContentMD5(true)); - } - } - - if let Some(l) = lease_id { - headers.set(XMSLeaseId(*l)); - } - - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Get, &headers, None)); - - // if we have requested a range the response code should be 207 (partial content) - // otherwise 200 (ok). - if range.is_some() { - try!(check_status(&mut resp, StatusCode::PartialContent)); - } else { - try!(check_status(&mut resp, StatusCode::Ok)); - } - - let blob = try!(Blob::from_headers(blob_name, container_name, &resp.headers)); - let r: Box = Box::new(resp); - - Ok((blob, r)) - } - - pub fn put(&self, - c: &Client, - po: &PutOptions, - r: Option<(&mut Read, u64)>) - -> Result<(), AzureError> { - - // parameter sanity check - match self.blob_type { - BlobType::BlockBlob => { - if r.is_none() { - return Err(AzureError::InputParametersError("cannot use put_blob with \ - BlockBlob without a Read" - .to_owned())); - } - } - BlobType::PageBlob => { - if r.is_some() { - return Err(AzureError::InputParametersError("cannot use put_blob with \ - PageBlob with a Read" - .to_owned())); - } - - if self.content_length % 512 != 0 { - return Err(AzureError::InputParametersError("PageBlob size must be aligned \ - to 512 bytes boundary" - .to_owned())); - } - } - BlobType::AppendBlob => { - if r.is_some() { - return Err(AzureError::InputParametersError("cannot use put_blob with \ - AppendBlob with a Read" - .to_owned())); - } - } - } - - let mut uri = format!("{}://{}.blob.core.windows.net/{}/{}", - c.auth_scheme(), - c.account(), - self.container_name, - self.name); - - if let Some(ref timeout) = po.timeout { - uri = format!("{}&timeout={}", uri, timeout); - } - - let mut headers = Headers::new(); - - headers.set(ContentType(self.content_type.clone())); - - if let Some(ref content_encoding) = self.content_encoding { - use hyper::header::Encoding; - let enc = try!(content_encoding.parse::()); - headers.set(ContentEncoding(vec![enc])); - }; - - // TODO Content-Language - - if let Some(ref content_md5) = self.content_md5 { - headers.set(ContentMD5(content_md5.to_owned())); - }; - - headers.set(XMSBlobType(self.blob_type)); - - if let Some(ref lease_id) = po.lease_id { - headers.set(XMSLeaseId(*lease_id)); - } - - // TODO x-ms-blob-content-disposition - - if self.blob_type == BlobType::PageBlob { - headers.set(XMSBlobContentLength(self.content_length)); - } - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Put, &headers, r)); - - try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - Ok(()) - } - - pub fn lease(&self, - c: &Client, - la: LeaseAction, - lbo: &LeaseBlobOptions) - -> Result { - let mut uri = format!("{}://{}.blob.core.windows.net/{}/{}?comp=lease", - c.auth_scheme(), - c.account(), - self.container_name, - self.name); - if let Some(ref timeout) = lbo.timeout { - uri = format!("{}&timeout={}", uri, timeout); - } - - let mut headers = Headers::new(); - - if let Some(ref lease_id) = lbo.lease_id { - headers.set(XMSLeaseId(lease_id.to_owned())); - } - - headers.set(XMSLeaseAction(la)); - - if let Some(lease_break_period) = lbo.lease_break_period { - headers.set(XMSLeaseBreakPeriod(lease_break_period)); - } - if let Some(lease_duration) = lbo.lease_duration { - headers.set(XMSLeaseDurationSeconds(lease_duration)); - } - if let Some(ref proposed_lease_id) = lbo.proposed_lease_id { - headers.set(XMSProposedLeaseId(*proposed_lease_id)); - } - if let Some(ref request_id) = lbo.request_id { - headers.set(XMSClientRequestId(request_id.to_owned())); - } - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Put, &headers, None)); - - let expected_result = match la { - LeaseAction::Acquire => StatusCode::Created, - LeaseAction::Renew | LeaseAction::Change | LeaseAction::Release => StatusCode::Ok, - LeaseAction::Break => StatusCode::Accepted, - }; - - try!(core::errors::check_status(&mut resp, expected_result)); - - let lid = match resp.headers.get::() { - Some(l) => l as &Uuid, - None => return Err(AzureError::HeaderNotFound("x-ms-lease-id".to_owned())), - }; - - Ok(*lid) - } - - pub fn put_page(&self, - c: &Client, - range: &BA512Range, - ppo: &PutPageOptions, - content: (&mut Read, u64)) - -> Result<(), AzureError> { - - let mut uri = format!("{}://{}.blob.core.windows.net/{}/{}?comp=page", - c.auth_scheme(), - c.account(), - self.container_name, - self.name); - - if let Some(ref timeout) = ppo.timeout { - uri = format!("{}&timeout={}", uri, timeout); - } - - let mut headers = Headers::new(); - - headers.set(XMSRange(range.into())); - headers.set(XMSBlobContentLength(content.1)); - if let Some(ref lease_id) = ppo.lease_id { - headers.set(XMSLeaseId(*lease_id)); - } - - headers.set(XMSPageWrite(PageWriteType::Update)); - - let mut resp = - try!(c.perform_request(&uri, core::HTTPMethod::Put, &headers, Some(content))); - try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - Ok(()) - } - - pub fn put_block(&self, - c: &Client, - block_id: &str, - pbo: &PutBlockOptions, - content: (&mut Read, u64)) - -> Result<(), AzureError> { - - let encoded_block_id = base64::encode(block_id.as_bytes()); - - let mut uri = format!("{}://{}.blob.core.windows.net/{}/{}?comp=block&blockid={}", - c.auth_scheme(), - c.account(), - self.container_name, - self.name, - encoded_block_id); - - if let Some(ref timeout) = pbo.timeout { - uri = format!("{}&timeout={}", uri, timeout); - } - - let mut headers = Headers::new(); - - headers.set(XMSBlobContentLength(content.1)); - - if let Some(ref lease_id) = pbo.lease_id { - headers.set(XMSLeaseId(*lease_id)); - } - if let Some(ref request_id) = pbo.request_id { - headers.set(XMSClientRequestId(request_id.to_owned())); - } - - let mut resp = - try!(c.perform_request(&uri, core::HTTPMethod::Put, &headers, Some(content))); - - try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - Ok(()) - } - - pub fn clear_page(&self, - c: &Client, - range: &BA512Range, - lease_id: Option) - -> Result<(), AzureError> { - - let uri = format!("{}://{}.blob.core.windows.net/{}/{}?comp=page", - c.auth_scheme(), - c.account(), - self.container_name, - self.name); - let mut headers = Headers::new(); - - headers.set(XMSRange(range.into())); - headers.set(XMSBlobContentLength(0)); - if let Some(lease_id) = lease_id { - headers.set(XMSLeaseId(lease_id)); - } - - headers.set(XMSPageWrite(PageWriteType::Clear)); - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Put, &headers, None)); - try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - Ok(()) - } - - pub fn del(c: &Client, - container_name: &str, - blob_name: &str) - -> Result<(), core::errors::AzureError> { - let uri = format!("{}://{}.blob.core.windows.net/{}/{}", - c.auth_scheme(), - c.account(), - container_name, - blob_name); - let mut resp = - try!(c.perform_request(&uri, core::HTTPMethod::Delete, &Headers::new(), None)); - try!(core::errors::check_status(&mut resp, StatusCode::Accepted)); - Ok(()) - } -} diff --git a/src/azure/storage/blob/put_block_options.rs b/src/azure/storage/blob/put_block_options.rs deleted file mode 100644 index 69f590dd5..000000000 --- a/src/azure/storage/blob/put_block_options.rs +++ /dev/null @@ -1,14 +0,0 @@ -use azure::core::lease::LeaseId; - -#[derive(Debug, Clone, PartialEq)] -pub struct PutBlockOptions { - pub lease_id: Option, - pub timeout: Option, - pub request_id: Option, -} - -pub const PUT_BLOCK_OPTIONS_DEFAULT: PutBlockOptions = PutBlockOptions { - timeout: None, - lease_id: None, - request_id: None, -}; diff --git a/src/azure/storage/blob/put_options.rs b/src/azure/storage/blob/put_options.rs deleted file mode 100644 index 502053575..000000000 --- a/src/azure/storage/blob/put_options.rs +++ /dev/null @@ -1,12 +0,0 @@ -use azure::core::lease::LeaseId; - -#[derive(Debug, Clone, PartialEq)] -pub struct PutOptions { - pub lease_id: Option, - pub timeout: Option, -} - -pub const PUT_OPTIONS_DEFAULT: PutOptions = PutOptions { - timeout: None, - lease_id: None, -}; diff --git a/src/azure/storage/blob/put_page_options.rs b/src/azure/storage/blob/put_page_options.rs deleted file mode 100644 index 79f352a73..000000000 --- a/src/azure/storage/blob/put_page_options.rs +++ /dev/null @@ -1,12 +0,0 @@ -use azure::core::lease::LeaseId; - -#[derive(Debug, Clone, PartialEq)] -pub struct PutPageOptions { - pub lease_id: Option, - pub timeout: Option, -} - -pub const PUT_PAGE_OPTIONS_DEFAULT: PutPageOptions = PutPageOptions { - timeout: None, - lease_id: None, -}; diff --git a/src/azure/storage/client.rs b/src/azure/storage/client.rs deleted file mode 100644 index c56d767b9..000000000 --- a/src/azure/storage/client.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::io::Read; -use hyper::net::HttpsConnector; -use hyper_native_tls::NativeTlsClient; -use hyper::client::response::Response; -use hyper::client::Client as HyperClient; -use hyper::error::Error; -use hyper::header::Headers; -use azure::core::HTTPMethod; -use super::rest_client::{perform_request, ServiceType}; - -// Can be variant for different cloud environment -const SERVICE_SUFFIX_BLOB: &'static str = ".blob.core.windows.net"; -const SERVICE_SUFFIX_TABLE: &'static str = ".table.core.windows.net"; - -pub struct Client { - account: String, - key: String, - use_https: bool, - hc: HyperClient, -} - -impl Client { - pub fn new(account: &str, key: &str, use_https: bool) -> Client { - use hyper; - - let ssl = NativeTlsClient::new().unwrap(); - let connector = HttpsConnector::new(ssl); - let client = hyper::Client::with_connector(connector); - - Client { - account: account.to_owned(), - key: key.to_owned(), - use_https: use_https, - hc: client, - } - } - - pub fn account(&self) -> &str { - &self.account - } - - pub fn use_https(&self) -> bool { - self.use_https - } - - pub fn auth_scheme(&self) -> &'static str { - if self.use_https { "https" } else { "http" } - } - - pub fn key(&self) -> &str { - &self.key - } - - pub fn perform_request(&self, - uri: &str, - method: HTTPMethod, - headers: &Headers, - request_body: Option<(&mut Read, u64)>) - -> Result { - perform_request(&self.hc, - uri, - method, - &self.key, - headers, - request_body, - None, - ServiceType::Blob) - } - - pub fn perform_table_request(&self, - segment: &str, - method: HTTPMethod, - headers: Headers, - request_str: Option<&str>) - -> Result { - - debug!("segment: {}, method: {:?}, headers: {:?}", - segment, - method, - headers); - perform_request(&self.hc, - (self.get_uri_prefix(ServiceType::Table) + segment).as_str(), - method, - &self.key, - &headers, - None, - request_str, - ServiceType::Table) - } - - /// Uri scheme + authority e.g. http://myaccount.table.core.windows.net/ - pub fn get_uri_prefix(&self, service_type: ServiceType) -> String { - self.auth_scheme().to_owned() + "://" + self.account() + - match service_type { - ServiceType::Blob => SERVICE_SUFFIX_BLOB, - ServiceType::Table => SERVICE_SUFFIX_TABLE, - } + "/" - } -} diff --git a/src/azure/storage/container/list_container_options.rs b/src/azure/storage/container/list_container_options.rs deleted file mode 100644 index 84194bd21..000000000 --- a/src/azure/storage/container/list_container_options.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[derive(Debug, Clone, PartialEq)] -pub struct ListContainerOptions { - pub max_results: u32, - pub include_metadata: bool, - pub next_marker: Option, - pub prefix: Option, - pub timeout: Option, -} - -pub const LIST_CONTAINER_OPTIONS_DEFAULT: ListContainerOptions = ListContainerOptions { - max_results: 5000, - include_metadata: false, - next_marker: None, - prefix: None, - timeout: None, -}; diff --git a/src/azure/storage/container/mod.rs b/src/azure/storage/container/mod.rs deleted file mode 100644 index 66c02a5eb..000000000 --- a/src/azure/storage/container/mod.rs +++ /dev/null @@ -1,175 +0,0 @@ -mod list_container_options; -pub use self::list_container_options::{ListContainerOptions, LIST_CONTAINER_OPTIONS_DEFAULT}; - -use azure::core; -use azure::core::errors; -use azure::core::enumerations; -use azure::core::parsing::{traverse, cast_must, cast_optional}; -use azure::core::incompletevector::IncompleteVector; - -use azure::core::lease::{LeaseStatus, LeaseState, LeaseDuration}; -use azure::storage::client::Client; - -use hyper::header::Headers; -use hyper::status::StatusCode; - -use std::str::FromStr; -use chrono::datetime::DateTime; -use chrono::UTC; - -use std::io::Read; - -use std::fmt; - -use xml::Element; - -use azure::core::errors::TraversingError; -use azure::core::parsing::FromStringOptional; - -header! { (XMSBlobPublicAccess, "x-ms-blob-public-access") => [PublicAccess] } - -create_enum!(PublicAccess, - (None, "none"), - (Container, "container"), - (Blob, "blob")); - - -#[derive(Debug, Clone)] -pub struct Container { - pub name: String, - pub last_modified: DateTime, - pub e_tag: String, - pub lease_status: LeaseStatus, - pub lease_state: LeaseState, - pub lease_duration: Option, -} - -impl Container { - pub fn new(name: &str) -> Container { - Container { - name: name.to_owned(), - last_modified: UTC::now(), - e_tag: "".to_owned(), - lease_status: LeaseStatus::Unlocked, - lease_state: LeaseState::Available, - lease_duration: None, - } - } - - pub fn parse(elem: &Element) -> Result { - let name = try!(cast_must::(elem, &["Name"])); - let last_modified = try!(cast_must::>(elem, - &["Properties", "Last-Modified"])); - let e_tag = try!(cast_must::(elem, &["Properties", "Etag"])); - - let lease_state = try!(cast_must::(elem, &["Properties", "LeaseState"])); - - let lease_duration = try!(cast_optional::(elem, - &["Properties", "LeaseDuration"])); - - let lease_status = try!(cast_must::(elem, &["Properties", "LeaseStatus"])); - - Ok(Container { - name: name, - last_modified: last_modified, - e_tag: e_tag, - lease_status: lease_status, - lease_state: lease_state, - lease_duration: lease_duration, - }) - } - - pub fn delete(&mut self, c: &Client) -> Result<(), core::errors::AzureError> { - let uri = format!("{}://{}.blob.core.windows.net/{}?restype=container", - c.auth_scheme(), - c.account(), - self.name); - - let mut resp = - try!(c.perform_request(&uri, core::HTTPMethod::Delete, &Headers::new(), None)); - - try!(errors::check_status(&mut resp, StatusCode::Accepted)); - Ok(()) - } - - pub fn create(c: &Client, - container_name: &str, - pa: PublicAccess) - -> Result<(), core::errors::AzureError> { - let uri = format!("{}://{}.blob.core.windows.net/{}?restype=container", - c.auth_scheme(), - c.account(), - container_name); - - let mut headers = Headers::new(); - - if pa != PublicAccess::None { - headers.set(XMSBlobPublicAccess(pa)); - } - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Put, &headers, None)); - - try!(errors::check_status(&mut resp, StatusCode::Created)); - - Ok(()) - } - - // TODO - // pub fn get_acl(c : &Client, gao : &GetAclOptions) - - pub fn list(c: &Client, - lco: &ListContainerOptions) - -> Result, core::errors::AzureError> { - let mut uri = format!("{}://{}.blob.core.windows.net?comp=list&maxresults={}", - c.auth_scheme(), - c.account(), - lco.max_results); - - if !lco.include_metadata { - uri = format!("{}&include=metadata", uri); - } - - if let Some(ref prefix) = lco.prefix { - uri = format!("{}&prefix={}", uri, prefix); - } - - if let Some(ref nm) = lco.next_marker { - uri = format!("{}&marker={}", uri, nm); - } - - if let Some(ref timeout) = lco.timeout { - uri = format!("{}&timeout={}", uri, timeout); - } - - let mut resp = try!(c.perform_request(&uri, core::HTTPMethod::Get, &Headers::new(), None)); - - try!(errors::check_status(&mut resp, StatusCode::Ok)); - - // println!("{:?}", resp.status); - - let mut resp_s = String::new(); - try!(resp.read_to_string(&mut resp_s)); - - // println!("response == \n\n{:?}\n\n", resp_s); - - let sp = &resp_s; - let elem: Element = try!(sp.parse()); - - let mut v = Vec::new(); - - // let containers = try!(traverse(&elem, &["Containers", "Container"])); - // println!("containers == {:?}", containers); - - for container in try!(traverse(&elem, &["Containers", "Container"], true)) { - v.push(try!(Container::parse(container))); - } - - let next_marker = match try!(cast_optional::(&elem, &["NextMarker"])) { - Some(ref nm) if nm == "" => None, - Some(nm) => Some(nm), - None => None, - }; - - Ok(IncompleteVector::new(next_marker, v)) - } -} diff --git a/src/azure/storage/mod.rs b/src/azure/storage/mod.rs deleted file mode 100644 index e72eb474c..000000000 --- a/src/azure/storage/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod client; -pub mod container; -pub mod blob; -pub mod table; -mod rest_client; diff --git a/src/azure/storage/rest_client.rs b/src/azure/storage/rest_client.rs deleted file mode 100644 index c6597b507..000000000 --- a/src/azure/storage/rest_client.rs +++ /dev/null @@ -1,465 +0,0 @@ -use azure::core::range; -use azure::core::HTTPMethod; -use azure::core::lease::{LeaseId, LeaseStatus, LeaseState, LeaseDuration, LeaseAction}; -use chrono; -use crypto::hmac::Hmac; -use crypto::mac::Mac; -use crypto::sha2::Sha256; -use hyper; -use hyper::Client; -use hyper::header::{Header, HeaderFormat, Headers, ContentEncoding, ContentLanguage, - ContentLength, ContentType, Date, IfModifiedSince, IfUnmodifiedSince}; -use base64; -use std::fmt::Display; -use std::io::Read; -use url; - -pub enum ServiceType { - Blob, - // Queue, File, - Table, -} - -const AZURE_VERSION: &'static str = "2016-05-31"; - -header! { (XMSVersion, "x-ms-version") => [String] } -header! { (XMSDate, "x-ms-date") => [String] } -header! { (Authorization, "Authorization") => [String] } -header! { (ContentMD5, "Content-MD5") => [String] } -header! { (IfMatch, "If-Match") => [String] } -header! { (IfNoneMatch, "If-None-Match") => [String] } -header! { (Range, "Range") => [String] } -header! { (XMSRange, "x-ms-range") => [range::Range] } -header! { (XMSLeaseId, "x-ms-lease-id") => [LeaseId] } -header! { (XMSLeaseStatus, "x-ms-lease-status") => [LeaseStatus] } -header! { (XMSLeaseState, "x-ms-lease-state") => [LeaseState] } -header! { (XMSLeaseAction, "x-ms-lease-action") => [LeaseAction] } -header! { (XMSLeaseDuration, "x-ms-lease-duration") => [LeaseDuration] } -header! { (XMSLeaseDurationSeconds, "x-ms-lease-duration") => [u32] } -header! { (XMSLeaseBreakPeriod, "x-ms-lease-break-period") => [u32] } -header! { (XMSProposedLeaseId, "x-ms-proposed-lease-id") => [LeaseId] } -header! { (ETag, "ETag") => [String] } -header! { (XMSRangeGetContentMD5, "x-ms-range-get-content-md5") => [bool] } -header! { (XMSClientRequestId, "x-ms-client-request-id") => [String] } - -fn generate_authorization(h: &Headers, - u: &url::Url, - method: HTTPMethod, - hmac_key: &str, - service_type: ServiceType) - -> String { - let str_to_sign = string_to_sign(h, u, method, service_type); - - // println!("\nstr_to_sign == {:?}\n", str_to_sign); - // println!("str_to_sign == {}", str_to_sign); - - let auth = encode_str_to_sign(&str_to_sign, hmac_key); - // println!("auth == {:?}", auth); - - format!("SharedKey {}:{}", get_account(u), auth) -} - -fn encode_str_to_sign(str_to_sign: &str, hmac_key: &str) -> String { - let mut v_hmac_key: Vec = Vec::new(); - - v_hmac_key.extend(base64::decode(hmac_key).unwrap()); - - let mut hmac = Hmac::new(Sha256::new(), &v_hmac_key); - hmac.input(str_to_sign.as_bytes()); - - // let res = hmac.result(); - // println!("{:?}", res.code()); - - base64::encode(hmac.result().code()) -} - -#[inline] -fn add_if_exists(h: &Headers) -> String { - let m = match h.get::() { - Some(ce) => ce.to_string(), - None => String::default(), - }; - - m + "\n" -} - -#[allow(unknown_lints)] -#[allow(needless_pass_by_value)] -fn string_to_sign(h: &Headers, - u: &url::Url, - method: HTTPMethod, - service_type: ServiceType) - -> String { - let mut str_to_sign = String::new(); - let verb = format!("{:?}", method); - str_to_sign = str_to_sign + &verb.to_uppercase() + "\n"; - - match service_type { - ServiceType::Table => {} - _ => { - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - // content lenght must only be specified if != 0 - // this is valid from 2015-02-21 - let m = match h.get::() { - Some(ce) => { - if ce.to_be() != 0u64 { - ce.to_string() - } else { - String::default() - } - } - None => String::default(), - }; - - str_to_sign = str_to_sign + &m + "\n"; - } - } - - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - - match service_type { - ServiceType::Table => { - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &canonicalized_resource_table(u); - } - _ => { - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &add_if_exists::(h); - str_to_sign = str_to_sign + &canonicalize_header(h); - str_to_sign = str_to_sign + &canonicalized_resource(u); - } - } - - - - // expected - // GET\n /*HTTP Verb*/ - // \n /*Content-Encoding*/ - // \n /*Content-Language*/ - // \n /*Content-Length (include value when zero)*/ - // \n /*Content-MD5*/ - // \n /*Content-Type*/ - // \n /*Date*/ - // \n /*If-Modified-Since */ - // \n /*If-Match*/ - // \n /*If-None-Match*/ - // \n /*If-Unmodified-Since*/ - // \n /*Range*/ - // x-ms-date:Sun, 11 Oct 2009 21:49:13 GMT\nx-ms-version:2009-09-19\n - // /*CanonicalizedHeaders*/ - // /myaccount /mycontainer\ncomp:metadata\nrestype:container\ntimeout:20 - // /*CanonicalizedResource*/ - // - // - - str_to_sign -} - -fn canonicalize_header(h: &Headers) -> String { - // println!("{:?}", h); - - let mut v_headers = Vec::new(); - - for header in h.iter().filter(|h| h.name().starts_with("x-ms")) { - let s: String = header.name().to_owned().trim().to_lowercase(); - - v_headers.push(s); - } - - // println!("{:?}", v_headers); - - v_headers.sort(); - - let mut can = String::new(); - - for header_name in v_headers { - let h = h.iter().find(|x| x.name() == header_name).unwrap(); - // println!("looking for {} => {:?}", header_name, h); - - let s = h.value_string(); - // println!("h.to_string() == {:?}", s); - - can = can + &header_name + ":" + &s + "\n"; - } - - // println!("{:?}", can); - - can -} - -#[inline] -fn get_account(u: &url::Url) -> String { - match u.host().unwrap().clone() { - url::Host::Domain(dm) => { - // println!("dom == {:?}", dm); - - let first_dot = dm.find('.').unwrap(); - String::from(&dm[0..first_dot]) - } - _ => panic!("only Domains are supported in canonicalized_resource"), - } -} - -// For table -fn canonicalized_resource_table(u: &url::Url) -> String { - format!("/{}{}", get_account(u), u.path()) -} - -fn canonicalized_resource(u: &url::Url) -> String { - let mut can_res: String = String::new(); - can_res = can_res + "/"; - - let account = get_account(u); - can_res = can_res + &account; - - let paths = u.path_segments().unwrap(); - - { - let mut path = String::new(); - for p in paths { - path.push_str("/"); - path.push_str(&*p); - } - - can_res = can_res + &path; - } - can_res = can_res + "\n"; - - // query parameters - let query_pairs = u.query_pairs(); //.into_owned(); - { - let mut qps = Vec::new(); - { - for qp in query_pairs { - trace!("adding to qps {:?}", qp); - - // add only once - if !(qps.iter().any(|x: &String| x == &qp.0)) { - qps.push(qp.0.into_owned()); - } - } - } - - qps.sort(); - - for qparam in qps { - // find correct parameter - let ret = lexy_sort(&query_pairs, &qparam); - - // println!("adding to can_res {:?}", ret); - - can_res = can_res + &qparam.to_lowercase() + ":"; - - for (i, item) in ret.iter().enumerate() { - if i > 0 { - can_res = can_res + "," - } - can_res = can_res + item; - } - - can_res = can_res + "\n"; - } - }; - - can_res[0..can_res.len() - 1].to_owned() -} - -fn lexy_sort(vec: &url::form_urlencoded::Parse, query_param: &str) -> Vec<(String)> { - let mut v_values: Vec = Vec::new(); - - for item in vec.filter(|x| x.0 == *query_param) { - v_values.push(item.1.into_owned()) - } - v_values.sort(); - - v_values -} - -#[allow(unknown_lints)] -#[allow(too_many_arguments)] -pub fn perform_request(client: &Client, - uri: &str, - method: HTTPMethod, - azure_key: &str, - headers: &Headers, - request_body: Option<(&mut Read, u64)>, - request_str: Option<&str>, - service_type: ServiceType) - -> Result { - let dt = chrono::UTC::now(); - let time = format!("{}", dt.format("%a, %d %h %Y %T GMT")); - - // let mut h = Headers::new(); - - let u = url::Url::parse(uri).unwrap(); - - // for header in additional_headers.iter() { - // println!("{:?}", header.value_string()); - // h.set(); - // } - - let mut h = headers.clone(); - - h.set(XMSDate(time)); - h.set(XMSVersion(AZURE_VERSION.to_owned())); - - if let Some((_, size)) = request_body { - h.set(ContentLength(size)); - } - - let auth = generate_authorization(&h, &u, method, azure_key, service_type); - // println!("auth == {:?}", auth); - - h.set(Authorization(auth)); - - // println!("{:?}", h); - - let mut builder = match method { - HTTPMethod::Get => client.get(&u.to_string()), - HTTPMethod::Put => client.put(&u.to_string()), - HTTPMethod::Post => client.post(&u.to_string()), - HTTPMethod::Delete => client.delete(&u.to_string()), - }; - - if let Some((mut rb, size)) = request_body { - let b = hyper::client::Body::SizedBody(rb, size); - builder = builder.body(b); - } else if let Some(body) = request_str { - builder = builder.body(body); - } - - builder.headers(h).send() -} - - -mod test { - extern crate hyper; - extern crate chrono; - extern crate url; - - #[test] - fn test_canonicalize_header() { - use super::*; - - let dt = chrono::DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900").unwrap(); - let time = format!("{}", dt.format("%a, %d %h %Y %T GMT%Z")); - - println!("time == {}", time); - - let mut h = hyper::header::Headers::new(); - - h.set(XMSDate(time)); - h.set(XMSVersion("2015-04-05".to_owned())); - - assert_eq!(super::canonicalize_header(&h), - "x-ms-date:Fri, 28 Nov 2014 21:00:09 GMT+09:00\nx-ms-version:2015-04-05\n"); - } - - #[test] - fn str_to_sign_test() { - use super::*; - use hyper::header::{Accept, qitem}; - use azure::storage::table::{get_json_mime_nometadata, get_default_json_mime}; - - let mut headers: Headers = Headers::new(); - headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); - headers.set(ContentType(get_default_json_mime())); - - - let u: url::Url = url::Url::parse("https://mindrust.table.core.windows.net/TABLES") - .unwrap(); - let method: HTTPMethod = HTTPMethod::Post; - let service_type: ServiceType = ServiceType::Table; - - let dt = chrono::DateTime::parse_from_rfc2822("Wed, 3 May 2017 14:04:56 +0000").unwrap(); - let time = format!("{}", dt.format("%a, %d %h %Y %T GMT")); - - headers.set(XMSDate(time)); - headers.set(XMSVersion(AZURE_VERSION.to_owned())); - - let s = string_to_sign(&headers, &u, method, service_type); - - assert_eq!( - s, - "POST - -application/json; charset=utf-8 -Wed, 03 May 2017 14:04:56 GMT -/mindrust/TABLES" - ); - } - - #[test] - fn test_canonicalize_resource_10() { - let url = url::Url::parse("https://mindrust.table.core.windows.net/TABLES").unwrap(); - assert_eq!(super::canonicalized_resource(&url), "/mindrust/TABLES"); - } - - #[test] - fn test_canonicalize_resource_1() { - let url = url::Url::parse("http://myaccount.blob.core.windows.\ - net/mycontainer?restype=container&comp=metadata") - .unwrap(); - assert_eq!(super::canonicalized_resource(&url), - "/myaccount/mycontainer\ncomp:metadata\nrestype:container"); - } - - #[test] - fn test_canonicalize_resource_2() { - let url = url::Url::parse("http://myaccount.blob.core.windows.\ - net/mycontainer?restype=container&comp=list&include=snapshots&\ - include=metadata&include=uncommittedblobs") - .unwrap(); - assert_eq!(super::canonicalized_resource(&url), - "/myaccount/mycontainer\ncomp:list\ninclude:metadata,snapshots,\ - uncommittedblobs\nrestype:container"); - } - - #[test] - fn test_canonicalize_resource_3() { - let url = url::Url::parse("https://myaccount-secondary.blob.core.windows.\ - net/mycontainer/myblob") - .unwrap(); - assert_eq!(super::canonicalized_resource(&url), - "/myaccount-secondary/mycontainer/myblob"); - } - - #[test] - fn test_encode_str_to_sign_1() { - let str_to_sign = "53d7e14aee681a00340300032015-01-01T10:00:00.0000000".to_owned(); - let hmac_key = "pXeTVaaaaU9XxH6fPcPlq8Y9D9G3Cdo5Eh2nMSgKj/DWqeSFFXDdmpz5Trv+L2hQNM+nGa704R\ - f8Z22W9O1jdQ==" - .to_owned(); - - assert_eq!(super::encode_str_to_sign(&str_to_sign, &hmac_key), - "gZzaRaIkvC9jYRY123tq3xXZdsMAcgAbjKQo8y0p0Fs=".to_owned()); - } - - #[test] - fn test_encode_str_to_sign_2() { - let str_to_sign = "This is the data to sign".to_owned(); - let hmac_key = "pXeTVaaaaU9XxH6fPcPlq8Y9D9G3Cdo5Eh2nMSgKj/DWqeSFFXDdmpz5Trv+L2hQNM+nGa704R\ - f8Z22W9O1jdQ==" - .to_owned(); - - assert_eq!(super::encode_str_to_sign(&str_to_sign, &hmac_key), - "YuKoXELO9M9HXeeGaSXBr4Nk+CgPAEQhcwJ6tVtBRCw=".to_owned()); - } - - #[test] - fn test_encode_str_to_sign_3() { - let str_to_sign = "This is the data to sign".to_owned(); - let hmac_key = "pXeTVaaaaU9XxH6fPcPlq8Y9D9G3Cdo5Eh2nMSgKj/DWqeSFFXDdmpz5Trv+L2hQNM+nGa704R\ - f8Z22W9O1jdQ==" - .to_owned(); - - assert_eq!(super::encode_str_to_sign(&str_to_sign, &hmac_key), - "YuKoXELO9M9HXeeGaSXBr4Nk+CgPAEQhcwJ6tVtBRCw=".to_owned()); - } -} diff --git a/src/azure/storage/table/batch.rs b/src/azure/storage/table/batch.rs deleted file mode 100644 index 34e9a451b..000000000 --- a/src/azure/storage/table/batch.rs +++ /dev/null @@ -1,132 +0,0 @@ -/* Table batch support -* Current limitation: -1. Only support single changeset in a batch request -2. Only allow PUT and GET in changeset -*/ -use serde::Serialize; -use super::entity_path; -use serde_json; - -const BATCH_BEGIN: &'static str = r#"--batch_a1e9d677-b28b-435e-a89e-87e6a768a431 -Content-Type: multipart/mixed; boundary=changeset_8a28b620-b4bb-458c-a177-0959fb14c977 - -"#; -const BATCH_END: &'static str = "--batch_a1e9d677-b28b-435e-a89e-87e6a768a431\n"; -const CHANGESET_BEGIN: &'static str = r#"--changeset_8a28b620-b4bb-458c-a177-0959fb14c977 -Content-Type: application/http -Content-Transfer-Encoding: binary - -"#; -const CHANGESET_END: &'static str = "--changeset_8a28b620-b4bb-458c-a177-0959fb14c977--\n"; -const UPDATE_HEADER: &'static str = "Content-Type: application/json\n"; -const ACCEPT_HEADER: &'static str = "Accept: application/json;odata=nometadata\n"; -const IF_MATCH_HEADER: &'static str = "If-Match: *\n"; - -// RowKey, Payload. Payload None for deletion -pub struct BatchItem(String, Option); - -impl BatchItem { - pub fn new(row_key: String, value: Option) -> Self { - BatchItem(row_key, value) - } -} - -pub fn generate_batch_payload(uri_prefix: &str, - table: &str, - primary_key: &str, - items: &[BatchItem]) - -> String { - let mut payload: String = BATCH_BEGIN.to_owned(); - for item in items { - payload.push_str(CHANGESET_BEGIN); - payload.push_str(if item.1.is_some() { "PUT" } else { "DELETE" }); - payload.push_str(" "); - payload.push_str(uri_prefix); - payload.push_str(entity_path(table, primary_key, item.0.as_str()).as_str()); - payload.push_str(" HTTP/1.1\n"); - payload.push_str(ACCEPT_HEADER); - if let Some(ref v) = item.1 { - payload.push_str(UPDATE_HEADER); - payload.push_str("\n"); - payload.push_str(serde_json::to_string(v).unwrap().as_str()); - } else { - payload.push_str(IF_MATCH_HEADER); - } - - payload.push_str("\n"); - } - payload + CHANGESET_END + BATCH_END -} - -#[cfg(test)] -mod test { - use super::*; - - #[allow(non_snake_case)] - #[derive(Serialize)] - struct Entity { - PartitionKey: String, - RowKey: String, - Rating: i32, - Text: String, - } - - #[test] - fn verify_batch_payload() { - let expected = r#"--batch_a1e9d677-b28b-435e-a89e-87e6a768a431 -Content-Type: multipart/mixed; boundary=changeset_8a28b620-b4bb-458c-a177-0959fb14c977 - ---changeset_8a28b620-b4bb-458c-a177-0959fb14c977 -Content-Type: application/http -Content-Transfer-Encoding: binary - -PUT https://myaccount.table.core.windows.net/Blogs(PartitionKey='Channel_17',RowKey='3') HTTP/1.1 -Accept: application/json;odata=nometadata -Content-Type: application/json - -{"PartitionKey":"Channel_17","RowKey":"3","Rating":9,"Text":".NET..."} ---changeset_8a28b620-b4bb-458c-a177-0959fb14c977 -Content-Type: application/http -Content-Transfer-Encoding: binary - -PUT https://myaccount.table.core.windows.net/Blogs(PartitionKey='Channel_17',RowKey='3') HTTP/1.1 -Accept: application/json;odata=nometadata -Content-Type: application/json - -{"PartitionKey":"Channel_17","RowKey":"3","Rating":9,"Text":"PDC 2008..."} ---changeset_8a28b620-b4bb-458c-a177-0959fb14c977 -Content-Type: application/http -Content-Transfer-Encoding: binary - -DELETE https://myaccount.table.core.windows.net/Blogs(PartitionKey='Channel_17',RowKey='3') HTTP/1.1 -Accept: application/json;odata=nometadata -If-Match: * - ---changeset_8a28b620-b4bb-458c-a177-0959fb14c977-- ---batch_a1e9d677-b28b-435e-a89e-87e6a768a431 -"#; - - let items = vec![bupdate("Channel_17", "3", 9, ".NET..."), - bupdate("Channel_17", "3", 9, "PDC 2008..."), - bdelete("3")]; - let actual = generate_batch_payload("https://myaccount.table.core.windows.net/", - "Blogs", - "Channel_17", - items.as_slice()); - assert_eq!(expected, actual); - } - - fn bupdate(pk: &str, rk: &str, rating: i32, text: &str) -> BatchItem { - BatchItem(rk.to_owned(), - Some(Entity { - PartitionKey: pk.to_owned(), - RowKey: rk.to_owned(), - Rating: rating, - Text: text.to_owned(), - })) - } - - fn bdelete(rk: &str) -> BatchItem { - BatchItem(rk.to_owned(), None) - } -} diff --git a/src/azure/storage/table/mod.rs b/src/azure/storage/table/mod.rs deleted file mode 100644 index a29a6010d..000000000 --- a/src/azure/storage/table/mod.rs +++ /dev/null @@ -1,228 +0,0 @@ -mod batch; - -pub use self::batch::BatchItem; - -use self::batch::generate_batch_payload; -use std::io::Read; -use azure::core; -use azure::core::errors::{self, AzureError}; -use azure::storage::client::Client; -use azure::storage::rest_client::ServiceType; -use hyper::client::response::Response; -use hyper::header::{Accept, ContentType, Headers, IfMatch, qitem}; -use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value}; -use hyper::status::StatusCode; -use serde::Serialize; -use serde::de::DeserializeOwned; -use serde_json; - -const TABLE_TABLES: &'static str = "TABLES"; - -pub struct TableService { - client: Client, -} - -impl TableService { - pub fn new(client: Client) -> Self { - TableService { client: client } - } - - pub fn list_tables(&self) -> Result, AzureError> { - Ok(self.query_entities(TABLE_TABLES, None)? - .into_iter() - .map(|x: TableEntity| x.TableName) - .collect()) - } - - // Create table if not exists. - pub fn create_table>(&self, table_name: T) -> Result<(), AzureError> { - let body = &serde_json::to_string(&TableEntity { TableName: table_name.into() }).unwrap(); - debug!("body == {}", body); - let mut response = try!(self.request_with_default_header(TABLE_TABLES, - core::HTTPMethod::Post, - Some(body))); - // TODO: Here treats conflict as existed, but could be reserved name, such as 'Tables', - // should check table existence directly - if !(StatusCode::Created == response.status || StatusCode::Conflict == response.status) { - try!(errors::check_status(&mut response, StatusCode::Created)); - } - - Ok(()) - } - - pub fn get_entity(&self, - table_name: &str, - partition_key: &str, - row_key: &str) - -> Result, AzureError> { - let path = &entity_path(table_name, partition_key, row_key); - let mut response = - try!(self.request_with_default_header(path, core::HTTPMethod::Get, None)); - if StatusCode::NotFound == response.status { - return Ok(None); - } - try!(errors::check_status(&mut response, StatusCode::Ok)); - let body = try!(get_response_body(&mut response)); - - let res = serde_json::from_str(&body).unwrap(); - - //res = res.clone(); - - Ok(res) - } - - pub fn query_entities(&self, - table_name: &str, - query: Option<&str>) - -> Result, AzureError> { - let mut path = table_name.to_owned(); - if let Some(clause) = query { - path.push_str("?"); - path.push_str(clause); - } - - let mut response = - try!(self.request_with_default_header(path.as_str(), core::HTTPMethod::Get, None)); - try!(errors::check_status(&mut response, StatusCode::Ok)); - let body = &try!(get_response_body(&mut response)); - let ec: EntityCollection = serde_json::from_str(body).unwrap(); - Ok(ec.value) - } - - pub fn insert_entity(&self, - table_name: &str, - entity: &T) - -> Result<(), AzureError> { - let body = &serde_json::to_string(entity).unwrap(); - let mut resp = - try!(self.request_with_default_header(table_name, core::HTTPMethod::Post, Some(body))); - try!(errors::check_status(&mut resp, StatusCode::Created)); - Ok(()) - } - - pub fn update_entity(&self, - table_name: &str, - partition_key: &str, - row_key: &str, - entity: &T) - -> Result<(), AzureError> { - let body = &serde_json::to_string(entity).unwrap(); - let path = &entity_path(table_name, partition_key, row_key); - let mut resp = - try!(self.request_with_default_header(path, core::HTTPMethod::Put, Some(body))); - try!(errors::check_status(&mut resp, StatusCode::NoContent)); - Ok(()) - } - - pub fn delete_entity(&self, - table_name: &str, - partition_key: &str, - row_key: &str) - -> Result<(), AzureError> { - let path = &entity_path(table_name, partition_key, row_key); - let mut headers = Headers::new(); - headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); - headers.set(IfMatch::Any); - - let mut resp = try!(self.request(path, core::HTTPMethod::Delete, None, headers)); - try!(errors::check_status(&mut resp, StatusCode::NoContent)); - Ok(()) - } - - pub fn batch(&self, - table_name: &str, - partition_key: &str, - batch_items: &[BatchItem]) - -> Result<(), AzureError> { - let payload = - &generate_batch_payload(self.client.get_uri_prefix(ServiceType::Table).as_str(), - table_name, - partition_key, - batch_items); - let mut headers = Headers::new(); - headers.set(ContentType(get_batch_mime())); - let mut response = - try!(self.request("$batch", core::HTTPMethod::Post, Some(payload), headers)); - try!(errors::check_status(&mut response, StatusCode::Accepted)); - // TODO deal with body response, handle batch failure. - // let ref body = try!(get_response_body(&mut response)); - // info!("{}", body); - Ok(()) - } - - fn request_with_default_header(&self, - segment: &str, - method: core::HTTPMethod, - request_str: Option<&str>) - -> Result { - let mut headers = Headers::new(); - headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); - if request_str.is_some() { - headers.set(ContentType(get_default_json_mime())); - } - self.request(segment, method, request_str, headers) - } - - fn request(&self, - segment: &str, - method: core::HTTPMethod, - request_str: Option<&str>, - headers: Headers) - -> Result { - trace!("{:?} {}", method, segment); - if let Some(body) = request_str { - trace!("Request: {}", body); - } - - let resp = try!(self.client - .perform_table_request(segment, method, headers, request_str)); - trace!("Response status: {:?}", resp.status); - Ok(resp) - } -} - - -#[allow(non_snake_case)] -#[derive(Serialize, Deserialize)] -struct TableEntity { - TableName: String, -} - -#[derive(Deserialize)] -struct EntityCollection { - value: Vec, -} - -fn get_response_body(resp: &mut Response) -> Result { - let mut body = String::new(); - try!(resp.read_to_string(&mut body)); - trace!("Response Body:{}", body); - Ok(body) -} - -#[inline] -fn entity_path(table_name: &str, partition_key: &str, row_key: &str) -> String { - table_name.to_owned() + "(PartitionKey='" + partition_key + "',RowKey='" + row_key + "')" -} - -#[inline] -pub fn get_default_json_mime() -> Mime { - Mime(TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]) -} - -#[inline] -pub fn get_json_mime_nometadata() -> Mime { - Mime(TopLevel::Application, - SubLevel::Json, - vec![(Attr::Ext("odata".to_owned()), Value::Ext("nometadata".to_owned()))]) -} - -#[inline] -pub fn get_batch_mime() -> Mime { - Mime(TopLevel::Multipart, - SubLevel::Ext("Mixed".to_owned()), - vec![(Attr::Ext("boundary".to_owned()), - Value::Ext("batch_a1e9d677-b28b-435e-a89e-87e6a768a431".to_owned()))]) -} diff --git a/src/lib.rs b/src/lib.rs index 0554a3645..105d97417 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,12 @@ #[macro_use] extern crate hyper; -extern crate hyper_native_tls; extern crate chrono; + +extern crate futures; +extern crate tokio_core; +extern crate hyper_tls; + #[macro_use] extern crate url; extern crate crypto; From 3afd5cdcf1b90d40692828eb3ea120fdaac7f997 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 15 Jun 2017 18:09:43 +0200 Subject: [PATCH 05/54] Working on moving migrating to tokio --- Cargo.lock | 1 + Cargo.toml | 1 + src/azure/cosmos/client.rs | 49 ++++++++++++++++---------------------- src/lib.rs | 1 + 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcfe7f7d9..482ec41bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index bc0ab536d..728879a00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ tokio = "*" tokio-core = "*" futures = "*" hyper-tls = "*" +native-tls ="*" [features] test_e2e = [] diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index c376264ee..94c540f8f 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -32,6 +32,7 @@ use url::percent_encoding::utf8_percent_encode; use tokio_core; use hyper_tls; +use native_tls; const AZURE_VERSION: &'static str = "2017-02-22"; const VERSION: &'static str = "1.0"; @@ -59,22 +60,25 @@ pub enum ResourceType { } pub struct Client<'a> { - //hyper_client: hyper::Client, + hyper_client: hyper::Client, authorization_token: &'a AuthorizationToken<'a>, } impl<'a> Client<'a> { pub fn new( + handle: &tokio_core::reactor::Handle, authorization_token: &'a AuthorizationToken<'a>) - -> Result, ()>{ //hyper_tls::HttpsConnector::Error> { - + -> Result, native_tls::Error> { + let client = hyper::Client::configure() - .connector(hyper_tls::HttpsConnector::new(4, handle)) - .build(handle)?; + .connector(hyper_tls::HttpsConnector::new(4, handle)?) + .build(handle); + + let client = hyper::Client::new(handle); Ok(Client { - // hyper_client: client, + hyper_client: client, authorization_token: authorization_token, }) } @@ -86,10 +90,10 @@ impl<'a> Client<'a> { fn perform_request(&self, uri: hyper::Uri, http_method: hyper::Method, - request_body: Option<(&mut Read, u64)>, + request_body: Option<&str>, resource_type: ResourceType, headers: Option) - -> Result { + -> Result { let dt = chrono::UTC::now(); let time = format!("{}", dt.format(TIME_FORMAT)); @@ -105,26 +109,22 @@ impl<'a> Client<'a> { let mut request = hyper::Request::new(http_method, uri); // we need to add custom headers. If the caller has passed its collection of - // headers we will add to his ones. Otherwise we create one from scratch. + // headers we will import them. if let Some(hs) = headers { for h in hs.iter() { - request.headers_mut().set(h); + request.headers_mut().set_raw(h.name(), h.value_string()); } } - if let Some((_, size)) = request_body { - request.headers_mut().set(ContentLength(size)); - } - request.headers_mut().set(XMSDate(time)); request.headers_mut().set(XMSVersion(AZURE_VERSION.to_owned())); request.headers_mut().set(Authorization(auth)); trace!("perform_request::headers == {:?}", request.headers()); - if let Some((mut rb, size)) = request_body { - //let b = hyper::client::Body::SizedBody(rb, size); - request.set_body(request_body); + if let Some(body) = request_body { + request.headers_mut().set(ContentLength(body.len() as u64)); + request.set_body(body.to_string()); } let future = self.hyper_client.request(request); @@ -162,11 +162,10 @@ impl<'a> Client<'a> { let req = CreateDatabaseRequest { id: database_name }; let req = serde_json::to_string(&req)?; - let mut curs = Cursor::new(&req); let mut resp = self.perform_request(uri, hyper::Method::Post, - Some((&mut curs, req.len() as u64)), + Some(&req), ResourceType::Databases, None)?; @@ -277,11 +276,9 @@ impl<'a> Client<'a> { trace!("collection_serialized == {}", collection_serialized); - let mut curs = Cursor::new(&collection_serialized); - let mut resp = self.perform_request(uri, hyper::Method::Post, - Some((&mut curs, collection_serialized.len() as u64)), + Some(&collection_serialized), ResourceType::Collections, Some(headers))?; @@ -333,11 +330,9 @@ impl<'a> Client<'a> { trace!("collection_serialized == {}", collection_serialized); - let mut curs = Cursor::new(&collection_serialized); - let mut resp = self.perform_request(uri, hyper::Method::Put, - Some((&mut curs, collection_serialized.len() as u64)), + Some(&collection_serialized), ResourceType::Collections, None)?; @@ -377,11 +372,9 @@ impl<'a> Client<'a> { let document_serialized = serde_json::to_string(document)?; trace!("document_serialized == {}", document_serialized); - let mut curs = Cursor::new(&document_serialized); - let mut resp = self.perform_request(uri, hyper::Method::Post, - Some((&mut curs, document_serialized.len() as u64)), + Some(&document_serialized), ResourceType::Documents, Some(headers))?; diff --git a/src/lib.rs b/src/lib.rs index 105d97417..3fe2c62c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ extern crate chrono; extern crate futures; extern crate tokio_core; extern crate hyper_tls; +extern crate native_tls; #[macro_use] extern crate url; From cb6bb9fc69d547afb3d745683f1a3c38e82529e0 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 15 Jun 2017 18:10:41 +0200 Subject: [PATCH 06/54] formatted with tabularize --- Cargo.toml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 728879a00..69398c2db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,26 +16,26 @@ name = "main" doc = false [dependencies] -chrono = "*" -rust-crypto = "*" -quick-error = "*" -RustyXML = "*" -log = "*" -env_logger = "*" -uuid = "*" -time = "*" -url = "*" -base64 = "*" -serde = "*" -serde_json = "*" -serde_derive = "*" -hyper = "*" +chrono = "*" +rust-crypto = "*" +quick-error = "*" +RustyXML = "*" +log = "*" +env_logger = "*" +uuid = "*" +time = "*" +url = "*" +base64 = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" +hyper = "*" -tokio = "*" -tokio-core = "*" -futures = "*" -hyper-tls = "*" -native-tls ="*" +tokio = "*" +tokio-core = "*" +futures = "*" +hyper-tls = "*" +native-tls = "*" [features] test_e2e = [] From a1f7ce39caf17e4994a5c5a1e1a9a74dd80eb439 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 15 Jun 2017 21:50:07 +0200 Subject: [PATCH 07/54] Have to return a future --- src/azure/core/errors.rs | 36 ++- src/azure/cosmos/client.rs | 485 ++++++++++++++++++++++--------------- 2 files changed, 313 insertions(+), 208 deletions(-) diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 49efb7ac8..0cb83b5e1 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -9,6 +9,8 @@ use url::ParseError as URLParseError; use azure::core::enumerations::ParsingError; use azure::core::range::ParseError; use serde_json; +use futures::Future; +use futures::Stream; #[derive(Debug, Clone, PartialEq)] pub struct UnexpectedHTTPResult { @@ -137,24 +139,32 @@ impl From<()> for AzureError { } #[inline] -pub fn check_status(resp: &mut hyper::client::response::Response, - s: StatusCode) - -> Result<(), AzureError> { - if resp.status != s { - let mut resp_s = String::new(); - resp.read_to_string(&mut resp_s)?; +pub fn check_status( + resp: hyper::client::FutureResponse, + s: StatusCode, +) -> Result { - return Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, - resp.status, - &resp_s))); - } + resp.and_then(|res| if res.status() != s { + let resp_s = res.body().concat2(); + return Err(AzureError::UnexpectedHTTPResult( + UnexpectedHTTPResult::new(s, res.status(), &resp_s), + )); + }); + // let mut resp_s = String::new(); + //resp.read_to_string(&mut resp_s)?; + + // return Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, + // resp.status, + // &resp_s))); + // } Ok(()) } -pub fn check_status_extract_body(resp: &mut hyper::client::response::Response, - s: StatusCode) - -> Result { +pub fn check_status_extract_body( + resp: &mut hyper::client::response::Response, + s: StatusCode, +) -> Result { check_status(resp, s)?; diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 94c540f8f..7f14c9a79 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -66,58 +66,62 @@ pub struct Client<'a> { impl<'a> Client<'a> { pub fn new( - handle: &tokio_core::reactor::Handle, - authorization_token: &'a AuthorizationToken<'a>) - -> Result, native_tls::Error> { - + authorization_token: &'a AuthorizationToken<'a>, + ) -> Result, native_tls::Error> { + let client = hyper::Client::configure() - .connector(hyper_tls::HttpsConnector::new(4, handle)?) + .connector(hyper_tls::HttpsConnector::new(4, handle)?) .build(handle); - + let client = hyper::Client::new(handle); - + Ok(Client { - hyper_client: client, - authorization_token: authorization_token, - }) + hyper_client: client, + authorization_token: authorization_token, + }) } pub fn set_authorization_token(&mut self, at: &'a AuthorizationToken<'a>) { self.authorization_token = at; } - fn perform_request(&self, - uri: hyper::Uri, - http_method: hyper::Method, - request_body: Option<&str>, - resource_type: ResourceType, - headers: Option) - -> Result { + fn perform_request( + &self, + uri: hyper::Uri, + http_method: hyper::Method, + request_body: Option<&str>, + resource_type: ResourceType, + headers: Option, + ) -> Result { let dt = chrono::UTC::now(); let time = format!("{}", dt.format(TIME_FORMAT)); let resource_link = generate_resource_link(&uri); - let auth = generate_authorization(self.authorization_token, - http_method, - resource_type, - resource_link, - &time); + let auth = generate_authorization( + self.authorization_token, + http_method, + resource_type, + resource_link, + &time, + ); trace!("perform_request::auth == {:?}", auth); let mut request = hyper::Request::new(http_method, uri); - + // we need to add custom headers. If the caller has passed its collection of // headers we will import them. if let Some(hs) = headers { for h in hs.iter() { - request.headers_mut().set_raw(h.name(), h.value_string()); + request.headers_mut().set_raw(h.name(), h.value_string()); } } request.headers_mut().set(XMSDate(time)); - request.headers_mut().set(XMSVersion(AZURE_VERSION.to_owned())); + request.headers_mut().set( + XMSVersion(AZURE_VERSION.to_owned()), + ); request.headers_mut().set(Authorization(auth)); trace!("perform_request::headers == {:?}", request.headers()); @@ -135,13 +139,20 @@ impl<'a> Client<'a> { pub fn list_databases(&self) -> Result, AzureError> { trace!("list_databases called"); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs", - self.authorization_token.account()))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs", + self.authorization_token.account() + ))?; // No specific headers are required, list databases only needs standard headers // which will be provied by perform_request - let mut resp = - self.perform_request(uri, hyper::Method::Get, None, ResourceType::Databases, None)?; + let mut resp = self.perform_request( + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + None, + )?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let db: ListDatabasesResponse = serde_json::from_str(&body)?; @@ -150,11 +161,15 @@ impl<'a> Client<'a> { } pub fn create_database(&self, database_name: &str) -> Result { - trace!("create_databases called (database_name == {})", - database_name); + trace!( + "create_databases called (database_name == {})", + database_name + ); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs", - self.authorization_token.account()))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs", + self.authorization_token.account() + ))?; // No specific headers are required, create databases only needs standard headers // which will be provied by perform_request @@ -163,11 +178,13 @@ impl<'a> Client<'a> { let req = CreateDatabaseRequest { id: database_name }; let req = serde_json::to_string(&req)?; - let mut resp = self.perform_request(uri, - hyper::Method::Post, - Some(&req), - ResourceType::Databases, - None)?; + let mut resp = self.perform_request( + uri, + hyper::Method::Post, + Some(&req), + ResourceType::Databases, + None, + )?; let body = check_status_extract_body(&mut resp, StatusCode::Created)?; let db: Database = serde_json::from_str(&body)?; @@ -178,14 +195,21 @@ impl<'a> Client<'a> { pub fn get_database(&self, database_name: &str) -> Result { trace!("get_database called (database_name == {})", database_name); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}", - self.authorization_token.account(), - database_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}", + self.authorization_token.account(), + database_name + ))?; // No specific headers are required, get database only needs standard headers // which will be provied by perform_request - let mut resp = - self.perform_request(uri, hyper::Method::Get, None, ResourceType::Databases, None)?; + let mut resp = self.perform_request( + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + None, + )?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let db: Database = serde_json::from_str(&body)?; @@ -194,43 +218,59 @@ impl<'a> Client<'a> { } pub fn delete_database(&self, database_name: &str) -> Result<(), AzureError> { - trace!("delete_database called (database_name == {})", - database_name); + trace!( + "delete_database called (database_name == {})", + database_name + ); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}", - self.authorization_token.account(), - database_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}", + self.authorization_token.account(), + database_name + ))?; // No specific headers are required, delete database only needs standard headers // which will be provied by perform_request - let mut resp = self.perform_request(uri, - hyper::Method::Delete, - None, - ResourceType::Databases, - None)?; + let future = self.perform_request( + uri, + hyper::Method::Delete, + None, + ResourceType::Databases, + None, + )?; - check_status(&mut resp, StatusCode::NoContent)?; + check_status(future, StatusCode::NoContent)?; Ok(()) } - pub fn get_collection(&self, - database_name: &str, - collection_name: &str) - -> Result { - trace!("get_collection called (database_name == {}, collection_name == {})", - database_name, - collection_name); + pub fn get_collection( + &self, + database_name: &str, + collection_name: &str, + ) -> Result { + trace!( + "get_collection called (database_name == {}, collection_name == {})", + database_name, + collection_name + ); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", - self.authorization_token.account(), - database_name, - collection_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls/{}", + self.authorization_token.account(), + database_name, + collection_name + ))?; // No specific headers are required, get database only needs standard headers // which will be provied by perform_request - let mut resp = - self.perform_request(uri, hyper::Method::Get, None, ResourceType::Collections, None)?; + let mut resp = self.perform_request( + uri, + hyper::Method::Get, + None, + ResourceType::Collections, + None, + )?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let coll: Collection = serde_json::from_str(&body)?; @@ -241,14 +281,21 @@ impl<'a> Client<'a> { pub fn list_collections(&self, database_name: &str) -> Result, AzureError> { trace!("list_collections called"); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))?; // No specific headers are required, list collections only needs standard headers // which will be provied by perform_request - let mut resp = - self.perform_request(uri, hyper::Method::Get, None, ResourceType::Collections, None)?; + let mut resp = self.perform_request( + uri, + hyper::Method::Get, + None, + ResourceType::Collections, + None, + )?; let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; let colls: ListCollectionsResponse = serde_json::from_str(&body)?; @@ -256,16 +303,19 @@ impl<'a> Client<'a> { Ok(colls.collections) } - pub fn create_collection(&self, - database_name: &str, - required_throughput: u64, - collection: &Collection) - -> Result { + pub fn create_collection( + &self, + database_name: &str, + required_throughput: u64, + collection: &Collection, + ) -> Result { trace!("create_collection called"); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))?; // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection // Standard headers (auth and version) will be provied by perform_request @@ -276,11 +326,13 @@ impl<'a> Client<'a> { trace!("collection_serialized == {}", collection_serialized); - let mut resp = self.perform_request(uri, - hyper::Method::Post, - Some(&collection_serialized), - ResourceType::Collections, - Some(headers))?; + let mut resp = self.perform_request( + uri, + hyper::Method::Post, + Some(&collection_serialized), + ResourceType::Collections, + Some(headers), + )?; let body = check_status_extract_body(&mut resp, StatusCode::Created)?; let coll: Collection = serde_json::from_str(&body)?; @@ -288,41 +340,51 @@ impl<'a> Client<'a> { Ok(coll) } - pub fn delete_collection(&self, - database_name: &str, - collection_name: &str) - -> Result<(), AzureError> { - trace!("delete_collection called (database_name == {}, collection_name == {}", - database_name, - collection_name); + pub fn delete_collection( + &self, + database_name: &str, + collection_name: &str, + ) -> Result<(), AzureError> { + trace!( + "delete_collection called (database_name == {}, collection_name == {}", + database_name, + collection_name + ); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}", - self.authorization_token.account(), - database_name, - collection_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls/{}", + self.authorization_token.account(), + database_name, + collection_name + ))?; // No specific headers are required. // Standard headers (auth and version) will be provied by perform_request - let mut resp = self.perform_request(uri, - hyper::Method::Delete, - None, - ResourceType::Collections, - None)?; + let future = self.perform_request( + uri, + hyper::Method::Delete, + None, + ResourceType::Collections, + None, + )?; - check_status(&mut resp, StatusCode::NoContent)?; + check_status(future, StatusCode::NoContent)?; Ok(()) } - pub fn replace_collection(&self, - database_name: &str, - collection: &str) - -> Result { + pub fn replace_collection( + &self, + database_name: &str, + collection: &str, + ) -> Result { trace!("replace_collection called"); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))?; // No specific headers are required. // Standard headers (auth and version) will be provied by perform_request @@ -330,11 +392,13 @@ impl<'a> Client<'a> { trace!("collection_serialized == {}", collection_serialized); - let mut resp = self.perform_request(uri, - hyper::Method::Put, - Some(&collection_serialized), - ResourceType::Collections, - None)?; + let mut resp = self.perform_request( + uri, + hyper::Method::Put, + Some(&collection_serialized), + ResourceType::Collections, + None, + )?; let body = check_status_extract_body(&mut resp, StatusCode::Created)?; let coll: Collection = serde_json::from_str(&body)?; @@ -342,24 +406,30 @@ impl<'a> Client<'a> { Ok(coll) } - pub fn create_document(&self, - database: &str, - collection: &str, - is_upsert: bool, - indexing_directive: Option, - document: &T) - -> Result - where T: Serialize + pub fn create_document( + &self, + database: &str, + collection: &str, + is_upsert: bool, + indexing_directive: Option, + document: &T, + ) -> Result + where + T: Serialize, { - trace!("create_document called(database == {}, collection == {}, is_upsert == {}", - database, - collection, - is_upsert); + trace!( + "create_document called(database == {}, collection == {}, is_upsert == {}", + database, + collection, + is_upsert + ); - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs/{}/colls/{}/docs", - self.authorization_token.account(), - database, - collection))?; + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls/{}/docs", + self.authorization_token.account(), + database, + collection + ))?; // Standard headers (auth and version) will be provied by perform_request // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document @@ -372,11 +442,13 @@ impl<'a> Client<'a> { let document_serialized = serde_json::to_string(document)?; trace!("document_serialized == {}", document_serialized); - let mut resp = self.perform_request(uri, - hyper::Method::Post, - Some(&document_serialized), - ResourceType::Documents, - Some(headers))?; + let mut resp = self.perform_request( + uri, + hyper::Method::Post, + Some(&document_serialized), + ResourceType::Documents, + Some(headers), + )?; let body = check_status_extract_body(&mut resp, StatusCode::Created)?; let document_attributes: DocumentAttributes = serde_json::from_str(&body)?; @@ -386,26 +458,33 @@ impl<'a> Client<'a> { } -fn generate_authorization(authorization_token: &AuthorizationToken, - http_method: hyper::Method, - resource_type: ResourceType, - resource_link: &str, - time: &str) - -> String { +fn generate_authorization( + authorization_token: &AuthorizationToken, + http_method: hyper::Method, + resource_type: ResourceType, + resource_link: &str, + time: &str, +) -> String { let string_to_sign = string_to_sign(http_method, resource_type, resource_link, time); - trace!("generate_authorization::string_to_sign == {:?}", - string_to_sign); - - let str_unencoded = format!("type={}&ver={}&sig={}", - match authorization_token.token_type() { - TokenType::Master => "master", - TokenType::Resource => "resource", - }, - VERSION, - encode_str_to_sign(&string_to_sign, authorization_token)); - - trace!("generate_authorization::str_unencoded == {:?}", - str_unencoded); + trace!( + "generate_authorization::string_to_sign == {:?}", + string_to_sign + ); + + let str_unencoded = format!( + "type={}&ver={}&sig={}", + match authorization_token.token_type() { + TokenType::Master => "master", + TokenType::Resource => "resource", + }, + VERSION, + encode_str_to_sign(&string_to_sign, authorization_token) + ); + + trace!( + "generate_authorization::str_unencoded == {:?}", + str_unencoded + ); utf8_percent_encode(&str_unencoded, COMPLETE_ENCODE_SET).collect::() } @@ -419,30 +498,33 @@ fn encode_str_to_sign(str_to_sign: &str, authorization_token: &AuthorizationToke -fn string_to_sign(http_method: hyper::Method, - rt: ResourceType, - resource_link: &str, - time: &str) - -> String { +fn string_to_sign( + http_method: hyper::Method, + rt: ResourceType, + resource_link: &str, + time: &str, +) -> String { // From official docs: // StringToSign = Verb.toLowerCase() + "\n" + ResourceType.toLowerCase() + "\n" + ResourceLink + "\n" + Date.toLowerCase() + "\n" + "" + "\n"; // Notice the empty string at the end so we need to add two carriage returns - format!("{}\n{}\n{}\n{}\n\n", - match http_method { - hyper::Method::Get => "get", - hyper::Method::Put => "put", - hyper::Method::Post => "post", - hyper::Method::Delete => "delete", - }, - match rt { - ResourceType::Databases => "dbs", - ResourceType::Collections => "colls", - ResourceType::Documents => "docs", - }, - resource_link, - time.to_lowercase()) + format!( + "{}\n{}\n{}\n{}\n\n", + match http_method { + hyper::Method::Get => "get", + hyper::Method::Put => "put", + hyper::Method::Post => "post", + hyper::Method::Delete => "delete", + }, + match rt { + ResourceType::Databases => "dbs", + ResourceType::Collections => "colls", + ResourceType::Documents => "docs", + }, + resource_link, + time.to_lowercase() + ) } @@ -484,10 +566,12 @@ mod tests { let time = time.with_timezone(&chrono::UTC); let time = format!("{}", time.format(TIME_FORMAT)); - let ret = string_to_sign(HTTPMethod::Get, - ResourceType::Databases, - "dbs/MyDatabase/colls/MyCollection", - &time); + let ret = string_to_sign( + HTTPMethod::Get, + ResourceType::Databases, + "dbs/MyDatabase/colls/MyCollection", + &time, + ); assert_eq!( ret, "get @@ -512,13 +596,17 @@ mon, 01 jan 1900 01:00:00 gmt - let ret = generate_authorization(&authorization_token, - HTTPMethod::Get, - ResourceType::Databases, - "dbs/MyDatabase/colls/MyCollection", - &time); - assert_eq!(ret, - "type%3Dmaster%26ver%3D1.0%26sig%3DQkz%2Fr%2B1N2%2BPEnNijxGbGB%2FADvLsLBQmZ7uBBMuIwf4I%3D"); + let ret = generate_authorization( + &authorization_token, + HTTPMethod::Get, + ResourceType::Databases, + "dbs/MyDatabase/colls/MyCollection", + &time, + ); + assert_eq!( + ret, + "type%3Dmaster%26ver%3D1.0%26sig%3DQkz%2Fr%2B1N2%2BPEnNijxGbGB%2FADvLsLBQmZ7uBBMuIwf4I%3D" + ); } #[test] @@ -528,23 +616,30 @@ mon, 01 jan 1900 01:00:00 gmt let time = time.with_timezone(&chrono::UTC); let time = format!("{}", time.format(TIME_FORMAT)); - let authorization_token = - authorization_token::AuthorizationToken::new("mindflavor", authorization_token::TokenType::Master, - "dsZQi3KtZmCv1ljt3VNWNm7sQUF1y5rJfC6kv5JiwvW0EndXdDku/dkKBp8/ufDToSxL".to_owned()).unwrap(); - - let ret = generate_authorization(&authorization_token, - HTTPMethod::Get, - ResourceType::Databases, - "dbs/ToDoList", - &time); + let authorization_token = authorization_token::AuthorizationToken::new( + "mindflavor", + authorization_token::TokenType::Master, + "dsZQi3KtZmCv1ljt3VNWNm7sQUF1y5rJfC6kv5JiwvW0EndXdDku/dkKBp8/ufDToSxL" + .to_owned(), + ).unwrap(); + + let ret = generate_authorization( + &authorization_token, + HTTPMethod::Get, + ResourceType::Databases, + "dbs/ToDoList", + &time, + ); // This is the result shown in the MSDN page. It's clearly wrong :) // below is the correct one. //assert_eq!(ret, // "type%3dmaster%26ver%3d1.0%26sig%3dc09PEVJrgp2uQRkr934kFbTqhByc7TVr3O"); - assert_eq!(ret, - "type%3Dmaster%26ver%3D1.0%26sig%3DKvBM8vONofkv3yKm%2F8zD9MEGlbu6jjHDJBp4E9c2ZZI%3D"); + assert_eq!( + ret, + "type%3Dmaster%26ver%3D1.0%26sig%3DKvBM8vONofkv3yKm%2F8zD9MEGlbu6jjHDJBp4E9c2ZZI%3D" + ); } #[test] From 601d64a6d07de9cc0d12f4eb349288d2bdc01191 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 15 Jun 2017 22:16:32 +0200 Subject: [PATCH 08/54] futures? :( --- src/azure/core/errors.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 0cb83b5e1..13002c599 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -139,17 +139,16 @@ impl From<()> for AzureError { } #[inline] -pub fn check_status( - resp: hyper::client::FutureResponse, - s: StatusCode, -) -> Result { +pub fn check_status(resp: hyper::client::FutureResponse, + s: StatusCode) + -> Box> { resp.and_then(|res| if res.status() != s { let resp_s = res.body().concat2(); - return Err(AzureError::UnexpectedHTTPResult( - UnexpectedHTTPResult::new(s, res.status(), &resp_s), - )); - }); + return Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, + res.status(), + &resp_s))); + }) // let mut resp_s = String::new(); //resp.read_to_string(&mut resp_s)?; @@ -158,13 +157,12 @@ pub fn check_status( // &resp_s))); // } - Ok(()) + //Ok(()) } -pub fn check_status_extract_body( - resp: &mut hyper::client::response::Response, - s: StatusCode, -) -> Result { +pub fn check_status_extract_body(resp: &mut hyper::client::response::Response, + s: StatusCode) + -> Result { check_status(resp, s)?; From c637a8041d3db1a4d3e6ab483eba054fc27b171e Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 19 Jun 2017 13:26:54 +0200 Subject: [PATCH 09/54] Completed return type map --- Cargo.lock | 40 +++++++++++++++++++------------------- src/azure/core/errors.rs | 42 ++++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 482ec41bd..fd2a2c443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -101,7 +101,7 @@ name = "core-foundation-sys" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -199,7 +199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -207,7 +207,7 @@ name = "iovec" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -242,7 +242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -260,7 +260,7 @@ name = "memchr" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -279,7 +279,7 @@ dependencies = [ "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", @@ -317,7 +317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -359,7 +359,7 @@ name = "num_cpus" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -370,7 +370,7 @@ dependencies = [ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -380,7 +380,7 @@ version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -409,7 +409,7 @@ name = "rand" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -440,7 +440,7 @@ version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", @@ -499,7 +499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -509,7 +509,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -599,7 +599,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -617,7 +617,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -706,7 +706,7 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -795,7 +795,7 @@ dependencies = [ "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" -"checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e" +"checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc" "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" @@ -850,7 +850,7 @@ dependencies = [ "checksum tokio-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "666266622d9a4d1974a0beda33d505999515b0c60edc0c3fda09784e56609a97" "checksum unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e01da42520092d0cd2d6ac3ae69eb21a22ad43ff195676b86f8c37f487d6b80" "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a" -"checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" +"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum url 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a69a2e36a5e5ed3f3063c8c64a3b028c4d50d689fa6c862abd7cfe65f882595c" diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 13002c599..3ede3bd06 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -139,16 +139,33 @@ impl From<()> for AzureError { } #[inline] -pub fn check_status(resp: hyper::client::FutureResponse, - s: StatusCode) - -> Box> { +pub fn check_status( + resp: hyper::client::FutureResponse, + s: StatusCode, +) -> Box> { + Box::new(resp.then(|res| match res { + Ok(res) => { + if res.status() != s { + let resp_s = res.body().concat2(); + //let resp_s = "palazzo!"; - resp.and_then(|res| if res.status() != s { - let resp_s = res.body().concat2(); - return Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, - res.status(), - &resp_s))); - }) + Err(AzureError::UnexpectedHTTPResult( + UnexpectedHTTPResult::new(s, res.status(), &resp_s), + )) + } else { + Ok(()) + } + } + Err(he) => Err(AzureError::HyperError(he)), + })) + + //Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, + // res.status(), + // &resp_s))) + //} else { + // Ok(()) + //})) + // // let mut resp_s = String::new(); //resp.read_to_string(&mut resp_s)?; @@ -160,9 +177,10 @@ pub fn check_status(resp: hyper::client::FutureResponse, //Ok(()) } -pub fn check_status_extract_body(resp: &mut hyper::client::response::Response, - s: StatusCode) - -> Result { +pub fn check_status_extract_body( + resp: &mut hyper::client::response::Response, + s: StatusCode, +) -> Result { check_status(resp, s)?; From f84e2a9d41bd92281f44d415b47c4884f2fc5e8c Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Tue, 20 Jun 2017 11:42:32 +0200 Subject: [PATCH 10/54] Pre migration --- src/azure/core/ba512_range.rs | 6 +-- src/azure/core/errors.rs | 36 +++++++++++++++--- src/azure/core/parsing.rs | 49 +++++++++++++++---------- src/azure/core/range.rs | 6 +-- src/azure/cosmos/authorization_token.rs | 33 +++++++++-------- src/lib.rs | 5 +-- 6 files changed, 85 insertions(+), 50 deletions(-) diff --git a/src/azure/core/ba512_range.rs b/src/azure/core/ba512_range.rs index c2256c219..9342cdb04 100644 --- a/src/azure/core/ba512_range.rs +++ b/src/azure/core/ba512_range.rs @@ -27,9 +27,9 @@ impl BA512Range { } Ok(BA512Range { - start: start, - end: end, - }) + start: start, + end: end, + }) } #[inline] diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 3ede3bd06..67f245d32 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -11,6 +11,7 @@ use azure::core::range::ParseError; use serde_json; use futures::Future; use futures::Stream; +use std::str; #[derive(Debug, Clone, PartialEq)] pub struct UnexpectedHTTPResult { @@ -96,6 +97,11 @@ quick_error! { display("Chrono parser error: {}", err) cause(err) } + UTF8Error (err: str::Utf8Error) { + from() + display("UTF8 conversion error: {}", err) + cause(err) + } } } @@ -129,7 +135,7 @@ quick_error! { from() display("Parsing error") } - } + } } impl From<()> for AzureError { @@ -138,20 +144,38 @@ impl From<()> for AzureError { } } +pub fn extract_body(b: &hyper::Body) -> Box> { + let resp_s = b.concat2().then(|body| match body { + Ok(body) => Ok(str::from_utf8(&body)?.to_owned()), + Err(error) => Err(AzureError::HyperError(error)), + }); + Box::new(resp_s) +} + #[inline] pub fn check_status( resp: hyper::client::FutureResponse, s: StatusCode, ) -> Box> { + use std::str::from_utf8; + Box::new(resp.then(|res| match res { Ok(res) => { if res.status() != s { - let resp_s = res.body().concat2(); - //let resp_s = "palazzo!"; - Err(AzureError::UnexpectedHTTPResult( - UnexpectedHTTPResult::new(s, res.status(), &resp_s), - )) + //let b = res.body(); + //let rb = extract_body(&b); + //res.body().rrr(); + + res.body().concat2().then(|body| match body { + Ok(body) => { + let resp_s = from_utf8(&body)?; + Err(AzureError::UnexpectedHTTPResult( + UnexpectedHTTPResult::new(s, res.status(), &resp_s), + )) + } + Err(error) => Err(AzureError::HyperError(error)), + }) } else { Ok(()) } diff --git a/src/azure/core/parsing.rs b/src/azure/core/parsing.rs index fcf485acd..86a70c403 100644 --- a/src/azure/core/parsing.rs +++ b/src/azure/core/parsing.rs @@ -36,23 +36,29 @@ pub fn from_azure_time(s: &str) -> Result, chrono: } #[inline] -pub fn traverse_single_must<'a>(node: &'a Element, - path: &[&str]) - -> Result<&'a Element, TraversingError> { +pub fn traverse_single_must<'a>( + node: &'a Element, + path: &[&str], +) -> Result<&'a Element, TraversingError> { let vec = try!(traverse(node, path, false)); if vec.len() > 1 { - return Err(TraversingError::MultipleNode(path[path.len() - 1].to_owned())); + return Err(TraversingError::MultipleNode( + path[path.len() - 1].to_owned(), + )); } Ok(vec[0]) } -pub fn traverse_single_optional<'a>(node: &'a Element, - path: &[&str]) - -> Result, TraversingError> { +pub fn traverse_single_optional<'a>( + node: &'a Element, + path: &[&str], +) -> Result, TraversingError> { let vec = try!(traverse(node, path, true)); if vec.len() > 1 { - return Err(TraversingError::MultipleNode(path[path.len() - 1].to_owned())); + return Err(TraversingError::MultipleNode( + path[path.len() - 1].to_owned(), + )); } if vec.is_empty() { @@ -63,10 +69,11 @@ pub fn traverse_single_optional<'a>(node: &'a Element, } #[inline] -pub fn traverse<'a>(node: &'a Element, - path: &[&str], - ignore_empty_leaf: bool) - -> Result, TraversingError> { +pub fn traverse<'a>( + node: &'a Element, + path: &[&str], + ignore_empty_leaf: bool, +) -> Result, TraversingError> { // println!("path.len() == {:?}", path.len()); if path.is_empty() { @@ -108,13 +115,13 @@ pub fn find_subnodes<'a>(node: &'a Element, subnode: &str) -> Vec<&'a Element> { node.children .iter() .filter(|x| match **x { - ElementNode(ref mynode) => mynode.name == subnode, - _ => false, - }) + ElementNode(ref mynode) => mynode.name == subnode, + _ => false, + }) .map(|x| match *x { - ElementNode(ref mynode) => mynode, - _ => unreachable!(), - }) + ElementNode(ref mynode) => mynode, + _ => unreachable!(), + }) .collect::>() } @@ -132,7 +139,8 @@ pub fn inner_text(node: &Element) -> Result<&str, TraversingError> { #[inline] pub fn cast_optional<'a, T>(node: &'a Element, path: &[&str]) -> Result, TraversingError> - where T: FromStringOptional +where + T: FromStringOptional, { match try!(traverse_single_optional(node, path)) { Some(e) => { @@ -147,7 +155,8 @@ pub fn cast_optional<'a, T>(node: &'a Element, path: &[&str]) -> Result(node: &'a Element, path: &[&str]) -> Result - where T: FromStringOptional +where + T: FromStringOptional, { let node = try!(traverse_single_must(node, path)); let itxt = try!(inner_text(node)); diff --git a/src/azure/core/range.rs b/src/azure/core/range.rs index de3bdb398..a8776b70d 100644 --- a/src/azure/core/range.rs +++ b/src/azure/core/range.rs @@ -43,9 +43,9 @@ impl FromStr for Range { let cp_end = try!(v[1].parse::()); Ok(Range { - start: cp_start, - end: cp_end, - }) + start: cp_start, + end: cp_end, + }) } } diff --git a/src/azure/cosmos/authorization_token.rs b/src/azure/cosmos/authorization_token.rs index a96e18264..53b996376 100644 --- a/src/azure/cosmos/authorization_token.rs +++ b/src/azure/cosmos/authorization_token.rs @@ -16,20 +16,21 @@ pub struct AuthorizationToken<'a> { } impl<'a> AuthorizationToken<'a> { - pub fn new(account: &'a str, - token_type: TokenType, - base64_encoded: String) - -> Result, base64::DecodeError> { + pub fn new( + account: &'a str, + token_type: TokenType, + base64_encoded: String, + ) -> Result, base64::DecodeError> { let mut v_hmac_key: Vec = Vec::new(); v_hmac_key.extend(base64::decode(&base64_encoded)?); Ok(AuthorizationToken { - account: account, - token_type: token_type, - base64_encoded: base64_encoded, - binary_form: v_hmac_key, - }) + account: account, + token_type: token_type, + base64_encoded: base64_encoded, + binary_form: v_hmac_key, + }) } pub fn account(&self) -> &str { @@ -62,12 +63,14 @@ impl<'a> Debug for AuthorizationToken<'a> { let so = obfuscated.into_iter().collect::(); - write!(f, - "azure::core::cosmos::AuthorizationToken[account == {}, token_type == {:?}, base64_encoded == {}, binary_form.len() == {}]", - self.account, - self.token_type, - so, - self.binary_form.len()) + write!( + f, + "azure::core::cosmos::AuthorizationToken[account == {}, token_type == {:?}, base64_encoded == {}, binary_form.len() == {}]", + self.account, + self.token_type, + so, + self.binary_form.len() + ) } } diff --git a/src/lib.rs b/src/lib.rs index 3fe2c62c9..90806997d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ -// #![feature(plugin)] -// #![plugin(clippy)] +#![recursion_limit="128"] #[macro_use] extern crate hyper; @@ -8,7 +7,7 @@ extern crate chrono; extern crate futures; extern crate tokio_core; extern crate hyper_tls; -extern crate native_tls; +extern crate native_tls; #[macro_use] extern crate url; From a33ae91133540bc19c9d1da5a71ca1e19a3479bd Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 23 Jun 2017 15:55:39 +0200 Subject: [PATCH 11/54] more an less: --- src/azure/core/errors.rs | 97 ++---- src/azure/cosmos/client.rs | 653 +++++++++++++++++++------------------ src/lib.rs | 5 + 3 files changed, 367 insertions(+), 388 deletions(-) diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 67f245d32..881aac8da 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -12,6 +12,8 @@ use serde_json; use futures::Future; use futures::Stream; use std::str; +use std::str::from_utf8; +use futures::future::*; #[derive(Debug, Clone, PartialEq)] pub struct UnexpectedHTTPResult { @@ -97,7 +99,7 @@ quick_error! { display("Chrono parser error: {}", err) cause(err) } - UTF8Error (err: str::Utf8Error) { + UTF8Error(err: str::Utf8Error) { from() display("UTF8 conversion error: {}", err) cause(err) @@ -144,74 +146,37 @@ impl From<()> for AzureError { } } -pub fn extract_body(b: &hyper::Body) -> Box> { - let resp_s = b.concat2().then(|body| match body { - Ok(body) => Ok(str::from_utf8(&body)?.to_owned()), - Err(error) => Err(AzureError::HyperError(error)), - }); - Box::new(resp_s) -} - #[inline] -pub fn check_status( +pub fn extract_status_and_body( resp: hyper::client::FutureResponse, - s: StatusCode, -) -> Box> { - use std::str::from_utf8; - - Box::new(resp.then(|res| match res { - Ok(res) => { - if res.status() != s { - - //let b = res.body(); - //let rb = extract_body(&b); - //res.body().rrr(); - - res.body().concat2().then(|body| match body { - Ok(body) => { - let resp_s = from_utf8(&body)?; - Err(AzureError::UnexpectedHTTPResult( - UnexpectedHTTPResult::new(s, res.status(), &resp_s), - )) - } - Err(error) => Err(AzureError::HyperError(error)), - }) - } else { - Ok(()) - } - } - Err(he) => Err(AzureError::HyperError(he)), - })) - - //Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, - // res.status(), - // &resp_s))) - //} else { - // Ok(()) - //})) - // - // let mut resp_s = String::new(); - //resp.read_to_string(&mut resp_s)?; - - // return Err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult::new(s, - // resp.status, - // &resp_s))); - // } - - //Ok(()) +) -> impl Future { + resp.from_err().and_then(|res| { + let status = res.status(); + res.body().concat2().from_err().and_then( + move |whole_body| { + match str::from_utf8(&whole_body) { + Ok(s_body) => ok((status, s_body.to_owned())), + Err(error) => err(AzureError::UTF8Error(error)), + } + }, + ) + }) } +#[inline] pub fn check_status_extract_body( - resp: &mut hyper::client::response::Response, - s: StatusCode, -) -> Result { - - check_status(resp, s)?; - - let mut resp_s = String::new(); - resp.read_to_string(&mut resp_s)?; - - debug!("resp_s == {}", resp_s); - - Ok(resp_s) + resp: hyper::client::FutureResponse, + expected_status_code: hyper::StatusCode, +) -> impl Future { + extract_status_and_body(resp).and_then( + move |(status, body)| if status == expected_status_code { + ok(body) + } else { + err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult { + expected: expected_status_code, + received: status, + body: body, + })) + }, + ) } diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 7f14c9a79..5e9871bb8 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -5,7 +5,7 @@ use azure::cosmos::database::Database; use azure::cosmos::collection::Collection; use azure::cosmos::document::{IndexingDirective, DocumentAttributes}; -use azure::core::errors::{AzureError, check_status_extract_body, check_status}; +use azure::core::errors::{AzureError, check_status_extract_body, extract_status_and_body}; use azure::cosmos::request_response::{ListDatabasesResponse, CreateDatabaseRequest, ListCollectionsResponse}; @@ -34,6 +34,8 @@ use tokio_core; use hyper_tls; use native_tls; +use futures::future::{Future, ok, err, done}; + const AZURE_VERSION: &'static str = "2017-02-22"; const VERSION: &'static str = "1.0"; const TIME_FORMAT: &'static str = "%a, %d %h %Y %T GMT"; @@ -86,14 +88,15 @@ impl<'a> Client<'a> { self.authorization_token = at; } - fn perform_request( + #[inline] + fn prepare_request( &self, uri: hyper::Uri, http_method: hyper::Method, request_body: Option<&str>, resource_type: ResourceType, headers: Option, - ) -> Result { + ) -> hyper::client::FutureResponse { let dt = chrono::UTC::now(); let time = format!("{}", dt.format(TIME_FORMAT)); @@ -101,12 +104,12 @@ impl<'a> Client<'a> { let auth = generate_authorization( self.authorization_token, - http_method, + http_method.clone(), resource_type, resource_link, &time, ); - trace!("perform_request::auth == {:?}", auth); + trace!("prepare_request::auth == {:?}", auth); let mut request = hyper::Request::new(http_method, uri); @@ -124,337 +127,337 @@ impl<'a> Client<'a> { ); request.headers_mut().set(Authorization(auth)); - trace!("perform_request::headers == {:?}", request.headers()); + trace!("prepare_request::headers == {:?}", request.headers()); if let Some(body) = request_body { request.headers_mut().set(ContentLength(body.len() as u64)); request.set_body(body.to_string()); } - let future = self.hyper_client.request(request); - - Ok(future) + self.hyper_client.request(request) } - pub fn list_databases(&self) -> Result, AzureError> { + pub fn list_databases(&'a self) -> impl Future, Error = AzureError> { trace!("list_databases called"); - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs", - self.authorization_token.account() - ))?; - - // No specific headers are required, list databases only needs standard headers - // which will be provied by perform_request - let mut resp = self.perform_request( - uri, - hyper::Method::Get, - None, - ResourceType::Databases, - None, - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - let db: ListDatabasesResponse = serde_json::from_str(&body)?; - - Ok(db.databases) - } - - pub fn create_database(&self, database_name: &str) -> Result { - trace!( - "create_databases called (database_name == {})", - database_name - ); - - let uri = hyper::Uri::from_str(&format!( + done(hyper::Uri::from_str(&format!( "https://{}.documents.azure.com/dbs", self.authorization_token.account() - ))?; - - // No specific headers are required, create databases only needs standard headers - // which will be provied by perform_request - // for the body, we will serialize the appropriate structure - - let req = CreateDatabaseRequest { id: database_name }; - let req = serde_json::to_string(&req)?; - - let mut resp = self.perform_request( - uri, - hyper::Method::Post, - Some(&req), - ResourceType::Databases, - None, - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - let db: Database = serde_json::from_str(&body)?; - - Ok(db) - } - - pub fn get_database(&self, database_name: &str) -> Result { - trace!("get_database called (database_name == {})", database_name); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}", - self.authorization_token.account(), - database_name - ))?; - - // No specific headers are required, get database only needs standard headers - // which will be provied by perform_request - let mut resp = self.perform_request( - uri, - hyper::Method::Get, - None, - ResourceType::Databases, - None, - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - let db: Database = serde_json::from_str(&body)?; - - Ok(db) - } - - pub fn delete_database(&self, database_name: &str) -> Result<(), AzureError> { - trace!( - "delete_database called (database_name == {})", - database_name - ); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}", - self.authorization_token.account(), - database_name - ))?; - - // No specific headers are required, delete database only needs standard headers - // which will be provied by perform_request - let future = self.perform_request( - uri, - hyper::Method::Delete, - None, - ResourceType::Databases, - None, - )?; - - check_status(future, StatusCode::NoContent)?; - - Ok(()) - } - - pub fn get_collection( - &self, - database_name: &str, - collection_name: &str, - ) -> Result { - trace!( - "get_collection called (database_name == {}, collection_name == {})", - database_name, - collection_name - ); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls/{}", - self.authorization_token.account(), - database_name, - collection_name - ))?; - - // No specific headers are required, get database only needs standard headers - // which will be provied by perform_request - let mut resp = self.perform_request( - uri, - hyper::Method::Get, - None, - ResourceType::Collections, - None, - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - let coll: Collection = serde_json::from_str(&body)?; - - Ok(coll) - } - - pub fn list_collections(&self, database_name: &str) -> Result, AzureError> { - trace!("list_collections called"); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name - ))?; - - // No specific headers are required, list collections only needs standard headers - // which will be provied by perform_request - let mut resp = self.perform_request( - uri, - hyper::Method::Get, - None, - ResourceType::Collections, - None, - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - let colls: ListCollectionsResponse = serde_json::from_str(&body)?; - - Ok(colls.collections) - } - - pub fn create_collection( - &self, - database_name: &str, - required_throughput: u64, - collection: &Collection, - ) -> Result { - trace!("create_collection called"); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name - ))?; - - // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection - // Standard headers (auth and version) will be provied by perform_request - let mut headers = Headers::new(); - headers.set(OfferThroughput(required_throughput)); - - let collection_serialized = serde_json::to_string(collection)?; - - trace!("collection_serialized == {}", collection_serialized); - - let mut resp = self.perform_request( - uri, - hyper::Method::Post, - Some(&collection_serialized), - ResourceType::Collections, - Some(headers), - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - let coll: Collection = serde_json::from_str(&body)?; - - Ok(coll) - } - - pub fn delete_collection( - &self, - database_name: &str, - collection_name: &str, - ) -> Result<(), AzureError> { - trace!( - "delete_collection called (database_name == {}, collection_name == {}", - database_name, - collection_name - ); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls/{}", - self.authorization_token.account(), - database_name, - collection_name - ))?; - - // No specific headers are required. - // Standard headers (auth and version) will be provied by perform_request - let future = self.perform_request( - uri, - hyper::Method::Delete, - None, - ResourceType::Collections, - None, - )?; - - check_status(future, StatusCode::NoContent)?; - - Ok(()) + ))).from_err() + .and_then(move |uri| { + // No specific headers are required, list databases only needs standard headers + // which will be provied by perform_request + let future_request = self.prepare_request( + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + None, + ); + check_status_extract_body(future_request, StatusCode::Ok).and_then(|body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.databases), + Err(error) => err(error.into()), + } + }) + }) } - pub fn replace_collection( - &self, - database_name: &str, - collection: &str, - ) -> Result { - trace!("replace_collection called"); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name - ))?; - - // No specific headers are required. - // Standard headers (auth and version) will be provied by perform_request - let collection_serialized = serde_json::to_string(collection)?; - - trace!("collection_serialized == {}", collection_serialized); - - let mut resp = self.perform_request( - uri, - hyper::Method::Put, - Some(&collection_serialized), - ResourceType::Collections, - None, - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - let coll: Collection = serde_json::from_str(&body)?; - - Ok(coll) - } - - pub fn create_document( - &self, - database: &str, - collection: &str, - is_upsert: bool, - indexing_directive: Option, - document: &T, - ) -> Result - where - T: Serialize, - { - trace!( - "create_document called(database == {}, collection == {}, is_upsert == {}", - database, - collection, - is_upsert - ); - - let uri = hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls/{}/docs", - self.authorization_token.account(), - database, - collection - ))?; - - // Standard headers (auth and version) will be provied by perform_request - // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document - let mut headers = Headers::new(); - headers.set(DocumentIsUpsert(is_upsert)); - if let Some(id) = indexing_directive { - headers.set(DocumentIndexingDirective(id)); - } - - let document_serialized = serde_json::to_string(document)?; - trace!("document_serialized == {}", document_serialized); - - let mut resp = self.perform_request( - uri, - hyper::Method::Post, - Some(&document_serialized), - ResourceType::Documents, - Some(headers), - )?; - - let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - let document_attributes: DocumentAttributes = serde_json::from_str(&body)?; - - Ok(document_attributes) - } + // pub fn create_database(&self, database_name: &str) -> Result { + // trace!( + // "create_databases called (database_name == {})", + // database_name + // ); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs", + // self.authorization_token.account() + // ))?; + // + // // No specific headers are required, create databases only needs standard headers + // // which will be provied by perform_request + // // for the body, we will serialize the appropriate structure + // + // let req = CreateDatabaseRequest { id: database_name }; + // let req = serde_json::to_string(&req)?; + // + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Post, + // Some(&req), + // ResourceType::Databases, + // None, + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; + // let db: Database = serde_json::from_str(&body)?; + // + // Ok(db) + // } + // + // pub fn get_database(&self, database_name: &str) -> Result { + // trace!("get_database called (database_name == {})", database_name); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}", + // self.authorization_token.account(), + // database_name + // ))?; + // + // // No specific headers are required, get database only needs standard headers + // // which will be provied by perform_request + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Get, + // None, + // ResourceType::Databases, + // None, + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; + // let db: Database = serde_json::from_str(&body)?; + // + // Ok(db) + // } + // + // pub fn delete_database(&self, database_name: &str) -> Result<(), AzureError> { + // trace!( + // "delete_database called (database_name == {})", + // database_name + // ); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}", + // self.authorization_token.account(), + // database_name + // ))?; + // + // // No specific headers are required, delete database only needs standard headers + // // which will be provied by perform_request + // let future = self.perform_request( + // uri, + // hyper::Method::Delete, + // None, + // ResourceType::Databases, + // None, + // )?; + // + // check_status(future, StatusCode::NoContent)?; + // + // Ok(()) + // } + // + // pub fn get_collection( + // &self, + // database_name: &str, + // collection_name: &str, + // ) -> Result { + // trace!( + // "get_collection called (database_name == {}, collection_name == {})", + // database_name, + // collection_name + // ); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls/{}", + // self.authorization_token.account(), + // database_name, + // collection_name + // ))?; + // + // // No specific headers are required, get database only needs standard headers + // // which will be provied by perform_request + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Get, + // None, + // ResourceType::Collections, + // None, + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; + // let coll: Collection = serde_json::from_str(&body)?; + // + // Ok(coll) + // } + // + // pub fn list_collections(&self, database_name: &str) -> Result, AzureError> { + // trace!("list_collections called"); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls", + // self.authorization_token.account(), + // database_name + // ))?; + // + // // No specific headers are required, list collections only needs standard headers + // // which will be provied by perform_request + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Get, + // None, + // ResourceType::Collections, + // None, + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; + // let colls: ListCollectionsResponse = serde_json::from_str(&body)?; + // + // Ok(colls.collections) + // } + // + // pub fn create_collection( + // &self, + // database_name: &str, + // required_throughput: u64, + // collection: &Collection, + // ) -> Result { + // trace!("create_collection called"); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls", + // self.authorization_token.account(), + // database_name + // ))?; + // + // // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection + // // Standard headers (auth and version) will be provied by perform_request + // let mut headers = Headers::new(); + // headers.set(OfferThroughput(required_throughput)); + // + // let collection_serialized = serde_json::to_string(collection)?; + // + // trace!("collection_serialized == {}", collection_serialized); + // + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Post, + // Some(&collection_serialized), + // ResourceType::Collections, + // Some(headers), + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; + // let coll: Collection = serde_json::from_str(&body)?; + // + // Ok(coll) + // } + // + // pub fn delete_collection( + // &self, + // database_name: &str, + // collection_name: &str, + // ) -> Result<(), AzureError> { + // trace!( + // "delete_collection called (database_name == {}, collection_name == {}", + // database_name, + // collection_name + // ); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls/{}", + // self.authorization_token.account(), + // database_name, + // collection_name + // ))?; + // + // // No specific headers are required. + // // Standard headers (auth and version) will be provied by perform_request + // let future = self.perform_request( + // uri, + // hyper::Method::Delete, + // None, + // ResourceType::Collections, + // None, + // )?; + // + // check_status(future, StatusCode::NoContent)?; + // + // Ok(()) + // } + // + // pub fn replace_collection( + // &self, + // database_name: &str, + // collection: &str, + // ) -> Result { + // trace!("replace_collection called"); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls", + // self.authorization_token.account(), + // database_name + // ))?; + // + // // No specific headers are required. + // // Standard headers (auth and version) will be provied by perform_request + // let collection_serialized = serde_json::to_string(collection)?; + // + // trace!("collection_serialized == {}", collection_serialized); + // + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Put, + // Some(&collection_serialized), + // ResourceType::Collections, + // None, + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; + // let coll: Collection = serde_json::from_str(&body)?; + // + // Ok(coll) + // } + // + // pub fn create_document( + // &self, + // database: &str, + // collection: &str, + // is_upsert: bool, + // indexing_directive: Option, + // document: &T, + // ) -> Result + // where + // T: Serialize, + // { + // trace!( + // "create_document called(database == {}, collection == {}, is_upsert == {}", + // database, + // collection, + // is_upsert + // ); + // + // let uri = hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls/{}/docs", + // self.authorization_token.account(), + // database, + // collection + // ))?; + // + // // Standard headers (auth and version) will be provied by perform_request + // // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document + // let mut headers = Headers::new(); + // headers.set(DocumentIsUpsert(is_upsert)); + // if let Some(id) = indexing_directive { + // headers.set(DocumentIndexingDirective(id)); + // } + // + // let document_serialized = serde_json::to_string(document)?; + // trace!("document_serialized == {}", document_serialized); + // + // let mut resp = self.perform_request( + // uri, + // hyper::Method::Post, + // Some(&document_serialized), + // ResourceType::Documents, + // Some(headers), + // )?; + // + // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; + // let document_attributes: DocumentAttributes = serde_json::from_str(&body)?; + // + // Ok(document_attributes) + // } } @@ -516,6 +519,12 @@ fn string_to_sign( hyper::Method::Put => "put", hyper::Method::Post => "post", hyper::Method::Delete => "delete", + hyper::Method::Head => "head", + hyper::Method::Trace => "trace", + hyper::Method::Options => "options", + hyper::Method::Connect => "connect", + hyper::Method::Patch => "patch", + hyper::Method::Extension(_) => "extension", }, match rt { ResourceType::Databases => "dbs", diff --git a/src/lib.rs b/src/lib.rs index 90806997d..0f5926b24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,10 @@ #![recursion_limit="128"] +// this will force us on nightly. The alternative is to +// Box everything which is annoying. This will *eventually* become +// stable so we will support stable too. +#![feature(conservative_impl_trait)] + #[macro_use] extern crate hyper; extern crate chrono; From 1f5831a7e5418136c02525441fd76ed8fe7b7a7a Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 23 Jun 2017 22:49:48 +0200 Subject: [PATCH 12/54] Pre tls --- Cargo.lock | 24 +- Cargo.toml | 2 +- src/azure/cosmos/client.rs | 130 ++++++----- src/main.rs | 454 ++----------------------------------- 4 files changed, 95 insertions(+), 515 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd2a2c443..a481ac9d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = "0.3.1" dependencies = [ "RustyXML 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "chrono" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", @@ -143,7 +143,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -304,7 +304,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -356,7 +356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "num_cpus" -version = "1.5.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -466,7 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "schannel" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -775,7 +775,7 @@ dependencies = [ "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14" "checksum cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c47d456a36ebf0536a6705c83c1cbbcb9255fbc1d905a6ded104f479268a29" -"checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056" +"checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" "checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" @@ -787,7 +787,7 @@ dependencies = [ "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" "checksum hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8590f308416a428dca05ca67020283105344e94059fd2f02cc72e9c913c30fb" -"checksum hyper-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6829541bb9b1716568b3cf353bbcf0566578874b0ffa147922eadc67c7aa3a6" +"checksum hyper-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "faea5efeea2b1cd9596a5d2792ff13de558cc2e578f32310b3ad2e8c35c349f3" "checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37" "checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" @@ -808,7 +808,7 @@ dependencies = [ "checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" "checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" "checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6" -"checksum num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e416ba127a4bb3ff398cb19546a8d0414f73352efe2857f4060d36f5fe5983a" +"checksum num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aec53c34f2d0247c5ca5d32cca1478762f301740468ee9ee6dcb7a0dd7a0c584" "checksum openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11ba043cb65fc9af71a431b8a36ffe8686cd4751cdf70a473ec1d01066ac7e41" "checksum openssl-sys 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "236c718c2e2c2b58a546d86ffea5194400bb15dbe01ca85325ffd357b03cf66c" "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" @@ -823,7 +823,7 @@ dependencies = [ "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" -"checksum schannel 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4e45ac5e9e4698c1c138d2972bedcd90b81fe1efeba805449d2bdd54512de5f9" +"checksum schannel 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "14a5f8491ae5fc8c51aded1f5806282a0218b4d69b1b76913a0559507e559b90" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" "checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" "checksum security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "42ddf098d78d0b64564b23ee6345d07573e7d10e52ad86875d89ddf5f8378a02" diff --git a/Cargo.toml b/Cargo.toml index 69398c2db..80953835c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ name = "main" doc = false [dependencies] -chrono = "*" +chrono = "0.3.0" rust-crypto = "*" quick-error = "*" RustyXML = "*" diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 5e9871bb8..45baadb52 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -5,7 +5,7 @@ use azure::cosmos::database::Database; use azure::cosmos::collection::Collection; use azure::cosmos::document::{IndexingDirective, DocumentAttributes}; -use azure::core::errors::{AzureError, check_status_extract_body, extract_status_and_body}; +use azure::core::errors::{AzureError, check_status_extract_body}; use azure::cosmos::request_response::{ListDatabasesResponse, CreateDatabaseRequest, ListCollectionsResponse}; @@ -13,8 +13,6 @@ use std::str::FromStr; use serde::Serialize; -use std::io::{Read, Cursor}; - use crypto::hmac::Hmac; use crypto::mac::Mac; use crypto::sha2::Sha256; @@ -76,7 +74,7 @@ impl<'a> Client<'a> { .connector(hyper_tls::HttpsConnector::new(4, handle)?) .build(handle); - let client = hyper::Client::new(handle); + //let client = hyper::Client::new(handle); Ok(Client { hyper_client: client, @@ -89,37 +87,44 @@ impl<'a> Client<'a> { } #[inline] - fn prepare_request( + fn prepare_request( &self, uri: hyper::Uri, http_method: hyper::Method, request_body: Option<&str>, resource_type: ResourceType, - headers: Option, - ) -> hyper::client::FutureResponse { + headers_func: F, + ) -> hyper::client::FutureResponse + where + F: FnOnce(&mut Headers), + { let dt = chrono::UTC::now(); let time = format!("{}", dt.format(TIME_FORMAT)); - let resource_link = generate_resource_link(&uri); - - let auth = generate_authorization( - self.authorization_token, - http_method.clone(), - resource_type, - resource_link, - &time, - ); + // we surround this two statements with a scope so the borrow + // on uri owned by generate_resource_link is released + // as soon as generate_authorization ends. This is needed + // because hyper::Request::new takes ownership of uri. And + // the borrow checked won't allow ownership move of a borrowed + // item. This way we save a useless clone. + let auth = { + let resource_link = generate_resource_link(&uri); + + generate_authorization( + self.authorization_token, + http_method.clone(), + resource_type, + resource_link, + &time, + ) + }; trace!("prepare_request::auth == {:?}", auth); - let mut request = hyper::Request::new(http_method, uri); - // we need to add custom headers. If the caller has passed its collection of - // headers we will import them. - if let Some(hs) = headers { - for h in hs.iter() { - request.headers_mut().set_raw(h.name(), h.value_string()); - } - } + // This will give the caller the ability to add custom headers. + // The closure is needed to because request.headers_mut().set_raw(...) requires + // a Cow with 'static lifetime... + headers_func(request.headers_mut()); request.headers_mut().set(XMSDate(time)); request.headers_mut().set( @@ -146,13 +151,14 @@ impl<'a> Client<'a> { ))).from_err() .and_then(move |uri| { // No specific headers are required, list databases only needs standard headers - // which will be provied by perform_request + // which will be provied by perform_request. This is handled by passing an + // empty closure. let future_request = self.prepare_request( uri, hyper::Method::Get, None, ResourceType::Databases, - None, + |_| {}, ); check_status_extract_body(future_request, StatusCode::Ok).and_then(|body| { match serde_json::from_str::(&body) { @@ -306,42 +312,42 @@ impl<'a> Client<'a> { // Ok(colls.collections) // } // - // pub fn create_collection( - // &self, - // database_name: &str, - // required_throughput: u64, - // collection: &Collection, - // ) -> Result { - // trace!("create_collection called"); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls", - // self.authorization_token.account(), - // database_name - // ))?; - // - // // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection - // // Standard headers (auth and version) will be provied by perform_request - // let mut headers = Headers::new(); - // headers.set(OfferThroughput(required_throughput)); - // - // let collection_serialized = serde_json::to_string(collection)?; - // - // trace!("collection_serialized == {}", collection_serialized); - // - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Post, - // Some(&collection_serialized), - // ResourceType::Collections, - // Some(headers), - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - // let coll: Collection = serde_json::from_str(&body)?; - // - // Ok(coll) - // } + pub fn create_collection<'b>( + &'a self, + database_name: &'b str, + required_throughput: u64, + collection: &'b Collection, + ) -> impl Future { + trace!("create_collection called"); + + done(hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))).from_err().and_then(move |uri| { + done(serde_json::to_string(collection)).from_err().and_then(move |collection_serialized| { + trace!("collection_serialized == {}", collection_serialized); + + // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection + // Standard headers (auth and version) will be provied by perform_request + let future_request = self.prepare_request( + uri, + hyper::Method::Post, + Some(&collection_serialized), + ResourceType::Collections, + |hs| { hs.set(OfferThroughput(required_throughput)); } + ); + + check_status_extract_body(future_request, StatusCode::Created).and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r), + Err(error) => err(error.into()), + } + }) + }) + }) + } + // // pub fn delete_collection( // &self, diff --git a/src/main.rs b/src/main.rs index beac0d902..5d8d9adbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,465 +1,39 @@ -// #![feature(plugin)] -// #![plugin(clippy)] +#![recursion_limit="128"] -#![allow(unused_imports)] -#![allow(unreachable_code)] +// this will force us on nightly. The alternative is to +// Box everything which is annoying. This will *eventually* become +// stable so we will support stable too. +#![feature(conservative_impl_trait)] #[macro_use] extern crate hyper; -extern crate hyper_native_tls; extern crate chrono; + +extern crate futures; +extern crate tokio_core; +extern crate hyper_tls; +extern crate native_tls; + #[macro_use] extern crate url; extern crate crypto; extern crate base64; extern crate xml; +extern crate uuid; extern crate time; + #[macro_use] extern crate log; #[macro_use] extern crate quick_error; -extern crate env_logger; #[macro_use] extern crate serde_derive; extern crate serde; extern crate serde_json; -extern crate uuid; - -use azure::core::lease::{LeaseState, LeaseStatus, LeaseAction}; -use azure::storage::client::Client; -use azure::storage::blob::{Blob, BlobType, LIST_BLOB_OPTIONS_DEFAULT, PUT_OPTIONS_DEFAULT, - PUT_BLOCK_OPTIONS_DEFAULT, PUT_PAGE_OPTIONS_DEFAULT, - LEASE_BLOB_OPTIONS_DEFAULT}; -use azure::storage::container::{Container, PublicAccess, LIST_CONTAINER_OPTIONS_DEFAULT}; -use azure::core::ba512_range::BA512Range; - -use std::fs; -use time::Duration; -use chrono::TimeZone; - -use azure::storage::table::TableService; - -// use azure::storage::container::PublicAccess; #[macro_use] pub mod azure; -// use chrono::datetime::DateTime; -use chrono::UTC; - -use hyper::mime::Mime; - -use azure::cosmos::client::ResourceType; -use azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; - -#[allow(unused_variables)] -fn main() { - env_logger::init().unwrap(); - - //let time = chrono::DateTime::parse_from_rfc3339("2017-04-27T00:51:12.000000000+00:00").unwrap(); - - //let time = time.with_timezone(&chrono::UTC); - //println!("{}", time); - - //let time = chrono::DateTime::parse_from_rfc3339("1900-01-01T01:00:00.000000000+00:00").unwrap(); - //let time = format!("{}", time.format("%a, %d %h %Y %T GMT")); - - //let time = time.with_timezone(&chrono::UTC); - //let ret = azure::cosmos::string_to_sign("GET", - // ResourceType::Databases, - // "dbs/MyDatabase/colls/MyCollection", - // &time); - //println!("{}", ret); - - //let time = chrono::UTC::now(); - - //let time = chrono::DateTime::parse_from_rfc3339("1900-01-01T00:00:00.000000000+00:00").unwrap(); - //let time = time.with_timezone(&chrono::UTC); - - //let time = format!("{}", time.format("%a, %d %h %Y %T GMT")); - - //let auth = generate_authorization("8F8xXXOptJxkblM1DBXW7a6NMI5oE8NnwPGYBmwxLCKfejOK7B7yhcCHMGvN3PBrlMLIOeol1Hv9RCdzAZR5sg==", - // "GET", - // TokenType::Master, - // ResourceType::Databases, - // "dbs/MyDatabase/colls/MyCollection", - // &time); - - //println!("auth == {}", auth); - - let master_key = std::env::var("COSMOS_MASTER_KEY") - .expect("Set env variable COSMOS_MASTER_KEY first!"); - let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); - - - let authorization_token = AuthorizationToken::new(&account, TokenType::Master, master_key) - .unwrap(); - - let c = azure::cosmos::client::Client::new(&authorization_token).unwrap(); - - let colls = c.list_collections("test_db").unwrap(); - println!("colls == {:?}", colls); - - // delete mycollection if exists - if let Some(coll_to_del) = colls.iter().find(|ref c| c.id == "mycollection") { - c.delete_collection("test_db", coll_to_del).unwrap(); - println!("collection deleted!"); - } - - let ep = azure::cosmos::collection::ExcludedPath { path: "".to_owned() }; - - let indexes = azure::cosmos::collection::IncludedPathIndex { - kind: azure::cosmos::collection::KeyKind::Hash, - data_type: azure::cosmos::collection::DataType::String, - precision: Some(3), - }; - - let ip = azure::cosmos::collection::IncludedPath { - path: "/*".to_owned(), - indexes: vec![indexes], - }; - - - let ip = azure::cosmos::collection::IndexingPolicy { - automatic: true, - indexing_mode: azure::cosmos::collection::IndexingMode::Consistent, - included_paths: vec![ip], - excluded_paths: vec![], - }; - - - let coll = azure::cosmos::collection::Collection::new("mycollection", ip); - - let created_coll = c.create_collection("test_db", 400, &coll).unwrap(); - println!("collection created == {:?}", created_coll); - - - //// now let's get back the created collection and add an index - //// using replace_collection - //let mut created_coll = c.get_collection("test_db", &created_coll).unwrap(); - //println!("\nretrieved coll = {:?}", created_coll); - //created_coll.indexing_policy.included_paths[0] - // .indexes - // .push(azure::cosmos::collection::IncludedPathIndex { - // data_type: azure::cosmos::collection::DataType::Point, - // precision: Some(-1), - // kind: azure::cosmos::collection::KeyKind::Hash, - // }); - - //c.replace_collection("test_db", &created_coll).unwrap(); - //println!("collection replaced!"); - return; - - let tdb = c.get_database("test_db").unwrap(); - let coll = c.get_collection(&tdb, "test_collection").unwrap(); - println!("coll == {:?}", coll); - - return; - - let new_db = c.create_database("palazzo").unwrap(); - println!("palazzo created"); - - let my_db = c.get_database("palazzo").unwrap(); - println!("{:?}", my_db); - - - let dbs = c.list_databases().unwrap(); - - println!("dbs.len() == {}", dbs.len()); - - c.delete_database("palazzo").unwrap(); - - println!("palazzo deleted"); - - return; - - - - let azure_storage_account = match std::env::var("AZURE_STORAGE_ACCOUNT") { - Ok(val) => val, - _ => { - panic!("Please set AZURE_STORAGE_ACCOUNT env variable first!"); - } - }; - - let azure_storage_key = match std::env::var("AZURE_STORAGE_KEY") { - Ok(val) => val, - _ => { - panic!("Please set AZURE_STORAGE_KEY env variable first!"); - } - }; - - let client = Client::new(&azure_storage_account, &azure_storage_key, true); - - let policy_name = match std::env::var("AZURE_POLICY_NAME") { - Ok(val) => val, - _ => { - panic!("Please set AZURE_POLICY_NAME env variable first!"); - } - }; - - let policy_key = match std::env::var("AZURE_POLICY_KEY") { - Ok(val) => val, - _ => { - panic!("Please set AZURE_POLICY_KEY env variable first!"); - } - }; - - let sb_namespace = match std::env::var("AZURE_SERVICE_BUS_NAMESPACE") { - Ok(val) => val, - _ => { - panic!("Please set AZURE_SERVICE_BUS_NAMESPACE env variable first!"); - } - }; - - let ev_name = match std::env::var("AZURE_EVENT_HUB_NAME") { - Ok(val) => val, - _ => { - panic!("Please set AZURE_EVENT_HUB_NAME env variable first!"); - } - }; - - let table_service = - TableService::new(Client::new(&azure_storage_account, &azure_storage_key, true)); - table_service.create_table("mytable").unwrap(); - - - let mut eh_client = azure::service_bus::event_hub::Client::new(&sb_namespace, - &ev_name, - &policy_name, - &policy_key); - info!("Enumerating containers"); - Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap(); - info!("Enumeration completed"); - - info!("Creating sample container"); - match Container::create(&client, "sample", PublicAccess::Blob) { - Ok(_) => info!("container created"), - Err(ref ae) => error!("error: {}", ae), - }; - - - //client.create_container("balocco3", PublicAccess::Blob).unwrap(); - // println!("{:?}", new); - - - info!("Beginning tests"); - - for i in 0..20 { - info!("Sending message {}", i); - send_event(&mut eh_client); - } - - // lease_blob(&client); - - // put_block_blob(&client); - // - // put_page_blob(&client); - - // { - // let vhds = ret.iter_mut().find(|x| x.name == "canotto").unwrap(); - // - // let blobs = vhds.list_blobs(&client, true, true, true, true).unwrap(); - // - // println!("len == {:?}", blobs.len()); - // - // for blob in &blobs { - // println!("{}, {} KB ({:?})", - // blob.name, - // (blob.content_length / 1024), - // blob.lease_state) - // } - // - // let (blob, mut stream) = vhds.get_blob(&client, "DataCollector01.csv", None, None, None) - // .unwrap(); - // println!("blob == {:?}", blob); - // - // let mut buffer = String::new(); - // stream.read_to_string(&mut buffer).unwrap(); - // - // // println!("buffer == {:?}", buffer); - // } - - - // bal2.delete(&client).unwrap(); - // println!("{:?} deleted!", bal2); - - // let ret = client.delete_container("balocco2").unwrap(); - // println!("{:?}", ret); - // inc_a!("main"); -} - -#[allow(dead_code)] -fn send_event(cli: &mut azure::service_bus::event_hub::Client) { - debug!("running send_event"); - let file_name = "/home/mindflavor/samplein.json"; - - let metadata = fs::metadata(file_name).unwrap(); - let mut file_handle = fs::File::open(file_name).unwrap(); - - cli.send_event(&mut (&mut file_handle, metadata.len()), Duration::hours(1)) - .unwrap(); -} - -#[allow(dead_code)] -fn lease_blob(client: &Client) { - println!("running lease_blob"); - - let ret = Container::list(client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap(); - let vhds = ret.iter().find(|x| x.name == "rust").unwrap(); - let blobs = Blob::list(client, &vhds.name, &LIST_BLOB_OPTIONS_DEFAULT).unwrap(); - let blob = blobs.iter().find(|x| x.name == "go_rust12.txt").unwrap(); - - println!("blob == {:?}", blob); - - let mut lbo = LEASE_BLOB_OPTIONS_DEFAULT.clone(); - lbo.lease_duration = Some(30); - let ret = blob.lease(client, LeaseAction::Acquire, &lbo).unwrap(); - println!("ret == {:?}", ret); - -} - -#[allow(dead_code)] -fn list_blobs(client: &Client) { - println!("running list_blobs"); - - let mut lbo2 = LIST_BLOB_OPTIONS_DEFAULT.clone(); - lbo2.max_results = 15; - - loop { - let uc = Blob::list(client, "rust", &lbo2).unwrap(); - - println!("uc {:?}\n\n", uc); - - if !uc.is_complete() { - lbo2.next_marker = Some(uc.next_marker().unwrap().to_owned()); - } else { - break; - } - } -} - -#[allow(dead_code)] -fn put_block_blob(client: &Client) { - use std::fs::metadata; - use std::fs::File; - - println!("\nrunning put_block_blob"); - - let blob_name: &'static str = "Win64OpenSSL-1_0_2e.exe"; - let file_name: &'static str = "C:\\temp\\Win64OpenSSL-1_0_2e.exe"; - let container_name: &'static str = "rust"; - let metadata = metadata(file_name).unwrap(); - let mut file = File::open(file_name).unwrap(); - - let content_length = metadata.len(); - - { - let containers = Container::list(client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap(); - - let cont = containers.iter().find(|x| x.name == container_name); - if cont.is_none() { - Container::create(client, container_name, PublicAccess::Blob).unwrap(); - } - } - - let new_blob = Blob { - name: blob_name.to_owned(), - container_name: container_name.to_owned(), - snapshot_time: None, - last_modified: UTC::now(), - etag: "".to_owned(), - content_length: content_length, - content_type: "application/octet-stream".parse::().unwrap(), - content_encoding: None, - content_language: None, - content_md5: None, - cache_control: None, - x_ms_blob_sequence_number: None, - blob_type: BlobType::BlockBlob, - lease_status: LeaseStatus::Unlocked, - lease_state: LeaseState::Available, - lease_duration: None, - copy_id: None, - copy_status: None, - copy_source: None, - copy_progress: None, - copy_completion: None, - copy_status_description: None, - }; - - new_blob - .put_block(client, - "block_name", - &PUT_BLOCK_OPTIONS_DEFAULT, - (&mut file, 1024 * 1024)) - .unwrap(); - - println!("created {:?}", new_blob); -} - -#[allow(dead_code)] -fn put_page_blob(client: &Client) { - use std::fs::metadata; - use std::fs::File; - - println!("\nrunning put_page_blob"); - - let blob_name: &'static str = "MindDB_Log.ldf"; - let file_name: &'static str = "C:\\temp\\MindDB_Log.ldf"; - let container_name: &'static str = "rust"; - let metadata = metadata(file_name).unwrap(); - let mut file = File::open(file_name).unwrap(); - - { - let containers = Container::list(client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap(); - - let cont = containers.iter().find(|x| x.name == container_name); - if cont.is_none() { - Container::create(client, container_name, PublicAccess::Blob).unwrap(); - } - } - - // align to 512 bytes - let content_length = metadata.len() % 512 + metadata.len(); - - let new_blob = Blob { - name: blob_name.to_owned(), - container_name: container_name.to_owned(), - snapshot_time: None, - last_modified: UTC::now(), - etag: "".to_owned(), - content_length: content_length, - content_type: "application/octet-stream".parse::().unwrap(), - content_encoding: None, - content_language: None, - content_md5: None, - cache_control: None, - x_ms_blob_sequence_number: None, - blob_type: BlobType::PageBlob, - lease_status: LeaseStatus::Unlocked, - lease_state: LeaseState::Available, - lease_duration: None, - copy_id: None, - copy_status: None, - copy_source: None, - copy_progress: None, - copy_completion: None, - copy_status_description: None, - }; - - new_blob.put(client, &PUT_OPTIONS_DEFAULT, None).unwrap(); - - let range = BA512Range::new(0, 1024 * 1024 - 1).unwrap(); - - new_blob - .put_page(client, - &range, // 1MB - &PUT_PAGE_OPTIONS_DEFAULT, - (&mut file, range.size())) - .unwrap(); - - println!("created {:?}", new_blob); -} +fn main() {} From e79c0c6159461734d5a10abff09a617d43c8316d Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 24 Jun 2017 08:08:02 +0200 Subject: [PATCH 13/54] rustc crash --- Cargo.lock | 10 ++--- examples/collection.rs | 53 +++++++++++++++---------- src/azure/core/errors.rs | 2 - src/azure/core/mod.rs | 8 ---- src/azure/cosmos/client.rs | 80 +++++++++++++++++--------------------- 5 files changed, 74 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a481ac9d7..2ac634e4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -185,7 +185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -683,7 +683,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -802,7 +802,7 @@ dependencies = [ "checksum mime 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c5ca99d8a021c1687882fd68dca26e601ceff5c26571c7cb41cf4ed60d57cb2d" "checksum mio 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9e965267d4d58496fc4f740e9861118367f13570cadf66316ed2c3f2f14d87c7" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e94a2fc65a44729fe969cc973da87c1052ae3f000b2cb33029f14aeb85550d5" +"checksum native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04b781c9134a954c84f0594b9ab3f5606abc516030388e8511887ef4c204a1e5" "checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "checksum num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "2c3a3dc9f30bf824141521b30c908a859ab190b76e20435fcd89f35eb6583887" "checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" diff --git a/examples/collection.rs b/examples/collection.rs index ff834e8fe..a88c9e1c5 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -1,22 +1,31 @@ extern crate azure_sdk_for_rust; +extern crate futures; +extern crate tokio_core; +extern crate tokio; + use std::error::Error; +use futures::future::*; +use tokio_core::reactor::Core; + use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; use azure_sdk_for_rust::azure::cosmos::client::Client; fn main() { - code().unwrap(); + let mut core = Core::new().unwrap(); + + code(&mut core).unwrap(); } // We run a separate method to use the elegant quotation mark operator. // A series of unwrap(), unwrap() would have achieved the same result. -fn code() -> Result<(), Box> { +fn code(core: &mut Core) -> Result<(), Box> { // First we retrieve the account name and master key from environment variables. // We expect master keys (ie, not resource constrained) - let master_key = std::env::var("COSMOS_MASTER_KEY") - .expect("Set env variable COSMOS_MASTER_KEY first!"); + let master_key = + std::env::var("COSMOS_MASTER_KEY").expect("Set env variable COSMOS_MASTER_KEY first!"); let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); // This is how you construct an authorization token. @@ -32,25 +41,29 @@ fn code() -> Result<(), Box> { // Once we have an authorization token you can create a client instance. You can change the // authorization token at later time if you need, for example, to escalate the privileges for a // single operation. - let client = Client::new(&authorization_token)?; + let client = Client::new(&core.handle(), &authorization_token)?; // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified // account. Database do not implement Display but defef to &str so you can pass it to methods // both as struct or id. - let databases = client.list_databases()?; - - println!("Account {} has {} databases", account, databases.len()); - - // Each Cosmos' database contains zero or more collections. We can enumerate them using the - // list_collection method. - for db in &databases { - let collections = client.list_collections(db)?; - println!("*** {} *** ({} collections)", db as &str, collections.len()); - for coll in &collections { - // Collection does not implement Display but Deref to &str so this print works as - // expected. - println!("\t{}", coll as &str); - } - } + let future = client.list_databases().map(|res| { + println!("{:?}", res); + }); + + core.run(future)?; + + //println!("Account {} has {} databases", account, databases.len()); + + //// Each Cosmos' database contains zero or more collections. We can enumerate them using the + //// list_collection method. + //for db in &databases { + // let collections = client.list_collections(db)?; + // println!("*** {} *** ({} collections)", db as &str, collections.len()); + // for coll in &collections { + // // Collection does not implement Display but Deref to &str so this print works as + // // expected. + // println!("\t{}", coll as &str); + // } + //} Ok(()) } diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 881aac8da..9722dd7dc 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -2,7 +2,6 @@ use hyper; use hyper::StatusCode; use chrono; use std::io::Error as IOError; -use std::io::Read; use std::num; use xml::BuilderError as XMLError; use url::ParseError as URLParseError; @@ -12,7 +11,6 @@ use serde_json; use futures::Future; use futures::Stream; use std::str; -use std::str::from_utf8; use futures::future::*; #[derive(Debug, Clone, PartialEq)] diff --git a/src/azure/core/mod.rs b/src/azure/core/mod.rs index 400756a9b..269d7c23f 100644 --- a/src/azure/core/mod.rs +++ b/src/azure/core/mod.rs @@ -12,11 +12,3 @@ pub mod lease; pub mod range; pub mod ba512_range; - -#[derive(Debug, Copy, Clone)] -pub enum HTTPMethod { - Get, - Put, - Post, - Delete, -} diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 45baadb52..b344dcbff 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -1,5 +1,4 @@ use azure::cosmos::authorization_token::{TokenType, AuthorizationToken}; -use azure::core::HTTPMethod; use azure::cosmos::database::Database; use azure::cosmos::collection::Collection; @@ -60,7 +59,7 @@ pub enum ResourceType { } pub struct Client<'a> { - hyper_client: hyper::Client, + hyper_client: hyper::Client>, authorization_token: &'a AuthorizationToken<'a>, } @@ -74,8 +73,6 @@ impl<'a> Client<'a> { .connector(hyper_tls::HttpsConnector::new(4, handle)?) .build(handle); - //let client = hyper::Client::new(handle); - Ok(Client { hyper_client: client, authorization_token: authorization_token, @@ -312,41 +309,41 @@ impl<'a> Client<'a> { // Ok(colls.collections) // } // - pub fn create_collection<'b>( - &'a self, - database_name: &'b str, - required_throughput: u64, - collection: &'b Collection, - ) -> impl Future { - trace!("create_collection called"); - - done(hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name - ))).from_err().and_then(move |uri| { - done(serde_json::to_string(collection)).from_err().and_then(move |collection_serialized| { - trace!("collection_serialized == {}", collection_serialized); - - // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection - // Standard headers (auth and version) will be provied by perform_request - let future_request = self.prepare_request( - uri, - hyper::Method::Post, - Some(&collection_serialized), - ResourceType::Collections, - |hs| { hs.set(OfferThroughput(required_throughput)); } - ); - - check_status_extract_body(future_request, StatusCode::Created).and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r), - Err(error) => err(error.into()), - } - }) - }) - }) - } + //pub fn create_collection<'b>( + // &'a self, + // database_name: &'b str, + // required_throughput: u64, + // collection: &'b Collection, + //) -> impl Future { + // trace!("create_collection called"); + + // done(hyper::Uri::from_str(&format!( + // "https://{}.documents.azure.com/dbs/{}/colls", + // self.authorization_token.account(), + // database_name + // ))).from_err().and_then(move |uri| { + // done(serde_json::to_string(collection)).from_err().and_then(move |collection_serialized| { + // trace!("collection_serialized == {}", collection_serialized); + // + // // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection + // // Standard headers (auth and version) will be provied by perform_request + // let future_request = self.prepare_request( + // uri, + // hyper::Method::Post, + // Some(&collection_serialized), + // ResourceType::Collections, + // |hs| { hs.set(OfferThroughput(required_throughput)); } + // ); + + // check_status_extract_body(future_request, StatusCode::Created).and_then(move |body| { + // match serde_json::from_str::(&body) { + // Ok(r) => ok(r), + // Err(error) => err(error.into()), + // } + // }) + // }) + // }) + //} // // pub fn delete_collection( @@ -466,7 +463,6 @@ impl<'a> Client<'a> { // } } - fn generate_authorization( authorization_token: &AuthorizationToken, http_method: hyper::Method, @@ -505,8 +501,6 @@ fn encode_str_to_sign(str_to_sign: &str, authorization_token: &AuthorizationToke base64::encode(hmac.result().code()) } - - fn string_to_sign( http_method: hyper::Method, rt: ResourceType, @@ -540,8 +534,6 @@ fn string_to_sign( resource_link, time.to_lowercase() ) - - } fn generate_resource_link<'a>(u: &'a hyper::Uri) -> &'a str { From 285b1c6992e49bc27c5ee14551db6c3204e18cb1 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 24 Jun 2017 13:17:25 +0200 Subject: [PATCH 14/54] First compiling version --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/collection.rs | 18 +- src/azure/cosmos/authorization_token.rs | 15 +- src/azure/cosmos/client.rs | 211 ++++++++++++++---------- 5 files changed, 150 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ac634e4b..7595318fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "azure_sdk_for_rust" -version = "0.3.1" +version = "0.4.0" dependencies = [ "RustyXML 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 80953835c..6c18a68e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "azure_sdk_for_rust" -version = "0.3.1" +version = "0.4.0" description = "Rust wrappers around Microsoft Azure REST APIs" readme = "README.md" authors = ["Francesco Cogno ", "Dong Liu "] diff --git a/examples/collection.rs b/examples/collection.rs index a88c9e1c5..02055262c 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -3,6 +3,8 @@ extern crate azure_sdk_for_rust; extern crate futures; extern crate tokio_core; extern crate tokio; +extern crate hyper; +extern crate hyper_tls; use std::error::Error; @@ -10,7 +12,7 @@ use futures::future::*; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; -use azure_sdk_for_rust::azure::cosmos::client::Client; +use azure_sdk_for_rust::azure::cosmos::client::{Client, list_databases}; fn main() { @@ -36,16 +38,26 @@ fn code(core: &mut Core) -> Result<(), Box> { // errors, plus Azure specific ones. For example if a REST call returns the // unexpected result (ie NotFound instead of Ok) we return a Err telling // you that. - let authorization_token = AuthorizationToken::new(&account, TokenType::Master, master_key)?; + let authorization_token = AuthorizationToken::new(account, TokenType::Master, master_key)?; // Once we have an authorization token you can create a client instance. You can change the // authorization token at later time if you need, for example, to escalate the privileges for a // single operation. - let client = Client::new(&core.handle(), &authorization_token)?; + let client = Client::new(&core.handle(), authorization_token)?; // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified // account. Database do not implement Display but defef to &str so you can pass it to methods // both as struct or id. + + //let client = hyper::Client::configure() + // .connector(hyper_tls::HttpsConnector::new(4, &core.handle())?) + // .build(&core.handle()); + + + //let future = list_databases(&client, &authorization_token).map(|res| { + // println!("{:?}", res); + //}); + let future = client.list_databases().map(|res| { println!("{:?}", res); }); diff --git a/src/azure/cosmos/authorization_token.rs b/src/azure/cosmos/authorization_token.rs index 53b996376..aa39896ff 100644 --- a/src/azure/cosmos/authorization_token.rs +++ b/src/azure/cosmos/authorization_token.rs @@ -8,19 +8,20 @@ pub enum TokenType { Resource, } -pub struct AuthorizationToken<'a> { - account: &'a str, +#[derive(Clone)] +pub struct AuthorizationToken { + account: String, token_type: TokenType, base64_encoded: String, binary_form: Vec, } -impl<'a> AuthorizationToken<'a> { +impl AuthorizationToken { pub fn new( - account: &'a str, + account: String, token_type: TokenType, base64_encoded: String, - ) -> Result, base64::DecodeError> { + ) -> Result { let mut v_hmac_key: Vec = Vec::new(); v_hmac_key.extend(base64::decode(&base64_encoded)?); @@ -34,7 +35,7 @@ impl<'a> AuthorizationToken<'a> { } pub fn account(&self) -> &str { - self.account + &self.account } pub fn token_type(&self) -> TokenType { @@ -48,7 +49,7 @@ impl<'a> AuthorizationToken<'a> { } } -impl<'a> Debug for AuthorizationToken<'a> { +impl Debug for AuthorizationToken { //! We provide a custom implementation to hide some of the chars //! since they are security sentive. //! We show only the 6 first chars of ```base64_encoded``` form and only diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index b344dcbff..a5896ca5e 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -58,16 +58,16 @@ pub enum ResourceType { Documents, } -pub struct Client<'a> { +pub struct Client { hyper_client: hyper::Client>, - authorization_token: &'a AuthorizationToken<'a>, + authorization_token: AuthorizationToken, } -impl<'a> Client<'a> { +impl<'a> Client { pub fn new( handle: &tokio_core::reactor::Handle, - authorization_token: &'a AuthorizationToken<'a>, - ) -> Result, native_tls::Error> { + authorization_token: AuthorizationToken, + ) -> Result { let client = hyper::Client::configure() .connector(hyper_tls::HttpsConnector::new(4, handle)?) @@ -79,93 +79,29 @@ impl<'a> Client<'a> { }) } - pub fn set_authorization_token(&mut self, at: &'a AuthorizationToken<'a>) { + pub fn set_authorization_token(&mut self, at: AuthorizationToken) { self.authorization_token = at; } - #[inline] - fn prepare_request( - &self, - uri: hyper::Uri, - http_method: hyper::Method, - request_body: Option<&str>, - resource_type: ResourceType, - headers_func: F, - ) -> hyper::client::FutureResponse - where - F: FnOnce(&mut Headers), - { - let dt = chrono::UTC::now(); - let time = format!("{}", dt.format(TIME_FORMAT)); - - // we surround this two statements with a scope so the borrow - // on uri owned by generate_resource_link is released - // as soon as generate_authorization ends. This is needed - // because hyper::Request::new takes ownership of uri. And - // the borrow checked won't allow ownership move of a borrowed - // item. This way we save a useless clone. - let auth = { - let resource_link = generate_resource_link(&uri); - - generate_authorization( - self.authorization_token, - http_method.clone(), - resource_type, - resource_link, - &time, - ) - }; - trace!("prepare_request::auth == {:?}", auth); - let mut request = hyper::Request::new(http_method, uri); - - // This will give the caller the ability to add custom headers. - // The closure is needed to because request.headers_mut().set_raw(...) requires - // a Cow with 'static lifetime... - headers_func(request.headers_mut()); - - request.headers_mut().set(XMSDate(time)); - request.headers_mut().set( - XMSVersion(AZURE_VERSION.to_owned()), - ); - request.headers_mut().set(Authorization(auth)); - - trace!("prepare_request::headers == {:?}", request.headers()); - - if let Some(body) = request_body { - request.headers_mut().set(ContentLength(body.len() as u64)); - request.set_body(body.to_string()); - } - - self.hyper_client.request(request) - } - - pub fn list_databases(&'a self) -> impl Future, Error = AzureError> { + pub fn list_databases(&self) -> Box, Error = AzureError>> { trace!("list_databases called"); - done(hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs", - self.authorization_token.account() - ))).from_err() - .and_then(move |uri| { - // No specific headers are required, list databases only needs standard headers - // which will be provied by perform_request. This is handled by passing an - // empty closure. - let future_request = self.prepare_request( - uri, - hyper::Method::Get, - None, - ResourceType::Databases, - |_| {}, - ); - check_status_extract_body(future_request, StatusCode::Ok).and_then(|body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.databases), - Err(error) => err(error.into()), - } - }) - }) + Box::new( + done(prepare_list_database_request( + &self.hyper_client, + &self.authorization_token, + )).from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok) + .and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.databases), + Err(error) => err(error.into()), + } + }) + }), + ) } - // pub fn create_database(&self, database_name: &str) -> Result { // trace!( // "create_databases called (database_name == {})", @@ -463,6 +399,109 @@ impl<'a> Client<'a> { // } } +#[inline] +fn prepare_list_database_request( + hc: &hyper::Client>, + at: &AuthorizationToken, +) -> Result { + let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs", at.account()))?; + + // No specific headers are required, list databases only needs standard headers + // which will be provied by perform_request. This is handled by passing an + // empty closure. + let request = prepare_request( + at, + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + |_| {}, + ); + + Ok(hc.request(request)) +} + +pub fn list_databases<'a>( + hc: &'a hyper::Client>, + at: &'a AuthorizationToken, +) -> Box, Error = AzureError>> { + trace!("list_databases called"); + + Box::new( + done(prepare_list_database_request(hc, at)) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.databases), + Err(error) => err(error.into()), + } + }) + }), + ) +} + +#[inline] +fn prepare_request( + authorization_token: &AuthorizationToken, + uri: hyper::Uri, + http_method: hyper::Method, + request_body: Option<&str>, + resource_type: ResourceType, + headers_func: F, +) -> hyper::client::Request +where + F: FnOnce(&mut Headers), +{ + let dt = chrono::UTC::now(); + let time = format!("{}", dt.format(TIME_FORMAT)); + + // we surround this two statements with a scope so the borrow + // on uri owned by generate_resource_link is released + // as soon as generate_authorization ends. This is needed + // because hyper::Request::new takes ownership of uri. And + // the borrow checked won't allow ownership move of a borrowed + // item. This way we save a useless clone. + let auth = { + let resource_link = generate_resource_link(&uri); + + generate_authorization( + authorization_token, + http_method.clone(), + resource_type, + resource_link, + &time, + ) + }; + trace!("prepare_request::auth == {:?}", auth); + let mut request = hyper::Request::new(http_method, uri); + + // This will give the caller the ability to add custom headers. + // The closure is needed to because request.headers_mut().set_raw(...) requires + // a Cow with 'static lifetime... + headers_func(request.headers_mut()); + + request.headers_mut().set(XMSDate(time)); + request.headers_mut().set( + XMSVersion(AZURE_VERSION.to_owned()), + ); + request.headers_mut().set(Authorization(auth)); + + trace!("prepare_request::headers == {:?}", request.headers()); + + if let Some(body) = request_body { + request.headers_mut().set(ContentLength(body.len() as u64)); + request.set_body(body.to_string()); + } + + request + + //self.hyper_client.request(request) +} + + + + fn generate_authorization( authorization_token: &AuthorizationToken, http_method: hyper::Method, From 6842c265de01ed2c2b6250873e296a25a47a0113 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 24 Jun 2017 15:39:13 +0200 Subject: [PATCH 15/54] Removed boxes --- examples/collection.rs | 19 ++++--------------- src/azure/cosmos/client.rs | 29 +++++++++++++---------------- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/examples/collection.rs b/examples/collection.rs index 02055262c..f82963a59 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -38,7 +38,8 @@ fn code(core: &mut Core) -> Result<(), Box> { // errors, plus Azure specific ones. For example if a REST call returns the // unexpected result (ie NotFound instead of Ok) we return a Err telling // you that. - let authorization_token = AuthorizationToken::new(account, TokenType::Master, master_key)?; + let authorization_token = + AuthorizationToken::new(account.clone(), TokenType::Master, master_key)?; // Once we have an authorization token you can create a client instance. You can change the // authorization token at later time if you need, for example, to escalate the privileges for a @@ -48,24 +49,12 @@ fn code(core: &mut Core) -> Result<(), Box> { // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified // account. Database do not implement Display but defef to &str so you can pass it to methods // both as struct or id. - - //let client = hyper::Client::configure() - // .connector(hyper_tls::HttpsConnector::new(4, &core.handle())?) - // .build(&core.handle()); - - - //let future = list_databases(&client, &authorization_token).map(|res| { - // println!("{:?}", res); - //}); - - let future = client.list_databases().map(|res| { - println!("{:?}", res); + let future = client.list_databases().map(move |databases| { + println!("Account {} has {} databases", account, databases.len()); }); core.run(future)?; - //println!("Account {} has {} databases", account, databases.len()); - //// Each Cosmos' database contains zero or more collections. We can enumerate them using the //// list_collection method. //for db in &databases { diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index a5896ca5e..84f1a9b88 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -83,24 +83,21 @@ impl<'a> Client { self.authorization_token = at; } - pub fn list_databases(&self) -> Box, Error = AzureError>> { + pub fn list_databases(&self) -> impl Future, Error = AzureError> { trace!("list_databases called"); - Box::new( - done(prepare_list_database_request( - &self.hyper_client, - &self.authorization_token, - )).from_err() - .and_then(move |future_response| { - check_status_extract_body(future_response, StatusCode::Ok) - .and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.databases), - Err(error) => err(error.into()), - } - }) - }), - ) + done(prepare_list_database_request( + &self.hyper_client, + &self.authorization_token, + )).from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.databases), + Err(error) => err(error.into()), + } + }) + }) } // pub fn create_database(&self, database_name: &str) -> Result { // trace!( From 0218bf7f94d469d97ec9819bf806a5ecdeda1d95 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 24 Jun 2017 16:08:05 +0200 Subject: [PATCH 16/54] Completed first example --- examples/collection.rs | 19 +++++++++--- src/azure/cosmos/client.rs | 63 +++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/examples/collection.rs b/examples/collection.rs index f82963a59..35fdc0003 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -12,24 +12,33 @@ use futures::future::*; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; -use azure_sdk_for_rust::azure::cosmos::client::{Client, list_databases}; +use azure_sdk_for_rust::azure::cosmos::client::Client; fn main() { - let mut core = Core::new().unwrap(); - - code(&mut core).unwrap(); + code().unwrap(); } + // We run a separate method to use the elegant quotation mark operator. // A series of unwrap(), unwrap() would have achieved the same result. -fn code(core: &mut Core) -> Result<(), Box> { +fn code() -> Result<(), Box> { // First we retrieve the account name and master key from environment variables. // We expect master keys (ie, not resource constrained) let master_key = std::env::var("COSMOS_MASTER_KEY").expect("Set env variable COSMOS_MASTER_KEY first!"); let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); + // let's create a tokio-core reactor. + // It will drive our request. Remember, util run, futures do absolutely + // nothing. So, run them. Also note that, in order to avoid cloning the authorization_token at + // each request this library constructs the request **before** the future. This means the date + // sent to the server will be the one at Future creation time, not the execution time. + // Azure calls will block requests with time too much in the past (in order to prevent reply + // attacks) so make sure to execute the Future as soon as possibile after having it created. + // * This is something worth discussing * + let mut core = Core::new()?; + // This is how you construct an authorization token. // Remeber to pick the correct token type. // Here we assume master. diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 84f1a9b88..145fc1fc8 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -86,19 +86,40 @@ impl<'a> Client { pub fn list_databases(&self) -> impl Future, Error = AzureError> { trace!("list_databases called"); - done(prepare_list_database_request( - &self.hyper_client, - &self.authorization_token, - )).from_err() - .and_then(move |future_response| { - check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.databases), - Err(error) => err(error.into()), - } - }) + let req: Result = { + match hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs", + &self.authorization_token.account() + )) { + Ok(uri) => { + // No specific headers are required, list databases only needs standard headers + // which will be provied by perform_request. This is handled by passing an + // empty closure. + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + |_| {}, + ); + + Ok(self.hyper_client.request(request)) + } + Err(error) => Err(error.into()), + } + }; + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.databases), + Err(error) => err(error.into()), + } }) + }) } + // pub fn create_database(&self, database_name: &str) -> Result { // trace!( // "create_databases called (database_name == {})", @@ -418,26 +439,6 @@ fn prepare_list_database_request( Ok(hc.request(request)) } -pub fn list_databases<'a>( - hc: &'a hyper::Client>, - at: &'a AuthorizationToken, -) -> Box, Error = AzureError>> { - trace!("list_databases called"); - - Box::new( - done(prepare_list_database_request(hc, at)) - .from_err() - .and_then(move |future_response| { - check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.databases), - Err(error) => err(error.into()), - } - }) - }), - ) -} - #[inline] fn prepare_request( authorization_token: &AuthorizationToken, From ad857e6d5e84d3562717762cd0ded5c14575b64f Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 24 Jun 2017 22:14:46 +0200 Subject: [PATCH 17/54] Choices --- examples/collection.rs | 18 +++++++++++++- src/azure/cosmos/client.rs | 51 +++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/examples/collection.rs b/examples/collection.rs index 35fdc0003..7078f9b27 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -12,7 +12,7 @@ use futures::future::*; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; -use azure_sdk_for_rust::azure::cosmos::client::Client; +use azure_sdk_for_rust::azure::cosmos::client::{Client, list_databases}; fn main() { @@ -39,6 +39,8 @@ fn code() -> Result<(), Box> { // * This is something worth discussing * let mut core = Core::new()?; + + // This is how you construct an authorization token. // Remeber to pick the correct token type. // Here we assume master. @@ -50,6 +52,16 @@ fn code() -> Result<(), Box> { let authorization_token = AuthorizationToken::new(account.clone(), TokenType::Master, master_key)?; + let hyper_client = hyper::Client::configure() + .connector(hyper_tls::HttpsConnector::new(4, &core.handle())?) + .build(&core.handle()); + + //println!("before list_databases"); + //let future = list_databases(&authorization_token, &hyper_client).map(move |databases| { + // println!("Account {} has {} databases", account, databases.len()); + //}); + //println!("after list_databases"); + // Once we have an authorization token you can create a client instance. You can change the // authorization token at later time if you need, for example, to escalate the privileges for a // single operation. @@ -58,11 +70,15 @@ fn code() -> Result<(), Box> { // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified // account. Database do not implement Display but defef to &str so you can pass it to methods // both as struct or id. + println!("before client.list_databases"); let future = client.list_databases().map(move |databases| { println!("Account {} has {} databases", account, databases.len()); }); + println!("after client.list_databases"); + println!("before core.run"); core.run(future)?; + println!("after core.run"); //// Each Cosmos' database contains zero or more collections. We can enumerate them using the //// list_collection method. diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 145fc1fc8..91624c50c 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -104,6 +104,8 @@ impl<'a> Client { |_| {}, ); + println!("request prepared"); + Ok(self.hyper_client.request(request)) } Err(error) => Err(error.into()), @@ -417,6 +419,51 @@ impl<'a> Client { // } } + +pub fn list_databases<'a>( + authorization_token: &'a AuthorizationToken, + hyper_client: &'a hyper::Client>, +) -> Box, Error = AzureError>> { + trace!("list_databases called"); + + let authorization_token = authorization_token.clone(); + let hyper_client = hyper_client.clone(); + + Box::new( + done(hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs", + authorization_token.account() + ))).and_then(move |uri| { + // No specific headers are required, list databases only needs standard headers + // which will be provied by perform_request. This is handled by passing an + // empty closure. + let request = prepare_request( + &authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + |_| {}, + ); + + println!("request prepared"); + + ok(hyper_client.request(request)) + }) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.databases), + Err(error) => err(error.into()), + } + }) + }), + ) +} + + + #[inline] fn prepare_list_database_request( hc: &hyper::Client>, @@ -493,13 +540,9 @@ where } request - - //self.hyper_client.request(request) } - - fn generate_authorization( authorization_token: &AuthorizationToken, http_method: hyper::Method, From e33fc200f3b7ae47e5605a09dc9a13ef0259af64 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 24 Jun 2017 23:08:25 +0200 Subject: [PATCH 18/54] Trying to figure out how to execute for_each on items --- examples/collection.rs | 26 +++---- src/azure/cosmos/client.rs | 139 +++++++++++++------------------------ 2 files changed, 60 insertions(+), 105 deletions(-) diff --git a/examples/collection.rs b/examples/collection.rs index 7078f9b27..d4e215901 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -12,7 +12,7 @@ use futures::future::*; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; -use azure_sdk_for_rust::azure::cosmos::client::{Client, list_databases}; +use azure_sdk_for_rust::azure::cosmos::client::Client; fn main() { @@ -40,7 +40,6 @@ fn code() -> Result<(), Box> { let mut core = Core::new()?; - // This is how you construct an authorization token. // Remeber to pick the correct token type. // Here we assume master. @@ -56,12 +55,6 @@ fn code() -> Result<(), Box> { .connector(hyper_tls::HttpsConnector::new(4, &core.handle())?) .build(&core.handle()); - //println!("before list_databases"); - //let future = list_databases(&authorization_token, &hyper_client).map(move |databases| { - // println!("Account {} has {} databases", account, databases.len()); - //}); - //println!("after list_databases"); - // Once we have an authorization token you can create a client instance. You can change the // authorization token at later time if you need, for example, to escalate the privileges for a // single operation. @@ -71,19 +64,26 @@ fn code() -> Result<(), Box> { // account. Database do not implement Display but defef to &str so you can pass it to methods // both as struct or id. println!("before client.list_databases"); - let future = client.list_databases().map(move |databases| { - println!("Account {} has {} databases", account, databases.len()); - }); + let future = client + .list_databases() + .and_then(move |databases| { + println!("Account {} has {} databases", account, databases.len()); + ok(databases) + }) + .for_each(|db| println!("ddd")) + .map(move |collections| { + println!("Database xxx has {} collections", collections.len()); + }); println!("after client.list_databases"); println!("before core.run"); core.run(future)?; println!("after core.run"); - //// Each Cosmos' database contains zero or more collections. We can enumerate them using the + //// Each Cosmos' database contains zo or more collections. We can enumerate them using the //// list_collection method. //for db in &databases { - // let collections = client.list_collections(db)?; + // let collections = client.list_clections(db)?; // println!("*** {} *** ({} collections)", db as &str, collections.len()); // for coll in &collections { // // Collection does not implement Display but Deref to &str so this print works as diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 91624c50c..3ce818fbc 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -104,7 +104,7 @@ impl<'a> Client { |_| {}, ); - println!("request prepared"); + trace!("request prepared"); Ok(self.hyper_client.request(request)) } @@ -122,6 +122,52 @@ impl<'a> Client { }) } + pub fn list_collections( + &self, + database_name: &str, + ) -> impl Future, Error = AzureError> { + trace!("list_collections called"); + + let req: Result = { + match hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + )) { + Ok(uri) => { + // No specific headers are required, list collections only needs standard headers + // which will be provied by perform_request. This is handled by passing an + // empty closure. + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Collections, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + Err(error) => Err(error.into()), + } + }; + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + match serde_json::from_str::(&body) { + Ok(r) => ok(r.collections), + Err(error) => err(error.into()), + } + }) + }) + } + + + + // pub fn create_database(&self, database_name: &str) -> Result { // trace!( // "create_databases called (database_name == {})", @@ -240,30 +286,6 @@ impl<'a> Client { // Ok(coll) // } // - // pub fn list_collections(&self, database_name: &str) -> Result, AzureError> { - // trace!("list_collections called"); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls", - // self.authorization_token.account(), - // database_name - // ))?; - // - // // No specific headers are required, list collections only needs standard headers - // // which will be provied by perform_request - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Get, - // None, - // ResourceType::Collections, - // None, - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - // let colls: ListCollectionsResponse = serde_json::from_str(&body)?; - // - // Ok(colls.collections) - // } // //pub fn create_collection<'b>( // &'a self, @@ -419,73 +441,6 @@ impl<'a> Client { // } } - -pub fn list_databases<'a>( - authorization_token: &'a AuthorizationToken, - hyper_client: &'a hyper::Client>, -) -> Box, Error = AzureError>> { - trace!("list_databases called"); - - let authorization_token = authorization_token.clone(); - let hyper_client = hyper_client.clone(); - - Box::new( - done(hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs", - authorization_token.account() - ))).and_then(move |uri| { - // No specific headers are required, list databases only needs standard headers - // which will be provied by perform_request. This is handled by passing an - // empty closure. - let request = prepare_request( - &authorization_token, - uri, - hyper::Method::Get, - None, - ResourceType::Databases, - |_| {}, - ); - - println!("request prepared"); - - ok(hyper_client.request(request)) - }) - .from_err() - .and_then(move |future_response| { - check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.databases), - Err(error) => err(error.into()), - } - }) - }), - ) -} - - - -#[inline] -fn prepare_list_database_request( - hc: &hyper::Client>, - at: &AuthorizationToken, -) -> Result { - let uri = hyper::Uri::from_str(&format!("https://{}.documents.azure.com/dbs", at.account()))?; - - // No specific headers are required, list databases only needs standard headers - // which will be provied by perform_request. This is handled by passing an - // empty closure. - let request = prepare_request( - at, - uri, - hyper::Method::Get, - None, - ResourceType::Databases, - |_| {}, - ); - - Ok(hc.request(request)) -} - #[inline] fn prepare_request( authorization_token: &AuthorizationToken, From 3336f167c2b28442617336e30cb7e1dad9105d02 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sun, 25 Jun 2017 16:31:25 +0200 Subject: [PATCH 19/54] First example completed --- Cargo.lock | 12 ++++----- examples/collection.rs | 57 +++++++++++++++++++----------------------- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7595318fa..4f2f9e23a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -189,7 +189,7 @@ dependencies = [ "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -679,7 +679,7 @@ dependencies = [ [[package]] name = "tokio-tls" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -724,7 +724,7 @@ dependencies = [ [[package]] name = "url" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -847,13 +847,13 @@ dependencies = [ "checksum tokio-io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c2c3ce9739f7387a0fa65b5421e81feae92e04d603f008898f4257790ce8c2db" "checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" -"checksum tokio-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "666266622d9a4d1974a0beda33d505999515b0c60edc0c3fda09784e56609a97" +"checksum tokio-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d88e411cac1c87e405e4090be004493c5d8072a370661033b1a64ea205ec2e13" "checksum unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e01da42520092d0cd2d6ac3ae69eb21a22ad43ff195676b86f8c37f487d6b80" "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" -"checksum url 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a69a2e36a5e5ed3f3063c8c64a3b028c4d50d689fa6c862abd7cfe65f882595c" +"checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d0f5103675a280a926ec2f9b7bcc2ef49367df54e8c570c3311fec919f9a8b" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/examples/collection.rs b/examples/collection.rs index d4e215901..f26705e3a 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -9,6 +9,7 @@ extern crate hyper_tls; use std::error::Error; use futures::future::*; +use futures::Stream; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; @@ -30,21 +31,21 @@ fn code() -> Result<(), Box> { let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); // let's create a tokio-core reactor. - // It will drive our request. Remember, util run, futures do absolutely + // It will drive our request. Remember, until run, futures do absolutely // nothing. So, run them. Also note that, in order to avoid cloning the authorization_token at // each request this library constructs the request **before** the future. This means the date // sent to the server will be the one at Future creation time, not the execution time. // Azure calls will block requests with time too much in the past (in order to prevent reply - // attacks) so make sure to execute the Future as soon as possibile after having it created. + // attacks) so make sure to execute the Future as soon as possible after having it created. // * This is something worth discussing * let mut core = Core::new()?; // This is how you construct an authorization token. - // Remeber to pick the correct token type. + // Remember to pick the correct token type. // Here we assume master. // Most methods return a ```Result<_, AzureError>```. - // ```AzureError``` is an enum union of all the possibile undelying + // ```AzureError``` is an enum union of all the possible underlying // errors, plus Azure specific ones. For example if a REST call returns the // unexpected result (ie NotFound instead of Ok) we return a Err telling // you that. @@ -61,35 +62,29 @@ fn code() -> Result<(), Box> { let client = Client::new(&core.handle(), authorization_token)?; // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified - // account. Database do not implement Display but defef to &str so you can pass it to methods + // account. Database do not implement Display but deref to &str so you can pass it to methods // both as struct or id. println!("before client.list_databases"); - let future = client - .list_databases() - .and_then(move |databases| { - println!("Account {} has {} databases", account, databases.len()); - ok(databases) - }) - .for_each(|db| println!("ddd")) - .map(move |collections| { - println!("Database xxx has {} collections", collections.len()); - }); - println!("after client.list_databases"); - - println!("before core.run"); + let future = client.list_databases().and_then(move |databases| { + println!("Account {} has {} database(s)", account, databases.len()); + + let mut v = Vec::new(); + + // Each Cosmos' database contains so or more collections. We can enumerate them using the + // list_collection method. + for db in databases { + v.push(client.list_collections(&db).map(move |collections| { + println!("database {} has {} collection(s)", db.id, collections.len()); + + for collection in collections { + println!("\tcollection {}", collection.id); + } + })); + } + + futures::future::join_all(v) + }); + core.run(future)?; - println!("after core.run"); - - //// Each Cosmos' database contains zo or more collections. We can enumerate them using the - //// list_collection method. - //for db in &databases { - // let collections = client.list_clections(db)?; - // println!("*** {} *** ({} collections)", db as &str, collections.len()); - // for coll in &collections { - // // Collection does not implement Display but Deref to &str so this print works as - // // expected. - // println!("\t{}", coll as &str); - // } - //} Ok(()) } From 5edada61e2a2e296b7b2a39dbbfb1fde10bd7fb3 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sun, 25 Jun 2017 22:58:13 +0200 Subject: [PATCH 20/54] Added other methods --- examples/collection.rs | 8 +- examples/create_database.rs | 67 ++++++++++ src/azure/cosmos/client.rs | 258 ++++++++++++++++++++---------------- 3 files changed, 211 insertions(+), 122 deletions(-) create mode 100644 examples/create_database.rs diff --git a/examples/collection.rs b/examples/collection.rs index f26705e3a..3b1f0b99f 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -9,7 +9,6 @@ extern crate hyper_tls; use std::error::Error; use futures::future::*; -use futures::Stream; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; @@ -47,15 +46,11 @@ fn code() -> Result<(), Box> { // Most methods return a ```Result<_, AzureError>```. // ```AzureError``` is an enum union of all the possible underlying // errors, plus Azure specific ones. For example if a REST call returns the - // unexpected result (ie NotFound instead of Ok) we return a Err telling + // unexpected result (ie NotFound instead of Ok) we return an Err telling // you that. let authorization_token = AuthorizationToken::new(account.clone(), TokenType::Master, master_key)?; - let hyper_client = hyper::Client::configure() - .connector(hyper_tls::HttpsConnector::new(4, &core.handle())?) - .build(&core.handle()); - // Once we have an authorization token you can create a client instance. You can change the // authorization token at later time if you need, for example, to escalate the privileges for a // single operation. @@ -64,7 +59,6 @@ fn code() -> Result<(), Box> { // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified // account. Database do not implement Display but deref to &str so you can pass it to methods // both as struct or id. - println!("before client.list_databases"); let future = client.list_databases().and_then(move |databases| { println!("Account {} has {} database(s)", account, databases.len()); diff --git a/examples/create_database.rs b/examples/create_database.rs new file mode 100644 index 000000000..a3a2f0144 --- /dev/null +++ b/examples/create_database.rs @@ -0,0 +1,67 @@ +extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; + +use std::error::Error; + +use futures::future::*; +use tokio_core::reactor::Core; + +use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; +use azure_sdk_for_rust::azure::cosmos::client::Client; + + +fn main() { + code().unwrap(); +} + + +// We run a separate method to use the elegant quotation mark operator. +// A series of unwrap(), unwrap() would have achieved the same result. +fn code() -> Result<(), Box> { + // First we retrieve the account name and master key from environment variables. + // We expect master keys (ie, not resource constrained) + let master_key = + std::env::var("COSMOS_MASTER_KEY").expect("Set env variable COSMOS_MASTER_KEY first!"); + let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); + + // let's create a tokio-core reactor. + // It will drive our request. Remember, until run, futures do absolutely + // nothing. So, run them. Also note that, in order to avoid cloning the authorization_token at + // each request this library constructs the request **before** the future. This means the date + // sent to the server will be the one at Future creation time, not the execution time. + // Azure calls will block requests with time too much in the past (in order to prevent reply + // attacks) so make sure to execute the Future as soon as possible after having it created. + // * This is something worth discussing * + let mut core = Core::new()?; + + // This is how you construct an authorization token. + // Remember to pick the correct token type. + // Here we assume master. + // Most methods return a ```Result<_, AzureError>```. + // ```AzureError``` is an enum union of all the possible underlying + // errors, plus Azure specific ones. For example if a REST call returns the + // unexpected result (ie NotFound instead of Ok) we return an Err telling + // you that. + let authorization_token = + AuthorizationToken::new(account.clone(), TokenType::Master, master_key)?; + + // Once we have an authorization token you can create a client instance. You can change the + // authorization token at later time if you need, for example, to escalate the privileges for a + // single operation. + let client = Client::new(&core.handle(), authorization_token)?; + + // The Cosmos' client exposes a lot of methods. This one lists the databases in the specified + // account. Database do not implement Display but deref to &str so you can pass it to methods + // both as struct or id. + + let future = client.create_database("something").map(|db| { + println!("created database = {:?}", db); + }); + core.run(future)?; + Ok(()) +} diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 3ce818fbc..916e76fa0 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -83,147 +83,175 @@ impl<'a> Client { self.authorization_token = at; } + fn list_databases_create_request(&self) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs", + &self.authorization_token.account() + ))?; + + // No specific headers are required, list databases only needs standard headers + // which will be provied by perform_request. This is handled by passing an + // empty closure. + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + pub fn list_databases(&self) -> impl Future, Error = AzureError> { trace!("list_databases called"); - let req: Result = { - match hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs", - &self.authorization_token.account() - )) { - Ok(uri) => { - // No specific headers are required, list databases only needs standard headers - // which will be provied by perform_request. This is handled by passing an - // empty closure. - let request = prepare_request( - &self.authorization_token, - uri, - hyper::Method::Get, - None, - ResourceType::Databases, - |_| {}, - ); - - trace!("request prepared"); - - Ok(self.hyper_client.request(request)) - } - Err(error) => Err(error.into()), - } - }; + let req = self.list_databases_create_request(); done(req).from_err().and_then(move |future_response| { check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.databases), - Err(error) => err(error.into()), - } + done(serde_json::from_str::(&body)) + .from_err() + .and_then(move |response| ok(response.databases)) }) }) } + #[inline] + fn list_collections_create_request( + &self, + database_name: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))?; + + // No specific headers are required, list collections only needs standard headers + // which will be provied by perform_request. This is handled by passing an + // empty closure. + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Collections, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + pub fn list_collections( &self, database_name: &str, ) -> impl Future, Error = AzureError> { trace!("list_collections called"); - let req: Result = { - match hyper::Uri::from_str(&format!( - "https://{}.documents.azure.com/dbs/{}/colls", - self.authorization_token.account(), - database_name - )) { - Ok(uri) => { - // No specific headers are required, list collections only needs standard headers - // which will be provied by perform_request. This is handled by passing an - // empty closure. - let request = prepare_request( - &self.authorization_token, - uri, - hyper::Method::Get, - None, - ResourceType::Collections, - |_| {}, - ); - - trace!("request prepared"); - - Ok(self.hyper_client.request(request)) - } - Err(error) => Err(error.into()), - } - }; + let req = self.list_collections_create_request(database_name); done(req).from_err().and_then(move |future_response| { check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { - match serde_json::from_str::(&body) { - Ok(r) => ok(r.collections), - Err(error) => err(error.into()), - } + done(serde_json::from_str::(&body)) + .from_err() + .and_then(|database_response| ok(database_response.collections)) }) }) } + #[inline] + fn create_database_create_request( + &self, + database_name: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs", + self.authorization_token.account() + ))?; + + let req = CreateDatabaseRequest { id: database_name }; + let req = serde_json::to_string(&req)?; + + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Post, + Some(&req), + ResourceType::Databases, + |_| {}, + ); + trace!("request prepared"); + Ok(self.hyper_client.request(request)) + } + + pub fn create_database( + &self, + database_name: &str, + ) -> impl Future { + trace!( + "create_databases called (database_name == {})", + database_name + ); + + let req = self.create_database_create_request(database_name); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(move |body| { + done(serde_json::from_str::(&body)).from_err() + }) + }) + } + + #[inline] + fn get_database_create_request( + &self, + database_name: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}", + self.authorization_token.account(), + database_name + ))?; + + // No specific headers are required, get database only needs standard headers + // which will be provied by perform_request + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Databases, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn get_database( + &self, + database_name: &str, + ) -> impl Future { + trace!("get_database called (database_name == {})", database_name); + + let req = self.get_database_create_request(database_name); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + done(serde_json::from_str::(&body)).from_err() + }) + }) + } - // pub fn create_database(&self, database_name: &str) -> Result { - // trace!( - // "create_databases called (database_name == {})", - // database_name - // ); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs", - // self.authorization_token.account() - // ))?; - // - // // No specific headers are required, create databases only needs standard headers - // // which will be provied by perform_request - // // for the body, we will serialize the appropriate structure - // - // let req = CreateDatabaseRequest { id: database_name }; - // let req = serde_json::to_string(&req)?; - // - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Post, - // Some(&req), - // ResourceType::Databases, - // None, - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - // let db: Database = serde_json::from_str(&body)?; - // - // Ok(db) - // } - // - // pub fn get_database(&self, database_name: &str) -> Result { - // trace!("get_database called (database_name == {})", database_name); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}", - // self.authorization_token.account(), - // database_name - // ))?; - // - // // No specific headers are required, get database only needs standard headers - // // which will be provied by perform_request - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Get, - // None, - // ResourceType::Databases, - // None, - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - // let db: Database = serde_json::from_str(&body)?; - // - // Ok(db) - // } // // pub fn delete_database(&self, database_name: &str) -> Result<(), AzureError> { // trace!( From 303089c718e550e728b60707f7d80ff13c614ed2 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sun, 25 Jun 2017 23:23:10 +0200 Subject: [PATCH 21/54] Added delete database --- ..._database.rs => create_delete_database.rs} | 12 ++- src/azure/core/errors.rs | 8 +- src/azure/core/lease.rs | 28 ++++--- src/azure/cosmos/client.rs | 80 +++++++++++-------- 4 files changed, 75 insertions(+), 53 deletions(-) rename examples/{create_database.rs => create_delete_database.rs} (90%) diff --git a/examples/create_database.rs b/examples/create_delete_database.rs similarity index 90% rename from examples/create_database.rs rename to examples/create_delete_database.rs index a3a2f0144..4f4f3c495 100644 --- a/examples/create_database.rs +++ b/examples/create_delete_database.rs @@ -59,9 +59,15 @@ fn code() -> Result<(), Box> { // account. Database do not implement Display but deref to &str so you can pass it to methods // both as struct or id. - let future = client.create_database("something").map(|db| { - println!("created database = {:?}", db); - }); + let future = client + .create_database("something") + .and_then(|db| { + println!("created database = {:?}", db); + client.delete_database("something") + }) + .map(|_| { + println!("database deleted"); + }); core.run(future)?; Ok(()) } diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 9722dd7dc..57860f850 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -151,11 +151,9 @@ pub fn extract_status_and_body( resp.from_err().and_then(|res| { let status = res.status(); res.body().concat2().from_err().and_then( - move |whole_body| { - match str::from_utf8(&whole_body) { - Ok(s_body) => ok((status, s_body.to_owned())), - Err(error) => err(AzureError::UTF8Error(error)), - } + move |whole_body| match str::from_utf8(&whole_body) { + Ok(s_body) => ok((status, s_body.to_owned())), + Err(error) => err(AzureError::UTF8Error(error)), }, ) }) diff --git a/src/azure/core/lease.rs b/src/azure/core/lease.rs index 6a2455ad5..5c26408b7 100644 --- a/src/azure/core/lease.rs +++ b/src/azure/core/lease.rs @@ -7,20 +7,24 @@ use uuid::Uuid; create_enum!(LeaseStatus, (Locked, "locked"), (Unlocked, "unlocked")); -create_enum!(LeaseState, - (Available, "available"), - (Leased, "leased"), - (Expired, "expired"), - (Breaking, "breaking"), - (Broken, "broken")); +create_enum!( + LeaseState, + (Available, "available"), + (Leased, "leased"), + (Expired, "expired"), + (Breaking, "breaking"), + (Broken, "broken") +); create_enum!(LeaseDuration, (Infinite, "infinite"), (Fixed, "fixed")); -create_enum!(LeaseAction, - (Acquire, "acquire"), - (Renew, "renew "), - (Change, "change"), - (Release, "release "), - (Break, "break")); +create_enum!( + LeaseAction, + (Acquire, "acquire"), + (Renew, "renew "), + (Change, "change"), + (Release, "release "), + (Break, "break") +); pub type LeaseId = Uuid; diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 916e76fa0..00b6e77f2 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -31,7 +31,7 @@ use tokio_core; use hyper_tls; use native_tls; -use futures::future::{Future, ok, err, done}; +use futures::future::{Future, ok, done}; const AZURE_VERSION: &'static str = "2017-02-22"; const VERSION: &'static str = "1.0"; @@ -252,33 +252,48 @@ impl<'a> Client { }) } - // - // pub fn delete_database(&self, database_name: &str) -> Result<(), AzureError> { - // trace!( - // "delete_database called (database_name == {})", - // database_name - // ); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}", - // self.authorization_token.account(), - // database_name - // ))?; - // - // // No specific headers are required, delete database only needs standard headers - // // which will be provied by perform_request - // let future = self.perform_request( - // uri, - // hyper::Method::Delete, - // None, - // ResourceType::Databases, - // None, - // )?; - // - // check_status(future, StatusCode::NoContent)?; - // - // Ok(()) - // } + #[inline] + fn delete_database_create_request( + &self, + database_name: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}", + self.authorization_token.account(), + database_name + ))?; + + // No specific headers are required, delete database only needs standard headers + // which will be provied by perform_request + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Delete, + None, + ResourceType::Databases, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn delete_database( + &self, + database_name: &str, + ) -> impl Future { + trace!( + "delete_database called (database_name == {})", + database_name + ); + + let req = self.delete_database_create_request(database_name); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::NoContent).and_then(|_| ok(())) + }) + } // // pub fn get_collection( // &self, @@ -510,9 +525,9 @@ where headers_func(request.headers_mut()); request.headers_mut().set(XMSDate(time)); - request.headers_mut().set( - XMSVersion(AZURE_VERSION.to_owned()), - ); + request + .headers_mut() + .set(XMSVersion(AZURE_VERSION.to_owned())); request.headers_mut().set(Authorization(auth)); trace!("prepare_request::headers == {:?}", request.headers()); @@ -689,8 +704,7 @@ mon, 01 jan 1900 01:00:00 gmt let authorization_token = authorization_token::AuthorizationToken::new( "mindflavor", authorization_token::TokenType::Master, - "dsZQi3KtZmCv1ljt3VNWNm7sQUF1y5rJfC6kv5JiwvW0EndXdDku/dkKBp8/ufDToSxL" - .to_owned(), + "dsZQi3KtZmCv1ljt3VNWNm7sQUF1y5rJfC6kv5JiwvW0EndXdDku/dkKBp8/ufDToSxL".to_owned(), ).unwrap(); let ret = generate_authorization( From 1db8a4af8602d078a3eb609c25f26fe3e8075ae6 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 12:10:39 +0200 Subject: [PATCH 22/54] Missing only create_document --- src/azure/cosmos/client.rs | 339 ++++++++++++++++++++++--------------- 1 file changed, 201 insertions(+), 138 deletions(-) diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 00b6e77f2..5d9ad6143 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -294,144 +294,207 @@ impl<'a> Client { check_status_extract_body(future_response, StatusCode::NoContent).and_then(|_| ok(())) }) } - // - // pub fn get_collection( - // &self, - // database_name: &str, - // collection_name: &str, - // ) -> Result { - // trace!( - // "get_collection called (database_name == {}, collection_name == {})", - // database_name, - // collection_name - // ); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls/{}", - // self.authorization_token.account(), - // database_name, - // collection_name - // ))?; - // - // // No specific headers are required, get database only needs standard headers - // // which will be provied by perform_request - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Get, - // None, - // ResourceType::Collections, - // None, - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Ok)?; - // let coll: Collection = serde_json::from_str(&body)?; - // - // Ok(coll) - // } - // - // - //pub fn create_collection<'b>( - // &'a self, - // database_name: &'b str, - // required_throughput: u64, - // collection: &'b Collection, - //) -> impl Future { - // trace!("create_collection called"); - - // done(hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls", - // self.authorization_token.account(), - // database_name - // ))).from_err().and_then(move |uri| { - // done(serde_json::to_string(collection)).from_err().and_then(move |collection_serialized| { - // trace!("collection_serialized == {}", collection_serialized); - // - // // Headers added as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection - // // Standard headers (auth and version) will be provied by perform_request - // let future_request = self.prepare_request( - // uri, - // hyper::Method::Post, - // Some(&collection_serialized), - // ResourceType::Collections, - // |hs| { hs.set(OfferThroughput(required_throughput)); } - // ); - - // check_status_extract_body(future_request, StatusCode::Created).and_then(move |body| { - // match serde_json::from_str::(&body) { - // Ok(r) => ok(r), - // Err(error) => err(error.into()), - // } - // }) - // }) - // }) - //} - // - // pub fn delete_collection( - // &self, - // database_name: &str, - // collection_name: &str, - // ) -> Result<(), AzureError> { - // trace!( - // "delete_collection called (database_name == {}, collection_name == {}", - // database_name, - // collection_name - // ); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls/{}", - // self.authorization_token.account(), - // database_name, - // collection_name - // ))?; - // - // // No specific headers are required. - // // Standard headers (auth and version) will be provied by perform_request - // let future = self.perform_request( - // uri, - // hyper::Method::Delete, - // None, - // ResourceType::Collections, - // None, - // )?; - // - // check_status(future, StatusCode::NoContent)?; - // - // Ok(()) - // } - // - // pub fn replace_collection( - // &self, - // database_name: &str, - // collection: &str, - // ) -> Result { - // trace!("replace_collection called"); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls", - // self.authorization_token.account(), - // database_name - // ))?; - // - // // No specific headers are required. - // // Standard headers (auth and version) will be provied by perform_request - // let collection_serialized = serde_json::to_string(collection)?; - // - // trace!("collection_serialized == {}", collection_serialized); - // - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Put, - // Some(&collection_serialized), - // ResourceType::Collections, - // None, - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - // let coll: Collection = serde_json::from_str(&body)?; - // - // Ok(coll) - // } + #[inline] + fn get_collection_create_request( + &self, + database_name: &str, + collection_name: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls/{}", + self.authorization_token.account(), + database_name, + collection_name + ))?; + + // No specific headers are required, get database only needs standard headers + // which will be provied by perform_request + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Get, + None, + ResourceType::Collections, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn get_collection( + &self, + database_name: &str, + collection_name: &str, + ) -> impl Future { + trace!( + "get_collection called (database_name == {}, collection_name == {})", + database_name, + collection_name + ); + + let req = self.get_collection_create_request(database_name, collection_name); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + done(serde_json::from_str::(&body)).from_err() + }) + }) + } + + #[inline] + fn create_collection_create_request( + &self, + database_name: &str, + required_throughput: u64, + collection: &Collection, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))?; + + // Headers added as per + // https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-collection + // Standard headers (auth and version) will be provied by perform_request + let collection_serialized = serde_json::to_string(collection)?; + trace!("collection_serialized == {}", collection_serialized); + + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Post, + Some(&collection_serialized), + ResourceType::Collections, + |ref mut headers| { + headers.set(OfferThroughput(required_throughput)); + } + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn create_collection( + &self, + database_name: &str, + required_throughput: u64, + collection: &Collection, + ) -> impl Future { + trace!( + "create_collection(database_name == {:?}, required_throughput == {:?}, collection == {:?} called", + database_name, + required_throughput, + collection + ); + + let req = + self.create_collection_create_request(database_name, required_throughput, collection); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(move |body| { + done(serde_json::from_str::(&body)).from_err() + }) + }) + } + + #[inline] + fn delete_collection_create_request( + &self, + database_name: &str, + collection_name: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls/{}", + self.authorization_token.account(), + database_name, + collection_name + ))?; + + // No specific headers are required. + // Standard headers (auth and version) will be provied by perform_request + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Delete, + None, + ResourceType::Collections, + |_| {} + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn delete_collection( + &self, + database_name: &str, + collection_name: &str, + ) -> impl Future { + trace!( + "delete_collection called (database_name == {}, collection_name == {}", + database_name, + collection_name + ); + + let req = self.delete_collection_create_request(database_name, collection_name); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::NoContent).and_then(|_| ok(())) + }) + } + + #[inline] + fn replace_collection_prepare_request( + &self, + database_name: &str, + collection: &str, + ) -> Result { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls", + self.authorization_token.account(), + database_name + ))?; + + // No specific headers are required. + // Standard headers (auth and version) will be provied by perform_request + let collection_serialized = serde_json::to_string(collection)?; + trace!("collection_serialized == {}", collection_serialized); + + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Put, + Some(&collection_serialized), + ResourceType::Collections, + |_| {}, + ); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn replace_collection( + &self, + database_name: &str, + collection: &str, + ) -> impl Future { + trace!("replace_collection called"); + + let req = self.replace_collection_prepare_request(database_name, collection); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(move |body| { + done(serde_json::from_str::(&body)).from_err() + }) + }) + } // // pub fn create_document( // &self, @@ -604,7 +667,7 @@ fn string_to_sign( hyper::Method::Patch => "patch", hyper::Method::Extension(_) => "extension", }, - match rt { + match rt { ResourceType::Databases => "dbs", ResourceType::Collections => "colls", ResourceType::Documents => "docs", From 0d3e24f75f29dc3f4fbbe265bc98192bc0b74edc Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 16:17:15 +0200 Subject: [PATCH 23/54] Completed document example --- examples/document0.rs | 60 ++++++++++------- src/azure/cosmos/client.rs | 130 +++++++++++++++++++++++-------------- 2 files changed, 116 insertions(+), 74 deletions(-) diff --git a/examples/document0.rs b/examples/document0.rs index 44ede04f0..d3df6d2af 100644 --- a/examples/document0.rs +++ b/examples/document0.rs @@ -1,18 +1,25 @@ extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; extern crate chrono; -extern crate serde; -extern crate serde_json; -#[macro_use] -extern crate serde_derive; use std::error::Error; +use futures::future::*; +use tokio_core::reactor::Core; + use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; use azure_sdk_for_rust::azure::cosmos::client::Client; +#[macro_use] +extern crate serde_derive; use azure_sdk_for_rust::azure::cosmos; -//use chrono::{DateTime, UTC}; +use chrono::{DateTime, UTC}; //use serde::Serialize; @@ -37,25 +44,27 @@ fn code() -> Result<(), Box> { let master_key = std::env::var("COSMOS_MASTER_KEY") .expect("Set env variable COSMOS_MASTER_KEY first!"); let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); - let authorization_token = AuthorizationToken::new(&account, TokenType::Master, master_key)?; - let client = Client::new(&authorization_token)?; + let authorization_token = AuthorizationToken::new(account, TokenType::Master, master_key)?; - let database = { - let dbs = client.list_databases()?; - if let Some(db) = dbs.into_iter().find(|db| db.id == DATABASE) { - db - } else { - client.create_database(DATABASE)? - } + let mut core = Core::new()?; + let client = Client::new(&core.handle(), authorization_token)?; + + let future = client.list_databases().and_then(|databases| { + ok(databases.into_iter().find(|db| db.id == DATABASE)) + }); + + let database = match core.run(future)? { + Some(db) => db, + None => core.run(client.create_database(DATABASE))?, }; println!("database == {:?}", database); - let coll = { - let colls = client.list_collections(&database)?; - if let Some(coll) = colls.into_iter().find(|coll| coll.id == COLLECTION) { - coll - } else { + let collection = { + let collections = core.run(client.list_collections(&database))?; + if let Some(collection) = collections.into_iter().find(|coll| coll.id == COLLECTION) { + collection + } else { let indexes = cosmos::collection::IncludedPathIndex { kind: cosmos::collection::KeyKind::Hash, data_type: cosmos::collection::DataType::String, @@ -76,10 +85,11 @@ fn code() -> Result<(), Box> { }; let coll = cosmos::collection::Collection::new(COLLECTION, ip); - client.create_collection(DATABASE, 400, &coll)? + core.run(client.create_collection(&database, 400, &coll))? } }; - println!("collection == {:?}", coll); + + println!("collection = {:?}", collection); let doc = MySampleStruct { id: "unique_id1", @@ -88,12 +98,14 @@ fn code() -> Result<(), Box> { a_timestamp: chrono::UTC::now().timestamp(), }; - let document_attributes = client.create_document(&database, &coll, false, None, &doc)?; + let document_attributes = core.run( + client.create_document(&database, &collection, false, None, &doc), + )?; println!("document_attributes == {:?}", document_attributes); - client.delete_collection(DATABASE, COLLECTION)?; + core.run(client.delete_collection(DATABASE, COLLECTION))?; println!("collection deleted"); - client.delete_database(DATABASE)?; + core.run(client.delete_database(DATABASE))?; println!("database deleted"); Ok(()) diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 5d9ad6143..9e0a17ae2 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -495,56 +495,86 @@ impl<'a> Client { }) }) } - // - // pub fn create_document( - // &self, - // database: &str, - // collection: &str, - // is_upsert: bool, - // indexing_directive: Option, - // document: &T, - // ) -> Result - // where - // T: Serialize, - // { - // trace!( - // "create_document called(database == {}, collection == {}, is_upsert == {}", - // database, - // collection, - // is_upsert - // ); - // - // let uri = hyper::Uri::from_str(&format!( - // "https://{}.documents.azure.com/dbs/{}/colls/{}/docs", - // self.authorization_token.account(), - // database, - // collection - // ))?; - // - // // Standard headers (auth and version) will be provied by perform_request - // // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document - // let mut headers = Headers::new(); - // headers.set(DocumentIsUpsert(is_upsert)); - // if let Some(id) = indexing_directive { - // headers.set(DocumentIndexingDirective(id)); - // } - // - // let document_serialized = serde_json::to_string(document)?; - // trace!("document_serialized == {}", document_serialized); - // - // let mut resp = self.perform_request( - // uri, - // hyper::Method::Post, - // Some(&document_serialized), - // ResourceType::Documents, - // Some(headers), - // )?; - // - // let body = check_status_extract_body(&mut resp, StatusCode::Created)?; - // let document_attributes: DocumentAttributes = serde_json::from_str(&body)?; - // - // Ok(document_attributes) - // } + + #[inline] + fn create_document_create_request( + &self, + database: &str, + collection: &str, + is_upsert: bool, + indexing_directive: Option, + document: &T, + ) -> Result + where + T: Serialize, + { + let uri = hyper::Uri::from_str(&format!( + "https://{}.documents.azure.com/dbs/{}/colls/{}/docs", + self.authorization_token.account(), + database, + collection + ))?; + + // Standard headers (auth and version) will be provied by perform_request + // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document + let mut headers = Headers::new(); + headers.set(DocumentIsUpsert(is_upsert)); + if let Some(id) = indexing_directive { + headers.set(DocumentIndexingDirective(id)); + } + + let document_serialized = serde_json::to_string(document)?; + trace!("document_serialized == {}", document_serialized); + + let request = prepare_request( + &self.authorization_token, + uri, + hyper::Method::Post, + Some(&document_serialized), + ResourceType::Documents, + |ref mut headers| { + if let Some(id) = indexing_directive { + headers.set(DocumentIndexingDirective(id)); + } + }); + + trace!("request prepared"); + + Ok(self.hyper_client.request(request)) + } + + pub fn create_document( + &self, + database: &str, + collection: &str, + is_upsert: bool, + indexing_directive: Option, + document: &T, + ) -> impl Future + where + T: Serialize, + { + trace!( + "create_document called(database == {}, collection == {}, is_upsert == {}", + database, + collection, + is_upsert + ); + + let req = self.create_document_create_request( + database, + collection, + is_upsert, + indexing_directive, + document, + ); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(move |body| { + done(serde_json::from_str::(&body)).from_err() + }) + }) + } } #[inline] From fbbcb61dee2c73609d09bf087f08a0adb319de70 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 19:21:17 +0200 Subject: [PATCH 24/54] Corrected tests: --- Cargo.toml | 2 +- examples/collection.rs | 4 +- examples/create_delete_database.rs | 4 +- examples/document0.rs | 4 -- src/azure/cosmos/authorization_token.rs | 4 +- src/azure/cosmos/client.rs | 43 ++++++++----- tests/blob.rs | 9 +-- tests/event_hub.rs | 14 ++--- tests/table.rs | 84 ++++++++++++++----------- 9 files changed, 93 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c18a68e8..091bb9a1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,4 +38,4 @@ hyper-tls = "*" native-tls = "*" [features] -test_e2e = [] +test_e2e = [] diff --git a/examples/collection.rs b/examples/collection.rs index 3b1f0b99f..00138651b 100644 --- a/examples/collection.rs +++ b/examples/collection.rs @@ -25,8 +25,8 @@ fn main() { fn code() -> Result<(), Box> { // First we retrieve the account name and master key from environment variables. // We expect master keys (ie, not resource constrained) - let master_key = - std::env::var("COSMOS_MASTER_KEY").expect("Set env variable COSMOS_MASTER_KEY first!"); + let master_key = std::env::var("COSMOS_MASTER_KEY") + .expect("Set env variable COSMOS_MASTER_KEY first!"); let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); // let's create a tokio-core reactor. diff --git a/examples/create_delete_database.rs b/examples/create_delete_database.rs index 4f4f3c495..6cfad5ccf 100644 --- a/examples/create_delete_database.rs +++ b/examples/create_delete_database.rs @@ -25,8 +25,8 @@ fn main() { fn code() -> Result<(), Box> { // First we retrieve the account name and master key from environment variables. // We expect master keys (ie, not resource constrained) - let master_key = - std::env::var("COSMOS_MASTER_KEY").expect("Set env variable COSMOS_MASTER_KEY first!"); + let master_key = std::env::var("COSMOS_MASTER_KEY") + .expect("Set env variable COSMOS_MASTER_KEY first!"); let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); // let's create a tokio-core reactor. diff --git a/examples/document0.rs b/examples/document0.rs index d3df6d2af..e394136be 100644 --- a/examples/document0.rs +++ b/examples/document0.rs @@ -19,10 +19,6 @@ use azure_sdk_for_rust::azure::cosmos::client::Client; extern crate serde_derive; use azure_sdk_for_rust::azure::cosmos; -use chrono::{DateTime, UTC}; - -//use serde::Serialize; - #[derive(Serialize, Deserialize, Debug)] struct MySampleStruct<'a> { id: &'a str, diff --git a/src/azure/cosmos/authorization_token.rs b/src/azure/cosmos/authorization_token.rs index aa39896ff..fe0b3f1e9 100644 --- a/src/azure/cosmos/authorization_token.rs +++ b/src/azure/cosmos/authorization_token.rs @@ -66,7 +66,9 @@ impl Debug for AuthorizationToken { write!( f, - "azure::core::cosmos::AuthorizationToken[account == {}, token_type == {:?}, base64_encoded == {}, binary_form.len() == {}]", + "azure::core::cosmos::AuthorizationToken[\ + account == {}, token_type == {:?}, \ + base64_encoded == {}, binary_form.len() == {}]", self.account, self.token_type, so, diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 9e0a17ae2..7d5c1aaff 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -386,7 +386,8 @@ impl<'a> Client { collection: &Collection, ) -> impl Future { trace!( - "create_collection(database_name == {:?}, required_throughput == {:?}, collection == {:?} called", + "create_collection(database_name == {:?}, \ + required_throughput == {:?}, collection == {:?} called", database_name, required_throughput, collection @@ -516,7 +517,8 @@ impl<'a> Client { ))?; // Standard headers (auth and version) will be provied by perform_request - // Optional headers as per https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document + // Optional headers as per + // https://docs.microsoft.com/en-us/rest/api/documentdb/create-a-document let mut headers = Headers::new(); headers.set(DocumentIsUpsert(is_upsert)); if let Some(id) = indexing_directive { @@ -532,7 +534,7 @@ impl<'a> Client { hyper::Method::Post, Some(&document_serialized), ResourceType::Documents, - |ref mut headers| { + |ref mut headers| { if let Some(id) = indexing_directive { headers.set(DocumentIndexingDirective(id)); } @@ -680,7 +682,12 @@ fn string_to_sign( ) -> String { // From official docs: - // StringToSign = Verb.toLowerCase() + "\n" + ResourceType.toLowerCase() + "\n" + ResourceLink + "\n" + Date.toLowerCase() + "\n" + "" + "\n"; + // StringToSign = + // Verb.toLowerCase() + "\n" + + // ResourceType.toLowerCase() + "\n" + + // ResourceLink + "\n" + + // Date.toLowerCase() + "\n" + + // "" + "\n"; // Notice the empty string at the end so we need to add two carriage returns format!( @@ -735,7 +742,7 @@ fn generate_resource_link<'a>(u: &'a hyper::Uri) -> &'a str { mod tests { use azure::cosmos::client::*; use azure::cosmos::authorization_token; - use uri::Url; + use hyper::Uri; #[test] fn string_to_sign_00() { @@ -745,7 +752,7 @@ mod tests { let time = format!("{}", time.format(TIME_FORMAT)); let ret = string_to_sign( - HTTPMethod::Get, + hyper::Method::Get, ResourceType::Databases, "dbs/MyDatabase/colls/MyCollection", &time, @@ -769,14 +776,16 @@ mon, 01 jan 1900 01:00:00 gmt let time = format!("{}", time.format(TIME_FORMAT)); let authorization_token = - authorization_token::AuthorizationToken::new("mindflavor", authorization_token::TokenType::Master, - "8F8xXXOptJxkblM1DBXW7a6NMI5oE8NnwPGYBmwxLCKfejOK7B7yhcCHMGvN3PBrlMLIOeol1Hv9RCdzAZR5sg==".to_owned()).unwrap(); - - + authorization_token::AuthorizationToken::new( + "mindflavor".to_owned(), + authorization_token::TokenType::Master, + "8F8xXXOptJxkblM1DBXW7a6NMI5oE8NnwPGYBmwxLCKfejOK7B7yhcCHMGvN3PBrlMLIOeol1Hv9RCdzAZR5sg==" + .to_owned()) + .unwrap(); let ret = generate_authorization( &authorization_token, - HTTPMethod::Get, + hyper::Method::Get, ResourceType::Databases, "dbs/MyDatabase/colls/MyCollection", &time, @@ -795,14 +804,14 @@ mon, 01 jan 1900 01:00:00 gmt let time = format!("{}", time.format(TIME_FORMAT)); let authorization_token = authorization_token::AuthorizationToken::new( - "mindflavor", + "mindflavor".to_owned(), authorization_token::TokenType::Master, "dsZQi3KtZmCv1ljt3VNWNm7sQUF1y5rJfC6kv5JiwvW0EndXdDku/dkKBp8/ufDToSxL".to_owned(), ).unwrap(); let ret = generate_authorization( &authorization_token, - HTTPMethod::Get, + hyper::Method::Get, ResourceType::Databases, "dbs/ToDoList", &time, @@ -821,13 +830,13 @@ mon, 01 jan 1900 01:00:00 gmt #[test] fn generate_resource_link_00() { - let u = Url::parse("https://mindflavor.raldld.r4eee.sss/dbs/second").unwrap(); + let u = Uri::from_str("https://mindflavor.raldld.r4eee.sss/dbs/second").unwrap(); assert_eq!(generate_resource_link(&u), "dbs/second"); - let u = Url::parse("https://mindflavor.raldld.r4eee.sss/dbs").unwrap(); + let u = Uri::from_str("https://mindflavor.raldld.r4eee.sss/dbs").unwrap(); assert_eq!(generate_resource_link(&u), ""); - let u = Url::parse("https://mindflavor.raldld.r4eee.sss/colls/second/third").unwrap(); + let u = Uri::from_str("https://mindflavor.raldld.r4eee.sss/colls/second/third").unwrap(); assert_eq!(generate_resource_link(&u), "colls/second/third"); - let u = Url::parse("https://mindflavor.documents.azure.com/dbs/test_db/colls").unwrap(); + let u = Uri::from_str("https://mindflavor.documents.azure.com/dbs/test_db/colls").unwrap(); assert_eq!(generate_resource_link(&u), "dbs/test_db"); } diff --git a/tests/blob.rs b/tests/blob.rs index 76801f09b..7233f69c6 100644 --- a/tests/blob.rs +++ b/tests/blob.rs @@ -78,10 +78,11 @@ fn put_blob() { let len = value.len() as u64; if Container::list(client, &LIST_CONTAINER_OPTIONS_DEFAULT) - .unwrap() - .iter() - .find(|x| x.name == container_name) - .is_none() { + .unwrap() + .iter() + .find(|x| x.name == container_name) + .is_none() + { Container::create(client, container_name, PublicAccess::Blob).unwrap(); } diff --git a/tests/event_hub.rs b/tests/event_hub.rs index d0f575887..1c5bea8a9 100644 --- a/tests/event_hub.rs +++ b/tests/event_hub.rs @@ -30,10 +30,8 @@ fn send_events_to_event_hub() { fn send_event(cli: &mut azure::service_bus::event_hub::Client) { debug!("running send_event"); - let mut cursor = std::io::Cursor::new(vec![0;255]); - cursor - .write(b"{ numero: 100, testo: \"sample\" }") - .unwrap(); + let mut cursor = std::io::Cursor::new(vec![0; 255]); + cursor.write(b"{ numero: 100, testo: \"sample\" }").unwrap(); cursor.flush().unwrap(); cursor.seek(std::io::SeekFrom::Start(0)).unwrap(); @@ -43,11 +41,11 @@ fn send_event(cli: &mut azure::service_bus::event_hub::Client) { fn create_client() -> azure::service_bus::event_hub::Client { - let policy_name = std::env::var("AZURE_POLICY_NAME"). - expect("Please set AZURE_POLICY_NAME env variable first!"); + let policy_name = std::env::var("AZURE_POLICY_NAME") + .expect("Please set AZURE_POLICY_NAME env variable first!"); - let policy_key = - std::env::var("AZURE_POLICY_KEY").expect("Please set AZURE_POLICY_KEY env variable first!"); + let policy_key = std::env::var("AZURE_POLICY_KEY") + .expect("Please set AZURE_POLICY_KEY env variable first!"); let sb_namespace = std::env::var("AZURE_SERVICE_BUS_NAMESPACE") .expect("Please set AZURE_SERVICE_BUS_NAMESPACE env variable first!"); diff --git a/tests/table.rs b/tests/table.rs index 32e04f0f7..5e09aa516 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -39,22 +39,22 @@ fn insert_get() { let utc = chrono::UTC::now(); let ref s = utc.to_string(); let ref entity1 = &Entity { - PartitionKey: "e1".to_owned(), - RowKey: s.to_owned(), - c: "mot1".to_owned(), - deleted: Some("DELET".to_owned()), - }; + PartitionKey: "e1".to_owned(), + RowKey: s.to_owned(), + c: "mot1".to_owned(), + deleted: Some("DELET".to_owned()), + }; client.insert_entity("rtest1", entity1).unwrap(); let entity: Entity = client.get_entity("rtest1", "e1", s).unwrap().unwrap(); assert_eq!("mot1", entity.c); assert!(entity.deleted.is_some()); let ref entity2 = &Entity { - PartitionKey: "e2".to_owned(), - RowKey: s.to_owned(), - c: "mot2".to_owned(), - deleted: None, - }; + PartitionKey: "e2".to_owned(), + RowKey: s.to_owned(), + c: "mot2".to_owned(), + deleted: None, + }; client.insert_entity("rtest1", entity2).unwrap(); let entity: Entity = client.get_entity("rtest1", "e2", s).unwrap().unwrap(); assert_eq!("mot2", entity.c); @@ -78,9 +78,7 @@ fn insert_update() { assert!(entity.deleted.is_some()); entity1.c = "mot1edit".to_owned(); - client - .update_entity("rtest1", "e1", s, &entity1) - .unwrap(); + client.update_entity("rtest1", "e1", s, &entity1).unwrap(); let entity: Entity = client.get_entity("rtest1", "e1", s).unwrap().unwrap(); assert_eq!("mot1edit", entity.c); } @@ -149,14 +147,18 @@ fn batch() { assert_eq!("mot2", entity.c); assert!(entity.deleted.is_some()); - let ref items = [BatchItem::new(r1.clone(), - Some(Entity { - PartitionKey: partition_key.to_owned(), - RowKey: r1.clone(), - c: "mot1edit".to_owned(), - deleted: Some("DELET".to_owned()), - })), - BatchItem::new(r2.clone(), None)]; + let ref items = [ + BatchItem::new( + r1.clone(), + Some(Entity { + PartitionKey: partition_key.to_owned(), + RowKey: r1.clone(), + c: "mot1edit".to_owned(), + deleted: Some("DELET".to_owned()), + }), + ), + BatchItem::new(r2.clone(), None), + ]; client.batch(TEST_TABLE, partition_key, items).unwrap(); let entity: Entity = client @@ -216,24 +218,34 @@ fn query_range() { } } -fn test_query_range(client: &TableService, - table_name: &str, - partition_key: &str, - row_key: &str, - ge: bool, - limit: u16) - -> Result, AzureError> { - client.query_entities(table_name, - Some(format!("$filter=PartitionKey {} '{}' and RowKey le '{}'&$top={}", - if ge { "ge" } else { "le" }, - partition_key, - row_key, - limit) - .as_str())) +fn test_query_range( + client: &TableService, + table_name: &str, + partition_key: &str, + row_key: &str, + ge: bool, + limit: u16, +) -> Result, AzureError> { + client.query_entities( + table_name, + Some( + format!( + "$filter=PartitionKey {} '{}' and RowKey le '{}'&$top={}", + if ge { "ge" } else { "le" }, + partition_key, + row_key, + limit + ).as_str(), + ), + ) } fn create_table_service() -> TableService { let azure_storage_account = get_from_env("AZURE_STORAGE_ACCOUNT"); let azure_storage_key = get_from_env("AZURE_STORAGE_KEY"); - TableService::new(Client::new(&azure_storage_account, &azure_storage_key, true)) + TableService::new(Client::new( + &azure_storage_account, + &azure_storage_key, + true, + )) } From 363cab7462f4abfc55da31463ed1039f68c43fc5 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 19:26:01 +0200 Subject: [PATCH 25/54] Updated chrono reference --- Cargo.lock | 13 +++++++------ Cargo.toml | 2 +- examples/document0.rs | 2 +- src/azure/core/parsing.rs | 8 ++++---- src/azure/cosmos/client.rs | 8 ++++---- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f2f9e23a..b64e313e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = "0.4.0" dependencies = [ "RustyXML 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "chrono" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", @@ -595,11 +595,12 @@ dependencies = [ [[package]] name = "thread-id" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -607,7 +608,7 @@ name = "thread_local" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-id 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -775,7 +776,7 @@ dependencies = [ "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14" "checksum cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c47d456a36ebf0536a6705c83c1cbbcb9255fbc1d905a6ded104f479268a29" -"checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" +"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" "checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" @@ -839,7 +840,7 @@ dependencies = [ "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" -"checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773" +"checksum thread-id 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2af4d6289a69a35c4d3aea737add39685f2784122c28119a7713165a63d68c9d" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" "checksum tokio 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad008a2129866117d3b0424c047f1302f3d8974ff58c6ade59ec14cd6c59fee2" diff --git a/Cargo.toml b/Cargo.toml index 091bb9a1c..78ee78361 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ name = "main" doc = false [dependencies] -chrono = "0.3.0" +chrono = "*" rust-crypto = "*" quick-error = "*" RustyXML = "*" diff --git a/examples/document0.rs b/examples/document0.rs index e394136be..b5be201ed 100644 --- a/examples/document0.rs +++ b/examples/document0.rs @@ -91,7 +91,7 @@ fn code() -> Result<(), Box> { id: "unique_id1", a_string: "Something here", a_number: 100, - a_timestamp: chrono::UTC::now().timestamp(), + a_timestamp: chrono::Utc::now().timestamp(), }; let document_attributes = core.run( diff --git a/src/azure/core/parsing.rs b/src/azure/core/parsing.rs index 86a70c403..d70bb782b 100644 --- a/src/azure/core/parsing.rs +++ b/src/azure/core/parsing.rs @@ -19,8 +19,8 @@ impl FromStringOptional for String { } } -impl FromStringOptional> for chrono::DateTime { - fn from_str_optional(s: &str) -> Result, TraversingError> { +impl FromStringOptional> for chrono::DateTime { + fn from_str_optional(s: &str) -> Result, TraversingError> { match from_azure_time(s) { Err(e) => Err(TraversingError::DateTimeParseError(e)), Ok(dt) => Ok(dt), @@ -29,9 +29,9 @@ impl FromStringOptional> for chrono::DateTime Result, chrono::ParseError> { +pub fn from_azure_time(s: &str) -> Result, chrono::ParseError> { let dt = try!(chrono::DateTime::parse_from_rfc2822(s)); - let dt_utc: chrono::DateTime = dt.with_timezone(&chrono::UTC); + let dt_utc: chrono::DateTime = dt.with_timezone(&chrono::Utc); Ok(dt_utc) } diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 7d5c1aaff..7cbd64fc5 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -591,7 +591,7 @@ fn prepare_request( where F: FnOnce(&mut Headers), { - let dt = chrono::UTC::now(); + let dt = chrono::Utc::now(); let time = format!("{}", dt.format(TIME_FORMAT)); // we surround this two statements with a scope so the borrow @@ -748,7 +748,7 @@ mod tests { fn string_to_sign_00() { let time = chrono::DateTime::parse_from_rfc3339("1900-01-01T01:00:00.000000000+00:00") .unwrap(); - let time = time.with_timezone(&chrono::UTC); + let time = time.with_timezone(&chrono::Utc); let time = format!("{}", time.format(TIME_FORMAT)); let ret = string_to_sign( @@ -772,7 +772,7 @@ mon, 01 jan 1900 01:00:00 gmt fn generate_authorization_00() { let time = chrono::DateTime::parse_from_rfc3339("1900-01-01T01:00:00.000000000+00:00") .unwrap(); - let time = time.with_timezone(&chrono::UTC); + let time = time.with_timezone(&chrono::Utc); let time = format!("{}", time.format(TIME_FORMAT)); let authorization_token = @@ -800,7 +800,7 @@ mon, 01 jan 1900 01:00:00 gmt fn generate_authorization_01() { let time = chrono::DateTime::parse_from_rfc3339("2017-04-27T00:51:12.000000000+00:00") .unwrap(); - let time = time.with_timezone(&chrono::UTC); + let time = time.with_timezone(&chrono::Utc); let time = format!("{}", time.format(TIME_FORMAT)); let authorization_token = authorization_token::AuthorizationToken::new( From 31ea75eac1762bacd7f7f1af936ad3f7681b033b Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 22:42:08 +0200 Subject: [PATCH 26/54] Migrated service_bus, pre test --- src/azure/core/errors.rs | 6 + src/azure/core/mod.rs | 7 ++ src/azure/cosmos/client.rs | 9 +- src/azure/mod.rs | 2 +- src/azure/service_bus/event_hub/client.rs | 84 ++++++++++++++ src/azure/service_bus/event_hub/mod.rs | 134 ++++++++++++++++++++++ src/azure/service_bus/mod.rs | 1 + 7 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 src/azure/service_bus/event_hub/client.rs create mode 100644 src/azure/service_bus/event_hub/mod.rs create mode 100644 src/azure/service_bus/mod.rs diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 57860f850..dc943badf 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -1,4 +1,5 @@ use hyper; +use native_tls; use hyper::StatusCode; use chrono; use std::io::Error as IOError; @@ -102,6 +103,11 @@ quick_error! { display("UTF8 conversion error: {}", err) cause(err) } + NativeTLSError(err: native_tls::Error) { + from() + display("Native TLS error: {}", err) + cause(err) + } } } diff --git a/src/azure/core/mod.rs b/src/azure/core/mod.rs index 269d7c23f..54fb14551 100644 --- a/src/azure/core/mod.rs +++ b/src/azure/core/mod.rs @@ -12,3 +12,10 @@ pub mod lease; pub mod range; pub mod ba512_range; + +use url::percent_encoding; +define_encode_set! { + pub COMPLETE_ENCODE_SET = [percent_encoding::USERINFO_ENCODE_SET] | { + '+', '-', '&' + } +} diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 7cbd64fc5..120088e66 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -8,6 +8,8 @@ use azure::core::errors::{AzureError, check_status_extract_body}; use azure::cosmos::request_response::{ListDatabasesResponse, CreateDatabaseRequest, ListCollectionsResponse}; +use azure::core::COMPLETE_ENCODE_SET; + use std::str::FromStr; use serde::Serialize; @@ -24,7 +26,6 @@ use hyper::StatusCode; use chrono; -use url::percent_encoding; use url::percent_encoding::utf8_percent_encode; use tokio_core; @@ -44,12 +45,6 @@ header! { (OfferThroughput, "x-ms-offer-throughput") => [u64] } header! { (DocumentIsUpsert, "x-ms-documentdb-is-upsert") => [bool] } header! { (DocumentIndexingDirective, "x-ms-indexing-directive ") => [IndexingDirective] } -define_encode_set! { - pub COMPLETE_ENCODE_SET = [percent_encoding::USERINFO_ENCODE_SET] | { - '+', '-', '&' - } -} - #[derive(Clone, Copy)] pub enum ResourceType { diff --git a/src/azure/mod.rs b/src/azure/mod.rs index e7d374594..8f99729aa 100644 --- a/src/azure/mod.rs +++ b/src/azure/mod.rs @@ -1,5 +1,5 @@ #[macro_use] pub mod core; //pub mod storage; -//pub mod service_bus; +pub mod service_bus; pub mod cosmos; diff --git a/src/azure/service_bus/event_hub/client.rs b/src/azure/service_bus/event_hub/client.rs new file mode 100644 index 000000000..f0c111287 --- /dev/null +++ b/src/azure/service_bus/event_hub/client.rs @@ -0,0 +1,84 @@ +use tokio_core; +use futures::future::*; + +use azure::service_bus::event_hub::send_event; +use azure::core::errors::AzureError; + +use time::Duration; + +use crypto::sha2::Sha256; +use crypto::hmac::Hmac; + +pub struct Client { + handle: tokio_core::reactor::Handle, + namespace: String, + event_hub: String, + policy_name: String, + hmac: Hmac, +} + +impl Client { + pub fn new( + handle: tokio_core::reactor::Handle, + namespace: &str, + event_hub: &str, + policy_name: &str, + key: &str, + ) -> Client { + let mut v_hmac_key: Vec = Vec::new(); + v_hmac_key.extend(key.as_bytes()); + let hmac = Hmac::new(Sha256::new(), &v_hmac_key); + + Client { + handle: handle, + namespace: namespace.to_owned(), + event_hub: event_hub.to_owned(), + policy_name: policy_name.to_owned(), + hmac: hmac, + } + } + + pub fn send_event( + &mut self, + event_body: &str, + duration: Duration, + ) -> impl Future { + { + send_event( + &self.handle.clone(), + &self.namespace, + &self.event_hub, + &self.policy_name, + &mut self.hmac, + event_body, + duration, + ) + } + } +} + +#[cfg(test)] +mod test { + #[allow(unused_imports)] + use super::Client; + + #[test] + pub fn client_ctor() { + Client::new("namespace", "event_hub", "policy", "key"); + } + + #[test] + pub fn client_enc() { + use crypto::mac::Mac; + use base64; + + let str_to_sign = "This must be secret!"; + + let mut c = Client::new("namespace", "event_hub", "policy", "key"); + + c.hmac.input(str_to_sign.as_bytes()); + let sig = base64::encode(c.hmac.result().code()); + + assert_eq!(sig, "2UNXaoPpeJBAhh6qxmTqXyNzTpOflGO6IhxegeUQBcU="); + } +} diff --git a/src/azure/service_bus/event_hub/mod.rs b/src/azure/service_bus/event_hub/mod.rs new file mode 100644 index 000000000..2f6ac1787 --- /dev/null +++ b/src/azure/service_bus/event_hub/mod.rs @@ -0,0 +1,134 @@ +use tokio_core; +use hyper_tls; +use futures::future::*; + +use azure::core::errors::{AzureError, check_status_extract_body}; +use azure::core::COMPLETE_ENCODE_SET; + +use std::str::FromStr; + +use hyper; +use hyper::header::ContentLength; +use hyper::StatusCode; + +use chrono; +use time::Duration; + +use std::ops::Add; +use base64; + +use url::percent_encoding::utf8_percent_encode; +use url::form_urlencoded::Serializer; + +use crypto::sha2::Sha256; +use crypto::hmac::Hmac; +use crypto::mac::Mac; + +mod client; +pub use self::client::Client; + +header! { (Authorization, "Authorization") => [String] } + +#[inline] +fn send_event_prepare( + handle: &tokio_core::reactor::Handle, + namespace: &str, + event_hub: &str, + policy_name: &str, + hmac: &mut Hmac, + event_body: &str, + duration: Duration, +) -> Result { + + // prepare the url to call + let url = format!( + "https://{}.servicebus.windows.net/{}/messages", + namespace, + event_hub + ); + let url = hyper::Uri::from_str(&url)?; + debug!("url == {:?}", url); + + // generate sas signature based on key name, key value, url and duration. + let sas = generate_signature(policy_name, hmac, &url.to_string(), duration); + debug!("sas == {}", sas); + + let client = hyper::Client::configure() + .connector(hyper_tls::HttpsConnector::new(4, handle)?) + .build(handle); + + let mut request = hyper::Request::new(hyper::Method::Post, url); + + request.headers_mut().set(Authorization(sas)); + request + .headers_mut() + .set(ContentLength(event_body.len() as u64)); + debug!("request.headers() == {:?}", request.headers()); + + request.set_body(event_body.to_string()); + + Ok(client.request(request)) +} + +pub fn send_event( + handle: &tokio_core::reactor::Handle, + namespace: &str, + event_hub: &str, + policy_name: &str, + hmac: &mut Hmac, + event_body: &str, + duration: Duration, +) -> impl Future { + + let req = send_event_prepare( + handle, + namespace, + event_hub, + policy_name, + hmac, + event_body, + duration, + ); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(|_| ok(())) + }) +} + +fn generate_signature( + policy_name: &str, + hmac: &mut Hmac, + url: &str, + ttl: Duration, +) -> String { + let expiry = chrono::Utc::now().add(ttl).timestamp(); + debug!("expiry == {:?}", expiry); + + let url_encoded = utf8_percent_encode(url, COMPLETE_ENCODE_SET); + //debug!("url_encoded == {:?}", url_encoded); + + let str_to_sign = format!("{}\n{}", url_encoded, expiry); + debug!("str_to_sign == {:?}", str_to_sign); + + hmac.reset(); + hmac.input(str_to_sign.as_bytes()); + let sig = { + let sig = base64::encode(hmac.result().code()); + debug!("sig == {}", sig); + let mut ser = Serializer::new(String::new()); + ser.append_pair("sig", &sig); + let sig = ser.finish(); + debug!("sig == {}", sig); + sig + }; + + debug!("sig == {:?}", sig); + + format!( + "SharedAccessSignature sr={}&{}&se={}&skn={}", + &url_encoded, + sig, + expiry, + policy_name + ) +} diff --git a/src/azure/service_bus/mod.rs b/src/azure/service_bus/mod.rs new file mode 100644 index 000000000..351deea58 --- /dev/null +++ b/src/azure/service_bus/mod.rs @@ -0,0 +1 @@ +pub mod event_hub; From e0ccbe177c2e29a1dc99cd3ae4efd42956d7666a Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 22:45:43 +0200 Subject: [PATCH 27/54] fixed tests --- src/azure/service_bus/event_hub/client.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/azure/service_bus/event_hub/client.rs b/src/azure/service_bus/event_hub/client.rs index f0c111287..b47e5f44b 100644 --- a/src/azure/service_bus/event_hub/client.rs +++ b/src/azure/service_bus/event_hub/client.rs @@ -62,19 +62,16 @@ mod test { #[allow(unused_imports)] use super::Client; - #[test] - pub fn client_ctor() { - Client::new("namespace", "event_hub", "policy", "key"); - } - #[test] pub fn client_enc() { use crypto::mac::Mac; use base64; + use tokio_core::reactor::Core; let str_to_sign = "This must be secret!"; - let mut c = Client::new("namespace", "event_hub", "policy", "key"); + let core = Core::new().unwrap(); + let mut c = Client::new(core.handle(), "namespace", "event_hub", "policy", "key"); c.hmac.input(str_to_sign.as_bytes()); let sig = base64::encode(c.hmac.result().code()); From f0358a4658d8bb5773bd45ec47f5b0ce8a6586df Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 23:11:47 +0200 Subject: [PATCH 28/54] Added service bus example --- examples/service_bus00.rs | 56 ++++++++++++++++++++++++++ src/azure/service_bus/event_hub/mod.rs | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 examples/service_bus00.rs diff --git a/examples/service_bus00.rs b/examples/service_bus00.rs new file mode 100644 index 000000000..838104611 --- /dev/null +++ b/examples/service_bus00.rs @@ -0,0 +1,56 @@ +extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; + +use std::error::Error; + +use tokio_core::reactor::Core; +use futures::Future; + +use azure_sdk_for_rust::azure::service_bus::event_hub::Client; + +extern crate time; + +fn main() { + code().unwrap(); +} + + +// We run a separate method to use the elegant quotation mark operator. +// A series of unwrap(), unwrap() would have achieved the same result. +fn code() -> Result<(), Box> { + // First we retrieve the account name and master key from environment variables. + // We expect master keys (ie, not resource constrained) + let service_bus_namespace = std::env::var("AZURE_SERVICE_BUS_NAMESPACE") + .expect("Set env variable AZURE_SERVICE_BUS_NAMESPACE first!"); + let event_hub_name = std::env::var("AZURE_EVENT_HUB_NAME") + .expect("Set env variable AZURE_EVENT_HUB_NAME first!"); + let policy_name = std::env::var("AZURE_POLICY_NAME") + .expect("Set env variable AZURE_POLICY_NAME first!"); + let policy_key = std::env::var("AZURE_POLICY_KEY") + .expect("Set env variable AZURE_POLICY_KEY first!"); + + let mut core = Core::new()?; + + let mut client = Client::new( + core.handle(), + &service_bus_namespace, + &event_hub_name, + &policy_name, + &policy_key, + ); + + let future = client + .send_event("sample body", time::Duration::days(1)) + .map(|_| { + println!("event sent!"); + }); + + core.run(future)?; + + Ok(()) +} diff --git a/src/azure/service_bus/event_hub/mod.rs b/src/azure/service_bus/event_hub/mod.rs index 2f6ac1787..13a19c22a 100644 --- a/src/azure/service_bus/event_hub/mod.rs +++ b/src/azure/service_bus/event_hub/mod.rs @@ -70,7 +70,7 @@ fn send_event_prepare( Ok(client.request(request)) } -pub fn send_event( +fn send_event( handle: &tokio_core::reactor::Handle, namespace: &str, event_hub: &str, From c0fd750ee1e8bf88707cac976440db9338c4e992 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 23:39:58 +0200 Subject: [PATCH 29/54] Completed service bus example --- examples/{document0.rs => document00.rs} | 0 examples/service_bus00.rs | 32 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) rename examples/{document0.rs => document00.rs} (100%) diff --git a/examples/document0.rs b/examples/document00.rs similarity index 100% rename from examples/document0.rs rename to examples/document00.rs diff --git a/examples/service_bus00.rs b/examples/service_bus00.rs index 838104611..22a6a2aef 100644 --- a/examples/service_bus00.rs +++ b/examples/service_bus00.rs @@ -44,11 +44,33 @@ fn code() -> Result<(), Box> { &policy_key, ); - let future = client - .send_event("sample body", time::Duration::days(1)) - .map(|_| { - println!("event sent!"); - }); + let messages = vec![ + "These", + "are", + "useless", + "messages", + "provided", + "for", + "free", + "with", + "love", + ]; + println!( + "Sending the following messages: {:?}. \ + Please note they will be sent out of order!", + messages + ); + + let mut v = Vec::new(); + for s in messages { + v.push(client.send_event(s, time::Duration::days(1)).map( + move |_| { + println!("{:?} event sent!", s); + }, + )) + } + + let future = futures::future::join_all(v); core.run(future)?; From 04c32897baecc89c1d8101841e447cba5eb03138 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 26 Jun 2017 23:44:20 +0200 Subject: [PATCH 30/54] disabled stable and beta builds of travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 886660c56..ff92ce332 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,9 @@ addons: # run builds for all the trains (and more) rust: - nightly - - beta + #- beta # check it compiles on the latest stable compiler - - stable + #- stable # load travis-cargo before_script: From 4bcaef92b12753650d38ce0a6f88ce5e86bc6e94 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Tue, 27 Jun 2017 00:07:08 +0200 Subject: [PATCH 31/54] Elided useless lifetime --- src/azure/cosmos/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure/cosmos/client.rs b/src/azure/cosmos/client.rs index 120088e66..c6846469b 100644 --- a/src/azure/cosmos/client.rs +++ b/src/azure/cosmos/client.rs @@ -709,7 +709,7 @@ fn string_to_sign( ) } -fn generate_resource_link<'a>(u: &'a hyper::Uri) -> &'a str { +fn generate_resource_link(u: &hyper::Uri) -> &str { static ENDING_STRINGS: &'static [&str] = &["/dbs", "/colls", "/docs"]; // store the element only if it does not end with dbs, colls or docs From 25a9028134e5a5b0794bd8a8e01e1dc8b6c7555c Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Tue, 27 Jun 2017 23:18:31 +0200 Subject: [PATCH 32/54] Completed container without checks --- src/azure/mod.rs | 2 +- src/azure/storage/client.rs | 95 ++++ .../container/list_container_options.rs | 16 + src/azure/storage/container/mod.rs | 188 +++++++ src/azure/storage/mod.rs | 5 + src/azure/storage/rest_client.rs | 479 ++++++++++++++++++ 6 files changed, 784 insertions(+), 1 deletion(-) create mode 100644 src/azure/storage/client.rs create mode 100644 src/azure/storage/container/list_container_options.rs create mode 100644 src/azure/storage/container/mod.rs create mode 100644 src/azure/storage/mod.rs create mode 100644 src/azure/storage/rest_client.rs diff --git a/src/azure/mod.rs b/src/azure/mod.rs index 8f99729aa..8bb4fb19d 100644 --- a/src/azure/mod.rs +++ b/src/azure/mod.rs @@ -1,5 +1,5 @@ #[macro_use] pub mod core; -//pub mod storage; +pub mod storage; pub mod service_bus; pub mod cosmos; diff --git a/src/azure/storage/client.rs b/src/azure/storage/client.rs new file mode 100644 index 000000000..7ddad20fb --- /dev/null +++ b/src/azure/storage/client.rs @@ -0,0 +1,95 @@ +use hyper; +use hyper_tls; +use hyper::header::Headers; +use hyper::Method; +use super::rest_client::{perform_request, ServiceType}; + +use azure::core::errors::AzureError; + +use tokio_core::reactor::Handle; + +// Can be variant for different cloud environment +const SERVICE_SUFFIX_BLOB: &'static str = ".blob.core.windows.net"; +const SERVICE_SUFFIX_TABLE: &'static str = ".table.core.windows.net"; + +pub struct Client { + account: String, + key: String, + hc: hyper::Client>, +} + +impl Client { + pub fn new(handle: &Handle, account: &str, key: &str) -> Result { + use hyper; + + let client = hyper::Client::configure() + .connector(hyper_tls::HttpsConnector::new(4, handle)?) + .build(handle); + + Ok(Client { + account: account.to_owned(), + key: key.to_owned(), + hc: client, + }) + } + + pub fn account(&self) -> &str { + &self.account + } + + pub fn key(&self) -> &str { + &self.key + } + + pub fn perform_request( + &self, + uri: &str, + method: Method, + headers_func: F, + request_body: Option<&str>, + ) -> Result + where + F: FnOnce(&mut Headers), + { + perform_request( + &self.hc, + uri, + method, + &self.key, + headers_func, + request_body, + ServiceType::Blob, + ) + } + + pub fn perform_table_request( + &self, + segment: &str, + method: Method, + headers_func: F, + request_str: Option<&str>, + ) -> Result + where + F: FnOnce(&mut Headers), + { + debug!("segment: {}, method: {:?}", segment, method,); + perform_request( + &self.hc, + (self.get_uri_prefix(ServiceType::Table) + segment).as_str(), + method, + &self.key, + headers_func, + request_str, + ServiceType::Table, + ) + } + + /// Uri scheme + authority e.g. http://myaccount.table.core.windows.net/ + pub fn get_uri_prefix(&self, service_type: ServiceType) -> String { + "https://".to_owned() + self.account() + + match service_type { + ServiceType::Blob => SERVICE_SUFFIX_BLOB, + ServiceType::Table => SERVICE_SUFFIX_TABLE, + } + "/" + } +} diff --git a/src/azure/storage/container/list_container_options.rs b/src/azure/storage/container/list_container_options.rs new file mode 100644 index 000000000..84194bd21 --- /dev/null +++ b/src/azure/storage/container/list_container_options.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct ListContainerOptions { + pub max_results: u32, + pub include_metadata: bool, + pub next_marker: Option, + pub prefix: Option, + pub timeout: Option, +} + +pub const LIST_CONTAINER_OPTIONS_DEFAULT: ListContainerOptions = ListContainerOptions { + max_results: 5000, + include_metadata: false, + next_marker: None, + prefix: None, + timeout: None, +}; diff --git a/src/azure/storage/container/mod.rs b/src/azure/storage/container/mod.rs new file mode 100644 index 000000000..57ae58e39 --- /dev/null +++ b/src/azure/storage/container/mod.rs @@ -0,0 +1,188 @@ +mod list_container_options; +pub use self::list_container_options::{ListContainerOptions, LIST_CONTAINER_OPTIONS_DEFAULT}; + +use azure::core; +use azure::core::errors::AzureError; +use azure::core::errors::check_status_extract_body; +use azure::core::enumerations; +use azure::core::parsing::{traverse, cast_must, cast_optional}; +use azure::core::incompletevector::IncompleteVector; + +use azure::core::lease::{LeaseStatus, LeaseState, LeaseDuration}; +use azure::storage::client::Client; + +use futures::future::*; + +use hyper::Method; +use hyper::StatusCode; + +use std::str::FromStr; +use chrono::DateTime; +use chrono::Utc; + +use std::fmt; + +use xml::Element; + +use azure::core::errors::TraversingError; +use azure::core::parsing::FromStringOptional; + +header! { (XMSBlobPublicAccess, "x-ms-blob-public-access") => [PublicAccess] } + +create_enum!( + PublicAccess, + (None, "none"), + (Container, "container"), + (Blob, "blob") +); + + +#[derive(Debug, Clone)] +pub struct Container { + pub name: String, + pub last_modified: DateTime, + pub e_tag: String, + pub lease_status: LeaseStatus, + pub lease_state: LeaseState, + pub lease_duration: Option, +} + +impl Container { + pub fn new(name: &str) -> Container { + Container { + name: name.to_owned(), + last_modified: Utc::now(), + e_tag: "".to_owned(), + lease_status: LeaseStatus::Unlocked, + lease_state: LeaseState::Available, + lease_duration: None, + } + } + + pub fn parse(elem: &Element) -> Result { + let name = try!(cast_must::(elem, &["Name"])); + let last_modified = try!(cast_must::>( + elem, + &["Properties", "Last-Modified"] + )); + let e_tag = try!(cast_must::(elem, &["Properties", "Etag"])); + + let lease_state = try!(cast_must::(elem, &["Properties", "LeaseState"])); + + let lease_duration = try!(cast_optional::( + elem, + &["Properties", "LeaseDuration"] + )); + + let lease_status = try!(cast_must::( + elem, + &["Properties", "LeaseStatus"] + )); + + Ok(Container { + name: name, + last_modified: last_modified, + e_tag: e_tag, + lease_status: lease_status, + lease_state: lease_state, + lease_duration: lease_duration, + }) + } + + pub fn delete(&mut self, c: &Client) -> impl Future { + let uri = format!( + "https://{}.blob.core.windows.net/{}?restype=container", + c.account(), + self.name + ); + + let req = c.perform_request(&uri, Method::Delete, |_| {}, None); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Accepted).and_then(|_| ok(())) + }) + } + + pub fn create( + c: &Client, + container_name: &str, + pa: PublicAccess, + ) -> impl Future { + let uri = format!( + "https://{}.blob.core.windows.net/{}?restype=container", + c.account(), + container_name + ); + + let req = c.perform_request( + &uri, + Method::Put, + |ref mut headers| { headers.set(XMSBlobPublicAccess(pa)); }, + None, + ); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(|_| ok(())) + }) + } + + // TODO + // pub fn get_acl(c : &Client, gao : &GetAclOptions) + + pub fn list( + c: &Client, + lco: &ListContainerOptions, + ) -> impl Future, Error = AzureError> { + let mut uri = format!( + "https://{}.blob.core.windows.net?comp=list&maxresults={}", + c.account(), + lco.max_results + ); + + if !lco.include_metadata { + uri = format!("{}&include=metadata", uri); + } + + if let Some(ref prefix) = lco.prefix { + uri = format!("{}&prefix={}", uri, prefix); + } + + if let Some(ref nm) = lco.next_marker { + uri = format!("{}&marker={}", uri, nm); + } + + if let Some(ref timeout) = lco.timeout { + uri = format!("{}&timeout={}", uri, timeout); + } + + let req = c.perform_request(&uri, Method::Get, |_| {}, None); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(|body| { + done(incomplete_vector_from_response(&body)).from_err() + }) + }) + } +} + +#[inline] +fn incomplete_vector_from_response(body: &str) -> Result, AzureError> { + let elem: Element = body.parse()?; + + let mut v = Vec::new(); + + // let containers = try!(traverse(&elem, &["Containers", "Container"])); + // println!("containers == {:?}", containers); + + for container in traverse(&elem, &["Containers", "Container"], true)? { + v.push(Container::parse(container)?); + } + + let next_marker = match cast_optional::(&elem, &["NextMarker"])? { + Some(ref nm) if nm == "" => None, + Some(nm) => Some(nm), + None => None, + }; + + Ok(IncompleteVector::new(next_marker, v)) +} diff --git a/src/azure/storage/mod.rs b/src/azure/storage/mod.rs new file mode 100644 index 000000000..a814a741e --- /dev/null +++ b/src/azure/storage/mod.rs @@ -0,0 +1,5 @@ +pub mod client; +pub mod container; +//pub mod blob; +//pub mod table; +mod rest_client; diff --git a/src/azure/storage/rest_client.rs b/src/azure/storage/rest_client.rs new file mode 100644 index 000000000..68770ad49 --- /dev/null +++ b/src/azure/storage/rest_client.rs @@ -0,0 +1,479 @@ +use azure::core::range; +use azure::core::errors::AzureError; +use hyper::Method; +use azure::core::lease::{LeaseId, LeaseStatus, LeaseState, LeaseDuration, LeaseAction}; +use chrono; +use crypto::hmac::Hmac; +use crypto::mac::Mac; +use crypto::sha2::Sha256; +use hyper; +use hyper_tls; +use hyper::header::{Header, Headers, ContentEncoding, ContentLanguage, ContentLength, ContentType, + Date, IfModifiedSince, IfUnmodifiedSince}; +use base64; +use std::fmt::Display; +use url; + +use std::str::FromStr; + +pub enum ServiceType { + Blob, + // Queue, File, + Table, +} + +const AZURE_VERSION: &'static str = "2016-05-31"; + +header! { (XMSVersion, "x-ms-version") => [String] } +header! { (XMSDate, "x-ms-date") => [String] } +header! { (Authorization, "Authorization") => [String] } +header! { (ContentMD5, "Content-MD5") => [String] } +header! { (IfMatch, "If-Match") => [String] } +header! { (IfNoneMatch, "If-None-Match") => [String] } +header! { (Range, "Range") => [String] } +header! { (XMSRange, "x-ms-range") => [range::Range] } +header! { (XMSLeaseId, "x-ms-lease-id") => [LeaseId] } +header! { (XMSLeaseStatus, "x-ms-lease-status") => [LeaseStatus] } +header! { (XMSLeaseState, "x-ms-lease-state") => [LeaseState] } +header! { (XMSLeaseAction, "x-ms-lease-action") => [LeaseAction] } +header! { (XMSLeaseDuration, "x-ms-lease-duration") => [LeaseDuration] } +header! { (XMSLeaseDurationSeconds, "x-ms-lease-duration") => [u32] } +header! { (XMSLeaseBreakPeriod, "x-ms-lease-break-period") => [u32] } +header! { (XMSProposedLeaseId, "x-ms-proposed-lease-id") => [LeaseId] } +header! { (ETag, "ETag") => [String] } +header! { (XMSRangeGetContentMD5, "x-ms-range-get-content-md5") => [bool] } +header! { (XMSClientRequestId, "x-ms-client-request-id") => [String] } + +fn generate_authorization( + h: &Headers, + u: &url::Url, + method: Method, + hmac_key: &str, + service_type: ServiceType, +) -> String { + let str_to_sign = string_to_sign(h, u, method, service_type); + + // println!("\nstr_to_sign == {:?}\n", str_to_sign); + // println!("str_to_sign == {}", str_to_sign); + + let auth = encode_str_to_sign(&str_to_sign, hmac_key); + // println!("auth == {:?}", auth); + + format!("SharedKey {}:{}", get_account(u), auth) +} + +fn encode_str_to_sign(str_to_sign: &str, hmac_key: &str) -> String { + let mut v_hmac_key: Vec = Vec::new(); + + v_hmac_key.extend(base64::decode(hmac_key).unwrap()); + + let mut hmac = Hmac::new(Sha256::new(), &v_hmac_key); + hmac.input(str_to_sign.as_bytes()); + + // let res = hmac.result(); + // println!("{:?}", res.code()); + + base64::encode(hmac.result().code()) +} + +#[inline] +fn add_if_exists(h: &Headers) -> String { + let m = match h.get::() { + Some(ce) => ce.to_string(), + None => String::default(), + }; + + m + "\n" +} + +#[allow(unknown_lints)] +#[allow(needless_pass_by_value)] +fn string_to_sign(h: &Headers, u: &url::Url, method: Method, service_type: ServiceType) -> String { + let mut str_to_sign = String::new(); + let verb = format!("{:?}", method); + str_to_sign = str_to_sign + &verb.to_uppercase() + "\n"; + + match service_type { + ServiceType::Table => {} + _ => { + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + // content lenght must only be specified if != 0 + // this is valid from 2015-02-21 + let m = match h.get::() { + Some(ce) => { + if ce.to_be() != 0u64 { + ce.to_string() + } else { + String::default() + } + } + None => String::default(), + }; + + str_to_sign = str_to_sign + &m + "\n"; + } + } + + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + + match service_type { + ServiceType::Table => { + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &canonicalized_resource_table(u); + } + _ => { + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &add_if_exists::(h); + str_to_sign = str_to_sign + &canonicalize_header(h); + str_to_sign = str_to_sign + &canonicalized_resource(u); + } + } + + + + // expected + // GET\n /*HTTP Verb*/ + // \n /*Content-Encoding*/ + // \n /*Content-Language*/ + // \n /*Content-Length (include value when zero)*/ + // \n /*Content-MD5*/ + // \n /*Content-Type*/ + // \n /*Date*/ + // \n /*If-Modified-Since */ + // \n /*If-Match*/ + // \n /*If-None-Match*/ + // \n /*If-Unmodified-Since*/ + // \n /*Range*/ + // x-ms-date:Sun, 11 Oct 2009 21:49:13 GMT\nx-ms-version:2009-09-19\n + // /*CanonicalizedHeaders*/ + // /myaccount /mycontainer\ncomp:metadata\nrestype:container\ntimeout:20 + // /*CanonicalizedResource*/ + // + // + + str_to_sign +} + +fn canonicalize_header(h: &Headers) -> String { + // println!("{:?}", h); + + let mut v_headers = Vec::new(); + + for header in h.iter().filter(|h| h.name().starts_with("x-ms")) { + let s: String = header.name().to_owned().trim().to_lowercase(); + + v_headers.push(s); + } + + // println!("{:?}", v_headers); + + v_headers.sort(); + + let mut can = String::new(); + + for header_name in v_headers { + let h = h.iter().find(|x| x.name() == header_name).unwrap(); + // println!("looking for {} => {:?}", header_name, h); + + let s = h.value_string(); + // println!("h.to_string() == {:?}", s); + + can = can + &header_name + ":" + &s + "\n"; + } + + // println!("{:?}", can); + + can +} + +#[inline] +fn get_account(u: &url::Url) -> String { + match u.host().unwrap().clone() { + url::Host::Domain(dm) => { + // println!("dom == {:?}", dm); + + let first_dot = dm.find('.').unwrap(); + String::from(&dm[0..first_dot]) + } + _ => panic!("only Domains are supported in canonicalized_resource"), + } +} + +// For table +fn canonicalized_resource_table(u: &url::Url) -> String { + format!("/{}{}", get_account(u), u.path()) +} + +fn canonicalized_resource(u: &url::Url) -> String { + let mut can_res: String = String::new(); + can_res = can_res + "/"; + + let account = get_account(u); + can_res = can_res + &account; + + let paths = u.path_segments().unwrap(); + + { + let mut path = String::new(); + for p in paths { + path.push_str("/"); + path.push_str(&*p); + } + + can_res = can_res + &path; + } + can_res = can_res + "\n"; + + // query parameters + let query_pairs = u.query_pairs(); //.into_owned(); + { + let mut qps = Vec::new(); + { + for qp in query_pairs { + trace!("adding to qps {:?}", qp); + + // add only once + if !(qps.iter().any(|x: &String| x == &qp.0)) { + qps.push(qp.0.into_owned()); + } + } + } + + qps.sort(); + + for qparam in qps { + // find correct parameter + let ret = lexy_sort(&query_pairs, &qparam); + + // println!("adding to can_res {:?}", ret); + + can_res = can_res + &qparam.to_lowercase() + ":"; + + for (i, item) in ret.iter().enumerate() { + if i > 0 { + can_res = can_res + "," + } + can_res = can_res + item; + } + + can_res = can_res + "\n"; + } + }; + + can_res[0..can_res.len() - 1].to_owned() +} + +fn lexy_sort(vec: &url::form_urlencoded::Parse, query_param: &str) -> Vec<(String)> { + let mut v_values: Vec = Vec::new(); + + for item in vec.filter(|x| x.0 == *query_param) { + v_values.push(item.1.into_owned()) + } + v_values.sort(); + + v_values +} + +#[allow(unknown_lints)] +#[allow(too_many_arguments)] +pub fn perform_request( + client: &hyper::Client>, + uri: &str, + http_method: Method, + azure_key: &str, + headers_func: F, + request_body: Option<&str>, + service_type: ServiceType, +) -> Result +where + F: FnOnce(&mut Headers), +{ + let dt = chrono::Utc::now(); + let time = format!("{}", dt.format("%a, %d %h %Y %T GMT")); + + let url = url::Url::parse(uri)?; + let uri = hyper::Uri::from_str(uri)?; + + // for header in additional_headers.iter() { + // println!("{:?}", header.value_string()); + // h.set(); + // } + let mut request = hyper::Request::new(http_method.clone(), uri); + + // This will give the caller the ability to add custom headers. + // The closure is needed to because request.headers_mut().set_raw(...) requires + // a Cow with 'static lifetime... + headers_func(request.headers_mut()); + + request.headers_mut().set(XMSDate(time)); + request + .headers_mut() + .set(XMSVersion(AZURE_VERSION.to_owned())); + + if let Some(body) = request_body { + request.headers_mut().set(ContentLength(body.len() as u64)); + request.set_body(body.to_string()); + } + + let auth = generate_authorization( + request.headers(), + &url, + http_method, + azure_key, + service_type, + ); + + request.headers_mut().set(Authorization(auth)); + + Ok(client.request(request)) +} + + +mod test { + extern crate hyper; + extern crate chrono; + extern crate url; + + #[test] + fn test_canonicalize_header() { + use super::*; + + let dt = chrono::DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900").unwrap(); + let time = format!("{}", dt.format("%a, %d %h %Y %T GMT%Z")); + + println!("time == {}", time); + + let mut h = hyper::header::Headers::new(); + + h.set(XMSDate(time)); + h.set(XMSVersion("2015-04-05".to_owned())); + + assert_eq!( + super::canonicalize_header(&h), + "x-ms-date:Fri, 28 Nov 2014 21:00:09 GMT+09:00\nx-ms-version:2015-04-05\n" + ); + } + + #[test] + fn str_to_sign_test() { + use super::*; + use hyper::header::{Accept, qitem}; + use azure::storage::table::{get_json_mime_nometadata, get_default_json_mime}; + + let mut headers: Headers = Headers::new(); + headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); + headers.set(ContentType(get_default_json_mime())); + + + let u: url::Url = url::Url::parse("https://mindrust.table.core.windows.net/TABLES") + .unwrap(); + let method: Method = Method::Post; + let service_type: ServiceType = ServiceType::Table; + + let dt = chrono::DateTime::parse_from_rfc2822("Wed, 3 May 2017 14:04:56 +0000").unwrap(); + let time = format!("{}", dt.format("%a, %d %h %Y %T GMT")); + + headers.set(XMSDate(time)); + headers.set(XMSVersion(AZURE_VERSION.to_owned())); + + let s = string_to_sign(&headers, &u, method, service_type); + + assert_eq!( + s, + "POST + +application/json; charset=utf-8 +Wed, 03 May 2017 14:04:56 GMT +/mindrust/TABLES" + ); + } + + #[test] + fn test_canonicalize_resource_10() { + let url = url::Url::parse("https://mindrust.table.core.windows.net/TABLES").unwrap(); + assert_eq!(super::canonicalized_resource(&url), "/mindrust/TABLES"); + } + + #[test] + fn test_canonicalize_resource_1() { + let url = url::Url::parse( + "http://myaccount.blob.core.windows.\ + net/mycontainer?restype=container&comp=metadata", + ).unwrap(); + assert_eq!( + super::canonicalized_resource(&url), + "/myaccount/mycontainer\ncomp:metadata\nrestype:container" + ); + } + + #[test] + fn test_canonicalize_resource_2() { + let url = url::Url::parse( + "http://myaccount.blob.core.windows.\ + net/mycontainer?restype=container&comp=list&include=snapshots&\ + include=metadata&include=uncommittedblobs", + ).unwrap(); + assert_eq!( + super::canonicalized_resource(&url), + "/myaccount/mycontainer\ncomp:list\ninclude:metadata,snapshots,\ + uncommittedblobs\nrestype:container" + ); + } + + #[test] + fn test_canonicalize_resource_3() { + let url = url::Url::parse( + "https://myaccount-secondary.blob.core.windows.\ + net/mycontainer/myblob", + ).unwrap(); + assert_eq!( + super::canonicalized_resource(&url), + "/myaccount-secondary/mycontainer/myblob" + ); + } + + #[test] + fn test_encode_str_to_sign_1() { + let str_to_sign = "53d7e14aee681a00340300032015-01-01T10:00:00.0000000".to_owned(); + let hmac_key = "pXeTVaaaaU9XxH6fPcPlq8Y9D9G3Cdo5Eh2nMSgKj/DWqeSFFXDdmpz5Trv+L2hQNM+nGa704R\ + f8Z22W9O1jdQ==" + .to_owned(); + + assert_eq!( + super::encode_str_to_sign(&str_to_sign, &hmac_key), + "gZzaRaIkvC9jYRY123tq3xXZdsMAcgAbjKQo8y0p0Fs=".to_owned() + ); + } + + #[test] + fn test_encode_str_to_sign_2() { + let str_to_sign = "This is the data to sign".to_owned(); + let hmac_key = "pXeTVaaaaU9XxH6fPcPlq8Y9D9G3Cdo5Eh2nMSgKj/DWqeSFFXDdmpz5Trv+L2hQNM+nGa704R\ + f8Z22W9O1jdQ==" + .to_owned(); + + assert_eq!( + super::encode_str_to_sign(&str_to_sign, &hmac_key), + "YuKoXELO9M9HXeeGaSXBr4Nk+CgPAEQhcwJ6tVtBRCw=".to_owned() + ); + } + + #[test] + fn test_encode_str_to_sign_3() { + let str_to_sign = "This is the data to sign".to_owned(); + let hmac_key = "pXeTVaaaaU9XxH6fPcPlq8Y9D9G3Cdo5Eh2nMSgKj/DWqeSFFXDdmpz5Trv+L2hQNM+nGa704R\ + f8Z22W9O1jdQ==" + .to_owned(); + + assert_eq!( + super::encode_str_to_sign(&str_to_sign, &hmac_key), + "YuKoXELO9M9HXeeGaSXBr4Nk+CgPAEQhcwJ6tVtBRCw=".to_owned() + ); + } +} From 2fe0e81e98e152f4fc2ed0bbd8eabbcb7120f775 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Tue, 27 Jun 2017 23:38:47 +0200 Subject: [PATCH 33/54] Completed container example --- examples/container00.rs | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 examples/container00.rs diff --git a/examples/container00.rs b/examples/container00.rs new file mode 100644 index 000000000..88d804f6f --- /dev/null +++ b/examples/container00.rs @@ -0,0 +1,45 @@ +extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; + +use std::error::Error; + +use futures::future::*; +use tokio_core::reactor::Core; + +use azure_sdk_for_rust::azure::storage::client::Client; + +use azure_sdk_for_rust::azure::storage::container::Container; +use azure_sdk_for_rust::azure::storage::container::LIST_CONTAINER_OPTIONS_DEFAULT; + +fn main() { + code().unwrap(); +} + +// We run a separate method to use the elegant quotation mark operator. +// A series of unwrap(), unwrap() would have achieved the same result. +fn code() -> Result<(), Box> { + // First we retrieve the account name and master key from environment variables. + let account = std::env::var("STORAGE_ACCOUNT") + .expect("Set env variable STORAGE_ACCOUNT first!"); + let master_key = std::env::var("STORAGE_MASTER_KEY") + .expect("Set env variable STORAGE_MASTER_KEY first!"); + + let mut core = Core::new()?; + + let client = Client::new(&core.handle(), &account, &master_key)?; + + let future = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).map(|iv| { + println!("List containers returned {} containers.", iv.len()); + for ref cont in iv.iter() { + println!("\t{}", cont.name); + } + }); + + core.run(future)?; + Ok(()) +} From 9787ac418377ba1552f98570b3b86135a4fd2d76 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 28 Jun 2017 00:15:08 +0200 Subject: [PATCH 34/54] Starting with blob (no test) --- src/azure/storage/blob/lease_blob_options.rs | 20 + src/azure/storage/blob/list_blob_options.rs | 24 + src/azure/storage/blob/mod.rs | 767 +++++++++++++++++++ src/azure/storage/blob/put_block_options.rs | 14 + src/azure/storage/blob/put_options.rs | 12 + src/azure/storage/blob/put_page_options.rs | 12 + src/azure/storage/mod.rs | 2 +- 7 files changed, 850 insertions(+), 1 deletion(-) create mode 100644 src/azure/storage/blob/lease_blob_options.rs create mode 100644 src/azure/storage/blob/list_blob_options.rs create mode 100644 src/azure/storage/blob/mod.rs create mode 100644 src/azure/storage/blob/put_block_options.rs create mode 100644 src/azure/storage/blob/put_options.rs create mode 100644 src/azure/storage/blob/put_page_options.rs diff --git a/src/azure/storage/blob/lease_blob_options.rs b/src/azure/storage/blob/lease_blob_options.rs new file mode 100644 index 000000000..f2f21f4e5 --- /dev/null +++ b/src/azure/storage/blob/lease_blob_options.rs @@ -0,0 +1,20 @@ +use azure::core::lease::LeaseId; + +#[derive(Debug, Clone, PartialEq)] +pub struct LeaseBlobOptions { + pub lease_id: Option, + pub timeout: Option, + pub lease_break_period: Option, + pub lease_duration: Option, + pub proposed_lease_id: Option, + pub request_id: Option, +} + +pub const LEASE_BLOB_OPTIONS_DEFAULT: LeaseBlobOptions = LeaseBlobOptions { + lease_id: None, + timeout: None, + lease_break_period: None, + lease_duration: None, + proposed_lease_id: None, + request_id: None, +}; diff --git a/src/azure/storage/blob/list_blob_options.rs b/src/azure/storage/blob/list_blob_options.rs new file mode 100644 index 000000000..c3bfd0310 --- /dev/null +++ b/src/azure/storage/blob/list_blob_options.rs @@ -0,0 +1,24 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct ListBlobOptions { + pub max_results: u32, + pub include_snapshots: bool, + pub include_metadata: bool, + pub include_uncommittedblobs: bool, + pub include_copy: bool, + pub next_marker: Option, + pub prefix: Option, + pub timeout: Option, +} + +pub const LIST_BLOB_OPTIONS_DEFAULT: ListBlobOptions = ListBlobOptions { + max_results: 5000, + include_snapshots: false, + include_metadata: false, + include_uncommittedblobs: false, + include_copy: false, + next_marker: None, + prefix: None, + timeout: None, +}; + +impl ListBlobOptions {} diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs new file mode 100644 index 000000000..7c79229e1 --- /dev/null +++ b/src/azure/storage/blob/mod.rs @@ -0,0 +1,767 @@ +extern crate uuid; + +mod put_options; +pub use self::put_options::{PutOptions, PUT_OPTIONS_DEFAULT}; + +mod list_blob_options; +pub use self::list_blob_options::{ListBlobOptions, LIST_BLOB_OPTIONS_DEFAULT}; + +mod put_block_options; +pub use self::put_block_options::{PutBlockOptions, PUT_BLOCK_OPTIONS_DEFAULT}; + +mod put_page_options; +pub use self::put_page_options::{PutPageOptions, PUT_PAGE_OPTIONS_DEFAULT}; + +mod lease_blob_options; +pub use self::lease_blob_options::{LeaseBlobOptions, LEASE_BLOB_OPTIONS_DEFAULT}; + +use hyper::Method; + +use chrono::DateTime; +use chrono::Utc; + +use futures::future::*; + +use azure::core::lease::{LeaseId, LeaseStatus, LeaseState, LeaseDuration, LeaseAction}; +use azure::storage::client::Client; + +use azure::core; +use azure::storage::rest_client::{XMSRange, ContentMD5, XMSLeaseStatus, XMSLeaseDuration, + XMSLeaseState, XMSLeaseId, XMSRangeGetContentMD5, + XMSClientRequestId, XMSLeaseAction, XMSLeaseDurationSeconds, + XMSLeaseBreakPeriod, XMSProposedLeaseId, ETag}; + +use azure::core::parsing::{cast_must, cast_optional, from_azure_time, traverse}; + +use xml::Element; + +use std::str::FromStr; +use azure::core::enumerations; +use std::fmt; + +use std::io::Read; + +use azure::core::errors::{TraversingError, AzureError, check_status_extract_body}; +use azure::core::parsing::FromStringOptional; + +use azure::core::range::Range; +use azure::core::ba512_range::BA512Range; +use azure::core::incompletevector::IncompleteVector; + +//use mime::Mime; + +use hyper::mime::Mime; + +use hyper::StatusCode; +use hyper::header::{Headers, ContentType, ContentLength, LastModified, ContentEncoding, + ContentLanguage}; + +use base64; + +use uuid::Uuid; + +create_enum!( + BlobType, + (BlockBlob, "BlockBlob"), + (PageBlob, "PageBlob"), + (AppendBlob, "AppendBlob") +); + +create_enum!( + CopyStatus, + (Pending, "pending"), + (Success, "success"), + (Aborted, "aborted"), + (Failed, "failed") +); + +create_enum!(PageWriteType, (Update, "update"), (Clear, "clear")); + +header! { (XMSBlobContentLength, "x-ms-blob-content-length") => [u64] } +header! { (XMSBlobSequenceNumber, "x-ms-blob-sequence-number") => [u64] } +header! { (XMSBlobType, "x-ms-blob-type") => [BlobType] } +header! { (XMSBlobContentDisposition, "x-ms-blob-content-disposition") => [String] } +header! { (XMSPageWrite, "x-ms-page-write") => [PageWriteType] } + +#[derive(Debug)] +pub struct Blob { + pub name: String, + pub container_name: String, + pub snapshot_time: Option>, + pub last_modified: DateTime, + pub etag: String, + pub content_length: u64, + pub content_type: Mime, + pub content_encoding: Option, + pub content_language: Option, + pub content_md5: Option, + pub cache_control: Option, + pub x_ms_blob_sequence_number: Option, + pub blob_type: BlobType, + pub lease_status: LeaseStatus, + pub lease_state: LeaseState, + pub lease_duration: Option, + pub copy_id: Option, + pub copy_status: Option, + pub copy_source: Option, + pub copy_progress: Option, + pub copy_completion: Option>, + pub copy_status_description: Option, +} + +impl Blob { + pub fn parse(elem: &Element, container_name: &str) -> Result { + let name = try!(cast_must::(elem, &["Name"])); + let snapshot_time = try!(cast_optional::>(elem, &["Snapshot"])); + let last_modified = try!(cast_must::>( + elem, + &["Properties", "Last-Modified"] + )); + let etag = try!(cast_must::(elem, &["Properties", "Etag"])); + + let content_length = try!(cast_must::(elem, &["Properties", "Content-Length"])); + + let content_type = try!(cast_must::(elem, &["Properties", "Content-Type"])); + let content_encoding = try!(cast_optional::( + elem, + &["Properties", "Content-Encoding"] + )); + let content_language = try!(cast_optional::( + elem, + &["Properties", "Content-Language"] + )); + let content_md5 = try!(cast_optional::( + elem, + &["Properties", "Content-MD5"] + )); + let cache_control = try!(cast_optional::( + elem, + &["Properties", "Cache-Control"] + )); + let x_ms_blob_sequence_number = try!(cast_optional::( + elem, + &["Properties", "x-ms-blob-sequence-number"] + )); + + let blob_type = try!(cast_must::(elem, &["Properties", "BlobType"])); + + let lease_status = try!(cast_must::( + elem, + &["Properties", "LeaseStatus"] + )); + let lease_state = try!(cast_must::(elem, &["Properties", "LeaseState"])); + let lease_duration = try!(cast_optional::( + elem, + &["Properties", "LeaseDuration"] + )); + let copy_id = try!(cast_optional::(elem, &["Properties", "CopyId"])); + let copy_status = try!(cast_optional::( + elem, + &["Properties", "CopyStatus"] + )); + let copy_source = try!(cast_optional::(elem, &["Properties", "CopySource"])); + let copy_progress = try!(cast_optional::( + elem, + &["Properties", "CopyProgress"] + )); + let copy_completion = try!(cast_optional::>( + elem, + &["Properties", "CopyCompletionTime"] + )); + let copy_status_description = try!(cast_optional::( + elem, + &["Properties", "CopyStatusDescription"] + )); + + let mut cp_bytes: Option = None; + if let Some(txt) = copy_progress { + cp_bytes = Some(try!(txt.parse::())); + } + + let ctype = { + if let Ok(ctype) = content_type.parse::() { + ctype + } else { + return Err(AzureError::GenericError); + } + }; + + Ok(Blob { + name: name, + container_name: container_name.to_owned(), + snapshot_time: snapshot_time, + last_modified: last_modified, + etag: etag, + content_length: content_length, + content_type: ctype, + content_encoding: content_encoding, + content_language: content_language, + content_md5: content_md5, + cache_control: cache_control, + x_ms_blob_sequence_number: x_ms_blob_sequence_number, + blob_type: blob_type, + lease_status: lease_status, + lease_state: lease_state, + lease_duration: lease_duration, + copy_id: copy_id, + copy_status: copy_status, + copy_source: copy_source, + copy_progress: cp_bytes, + copy_completion: copy_completion, + copy_status_description: copy_status_description, + }) + } + + pub fn from_headers( + blob_name: &str, + container_name: &str, + h: &Headers, + ) -> Result { + let content_type = match h.get::() { + Some(ct) => (ct as &Mime).clone(), + None => "application/octet-stream".parse::().unwrap(), + }; + trace!("content_type == {:?}", content_type); + + let content_length = match h.get::() { + Some(cl) => *(cl as &u64), + None => return Err(AzureError::HeaderNotFound("Content-Length".to_owned())), + }; + trace!("content_length == {:?}", content_length); + + let last_modified = match h.get::() { + Some(lm) => { + try!(from_azure_time(&lm.to_string())) + //{let te: TraversingError= e.into(); te})) + } + None => return Err(AzureError::HeaderNotFound("Last-Modified".to_owned())), + }; + trace!("last_modified == {:?}", last_modified); + + let etag = match h.get::() { + Some(lm) => lm.to_string(), + None => return Err(AzureError::HeaderNotFound("ETag".to_owned())), + }; + trace!("etag == {:?}", etag); + + let x_ms_blob_sequence_number = match h.get::() { + Some(lm) => Some(*(lm as &u64)), + None => None, + }; + trace!( + "x_ms_blob_sequence_number == {:?}", + x_ms_blob_sequence_number + ); + + let blob_type = match h.get::() { + Some(lm) => try!((&lm.to_string()).parse::()), + None => return Err(AzureError::HeaderNotFound("x-ms-blob-type".to_owned())), + }; + trace!("blob_type == {:?}", blob_type); + + let content_encoding = match h.get::() { + Some(ce) => Some(ce.to_string()), + None => None, + }; + trace!("content_encoding == {:?}", content_encoding); + + let content_language = match h.get::() { + Some(cl) => Some(cl.to_string()), + None => None, + }; + trace!("content_language == {:?}", content_language); + + let content_md5 = match h.get::() { + Some(md5) => Some(md5.to_string()), + None => None, + }; + trace!("content_md5 == {:?}", content_md5); + + // TODO + // let cache_control = match h.get::() { + // Some(cc) => Some(cc.to_string()), + // None => None + // }; + // println!("cache_control == {:?}", cache_control); + + let lease_status = match h.get::() { + Some(ls) => try!(ls.to_string().parse::()), + None => return Err(AzureError::HeaderNotFound("x-ms-lease-status".to_owned())), + }; + trace!("lease_status == {:?}", lease_status); + + + let lease_state = match h.get::() { + Some(ls) => try!(ls.to_string().parse::()), + None => return Err(AzureError::HeaderNotFound("x-ms-lease-state".to_owned())), + }; + trace!("lease_state == {:?}", lease_state); + + + let lease_duration = match h.get::() { + Some(ls) => Some(try!(ls.to_string().parse::())), + None => None, + }; + trace!("lease_duration == {:?}", lease_duration); + + // TODO: get the remaining headers + // (https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx) + + Ok(Blob { + name: blob_name.to_owned(), + container_name: container_name.to_owned(), + snapshot_time: None, + last_modified: last_modified, + etag: etag, + content_length: content_length, + content_type: content_type, + content_encoding: content_encoding, + content_language: content_language, + content_md5: content_md5, + cache_control: None, // TODO + x_ms_blob_sequence_number: x_ms_blob_sequence_number, + blob_type: blob_type, + lease_status: lease_status, + lease_state: lease_state, + lease_duration: lease_duration, + copy_id: None, // TODO + copy_status: None, // TODO + copy_source: None, // TODO + copy_progress: None, // TODO + copy_completion: None, // TODO + copy_status_description: None, // TODO + }) + } + + pub fn list( + c: &Client, + container_name: &str, + lbo: &ListBlobOptions, + ) -> impl Future, Error = AzureError> { + + let mut include = String::new(); + if lbo.include_snapshots { + include = include + "snapshots"; + } + if lbo.include_metadata { + if include.is_empty() { + include = include + ","; + } + include = include + "metadata"; + } + if lbo.include_uncommittedblobs { + if include.is_empty() { + include = include + ","; + } + include = include + "uncommittedblobs"; + } + if lbo.include_copy { + if include.is_empty() { + include = include + ","; + } + include = include + "copy"; + } + + let mut uri = format!( + "https://{}.blob.core.windows.\ + net/{}?restype=container&comp=list&maxresults={}", + c.account(), + container_name, + lbo.max_results + ); + + if !include.is_empty() { + uri = format!("{}&include={}", uri, include); + } + + if let Some(ref nm) = lbo.next_marker { + uri = format!("{}&marker={}", uri, nm); + } + + if let Some(ref pref) = lbo.prefix { + uri = format!("{}&prefix={}", uri, pref); + } + + if let Some(ref timeout) = lbo.timeout { + uri = format!("{}&timeout={}", uri, timeout); + } + + let req = c.perform_request(&uri, Method::Get, |_| {}, None); + + // we create a copy to move into the future's closure. + // We need to do this since the closure only accepts + // 'static lifetimes. + let container_name = container_name.to_owned(); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + done(incomplete_vector_from_response(&body, container_name)).from_err() + }) + }) + } +} + +//pub fn get( +// c: &Client, +// container_name: &str, +// blob_name: &str, +// snapshot: Option<&DateTime>, +// range: Option<&Range>, +// lease_id: Option<&LeaseId>, +//) -> Result<(Blob, Box), core::errors::AzureError> { +// let mut uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}", +// c.auth_scheme(), +// c.account(), +// container_name, +// blob_name +// ); + +// if let Some(snapshot) = snapshot { +// uri = format!("{}?snapshot={}", uri, snapshot.to_rfc2822()); +// } + +// let uri = uri; + +// trace!("uri == {:?}", uri); + +// let mut headers = Headers::new(); + +// if let Some(r) = range { +// headers.set(XMSRange(*r)); + +// // if range is < 4MB request md5 +// if r.end - r.start <= 1024 * 1024 * 4 { +// headers.set(XMSRangeGetContentMD5(true)); +// } +// } + +// if let Some(l) = lease_id { +// headers.set(XMSLeaseId(*l)); +// } + + +// let mut resp = try!(c.perform_request(&uri, Method::Get, &headers, None)); + +// // if we have requested a range the response code should be 207 (partial content) +// // otherwise 200 (ok). +// if range.is_some() { +// try!(check_status(&mut resp, StatusCode::PartialContent)); +// } else { +// try!(check_status(&mut resp, StatusCode::Ok)); +// } + +// let blob = try!(Blob::from_headers(blob_name, container_name, &resp.headers)); +// let r: Box = Box::new(resp); + +// Ok((blob, r)) +//} + +//pub fn put( +// &self, +// c: &Client, +// po: &PutOptions, +// r: Option<(&mut Read, u64)>, +//) -> Result<(), AzureError> { + +// // parameter sanity check +// match self.blob_type { +// BlobType::BlockBlob => { +// if r.is_none() { +// return Err(AzureError::InputParametersError( +// "cannot use put_blob with \ +// BlockBlob without a Read" +// .to_owned(), +// )); +// } +// } +// BlobType::PageBlob => { +// if r.is_some() { +// return Err(AzureError::InputParametersError( +// "cannot use put_blob with \ +// PageBlob with a Read" +// .to_owned(), +// )); +// } + +// if self.content_length % 512 != 0 { +// return Err(AzureError::InputParametersError( +// "PageBlob size must be aligned \ +// to 512 bytes boundary" +// .to_owned(), +// )); +// } +// } +// BlobType::AppendBlob => { +// if r.is_some() { +// return Err(AzureError::InputParametersError( +// "cannot use put_blob with \ +// AppendBlob with a Read" +// .to_owned(), +// )); +// } +// } +// } + +// let mut uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}", +// c.auth_scheme(), +// c.account(), +// self.container_name, +// self.name +// ); + +// if let Some(ref timeout) = po.timeout { +// uri = format!("{}&timeout={}", uri, timeout); +// } + +// let mut headers = Headers::new(); + +// headers.set(ContentType(self.content_type.clone())); + +// if let Some(ref content_encoding) = self.content_encoding { +// use hyper::header::Encoding; +// let enc = try!(content_encoding.parse::()); +// headers.set(ContentEncoding(vec![enc])); +// }; + +// // TODO Content-Language + +// if let Some(ref content_md5) = self.content_md5 { +// headers.set(ContentMD5(content_md5.to_owned())); +// }; + +// headers.set(XMSBlobType(self.blob_type)); + +// if let Some(ref lease_id) = po.lease_id { +// headers.set(XMSLeaseId(*lease_id)); +// } + +// // TODO x-ms-blob-content-disposition + +// if self.blob_type == BlobType::PageBlob { +// headers.set(XMSBlobContentLength(self.content_length)); +// } + +// let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, r)); + +// try!(core::errors::check_status(&mut resp, StatusCode::Created)); + +// Ok(()) +//} + +//pub fn lease( +// &self, +// c: &Client, +// la: LeaseAction, +// lbo: &LeaseBlobOptions, +//) -> Result { +// let mut uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}?comp=lease", +// c.auth_scheme(), +// c.account(), +// self.container_name, +// self.name +// ); +// if let Some(ref timeout) = lbo.timeout { +// uri = format!("{}&timeout={}", uri, timeout); +// } + +// let mut headers = Headers::new(); + +// if let Some(ref lease_id) = lbo.lease_id { +// headers.set(XMSLeaseId(lease_id.to_owned())); +// } + +// headers.set(XMSLeaseAction(la)); + +// if let Some(lease_break_period) = lbo.lease_break_period { +// headers.set(XMSLeaseBreakPeriod(lease_break_period)); +// } +// if let Some(lease_duration) = lbo.lease_duration { +// headers.set(XMSLeaseDurationSeconds(lease_duration)); +// } +// if let Some(ref proposed_lease_id) = lbo.proposed_lease_id { +// headers.set(XMSProposedLeaseId(*proposed_lease_id)); +// } +// if let Some(ref request_id) = lbo.request_id { +// headers.set(XMSClientRequestId(request_id.to_owned())); +// } + +// let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); + +// let expected_result = match la { +// LeaseAction::Acquire => StatusCode::Created, +// LeaseAction::Renew | LeaseAction::Change | LeaseAction::Release => StatusCode::Ok, +// LeaseAction::Break => StatusCode::Accepted, +// }; + +// try!(core::errors::check_status(&mut resp, expected_result)); + +// let lid = match resp.headers.get::() { +// Some(l) => l as &Uuid, +// None => return Err(AzureError::HeaderNotFound("x-ms-lease-id".to_owned())), +// }; + +// Ok(*lid) +//} + +//pub fn put_page( +// &self, +// c: &Client, +// range: &BA512Range, +// ppo: &PutPageOptions, +// content: (&mut Read, u64), +//) -> Result<(), AzureError> { + +// let mut uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}?comp=page", +// c.auth_scheme(), +// c.account(), +// self.container_name, +// self.name +// ); + +// if let Some(ref timeout) = ppo.timeout { +// uri = format!("{}&timeout={}", uri, timeout); +// } + +// let mut headers = Headers::new(); + +// headers.set(XMSRange(range.into())); +// headers.set(XMSBlobContentLength(content.1)); +// if let Some(ref lease_id) = ppo.lease_id { +// headers.set(XMSLeaseId(*lease_id)); +// } + +// headers.set(XMSPageWrite(PageWriteType::Update)); + +// let mut resp = try!(c.perform_request( +// &uri, +// Method::Put, +// &headers, +// Some(content) +// )); +// try!(core::errors::check_status(&mut resp, StatusCode::Created)); + +// Ok(()) +//} + +//pub fn put_block( +// &self, +// c: &Client, +// block_id: &str, +// pbo: &PutBlockOptions, +// content: (&mut Read, u64), +//) -> Result<(), AzureError> { + +// let encoded_block_id = base64::encode(block_id.as_bytes()); + +// let mut uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}?comp=block&blockid={}", +// c.auth_scheme(), +// c.account(), +// self.container_name, +// self.name, +// encoded_block_id +// ); + +// if let Some(ref timeout) = pbo.timeout { +// uri = format!("{}&timeout={}", uri, timeout); +// } + +// let mut headers = Headers::new(); + +// headers.set(XMSBlobContentLength(content.1)); + +// if let Some(ref lease_id) = pbo.lease_id { +// headers.set(XMSLeaseId(*lease_id)); +// } +// if let Some(ref request_id) = pbo.request_id { +// headers.set(XMSClientRequestId(request_id.to_owned())); +// } + +// let mut resp = try!(c.perform_request( +// &uri, +// Method::Put, +// &headers, +// Some(content) +// )); + +// try!(core::errors::check_status(&mut resp, StatusCode::Created)); + +// Ok(()) +//} + +//pub fn clear_page( +// &self, +// c: &Client, +// range: &BA512Range, +// lease_id: Option, +//) -> Result<(), AzureError> { + +// let uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}?comp=page", +// c.auth_scheme(), +// c.account(), +// self.container_name, +// self.name +// ); +// let mut headers = Headers::new(); + +// headers.set(XMSRange(range.into())); +// headers.set(XMSBlobContentLength(0)); +// if let Some(lease_id) = lease_id { +// headers.set(XMSLeaseId(lease_id)); +// } + +// headers.set(XMSPageWrite(PageWriteType::Clear)); + +// let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); +// try!(core::errors::check_status(&mut resp, StatusCode::Created)); + +// Ok(()) +//} + +//pub fn del( +// c: &Client, +// container_name: &str, +// blob_name: &str, +//) -> Result<(), core::errors::AzureError> { +// let uri = format!( +// "{}://{}.blob.core.windows.net/{}/{}", +// c.auth_scheme(), +// c.account(), +// container_name, +// blob_name +// ); +// let mut resp = try!(c.perform_request( +// &uri, +// Method::Delete, +// &Headers::new(), +// None +// )); +// try!(core::errors::check_status(&mut resp, StatusCode::Accepted)); +// Ok(()) +//} + +fn incomplete_vector_from_response( + body: &str, + container_name: String, +) -> Result, AzureError> { + let elem: Element = body.parse()?; + + let next_marker = match cast_optional::(&elem, &["NextMarker"])? { + Some(ref nm) if nm == "" => None, + Some(nm) => Some(nm), + None => None, + }; + + let mut v = Vec::new(); + for node_blob in traverse(&elem, &["Blobs", "Blob"], true)? { + // println!("{:?}", blob); + v.push(Blob::parse(node_blob, &container_name)?); + } + + Ok(IncompleteVector::::new(next_marker, v)) +} diff --git a/src/azure/storage/blob/put_block_options.rs b/src/azure/storage/blob/put_block_options.rs new file mode 100644 index 000000000..69f590dd5 --- /dev/null +++ b/src/azure/storage/blob/put_block_options.rs @@ -0,0 +1,14 @@ +use azure::core::lease::LeaseId; + +#[derive(Debug, Clone, PartialEq)] +pub struct PutBlockOptions { + pub lease_id: Option, + pub timeout: Option, + pub request_id: Option, +} + +pub const PUT_BLOCK_OPTIONS_DEFAULT: PutBlockOptions = PutBlockOptions { + timeout: None, + lease_id: None, + request_id: None, +}; diff --git a/src/azure/storage/blob/put_options.rs b/src/azure/storage/blob/put_options.rs new file mode 100644 index 000000000..502053575 --- /dev/null +++ b/src/azure/storage/blob/put_options.rs @@ -0,0 +1,12 @@ +use azure::core::lease::LeaseId; + +#[derive(Debug, Clone, PartialEq)] +pub struct PutOptions { + pub lease_id: Option, + pub timeout: Option, +} + +pub const PUT_OPTIONS_DEFAULT: PutOptions = PutOptions { + timeout: None, + lease_id: None, +}; diff --git a/src/azure/storage/blob/put_page_options.rs b/src/azure/storage/blob/put_page_options.rs new file mode 100644 index 000000000..79f352a73 --- /dev/null +++ b/src/azure/storage/blob/put_page_options.rs @@ -0,0 +1,12 @@ +use azure::core::lease::LeaseId; + +#[derive(Debug, Clone, PartialEq)] +pub struct PutPageOptions { + pub lease_id: Option, + pub timeout: Option, +} + +pub const PUT_PAGE_OPTIONS_DEFAULT: PutPageOptions = PutPageOptions { + timeout: None, + lease_id: None, +}; diff --git a/src/azure/storage/mod.rs b/src/azure/storage/mod.rs index a814a741e..64b372448 100644 --- a/src/azure/storage/mod.rs +++ b/src/azure/storage/mod.rs @@ -1,5 +1,5 @@ pub mod client; pub mod container; -//pub mod blob; +pub mod blob; //pub mod table; mod rest_client; From 1c4e4a040ef6b542e402a5bda4fb9b2b3464e023 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 28 Jun 2017 13:38:38 +0200 Subject: [PATCH 35/54] List blob migrated and tested --- examples/container00.rs | 18 +++++++++++- src/azure/core/errors.rs | 2 +- src/azure/core/parsing.rs | 11 ++++++- src/azure/storage/blob/mod.rs | 46 ++++++++++++++++++------------ src/azure/storage/container/mod.rs | 7 ++--- src/azure/storage/rest_client.rs | 14 ++++----- 6 files changed, 66 insertions(+), 32 deletions(-) diff --git a/examples/container00.rs b/examples/container00.rs index 88d804f6f..7020544b3 100644 --- a/examples/container00.rs +++ b/examples/container00.rs @@ -16,6 +16,8 @@ use azure_sdk_for_rust::azure::storage::client::Client; use azure_sdk_for_rust::azure::storage::container::Container; use azure_sdk_for_rust::azure::storage::container::LIST_CONTAINER_OPTIONS_DEFAULT; +use azure_sdk_for_rust::azure::storage::blob::{Blob, LIST_BLOB_OPTIONS_DEFAULT}; + fn main() { code().unwrap(); } @@ -35,11 +37,25 @@ fn code() -> Result<(), Box> { let future = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).map(|iv| { println!("List containers returned {} containers.", iv.len()); - for ref cont in iv.iter() { + for cont in iv.iter() { println!("\t{}", cont.name); } }); core.run(future)?; + + let future = Blob::list(&client, "mindarpa", &LIST_BLOB_OPTIONS_DEFAULT).map(|iv| { + println!("List blob returned {} blobs.", iv.len()); + for cont in iv.iter() { + println!( + "\t{}\t{} MB", + cont.name, + cont.content_length / (1024 * 1024) + ); + } + }); + + core.run(future)?; + Ok(()) } diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index dc943badf..74c23b74e 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -139,7 +139,7 @@ quick_error! { } ParsingError(err: ParsingError){ from() - display("Parsing error") + display("Parsing error: {:?}", err) } } } diff --git a/src/azure/core/parsing.rs b/src/azure/core/parsing.rs index d70bb782b..c317817e8 100644 --- a/src/azure/core/parsing.rs +++ b/src/azure/core/parsing.rs @@ -74,6 +74,12 @@ pub fn traverse<'a>( path: &[&str], ignore_empty_leaf: bool, ) -> Result, TraversingError> { + trace!( + "traverse(node == {:?}, path == {:?}, ignore_empty_leaf == {})", + node, + path, + ignore_empty_leaf + ); // println!("path.len() == {:?}", path.len()); if path.is_empty() { @@ -134,7 +140,10 @@ pub fn inner_text(node: &Element) -> Result<&str, TraversingError> { }; } - Err(TraversingError::TextNotFound) + Ok("") + + //println!("\n!!! node == {}", node); + //Err(TraversingError::TextNotFound) } #[inline] diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 7c79229e1..4c5999fc8 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -91,7 +91,7 @@ pub struct Blob { pub last_modified: DateTime, pub etag: String, pub content_length: u64, - pub content_type: Mime, + pub content_type: Option, pub content_encoding: Option, pub content_language: Option, pub content_md5: Option, @@ -179,10 +179,15 @@ impl Blob { } let ctype = { - if let Ok(ctype) = content_type.parse::() { - ctype + trace!("content_type == {:?}", content_type); + if content_type != "" { + if let Ok(ctype) = content_type.parse::() { + Some(ctype) + } else { + return Err(AzureError::GenericError); + } } else { - return Err(AzureError::GenericError); + None } }; @@ -314,7 +319,7 @@ impl Blob { last_modified: last_modified, etag: etag, content_length: content_length, - content_type: content_type, + content_type: Some(content_type), content_encoding: content_encoding, content_language: content_language, content_md5: content_md5, @@ -337,29 +342,29 @@ impl Blob { c: &Client, container_name: &str, lbo: &ListBlobOptions, - ) -> impl Future, Error = AzureError> { + ) -> Box, Error = AzureError>> { let mut include = String::new(); if lbo.include_snapshots { - include = include + "snapshots"; + include += "snapshots"; } if lbo.include_metadata { if include.is_empty() { - include = include + ","; + include += ","; } - include = include + "metadata"; + include += "metadata"; } if lbo.include_uncommittedblobs { if include.is_empty() { - include = include + ","; + include += ","; } - include = include + "uncommittedblobs"; + include += "uncommittedblobs"; } if lbo.include_copy { if include.is_empty() { - include = include + ","; + include += ","; } - include = include + "copy"; + include += "copy"; } let mut uri = format!( @@ -393,11 +398,11 @@ impl Blob { // 'static lifetimes. let container_name = container_name.to_owned(); - done(req).from_err().and_then(move |future_response| { + Box::new(done(req).from_err().and_then(move |future_response| { check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { - done(incomplete_vector_from_response(&body, container_name)).from_err() + done(incomplete_vector_from_response(&body, &container_name)).from_err() }) - }) + })) } } @@ -745,10 +750,13 @@ impl Blob { // Ok(()) //} +#[inline] fn incomplete_vector_from_response( body: &str, - container_name: String, + container_name: &str, ) -> Result, AzureError> { + trace!("body = {}", body); + let elem: Element = body.parse()?; let next_marker = match cast_optional::(&elem, &["NextMarker"])? { @@ -757,10 +765,12 @@ fn incomplete_vector_from_response( None => None, }; + debug!("next_marker == {:?}", next_marker); + let mut v = Vec::new(); for node_blob in traverse(&elem, &["Blobs", "Blob"], true)? { // println!("{:?}", blob); - v.push(Blob::parse(node_blob, &container_name)?); + v.push(Blob::parse(node_blob, container_name)?); } Ok(IncompleteVector::::new(next_marker, v)) diff --git a/src/azure/storage/container/mod.rs b/src/azure/storage/container/mod.rs index 57ae58e39..89231ff97 100644 --- a/src/azure/storage/container/mod.rs +++ b/src/azure/storage/container/mod.rs @@ -132,7 +132,7 @@ impl Container { pub fn list( c: &Client, lco: &ListContainerOptions, - ) -> impl Future, Error = AzureError> { + ) -> Box, Error = AzureError>> { let mut uri = format!( "https://{}.blob.core.windows.net?comp=list&maxresults={}", c.account(), @@ -157,15 +157,14 @@ impl Container { let req = c.perform_request(&uri, Method::Get, |_| {}, None); - done(req).from_err().and_then(move |future_response| { + Box::new(done(req).from_err().and_then(move |future_response| { check_status_extract_body(future_response, StatusCode::Ok).and_then(|body| { done(incomplete_vector_from_response(&body)).from_err() }) - }) + })) } } -#[inline] fn incomplete_vector_from_response(body: &str) -> Result, AzureError> { let elem: Element = body.parse()?; diff --git a/src/azure/storage/rest_client.rs b/src/azure/storage/rest_client.rs index 68770ad49..8ff0445b9 100644 --- a/src/azure/storage/rest_client.rs +++ b/src/azure/storage/rest_client.rs @@ -212,10 +212,10 @@ fn canonicalized_resource_table(u: &url::Url) -> String { fn canonicalized_resource(u: &url::Url) -> String { let mut can_res: String = String::new(); - can_res = can_res + "/"; + can_res += "/"; let account = get_account(u); - can_res = can_res + &account; + can_res += &account; let paths = u.path_segments().unwrap(); @@ -226,9 +226,9 @@ fn canonicalized_resource(u: &url::Url) -> String { path.push_str(&*p); } - can_res = can_res + &path; + can_res += &path; } - can_res = can_res + "\n"; + can_res += "\n"; // query parameters let query_pairs = u.query_pairs(); //.into_owned(); @@ -257,12 +257,12 @@ fn canonicalized_resource(u: &url::Url) -> String { for (i, item) in ret.iter().enumerate() { if i > 0 { - can_res = can_res + "," + can_res += "," } - can_res = can_res + item; + can_res += item; } - can_res = can_res + "\n"; + can_res += "\n"; } }; From b6a21b23ea89d578266e178152b05b193c664a35 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 28 Jun 2017 18:35:42 +0200 Subject: [PATCH 36/54] Complted blob::get without testing --- src/azure/core/errors.rs | 34 ++ src/azure/storage/blob/mod.rs | 693 +++++++++++++++++----------------- 2 files changed, 382 insertions(+), 345 deletions(-) diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index 74c23b74e..b064ba2ac 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -150,6 +150,40 @@ impl From<()> for AzureError { } } +#[inline] +pub fn extract_status_headers_and_body( + resp: hyper::client::FutureResponse, +) -> impl Future), Error = AzureError> { + resp.from_err().and_then(|res| { + let status = res.status(); + let headers = res.headers().clone(); + res.body().concat2().from_err().and_then(move |whole_body| { + ok((status, headers, Vec::from(&whole_body as &[u8]))) + }) + }) +} + +#[inline] +pub fn check_status_extract_headers_and_body( + resp: hyper::client::FutureResponse, + expected_status_code: hyper::StatusCode, +) -> impl Future), Error = AzureError> { + extract_status_headers_and_body(resp).and_then(move |(status, headers, body)| { + if status == expected_status_code { + ok((headers, body)) + } else { + match str::from_utf8(&body) { + Ok(s_body) => err(AzureError::UnexpectedHTTPResult(UnexpectedHTTPResult { + expected: expected_status_code, + received: status, + body: s_body.to_owned(), + })), + Err(error) => err(AzureError::UTF8Error(error)), + } + } + }) +} + #[inline] pub fn extract_status_and_body( resp: hyper::client::FutureResponse, diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 4c5999fc8..585c91824 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -41,7 +41,8 @@ use std::fmt; use std::io::Read; -use azure::core::errors::{TraversingError, AzureError, check_status_extract_body}; +use azure::core::errors::{TraversingError, AzureError, check_status_extract_body, + UnexpectedHTTPResult, check_status_extract_headers_and_body}; use azure::core::parsing::FromStringOptional; use azure::core::range::Range; @@ -404,351 +405,353 @@ impl Blob { }) })) } -} -//pub fn get( -// c: &Client, -// container_name: &str, -// blob_name: &str, -// snapshot: Option<&DateTime>, -// range: Option<&Range>, -// lease_id: Option<&LeaseId>, -//) -> Result<(Blob, Box), core::errors::AzureError> { -// let mut uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}", -// c.auth_scheme(), -// c.account(), -// container_name, -// blob_name -// ); - -// if let Some(snapshot) = snapshot { -// uri = format!("{}?snapshot={}", uri, snapshot.to_rfc2822()); -// } - -// let uri = uri; - -// trace!("uri == {:?}", uri); - -// let mut headers = Headers::new(); - -// if let Some(r) = range { -// headers.set(XMSRange(*r)); - -// // if range is < 4MB request md5 -// if r.end - r.start <= 1024 * 1024 * 4 { -// headers.set(XMSRangeGetContentMD5(true)); -// } -// } - -// if let Some(l) = lease_id { -// headers.set(XMSLeaseId(*l)); -// } - - -// let mut resp = try!(c.perform_request(&uri, Method::Get, &headers, None)); - -// // if we have requested a range the response code should be 207 (partial content) -// // otherwise 200 (ok). -// if range.is_some() { -// try!(check_status(&mut resp, StatusCode::PartialContent)); -// } else { -// try!(check_status(&mut resp, StatusCode::Ok)); -// } - -// let blob = try!(Blob::from_headers(blob_name, container_name, &resp.headers)); -// let r: Box = Box::new(resp); - -// Ok((blob, r)) -//} - -//pub fn put( -// &self, -// c: &Client, -// po: &PutOptions, -// r: Option<(&mut Read, u64)>, -//) -> Result<(), AzureError> { - -// // parameter sanity check -// match self.blob_type { -// BlobType::BlockBlob => { -// if r.is_none() { -// return Err(AzureError::InputParametersError( -// "cannot use put_blob with \ -// BlockBlob without a Read" -// .to_owned(), -// )); -// } -// } -// BlobType::PageBlob => { -// if r.is_some() { -// return Err(AzureError::InputParametersError( -// "cannot use put_blob with \ -// PageBlob with a Read" -// .to_owned(), -// )); -// } - -// if self.content_length % 512 != 0 { -// return Err(AzureError::InputParametersError( -// "PageBlob size must be aligned \ -// to 512 bytes boundary" -// .to_owned(), -// )); -// } -// } -// BlobType::AppendBlob => { -// if r.is_some() { -// return Err(AzureError::InputParametersError( -// "cannot use put_blob with \ -// AppendBlob with a Read" -// .to_owned(), -// )); -// } -// } -// } - -// let mut uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}", -// c.auth_scheme(), -// c.account(), -// self.container_name, -// self.name -// ); - -// if let Some(ref timeout) = po.timeout { -// uri = format!("{}&timeout={}", uri, timeout); -// } - -// let mut headers = Headers::new(); - -// headers.set(ContentType(self.content_type.clone())); - -// if let Some(ref content_encoding) = self.content_encoding { -// use hyper::header::Encoding; -// let enc = try!(content_encoding.parse::()); -// headers.set(ContentEncoding(vec![enc])); -// }; - -// // TODO Content-Language - -// if let Some(ref content_md5) = self.content_md5 { -// headers.set(ContentMD5(content_md5.to_owned())); -// }; - -// headers.set(XMSBlobType(self.blob_type)); - -// if let Some(ref lease_id) = po.lease_id { -// headers.set(XMSLeaseId(*lease_id)); -// } - -// // TODO x-ms-blob-content-disposition - -// if self.blob_type == BlobType::PageBlob { -// headers.set(XMSBlobContentLength(self.content_length)); -// } - -// let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, r)); - -// try!(core::errors::check_status(&mut resp, StatusCode::Created)); - -// Ok(()) -//} - -//pub fn lease( -// &self, -// c: &Client, -// la: LeaseAction, -// lbo: &LeaseBlobOptions, -//) -> Result { -// let mut uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}?comp=lease", -// c.auth_scheme(), -// c.account(), -// self.container_name, -// self.name -// ); -// if let Some(ref timeout) = lbo.timeout { -// uri = format!("{}&timeout={}", uri, timeout); -// } - -// let mut headers = Headers::new(); - -// if let Some(ref lease_id) = lbo.lease_id { -// headers.set(XMSLeaseId(lease_id.to_owned())); -// } - -// headers.set(XMSLeaseAction(la)); - -// if let Some(lease_break_period) = lbo.lease_break_period { -// headers.set(XMSLeaseBreakPeriod(lease_break_period)); -// } -// if let Some(lease_duration) = lbo.lease_duration { -// headers.set(XMSLeaseDurationSeconds(lease_duration)); -// } -// if let Some(ref proposed_lease_id) = lbo.proposed_lease_id { -// headers.set(XMSProposedLeaseId(*proposed_lease_id)); -// } -// if let Some(ref request_id) = lbo.request_id { -// headers.set(XMSClientRequestId(request_id.to_owned())); -// } - -// let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); - -// let expected_result = match la { -// LeaseAction::Acquire => StatusCode::Created, -// LeaseAction::Renew | LeaseAction::Change | LeaseAction::Release => StatusCode::Ok, -// LeaseAction::Break => StatusCode::Accepted, -// }; - -// try!(core::errors::check_status(&mut resp, expected_result)); - -// let lid = match resp.headers.get::() { -// Some(l) => l as &Uuid, -// None => return Err(AzureError::HeaderNotFound("x-ms-lease-id".to_owned())), -// }; - -// Ok(*lid) -//} - -//pub fn put_page( -// &self, -// c: &Client, -// range: &BA512Range, -// ppo: &PutPageOptions, -// content: (&mut Read, u64), -//) -> Result<(), AzureError> { - -// let mut uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}?comp=page", -// c.auth_scheme(), -// c.account(), -// self.container_name, -// self.name -// ); - -// if let Some(ref timeout) = ppo.timeout { -// uri = format!("{}&timeout={}", uri, timeout); -// } - -// let mut headers = Headers::new(); - -// headers.set(XMSRange(range.into())); -// headers.set(XMSBlobContentLength(content.1)); -// if let Some(ref lease_id) = ppo.lease_id { -// headers.set(XMSLeaseId(*lease_id)); -// } - -// headers.set(XMSPageWrite(PageWriteType::Update)); - -// let mut resp = try!(c.perform_request( -// &uri, -// Method::Put, -// &headers, -// Some(content) -// )); -// try!(core::errors::check_status(&mut resp, StatusCode::Created)); - -// Ok(()) -//} - -//pub fn put_block( -// &self, -// c: &Client, -// block_id: &str, -// pbo: &PutBlockOptions, -// content: (&mut Read, u64), -//) -> Result<(), AzureError> { - -// let encoded_block_id = base64::encode(block_id.as_bytes()); - -// let mut uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}?comp=block&blockid={}", -// c.auth_scheme(), -// c.account(), -// self.container_name, -// self.name, -// encoded_block_id -// ); - -// if let Some(ref timeout) = pbo.timeout { -// uri = format!("{}&timeout={}", uri, timeout); -// } - -// let mut headers = Headers::new(); - -// headers.set(XMSBlobContentLength(content.1)); - -// if let Some(ref lease_id) = pbo.lease_id { -// headers.set(XMSLeaseId(*lease_id)); -// } -// if let Some(ref request_id) = pbo.request_id { -// headers.set(XMSClientRequestId(request_id.to_owned())); -// } - -// let mut resp = try!(c.perform_request( -// &uri, -// Method::Put, -// &headers, -// Some(content) -// )); - -// try!(core::errors::check_status(&mut resp, StatusCode::Created)); - -// Ok(()) -//} - -//pub fn clear_page( -// &self, -// c: &Client, -// range: &BA512Range, -// lease_id: Option, -//) -> Result<(), AzureError> { - -// let uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}?comp=page", -// c.auth_scheme(), -// c.account(), -// self.container_name, -// self.name -// ); -// let mut headers = Headers::new(); - -// headers.set(XMSRange(range.into())); -// headers.set(XMSBlobContentLength(0)); -// if let Some(lease_id) = lease_id { -// headers.set(XMSLeaseId(lease_id)); -// } - -// headers.set(XMSPageWrite(PageWriteType::Clear)); - -// let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); -// try!(core::errors::check_status(&mut resp, StatusCode::Created)); - -// Ok(()) -//} - -//pub fn del( -// c: &Client, -// container_name: &str, -// blob_name: &str, -//) -> Result<(), core::errors::AzureError> { -// let uri = format!( -// "{}://{}.blob.core.windows.net/{}/{}", -// c.auth_scheme(), -// c.account(), -// container_name, -// blob_name -// ); -// let mut resp = try!(c.perform_request( -// &uri, -// Method::Delete, -// &Headers::new(), -// None -// )); -// try!(core::errors::check_status(&mut resp, StatusCode::Accepted)); -// Ok(()) -//} + pub fn get( + c: &Client, + container_name: &str, + blob_name: &str, + snapshot: Option<&DateTime>, + range: Option<&Range>, + lease_id: Option<&LeaseId>, + ) -> Box), Error = AzureError>> { + let mut uri = format!( + "https://{}.blob.core.windows.net/{}/{}", + c.account(), + container_name, + blob_name + ); + + if let Some(snapshot) = snapshot { + uri = format!("{}?snapshot={}", uri, snapshot.to_rfc2822()); + } + + trace!("uri == {:?}", uri); + + let req = c.perform_request( + &uri, + Method::Get, + |ref mut headers| { + if let Some(r) = range { + headers.set(XMSRange(*r)); + + // if range is < 4MB request md5 + if r.end - r.start <= 1024 * 1024 * 4 { + headers.set(XMSRangeGetContentMD5(true)); + } + } + if let Some(l) = lease_id { + headers.set(XMSLeaseId(*l)); + } + }, + None, + ); + + let expected_status_code = if range.is_some() { + StatusCode::PartialContent + } else { + StatusCode::Ok + }; + + let container_name = container_name.to_owned(); + let blob_name = blob_name.to_owned(); + + Box::new(done(req).from_err().and_then(move |future_response| { + check_status_extract_headers_and_body(future_response, expected_status_code) + .and_then(move |(headers, body)| { + done(Blob::from_headers(&blob_name, &container_name, &headers)) + .and_then(move |blob| ok((blob, body))) + }) + })) + } + + //pub fn put( + // &self, + // c: &Client, + // po: &PutOptions, + // r: Option<(&mut Read, u64)>, + //) -> Result<(), AzureError> { + + // // parameter sanity check + // match self.blob_type { + // BlobType::BlockBlob => { + // if r.is_none() { + // return Err(AzureError::InputParametersError( + // "cannot use put_blob with \ + // BlockBlob without a Read" + // .to_owned(), + // )); + // } + // } + // BlobType::PageBlob => { + // if r.is_some() { + // return Err(AzureError::InputParametersError( + // "cannot use put_blob with \ + // PageBlob with a Read" + // .to_owned(), + // )); + // } + + // if self.content_length % 512 != 0 { + // return Err(AzureError::InputParametersError( + // "PageBlob size must be aligned \ + // to 512 bytes boundary" + // .to_owned(), + // )); + // } + // } + // BlobType::AppendBlob => { + // if r.is_some() { + // return Err(AzureError::InputParametersError( + // "cannot use put_blob with \ + // AppendBlob with a Read" + // .to_owned(), + // )); + // } + // } + // } + + // let mut uri = format!( + // "{}://{}.blob.core.windows.net/{}/{}", + // c.auth_scheme(), + // c.account(), + // self.container_name, + // self.name + // ); + + // if let Some(ref timeout) = po.timeout { + // uri = format!("{}&timeout={}", uri, timeout); + // } + + // let mut headers = Headers::new(); + + // headers.set(ContentType(self.content_type.clone())); + + // if let Some(ref content_encoding) = self.content_encoding { + // use hyper::header::Encoding; + // let enc = try!(content_encoding.parse::()); + // headers.set(ContentEncoding(vec![enc])); + // }; + + // // TODO Content-Language + + // if let Some(ref content_md5) = self.content_md5 { + // headers.set(ContentMD5(content_md5.to_owned())); + // }; + + // headers.set(XMSBlobType(self.blob_type)); + + // if let Some(ref lease_id) = po.lease_id { + // headers.set(XMSLeaseId(*lease_id)); + // } + + // // TODO x-ms-blob-content-disposition + + // if self.blob_type == BlobType::PageBlob { + // headers.set(XMSBlobContentLength(self.content_length)); + // } + + // let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, r)); + + // try!(core::errors::check_status(&mut resp, StatusCode::Created)); + + // Ok(()) + //} + + //pub fn lease( + // &self, + // c: &Client, + // la: LeaseAction, + // lbo: &LeaseBlobOptions, + //) -> Result { + // let mut uri = format!( + // "{}://{}.blob.core.windows.net/{}/{}?comp=lease", + // c.auth_scheme(), + // c.account(), + // self.container_name, + // self.name + // ); + // if let Some(ref timeout) = lbo.timeout { + // uri = format!("{}&timeout={}", uri, timeout); + // } + + // let mut headers = Headers::new(); + + // if let Some(ref lease_id) = lbo.lease_id { + // headers.set(XMSLeaseId(lease_id.to_owned())); + // } + + // headers.set(XMSLeaseAction(la)); + + // if let Some(lease_break_period) = lbo.lease_break_period { + // headers.set(XMSLeaseBreakPeriod(lease_break_period)); + // } + // if let Some(lease_duration) = lbo.lease_duration { + // headers.set(XMSLeaseDurationSeconds(lease_duration)); + // } + // if let Some(ref proposed_lease_id) = lbo.proposed_lease_id { + // headers.set(XMSProposedLeaseId(*proposed_lease_id)); + // } + // if let Some(ref request_id) = lbo.request_id { + // headers.set(XMSClientRequestId(request_id.to_owned())); + // } + + // let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); + + // let expected_result = match la { + // LeaseAction::Acquire => StatusCode::Created, + // LeaseAction::Renew | LeaseAction::Change | LeaseAction::Release => StatusCode::Ok, + // LeaseAction::Break => StatusCode::Accepted, + // }; + + // try!(core::errors::check_status(&mut resp, expected_result)); + + // let lid = match resp.headers.get::() { + // Some(l) => l as &Uuid, + // None => return Err(AzureError::HeaderNotFound("x-ms-lease-id".to_owned())), + // }; + + // Ok(*lid) + //} + + //pub fn put_page( + // &self, + // c: &Client, + // range: &BA512Range, + // ppo: &PutPageOptions, + // content: (&mut Read, u64), + //) -> Result<(), AzureError> { + + // let mut uri = format!( + // "{}://{}.blob.core.windows.net/{}/{}?comp=page", + // c.auth_scheme(), + // c.account(), + // self.container_name, + // self.name + // ); + + // if let Some(ref timeout) = ppo.timeout { + // uri = format!("{}&timeout={}", uri, timeout); + // } + + // let mut headers = Headers::new(); + + // headers.set(XMSRange(range.into())); + // headers.set(XMSBlobContentLength(content.1)); + // if let Some(ref lease_id) = ppo.lease_id { + // headers.set(XMSLeaseId(*lease_id)); + // } + + // headers.set(XMSPageWrite(PageWriteType::Update)); + + // let mut resp = try!(c.perform_request( + // &uri, + // Method::Put, + // &headers, + // Some(content) + // )); + // try!(core::errors::check_status(&mut resp, StatusCode::Created)); + + // Ok(()) + //} + + //pub fn put_block( + // &self, + // c: &Client, + // block_id: &str, + // pbo: &PutBlockOptions, + // content: (&mut Read, u64), + //) -> Result<(), AzureError> { + + // let encoded_block_id = base64::encode(block_id.as_bytes()); + + // let mut uri = format!( + // "{}://{}.blob.core.windows.net/{}/{}?comp=block&blockid={}", + // c.auth_scheme(), + // c.account(), + // self.container_name, + // self.name, + // encoded_block_id + // ); + + // if let Some(ref timeout) = pbo.timeout { + // uri = format!("{}&timeout={}", uri, timeout); + // } + + // let mut headers = Headers::new(); + + // headers.set(XMSBlobContentLength(content.1)); + + // if let Some(ref lease_id) = pbo.lease_id { + // headers.set(XMSLeaseId(*lease_id)); + // } + // if let Some(ref request_id) = pbo.request_id { + // headers.set(XMSClientRequestId(request_id.to_owned())); + // } + + // let mut resp = try!(c.perform_request( + // &uri, + // Method::Put, + // &headers, + // Some(content) + // )); + + // try!(core::errors::check_status(&mut resp, StatusCode::Created)); + + // Ok(()) + //} + + //pub fn clear_page( + // &self, + // c: &Client, + // range: &BA512Range, + // lease_id: Option, + //) -> Result<(), AzureError> { + + // let uri = format!( + // "{}://{}.blob.core.windows.net/{}/{}?comp=page", + // c.auth_scheme(), + // c.account(), + // self.container_name, + // self.name + // ); + // let mut headers = Headers::new(); + + // headers.set(XMSRange(range.into())); + // headers.set(XMSBlobContentLength(0)); + // if let Some(lease_id) = lease_id { + // headers.set(XMSLeaseId(lease_id)); + // } + + // headers.set(XMSPageWrite(PageWriteType::Clear)); + + // let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); + // try!(core::errors::check_status(&mut resp, StatusCode::Created)); + + // Ok(()) + //} + + //pub fn del( + // c: &Client, + // container_name: &str, + // blob_name: &str, + //) -> Result<(), core::errors::AzureError> { + // let uri = format!( + // "{}://{}.blob.core.windows.net/{}/{}", + // c.auth_scheme(), + // c.account(), + // container_name, + // blob_name + // ); + // let mut resp = try!(c.perform_request( + // &uri, + // Method::Delete, + // &Headers::new(), + // None + // )); + // try!(core::errors::check_status(&mut resp, StatusCode::Accepted)); + // Ok(()) + //} +} #[inline] fn incomplete_vector_from_response( From f15493ddafe1e82c57483a218111bfe2687d7030 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 28 Jun 2017 23:34:36 +0200 Subject: [PATCH 37/54] Added blob get example --- examples/blob00.rs | 55 ++++++++++++++++++++++++++++++++++++++++ src/azure/core/errors.rs | 6 +++++ 2 files changed, 61 insertions(+) create mode 100644 examples/blob00.rs diff --git a/examples/blob00.rs b/examples/blob00.rs new file mode 100644 index 000000000..d2c7211d9 --- /dev/null +++ b/examples/blob00.rs @@ -0,0 +1,55 @@ +extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; + +use std::error::Error; + +use futures::future::*; +use tokio_core::reactor::Core; + +use azure_sdk_for_rust::azure::storage::client::Client; +use azure_sdk_for_rust::azure::storage::blob::Blob; + +use std::str; + +fn main() { + code().unwrap(); +} + +// We run a separate method to use the elegant quotation mark operator. +// A series of unwrap(), unwrap() would have achieved the same result. +fn code() -> Result<(), Box> { + // First we retrieve the account name and master key from environment variables. + let account = std::env::var("STORAGE_ACCOUNT") + .expect("Set env variable STORAGE_ACCOUNT first!"); + let master_key = std::env::var("STORAGE_MASTER_KEY") + .expect("Set env variable STORAGE_MASTER_KEY first!"); + + let container = std::env::args() + .nth(1) + .expect("please specify container name as command line parameter"); + let blob = std::env::args() + .nth(2) + .expect("please specify blob name as command line parameter"); + + let mut core = Core::new()?; + + let client = Client::new(&core.handle(), &account, &master_key)?; + + let future = Blob::get(&client, &container, &blob, None, None, None) + .and_then(move |(blob, content)| { + done(String::from_utf8(content)) + .map(move |s_content| { + println!("blob == {:?}", blob); + println!("s_content == {}", s_content); + }) + .from_err() + }); + core.run(future)?; + + Ok(()) +} diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index b064ba2ac..b9aaa69cf 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -12,6 +12,7 @@ use serde_json; use futures::Future; use futures::Stream; use std::str; +use std::string; use futures::future::*; #[derive(Debug, Clone, PartialEq)] @@ -103,6 +104,11 @@ quick_error! { display("UTF8 conversion error: {}", err) cause(err) } + FromUtf8Error(err: string::FromUtf8Error) { + from() + display("FromUTF8 error: {}", err) + cause(err) + } NativeTLSError(err: native_tls::Error) { from() display("Native TLS error: {}", err) From e1a74d7061454841029bf634aba3d63763649117 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 28 Jun 2017 23:37:07 +0200 Subject: [PATCH 38/54] Switched to impl Future --- src/azure/storage/blob/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 585c91824..1cbf4179d 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -413,7 +413,7 @@ impl Blob { snapshot: Option<&DateTime>, range: Option<&Range>, lease_id: Option<&LeaseId>, - ) -> Box), Error = AzureError>> { + ) -> impl Future), Error = AzureError> { let mut uri = format!( "https://{}.blob.core.windows.net/{}/{}", c.account(), @@ -455,13 +455,13 @@ impl Blob { let container_name = container_name.to_owned(); let blob_name = blob_name.to_owned(); - Box::new(done(req).from_err().and_then(move |future_response| { + done(req).from_err().and_then(move |future_response| { check_status_extract_headers_and_body(future_response, expected_status_code) .and_then(move |(headers, body)| { done(Blob::from_headers(&blob_name, &container_name, &headers)) .and_then(move |blob| ok((blob, body))) }) - })) + }) } //pub fn put( From 28c8623d509ff69e477efacd35dc441d94653f4f Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 28 Jun 2017 23:59:56 +0200 Subject: [PATCH 39/54] Changed core methods to support &[u8] instead of &str --- src/azure/storage/blob/mod.rs | 57 +++++++++++++++++--------------- src/azure/storage/client.rs | 4 +-- src/azure/storage/rest_client.rs | 5 +-- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 1cbf4179d..d33a65c56 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -468,8 +468,8 @@ impl Blob { // &self, // c: &Client, // po: &PutOptions, - // r: Option<(&mut Read, u64)>, - //) -> Result<(), AzureError> { + // r: Option<&[u8]>, + //) -> Box> { // // parameter sanity check // match self.blob_type { @@ -522,39 +522,42 @@ impl Blob { // uri = format!("{}&timeout={}", uri, timeout); // } - // let mut headers = Headers::new(); + // let req = c.perform_request( + // &uri, + // Method::Put, + // |ref mut headers| { + // headers.set(ContentType(self.content_type.clone())); - // headers.set(ContentType(self.content_type.clone())); + // if let Some(ref content_encoding) = self.content_encoding { + // use hyper::header::Encoding; + // let enc = content_encoding.parse::()?; + // headers.set(ContentEncoding(vec![enc])); + // }; - // if let Some(ref content_encoding) = self.content_encoding { - // use hyper::header::Encoding; - // let enc = try!(content_encoding.parse::()); - // headers.set(ContentEncoding(vec![enc])); - // }; + // // TODO Content-Language - // // TODO Content-Language + // if let Some(ref content_md5) = self.content_md5 { + // headers.set(ContentMD5(content_md5.to_owned())); + // }; - // if let Some(ref content_md5) = self.content_md5 { - // headers.set(ContentMD5(content_md5.to_owned())); - // }; - - // headers.set(XMSBlobType(self.blob_type)); + // headers.set(XMSBlobType(self.blob_type)); - // if let Some(ref lease_id) = po.lease_id { - // headers.set(XMSLeaseId(*lease_id)); - // } - - // // TODO x-ms-blob-content-disposition - - // if self.blob_type == BlobType::PageBlob { - // headers.set(XMSBlobContentLength(self.content_length)); - // } + // if let Some(ref lease_id) = po.lease_id { + // headers.set(XMSLeaseId(*lease_id)); + // } - // let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, r)); + // // TODO x-ms-blob-content-disposition - // try!(core::errors::check_status(&mut resp, StatusCode::Created)); + // if self.blob_type == BlobType::PageBlob { + // headers.set(XMSBlobContentLength(self.content_length)); + // } + // }, + // r, + // ); - // Ok(()) + // Box::new(done(req).from_err().and_then(move |future_response| { + // check_status_extract_body(future_response, StatusCode::Created) + // })) //} //pub fn lease( diff --git a/src/azure/storage/client.rs b/src/azure/storage/client.rs index 7ddad20fb..4e1564f7a 100644 --- a/src/azure/storage/client.rs +++ b/src/azure/storage/client.rs @@ -46,7 +46,7 @@ impl Client { uri: &str, method: Method, headers_func: F, - request_body: Option<&str>, + request_body: Option<&[u8]>, ) -> Result where F: FnOnce(&mut Headers), @@ -67,7 +67,7 @@ impl Client { segment: &str, method: Method, headers_func: F, - request_str: Option<&str>, + request_str: Option<&[u8]>, ) -> Result where F: FnOnce(&mut Headers), diff --git a/src/azure/storage/rest_client.rs b/src/azure/storage/rest_client.rs index 8ff0445b9..355157fff 100644 --- a/src/azure/storage/rest_client.rs +++ b/src/azure/storage/rest_client.rs @@ -288,7 +288,7 @@ pub fn perform_request( http_method: Method, azure_key: &str, headers_func: F, - request_body: Option<&str>, + request_body: Option<&[u8]>, service_type: ServiceType, ) -> Result where @@ -317,8 +317,9 @@ where .set(XMSVersion(AZURE_VERSION.to_owned())); if let Some(body) = request_body { + let b = Vec::from(body); request.headers_mut().set(ContentLength(body.len() as u64)); - request.set_body(body.to_string()); + request.set_body(b); } let auth = generate_authorization( From 27101f76296f2c569a1077b63a9c9ec96baea8bb Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 29 Jun 2017 19:01:48 +0200 Subject: [PATCH 40/54] Added put blob example --- examples/put_blob.rs | 109 +++++++++++++++++++++ src/azure/storage/blob/mod.rs | 177 ++++++++++++++++++---------------- 2 files changed, 202 insertions(+), 84 deletions(-) create mode 100644 examples/put_blob.rs diff --git a/examples/put_blob.rs b/examples/put_blob.rs new file mode 100644 index 000000000..2a752e2a8 --- /dev/null +++ b/examples/put_blob.rs @@ -0,0 +1,109 @@ +extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; +extern crate chrono; + +use std::error::Error; + +use futures::future::*; +use tokio_core::reactor::Core; + +use azure_sdk_for_rust::azure::storage::client::Client; +use azure_sdk_for_rust::azure::storage::blob::{BlobType, Blob, PUT_OPTIONS_DEFAULT}; +use azure_sdk_for_rust::azure::core::lease::{LeaseState, LeaseStatus}; +use azure_sdk_for_rust::azure::core::errors::AzureError; + +use std::str; +use std::fs::metadata; +use std::fs::File; +use std::path; + +use hyper::mime::Mime; +use std::io::Read; + +fn main() { + code().unwrap(); +} + +// We run a separate method to use the elegant quotation mark operator. +// A series of unwrap(), unwrap() would have achieved the same result. +fn code() -> Result<(), Box> { + // First we retrieve the account name and master key from environment variables. + let account = std::env::var("STORAGE_ACCOUNT") + .expect("Set env variable STORAGE_ACCOUNT first!"); + let master_key = std::env::var("STORAGE_MASTER_KEY") + .expect("Set env variable STORAGE_MASTER_KEY first!"); + + let container_name = std::env::args().nth(1).expect( + "please specify container name as first command line parameter", + ); + let file_name = std::env::args() + .nth(2) + .expect("please specify file name as second command line parameter"); + + let mut core = Core::new()?; + + let client = Client::new(&core.handle(), &account, &master_key)?; + + let metadata = metadata(&file_name)?; + + let name = { + let path = path::Path::new(&file_name); + + let name = match path.file_name() { + Some(name) => name, + None => return Err(Box::new(AzureError::GenericError)), + }; + + match name.to_str() { + Some(n) => n.to_owned(), + None => return Err(Box::new(AzureError::GenericError)), + } + }; + + let contents = { + let mut file = File::open(file_name)?; + let mut v = Vec::new(); + file.read_to_end(&mut v)?; + v + }; + + let new_blob = Blob { + name: name.to_owned(), + container_name: container_name.to_owned(), + snapshot_time: None, + last_modified: chrono::Utc::now(), + etag: "".to_owned(), + content_length: metadata.len(), + content_type: Some("application/octet-stream".parse::().unwrap()), + content_encoding: None, + content_language: None, + content_md5: None, + cache_control: None, + x_ms_blob_sequence_number: None, + blob_type: BlobType::BlockBlob, + lease_status: LeaseStatus::Unlocked, + lease_state: LeaseState::Available, + lease_duration: None, + copy_id: None, + copy_status: None, + copy_source: None, + copy_progress: None, + copy_completion: None, + copy_status_description: None, + }; + + let future = new_blob + .put(&client, &PUT_OPTIONS_DEFAULT, Some(&contents)) + .map(move |_| { + println!("{} uploaded", name); + }); + + core.run(future)?; + + Ok(()) +} diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index d33a65c56..f6df2dd03 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -464,101 +464,110 @@ impl Blob { }) } - //pub fn put( - // &self, - // c: &Client, - // po: &PutOptions, - // r: Option<&[u8]>, - //) -> Box> { - - // // parameter sanity check - // match self.blob_type { - // BlobType::BlockBlob => { - // if r.is_none() { - // return Err(AzureError::InputParametersError( - // "cannot use put_blob with \ - // BlockBlob without a Read" - // .to_owned(), - // )); - // } - // } - // BlobType::PageBlob => { - // if r.is_some() { - // return Err(AzureError::InputParametersError( - // "cannot use put_blob with \ - // PageBlob with a Read" - // .to_owned(), - // )); - // } - - // if self.content_length % 512 != 0 { - // return Err(AzureError::InputParametersError( - // "PageBlob size must be aligned \ - // to 512 bytes boundary" - // .to_owned(), - // )); - // } - // } - // BlobType::AppendBlob => { - // if r.is_some() { - // return Err(AzureError::InputParametersError( - // "cannot use put_blob with \ - // AppendBlob with a Read" - // .to_owned(), - // )); - // } - // } - // } + pub fn put( + &self, + c: &Client, + po: &PutOptions, + r: Option<&[u8]>, + ) -> Box> { + + // parameter sanity check + match self.blob_type { + BlobType::BlockBlob => { + if r.is_none() { + return Box::new(err(AzureError::InputParametersError( + "cannot use put_blob with \ + BlockBlob without a Read" + .to_owned(), + ))); + } + } + BlobType::PageBlob => { + if r.is_some() { + return Box::new(err(AzureError::InputParametersError( + "cannot use put_blob with \ + PageBlob with a Read" + .to_owned(), + ))); + } - // let mut uri = format!( - // "{}://{}.blob.core.windows.net/{}/{}", - // c.auth_scheme(), - // c.account(), - // self.container_name, - // self.name - // ); + if self.content_length % 512 != 0 { + return Box::new(err(AzureError::InputParametersError( + "PageBlob size must be aligned \ + to 512 bytes boundary" + .to_owned(), + ))); + } + } + BlobType::AppendBlob => { + if r.is_some() { + return Box::new(err(AzureError::InputParametersError( + "cannot use put_blob with \ + AppendBlob with a Read" + .to_owned(), + ))); + } + } + } - // if let Some(ref timeout) = po.timeout { - // uri = format!("{}&timeout={}", uri, timeout); - // } + let ce = if let Some(ref content_encoding) = self.content_encoding { + use hyper::header::Encoding; + match content_encoding.parse::() { + Ok(ct) => Some(ct), + Err(error) => return Box::new(err(error).from_err()), + } + } else { + None + }; - // let req = c.perform_request( - // &uri, - // Method::Put, - // |ref mut headers| { - // headers.set(ContentType(self.content_type.clone())); - // if let Some(ref content_encoding) = self.content_encoding { - // use hyper::header::Encoding; - // let enc = content_encoding.parse::()?; - // headers.set(ContentEncoding(vec![enc])); - // }; + let mut uri = format!( + "https://{}.blob.core.windows.net/{}/{}", + c.account(), + self.container_name, + self.name + ); - // // TODO Content-Language + if let Some(ref timeout) = po.timeout { + uri = format!("{}&timeout={}", uri, timeout); + } - // if let Some(ref content_md5) = self.content_md5 { - // headers.set(ContentMD5(content_md5.to_owned())); - // }; + let req = c.perform_request( + &uri, + Method::Put, + move |ref mut headers| { + if let Some(ct) = self.content_type.clone() { + headers.set(ContentType(ct)); + } - // headers.set(XMSBlobType(self.blob_type)); + if let Some(ce) = ce { + headers.set(ContentEncoding(vec![ce])); + } + // TODO Content-Language - // if let Some(ref lease_id) = po.lease_id { - // headers.set(XMSLeaseId(*lease_id)); - // } + if let Some(ref content_md5) = self.content_md5 { + headers.set(ContentMD5(content_md5.to_owned())); + }; - // // TODO x-ms-blob-content-disposition + headers.set(XMSBlobType(self.blob_type)); - // if self.blob_type == BlobType::PageBlob { - // headers.set(XMSBlobContentLength(self.content_length)); - // } - // }, - // r, - // ); + if let Some(ref lease_id) = po.lease_id { + headers.set(XMSLeaseId(*lease_id)); + } - // Box::new(done(req).from_err().and_then(move |future_response| { - // check_status_extract_body(future_response, StatusCode::Created) - // })) - //} + // TODO x-ms-blob-content-disposition + + if self.blob_type == BlobType::PageBlob { + headers.set(XMSBlobContentLength(self.content_length)); + } + }, + r, + ); + + Box::new(done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created).and_then(|_| ok(())) + })) + } //pub fn lease( // &self, From 7f1eff2b94393a803613a634b5283de7a11c48e9 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 29 Jun 2017 22:04:50 +0200 Subject: [PATCH 41/54] Tried impl Future on put blob, and failed --- examples/put_blob.rs | 2 +- src/azure/storage/blob/mod.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/put_blob.rs b/examples/put_blob.rs index 2a752e2a8..fa6c17bec 100644 --- a/examples/put_blob.rs +++ b/examples/put_blob.rs @@ -79,7 +79,7 @@ fn code() -> Result<(), Box> { last_modified: chrono::Utc::now(), etag: "".to_owned(), content_length: metadata.len(), - content_type: Some("application/octet-stream".parse::().unwrap()), + content_type: Some("text/plain".parse::().unwrap()), content_encoding: None, content_language: None, content_md5: None, diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index f6df2dd03..9e6dd7694 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -514,13 +514,12 @@ impl Blob { use hyper::header::Encoding; match content_encoding.parse::() { Ok(ct) => Some(ct), - Err(error) => return Box::new(err(error).from_err()), + Err(error) => return Box::new(err(AzureError::HyperError(error))), } } else { None }; - let mut uri = format!( "https://{}.blob.core.windows.net/{}/{}", c.account(), @@ -564,9 +563,14 @@ impl Blob { r, ); - Box::new(done(req).from_err().and_then(move |future_response| { - check_status_extract_body(future_response, StatusCode::Created).and_then(|_| ok(())) - })) + Box::new( + done(req) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created) + }) + .and_then(|_| ok(())), + ) } //pub fn lease( From 599bac4dbeac9fa33370714aebe506b12cbc568b Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 29 Jun 2017 23:07:40 +0200 Subject: [PATCH 42/54] Completed lease blob --- examples/put_blob.rs | 37 ++++++++++- src/azure/storage/blob/mod.rs | 114 +++++++++++++++++++--------------- 2 files changed, 97 insertions(+), 54 deletions(-) diff --git a/examples/put_blob.rs b/examples/put_blob.rs index fa6c17bec..959d24b24 100644 --- a/examples/put_blob.rs +++ b/examples/put_blob.rs @@ -1,5 +1,6 @@ extern crate azure_sdk_for_rust; +extern crate env_logger; extern crate futures; extern crate tokio_core; extern crate tokio; @@ -13,8 +14,11 @@ use futures::future::*; use tokio_core::reactor::Core; use azure_sdk_for_rust::azure::storage::client::Client; -use azure_sdk_for_rust::azure::storage::blob::{BlobType, Blob, PUT_OPTIONS_DEFAULT}; -use azure_sdk_for_rust::azure::core::lease::{LeaseState, LeaseStatus}; +use azure_sdk_for_rust::azure::storage::blob::{BlobType, Blob, PUT_OPTIONS_DEFAULT, + LEASE_BLOB_OPTIONS_DEFAULT, + LIST_BLOB_OPTIONS_DEFAULT}; +use azure_sdk_for_rust::azure::core::lease::{LeaseState, LeaseStatus, LeaseAction}; + use azure_sdk_for_rust::azure::core::errors::AzureError; use std::str; @@ -26,6 +30,7 @@ use hyper::mime::Mime; use std::io::Read; fn main() { + env_logger::init().unwrap(); code().unwrap(); } @@ -99,11 +104,37 @@ fn code() -> Result<(), Box> { let future = new_blob .put(&client, &PUT_OPTIONS_DEFAULT, Some(&contents)) - .map(move |_| { + .map(|_| { println!("{} uploaded", name); }); core.run(future)?; + let mut lbo = LEASE_BLOB_OPTIONS_DEFAULT.clone(); + lbo.lease_duration = Some(15); + let future = new_blob.lease(&client, LeaseAction::Acquire, &lbo).map( + |_| { + println!("Blob leased"); + }, + ); + + core.run(future)?; + + let future = Blob::list(&client, &container_name, &LIST_BLOB_OPTIONS_DEFAULT).map(|blobs| { + match blobs.iter().find(|blob| blob.name == name) { + Some(retrieved_blob) => { + println!( + "Our blob ({}/{}) == {:?}", + container_name, + name, + retrieved_blob + ) + } + None => println!("Blob not found... something is amiss..."), + }; + }); + + core.run(future)?; + Ok(()) } diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 9e6dd7694..b9ebc6f6a 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -290,6 +290,11 @@ impl Blob { // }; // println!("cache_control == {:?}", cache_control); + //println!( + // "h.get::() == {:?}", + // h.get::() + //); + let lease_status = match h.get::() { Some(ls) => try!(ls.to_string().parse::()), None => return Err(AzureError::HeaderNotFound("x-ms-lease-status".to_owned())), @@ -305,7 +310,7 @@ impl Blob { let lease_duration = match h.get::() { - Some(ls) => Some(try!(ls.to_string().parse::())), + Some(ld) => Some(ld.to_string().parse::()?), None => None, }; trace!("lease_duration == {:?}", lease_duration); @@ -344,7 +349,6 @@ impl Blob { container_name: &str, lbo: &ListBlobOptions, ) -> Box, Error = AzureError>> { - let mut include = String::new(); if lbo.include_snapshots { include += "snapshots"; @@ -573,61 +577,69 @@ impl Blob { ) } - //pub fn lease( - // &self, - // c: &Client, - // la: LeaseAction, - // lbo: &LeaseBlobOptions, - //) -> Result { - // let mut uri = format!( - // "{}://{}.blob.core.windows.net/{}/{}?comp=lease", - // c.auth_scheme(), - // c.account(), - // self.container_name, - // self.name - // ); - // if let Some(ref timeout) = lbo.timeout { - // uri = format!("{}&timeout={}", uri, timeout); - // } - - // let mut headers = Headers::new(); - - // if let Some(ref lease_id) = lbo.lease_id { - // headers.set(XMSLeaseId(lease_id.to_owned())); - // } + pub fn lease( + &self, + c: &Client, + la: LeaseAction, + lbo: &LeaseBlobOptions, + ) -> impl Future { + let mut uri = format!( + "https://{}.blob.core.windows.net/{}/{}?comp=lease", + c.account(), + self.container_name, + self.name + ); + if let Some(ref timeout) = lbo.timeout { + uri = format!("{}&timeout={}", uri, timeout); + } - // headers.set(XMSLeaseAction(la)); + let req = c.perform_request( + &uri, + Method::Put, + move |ref mut headers| { + if let Some(ref lease_id) = lbo.lease_id { + headers.set(XMSLeaseId(lease_id.to_owned())); + } - // if let Some(lease_break_period) = lbo.lease_break_period { - // headers.set(XMSLeaseBreakPeriod(lease_break_period)); - // } - // if let Some(lease_duration) = lbo.lease_duration { - // headers.set(XMSLeaseDurationSeconds(lease_duration)); - // } - // if let Some(ref proposed_lease_id) = lbo.proposed_lease_id { - // headers.set(XMSProposedLeaseId(*proposed_lease_id)); - // } - // if let Some(ref request_id) = lbo.request_id { - // headers.set(XMSClientRequestId(request_id.to_owned())); - // } + headers.set(XMSLeaseAction(la)); - // let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); + if let Some(lease_break_period) = lbo.lease_break_period { + headers.set(XMSLeaseBreakPeriod(lease_break_period)); + } + if let Some(lease_duration) = lbo.lease_duration { + headers.set(XMSLeaseDurationSeconds(lease_duration)); + } + if let Some(ref proposed_lease_id) = lbo.proposed_lease_id { + headers.set(XMSProposedLeaseId(*proposed_lease_id)); + } + if let Some(ref request_id) = lbo.request_id { + headers.set(XMSClientRequestId(request_id.to_owned())); + } - // let expected_result = match la { - // LeaseAction::Acquire => StatusCode::Created, - // LeaseAction::Renew | LeaseAction::Change | LeaseAction::Release => StatusCode::Ok, - // LeaseAction::Break => StatusCode::Accepted, - // }; + }, + None, + ); - // try!(core::errors::check_status(&mut resp, expected_result)); + let expected_result = match la { + LeaseAction::Acquire => StatusCode::Created, + LeaseAction::Renew | LeaseAction::Change | LeaseAction::Release => StatusCode::Ok, + LeaseAction::Break => StatusCode::Accepted, + }; - // let lid = match resp.headers.get::() { - // Some(l) => l as &Uuid, - // None => return Err(AzureError::HeaderNotFound("x-ms-lease-id".to_owned())), - // }; + done(req) + .from_err() + .and_then(move |future_response| { + check_status_extract_headers_and_body(future_response, expected_result) + }) + .and_then(|(headers, _)| { + let lid = match headers.get::() { + Some(l) => l as &Uuid, + None => return err(AzureError::HeaderNotFound("x-ms-lease-id".to_owned())), + }; - // Ok(*lid) - //} + ok(*lid) + }) + } //pub fn put_page( // &self, @@ -788,7 +800,7 @@ fn incomplete_vector_from_response( let mut v = Vec::new(); for node_blob in traverse(&elem, &["Blobs", "Blob"], true)? { - // println!("{:?}", blob); + //println!("{:?}", node_blob); v.push(Blob::parse(node_blob, container_name)?); } From 539171e3d702367456e674e4c81e1b861ac698e9 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 29 Jun 2017 23:32:47 +0200 Subject: [PATCH 43/54] Migrated delete blob (to enhance with lease ID) --- examples/put_blob.rs | 40 ++++++++++++++++++++----------- src/azure/core/errors.rs | 3 +++ src/azure/storage/blob/mod.rs | 44 +++++++++++++++++------------------ 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/examples/put_blob.rs b/examples/put_blob.rs index 959d24b24..81a62614b 100644 --- a/examples/put_blob.rs +++ b/examples/put_blob.rs @@ -84,6 +84,9 @@ fn code() -> Result<(), Box> { last_modified: chrono::Utc::now(), etag: "".to_owned(), content_length: metadata.len(), + // here we pass text/plain as content_type. This means your browser will + // try to show you the file if you click on it in the Azure portal. + // Make sure to send a text file :) content_type: Some("text/plain".parse::().unwrap()), content_encoding: None, content_language: None, @@ -113,26 +116,37 @@ fn code() -> Result<(), Box> { let mut lbo = LEASE_BLOB_OPTIONS_DEFAULT.clone(); lbo.lease_duration = Some(15); let future = new_blob.lease(&client, LeaseAction::Acquire, &lbo).map( - |_| { + |lease_id| { println!("Blob leased"); + lease_id }, ); - core.run(future)?; + let lease_id = core.run(future)?; + println!("lease id == {:?}", lease_id); - let future = Blob::list(&client, &container_name, &LIST_BLOB_OPTIONS_DEFAULT).map(|blobs| { - match blobs.iter().find(|blob| blob.name == name) { + let future = Blob::list(&client, &container_name, &LIST_BLOB_OPTIONS_DEFAULT) + .map(|blobs| match blobs.iter().find(|blob| blob.name == name) { Some(retrieved_blob) => { - println!( - "Our blob ({}/{}) == {:?}", - container_name, - name, - retrieved_blob - ) + let sc = (*retrieved_blob).clone(); + Ok(sc) } - None => println!("Blob not found... something is amiss..."), - }; - }); + None => Err(AzureError::GenericErrorWithText( + "our blob should be here... where is it?".to_owned(), + )), + }); + + let retrieved_blob = core.run(future)??; + println!("retrieved_blob == {:?}", retrieved_blob); + + // TODO + // this will fail because we did not specify a valid leaseID. + // I need to improve the method to allow that option at least. + let future = Blob::delete( + &client, + &retrieved_blob.container_name, + &retrieved_blob.name, + ); core.run(future)?; diff --git a/src/azure/core/errors.rs b/src/azure/core/errors.rs index b9aaa69cf..59f994da1 100644 --- a/src/azure/core/errors.rs +++ b/src/azure/core/errors.rs @@ -77,6 +77,9 @@ quick_error! { display("Parse error") } GenericError + GenericErrorWithText(err: String) { + display("Generic error: {}", err) + } ParsingError(err: ParsingError){ from() display("Parsing error") diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index b9ebc6f6a..71ee02b46 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -84,7 +84,7 @@ header! { (XMSBlobType, "x-ms-blob-type") => [BlobType] } header! { (XMSBlobContentDisposition, "x-ms-blob-content-disposition") => [String] } header! { (XMSPageWrite, "x-ms-page-write") => [PageWriteType] } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Blob { pub name: String, pub container_name: String, @@ -758,27 +758,27 @@ impl Blob { // Ok(()) //} - //pub fn del( - // c: &Client, - // container_name: &str, - // blob_name: &str, - //) -> Result<(), core::errors::AzureError> { - // let uri = format!( - // "{}://{}.blob.core.windows.net/{}/{}", - // c.auth_scheme(), - // c.account(), - // container_name, - // blob_name - // ); - // let mut resp = try!(c.perform_request( - // &uri, - // Method::Delete, - // &Headers::new(), - // None - // )); - // try!(core::errors::check_status(&mut resp, StatusCode::Accepted)); - // Ok(()) - //} + pub fn delete( + c: &Client, + container_name: &str, + blob_name: &str, + ) -> impl Future { + let uri = format!( + "https://{}.blob.core.windows.net/{}/{}", + c.account(), + container_name, + blob_name + ); + + let req = c.perform_request(&uri, Method::Delete, |_| {}, None); + + done(req) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Accepted) + }) + .and_then(|_| ok(())) + } } #[inline] From 0ef411d51cfb91d4c96a4fc0654681bec89be5d2 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 30 Jun 2017 11:59:29 +0200 Subject: [PATCH 44/54] Exposed lease_id as blob::delete parameter --- examples/put_blob.rs | 18 ++++++++++++++++-- src/azure/storage/blob/mod.rs | 10 +++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/examples/put_blob.rs b/examples/put_blob.rs index 81a62614b..c082199fc 100644 --- a/examples/put_blob.rs +++ b/examples/put_blob.rs @@ -139,15 +139,29 @@ fn code() -> Result<(), Box> { let retrieved_blob = core.run(future)??; println!("retrieved_blob == {:?}", retrieved_blob); - // TODO // this will fail because we did not specify a valid leaseID. - // I need to improve the method to allow that option at least. let future = Blob::delete( &client, &retrieved_blob.container_name, &retrieved_blob.name, + None, ); + core.run(future).unwrap_or_else(|err| { + println!( + "Failed to delete a locked blob without specifying a lease: {:?}", + err + ); + }); + + // this will work because we did specify the valid leaseID. + let future = Blob::delete( + &client, + &retrieved_blob.container_name, + &retrieved_blob.name, + Some(lease_id), + ).map(|_| println!("Blob deleted!")); + core.run(future)?; Ok(()) diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 71ee02b46..4232300c4 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -762,6 +762,7 @@ impl Blob { c: &Client, container_name: &str, blob_name: &str, + lease_id: Option, ) -> impl Future { let uri = format!( "https://{}.blob.core.windows.net/{}/{}", @@ -770,7 +771,14 @@ impl Blob { blob_name ); - let req = c.perform_request(&uri, Method::Delete, |_| {}, None); + let req = c.perform_request( + &uri, + Method::Delete, + |ref mut headers| if let Some(lease_id) = lease_id { + headers.set(XMSLeaseId(lease_id)); + }, + None, + ); done(req) .from_err() From 0934975921d54a05f306ecf769f6e29a1cb1b832 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 30 Jun 2017 12:02:59 +0200 Subject: [PATCH 45/54] Change lease_id a ref --- Cargo.lock | 57 ++++++++++++++--------------------- examples/put_blob.rs | 2 +- src/azure/storage/blob/mod.rs | 4 +-- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b64e313e5..2f9196170 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,19 +8,19 @@ dependencies = [ "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -414,7 +414,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -425,7 +425,7 @@ dependencies = [ "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -519,12 +519,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -549,7 +549,7 @@ dependencies = [ "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -593,23 +593,13 @@ dependencies = [ "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "thread-id" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "thread_local" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "thread-id 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -619,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -717,7 +707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unreachable" -version = "0.1.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -740,7 +730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "uuid" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -788,7 +778,7 @@ dependencies = [ "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" "checksum hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8590f308416a428dca05ca67020283105344e94059fd2f02cc72e9c913c30fb" -"checksum hyper-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "faea5efeea2b1cd9596a5d2792ff13de558cc2e578f32310b3ad2e8c35c349f3" +"checksum hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c81fa95203e2a6087242c38691a0210f23e9f3f8f944350bd676522132e2985" "checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37" "checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" @@ -817,7 +807,7 @@ dependencies = [ "checksum quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c36987d4978eb1be2e422b1e0423a557923a5c3e7e6f31d5699e9aafaefa469" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" -"checksum redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3041aeb6000db123d2c9c751433f526e1f404b23213bd733167ab770c3989b4d" +"checksum redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a357d14a12e90a37d658725df0e6468504750b5948b9710f83f94a0c5818e8" "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" @@ -830,8 +820,8 @@ dependencies = [ "checksum security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "42ddf098d78d0b64564b23ee6345d07573e7d10e52ad86875d89ddf5f8378a02" "checksum security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "5bacdada57ea62022500c457c8571c17dfb5e6240b7c8eac5916ffa8c7138a55" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" -"checksum serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f530d36fb84ec48fb7146936881f026cdbf4892028835fd9398475f82c1bb4" -"checksum serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "10552fad5500771f3902d0c5ba187c5881942b811b7ba0d8fbbfbf84d80806d3" +"checksum serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6a7c6b751a2e8d5df57a5ff71b5b4fc8aaee9ee28ff1341d640dd130bb5f4f7a" +"checksum serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2f6ca58905ebd3c3b285a8a6d4f3ac92b92c0d7951d5649b1bdd212549c06639" "checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a" "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" @@ -840,8 +830,7 @@ dependencies = [ "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" -"checksum thread-id 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2af4d6289a69a35c4d3aea737add39685f2784122c28119a7713165a63d68c9d" -"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" +"checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" "checksum tokio 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad008a2129866117d3b0424c047f1302f3d8974ff58c6ade59ec14cd6c59fee2" "checksum tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6a20ba4738d283cac7495ca36e045c80c2a8df3e05dd0909b17a06646af5a7ed" @@ -853,10 +842,10 @@ dependencies = [ "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -"checksum uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d0f5103675a280a926ec2f9b7bcc2ef49367df54e8c570c3311fec919f9a8b" +"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/examples/put_blob.rs b/examples/put_blob.rs index c082199fc..0d69768ac 100644 --- a/examples/put_blob.rs +++ b/examples/put_blob.rs @@ -159,7 +159,7 @@ fn code() -> Result<(), Box> { &client, &retrieved_blob.container_name, &retrieved_blob.name, - Some(lease_id), + Some(&lease_id), ).map(|_| println!("Blob deleted!")); core.run(future)?; diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 4232300c4..7fd539377 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -762,7 +762,7 @@ impl Blob { c: &Client, container_name: &str, blob_name: &str, - lease_id: Option, + lease_id: Option<&LeaseId>, ) -> impl Future { let uri = format!( "https://{}.blob.core.windows.net/{}/{}", @@ -775,7 +775,7 @@ impl Blob { &uri, Method::Delete, |ref mut headers| if let Some(lease_id) = lease_id { - headers.set(XMSLeaseId(lease_id)); + headers.set(XMSLeaseId(*lease_id)); }, None, ); From ce698a3522e434e7a64bcf2ed94d715c09fc9866 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 30 Jun 2017 12:15:10 +0200 Subject: [PATCH 46/54] Completed blob migration --- src/azure/storage/blob/mod.rs | 245 +++++++++++++++++----------------- 1 file changed, 125 insertions(+), 120 deletions(-) diff --git a/src/azure/storage/blob/mod.rs b/src/azure/storage/blob/mod.rs index 7fd539377..2cd1eb24d 100644 --- a/src/azure/storage/blob/mod.rs +++ b/src/azure/storage/blob/mod.rs @@ -25,7 +25,6 @@ use futures::future::*; use azure::core::lease::{LeaseId, LeaseStatus, LeaseState, LeaseDuration, LeaseAction}; use azure::storage::client::Client; -use azure::core; use azure::storage::rest_client::{XMSRange, ContentMD5, XMSLeaseStatus, XMSLeaseDuration, XMSLeaseState, XMSLeaseId, XMSRangeGetContentMD5, XMSClientRequestId, XMSLeaseAction, XMSLeaseDurationSeconds, @@ -39,10 +38,8 @@ use std::str::FromStr; use azure::core::enumerations; use std::fmt; -use std::io::Read; - use azure::core::errors::{TraversingError, AzureError, check_status_extract_body, - UnexpectedHTTPResult, check_status_extract_headers_and_body}; + check_status_extract_headers_and_body}; use azure::core::parsing::FromStringOptional; use azure::core::range::Range; @@ -641,122 +638,130 @@ impl Blob { }) } - //pub fn put_page( - // &self, - // c: &Client, - // range: &BA512Range, - // ppo: &PutPageOptions, - // content: (&mut Read, u64), - //) -> Result<(), AzureError> { - - // let mut uri = format!( - // "{}://{}.blob.core.windows.net/{}/{}?comp=page", - // c.auth_scheme(), - // c.account(), - // self.container_name, - // self.name - // ); - - // if let Some(ref timeout) = ppo.timeout { - // uri = format!("{}&timeout={}", uri, timeout); - // } - - // let mut headers = Headers::new(); - - // headers.set(XMSRange(range.into())); - // headers.set(XMSBlobContentLength(content.1)); - // if let Some(ref lease_id) = ppo.lease_id { - // headers.set(XMSLeaseId(*lease_id)); - // } - - // headers.set(XMSPageWrite(PageWriteType::Update)); - - // let mut resp = try!(c.perform_request( - // &uri, - // Method::Put, - // &headers, - // Some(content) - // )); - // try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - // Ok(()) - //} - - //pub fn put_block( - // &self, - // c: &Client, - // block_id: &str, - // pbo: &PutBlockOptions, - // content: (&mut Read, u64), - //) -> Result<(), AzureError> { - - // let encoded_block_id = base64::encode(block_id.as_bytes()); - - // let mut uri = format!( - // "{}://{}.blob.core.windows.net/{}/{}?comp=block&blockid={}", - // c.auth_scheme(), - // c.account(), - // self.container_name, - // self.name, - // encoded_block_id - // ); - - // if let Some(ref timeout) = pbo.timeout { - // uri = format!("{}&timeout={}", uri, timeout); - // } - - // let mut headers = Headers::new(); - - // headers.set(XMSBlobContentLength(content.1)); - - // if let Some(ref lease_id) = pbo.lease_id { - // headers.set(XMSLeaseId(*lease_id)); - // } - // if let Some(ref request_id) = pbo.request_id { - // headers.set(XMSClientRequestId(request_id.to_owned())); - // } - - // let mut resp = try!(c.perform_request( - // &uri, - // Method::Put, - // &headers, - // Some(content) - // )); - - // try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - // Ok(()) - //} - - //pub fn clear_page( - // &self, - // c: &Client, - // range: &BA512Range, - // lease_id: Option, - //) -> Result<(), AzureError> { - - // let uri = format!( - // "{}://{}.blob.core.windows.net/{}/{}?comp=page", - // c.auth_scheme(), - // c.account(), - // self.container_name, - // self.name - // ); - // let mut headers = Headers::new(); - - // headers.set(XMSRange(range.into())); - // headers.set(XMSBlobContentLength(0)); - // if let Some(lease_id) = lease_id { - // headers.set(XMSLeaseId(lease_id)); - // } - - // headers.set(XMSPageWrite(PageWriteType::Clear)); - - // let mut resp = try!(c.perform_request(&uri, Method::Put, &headers, None)); - // try!(core::errors::check_status(&mut resp, StatusCode::Created)); - - // Ok(()) - //} + pub fn put_page( + &self, + c: &Client, + range: &BA512Range, + ppo: &PutPageOptions, + content: &[u8], + ) -> impl Future { + + let mut uri = format!( + "https://{}.blob.core.windows.net/{}/{}?comp=page", + c.account(), + self.container_name, + self.name + ); + + if let Some(ref timeout) = ppo.timeout { + uri = format!("{}&timeout={}", uri, timeout); + } + + let req = c.perform_request( + &uri, + Method::Put, + move |ref mut headers| { + headers.set(XMSRange(range.into())); + headers.set(XMSBlobContentLength(content.len() as u64)); + if let Some(ref lease_id) = ppo.lease_id { + headers.set(XMSLeaseId(*lease_id)); + } + + headers.set(XMSPageWrite(PageWriteType::Update)); + }, + Some(content), + ); + + done(req) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created) + }) + .and_then(|_| ok(())) + } + + pub fn put_block( + &self, + c: &Client, + block_id: &str, + pbo: &PutBlockOptions, + content: &[u8], + ) -> impl Future { + + let encoded_block_id = base64::encode(block_id.as_bytes()); + + let mut uri = format!( + "https://{}.blob.core.windows.net/{}/{}?comp=block&blockid={}", + c.account(), + self.container_name, + self.name, + encoded_block_id + ); + + if let Some(ref timeout) = pbo.timeout { + uri = format!("{}&timeout={}", uri, timeout); + } + + let req = c.perform_request( + &uri, + Method::Put, + move |ref mut headers| { + headers.set(XMSBlobContentLength(content.len() as u64)); + + if let Some(ref lease_id) = pbo.lease_id { + headers.set(XMSLeaseId(*lease_id)); + } + if let Some(ref request_id) = pbo.request_id { + headers.set(XMSClientRequestId(request_id.to_owned())); + } + + }, + Some(content), + ); + + done(req) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created) + }) + .and_then(|_| ok(())) + } + + pub fn clear_page( + &self, + c: &Client, + range: &BA512Range, + lease_id: Option<&LeaseId>, + ) -> impl Future { + + let uri = format!( + "https://{}.blob.core.windows.net/{}/{}?comp=page", + c.account(), + self.container_name, + self.name + ); + let req = c.perform_request( + &uri, + Method::Put, + move |ref mut headers| { + headers.set(XMSRange(range.into())); + headers.set(XMSBlobContentLength(0)); + if let Some(lease_id) = lease_id { + headers.set(XMSLeaseId(*lease_id)); + } + + headers.set(XMSPageWrite(PageWriteType::Clear)); + }, + None, + ); + + done(req) + .from_err() + .and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created) + }) + .and_then(|_| ok(())) + } pub fn delete( c: &Client, From 7a1cdc46c715458336017685a89235c842ae543e Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 30 Jun 2017 12:17:32 +0200 Subject: [PATCH 47/54] Reimported table (pre migration) --- src/azure/storage/mod.rs | 2 +- src/azure/storage/table/batch.rs | 139 ++++++++++++++++ src/azure/storage/table/mod.rs | 274 +++++++++++++++++++++++++++++++ 3 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 src/azure/storage/table/batch.rs create mode 100644 src/azure/storage/table/mod.rs diff --git a/src/azure/storage/mod.rs b/src/azure/storage/mod.rs index 64b372448..e72eb474c 100644 --- a/src/azure/storage/mod.rs +++ b/src/azure/storage/mod.rs @@ -1,5 +1,5 @@ pub mod client; pub mod container; pub mod blob; -//pub mod table; +pub mod table; mod rest_client; diff --git a/src/azure/storage/table/batch.rs b/src/azure/storage/table/batch.rs new file mode 100644 index 000000000..0e5bb1cc4 --- /dev/null +++ b/src/azure/storage/table/batch.rs @@ -0,0 +1,139 @@ +/* Table batch support +* Current limitation: +1. Only support single changeset in a batch request +2. Only allow PUT and GET in changeset +*/ +use serde::Serialize; +use super::entity_path; +use serde_json; + +const BATCH_BEGIN: &'static str = r#"--batch_a1e9d677-b28b-435e-a89e-87e6a768a431 +Content-Type: multipart/mixed; boundary=changeset_8a28b620-b4bb-458c-a177-0959fb14c977 + +"#; +const BATCH_END: &'static str = "--batch_a1e9d677-b28b-435e-a89e-87e6a768a431\n"; +const CHANGESET_BEGIN: &'static str = r#"--changeset_8a28b620-b4bb-458c-a177-0959fb14c977 +Content-Type: application/http +Content-Transfer-Encoding: binary + +"#; +const CHANGESET_END: &'static str = "--changeset_8a28b620-b4bb-458c-a177-0959fb14c977--\n"; +const UPDATE_HEADER: &'static str = "Content-Type: application/json\n"; +const ACCEPT_HEADER: &'static str = "Accept: application/json;odata=nometadata\n"; +const IF_MATCH_HEADER: &'static str = "If-Match: *\n"; + +// RowKey, Payload. Payload None for deletion +pub struct BatchItem(String, Option); + +impl BatchItem { + pub fn new(row_key: String, value: Option) -> Self { + BatchItem(row_key, value) + } +} + +pub fn generate_batch_payload( + uri_prefix: &str, + table: &str, + primary_key: &str, + items: &[BatchItem], +) -> String { + let mut payload: String = BATCH_BEGIN.to_owned(); + for item in items { + payload.push_str(CHANGESET_BEGIN); + payload.push_str(if item.1.is_some() { "PUT" } else { "DELETE" }); + payload.push_str(" "); + payload.push_str(uri_prefix); + payload.push_str(entity_path(table, primary_key, item.0.as_str()).as_str()); + payload.push_str(" HTTP/1.1\n"); + payload.push_str(ACCEPT_HEADER); + if let Some(ref v) = item.1 { + payload.push_str(UPDATE_HEADER); + payload.push_str("\n"); + payload.push_str(serde_json::to_string(v).unwrap().as_str()); + } else { + payload.push_str(IF_MATCH_HEADER); + } + + payload.push_str("\n"); + } + payload + CHANGESET_END + BATCH_END +} + +#[cfg(test)] +mod test { + use super::*; + + #[allow(non_snake_case)] + #[derive(Serialize)] + struct Entity { + PartitionKey: String, + RowKey: String, + Rating: i32, + Text: String, + } + + #[test] + fn verify_batch_payload() { + let expected = r#"--batch_a1e9d677-b28b-435e-a89e-87e6a768a431 +Content-Type: multipart/mixed; boundary=changeset_8a28b620-b4bb-458c-a177-0959fb14c977 + +--changeset_8a28b620-b4bb-458c-a177-0959fb14c977 +Content-Type: application/http +Content-Transfer-Encoding: binary + +PUT https://myaccount.table.core.windows.net/Blogs(PartitionKey='Channel_17',RowKey='3') HTTP/1.1 +Accept: application/json;odata=nometadata +Content-Type: application/json + +{"PartitionKey":"Channel_17","RowKey":"3","Rating":9,"Text":".NET..."} +--changeset_8a28b620-b4bb-458c-a177-0959fb14c977 +Content-Type: application/http +Content-Transfer-Encoding: binary + +PUT https://myaccount.table.core.windows.net/Blogs(PartitionKey='Channel_17',RowKey='3') HTTP/1.1 +Accept: application/json;odata=nometadata +Content-Type: application/json + +{"PartitionKey":"Channel_17","RowKey":"3","Rating":9,"Text":"PDC 2008..."} +--changeset_8a28b620-b4bb-458c-a177-0959fb14c977 +Content-Type: application/http +Content-Transfer-Encoding: binary + +DELETE https://myaccount.table.core.windows.net/Blogs(PartitionKey='Channel_17',RowKey='3') HTTP/1.1 +Accept: application/json;odata=nometadata +If-Match: * + +--changeset_8a28b620-b4bb-458c-a177-0959fb14c977-- +--batch_a1e9d677-b28b-435e-a89e-87e6a768a431 +"#; + + let items = vec![ + bupdate("Channel_17", "3", 9, ".NET..."), + bupdate("Channel_17", "3", 9, "PDC 2008..."), + bdelete("3"), + ]; + let actual = generate_batch_payload( + "https://myaccount.table.core.windows.net/", + "Blogs", + "Channel_17", + items.as_slice(), + ); + assert_eq!(expected, actual); + } + + fn bupdate(pk: &str, rk: &str, rating: i32, text: &str) -> BatchItem { + BatchItem( + rk.to_owned(), + Some(Entity { + PartitionKey: pk.to_owned(), + RowKey: rk.to_owned(), + Rating: rating, + Text: text.to_owned(), + }), + ) + } + + fn bdelete(rk: &str) -> BatchItem { + BatchItem(rk.to_owned(), None) + } +} diff --git a/src/azure/storage/table/mod.rs b/src/azure/storage/table/mod.rs new file mode 100644 index 000000000..91ba74ecc --- /dev/null +++ b/src/azure/storage/table/mod.rs @@ -0,0 +1,274 @@ +mod batch; + +pub use self::batch::BatchItem; + +use self::batch::generate_batch_payload; +use std::io::Read; +use azure::core; +use azure::core::errors::{self, AzureError}; +use azure::storage::client::Client; +use azure::storage::rest_client::ServiceType; +use hyper::client::response::Response; +use hyper::header::{Accept, ContentType, Headers, IfMatch, qitem}; +use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value}; +use hyper::status::StatusCode; +use serde::Serialize; +use serde::de::DeserializeOwned; +use serde_json; + +const TABLE_TABLES: &'static str = "TABLES"; + +pub struct TableService { + client: Client, +} + +impl TableService { + pub fn new(client: Client) -> Self { + TableService { client: client } + } + + pub fn list_tables(&self) -> Result, AzureError> { + Ok( + self.query_entities(TABLE_TABLES, None)? + .into_iter() + .map(|x: TableEntity| x.TableName) + .collect(), + ) + } + + // Create table if not exists. + pub fn create_table>(&self, table_name: T) -> Result<(), AzureError> { + let body = &serde_json::to_string(&TableEntity { TableName: table_name.into() }).unwrap(); + debug!("body == {}", body); + let mut response = try!(self.request_with_default_header( + TABLE_TABLES, + core::HTTPMethod::Post, + Some(body) + )); + // TODO: Here treats conflict as existed, but could be reserved name, such as 'Tables', + // should check table existence directly + if !(StatusCode::Created == response.status || StatusCode::Conflict == response.status) { + try!(errors::check_status(&mut response, StatusCode::Created)); + } + + Ok(()) + } + + pub fn get_entity( + &self, + table_name: &str, + partition_key: &str, + row_key: &str, + ) -> Result, AzureError> { + let path = &entity_path(table_name, partition_key, row_key); + let mut response = try!(self.request_with_default_header( + path, + core::HTTPMethod::Get, + None + )); + if StatusCode::NotFound == response.status { + return Ok(None); + } + try!(errors::check_status(&mut response, StatusCode::Ok)); + let body = try!(get_response_body(&mut response)); + + let res = serde_json::from_str(&body).unwrap(); + + //res = res.clone(); + + Ok(res) + } + + pub fn query_entities( + &self, + table_name: &str, + query: Option<&str>, + ) -> Result, AzureError> { + let mut path = table_name.to_owned(); + if let Some(clause) = query { + path.push_str("?"); + path.push_str(clause); + } + + let mut response = try!(self.request_with_default_header( + path.as_str(), + core::HTTPMethod::Get, + None + )); + try!(errors::check_status(&mut response, StatusCode::Ok)); + let body = &try!(get_response_body(&mut response)); + let ec: EntityCollection = serde_json::from_str(body).unwrap(); + Ok(ec.value) + } + + pub fn insert_entity( + &self, + table_name: &str, + entity: &T, + ) -> Result<(), AzureError> { + let body = &serde_json::to_string(entity).unwrap(); + let mut resp = try!(self.request_with_default_header( + table_name, + core::HTTPMethod::Post, + Some(body) + )); + try!(errors::check_status(&mut resp, StatusCode::Created)); + Ok(()) + } + + pub fn update_entity( + &self, + table_name: &str, + partition_key: &str, + row_key: &str, + entity: &T, + ) -> Result<(), AzureError> { + let body = &serde_json::to_string(entity).unwrap(); + let path = &entity_path(table_name, partition_key, row_key); + let mut resp = try!(self.request_with_default_header( + path, + core::HTTPMethod::Put, + Some(body) + )); + try!(errors::check_status(&mut resp, StatusCode::NoContent)); + Ok(()) + } + + pub fn delete_entity( + &self, + table_name: &str, + partition_key: &str, + row_key: &str, + ) -> Result<(), AzureError> { + let path = &entity_path(table_name, partition_key, row_key); + let mut headers = Headers::new(); + headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); + headers.set(IfMatch::Any); + + let mut resp = try!(self.request(path, core::HTTPMethod::Delete, None, headers)); + try!(errors::check_status(&mut resp, StatusCode::NoContent)); + Ok(()) + } + + pub fn batch( + &self, + table_name: &str, + partition_key: &str, + batch_items: &[BatchItem], + ) -> Result<(), AzureError> { + let payload = &generate_batch_payload( + self.client.get_uri_prefix(ServiceType::Table).as_str(), + table_name, + partition_key, + batch_items, + ); + let mut headers = Headers::new(); + headers.set(ContentType(get_batch_mime())); + let mut response = try!(self.request( + "$batch", + core::HTTPMethod::Post, + Some(payload), + headers + )); + try!(errors::check_status(&mut response, StatusCode::Accepted)); + // TODO deal with body response, handle batch failure. + // let ref body = try!(get_response_body(&mut response)); + // info!("{}", body); + Ok(()) + } + + fn request_with_default_header( + &self, + segment: &str, + method: core::HTTPMethod, + request_str: Option<&str>, + ) -> Result { + let mut headers = Headers::new(); + headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); + if request_str.is_some() { + headers.set(ContentType(get_default_json_mime())); + } + self.request(segment, method, request_str, headers) + } + + fn request( + &self, + segment: &str, + method: core::HTTPMethod, + request_str: Option<&str>, + headers: Headers, + ) -> Result { + trace!("{:?} {}", method, segment); + if let Some(body) = request_str { + trace!("Request: {}", body); + } + + let resp = try!( + self.client + .perform_table_request(segment, method, headers, request_str) + ); + trace!("Response status: {:?}", resp.status); + Ok(resp) + } +} + + +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize)] +struct TableEntity { + TableName: String, +} + +#[derive(Deserialize)] +struct EntityCollection { + value: Vec, +} + +fn get_response_body(resp: &mut Response) -> Result { + let mut body = String::new(); + try!(resp.read_to_string(&mut body)); + trace!("Response Body:{}", body); + Ok(body) +} + +#[inline] +fn entity_path(table_name: &str, partition_key: &str, row_key: &str) -> String { + table_name.to_owned() + "(PartitionKey='" + partition_key + "',RowKey='" + row_key + "')" +} + +#[inline] +pub fn get_default_json_mime() -> Mime { + Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)], + ) +} + +#[inline] +pub fn get_json_mime_nometadata() -> Mime { + Mime( + TopLevel::Application, + SubLevel::Json, + vec![ + ( + Attr::Ext("odata".to_owned()), + Value::Ext("nometadata".to_owned()), + ), + ], + ) +} + +#[inline] +pub fn get_batch_mime() -> Mime { + Mime( + TopLevel::Multipart, + SubLevel::Ext("Mixed".to_owned()), + vec![ + ( + Attr::Ext("boundary".to_owned()), + Value::Ext("batch_a1e9d677-b28b-435e-a89e-87e6a768a431".to_owned()), + ), + ], + ) +} From 158b6875bb5f98897ba240b86308cef47a459f48 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 19:19:04 +0200 Subject: [PATCH 48/54] Half storage/table migrated --- Cargo.lock | 1 + Cargo.toml | 2 +- src/azure/storage/client.rs | 2 +- src/azure/storage/table/mod.rs | 324 ++++++++++++++++----------------- src/lib.rs | 1 + src/main.rs | 1 + 6 files changed, 159 insertions(+), 172 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f9196170..ace40dd73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "hyper 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 78ee78361..08b7980cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ serde = "*" serde_json = "*" serde_derive = "*" hyper = "*" - +mime = "*" tokio = "*" tokio-core = "*" futures = "*" diff --git a/src/azure/storage/client.rs b/src/azure/storage/client.rs index 4e1564f7a..c9c020a3c 100644 --- a/src/azure/storage/client.rs +++ b/src/azure/storage/client.rs @@ -70,7 +70,7 @@ impl Client { request_str: Option<&[u8]>, ) -> Result where - F: FnOnce(&mut Headers), + F: FnOnce(&mut hyper::header::Headers), { debug!("segment: {}, method: {:?}", segment, method,); perform_request( diff --git a/src/azure/storage/table/mod.rs b/src/azure/storage/table/mod.rs index 91ba74ecc..5e402d0c6 100644 --- a/src/azure/storage/table/mod.rs +++ b/src/azure/storage/table/mod.rs @@ -4,18 +4,24 @@ pub use self::batch::BatchItem; use self::batch::generate_batch_payload; use std::io::Read; +use mime; +use mime::Mime; use azure::core; -use azure::core::errors::{self, AzureError}; +use azure::core::errors::{self, AzureError, check_status_extract_body, + check_status_extract_headers_and_body, extract_status_and_body, + UnexpectedHTTPResult}; use azure::storage::client::Client; use azure::storage::rest_client::ServiceType; -use hyper::client::response::Response; +use hyper::Method; +use hyper::client::FutureResponse; use hyper::header::{Accept, ContentType, Headers, IfMatch, qitem}; -use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value}; -use hyper::status::StatusCode; +use hyper::StatusCode; use serde::Serialize; use serde::de::DeserializeOwned; use serde_json; +use futures::future::*; + const TABLE_TABLES: &'static str = "TABLES"; pub struct TableService { @@ -27,31 +33,28 @@ impl TableService { TableService { client: client } } - pub fn list_tables(&self) -> Result, AzureError> { - Ok( - self.query_entities(TABLE_TABLES, None)? - .into_iter() - .map(|x: TableEntity| x.TableName) - .collect(), - ) - } + //pub fn list_tables(&self) -> Result, AzureError> { + // Ok( + // self.query_entities(TABLE_TABLES, None)? + // .into_iter() + // .map(|x: TableEntity| x.TableName) + // .collect(), + // ) + //} // Create table if not exists. - pub fn create_table>(&self, table_name: T) -> Result<(), AzureError> { + pub fn create_table>( + &self, + table_name: T, + ) -> impl Future { let body = &serde_json::to_string(&TableEntity { TableName: table_name.into() }).unwrap(); debug!("body == {}", body); - let mut response = try!(self.request_with_default_header( - TABLE_TABLES, - core::HTTPMethod::Post, - Some(body) - )); - // TODO: Here treats conflict as existed, but could be reserved name, such as 'Tables', - // should check table existence directly - if !(StatusCode::Created == response.status || StatusCode::Conflict == response.status) { - try!(errors::check_status(&mut response, StatusCode::Created)); - } + let req = self.request_with_default_header(TABLE_TABLES, Method::Post, Some(body)); - Ok(()) + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created) + .and_then(move |_| ok(())) + }) } pub fn get_entity( @@ -59,156 +62,157 @@ impl TableService { table_name: &str, partition_key: &str, row_key: &str, - ) -> Result, AzureError> { + ) -> impl Future, Error = AzureError> { let path = &entity_path(table_name, partition_key, row_key); - let mut response = try!(self.request_with_default_header( - path, - core::HTTPMethod::Get, - None - )); - if StatusCode::NotFound == response.status { - return Ok(None); - } - try!(errors::check_status(&mut response, StatusCode::Ok)); - let body = try!(get_response_body(&mut response)); - - let res = serde_json::from_str(&body).unwrap(); - - //res = res.clone(); - - Ok(res) + let req = self.request_with_default_header(path, Method::Get, None); + done(req).from_err().and_then(move |future_response| { + extract_status_and_body(future_response).and_then( + move |(status, body)| if status == StatusCode::NotFound { + ok(None) + } else if status != StatusCode::Ok { + err(AzureError::UnexpectedHTTPResult( + UnexpectedHTTPResult::new(StatusCode::Ok, status, &body), + )) + } else { + match serde_json::from_str(&body) { + Ok(item) => ok(Some(item)), + Err(error) => err(error.into()), + } + }, + ) + }) } pub fn query_entities( &self, table_name: &str, query: Option<&str>, - ) -> Result, AzureError> { + ) -> impl Future, Error = AzureError> { let mut path = table_name.to_owned(); if let Some(clause) = query { path.push_str("?"); path.push_str(clause); } - let mut response = try!(self.request_with_default_header( - path.as_str(), - core::HTTPMethod::Get, - None - )); - try!(errors::check_status(&mut response, StatusCode::Ok)); - let body = &try!(get_response_body(&mut response)); - let ec: EntityCollection = serde_json::from_str(body).unwrap(); - Ok(ec.value) - } + let req = self.request_with_default_header(path.as_str(), Method::Get, None); - pub fn insert_entity( - &self, - table_name: &str, - entity: &T, - ) -> Result<(), AzureError> { - let body = &serde_json::to_string(entity).unwrap(); - let mut resp = try!(self.request_with_default_header( - table_name, - core::HTTPMethod::Post, - Some(body) - )); - try!(errors::check_status(&mut resp, StatusCode::Created)); - Ok(()) + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Ok).and_then(move |body| { + done(serde_json::from_str::>(&body)) + .from_err() + .and_then(|ec| ok(ec.value)) + }) + }) } - pub fn update_entity( - &self, - table_name: &str, - partition_key: &str, - row_key: &str, - entity: &T, - ) -> Result<(), AzureError> { - let body = &serde_json::to_string(entity).unwrap(); - let path = &entity_path(table_name, partition_key, row_key); - let mut resp = try!(self.request_with_default_header( - path, - core::HTTPMethod::Put, - Some(body) - )); - try!(errors::check_status(&mut resp, StatusCode::NoContent)); - Ok(()) - } + //pub fn insert_entity( + // &self, + // table_name: &str, + // entity: &T, + //) -> Result<(), AzureError> { + // let body = &serde_json::to_string(entity).unwrap(); + // let mut resp = try!(self.request_with_default_header( + // table_name, + // Method::Post, + // Some(body) + // )); + // try!(errors::check_status(&mut resp, StatusCode::Created)); + // Ok(()) + //} - pub fn delete_entity( - &self, - table_name: &str, - partition_key: &str, - row_key: &str, - ) -> Result<(), AzureError> { - let path = &entity_path(table_name, partition_key, row_key); - let mut headers = Headers::new(); - headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); - headers.set(IfMatch::Any); + //pub fn update_entity( + // &self, + // table_name: &str, + // partition_key: &str, + // row_key: &str, + // entity: &T, + //) -> Result<(), AzureError> { + // let body = &serde_json::to_string(entity).unwrap(); + // let path = &entity_path(table_name, partition_key, row_key); + // let mut resp = try!(self.request_with_default_header( + // path, + // Method::Put, + // Some(body) + // )); + // try!(errors::check_status(&mut resp, StatusCode::NoContent)); + // Ok(()) + //} - let mut resp = try!(self.request(path, core::HTTPMethod::Delete, None, headers)); - try!(errors::check_status(&mut resp, StatusCode::NoContent)); - Ok(()) - } + //pub fn delete_entity( + // &self, + // table_name: &str, + // partition_key: &str, + // row_key: &str, + //) -> Result<(), AzureError> { + // let path = &entity_path(table_name, partition_key, row_key); + // let mut headers = Headers::new(); + // headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); + // headers.set(IfMatch::Any); - pub fn batch( - &self, - table_name: &str, - partition_key: &str, - batch_items: &[BatchItem], - ) -> Result<(), AzureError> { - let payload = &generate_batch_payload( - self.client.get_uri_prefix(ServiceType::Table).as_str(), - table_name, - partition_key, - batch_items, - ); - let mut headers = Headers::new(); - headers.set(ContentType(get_batch_mime())); - let mut response = try!(self.request( - "$batch", - core::HTTPMethod::Post, - Some(payload), - headers - )); - try!(errors::check_status(&mut response, StatusCode::Accepted)); - // TODO deal with body response, handle batch failure. - // let ref body = try!(get_response_body(&mut response)); - // info!("{}", body); - Ok(()) - } + // let mut resp = try!(self.request(path, Method::Delete, None, headers)); + // try!(errors::check_status(&mut resp, StatusCode::NoContent)); + // Ok(()) + //} + + //pub fn batch( + // &self, + // table_name: &str, + // partition_key: &str, + // batch_items: &[BatchItem], + //) -> Result<(), AzureError> { + // let payload = &generate_batch_payload( + // self.client.get_uri_prefix(ServiceType::Table).as_str(), + // table_name, + // partition_key, + // batch_items, + // ); + // let mut headers = Headers::new(); + // headers.set(ContentType(get_batch_mime())); + // let mut response = try!(self.request("$batch", Method::Post, Some(payload), headers)); + // try!(errors::check_status(&mut response, StatusCode::Accepted)); + // // TODO deal with body response, handle batch failure. + // // let ref body = try!(get_response_body(&mut response)); + // // info!("{}", body); + // Ok(()) + //} fn request_with_default_header( &self, segment: &str, - method: core::HTTPMethod, + method: Method, request_str: Option<&str>, - ) -> Result { + ) -> Result { let mut headers = Headers::new(); - headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); - if request_str.is_some() { - headers.set(ContentType(get_default_json_mime())); - } - self.request(segment, method, request_str, headers) + self.request(segment, method, request_str, |ref mut headers| { + headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); + if request_str.is_some() { + headers.set(ContentType(get_default_json_mime())); + } + }) } - fn request( + fn request( &self, segment: &str, - method: core::HTTPMethod, + method: Method, request_str: Option<&str>, - headers: Headers, - ) -> Result { + headers_func: F, + ) -> Result + where + F: FnOnce(&mut Headers), + { trace!("{:?} {}", method, segment); if let Some(body) = request_str { trace!("Request: {}", body); } - let resp = try!( - self.client - .perform_table_request(segment, method, headers, request_str) - ); - trace!("Response status: {:?}", resp.status); - Ok(resp) + let request_vec: Option<&[u8]> = match request_str { + Some(s) => Some(s.as_bytes()), + None => None, + }; + + self.client + .perform_table_request(segment, method, headers_func, request_vec) } } @@ -224,12 +228,12 @@ struct EntityCollection { value: Vec, } -fn get_response_body(resp: &mut Response) -> Result { - let mut body = String::new(); - try!(resp.read_to_string(&mut body)); - trace!("Response Body:{}", body); - Ok(body) -} +//fn get_response_body(resp: &mut Response) -> Result { +// let mut body = String::new(); +// try!(resp.read_to_string(&mut body)); +// trace!("Response Body:{}", body); +// Ok(body) +//} #[inline] fn entity_path(table_name: &str, partition_key: &str, row_key: &str) -> String { @@ -238,37 +242,17 @@ fn entity_path(table_name: &str, partition_key: &str, row_key: &str) -> String { #[inline] pub fn get_default_json_mime() -> Mime { - Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)], - ) + "application/json; charset=utf-8".parse().unwrap() } #[inline] pub fn get_json_mime_nometadata() -> Mime { - Mime( - TopLevel::Application, - SubLevel::Json, - vec![ - ( - Attr::Ext("odata".to_owned()), - Value::Ext("nometadata".to_owned()), - ), - ], - ) + "application/json; odata=nometadata".parse().unwrap() } #[inline] pub fn get_batch_mime() -> Mime { - Mime( - TopLevel::Multipart, - SubLevel::Ext("Mixed".to_owned()), - vec![ - ( - Attr::Ext("boundary".to_owned()), - Value::Ext("batch_a1e9d677-b28b-435e-a89e-87e6a768a431".to_owned()), - ), - ], - ) + "multipart/mixed; boundary=batch_a1e9d677-b28b-435e-a89e-87e6a768a431" + .parse() + .unwrap() } diff --git a/src/lib.rs b/src/lib.rs index 0f5926b24..7583cf4f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ #[macro_use] extern crate hyper; +extern crate mime; extern crate chrono; extern crate futures; diff --git a/src/main.rs b/src/main.rs index 5d8d9adbe..de833cb58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ #[macro_use] extern crate hyper; +extern crate mime; extern crate chrono; extern crate futures; From 30f4803e4771e27c1b854f8aa8401f93b75e77ea Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 22:27:07 +0200 Subject: [PATCH 49/54] insert and update entity --- src/azure/storage/table/mod.rs | 103 ++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/src/azure/storage/table/mod.rs b/src/azure/storage/table/mod.rs index 5e402d0c6..cdbfeab3c 100644 --- a/src/azure/storage/table/mod.rs +++ b/src/azure/storage/table/mod.rs @@ -33,14 +33,17 @@ impl TableService { TableService { client: client } } - //pub fn list_tables(&self) -> Result, AzureError> { - // Ok( - // self.query_entities(TABLE_TABLES, None)? - // .into_iter() - // .map(|x: TableEntity| x.TableName) - // .collect(), - // ) - //} + pub fn list_tables(&self) -> impl Future, Error = AzureError> { + self.query_entities(TABLE_TABLES, None).and_then( + |entities| { + let e: Vec = entities + .into_iter() + .map(|x: TableEntity| x.TableName) + .collect(); + ok(e) + }, + ) + } // Create table if not exists. pub fn create_table>( @@ -105,38 +108,60 @@ impl TableService { }) } - //pub fn insert_entity( - // &self, - // table_name: &str, - // entity: &T, - //) -> Result<(), AzureError> { - // let body = &serde_json::to_string(entity).unwrap(); - // let mut resp = try!(self.request_with_default_header( - // table_name, - // Method::Post, - // Some(body) - // )); - // try!(errors::check_status(&mut resp, StatusCode::Created)); - // Ok(()) - //} + fn _prepare_insert_entity( + &self, + table_name: &str, + entity: &T, + ) -> Result + where + T: Serialize, + { + let obj_ser = serde_json::to_string(entity)?; + self.request_with_default_header(table_name, Method::Post, Some(&obj_ser)) + } - //pub fn update_entity( - // &self, - // table_name: &str, - // partition_key: &str, - // row_key: &str, - // entity: &T, - //) -> Result<(), AzureError> { - // let body = &serde_json::to_string(entity).unwrap(); - // let path = &entity_path(table_name, partition_key, row_key); - // let mut resp = try!(self.request_with_default_header( - // path, - // Method::Put, - // Some(body) - // )); - // try!(errors::check_status(&mut resp, StatusCode::NoContent)); - // Ok(()) - //} + pub fn insert_entity( + &self, + table_name: &str, + entity: &T, + ) -> impl Future { + let req = self._prepare_insert_entity(table_name, entity); + + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Created) + .and_then(move |_| ok(())) + }) + } + + + fn _prepare_update_entity( + &self, + table_name: &str, + partition_key: &str, + row_key: &str, + entity: &T, + ) -> Result + where + T: Serialize, + { + let body = &serde_json::to_string(entity)?; + let path = &entity_path(table_name, partition_key, row_key); + self.request_with_default_header(path, Method::Put, Some(body)) + } + + pub fn update_entity( + &self, + table_name: &str, + partition_key: &str, + row_key: &str, + entity: &T, + ) -> impl Future { + let req = _prepare_update_entity(table_name, partition_key, row_key, entity); + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::NoContent) + .and_then(move |_| ok(())) + }) + } //pub fn delete_entity( // &self, From 805c430e4fbedb71a5665f571679698a5e1e12ff Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 22:34:19 +0200 Subject: [PATCH 50/54] Completed migration to Tokio! --- src/azure/storage/client.rs | 2 +- src/azure/storage/table/mod.rs | 96 ++++++++++++++++------------------ 2 files changed, 46 insertions(+), 52 deletions(-) diff --git a/src/azure/storage/client.rs b/src/azure/storage/client.rs index c9c020a3c..4e1564f7a 100644 --- a/src/azure/storage/client.rs +++ b/src/azure/storage/client.rs @@ -70,7 +70,7 @@ impl Client { request_str: Option<&[u8]>, ) -> Result where - F: FnOnce(&mut hyper::header::Headers), + F: FnOnce(&mut Headers), { debug!("segment: {}, method: {:?}", segment, method,); perform_request( diff --git a/src/azure/storage/table/mod.rs b/src/azure/storage/table/mod.rs index cdbfeab3c..dd33f0c28 100644 --- a/src/azure/storage/table/mod.rs +++ b/src/azure/storage/table/mod.rs @@ -3,12 +3,8 @@ mod batch; pub use self::batch::BatchItem; use self::batch::generate_batch_payload; -use std::io::Read; -use mime; use mime::Mime; -use azure::core; -use azure::core::errors::{self, AzureError, check_status_extract_body, - check_status_extract_headers_and_body, extract_status_and_body, +use azure::core::errors::{AzureError, check_status_extract_body, extract_status_and_body, UnexpectedHTTPResult}; use azure::storage::client::Client; use azure::storage::rest_client::ServiceType; @@ -156,50 +152,56 @@ impl TableService { row_key: &str, entity: &T, ) -> impl Future { - let req = _prepare_update_entity(table_name, partition_key, row_key, entity); + let req = self._prepare_update_entity(table_name, partition_key, row_key, entity); done(req).from_err().and_then(move |future_response| { check_status_extract_body(future_response, StatusCode::NoContent) .and_then(move |_| ok(())) }) } - //pub fn delete_entity( - // &self, - // table_name: &str, - // partition_key: &str, - // row_key: &str, - //) -> Result<(), AzureError> { - // let path = &entity_path(table_name, partition_key, row_key); - // let mut headers = Headers::new(); - // headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); - // headers.set(IfMatch::Any); - - // let mut resp = try!(self.request(path, Method::Delete, None, headers)); - // try!(errors::check_status(&mut resp, StatusCode::NoContent)); - // Ok(()) - //} - - //pub fn batch( - // &self, - // table_name: &str, - // partition_key: &str, - // batch_items: &[BatchItem], - //) -> Result<(), AzureError> { - // let payload = &generate_batch_payload( - // self.client.get_uri_prefix(ServiceType::Table).as_str(), - // table_name, - // partition_key, - // batch_items, - // ); - // let mut headers = Headers::new(); - // headers.set(ContentType(get_batch_mime())); - // let mut response = try!(self.request("$batch", Method::Post, Some(payload), headers)); - // try!(errors::check_status(&mut response, StatusCode::Accepted)); - // // TODO deal with body response, handle batch failure. - // // let ref body = try!(get_response_body(&mut response)); - // // info!("{}", body); - // Ok(()) - //} + pub fn delete_entity( + &self, + table_name: &str, + partition_key: &str, + row_key: &str, + ) -> impl Future { + let path = &entity_path(table_name, partition_key, row_key); + + let req = self.request(path, Method::Delete, None, |ref mut headers| { + headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); + headers.set(IfMatch::Any); + }); + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::NoContent) + .and_then(move |_| ok(())) + }) + } + + pub fn batch( + &self, + table_name: &str, + partition_key: &str, + batch_items: &[BatchItem], + ) -> impl Future { + let payload = &generate_batch_payload( + self.client.get_uri_prefix(ServiceType::Table).as_str(), + table_name, + partition_key, + batch_items, + ); + + let req = self.request("$batch", Method::Post, Some(payload), |ref mut headers| { + headers.set(ContentType(get_batch_mime())); + }); + done(req).from_err().and_then(move |future_response| { + check_status_extract_body(future_response, StatusCode::Accepted).and_then(move |_| { + // TODO deal with body response, handle batch failure. + // let ref body = try!(get_response_body(&mut response)); + // info!("{}", body); + ok(()) + }) + }) + } fn request_with_default_header( &self, @@ -207,7 +209,6 @@ impl TableService { method: Method, request_str: Option<&str>, ) -> Result { - let mut headers = Headers::new(); self.request(segment, method, request_str, |ref mut headers| { headers.set(Accept(vec![qitem(get_json_mime_nometadata())])); if request_str.is_some() { @@ -253,13 +254,6 @@ struct EntityCollection { value: Vec, } -//fn get_response_body(resp: &mut Response) -> Result { -// let mut body = String::new(); -// try!(resp.read_to_string(&mut body)); -// trace!("Response Body:{}", body); -// Ok(body) -//} - #[inline] fn entity_path(table_name: &str, partition_key: &str, row_key: &str) -> String { table_name.to_owned() + "(PartitionKey='" + partition_key + "',RowKey='" + row_key + "')" From a69d0b625e9e5b61eee446c83e6e0f162894bf4a Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 23:13:04 +0200 Subject: [PATCH 51/54] Updated README --- CHANGELOG.md | 14 +++ README.md | 232 ++++++++++++++++++++++++++--------------- examples/document00.rs | 42 ++++++++ 3 files changed, 204 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce910218..e96d65bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [0.4.0](https://github.com/MindFlavor/AzureSDKForRust/releases/tag/0.4.0) (2017-07-02) + +### Migrated all code to asynchronous hyper using Futures + +** Breaking changes ** + +* Almost everything is now a future. So whenever you had a ```Result``` now you have ```impl FutureResult```. + +** Updated references to bleeding edge ** + +** TODO ** + +* Test the table code. My migration is now "on paper" only and should be tested. + ## [0.3.1](https://github.com/MindFlavor/AzureSDKForRust/releases/tag/0.3.1) (2017-06-10) **Implemented features:** diff --git a/README.md b/README.md index 998f6c631..4798af01d 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![Crate](https://img.shields.io/crates/v/azure_sdk_for_rust.svg)](https://crates.io/crates/azure_sdk_for_rust) [![legal](https://img.shields.io/crates/l/azure_sdk_for_rust.svg)](LICENSE) [![cratedown](https://img.shields.io/crates/d/azure_sdk_for_rust.svg)](https://crates.io/crates/azure_sdk_for_rust) [![cratelastdown](https://img.shields.io/crates/dv/azure_sdk_for_rust.svg)](https://crates.io/crates/azure_sdk_for_rust) -[![tag](https://img.shields.io/github/tag/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/tree/0.3.1) -[![release](https://img.shields.io/github/release/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/tree/0.3.1) -[![commitssince](https://img.shields.io/github/commits-since/mindflavor/AzureSDKForRust/0.3.1.svg)](https://img.shields.io/github/commits-since/mindflavor/AzureSDKForRust/0.3.1.svg) +[![tag](https://img.shields.io/github/tag/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/tree/0.4.0) +[![release](https://img.shields.io/github/release/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/tree/0.4.0) +[![commitssince](https://img.shields.io/github/commits-since/mindflavor/AzureSDKForRust/0.4.0.svg)](https://img.shields.io/github/commits-since/mindflavor/AzureSDKForRust/0.4.0.svg) ## Introduction Microsoft Azure expose its technologies via REST API. These APIs are easily consumable from any language (good) but are weakly typed. With this library and its related [crate](https://crates.io/crates/azure_sdk_for_rust/) you can exploit the power of Microsoft Azure from Rust in a idiomatic way. @@ -17,7 +17,7 @@ is likely to break over time. The current releases will probabily contain bugs. ## Disclaimer Although I am a Microsoft employee, this is not a Microsoft endorsed project. It's simply a pet project of mine: I love Rust (who doesn't? :smirk:) and Microsoft Azure technologies so I thought to close the gap between them. It's also a good project for learning Rust. This library relies heavily on [Hyper](https://github.com/hyperium/hyper). As the time of writing master Hyper does not support Tokio yet: this SDK will than be _blocking_. I plan to switch to futures as soon as possible. -## Run E2E test +## Run E2E test (in progress) ``` export AZURE_STORAGE_ACCOUNT= export AZURE_STORAGE_KEY= @@ -31,96 +31,160 @@ cargo test --features=test_e2e ``` ## Example -You can find examples in the [```tests```](https://github.com/MindFlavor/AzureSDKForRust/tree/master/tests) folder, in the [```examples```](https://github.com/MindFlavor/AzureSDKForRust/tree/master/examples) folder and in the [```src/main.rs```](https://github.com/MindFlavor/AzureSDKForRust/blob/master/src/main.rs) file (which I shall try to remove in the future). Here is a sample however: +You can find examples in the [```examples```](https://github.com/MindFlavor/AzureSDKForRust/tree/master/examples) folder. Here is a sample however: ### main.rs -```rust + +```rs extern crate azure_sdk_for_rust; + +extern crate futures; +extern crate tokio_core; +extern crate tokio; +extern crate hyper; +extern crate hyper_tls; extern crate chrono; + +use std::error::Error; + +use futures::future::*; +use tokio_core::reactor::Core; + +use azure_sdk_for_rust::azure::cosmos::authorization_token::{AuthorizationToken, TokenType}; +use azure_sdk_for_rust::azure::cosmos::client::Client; + #[macro_use] -extern crate mime; +extern crate serde_derive; +use azure_sdk_for_rust::azure::cosmos; + +#[derive(Serialize, Deserialize, Debug)] +struct MySampleStruct<'a> { + id: &'a str, + a_string: &'a str, + a_number: u64, + a_timestamp: i64, +} -use azure_sdk_for_rust::azure::core::lease::{LeaseState, LeaseStatus}; -use azure_sdk_for_rust::azure::storage::client::Client; -use azure_sdk_for_rust::azure::storage::blob::{Blob, BlobType, PUT_OPTIONS_DEFAULT}; -use azure_sdk_for_rust::azure::storage::container::{Container, PublicAccess, LIST_CONTAINER_OPTIONS_DEFAULT}; -use chrono::UTC; +const DATABASE: &'static str = "azuresdktestdb"; +const COLLECTION: &'static str = "azuresdktc"; -use mime::Mime; fn main() { - let azure_storage_account = &"azure_storage_account"; - let azure_storage_key= &"azure_storage_key"; - - // create the client struct. The third argument, if false, forces to use - // http instead of https. It's useful if you have trouble compiling - // hyper with openSSL activated. - let client = Client::new(azure_storage_account, azure_storage_key, false); - - - // This call will list your containers. - let containers = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap(); - println!("{:?}", containers); - - let container_name = "rust"; - // This call will create a new Azure Container called "wow" - // with public blob access (see https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx) - // if it doesn't exist already. - - let cont = containers.iter().find(|x| x.name == container_name); - if let None = cont { - Container::create(&client, container_name, PublicAccess::Blob).unwrap(); - } - - // this code will upload a file to the container just created. - { - use std::fs::metadata; - use std::fs::File; - - let file_name: &'static str = "C:\\temp\\from_rust.txt"; - let container_name: &'static str = "wow"; - - let metadata = metadata(file_name).unwrap(); - let mut file = File::open(file_name).unwrap(); - - let new_blob = Blob { - name: "from_rust.txt".to_owned(), - container_name: container_name.to_owned(), - snapshot_time: None, - last_modified: UTC::now(), - etag: "".to_owned(), - content_length: metadata.len(), - content_type: "application/octet-stream".parse::().unwrap(), - content_encoding: None, - content_language: None, - content_md5: None, - cache_control: None, - x_ms_blob_sequence_number: None, - blob_type: BlobType::BlockBlob, - lease_status: LeaseStatus::Unlocked, - lease_state: LeaseState::Available, - lease_duration: None, - copy_id: None, - copy_status: None, - copy_source: None, - copy_progress: None, - copy_completion: None, - copy_status_description: None, - }; - - new_blob.put(&client, - &PUT_OPTIONS_DEFAULT, - Some((&mut file, metadata.len()))) - .unwrap(); - } - - - // This code will look for the "todelete" container and - // remove from Azure. - let mut to_delete = containers.iter_mut().find(|x| x.name == "todelete").unwrap(); - to_delete.delete(&client).unwrap(); - println!("{:?} deleted!", to_delete); + code().unwrap(); +} + +// This code will perform these tasks: +// 1. Find an Azure Cosmos DB called *DATABASE*. If it does not exist, create it. +// 2. Find an Azure Cosmos collection called *COLLECTION* in *DATABASE*. If it does not exist, create it. +// 3. Store an entry in collection *COLLECTION* of database *DATABASE*. +// 4. Delete everything. +// +// We will use multiple futures for this hoping to make the code clearer. +// There is no need to proceed this way in your code. +// You can go crazy with future combinators if you want to :) +fn code() -> Result<(), Box> { + // Let's get Cosmos account and master key from env variables. + // This helps automated testing. + let master_key = std::env::var("COSMOS_MASTER_KEY") + .expect("Set env variable COSMOS_MASTER_KEY first!"); + let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); + + // First, we create an authorization token. There are two types of tokens, master and resource + // constrained. Please check the Azure documentation for details. You can change tokens + // at will and it's a good practice to raise your privileges only when needed. + let authorization_token = AuthorizationToken::new(account, TokenType::Master, master_key)?; + + // We will create a tokio-core reactor which will drive our futures. + let mut core = Core::new()?; + + // Next we will create a Cosmos client. You need an authorization_token but you can later + // change it if needed. Notice the client will be tied to your reactor. + let client = Client::new(&core.handle(), authorization_token)?; + + // list_databases will give us the databases available in our account. If there is + // an error (for example, the given key is not valid) you will receive a + // specific AzureError. In this example we will look for a specific database + // so we chain a filter operation. + let future = client.list_databases().and_then(|databases| { + ok(databases.into_iter().find(|db| db.id == DATABASE)) + }); + + // Now we run the future and check the answer. If the requested database + // is not found we create it. + let database = match core.run(future)? { + Some(db) => db, + None => core.run(client.create_database(DATABASE))?, + }; + println!("database == {:?}", database); + + // Now we look for a specific collection. If is not already present + // we will create it. The collection creation is more complex and + // has many options (such as indexing and so on). + let collection = { + let collections = core.run(client.list_collections(&database))?; + + if let Some(collection) = collections.into_iter().find(|coll| coll.id == COLLECTION) { + collection + } else { + let indexes = cosmos::collection::IncludedPathIndex { + kind: cosmos::collection::KeyKind::Hash, + data_type: cosmos::collection::DataType::String, + precision: Some(3), + }; + + let ip = cosmos::collection::IncludedPath { + path: "/*".to_owned(), + indexes: vec![indexes], + }; + + + let ip = cosmos::collection::IndexingPolicy { + automatic: true, + indexing_mode: cosmos::collection::IndexingMode::Consistent, + included_paths: vec![ip], + excluded_paths: vec![], + }; + + let coll = cosmos::collection::Collection::new(COLLECTION, ip); + // Notice here we specify the expected performance level. + // Performance levels have price impact. Also, higher + // performance levels force you to specify an indexing + // strategy. Consult the documentation for more details. + core.run(client.create_collection(&database, 400, &coll))? + } + }; + + println!("collection = {:?}", collection); + + // Now that we have a database and a collection we can insert + // data in them. Let's create a struct. The only constraint + // is that the struct should be Serializable. + let doc = MySampleStruct { + id: "unique_id1", + a_string: "Something here", + a_number: 100, + a_timestamp: chrono::Utc::now().timestamp(), + }; + + // Now we store the struct in Azure Cosmos DB. + // Notice how easy it is! :) + // The method create_document will return, upon success, + // the document attributes. + let document_attributes = core.run( + client.create_document(&database, &collection, false, None, &doc), + )?; + println!("document_attributes == {:?}", document_attributes); + + // We will perform some cleanup. First we delete the collection... + core.run(client.delete_collection(DATABASE, COLLECTION))?; + println!("collection deleted"); + + // And then we delete the database. + core.run(client.delete_database(DATABASE))?; + println!("database deleted"); + + Ok(()) } ``` diff --git a/examples/document00.rs b/examples/document00.rs index b5be201ed..9db82ebd1 100644 --- a/examples/document00.rs +++ b/examples/document00.rs @@ -36,25 +36,53 @@ fn main() { code().unwrap(); } +// This code will perform these tasks: +// 1. Find an Azure Cosmos DB called *DATABASE*. If it does not exist, create it. +// 2. Find an Azure Cosmos collection called *COLLECTION* in *DATABASE*. If it does not exist, create it. +// 3. Store an entry in collection *COLLECTION* of database *DATABASE*. +// 4. Delete everything. +// +// We will use multiple futures for this hoping to make the code clearer. +// There is no need to proceed this way in your code. +// You can go crazy with future combinators if you want to :) fn code() -> Result<(), Box> { + // Let's get Cosmos account and master key from env variables. + // This helps automated testing. let master_key = std::env::var("COSMOS_MASTER_KEY") .expect("Set env variable COSMOS_MASTER_KEY first!"); let account = std::env::var("COSMOS_ACCOUNT").expect("Set env variable COSMOS_ACCOUNT first!"); + + // First, we create an authorization token. There are two types of tokens, master and resource + // constrained. Please check the Azure documentation for details. You can change tokens + // at will and it's a good practice to raise your privileges only when needed. let authorization_token = AuthorizationToken::new(account, TokenType::Master, master_key)?; + // We will create a tokio-core reactor which will drive our futures. let mut core = Core::new()?; + + // Next we will create a Cosmos client. You need an authorization_token but you can later + // change it if needed. Notice the client will be tied to your reactor. let client = Client::new(&core.handle(), authorization_token)?; + // list_databases will give us the databases available in our account. If there is + // an error (for example, the given key is not valid) you will receive a + // specific AzureError. In this example we will look for a specific database + // so we chain a filter operation. let future = client.list_databases().and_then(|databases| { ok(databases.into_iter().find(|db| db.id == DATABASE)) }); + // Now we run the future and check the answer. If the requested database + // is not found we create it. let database = match core.run(future)? { Some(db) => db, None => core.run(client.create_database(DATABASE))?, }; println!("database == {:?}", database); + // Now we look for a specific collection. If is not already present + // we will create it. The collection creation is more complex and + // has many options (such as indexing and so on). let collection = { let collections = core.run(client.list_collections(&database))?; @@ -81,12 +109,19 @@ fn code() -> Result<(), Box> { }; let coll = cosmos::collection::Collection::new(COLLECTION, ip); + // Notice here we specify the expected performance level. + // Performance levels have price impact. Also, higher + // performance levels force you to specify an indexing + // strategy. Consult the documentation for more details. core.run(client.create_collection(&database, 400, &coll))? } }; println!("collection = {:?}", collection); + // Now that we have a database and a collection we can insert + // data in them. Let's create a struct. The only constraint + // is that the struct should be Serializable. let doc = MySampleStruct { id: "unique_id1", a_string: "Something here", @@ -94,13 +129,20 @@ fn code() -> Result<(), Box> { a_timestamp: chrono::Utc::now().timestamp(), }; + // Now we store the struct in Azure Cosmos DB. + // Notice how easy it is! :) + // The method create_document will return, upon success, + // the document attributes. let document_attributes = core.run( client.create_document(&database, &collection, false, None, &doc), )?; println!("document_attributes == {:?}", document_attributes); + // We will perform some cleanup. First we delete the collection... core.run(client.delete_collection(DATABASE, COLLECTION))?; println!("collection deleted"); + + // And then we delete the database. core.run(client.delete_database(DATABASE))?; println!("database deleted"); From b997820cd90fea1c5f11680dd4a9e1fce154cb49 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 23:25:37 +0200 Subject: [PATCH 52/54] Expanded README.md --- README.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4798af01d..38740b79a 100644 --- a/README.md +++ b/README.md @@ -11,31 +11,22 @@ ## Introduction Microsoft Azure expose its technologies via REST API. These APIs are easily consumable from any language (good) but are weakly typed. With this library and its related [crate](https://crates.io/crates/azure_sdk_for_rust/) you can exploit the power of Microsoft Azure from Rust in a idiomatic way. +This crate relies heavily on the excellent crate called [Hyper](https://github.com/hyperium/hyper). As of this library version [0.4.0](https://github.com/MindFlavor/AzureSDKForRust/releases/tag/0.4.0) all the methods are future-aware. That is, I'm using the latest Hyper code. + +Rust, however, still requires you to Box every future returned by a method. The alternative is to use the ```impl Trait``` feature which is nightly-only. Since I've used it everywhere this library will require a nightly Rust compiler until the ```impl Trait``` makes its way to the stable channel. Also since I'm using an unstable feature these is a very good chance of this code to break in the future. + > **NOTE:** This repository is under heavy development and is likely to break over time. The current releases will probabily contain bugs. As usual open issues if you find any. ## Disclaimer -Although I am a Microsoft employee, this is not a Microsoft endorsed project. It's simply a pet project of mine: I love Rust (who doesn't? :smirk:) and Microsoft Azure technologies so I thought to close the gap between them. It's also a good project for learning Rust. This library relies heavily on [Hyper](https://github.com/hyperium/hyper). As the time of writing master Hyper does not support Tokio yet: this SDK will than be _blocking_. I plan to switch to futures as soon as possible. - -## Run E2E test (in progress) -``` -export AZURE_STORAGE_ACCOUNT= -export AZURE_STORAGE_KEY= - -export AZURE_SERVICE_BUS_NAMESPACE= -export AZURE_EVENT_HUB_NAME= -export AZURE_POLICY_NAME= -export AZURE_POLICY_KEY= - -cargo test --features=test_e2e -``` - +Although I am a Microsoft employee, this is not a Microsoft endorsed project. It's simply a pet project of mine: I love Rust (who doesn't? :smirk:) and Microsoft Azure technologies so I thought to close the gap between them. It's also a good project for learning Rust. This library relies heavily on [Hyper](https://github.com/hyperium/hyper). We use the laters Hyper code sp this library is fully async with Futures and Tokio. + ## Example You can find examples in the [```examples```](https://github.com/MindFlavor/AzureSDKForRust/tree/master/examples) folder. Here is a sample however: ### main.rs -```rs +```rust extern crate azure_sdk_for_rust; extern crate futures; @@ -259,5 +250,18 @@ Azure tables entities can be manipulated in batches. The entities are serialized | Delete collection | [https://docs.microsoft.com/en-us/rest/api/documentdb/delete-a-collection](https://docs.microsoft.com/en-us/rest/api/documentdb/delete-a-collection) | | Replace collection | [https://docs.microsoft.com/en-us/rest/api/documentdb/replace-a-collection](https://docs.microsoft.com/en-us/rest/api/documentdb/replace-a-collection) | +## Run E2E test (in progress) +```bash +export AZURE_STORAGE_ACCOUNT= +export AZURE_STORAGE_KEY= + +export AZURE_SERVICE_BUS_NAMESPACE= +export AZURE_EVENT_HUB_NAME= +export AZURE_POLICY_NAME= +export AZURE_POLICY_KEY= + +cargo test --features=test_e2e +``` + ## License This project is published under [Apache license, version 2.0](LICENSE). From 53eb0e067752fb9c37d3ef4037f165a80f97a87e Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 23:28:21 +0200 Subject: [PATCH 53/54] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38740b79a..852a7c522 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Rust, however, still requires you to Box every future returned by a method. The is likely to break over time. The current releases will probabily contain bugs. As usual open issues if you find any. ## Disclaimer -Although I am a Microsoft employee, this is not a Microsoft endorsed project. It's simply a pet project of mine: I love Rust (who doesn't? :smirk:) and Microsoft Azure technologies so I thought to close the gap between them. It's also a good project for learning Rust. This library relies heavily on [Hyper](https://github.com/hyperium/hyper). We use the laters Hyper code sp this library is fully async with Futures and Tokio. +Although I am a Microsoft employee, this is not a Microsoft endorsed project. It's simply a pet project of mine: I love Rust (who doesn't? :smirk:) and Microsoft Azure technologies so I thought to close the gap between them. It's also a good project for learning Rust. This library relies heavily on [Hyper](https://github.com/hyperium/hyper). We use the laters Hyper code so this library is fully async with Futures and Tokio. ## Example You can find examples in the [```examples```](https://github.com/MindFlavor/AzureSDKForRust/tree/master/examples) folder. Here is a sample however: From 3473439141eec6e8b1f19cdeb50475c9bf37130a Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Sat, 1 Jul 2017 23:30:09 +0200 Subject: [PATCH 54/54] Corrected markdown errors --- CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e96d65bb8..5af380742 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,16 @@ ### Migrated all code to asynchronous hyper using Futures -** Breaking changes ** +**Breaking changes** * Almost everything is now a future. So whenever you had a ```Result``` now you have ```impl FutureResult```. -** Updated references to bleeding edge ** +**Updated references to bleeding edge** -** TODO ** +**TODO** -* Test the table code. My migration is now "on paper" only and should be tested. +* Test the table code. +* Perform the E2E test. ## [0.3.1](https://github.com/MindFlavor/AzureSDKForRust/releases/tag/0.3.1) (2017-06-10)