From d686ba240f4b823d16766b204996286524eaf4ef Mon Sep 17 00:00:00 2001 From: Thijs Cadier Date: Tue, 23 Jun 2015 22:10:31 +0200 Subject: [PATCH] Initial version --- .gitignore | 4 + Cargo.toml | 13 + LICENSE | 21 ++ README.md | 21 ++ mongo_c_driver_wrapper/Cargo.toml | 14 ++ mongo_c_driver_wrapper/build.rs | 86 +++++++ mongo_c_driver_wrapper/src/lib.rs | 4 + src/bsonc.rs | 145 ++++++++++++ src/client.rs | 161 +++++++++++++ src/collection.rs | 379 ++++++++++++++++++++++++++++++ src/cursor.rs | 139 +++++++++++ src/error.rs | 296 +++++++++++++++++++++++ src/flags.rs | 120 ++++++++++ src/lib.rs | 58 +++++ src/read_prefs.rs | 59 +++++ src/uri.rs | 69 ++++++ src/write_concern.rs | 36 +++ 17 files changed, 1625 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 mongo_c_driver_wrapper/Cargo.toml create mode 100644 mongo_c_driver_wrapper/build.rs create mode 100644 mongo_c_driver_wrapper/src/lib.rs create mode 100644 src/bsonc.rs create mode 100644 src/client.rs create mode 100644 src/collection.rs create mode 100644 src/cursor.rs create mode 100644 src/error.rs create mode 100644 src/flags.rs create mode 100644 src/lib.rs create mode 100644 src/read_prefs.rs create mode 100644 src/uri.rs create mode 100644 src/write_concern.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7e225c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +mongo_c_driver_wrapper/mongo-c-driver* +mongo_c_driver_wrapper/src/bindings.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..18efb91 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mongo_driver" +version = "0.1.0" +authors = ["Thijs Cadier "] + +[dependencies] +libc = "*" + +[dependencies.bson] +git = "https://github.com/zonyitoo/bson-rs.git" + +[dependencies.mongo_c_driver_wrapper] +path = "mongo_c_driver_wrapper" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..05cc473 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Thijs Cadier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..13b4a66 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Mongo Rust Driver + +## About + +Mongo Rust driver built on top of the [Mongo C driver](https://github.com/mongodb/mongo-c-driver). +This drivers aims to be a thin wrapper around the production-ready C driver, while providing a safe +and ergonomic Rust interface that handles all the gnarly usage details of the C driver for you. + +Bson encoding and decoding is handled by the [bson crate](https://github.com/zonyitoo/bson-rs), the bindings +are generated using [bindgen](https://github.com/crabtw/rust-bindgen). + +The API is experimental, it might change at any time. + +## Compatibility + +The driver currently only builds on Unix, only tested on Mac Os X so far. + +## Contributing + +Contributions are very welcome, only the parts of the C driver we need have been wrapped so far. Please +write a test for any behavior you add. diff --git a/mongo_c_driver_wrapper/Cargo.toml b/mongo_c_driver_wrapper/Cargo.toml new file mode 100644 index 0000000..d86c595 --- /dev/null +++ b/mongo_c_driver_wrapper/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mongo_c_driver_wrapper" +version = "1.1.8" +build = "build.rs" +authors = ["Thijs Cadier "] + +[dependencies] +libc = "*" + +[build-dependencies] +pkg-config = "*" + +[build-dependencies.bindgen] +git = "https://github.com/crabtw/rust-bindgen.git" diff --git a/mongo_c_driver_wrapper/build.rs b/mongo_c_driver_wrapper/build.rs new file mode 100644 index 0000000..adc00e5 --- /dev/null +++ b/mongo_c_driver_wrapper/build.rs @@ -0,0 +1,86 @@ +#![feature(path_ext)] + +extern crate bindgen; +extern crate pkg_config; + +use std::env; +use std::fs::PathExt; +use std::path::Path; +use std::process::Command; + +static VERSION: &'static str = "1.1.8"; // Should be the same as the version in the manifest + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let current_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let driver_path = format!( + "mongo-c-driver-{}", + VERSION + ); + + let libmongoc_path = Path::new(&out_dir).join("lib/libmongoc-1.0.a"); + if !libmongoc_path.exists() { // TODO: This should check if we're at the right version + // Download and extract driver archive + let url = format!( + "https://github.com/mongodb/mongo-c-driver/releases/download/{}/mongo-c-driver-{}.tar.gz", + VERSION, + VERSION + ); + assert!(Command::new("curl").arg("-O") // Save to disk + .arg("-L") // Follow redirects + .arg(url) + .status() + .unwrap() + .success()); + + let archive_name = format!( + "mongo-c-driver-{}.tar.gz", + VERSION + ); + assert!(Command::new("tar").arg("xzf") + .arg(&archive_name) + .status() + .unwrap() + .success()); + + // Configure and install + assert!(Command::new("sh").arg("configure") + .arg("--enable-ssl=yes") + .arg("--enable-sasl=no") + .arg("--enable-static=yes") + .arg("--enable-shared=no") + .arg("--with-libbson=bundled") + .arg(format!("--prefix={}", &out_dir)) + .current_dir(&driver_path) + .status() + .unwrap() + .success()); + assert!(Command::new("make").current_dir(&driver_path).status().unwrap().success()); + assert!(Command::new("make").arg("install").current_dir(&driver_path).status().unwrap().success()); + + // Generate bindings + let bindings_rs_path = Path::new(¤t_dir).join("src/bindings.rs"); + let mongo_h_path = Path::new(¤t_dir).join(&driver_path).join("src/mongoc/mongoc.h"); + let bson_path = Path::new(¤t_dir).join(&driver_path).join("src/libbson/src/bson"); + bindgen::builder() + .emit_builtins() + .header(mongo_h_path.to_str().unwrap()) + .clang_arg(format!("-I{}", bson_path.to_str().unwrap())) + .generate() + .unwrap() + .write_to_file(&bindings_rs_path) + .unwrap(); + } + + // Output to Cargo + println!("cargo:root={}", &out_dir); + println!("cargo:libdir={}/lib", &out_dir); + println!("cargo:include={}/include", &out_dir); + println!("cargo:rustc-link-search={}/lib", &out_dir); + println!("cargo:rustc-link-lib=static=bson-1.0"); + println!("cargo:rustc-link-lib=static=mongoc-1.0"); + + for link_path in pkg_config::find_library("openssl").unwrap().link_paths.iter(){ + println!("cargo:rustc-link-search=framework={}", &link_path.display()); + } +} diff --git a/mongo_c_driver_wrapper/src/lib.rs b/mongo_c_driver_wrapper/src/lib.rs new file mode 100644 index 0000000..7dfe404 --- /dev/null +++ b/mongo_c_driver_wrapper/src/lib.rs @@ -0,0 +1,4 @@ +extern crate libc; + +#[allow(non_camel_case_types,non_snake_case)] +pub mod bindings; diff --git a/src/bsonc.rs b/src/bsonc.rs new file mode 100644 index 0000000..b42984a --- /dev/null +++ b/src/bsonc.rs @@ -0,0 +1,145 @@ +use std::ffi::{CStr,CString}; +use std::ptr; +use std::borrow::Cow; +use std::fmt; +use std::slice; +use libc::types::common::c95::c_void; + +use super::BsoncError; + +use mongo_c_driver_wrapper::bindings; +use bson; + +use super::Result; + +pub struct Bsonc { + inner: *mut bindings::bson_t +} + +impl Bsonc { + pub fn from_ptr(inner: *const bindings::bson_t) -> Bsonc { + assert!(!inner.is_null()); + Bsonc { inner: inner as *mut bindings::bson_t } + } + + pub fn from_document(document: &bson::Document) -> Result { + let mut buffer = Vec::new(); + try!(bson::encode_document(&mut buffer, document)); + + let inner = unsafe { + bindings::bson_new_from_data( + buffer[..].as_ptr(), + buffer.len() as u64 + ) + }; + + // Inner will be null if there was an error converting the data. + // We're assuming the bson crate works and therefore assert here. + // See: http://api.mongodb.org/libbson/current/bson_new_from_data.html + assert!(!inner.is_null()); + + Ok(Bsonc{ inner: inner }) + } + + pub fn from_json>>(json: S) -> Result { + let json_cstring = CString::new(json).unwrap(); + let mut error = BsoncError::empty(); + + let inner = unsafe { + bindings::bson_new_from_json( + json_cstring.as_ptr() as *const u8, + json_cstring.as_bytes().len() as i64, + error.mut_inner() + ) + }; + + if error.is_empty() { + Ok(Bsonc{ inner: inner }) + } else { + Err(error.into()) + } + } + + pub fn as_document(&self) -> Result { + assert!(!self.inner.is_null()); + + // This pointer should not be modified or freed + // See: http://api.mongodb.org/libbson/current/bson_get_data.html + let data_ptr = unsafe { bindings::bson_get_data(self.inner) }; + assert!(!data_ptr.is_null()); + + let data_len = unsafe { + let bson = *self.inner; + bson.len + } as usize; + + let mut slice = unsafe { + slice::from_raw_parts(data_ptr, data_len) + }; + + let document = try!(bson::decode_document(&mut slice)); + Ok(document) + } + + pub fn as_json(&self) -> Cow { + assert!(!self.inner.is_null()); + let json_ptr = unsafe { bindings::bson_as_json(self.inner, ptr::null_mut()) }; + assert!(!json_ptr.is_null()); + let json_cstr = unsafe { CStr::from_ptr(json_ptr) }; + let out = String::from_utf8_lossy(json_cstr.to_bytes()); + unsafe { bindings::bson_free(json_ptr as *mut c_void); } + out + } + + pub fn inner(&self) -> *const bindings::bson_t { + self.inner + } +} + +impl fmt::Debug for Bsonc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Bsonc: {}", self.as_json()) + } +} + +impl Drop for Bsonc { + fn drop(&mut self) { + unsafe { + bindings::bson_destroy(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + use bson; + + #[test] + fn test_bsonc_from_and_as_document() { + let mut document = bson::Document::new(); + document.insert("key".to_string(), bson::Bson::String("value".to_string())); + + let bsonc = super::Bsonc::from_document(&document).unwrap(); + + let decoded = bsonc.as_document().unwrap(); + assert!(decoded.contains_key("key")); + } + + #[test] + fn test_bsonc_from_and_as_json() { + let json = "{ \"key\" : \"value\" }"; + let bsonc = super::Bsonc::from_json(json).unwrap(); + assert_eq!(json.to_string(), bsonc.as_json().into_owned()); + } + + #[test] + fn test_invalid_json() { + let malformed_json = "{ \"key\" : \"val }"; + let bsonc_result = super::Bsonc::from_json(malformed_json); + + assert!(bsonc_result.is_err()); + + let error_message = format!("{:?}", bsonc_result.err().unwrap()); + assert!(error_message.starts_with("MongoError (BsoncError: parse error: premature EOF")); + } +} diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..4756023 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,161 @@ +use std::fmt; +use std::ffi::CString; + +use mongo_c_driver_wrapper::bindings; + +use super::uri::Uri; +use super::collection::Collection; + +// TODO: We're using a sort of poor man's Arc here +// with this root bool, there must be a better way. +pub struct ClientPool { + root_instance: bool, + uri: Uri, + inner: *mut bindings::mongoc_client_pool_t +} + +impl ClientPool { + /// Create a new ClientPool + /// See: http://api.mongodb.org/c/current/mongoc_client_pool_t.html + pub fn new(uri: Uri) -> ClientPool { + let pool = unsafe { + let pool_ptr = bindings::mongoc_client_pool_new(uri.inner()); + assert!(!pool_ptr.is_null()); + pool_ptr + }; + ClientPool { + root_instance: true, + uri: uri, // Become owner of uri so it doesn't go out of scope + inner: pool + } + } + + /// Retrieve a client from the client pool, possibly blocking until one is available. + /// See: http://api.mongodb.org/c/current/mongoc_client_pool_pop.html + pub fn pop(&self) -> Client { + assert!(!self.inner.is_null()); + let client = unsafe { bindings::mongoc_client_pool_pop(self.inner) }; + Client{ + client_pool: self, + inner: client + } + } + + /// Return a client back to the client pool, called from drop of client. + /// See: http://api.mongodb.org/c/current/mongoc_client_pool_push.html + unsafe fn push(&self, mongo_client: *mut bindings::mongoc_client_t) { + assert!(!self.inner.is_null()); + bindings::mongoc_client_pool_push( + self.inner, + mongo_client + ); + } +} + +unsafe impl Send for ClientPool { } +unsafe impl Sync for ClientPool { } + +impl fmt::Debug for ClientPool { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ClientPool for {}", self.uri.as_str()) + } +} + +impl Clone for ClientPool { + fn clone(&self) -> ClientPool { + assert!(!self.inner.is_null()); + ClientPool { + root_instance: false, + uri: self.uri.clone(), + inner: self.inner.clone() + } + } +} + +impl Drop for ClientPool { + fn drop(&mut self) { + if self.root_instance { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_client_pool_destroy(self.inner); + } + } + } +} + +pub struct Client<'a> { + client_pool: &'a ClientPool, + inner: *mut bindings::mongoc_client_t +} + +impl<'a> Client<'a> { + pub fn get_collection>>(&'a self, db: S, collection: S) -> Collection<'a> { + assert!(!self.inner.is_null()); + let mut coll; + unsafe { + let db_cstring = CString::new(db).unwrap(); + let collection_cstring = CString::new(collection).unwrap(); + + coll = bindings::mongoc_client_get_collection( + self.inner, + db_cstring.as_ptr(), + collection_cstring.as_ptr() + ); + } + Collection::new(self, coll) + } +} + +impl<'a> Drop for Client<'a> { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + self.client_pool.push(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + use std::thread; + use super::super::uri::Uri; + use super::super::client::ClientPool; + + #[test] + fn test_new_pool_and_pop_client() { + super::super::init(); + + let uri = Uri::new("mongodb://localhost:27017/"); + let pool = ClientPool::new(uri); + + // Pop a client and insert a couple of times + for _ in 0..10 { + let client = pool.pop(); + pool.pop(); + client.get_collection("rust_test", "items"); + } + } + + #[test] + fn test_new_pool_and_pop_client_in_threads() { + super::super::init(); + + let uri = Uri::new("mongodb://localhost:27017/"); + let pool = ClientPool::new(uri); + + let pool1 = pool.clone(); + let guard1 = thread::scoped(move || { + let client = pool1.pop(); + client.get_collection("test", "items"); + }); + + let pool2 = pool.clone(); + let guard2 = thread::scoped(move || { + let client = pool2.pop(); + client.get_collection("test", "items"); + }); + + guard1.join(); + guard2.join(); + } +} diff --git a/src/collection.rs b/src/collection.rs new file mode 100644 index 0000000..ad1bb2c --- /dev/null +++ b/src/collection.rs @@ -0,0 +1,379 @@ +use std::ptr; + +use mongo_c_driver_wrapper::bindings; + +use bson::Document; + +use super::Result; +use super::{BsoncError,InvalidParamsError}; +use super::bsonc::Bsonc; +use super::client::Client; +use super::cursor; +use super::cursor::Cursor; +use super::flags::{Flags,FlagsValue,InsertFlag,QueryFlag,RemoveFlag}; +use super::write_concern::WriteConcern; +use super::read_prefs::ReadPrefs; + +pub enum CreatedBy<'a> { + Client(&'a Client<'a>) +} + +pub struct Collection<'a> { + _created_by: CreatedBy<'a>, + inner: *mut bindings::mongoc_collection_t +} + +impl<'a> Collection<'a> { + pub fn new( + client: &'a Client<'a>, + inner: *mut bindings::mongoc_collection_t + ) -> Collection<'a> { + assert!(!inner.is_null()); + Collection { + _created_by: CreatedBy::Client(client), + inner: inner + } + } + + pub fn count_with_options( + &self, + query_flags: &Flags, + query: &Document, + skip: u32, + limit: u32, + opts: Option<&Document>, + read_prefs: Option<&ReadPrefs> + ) -> Result { + assert!(!self.inner.is_null()); + + let mut error = BsoncError::empty(); + let count = unsafe { + bindings::mongoc_collection_count_with_opts( + self.inner, + query_flags.flags(), + try!(Bsonc::from_document(query)).inner(), + skip as i64, + limit as i64, + match opts { + Some(o) => try!(Bsonc::from_document(o)).inner(), + None => ptr::null() + }, + match read_prefs { + Some(prefs) => prefs.inner(), + None => ptr::null() + }, + error.mut_inner() + ) + }; + + if error.is_empty() { + Ok(count) + } else { + Err(error.into()) + } + } + + pub fn count( + &self, + query: &Document + ) -> Result { + self.count_with_options( + &Flags::new(), + query, + 0, + 0, + None, + None + ) + } + + pub fn drop(&mut self) -> Result<()> { + assert!(!self.inner.is_null()); + let mut error = BsoncError::empty(); + let success = unsafe { + bindings::mongoc_collection_drop( + self.inner, + error.mut_inner() + ) + }; + if success == 0 { + assert!(!error.is_empty()); + return Err(error.into()) + } + Ok(()) + } + + pub fn find_with_options( + &'a self, + query_flags: &Flags, + skip: u32, + limit: u32, + batch_size: u32, + query: &Document, + fields: Option<&Document>, + read_prefs: Option<&ReadPrefs> + ) -> Result> { + assert!(!self.inner.is_null()); + + let inner = unsafe { + bindings::mongoc_collection_find( + self.inner, + query_flags.flags(), + skip, + limit, + batch_size, + try!(Bsonc::from_document(query)).inner(), + match fields { + Some(f) => { + try!(Bsonc::from_document(f)).inner() + }, + None => ptr::null() + }, + match read_prefs { + Some(prefs) => prefs.inner(), + None => ptr::null() + } + ) + }; + + if inner.is_null() { + return Err(InvalidParamsError.into()) + } + + Ok(Cursor::new(cursor::CreatedBy::Collection(self), inner)) + } + + pub fn find( + &'a self, + query: &Document + ) -> Result> { + self.find_with_options( + &Flags::new(), + 0, + 0, + 0, + &query, + None, + None + ) + } + + pub fn insert_with_options( + &'a self, + insert_flags: &Flags, + document: &Document, + write_concern: &WriteConcern + ) -> Result<()> { + assert!(!self.inner.is_null()); + + let mut error = BsoncError::empty(); + let success = unsafe { + bindings::mongoc_collection_insert( + self.inner, + insert_flags.flags(), + try!(Bsonc::from_document(&document)).inner(), + write_concern.inner(), + error.mut_inner() + ) + }; + + if success == 1 { + Ok(()) + } else { + Err(error.into()) + } + } + + pub fn insert(&'a self, document: &Document) -> Result<()> { + self.insert_with_options( + &Flags::new(), + document, + &WriteConcern::new() + ) + } + + pub fn remove_with_options( + &self, + remove_flags: &Flags, + selector: &Document, + write_concern: &WriteConcern + ) -> Result<()> { + assert!(!self.inner.is_null()); + + let mut error = BsoncError::empty(); + let success = unsafe { + bindings::mongoc_collection_remove( + self.inner, + remove_flags.flags(), + try!(Bsonc::from_document(&selector)).inner(), + write_concern.inner(), + error.mut_inner() + ) + }; + + if success == 1 { + Ok(()) + } else { + Err(error.into()) + } + } + + pub fn remove( + &self, + selector: &Document + ) -> Result<()> { + self.remove_with_options( + &Flags::new(), + selector, + &WriteConcern::new() + ) + } + + pub fn save_with_options( + &self, + document: &Document, + write_concern: &WriteConcern + ) -> Result<()> { + assert!(!self.inner.is_null()); + + let mut error = BsoncError::empty(); + let success = unsafe { + bindings::mongoc_collection_save( + self.inner, + try!(Bsonc::from_document(&document)).inner(), + write_concern.inner(), + error.mut_inner() + ) + }; + + if success == 1 { + Ok(()) + } else { + Err(error.into()) + } + } + + pub fn save( + &self, + document: &Document, + ) -> Result<()> { + self.save_with_options( + document, + &WriteConcern::new() + ) + } +} + +impl<'a> Drop for Collection<'a> { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_collection_destroy(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + use bson; + use super::super::uri::Uri; + use super::super::client::ClientPool; + use super::super::flags::{Flags}; + + #[test] + fn test_mutation_and_finding() { + let uri = Uri::new("mongodb://localhost:27017/"); + let pool = ClientPool::new(uri); + let client = pool.pop(); + let mut collection = client.get_collection("rust_driver_test", "items"); + collection.drop().unwrap_or(()); + + let mut document = bson::Document::new(); + document.insert("key_1".to_string(), bson::Bson::String("Value 1".to_string())); + document.insert("key_2".to_string(), bson::Bson::String("Value 2".to_string())); + assert!(collection.insert(&document).is_ok()); + + let mut second_document = bson::Document::new(); + second_document.insert("key_1".to_string(), bson::Bson::String("Value 3".to_string())); + assert!(collection.insert(&second_document).is_ok()); + + let query = bson::Document::new(); + + // Count the documents in the collection + assert_eq!(2, collection.count(&query).unwrap()); + + // Find the documents + assert_eq!( + collection.find(&document).unwrap().next().unwrap().unwrap().get("key_1").unwrap().to_json(), + bson::Bson::String("Value 1".to_string()).to_json() + ); + let mut found_document = collection.find(&second_document).unwrap().next().unwrap().unwrap(); + assert_eq!( + found_document.get("key_1").unwrap().to_json(), + bson::Bson::String("Value 3".to_string()).to_json() + ); + + // Update the second document + found_document.insert("key_1".to_string(), bson::Bson::String("Value 4".to_string())); + assert!(collection.save(&found_document).is_ok()); + + // Reload and check value + let found_document = collection.find(&found_document).unwrap().next().unwrap().unwrap(); + assert_eq!( + found_document.get("key_1").unwrap().to_json(), + bson::Bson::String("Value 4".to_string()).to_json() + ); + + // Remove one + assert!(collection.remove(&found_document).is_ok()); + + // Count again + assert_eq!(1, collection.count(&query).unwrap()); + + // Find the document and see if it has the keys we expect + { + let mut cursor = collection.find(&query).unwrap(); + let next_document = cursor.next().unwrap().unwrap(); + assert!(next_document.contains_key("key_1")); + assert!(next_document.contains_key("key_2")); + } + + // Find the document with fields set + let mut fields = bson::Document::new(); + fields.insert("key_1".to_string(), bson::Bson::Boolean(true)); + { + let mut cursor = collection.find_with_options( + &Flags::new(), + 0, + 0, + 0, + &query, + Some(&fields), + None + ).unwrap(); + let next_document = cursor.next().unwrap().unwrap(); + assert!(next_document.contains_key("key_1")); + assert!(!next_document.contains_key("key_2")); + } + + // Drop collection + collection.drop().unwrap(); + assert_eq!(0, collection.count(&query).unwrap()); + } + + #[test] + fn test_insert_failure() { + let uri = Uri::new("mongodb://localhost:27018/"); // There should be no mongo server here + let pool = ClientPool::new(uri); + let client = pool.pop(); + let collection = client.get_collection("rust_driver_test", "items"); + let document = bson::Document::new(); + + let result = collection.insert(&document); + assert!(result.is_err()); + assert_eq!( + "MongoError (BsoncError: Failed to connect to target host: localhost:27018)", + format!("{:?}", result.err().unwrap()) + ); + } +} diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 0000000..c014d91 --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,139 @@ +use std::iter::Iterator; +use std::ptr; + +use mongo_c_driver_wrapper::bindings; +use bson::Document; + +use super::BsoncError; +use super::bsonc; +use super::client::Client; +use super::collection::Collection; + +use super::Result; + +pub enum CreatedBy<'a> { + Collection(&'a Collection<'a>), + Client(&'a Client<'a>) +} + +pub struct Cursor<'a> { + _created_by: CreatedBy<'a>, + inner: *mut bindings::mongoc_cursor_t, +} + +impl<'a> Cursor<'a> { + pub fn new( + created_by: CreatedBy<'a>, + inner: *mut bindings::mongoc_cursor_t + ) -> Cursor<'a> { + assert!(!inner.is_null()); + Cursor { + _created_by: created_by, + inner: inner + } + } + + pub fn is_alive(&self) -> bool { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_cursor_is_alive(self.inner) == 1 + } + } + + pub fn more(&self) -> bool { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_cursor_more(self.inner) == 1 + } + } + + fn error(&self) -> BsoncError { + assert!(!self.inner.is_null()); + let mut error = BsoncError::empty(); + unsafe { + bindings::mongoc_cursor_error( + self.inner, + error.mut_inner() + ) + }; + error + } +} + +impl<'a> Iterator for Cursor<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + assert!(!self.inner.is_null()); + + // The C driver writes the document to memory and sets an + // already existing pointer to it. + let mut bson_ptr: *const bindings::bson_t = ptr::null(); + let success = unsafe { + bindings::mongoc_cursor_next( + self.inner, + &mut bson_ptr + ) + }; + + if success == 0 { + let error = self.error(); + if error.is_empty() { + return None + } else { + return Some(Err(error.into())) + } + } + assert!(!bson_ptr.is_null()); + + let bsonc = bsonc::Bsonc::from_ptr(bson_ptr); + let document = bsonc.as_document(); + match document { + Ok(document) => Some(Ok(document)), + Err(error) => Some(Err(error.into())) + } + } +} + +impl<'a> Drop for Cursor<'a> { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_cursor_destroy(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + use bson; + use super::super::uri::Uri; + use super::super::client::ClientPool; + use super::super::Result; + + #[test] + fn test_cursor() { + let uri = Uri::new("mongodb://localhost:27017/"); + let pool = ClientPool::new(uri); + let client = pool.pop(); + let mut collection = client.get_collection("rust_driver_test", "cursor_items"); + + let mut document = bson::Document::new(); + document.insert("key".to_string(), bson::Bson::String("value".to_string())); + + collection.drop().unwrap(); + for _ in 0..10 { + assert!(collection.insert(&document).is_ok()); + } + + let query = bson::Document::new(); + let cursor = collection.find(&query).unwrap(); + + assert!(cursor.is_alive()); + + let documents = cursor.into_iter().collect::>>(); + + // See if we got 10 results and the iterator then stopped + assert_eq!(10, documents.len()); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..44236af --- /dev/null +++ b/src/error.rs @@ -0,0 +1,296 @@ +use std::error; +use std::fmt; +use std::borrow::Cow; +use std::ffi::CStr; + +use bson::{DecoderError,EncoderError}; + +use mongo_c_driver_wrapper::bindings; + +pub enum MongoError { + Bsonc(BsoncError), + Decoder(DecoderError), + Encoder(EncoderError), + InvalidParams(InvalidParamsError) +} + +impl fmt::Display for MongoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + MongoError::Bsonc(ref err) => write!(f, "{}", err), + MongoError::Encoder(ref err) => write!(f, "{}", err), + MongoError::Decoder(ref err) => write!(f, "{}", err), + MongoError::InvalidParams(ref err) => write!(f, "{}", err) + } + } +} + +impl fmt::Debug for MongoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + MongoError::Bsonc(ref err) => write!(f, "MongoError ({:?})", err), + MongoError::Decoder(ref err) => write!(f, "MongoError ({:?})", err), + MongoError::Encoder(ref err) => write!(f, "MongoError ({:?})", err), + MongoError::InvalidParams(ref err) => write!(f, "MongoError ({:?})", err) + } + } +} + +impl error::Error for MongoError { + fn description(&self) -> &str { + match *self { + MongoError::Bsonc(ref err) => err.description(), + MongoError::Decoder(ref err) => err.description(), + MongoError::Encoder(ref err) => err.description(), + MongoError::InvalidParams(ref err) => err.description() + } + } + + fn cause(&self) -> Option<&error::Error> { + match *self { + MongoError::Bsonc(ref err) => Some(err), + MongoError::Decoder(ref err) => Some(err), + MongoError::Encoder(ref err) => Some(err), + MongoError::InvalidParams(ref err) => Some(err) + } + } +} + +impl From for MongoError { + fn from(error: DecoderError) -> MongoError { + MongoError::Decoder(error) + } +} + +impl From for MongoError { + fn from(error: EncoderError) -> MongoError { + MongoError::Encoder(error) + } +} + +pub struct BsoncError { + inner: bindings::bson_error_t, +} + +#[derive(Debug,PartialEq)] +pub enum MongoErrorDomain { + Blank, + Client, + Stream, + Protocol, + Cursor, + Query, + Insert, + Sasl, + Bson, + Matcher, + Namespace, + Command, + Collection, + Gridfs, + Scram, + Unknown +} + +#[derive(Debug,PartialEq)] +pub enum MongoErrorCode { + Blank, + StreamInvalidType, + StreamInvalidState, + StreamNameResolution, + StreamSocket, + StreamConnect, + StreamNotEstablished, + ClientNotReady, + ClientTooBig, + ClientTooSmall, + ClientGetnonce, + ClientAuthenticate, + ClientNoAcceptablePeer, + ClientInExhaust, + ProtocolInvalidReply, + ProtocolBadWireVersion, + CursorInvalidCursor, + QueryFailure, + BsonInvalid, + MatcherInvalid, + NamespaceInvalid, + NamespaceInvalidFilterType, + CommandInvalidArg, + CollectionInsertFailed, + CollectionUpdateFailed, + CollectionDeleteFailed, + CollectionDoesNotExist, + GridfsInvalidFilename, + ScramNotDone, + ScramProtocolError, + QueryCommandNotFound, + QueryNotTailable, + Unknown +} + +impl BsoncError { + pub fn empty() -> BsoncError { + BsoncError { + inner: bindings::bson_error_t { + domain: 0, + code: 0, + message: [0; 504] + } + } + } + + pub fn is_empty(&self) -> bool { + self.inner.domain == 0 && self.inner.code == 0 + } + + pub fn domain(&self) -> MongoErrorDomain { + match self.inner.domain { + 0 => MongoErrorDomain::Blank, + bindings::MONGOC_ERROR_CLIENT => MongoErrorDomain::Client, + bindings::MONGOC_ERROR_STREAM => MongoErrorDomain::Stream, + bindings::MONGOC_ERROR_PROTOCOL => MongoErrorDomain::Protocol, + bindings::MONGOC_ERROR_CURSOR => MongoErrorDomain::Cursor, + bindings::MONGOC_ERROR_QUERY => MongoErrorDomain::Query, + bindings::MONGOC_ERROR_INSERT => MongoErrorDomain::Insert, + bindings::MONGOC_ERROR_SASL => MongoErrorDomain::Sasl, + bindings::MONGOC_ERROR_BSON => MongoErrorDomain::Bson, + bindings::MONGOC_ERROR_MATCHER => MongoErrorDomain::Matcher, + bindings::MONGOC_ERROR_NAMESPACE => MongoErrorDomain::Namespace, + bindings::MONGOC_ERROR_COMMAND => MongoErrorDomain::Command, + bindings::MONGOC_ERROR_COLLECTION => MongoErrorDomain::Collection, + bindings::MONGOC_ERROR_GRIDFS => MongoErrorDomain::Gridfs, + bindings::MONGOC_ERROR_SCRAM => MongoErrorDomain::Scram, + _ => MongoErrorDomain::Unknown + } + } + + pub fn code(&self) -> MongoErrorCode { + match self.inner.code { + 0 => MongoErrorCode::Blank, + bindings::MONGOC_ERROR_STREAM_INVALID_TYPE => MongoErrorCode::StreamInvalidType, + bindings::MONGOC_ERROR_STREAM_INVALID_STATE => MongoErrorCode::StreamInvalidState, + bindings::MONGOC_ERROR_STREAM_NAME_RESOLUTION => MongoErrorCode::StreamNameResolution, + bindings::MONGOC_ERROR_STREAM_SOCKET => MongoErrorCode::StreamSocket, + bindings::MONGOC_ERROR_STREAM_CONNECT => MongoErrorCode::StreamConnect, + bindings::MONGOC_ERROR_STREAM_NOT_ESTABLISHED => MongoErrorCode::StreamNotEstablished, + bindings::MONGOC_ERROR_CLIENT_NOT_READY => MongoErrorCode::ClientNotReady, + bindings::MONGOC_ERROR_CLIENT_TOO_BIG => MongoErrorCode::ClientTooBig, + bindings::MONGOC_ERROR_CLIENT_TOO_SMALL => MongoErrorCode::ClientTooSmall, + bindings::MONGOC_ERROR_CLIENT_GETNONCE => MongoErrorCode::ClientGetnonce, + bindings::MONGOC_ERROR_CLIENT_AUTHENTICATE => MongoErrorCode::ClientAuthenticate, + bindings::MONGOC_ERROR_CLIENT_NO_ACCEPTABLE_PEER => MongoErrorCode::ClientNoAcceptablePeer, + bindings::MONGOC_ERROR_CLIENT_IN_EXHAUST => MongoErrorCode::ClientInExhaust, + bindings::MONGOC_ERROR_PROTOCOL_INVALID_REPLY => MongoErrorCode::ProtocolInvalidReply, + bindings::MONGOC_ERROR_PROTOCOL_BAD_WIRE_VERSION => MongoErrorCode::ProtocolBadWireVersion, + bindings::MONGOC_ERROR_CURSOR_INVALID_CURSOR => MongoErrorCode::CursorInvalidCursor, + bindings::MONGOC_ERROR_QUERY_FAILURE => MongoErrorCode::QueryFailure, + bindings::MONGOC_ERROR_BSON_INVALID => MongoErrorCode::BsonInvalid, + bindings::MONGOC_ERROR_MATCHER_INVALID => MongoErrorCode::MatcherInvalid, + bindings::MONGOC_ERROR_NAMESPACE_INVALID => MongoErrorCode::NamespaceInvalid, + bindings::MONGOC_ERROR_NAMESPACE_INVALID_FILTER_TYPE => MongoErrorCode::NamespaceInvalidFilterType, + bindings::MONGOC_ERROR_COMMAND_INVALID_ARG => MongoErrorCode::CommandInvalidArg, + bindings::MONGOC_ERROR_COLLECTION_INSERT_FAILED => MongoErrorCode::CollectionInsertFailed, + bindings::MONGOC_ERROR_COLLECTION_UPDATE_FAILED => MongoErrorCode::CollectionUpdateFailed, + bindings::MONGOC_ERROR_COLLECTION_DELETE_FAILED => MongoErrorCode::CollectionDeleteFailed, + bindings::MONGOC_ERROR_COLLECTION_DOES_NOT_EXIST => MongoErrorCode::CollectionDoesNotExist, + bindings::MONGOC_ERROR_GRIDFS_INVALID_FILENAME => MongoErrorCode::GridfsInvalidFilename, + bindings::MONGOC_ERROR_SCRAM_NOT_DONE => MongoErrorCode::ScramNotDone, + bindings::MONGOC_ERROR_SCRAM_PROTOCOL_ERROR => MongoErrorCode::ScramProtocolError, + bindings::MONGOC_ERROR_QUERY_COMMAND_NOT_FOUND => MongoErrorCode::QueryCommandNotFound, + bindings::MONGOC_ERROR_QUERY_NOT_TAILABLE => MongoErrorCode::QueryNotTailable, + _ => MongoErrorCode::Unknown + } + } + + pub fn get_message(&self) -> Cow { + let cstr = unsafe { CStr::from_ptr(&self.inner.message as *const i8) }; + String::from_utf8_lossy(cstr.to_bytes()) + } + + pub fn mut_inner(&mut self) -> &mut bindings::bson_error_t { + &mut self.inner + } +} + +impl fmt::Debug for BsoncError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BsoncError: {}", &self.get_message()) + } +} + +impl fmt::Display for BsoncError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.get_message()) + } +} + +impl error::Error for BsoncError { + fn description(&self) -> &str { + "Error reported by the underlying Mongo C driver" + } +} + +impl From for MongoError { + fn from(error: BsoncError) -> MongoError { + MongoError::Bsonc(error) + } +} + +pub struct InvalidParamsError; + +impl fmt::Debug for InvalidParamsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "InvalidParamsError: Invalid params supplied") + } +} + +impl fmt::Display for InvalidParamsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Invalid params supplied") + } +} + +impl error::Error for InvalidParamsError { + fn description(&self) -> &str { + "Invalid params reported by the underlying Mongo C driver, no more information is available" + } +} + +impl From for MongoError { + fn from(error: InvalidParamsError) -> MongoError { + MongoError::InvalidParams(error) + } +} + +#[cfg(test)] +mod tests { + use super::{BsoncError,MongoErrorDomain,MongoErrorCode}; + + #[test] + fn test_bson_error_empty() { + let mut error = BsoncError::empty(); + assert!(error.is_empty()); + error.mut_inner().code = 1; + assert!(!error.is_empty()); + error.mut_inner().domain = 1; + error.mut_inner().code = 0; + assert!(!error.is_empty()); + } + + #[test] + fn test_bson_error_domain() { + let mut error = BsoncError::empty(); + assert_eq!(MongoErrorDomain::Blank, error.domain()); + error.mut_inner().domain = 1; + assert_eq!(MongoErrorDomain::Client, error.domain()); + } + + #[test] + fn test_bson_error_code() { + let mut error = BsoncError::empty(); + assert_eq!(MongoErrorCode::Blank, error.code()); + error.mut_inner().code = 1; + assert_eq!(MongoErrorCode::StreamInvalidType, error.code()); + } +} diff --git a/src/flags.rs b/src/flags.rs new file mode 100644 index 0000000..8af7e2b --- /dev/null +++ b/src/flags.rs @@ -0,0 +1,120 @@ +use mongo_c_driver_wrapper::bindings; + +pub struct Flags { + flags: Vec +} + +impl Flags { + pub fn new() -> Flags { + Flags { + flags: Vec::new() + } + } + + pub fn add(&mut self, flag: T) { + self.flags.push(flag); + } +} + +pub trait FlagsValue { + fn flags(&self) -> u32; +} + +/// Flags for insert operations +/// See: http://api.mongodb.org/c/current/mongoc_insert_flags_t.html +pub enum InsertFlag { + ContinueOnError, + NoValidate +} + +impl FlagsValue for Flags { + fn flags(&self) -> u32 { + if self.flags.is_empty() { + bindings::MONGOC_INSERT_NONE + } else { + self.flags.iter().fold(0, { |flags, flag| + flags | match flag { + &InsertFlag::ContinueOnError => bindings::MONGOC_INSERT_CONTINUE_ON_ERROR, + &InsertFlag::NoValidate => 1 | 31 // MONGOC_INSERT_NO_VALIDATE defined in macro + } + }) + } + } +} + +/// Flags for query operations +/// See: http://api.mongodb.org/c/current/mongoc_query_flags_t.html +pub enum QueryFlag { + TailableCursor, + SlaveOk, + OplogReplay, + NoCursorTimeout, + AwaitData, + Exhaust, + Partial +} + +impl FlagsValue for Flags { + fn flags(&self) -> u32 { + if self.flags.is_empty() { + bindings::MONGOC_QUERY_NONE + } else { + self.flags.iter().fold(0, { |flags, flag| + flags | match flag { + &QueryFlag::TailableCursor => bindings::MONGOC_QUERY_TAILABLE_CURSOR, + &QueryFlag::SlaveOk => bindings::MONGOC_QUERY_SLAVE_OK, + &QueryFlag::OplogReplay => bindings::MONGOC_QUERY_OPLOG_REPLAY, + &QueryFlag::NoCursorTimeout => bindings::MONGOC_QUERY_NO_CURSOR_TIMEOUT, + &QueryFlag::AwaitData => bindings::MONGOC_QUERY_AWAIT_DATA, + &QueryFlag::Exhaust => bindings::MONGOC_QUERY_EXHAUST, + &QueryFlag::Partial => bindings::MONGOC_QUERY_PARTIAL + } + }) + } + } +} + +/// Flags for deletion operations +/// See: http://api.mongodb.org/c/1.1.8/mongoc_remove_flags_t.html +pub enum RemoveFlag { + SingleRemove +} + +impl FlagsValue for Flags { + fn flags(&self) -> u32 { + if self.flags.is_empty() { + bindings::MONGOC_REMOVE_NONE + } else { + bindings::MONGOC_REMOVE_SINGLE_REMOVE + } + } +} + +#[cfg(test)] +mod tests { + use super::FlagsValue; + + #[test] + pub fn test_insert_flags() { + let mut flags = super::Flags::new(); + assert_eq!(0, flags.flags()); + + flags.add(super::InsertFlag::ContinueOnError); + assert_eq!(1, flags.flags()); + + flags.add(super::InsertFlag::NoValidate); + assert_eq!(31, flags.flags()); + } + + #[test] + pub fn test_query_flags() { + let mut flags = super::Flags::new(); + assert_eq!(0, flags.flags()); + + flags.add(super::QueryFlag::TailableCursor); + assert_eq!(2, flags.flags()); + + flags.add(super::QueryFlag::Partial); + assert_eq!(130, flags.flags()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e27fc28 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,58 @@ +#![feature(scoped)] + +extern crate libc; +extern crate mongo_c_driver_wrapper; +extern crate bson; + +use std::result; + +use mongo_c_driver_wrapper::bindings; + +pub mod bsonc; +pub mod client; +pub mod collection; +pub mod cursor; +pub mod error; +pub mod flags; +pub mod read_prefs; +pub mod uri; +pub mod write_concern; + +pub use error::{MongoError,BsoncError,InvalidParamsError}; + +pub type Result = result::Result; + +static mut INITIALIZED: bool = false; + +/// Init mongo driver, needs to be called once before doing +/// anything else. +pub fn init() { + unsafe { + bindings::mongoc_init(); + INITIALIZED = true; + } +} + +/// Clean up mongo driver's resources +pub fn cleanup() { + unsafe { + bindings::mongoc_cleanup(); + INITIALIZED = false; + } +} + +pub fn is_initialized() -> bool { + unsafe { INITIALIZED } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_init_and_cleanup() { + super::init(); + assert!(super::is_initialized()); + + super::cleanup(); + assert!(!super::is_initialized()); + } +} diff --git a/src/read_prefs.rs b/src/read_prefs.rs new file mode 100644 index 0000000..c6e09af --- /dev/null +++ b/src/read_prefs.rs @@ -0,0 +1,59 @@ +use mongo_c_driver_wrapper::bindings; + +pub enum ReadMode { + Primary, + Secondary, + PrimaryPreferred, + SecondaryPreferred, + Nearest +} + +fn read_mode_value(read_mode: &ReadMode) -> bindings::mongoc_read_mode_t { + match read_mode { + &ReadMode::Primary => bindings::MONGOC_READ_PRIMARY, + &ReadMode::Secondary => bindings::MONGOC_READ_SECONDARY, + &ReadMode::PrimaryPreferred => bindings::MONGOC_READ_PRIMARY_PREFERRED, + &ReadMode::SecondaryPreferred => bindings::MONGOC_READ_SECONDARY_PREFERRED, + &ReadMode::Nearest => bindings::MONGOC_READ_NEAREST + } +} + +pub struct ReadPrefs { + inner: *mut bindings::mongoc_read_prefs_t +} + +impl ReadPrefs { + pub fn new(read_mode: &ReadMode) -> ReadPrefs { + let read_mode_value = read_mode_value(read_mode); + let inner = unsafe { bindings::mongoc_read_prefs_new(read_mode_value) }; + assert!(!inner.is_null()); + ReadPrefs { inner: inner } + } + + pub fn default() -> ReadPrefs{ + ReadPrefs::new(&ReadMode::Primary) + } + + pub fn inner(&self) -> *const bindings::mongoc_read_prefs_t { + assert!(!self.inner.is_null()); + self.inner + } +} + +impl Drop for ReadPrefs { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_read_prefs_destroy(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_read_prefs() { + let read_prefs = super::ReadPrefs::default(); + assert!(!read_prefs.inner().is_null()); + } +} diff --git a/src/uri.rs b/src/uri.rs new file mode 100644 index 0000000..2b07c48 --- /dev/null +++ b/src/uri.rs @@ -0,0 +1,69 @@ +use std::borrow::Cow; +use std::ffi::{CStr,CString}; +use std::fmt; + +use mongo_c_driver_wrapper::bindings; + +/// Abstraction on top of MongoDB connection URI format. +/// See: http://api.mongodb.org/c/current/mongoc_uri_t.html + +pub struct Uri { + inner: *mut bindings::mongoc_uri_t +} + +impl Uri { + pub fn new>>(uri_string: T) -> Uri { + let uri_cstring = CString::new(uri_string).unwrap(); + let uri = unsafe { bindings::mongoc_uri_new(uri_cstring.as_ptr()) }; + Uri { + inner: uri + } + } + + pub unsafe fn inner(&self) -> *const bindings::mongoc_uri_t { + assert!(!self.inner.is_null()); + self.inner + } + + pub fn as_str<'a>(&'a self) -> Cow<'a, str> { + assert!(!self.inner.is_null()); + unsafe { + let cstr = CStr::from_ptr( + bindings::mongoc_uri_get_string(self.inner) + ); + String::from_utf8_lossy(cstr.to_bytes()) + } + } + + // TODO add various methods that are available on uri +} + +impl fmt::Debug for Uri { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl Clone for Uri { + fn clone(&self) -> Uri { + Uri::new(self.as_str().into_owned()) + } +} + +impl Drop for Uri { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_uri_destroy(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_new_uri() { + let uri = super::Uri::new("mongodb://localhost:27017/"); + assert_eq!("mongodb://localhost:27017/", uri.as_str()); + } +} diff --git a/src/write_concern.rs b/src/write_concern.rs new file mode 100644 index 0000000..271e6d4 --- /dev/null +++ b/src/write_concern.rs @@ -0,0 +1,36 @@ +use mongo_c_driver_wrapper::bindings; + +pub struct WriteConcern { + inner: *mut bindings::mongoc_write_concern_t +} + +impl WriteConcern { + pub fn new() -> WriteConcern { + let inner = unsafe { bindings::mongoc_write_concern_new() }; + assert!(!inner.is_null()); + WriteConcern { inner: inner } + } + + pub fn inner(&self) -> *const bindings::mongoc_write_concern_t { + assert!(!self.inner.is_null()); + self.inner + } +} + +impl Drop for WriteConcern { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_write_concern_destroy(self.inner); + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_write_concern() { + let write_concern = super::WriteConcern::new(); + assert!(!write_concern.inner().is_null()); + } +}