mirror of
https://github.com/minio/minio-rs.git
synced 2025-12-06 15:26:51 +08:00
Add listen bucket notification (#3)
This commit is contained in:
parent
89d0c3accf
commit
128ecb871f
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
Cargo.lock
|
||||
.idea
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "minio-rs"
|
||||
version = "0.1.0"
|
||||
authors = ["Aditya Manthramurthy <aditya.mmy@gmail.com>", "Krishnan Parthasarathi <krishnan.parthasarathi@gmail.com>"]
|
||||
authors = ["MinIO Dev Team <dev@min.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
@ -12,6 +12,10 @@ hyper = "0.12.28"
|
||||
hyper-tls = "0.3.2"
|
||||
ring = "0.14.6"
|
||||
roxmltree = "0.6.0"
|
||||
serde = "1.0.92"
|
||||
serde_derive = "1.0.91"
|
||||
serde_json = "1.0.39"
|
||||
time = "0.1.42"
|
||||
tokio = "0.1.21"
|
||||
xml-rs = "0.8.0"
|
||||
|
||||
|
||||
23
src/lib.rs
23
src/lib.rs
@ -1,9 +1,18 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
pub mod minio;
|
||||
|
||||
21
src/main.rs
21
src/main.rs
@ -1,9 +1,26 @@
|
||||
mod minio;
|
||||
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
use futures::{future::Future, stream::Stream};
|
||||
use hyper::rt;
|
||||
|
||||
use minio::BucketInfo;
|
||||
|
||||
mod minio;
|
||||
|
||||
fn get_local_default_server() -> minio::Client {
|
||||
match minio::Client::new("http://localhost:9000") {
|
||||
Ok(mut c) => {
|
||||
|
||||
244
src/minio.rs
244
src/minio.rs
@ -1,26 +1,49 @@
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::string::String;
|
||||
|
||||
use futures::future::{self, Future};
|
||||
use futures::{stream, Stream};
|
||||
use http;
|
||||
use hyper::header::{HeaderName, HeaderValue};
|
||||
use hyper::{body::Body, client, header, header::HeaderMap, Method, Request, Response, Uri};
|
||||
use hyper_tls::HttpsConnector;
|
||||
use time;
|
||||
use time::Tm;
|
||||
|
||||
pub use types::BucketInfo;
|
||||
use types::{Err, GetObjectResp, ListObjectsResp, Region};
|
||||
|
||||
use crate::minio::net::{Values, ValuesAccess};
|
||||
use crate::minio::notification::NotificationInfo;
|
||||
|
||||
mod api;
|
||||
mod net;
|
||||
mod notification;
|
||||
mod sign;
|
||||
mod types;
|
||||
mod xml;
|
||||
|
||||
mod woxml;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::future::{self, Future};
|
||||
use futures::stream::Stream;
|
||||
use http;
|
||||
use hyper::header::{HeaderName, HeaderValue};
|
||||
use hyper::{body::Body, client, header, header::HeaderMap, Method, Request, Response, Uri};
|
||||
use hyper_tls::HttpsConnector;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::string::String;
|
||||
use time;
|
||||
use time::Tm;
|
||||
|
||||
use types::{Err, GetObjectResp, ListObjectsResp, Region};
|
||||
|
||||
pub use types::BucketInfo;
|
||||
pub const SPACE_BYTE: &[u8; 1] = b" ";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Credentials {
|
||||
@ -71,18 +94,20 @@ pub struct Client {
|
||||
|
||||
impl Client {
|
||||
pub fn new(server: &str) -> Result<Client, Err> {
|
||||
let v = server.parse::<Uri>();
|
||||
match v {
|
||||
Ok(s) => {
|
||||
if s.host().is_none() {
|
||||
let valid = server.parse::<Uri>();
|
||||
match valid {
|
||||
Ok(server_uri) => {
|
||||
if server_uri.host().is_none() {
|
||||
Err(Err::InvalidUrl("no host specified!".to_string()))
|
||||
} else if s.scheme_str() != Some("http") && s.scheme_str() != Some("https") {
|
||||
} else if server_uri.scheme_str() != Some("http")
|
||||
&& server_uri.scheme_str() != Some("https")
|
||||
{
|
||||
Err(Err::InvalidUrl("invalid scheme!".to_string()))
|
||||
} else {
|
||||
Ok(Client {
|
||||
server: s.clone(),
|
||||
server: server_uri.clone(),
|
||||
region: Region::empty(),
|
||||
conn_client: if s.scheme_str() == Some("http") {
|
||||
conn_client: if server_uri.scheme_str() == Some("http") {
|
||||
ConnClient::HttpCC(client::Client::new())
|
||||
} else {
|
||||
let https = HttpsConnector::new(4).unwrap();
|
||||
@ -106,14 +131,14 @@ impl Client {
|
||||
self.region = r;
|
||||
}
|
||||
|
||||
fn add_host_header(&self, h: &mut HeaderMap) {
|
||||
fn add_host_header(&self, header_map: &mut HeaderMap) {
|
||||
let host_val = match self.server.port_part() {
|
||||
Some(port) => format!("{}:{}", self.server.host().unwrap_or(""), port),
|
||||
None => self.server.host().unwrap_or("").to_string(),
|
||||
};
|
||||
match header::HeaderValue::from_str(&host_val) {
|
||||
Ok(v) => {
|
||||
h.insert(header::HOST, v);
|
||||
header_map.insert(header::HOST, v);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -185,13 +210,17 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_bucket_location(&self, b: &str) -> impl Future<Item = Region, Error = Err> {
|
||||
let mut qp = HashMap::new();
|
||||
qp.insert("location".to_string(), None);
|
||||
/// get_bucket_location - Get location for the bucket_name.
|
||||
pub fn get_bucket_location(
|
||||
&self,
|
||||
bucket_name: &str,
|
||||
) -> impl Future<Item = Region, Error = Err> {
|
||||
let mut qp = Values::new();
|
||||
qp.set_value("location", None);
|
||||
|
||||
let s3_req = S3Req {
|
||||
method: Method::GET,
|
||||
bucket: Some(b.to_string()),
|
||||
bucket: Some(bucket_name.to_string()),
|
||||
object: None,
|
||||
headers: HeaderMap::new(),
|
||||
query: qp,
|
||||
@ -204,18 +233,18 @@ impl Client {
|
||||
resp.into_body()
|
||||
.concat2()
|
||||
.map_err(|err| Err::HyperErr(err))
|
||||
.and_then(move |chunk| b2s(chunk.into_bytes()))
|
||||
.and_then(move |chunk| chunk_to_string(&chunk))
|
||||
.and_then(|s| xml::parse_bucket_location(s))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_bucket(&self, b: &str) -> impl Future<Item = (), Error = Err> {
|
||||
pub fn delete_bucket(&self, bucket_name: &str) -> impl Future<Item = (), Error = Err> {
|
||||
let s3_req = S3Req {
|
||||
method: Method::DELETE,
|
||||
bucket: Some(b.to_string()),
|
||||
bucket: Some(bucket_name.to_string()),
|
||||
object: None,
|
||||
headers: HeaderMap::new(),
|
||||
query: HashMap::new(),
|
||||
query: Values::new(),
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
};
|
||||
@ -223,13 +252,13 @@ impl Client {
|
||||
.and_then(|_| Ok(()))
|
||||
}
|
||||
|
||||
pub fn bucket_exists(&self, b: &str) -> impl Future<Item = bool, Error = Err> {
|
||||
pub fn bucket_exists(&self, bucket_name: &str) -> impl Future<Item = bool, Error = Err> {
|
||||
let s3_req = S3Req {
|
||||
method: Method::HEAD,
|
||||
bucket: Some(b.to_string()),
|
||||
bucket: Some(bucket_name.to_string()),
|
||||
object: None,
|
||||
headers: HeaderMap::new(),
|
||||
query: HashMap::new(),
|
||||
query: Values::new(),
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
};
|
||||
@ -250,7 +279,7 @@ impl Client {
|
||||
|
||||
pub fn get_object_req(
|
||||
&self,
|
||||
b: &str,
|
||||
bucket_name: &str,
|
||||
key: &str,
|
||||
get_obj_opts: Vec<(HeaderName, HeaderValue)>,
|
||||
) -> impl Future<Item = GetObjectResp, Error = Err> {
|
||||
@ -264,10 +293,10 @@ impl Client {
|
||||
|
||||
let s3_req = S3Req {
|
||||
method: Method::GET,
|
||||
bucket: Some(b.to_string()),
|
||||
bucket: Some(bucket_name.to_string()),
|
||||
object: Some(key.to_string()),
|
||||
headers: h,
|
||||
query: HashMap::new(),
|
||||
query: Values::new(),
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
};
|
||||
@ -276,14 +305,14 @@ impl Client {
|
||||
.and_then(GetObjectResp::new)
|
||||
}
|
||||
|
||||
pub fn make_bucket(&self, b: &str) -> impl Future<Item = (), Error = Err> {
|
||||
pub fn make_bucket(&self, bucket_name: &str) -> impl Future<Item = (), Error = Err> {
|
||||
let xml_body_res = xml::get_mk_bucket_body();
|
||||
let bucket = b.clone().to_string();
|
||||
let bucket = bucket_name.clone().to_string();
|
||||
let s3_req = S3Req {
|
||||
method: Method::PUT,
|
||||
bucket: Some(bucket),
|
||||
object: None,
|
||||
query: HashMap::new(),
|
||||
query: Values::new(),
|
||||
headers: HeaderMap::new(),
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
@ -297,7 +326,7 @@ impl Client {
|
||||
method: Method::GET,
|
||||
bucket: None,
|
||||
object: None,
|
||||
query: HashMap::new(),
|
||||
query: Values::new(),
|
||||
headers: HeaderMap::new(),
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
@ -308,7 +337,7 @@ impl Client {
|
||||
resp.into_body()
|
||||
.concat2()
|
||||
.map_err(|err| Err::HyperErr(err))
|
||||
.and_then(move |chunk| b2s(chunk.into_bytes()))
|
||||
.and_then(move |chunk| chunk_to_string(&chunk))
|
||||
.and_then(|s| xml::parse_bucket_list(s))
|
||||
})
|
||||
}
|
||||
@ -321,21 +350,21 @@ impl Client {
|
||||
delimiter: Option<&str>,
|
||||
max_keys: Option<i32>,
|
||||
) -> impl Future<Item = ListObjectsResp, Error = Err> {
|
||||
let mut qparams = HashMap::new();
|
||||
qparams.insert("list-type".to_string(), Some("2".to_string()));
|
||||
let mut qparams: Values = Values::new();
|
||||
qparams.set_value("list-type", Some("2".to_string()));
|
||||
if let Some(d) = delimiter {
|
||||
qparams.insert("delimiter".to_string(), Some(d.to_string()));
|
||||
qparams.set_value("delimiter", Some(d.to_string()));
|
||||
}
|
||||
if let Some(m) = marker {
|
||||
qparams.insert("marker".to_string(), Some(m.to_string()));
|
||||
qparams.set_value("marker", Some(m.to_string()));
|
||||
}
|
||||
|
||||
if let Some(p) = prefix {
|
||||
qparams.insert("prefix".to_string(), Some(p.to_string()));
|
||||
qparams.set_value("prefix", Some(p.to_string()));
|
||||
}
|
||||
|
||||
if let Some(mkeys) = max_keys {
|
||||
qparams.insert("max-keys".to_string(), Some(mkeys.to_string()));
|
||||
qparams.set_value("max-keys", Some(mkeys.to_string()));
|
||||
}
|
||||
|
||||
let s3_req = S3Req {
|
||||
@ -352,10 +381,68 @@ impl Client {
|
||||
resp.into_body()
|
||||
.concat2()
|
||||
.map_err(|err| Err::HyperErr(err))
|
||||
.and_then(move |chunk| b2s(chunk.into_bytes()))
|
||||
.and_then(move |chunk| chunk_to_string(&chunk))
|
||||
.and_then(|s| xml::parse_list_objects(s))
|
||||
})
|
||||
}
|
||||
|
||||
/// listen_bucket_notificaion - Get bucket notifications for the bucket_name.
|
||||
pub fn listen_bucket_notificaion(
|
||||
&self,
|
||||
bucket_name: &str,
|
||||
prefix: Option<String>,
|
||||
suffix: Option<String>,
|
||||
events: Vec<String>,
|
||||
) -> impl Stream<Item = notification::NotificationInfo, Error = Err> {
|
||||
// Prepare request query parameters
|
||||
let mut query_params: Values = Values::new();
|
||||
query_params.set_value("prefix", prefix);
|
||||
query_params.set_value("suffix", suffix);
|
||||
let opt_events: Vec<Option<String>> = events.into_iter().map(|evt| Some(evt)).collect();
|
||||
query_params.insert("events".to_string(), opt_events);
|
||||
|
||||
// build signed request
|
||||
let s3_req = S3Req {
|
||||
method: Method::GET,
|
||||
bucket: Some(bucket_name.to_string()),
|
||||
object: None,
|
||||
headers: HeaderMap::new(),
|
||||
query: query_params,
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
};
|
||||
|
||||
self.signed_req_future(s3_req, Ok(Body::empty()))
|
||||
.map(|resp| {
|
||||
// Read the whole body for bucket location response.
|
||||
resp.into_body()
|
||||
.map_err(|e| Err::HyperErr(e))
|
||||
.filter(|c| {
|
||||
// filter out white spaces sent by the server to indicate it's still alive
|
||||
c[0] != SPACE_BYTE[0]
|
||||
})
|
||||
.map(|chunk| {
|
||||
// Split the chunk by lines and process.
|
||||
// TODO: Handle case when partial lines are present in the chunk
|
||||
let chunk_lines = String::from_utf8(chunk.to_vec())
|
||||
.map(|p| {
|
||||
let lines =
|
||||
p.lines().map(|s| s.to_string()).collect::<Vec<String>>();
|
||||
stream::iter_ok(lines.into_iter())
|
||||
})
|
||||
.map_err(|e| Err::Utf8DecodingErr(e));
|
||||
futures::future::result(chunk_lines).flatten_stream()
|
||||
})
|
||||
.flatten()
|
||||
.map(|line| {
|
||||
// Deserialize the notification
|
||||
let notification_info: NotificationInfo =
|
||||
serde_json::from_str(&line).unwrap();
|
||||
notification_info
|
||||
})
|
||||
})
|
||||
.flatten_stream()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_req_future(
|
||||
@ -375,10 +462,11 @@ fn run_req_future(
|
||||
})
|
||||
}
|
||||
|
||||
fn b2s(b: Bytes) -> Result<String, Err> {
|
||||
match String::from_utf8(b.iter().map(|x| x.clone()).collect::<Vec<u8>>()) {
|
||||
/// Converts a `hyper::Chunk` into a string.
|
||||
fn chunk_to_string(chunk: &hyper::Chunk) -> Result<String, Err> {
|
||||
match String::from_utf8(chunk.to_vec()) {
|
||||
Err(e) => Err(Err::Utf8DecodingErr(e)),
|
||||
Ok(s) => Ok(s),
|
||||
Ok(s) => Ok(s.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -387,7 +475,7 @@ pub struct S3Req {
|
||||
bucket: Option<String>,
|
||||
object: Option<String>,
|
||||
headers: HeaderMap,
|
||||
query: HashMap<String, Option<String>>,
|
||||
query: Values,
|
||||
body: Body,
|
||||
ts: Tm,
|
||||
}
|
||||
@ -405,14 +493,54 @@ impl S3Req {
|
||||
res
|
||||
}
|
||||
|
||||
/// Takes the query_parameters and turn them into a valid query string for example:
|
||||
/// {"key1":["val1","val2"],"key2":["val1","val2"]}
|
||||
/// will be returned as:
|
||||
/// "key1=val1&key1=val2&key2=val3&key2=val4"
|
||||
fn mk_query(&self) -> String {
|
||||
self.query
|
||||
.iter()
|
||||
.map(|(x, y)| match y {
|
||||
Some(v) => format!("{}={}", x, v),
|
||||
None => x.to_string(),
|
||||
.map(|(key, values)| {
|
||||
values.iter().map(move |value| match value {
|
||||
Some(v) => format!("{}={}", &key, v),
|
||||
None => format!("{}=", &key,),
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<String>>()
|
||||
.join("&")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod minio_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_query_parameters() {
|
||||
let mut query_params: HashMap<String, Vec<Option<String>>> = HashMap::new();
|
||||
query_params.insert(
|
||||
"key1".to_string(),
|
||||
vec![Some("val1".to_string()), Some("val2".to_string())],
|
||||
);
|
||||
query_params.insert(
|
||||
"key2".to_string(),
|
||||
vec![Some("val3".to_string()), Some("val4".to_string())],
|
||||
);
|
||||
|
||||
let s3_req = S3Req {
|
||||
method: Method::GET,
|
||||
bucket: None,
|
||||
object: None,
|
||||
headers: HeaderMap::new(),
|
||||
query: query_params,
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
};
|
||||
let result = s3_req.mk_query();
|
||||
assert!(result.contains("key1=val1"));
|
||||
assert!(result.contains("key1=val2"));
|
||||
assert!(result.contains("key2=val3"));
|
||||
assert!(result.contains("key2=val4"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,24 @@
|
||||
use crate::minio;
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use hyper::{header::HeaderName, header::HeaderValue, Body, Request};
|
||||
|
||||
use crate::minio;
|
||||
|
||||
pub fn mk_request(
|
||||
r: &minio::S3Req,
|
||||
svr_str: &str,
|
||||
|
||||
113
src/minio/net.rs
Normal file
113
src/minio/net.rs
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type Values = HashMap<String, Vec<Option<String>>>;
|
||||
|
||||
pub trait ValuesAccess {
|
||||
fn get_value(&self, key: &str) -> Option<String>;
|
||||
fn set_value(&mut self, key: &str, value: Option<String>);
|
||||
fn add_value(&mut self, key: &str, value: Option<String>);
|
||||
fn del_value(&mut self, key: &str);
|
||||
}
|
||||
|
||||
impl ValuesAccess for Values {
|
||||
/// Gets the first item for a given key. If the key is invalid it returns `None`
|
||||
/// To get multiple values use the `Values` instance as map.
|
||||
fn get_value(&self, key: &str) -> Option<String> {
|
||||
let value_vec = match self.get(key) {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
if value_vec.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
return value_vec.get(0).unwrap().clone();
|
||||
}
|
||||
|
||||
/// Sets the key to value. It replaces any existing values.
|
||||
fn set_value(&mut self, key: &str, value: Option<String>) {
|
||||
self.insert(key.to_string(), vec![value]);
|
||||
}
|
||||
|
||||
/// Add adds the value to key. It appends to any existing values associated with key.
|
||||
fn add_value(&mut self, key: &str, value: Option<String>) {
|
||||
match self.get_mut(key) {
|
||||
Some(value_vec) => value_vec.push(value),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Del deletes the values associated with key.
|
||||
fn del_value(&mut self, key: &str) {
|
||||
self.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod net_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn values_set() {
|
||||
let mut values = Values::new();
|
||||
values.set_value("key", Some("value".to_string()));
|
||||
assert_eq!(values.len(), 1);
|
||||
assert_eq!(values.get("key").unwrap().len(), 1);
|
||||
|
||||
values.set_value("key", None);
|
||||
assert_eq!(values.len(), 1);
|
||||
assert_eq!(values.get("key").unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_add() {
|
||||
let mut values = Values::new();
|
||||
values.set_value("key", Some("value".to_string()));
|
||||
assert_eq!(values.get("key").unwrap().len(), 1);
|
||||
|
||||
values.add_value("key", None);
|
||||
assert_eq!(values.get("key").unwrap().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_del() {
|
||||
let mut values = Values::new();
|
||||
values.set_value("key", Some("value".to_string()));
|
||||
values.add_value("key", None);
|
||||
values.del_value("key");
|
||||
assert_eq!(values.len(), 0);
|
||||
|
||||
let mut values2 = Values::new();
|
||||
values2.set_value("key", Some("value".to_string()));
|
||||
values2.add_value("key", None);
|
||||
values2.set_value("key2", Some("value".to_string()));
|
||||
values2.add_value("key2", None);
|
||||
|
||||
values2.del_value("key");
|
||||
assert_eq!(values2.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_get() {
|
||||
let mut values = Values::new();
|
||||
values.set_value("key", Some("value".to_string()));
|
||||
values.add_value("key", None);
|
||||
assert_eq!(values.get_value("key"), Some("value".to_string()));
|
||||
}
|
||||
}
|
||||
112
src/minio/notification.rs
Normal file
112
src/minio/notification.rs
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// Notification event object metadata.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ObjectMeta {
|
||||
#[serde(rename(deserialize = "key"))]
|
||||
pub key: String,
|
||||
#[serde(rename(deserialize = "size"))]
|
||||
pub size: Option<i64>,
|
||||
#[serde(rename(deserialize = "eTag"))]
|
||||
pub e_tag: Option<String>,
|
||||
#[serde(rename(deserialize = "versionId"))]
|
||||
pub version_id: Option<String>,
|
||||
#[serde(rename(deserialize = "sequencer"))]
|
||||
pub sequencer: String,
|
||||
}
|
||||
|
||||
/// Notification event bucket metadata.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct BucketMeta {
|
||||
#[serde(rename(deserialize = "name"))]
|
||||
pub name: String,
|
||||
#[serde(rename(deserialize = "ownerIdentity"))]
|
||||
pub owner_identity: Identity,
|
||||
#[serde(rename(deserialize = "arn"))]
|
||||
pub arn: String,
|
||||
}
|
||||
|
||||
/// Indentity represents the user id, this is a compliance field.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Identity {
|
||||
#[serde(rename(deserialize = "principalId"))]
|
||||
pub principal_id: String,
|
||||
}
|
||||
|
||||
//// sourceInfo represents information on the client that
|
||||
//// triggered the event notification.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SourceInfo {
|
||||
#[serde(rename(deserialize = "host"))]
|
||||
pub host: String,
|
||||
#[serde(rename(deserialize = "port"))]
|
||||
pub port: String,
|
||||
#[serde(rename(deserialize = "userAgent"))]
|
||||
pub user_agent: String,
|
||||
}
|
||||
|
||||
/// Notification event server specific metadata.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EventMeta {
|
||||
#[serde(rename(deserialize = "s3SchemaVersion"))]
|
||||
pub schema_version: String,
|
||||
#[serde(rename(deserialize = "configurationId"))]
|
||||
pub configuration_id: String,
|
||||
#[serde(rename(deserialize = "bucket"))]
|
||||
pub bucket: BucketMeta,
|
||||
#[serde(rename(deserialize = "object"))]
|
||||
pub object: ObjectMeta,
|
||||
}
|
||||
|
||||
/// NotificationEvent represents an Amazon an S3 bucket notification event.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NotificationEvent {
|
||||
#[serde(rename(deserialize = "eventVersion"))]
|
||||
pub event_version: String,
|
||||
#[serde(rename(deserialize = "eventSource"))]
|
||||
pub event_source: String,
|
||||
#[serde(rename(deserialize = "awsRegion"))]
|
||||
pub aws_region: String,
|
||||
#[serde(rename(deserialize = "eventTime"))]
|
||||
pub event_time: String,
|
||||
#[serde(rename(deserialize = "eventName"))]
|
||||
pub event_name: String,
|
||||
#[serde(rename(deserialize = "source"))]
|
||||
pub source: SourceInfo,
|
||||
#[serde(rename(deserialize = "userIdentity"))]
|
||||
pub user_identity: Identity,
|
||||
#[serde(rename(deserialize = "requestParameters"))]
|
||||
pub request_parameters: HashMap<String, String>,
|
||||
#[serde(rename(deserialize = "responseElements"))]
|
||||
pub response_elements: HashMap<String, String>,
|
||||
#[serde(rename(deserialize = "s3"))]
|
||||
pub s3: EventMeta,
|
||||
}
|
||||
|
||||
/// NotificationInfo - represents the collection of notification events, additionally
|
||||
/// also reports errors if any while listening on bucket notifications.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NotificationInfo {
|
||||
#[serde(rename(deserialize = "Records"), default = "Vec::new")]
|
||||
pub records: Vec<NotificationEvent>,
|
||||
pub err: Option<String>,
|
||||
}
|
||||
@ -1,8 +1,26 @@
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use hyper::header::{
|
||||
HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT,
|
||||
};
|
||||
use ring::{digest, hmac};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use time::Tm;
|
||||
|
||||
use crate::minio;
|
||||
@ -66,20 +84,21 @@ fn uri_encode_str(s: &str, encode_slash: bool) -> String {
|
||||
s.chars().map(|x| uri_encode(x, encode_slash)).collect()
|
||||
}
|
||||
|
||||
fn get_canonical_querystr(q: &HashMap<String, Option<String>>) -> String {
|
||||
let mut hs: Vec<(String, Option<String>)> = q.clone().drain().collect();
|
||||
fn get_canonical_querystr(q: &HashMap<String, Vec<Option<String>>>) -> String {
|
||||
let mut hs: Vec<(String, Vec<Option<String>>)> = q.clone().drain().collect();
|
||||
// sort keys
|
||||
hs.sort();
|
||||
let vs: Vec<String> = hs
|
||||
.drain(..)
|
||||
.map(|(x, y)| {
|
||||
let val_str = match y {
|
||||
Some(s) => uri_encode_str(&s, true),
|
||||
None => "".to_string(),
|
||||
};
|
||||
uri_encode_str(&x, true) + "=" + &val_str
|
||||
// Build canonical query string
|
||||
hs.iter()
|
||||
.map(|(key, values)| {
|
||||
values.iter().map(move |value| match value {
|
||||
Some(v) => format!("{}={}", &key, uri_encode_str(&v, true)),
|
||||
None => format!("{}=", &key),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
vs[..].join("&")
|
||||
.flatten()
|
||||
.collect::<Vec<String>>()
|
||||
.join("&")
|
||||
}
|
||||
|
||||
fn get_canonical_request(
|
||||
@ -180,3 +199,25 @@ pub fn sign_v4(
|
||||
vec![auth_hdr, date_hdr]
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sign_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn canonical_ordered() {
|
||||
let mut query_params: HashMap<String, Vec<Option<String>>> = HashMap::new();
|
||||
|
||||
query_params.insert("key2".to_string(), vec![Some("val3".to_string()), None]);
|
||||
|
||||
query_params.insert(
|
||||
"key1".to_string(),
|
||||
vec![Some("val1".to_string()), Some("val2".to_string())],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_canonical_querystr(&query_params),
|
||||
"key1=val1&key1=val2&key2=val3&key2="
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::stream::Stream;
|
||||
use hyper::header::{
|
||||
|
||||
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
extern crate xml;
|
||||
|
||||
use xml::writer::{EmitterConfig, EventWriter, XmlEvent};
|
||||
|
||||
@ -1,10 +1,29 @@
|
||||
use crate::minio::types::{BucketInfo, Err, ListObjectsResp, ObjectInfo, Region};
|
||||
use crate::minio::woxml;
|
||||
use hyper::body::Body;
|
||||
use roxmltree;
|
||||
/*
|
||||
* MinIO Rust Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use hyper::body::Body;
|
||||
use roxmltree;
|
||||
|
||||
use crate::minio::types::{BucketInfo, Err, ListObjectsResp, ObjectInfo, Region};
|
||||
use crate::minio::woxml;
|
||||
|
||||
pub fn parse_bucket_location(s: String) -> Result<Region, Err> {
|
||||
let res = roxmltree::Document::parse(&s);
|
||||
match res {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user