use std::borrow::Borrow;
use serde_json::Value;
use bson::{ Bson, Document, ValueAccessError };
use serde::Serialize;
use crate::error::{ Error, ErrorKind, Result };
pub trait JsonExt: Sized {
fn try_into_bson(self) -> Result<Bson>;
}
pub trait BsonExt: Sized {
fn try_into_doc(self) -> Result<Document>;
fn try_as_bool(&self) -> Option<bool>;
}
impl JsonExt for Value {
fn try_into_bson(self) -> Result<Bson> {
match self {
Value::Number(n) => if n.is_i64() || n.is_f64() {
bson::to_bson(&n).map_err(Into::into)
} else {
Err(Error::new(
ErrorKind::BsonNumberRepr,
format!("Value `{}` can't be represented in BSON", n)
))
},
Value::Array(values) => values
.into_iter()
.map(JsonExt::try_into_bson)
.collect::<Result<Vec<_>>>()
.map(Bson::from),
Value::Object(values) => values
.into_iter()
.map(|(k, v)| v.try_into_bson().map(|v| (k, v)))
.collect::<Result<Document>>()
.map(Bson::from_extended_document),
value => Ok(value.into()),
}
}
}
impl BsonExt for Bson {
#[allow(clippy::float_cmp)]
fn try_as_bool(&self) -> Option<bool> {
match *self {
Bson::Boolean(b) => Some(b),
Bson::I32(0) | Bson::I64(0) => Some(false),
Bson::I32(1) | Bson::I64(1) => Some(true),
Bson::FloatingPoint(x) if x == 0.0 => Some(false),
Bson::FloatingPoint(x) if x == 1.0 => Some(true),
_ => None,
}
}
fn try_into_doc(self) -> Result<Document> {
match self {
Bson::Document(doc) => Ok(doc),
value => Err(Error::with_cause(
format!("expected Document, got {:?}", value.element_type()),
ValueAccessError::UnexpectedType,
))
}
}
}
pub fn serialize_document<T: Serialize>(value: &T) -> Result<Document> {
serde_json::to_value(value)
.map_err(From::from)
.and_then(JsonExt::try_into_bson)
.and_then(BsonExt::try_into_doc)
}
pub fn serialize_documents<T, I>(values: I) -> Result<Vec<Document>>
where T: Serialize,
I: IntoIterator,
I::Item: Borrow<T>,
{
values
.into_iter()
.map(|val| serialize_document(val.borrow()))
.collect()
}
#[cfg(test)]
mod tests {
use std::{ u64, i64, i128 };
use crate::error::Result;
use crate::prelude::*;
use super::*;
#[test]
fn json_ext_try_into_bson() -> Result<()> {
use std::iter::once;
use std::collections::HashMap;
use super::JsonExt;
use crate::prelude::*;
let oid = ObjectId::new()?;
let coll: Vec<HashMap<_, _>> = vec![
once(("key", oid.clone())).collect()
];
let good = serde_json::to_value(&coll)?;
let bad = serde_json::to_value(&u64::MAX)?;
assert_eq!(good.try_into_bson()?,
bson!([
{ "key": oid }
]));
assert!(bad.try_into_bson().is_err());
Ok(())
}
#[test]
fn bson_ext_try_into_doc() -> Result<()> {
let doc = bson!({ "foo": "bar", "qux": 3.14 });
let other = bson!([{ "key": "value" }, false, null]);
assert_eq!(doc.try_into_doc()?,
doc!{ "foo": "bar", "qux": 3.14 });
assert!(other.try_into_doc().is_err());
Ok(())
}
#[test]
fn bson_ext_try_as_bool() {
assert_eq!(Bson::Boolean(true).try_as_bool(), Some(true));
assert_eq!(Bson::Boolean(false).try_as_bool(), Some(false));
assert_eq!(Bson::I32(1).try_as_bool(), Some(true));
assert_eq!(Bson::I64(0).try_as_bool(), Some(false));
assert_eq!(Bson::FloatingPoint(1.0).try_as_bool(), Some(true));
assert_eq!(Bson::FloatingPoint(0.0).try_as_bool(), Some(false));
assert_eq!(Bson::I32(-1).try_as_bool(), None);
assert_eq!(Bson::FloatingPoint(0.999).try_as_bool(), None);
assert_eq!(Bson::Null.try_as_bool(), None);
assert_eq!(Bson::String("hello world".into()).try_as_bool(), None);
}
#[test]
fn serialize_one_document() -> Result<()> {
#[derive(Serialize)]
struct Number { value: u64 };
#[derive(Serialize)]
struct BigNumber { value: i128 };
let good = Number { value: i64::MAX as u64 };
let bad_64 = Number { value: i64::MAX as u64 + 1 };
let bad_128 = BigNumber { value: 0 };
let bad_nodoc: i64 = 0;
assert_eq!(
serialize_document(&good)?,
doc!{ "value": i64::MAX }
);
assert!(serialize_document(&bad_64)
.unwrap_err()
.to_string()
.contains("can't be represented in BSON"));
assert!(serialize_document(&bad_128)
.unwrap_err()
.to_string()
.contains("i128 is not supported"));
assert!(serialize_document(&bad_nodoc)
.unwrap_err()
.to_string()
.contains("expected Document, got Integer64Bit"));
Ok(())
}
#[test]
fn serialize_many_documents() -> Result<()> {
#[derive(Serialize)]
struct Number { value: u64 };
let good = Number { value: i64::MAX as u64 };
let bad = Number { value: i64::MAX as u64 + 1 };
assert_eq!(serialize_documents::<Number, _>(vec![&good, &good])?,
vec![
doc!{ "value": i64::MAX },
doc!{ "value": i64::MAX },
]);
assert!(serialize_documents::<Number, _>(vec![&good, &bad])
.unwrap_err()
.to_string()
.contains("can't be represented in BSON"));
Ok(())
}
}