use std::{
borrow::Cow,
convert::{TryFrom, TryInto},
};
use crate::{
error::{Error, Result},
raw::CStr,
Bson,
DateTime,
JavaScriptCodeWithScope,
RawBson,
RawJavaScriptCodeWithScope,
Timestamp,
Utf8Lossy,
};
use super::{
i32_from_slice,
iter::Iter,
try_to_str,
Error as RawError,
RawArray,
RawBinaryRef,
RawBsonRef,
RawDocumentBuf,
RawIter,
RawRegexRef,
Result as RawResult,
MIN_BSON_DOCUMENT_SIZE,
};
use crate::{oid::ObjectId, spec::ElementType, Document};
#[derive(PartialEq)]
#[repr(transparent)]
pub struct RawDocument {
data: [u8],
}
impl RawDocument {
pub fn from_bytes<D: AsRef<[u8]> + ?Sized>(data: &D) -> RawResult<&RawDocument> {
let data = data.as_ref();
if data.len() < 5 {
return Err(Error::malformed_bytes("document too short"));
}
let length = i32_from_slice(data)?;
if data.len() as i32 != length {
return Err(Error::malformed_bytes("document length incorrect"));
}
if data[data.len() - 1] != 0 {
return Err(Error::malformed_bytes("document not null-terminated"));
}
Ok(RawDocument::new_unchecked(data))
}
pub(crate) fn new_unchecked<D: AsRef<[u8]> + ?Sized>(data: &D) -> &RawDocument {
unsafe { &*(data.as_ref() as *const [u8] as *const RawDocument) }
}
pub fn get(&self, key: impl AsRef<str>) -> RawResult<Option<RawBsonRef<'_>>> {
for elem in RawIter::new(self) {
let elem = elem?;
if key.as_ref() == elem.key().as_str() {
return Ok(Some(elem.try_into()?));
}
}
Ok(None)
}
pub fn iter(&self) -> Iter<'_> {
Iter::new(self)
}
pub fn iter_elements(&self) -> RawIter<'_> {
RawIter::new(self)
}
fn get_with<'a, T>(
&'a self,
key: impl AsRef<str>,
expected_type: ElementType,
f: impl FnOnce(RawBsonRef<'a>) -> Option<T>,
) -> Result<T> {
let key = key.as_ref();
let bson = self
.get(key)
.map_err(|e| Error::value_access_invalid_bson(format!("{:?}", e)))?
.ok_or_else(Error::value_access_not_present)
.map_err(|e| e.with_key(key))?;
match f(bson) {
Some(t) => Ok(t),
None => Err(
Error::value_access_unexpected_type(bson.element_type(), expected_type)
.with_key(key),
),
}
}
pub fn get_f64(&self, key: impl AsRef<str>) -> Result<f64> {
self.get_with(key, ElementType::Double, RawBsonRef::as_f64)
}
pub fn get_str(&self, key: impl AsRef<str>) -> Result<&'_ str> {
self.get_with(key, ElementType::String, RawBsonRef::as_str)
}
pub fn get_document(&self, key: impl AsRef<str>) -> Result<&'_ RawDocument> {
self.get_with(key, ElementType::EmbeddedDocument, RawBsonRef::as_document)
}
pub fn get_array(&self, key: impl AsRef<str>) -> Result<&'_ RawArray> {
self.get_with(key, ElementType::Array, RawBsonRef::as_array)
}
pub fn get_binary(&self, key: impl AsRef<str>) -> Result<RawBinaryRef<'_>> {
self.get_with(key, ElementType::Binary, RawBsonRef::as_binary)
}
pub fn get_object_id(&self, key: impl AsRef<str>) -> Result<ObjectId> {
self.get_with(key, ElementType::ObjectId, RawBsonRef::as_object_id)
}
pub fn get_bool(&self, key: impl AsRef<str>) -> Result<bool> {
self.get_with(key, ElementType::Boolean, RawBsonRef::as_bool)
}
pub fn get_datetime(&self, key: impl AsRef<str>) -> Result<DateTime> {
self.get_with(key, ElementType::DateTime, RawBsonRef::as_datetime)
}
pub fn get_regex(&self, key: impl AsRef<str>) -> Result<RawRegexRef<'_>> {
self.get_with(key, ElementType::RegularExpression, RawBsonRef::as_regex)
}
pub fn get_timestamp(&self, key: impl AsRef<str>) -> Result<Timestamp> {
self.get_with(key, ElementType::Timestamp, RawBsonRef::as_timestamp)
}
pub fn get_i32(&self, key: impl AsRef<str>) -> Result<i32> {
self.get_with(key, ElementType::Int32, RawBsonRef::as_i32)
}
pub fn get_i64(&self, key: impl AsRef<str>) -> Result<i64> {
self.get_with(key, ElementType::Int64, RawBsonRef::as_i64)
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn is_empty(&self) -> bool {
self.as_bytes().len() == MIN_BSON_DOCUMENT_SIZE as usize
}
pub(crate) fn cstring_bytes_at(&self, start_at: usize) -> RawResult<&[u8]> {
let buf = &self.as_bytes()[start_at..];
let mut splits = buf.splitn(2, |x| *x == 0);
let value = splits
.next()
.ok_or_else(|| RawError::malformed_bytes("no value"))?;
if splits.next().is_some() {
Ok(value)
} else {
Err(RawError::malformed_bytes("expected null terminator"))
}
}
pub(crate) fn read_cstring_at(&self, start_at: usize) -> RawResult<&CStr> {
let bytes = self.cstring_bytes_at(start_at)?;
let s = try_to_str(bytes)?;
s.try_into()
}
}
#[cfg(feature = "serde")]
impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a RawDocument {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use super::serde::OwnedOrBorrowedRawDocument;
match OwnedOrBorrowedRawDocument::deserialize(deserializer)? {
OwnedOrBorrowedRawDocument::Borrowed(b) => Ok(b),
OwnedOrBorrowedRawDocument::Owned(d) => Err(serde::de::Error::custom(format!(
"expected borrowed raw document, instead got owned {:?}",
d
))),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for &RawDocument {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
struct KvpSerializer<'a>(&'a RawDocument);
impl serde::Serialize for KvpSerializer<'_> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap as _;
if serializer.is_human_readable() {
let mut map = serializer.serialize_map(None)?;
for kvp in self.0 {
let (k, v) = kvp.map_err(serde::ser::Error::custom)?;
map.serialize_entry(k.as_str(), &v)?;
}
map.end()
} else {
serializer.serialize_bytes(self.0.as_bytes())
}
}
}
serializer.serialize_newtype_struct(super::RAW_DOCUMENT_NEWTYPE, &KvpSerializer(self))
}
}
impl std::fmt::Debug for RawDocument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RawDocument")
.field("data", &hex::encode(&self.data))
.finish()
}
}
impl AsRef<RawDocument> for RawDocument {
fn as_ref(&self) -> &RawDocument {
self
}
}
impl ToOwned for RawDocument {
type Owned = RawDocumentBuf;
fn to_owned(&self) -> Self::Owned {
RawDocumentBuf::from_bytes(self.data.to_owned()).unwrap()
}
}
impl<'a> From<&'a RawDocument> for Cow<'a, RawDocument> {
fn from(rdr: &'a RawDocument) -> Self {
Cow::Borrowed(rdr)
}
}
impl TryFrom<&RawDocument> for Document {
type Error = RawError;
fn try_from(rawdoc: &RawDocument) -> RawResult<Document> {
rawdoc
.into_iter()
.map(|res| res.and_then(|(k, v)| Ok((k.as_str().to_owned(), v.try_into()?))))
.collect()
}
}
impl TryFrom<&RawDocument> for Utf8Lossy<Document> {
type Error = RawError;
fn try_from(rawdoc: &RawDocument) -> RawResult<Utf8Lossy<Document>> {
let mut out = Document::new();
for elem in rawdoc.iter_elements() {
let elem = elem?;
let value = deep_utf8_lossy(elem.value_utf8_lossy()?)?;
out.insert(elem.key().as_str(), value);
}
Ok(Utf8Lossy(out))
}
}
fn deep_utf8_lossy(src: RawBson) -> RawResult<Bson> {
match src {
RawBson::Array(arr) => {
let mut tmp = vec![];
for elem in arr.iter_elements() {
tmp.push(deep_utf8_lossy(elem?.value_utf8_lossy()?)?);
}
Ok(Bson::Array(tmp))
}
RawBson::Document(doc) => {
let mut tmp = doc! {};
for elem in doc.iter_elements() {
let elem = elem?;
tmp.insert(
elem.key().as_str(),
deep_utf8_lossy(elem.value_utf8_lossy()?)?,
);
}
Ok(Bson::Document(tmp))
}
RawBson::JavaScriptCodeWithScope(RawJavaScriptCodeWithScope { code, scope }) => {
let mut tmp = doc! {};
for elem in scope.iter_elements() {
let elem = elem?;
tmp.insert(
elem.key().as_str(),
deep_utf8_lossy(elem.value_utf8_lossy()?)?,
);
}
Ok(Bson::JavaScriptCodeWithScope(JavaScriptCodeWithScope {
code,
scope: tmp,
}))
}
v => v.try_into(),
}
}
impl TryFrom<RawDocumentBuf> for Document {
type Error = crate::error::Error;
fn try_from(raw: RawDocumentBuf) -> Result<Document> {
Document::try_from(raw.as_ref())
}
}
impl TryFrom<RawDocumentBuf> for Utf8Lossy<Document> {
type Error = crate::error::Error;
fn try_from(raw: RawDocumentBuf) -> Result<Utf8Lossy<Document>> {
Utf8Lossy::<Document>::try_from(raw.as_ref())
}
}
impl TryFrom<&RawDocumentBuf> for Document {
type Error = crate::error::Error;
fn try_from(raw: &RawDocumentBuf) -> Result<Document> {
Document::try_from(raw.as_ref())
}
}
impl TryFrom<&RawDocumentBuf> for Utf8Lossy<Document> {
type Error = crate::error::Error;
fn try_from(raw: &RawDocumentBuf) -> Result<Utf8Lossy<Document>> {
Utf8Lossy::<Document>::try_from(raw.as_ref())
}
}
impl<'a> IntoIterator for &'a RawDocument {
type IntoIter = Iter<'a>;
type Item = RawResult<(&'a CStr, RawBsonRef<'a>)>;
fn into_iter(self) -> Iter<'a> {
self.iter()
}
}