Tryout of libbson wrapper
parent
8ebb87792a
commit
7ee0a957f7
@ -0,0 +1,417 @@
|
||||
use std::fmt;
|
||||
use std::ffi::{CStr,CString};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
|
||||
use libc::types::common::c95::c_void;
|
||||
|
||||
use mongo_c_driver_wrapper::bindings;
|
||||
|
||||
#[derive(Debug,PartialEq)]
|
||||
pub enum Value {
|
||||
Document(Document),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
ObjectId(ObjectId),
|
||||
String(String)
|
||||
}
|
||||
|
||||
#[derive(Debug,PartialEq)]
|
||||
pub enum FieldAccessError {
|
||||
NotPresent,
|
||||
UnexpectedType
|
||||
}
|
||||
|
||||
pub type FieldAccessResult<T> = Result<T, FieldAccessError>;
|
||||
|
||||
pub struct ObjectId {
|
||||
oid: bindings::bson_oid_t
|
||||
}
|
||||
|
||||
impl ObjectId {
|
||||
pub fn new() -> ObjectId {
|
||||
let mut oid: bindings::bson_oid_t = unsafe { mem::uninitialized() };
|
||||
unsafe { bindings::bson_oid_init(&mut oid, ptr::null_mut()); }
|
||||
ObjectId { oid: oid }
|
||||
}
|
||||
|
||||
fn from_oid(oid: bindings::bson_oid_t) -> ObjectId {
|
||||
ObjectId { oid: oid }
|
||||
}
|
||||
|
||||
pub fn from_str(id: &str) -> ObjectId {
|
||||
let ptr = CString::new(id).unwrap().as_ptr();
|
||||
let mut oid: bindings::bson_oid_t = unsafe { mem::uninitialized() };
|
||||
unsafe { bindings::bson_oid_init_from_string(&mut oid, ptr); }
|
||||
ObjectId { oid: oid }
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let buffer: *mut i8 = unsafe { mem::uninitialized() };
|
||||
unsafe {
|
||||
bindings::bson_oid_to_string(&self.oid, buffer);
|
||||
let cstr = CStr::from_ptr(buffer);
|
||||
str::from_utf8_unchecked(cstr.to_bytes()).to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ObjectId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "ObjectId: {}", self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ObjectId {
|
||||
fn eq(&self, other: &ObjectId) -> bool {
|
||||
unsafe {
|
||||
bindings::bson_oid_equal(
|
||||
&self.oid,
|
||||
&other.oid
|
||||
) == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Document {
|
||||
inner: *mut bindings::bson_t
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn new() -> Document {
|
||||
let inner = unsafe { bindings::bson_new() };
|
||||
Document { inner: inner }
|
||||
}
|
||||
|
||||
fn from_ptr(inner: *const bindings::bson_t) -> Document {
|
||||
assert!(!inner.is_null());
|
||||
Document { inner: inner as *mut bindings::bson_t }
|
||||
}
|
||||
|
||||
fn from_data(data: *const u8, len: u32) -> Document {
|
||||
let inner = unsafe { bindings::bson_new_from_data(data, len as bindings::size_t) };
|
||||
Document { inner: inner as *mut bindings::bson_t }
|
||||
}
|
||||
|
||||
pub fn insert<T: Into<Value>>(&mut self, key: &str, value: T) {
|
||||
assert!(!self.inner.is_null());
|
||||
|
||||
let key_ptr = CString::new(key).unwrap().as_ptr();
|
||||
|
||||
let success = match value.into() {
|
||||
Value::Document(v) => unsafe { bindings::bson_append_document(self.inner, key_ptr, -1, v.inner) },
|
||||
Value::I32(v) => unsafe { bindings::bson_append_int32(self.inner, key_ptr, -1, v) },
|
||||
Value::I64(v) => unsafe { bindings::bson_append_int64(self.inner, key_ptr, -1, v) },
|
||||
Value::ObjectId(v) => unsafe { bindings::bson_append_oid(self.inner, key_ptr, -1, &v.oid) },
|
||||
Value::String(v) => unsafe {
|
||||
let cstring = CString::new(v).unwrap();
|
||||
let ptr = cstring.as_ptr();
|
||||
bindings::bson_append_utf8(self.inner, key_ptr, -1, ptr, -1)
|
||||
}
|
||||
};
|
||||
|
||||
// If this not true the bson should be discarded. We'll have
|
||||
// to see if this can happen when we enforce the types and
|
||||
// mutability properly.
|
||||
assert!(success == 1);
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<Value> {
|
||||
assert!(!self.inner.is_null());
|
||||
|
||||
// We need to create an iterator to be able to find the field we want
|
||||
// See: http://api.mongodb.org/libbson/current/parsing.html
|
||||
|
||||
let wanted_key_cstr = CString::new(key).unwrap();
|
||||
|
||||
unsafe {
|
||||
// Create and initialize the iterator
|
||||
let mut iter: bindings::bson_iter_t = mem::uninitialized();
|
||||
assert!(bindings::bson_iter_init(&mut iter, self.inner) == 1);
|
||||
|
||||
// Set the iterator to the position of the field we're looking for
|
||||
// or return None.
|
||||
if bindings::bson_iter_find(&mut iter, wanted_key_cstr.as_ptr()) != 1 {
|
||||
return None
|
||||
}
|
||||
|
||||
let bson_type = bindings::bson_iter_type(&iter);
|
||||
match bson_type {
|
||||
bindings::BSON_TYPE_DOCUMENT => {
|
||||
let mut len: u32 = mem::uninitialized();
|
||||
let mut buffer: *const u8 = mem::uninitialized();
|
||||
bindings::bson_iter_document(&iter, &mut len, &mut buffer);
|
||||
return Some(Value::Document(Document::from_data(buffer, len)))
|
||||
},
|
||||
bindings::BSON_TYPE_INT32 => {
|
||||
return Some(Value::I32(bindings::bson_iter_int32(&iter)))
|
||||
},
|
||||
bindings::BSON_TYPE_INT64 => {
|
||||
return Some(Value::I64(bindings::bson_iter_int64(&iter)))
|
||||
},
|
||||
bindings::BSON_TYPE_OID => {
|
||||
let oid = *bindings::bson_iter_oid(&iter);
|
||||
return Some(Value::ObjectId(ObjectId::from_oid(oid)))
|
||||
},
|
||||
bindings::BSON_TYPE_UTF8 => {
|
||||
let ptr = bindings::bson_iter_utf8(&iter, ptr::null_mut());
|
||||
let cstr = CStr::from_ptr(ptr);
|
||||
let string = str::from_utf8_unchecked(cstr.to_bytes()).to_string();
|
||||
return Some(Value::String(string))
|
||||
},
|
||||
_ => panic!("Type not supported yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_document(&self, key: &str) -> FieldAccessResult<Document> {
|
||||
match self.get(key) {
|
||||
Some(Value::Document(v)) => Ok(v),
|
||||
Some(_) => Err(FieldAccessError::UnexpectedType),
|
||||
None => Err(FieldAccessError::NotPresent)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i32(&self, key: &str) -> FieldAccessResult<i32> {
|
||||
match self.get(key) {
|
||||
Some(Value::I32(v)) => Ok(v),
|
||||
Some(_) => Err(FieldAccessError::UnexpectedType),
|
||||
None => Err(FieldAccessError::NotPresent)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i64(&self, key: &str) -> FieldAccessResult<i64> {
|
||||
match self.get(key) {
|
||||
Some(Value::I64(v)) => Ok(v),
|
||||
Some(_) => Err(FieldAccessError::UnexpectedType),
|
||||
None => Err(FieldAccessError::NotPresent)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object_id(&self, key: &str) -> FieldAccessResult<ObjectId> {
|
||||
match self.get(key) {
|
||||
Some(Value::ObjectId(v)) => Ok(v),
|
||||
Some(_) => Err(FieldAccessError::UnexpectedType),
|
||||
None => Err(FieldAccessError::NotPresent)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self, key: &str) -> FieldAccessResult<String> {
|
||||
match self.get(key) {
|
||||
Some(Value::String(v)) => Ok(v),
|
||||
Some(_) => Err(FieldAccessError::UnexpectedType),
|
||||
None => Err(FieldAccessError::NotPresent)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> String {
|
||||
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 = unsafe { str::from_utf8_unchecked(json_cstr.to_bytes()) };
|
||||
unsafe { bindings::bson_free(json_ptr as *mut c_void); }
|
||||
out.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Document {
|
||||
fn clone(&self) -> Document {
|
||||
assert!(!self.inner.is_null());
|
||||
let copied_inner = unsafe {
|
||||
bindings::bson_copy(self.inner)
|
||||
};
|
||||
Document { inner: copied_inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Document {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Document: {}", self.to_json())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Document {
|
||||
fn drop(&mut self) {
|
||||
assert!(!self.inner.is_null());
|
||||
unsafe {
|
||||
bindings::bson_destroy(self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Document {
|
||||
fn eq(&self, other: &Document) -> bool {
|
||||
assert!(!self.inner.is_null());
|
||||
assert!(!other.inner.is_null());
|
||||
unsafe {
|
||||
bindings::bson_compare(
|
||||
self.inner,
|
||||
other.inner
|
||||
) == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Document> for Value {
|
||||
fn from(v: Document) -> Value {
|
||||
Value::Document(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Value {
|
||||
fn from(v: i32) -> Value {
|
||||
Value::I32(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
fn from(v: i64) -> Value {
|
||||
Value::I64(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ObjectId> for Value {
|
||||
fn from(v: ObjectId) -> Value {
|
||||
Value::ObjectId(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Value {
|
||||
fn from(v: &str) -> Value {
|
||||
Value::String(v.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
fn from(v: String) -> Value {
|
||||
Value::String(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Document,ObjectId,Value};
|
||||
|
||||
#[test]
|
||||
fn test_object_id() {
|
||||
let id = ObjectId::new();
|
||||
|
||||
// Check something is in the bytes array
|
||||
assert!(id.oid.bytes[0] > 0);
|
||||
assert!(id.to_string().len() > 10);
|
||||
|
||||
// Make sure the next id is not the same
|
||||
assert!(id != ObjectId::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_object_from_and_to_string() {
|
||||
let id = "55dc546717ed939a14478a51";
|
||||
let oid = ObjectId::from_str(id);
|
||||
|
||||
assert_eq!(id.to_string(), oid.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_object_id_equality() {
|
||||
let id = "55dc546717ed939a14478a51";
|
||||
|
||||
let oid1 = ObjectId::from_str(id);
|
||||
let oid2 = ObjectId::from_str(id);
|
||||
let oid3 = ObjectId::new();
|
||||
|
||||
assert!(oid1 == oid2);
|
||||
assert!(oid1 != oid3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_get() {
|
||||
// Create a document with some fields
|
||||
let mut doc = Document::new();
|
||||
doc.insert("field1", 1i32);
|
||||
doc.insert("field2", 2i32);
|
||||
doc.insert("field3", 3i32);
|
||||
doc.insert("field4", 4i32);
|
||||
doc.insert("field5", 5i32);
|
||||
|
||||
// Find one in the middle
|
||||
assert_eq!(Some(Value::I32(3i32)), doc.get("field3"));
|
||||
|
||||
// Try to find one that does not exist
|
||||
assert_eq!(None, doc.get("something"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_clone_and_equality() {
|
||||
let mut doc1 = Document::new();
|
||||
doc1.insert("some_key", 10);
|
||||
|
||||
let mut doc2 = Document::new();
|
||||
doc2.insert("some_key", 10);
|
||||
|
||||
let doc3 = doc2.clone();
|
||||
|
||||
let mut doc4 = Document::new();
|
||||
doc4.insert("some_key", 20);
|
||||
|
||||
assert!(doc1 == doc2);
|
||||
assert!(doc1 == doc3);
|
||||
assert!(doc1 != doc4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document() {
|
||||
let mut doc = Document::new();
|
||||
let mut embedded = Document::new();
|
||||
embedded.insert("an_int", 10i32);
|
||||
doc.insert("embedded", embedded.clone());
|
||||
|
||||
assert_eq!(Some(Value::Document(embedded.clone())), doc.get("embedded"));
|
||||
assert_eq!(Ok(embedded), doc.as_document("embedded"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_i32() {
|
||||
let mut doc = Document::new();
|
||||
doc.insert("i32", 10i32);
|
||||
assert_eq!(Some(Value::I32(10i32)), doc.get("i32"));
|
||||
assert_eq!(Ok(10i32), doc.as_i32("i32"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_i64() {
|
||||
let mut doc = Document::new();
|
||||
doc.insert("i64", 10i64);
|
||||
assert_eq!(Some(Value::I64(10i64)), doc.get("i64"));
|
||||
assert_eq!(Ok(10i64), doc.as_i64("i64"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_object_id() {
|
||||
let id = "55dc546717ed939a14478a51";
|
||||
|
||||
let mut doc = Document::new();
|
||||
doc.insert("_id", ObjectId::from_str(id));
|
||||
assert_eq!(Some(Value::ObjectId(ObjectId::from_str(id))), doc.get("_id"));
|
||||
assert_eq!(Ok(ObjectId::from_str(id)), doc.as_object_id("_id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_string() {
|
||||
let mut doc = Document::new();
|
||||
doc.insert("str", "this is a str: Iñtërnâtiônàlizætiøn");
|
||||
doc.insert("string", "this is a string: Iñtërnâtiônàlizætiøn".to_string());
|
||||
assert_eq!(Some(Value::String("this is a str: Iñtërnâtiônàlizætiøn".to_string())), doc.get("str"));
|
||||
assert_eq!(Some(Value::String("this is a string: Iñtërnâtiônàlizætiøn".to_string())), doc.get("string"));
|
||||
assert_eq!(Ok("this is a string: Iñtërnâtiônàlizætiøn".to_string()), doc.as_string("string"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_document_to_json() {
|
||||
let mut doc = Document::new();
|
||||
doc.insert("str", "this is a str: Iñtërnâtiônàlizætiøn");
|
||||
assert_eq!("{ \"str\" : \"this is a str: Iñtërnâtiônàlizætiøn\" }".to_string(), doc.to_json());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue