pbbson 0.1.3

Utilities for pbjson to BSON conversion
use bson::document::{Iter, IterMut, Keys, ValueAccessError, ValueAccessResult, Values};
use bson::{Bson, DateTime, Decimal128, Document, Timestamp};
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::str::FromStr;

#[derive(Clone, Debug, Default, Deserialize)]
pub struct Model(Document);

impl Model {
    pub fn clear(&mut self) {
        self.0.clear()
    }

    pub fn contains_field(&self, field: impl AsRef<str>) -> bool {
        self.0.contains_key(field.as_ref())
    }

    pub fn get(&self, field: impl AsRef<str>) -> Option<&Bson> {
        self.0.get(field)
    }

    pub fn get_mut(&mut self, field: impl AsRef<str>) -> Option<&mut Bson> {
        self.0.get_mut(field.as_ref())
    }

    pub fn get_bool(&self, field: impl AsRef<str>) -> ValueAccessResult<bool> {
        self.0.get_bool(field)
    }

    pub fn get_datetime(&self, field: impl AsRef<str>) -> ValueAccessResult<&DateTime> {
        self.0.get_datetime(field)
    }

    pub fn get_decimal128(&self, field: impl AsRef<str>) -> ValueAccessResult<&Decimal128> {
        self.0.get_decimal128(field)
    }

    pub fn get_f64(&self, field: impl AsRef<str>) -> ValueAccessResult<f64> {
        self.0.get_f64(field)
    }

    pub fn get_i32(&self, field: impl AsRef<str>) -> ValueAccessResult<i32> {
        self.0.get_i32(field)
    }

    pub fn get_i64(&self, field: impl AsRef<str>) -> ValueAccessResult<i64> {
        self.0.get_i64(field)
    }

    pub fn get_str(&self, field: impl AsRef<str>) -> ValueAccessResult<&str> {
        self.0.get_str(field)
    }

    pub fn get_timestamp(&self, field: impl AsRef<str>) -> ValueAccessResult<Timestamp> {
        self.0.get_timestamp(field)
    }

    pub fn id(&self) -> ValueAccessResult<String> {
        match self.0.get("id") {
            Some(Bson::ObjectId(object_id)) => try_from_object_id(object_id),
            Some(Bson::String(str)) => Ok(str.clone()),
            _ => Err(ValueAccessError::NotPresent),
        }
    }

    pub fn insert<KT: Into<String>, BT: Into<Bson>>(&mut self, field: KT, value: BT) -> Option<Bson> {
        self.0.insert(field, value)
    }

    pub fn is_null(&self, field: impl AsRef<str>) -> bool {
        self.0.is_null(field)
    }

    pub fn iter(&self) -> Iter {
        self.0.iter()
    }

    pub fn iter_mut(&mut self) -> IterMut {
        self.0.iter_mut()
    }

    pub fn keys(&self) -> Keys {
        self.0.keys()
    }

    pub fn remove(&mut self, field: impl AsRef<str>) -> Option<Bson> {
        self.0.remove(field.as_ref())
    }

    pub fn set_datetime(&mut self, field: &str, value: DateTime) {
        self.0.insert(field, Some(value));
    }

    pub fn try_from<T: prost::Message + Serialize>(other: &T) -> ValueAccessResult<Self> {
        let buf = serde_json::to_vec(&other).map_err(|_e| ValueAccessError::UnexpectedType)?;
        let mut model: Model = serde_json::from_slice(&buf).map_err(|_e| ValueAccessError::UnexpectedType)?;
        for key in model.0.clone().keys() {
            if key == "id" {
                let value = model.0.get_str(key).unwrap();
                if value.is_empty() {
                    model.0.remove(key);
                } else {
                    let value = try_to_object_id(value)?;
                    model.0.insert(key, value);
                }
            }
            if key.ends_with("At") {
                let value = model.0.get_str(key).unwrap();
                let value = bson::DateTime::parse_rfc3339_str(value).unwrap();
                model.0.insert(key, value);
            }
        }
        Ok(model)
    }

    pub fn try_into<T: prost::Message + Clone + Default + for<'a> Deserialize<'a>>(self) -> ValueAccessResult<T> {
        let mut this = self.0.clone();

        // Perform some translations
        let this_ = this.clone();
        let keys = this_.keys();
        for key in keys {
            let value = this.get(key);
            if let Some(bson_value) = value {
                let new_value = to_pbjson(bson_value.clone())?;
                this.insert(key, new_value);
            }
        }

        let buf = serde_json::to_vec(&this).map_err(|_e| ValueAccessError::UnexpectedType)?;
        let msg: T = serde_json::from_slice::<T>(&buf)
            .map_err(|_e| ValueAccessError::UnexpectedType)?
            .clone();
        let mut message = T::default();
        message.clone_from(&msg);
        Ok(message)
    }

    pub fn values(&self) -> Values {
        self.0.values()
    }
}

pub fn to_pbjson(value: Bson) -> ValueAccessResult<Bson> {
    match value {
        Bson::ObjectId(object_id) => Ok(Bson::String(try_from_object_id(&object_id)?)),
        Bson::DateTime(date_time) => Ok(Bson::String(
            date_time
                .try_to_rfc3339_string()
                .map_err(|_e| ValueAccessError::UnexpectedType)?
                .to_string(),
        )),
        Bson::Array(array) => {
            let mut new_array = vec![];
            for value in array {
                new_array.push(to_pbjson(value.clone())?);
            }
            Ok(Bson::Array(new_array))
        }
        _ => Ok(value),
    }
}

impl From<Document> for Model {
    fn from(other: Document) -> Self {
        Model(other)
    }
}

fn try_from_object_id(id: &bson::oid::ObjectId) -> ValueAccessResult<String> {
    let id = xid::Id::from_bytes(&id.bytes()).map_err(|_e| ValueAccessError::UnexpectedType)?;
    Ok(id.to_string())
}

fn try_to_object_id(id: &str) -> ValueAccessResult<bson::oid::ObjectId> {
    const WHAT: &str = "IDs";
    require_xid_base32hex(WHAT, id)?;
    let as_xid = match xid::Id::from_str(id) {
        Ok(xid) => xid,
        Err(_e) => {
            // let msg = format!("{WHAT} must be 12-byte XID's in 20-byte base32hex encoded form ({e:?})");
            return Err(ValueAccessError::UnexpectedType);
        }
    };
    Ok(bson::oid::ObjectId::from_bytes(*as_xid.as_bytes()))
}

fn require_xid_base32hex(_what: &str, id: &str) -> ValueAccessResult<()> {
    if id.len() != 20 {
        // return Err(Status::invalid_argument(format!(
        //     "{what} must be 12-byte XID's in 20-byte base32hex encoded form"
        // )));
        return Err(ValueAccessError::UnexpectedType);
    }
    Ok(())
}

impl Borrow<Document> for Model {
    fn borrow(&self) -> &Document {
        self.0.borrow()
    }
}

impl From<Model> for Document {
    fn from(other: Model) -> Self {
        other.0
    }
}

#[cfg(test)]
mod tests {
    use super::Model;
    use bson::Document;

    #[test]
    fn can_access_id_when_none() {
        let model = Model::default();
        assert!(model.id().is_err());
    }

    #[test]
    fn can_deser() {
        const JSON: &str = r#"{"id":"d05vu4r71n0pfhlalahg","firstName":"Joe","age":23}"#;
        let model: Model = serde_json::from_str(JSON).unwrap();
        assert_eq!(model.id().unwrap(), "d05vu4r71n0pfhlalahg");
        assert_eq!(model.get_str("firstName").unwrap(), "Joe");
        assert_eq!(model.get_i32("age").unwrap(), 23);
    }

    #[test]
    fn can_convert_into_document() {
        let _doc: Document = Model::default().into();
    }
}