use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
util::convert_from,
FromVec, IntoVec,
};
use serde_json::{from_str, Map, Value};
use std::borrow::Cow;
use std::fmt;
#[derive(Debug, Clone)]
pub struct CosmosDbDocument(Value);
impl CosmosDbDocument {
pub fn new(value: Value) -> CosmosDbDocument {
if !value.is_object() {
panic!("expected a single object for a Cosmos DB document");
}
CosmosDbDocument(value)
}
pub fn is_null(&self) -> bool {
self.0.is_null()
}
pub fn as_object(&self) -> Option<&Map<String, Value>> {
self.0.as_object()
}
pub fn as_object_mut(&mut self) -> Option<&mut Map<String, Value>> {
self.0.as_object_mut()
}
}
impl fmt::Display for CosmosDbDocument {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> From<&'a str> for CosmosDbDocument {
fn from(json: &'a str) -> Self {
CosmosDbDocument::new(from_str(json).unwrap())
}
}
impl From<String> for CosmosDbDocument {
fn from(json: String) -> Self {
CosmosDbDocument::new(from_str(&json).unwrap())
}
}
impl From<Value> for CosmosDbDocument {
fn from(value: Value) -> Self {
CosmosDbDocument::new(value)
}
}
#[doc(hidden)]
impl IntoVec<CosmosDbDocument> for TypedData {
fn into_vec(self) -> Vec<CosmosDbDocument> {
if self.data.is_none() {
return vec![];
}
match convert_from(&self).expect("expected JSON data for Cosmos DB document") {
Value::Null => vec![],
Value::Array(arr) => arr.into_iter().map(CosmosDbDocument::new).collect(),
Value::Object(obj) => vec![CosmosDbDocument(Value::Object(obj))],
_ => panic!("expected array or object for Cosmos DB document data"),
}
}
}
#[doc(hidden)]
impl FromVec<CosmosDbDocument> for TypedData {
fn from_vec(vec: Vec<CosmosDbDocument>) -> Self {
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(|d| d.0).collect()).to_string(),
)),
}
}
}
#[doc(hidden)]
impl From<TypedData> for CosmosDbDocument {
fn from(data: TypedData) -> Self {
if data.data.is_none() {
return CosmosDbDocument(Value::Null);
}
let value: Value = convert_from(&data).expect("expected JSON data for Cosmos DB document");
match value {
Value::Null => CosmosDbDocument(Value::Null),
Value::Array(mut arr) => {
if arr.is_empty() {
CosmosDbDocument(Value::Null)
} else {
CosmosDbDocument::new(arr.swap_remove(0))
}
}
Value::Object(obj) => CosmosDbDocument(Value::Object(obj)),
_ => panic!("expected an array or object for Cosmos DB document data"),
}
}
}
impl Into<String> for CosmosDbDocument {
fn into(self) -> String {
self.0.to_string()
}
}
impl Into<Value> for CosmosDbDocument {
fn into(self) -> Value {
self.0
}
}
impl<'a> Into<Body<'a>> for CosmosDbDocument {
fn into(self) -> Body<'a> {
self.0.into()
}
}
impl<'a> Into<Body<'a>> for Vec<CosmosDbDocument> {
fn into(self) -> Body<'a> {
Body::Json(Cow::from(
Value::Array(self.into_iter().map(|d| d.0).collect()).to_string(),
))
}
}
#[doc(hidden)]
impl Into<TypedData> for CosmosDbDocument {
fn into(self) -> TypedData {
TypedData {
data: Some(Data::Json(self.0.to_string())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn it_constructs_from_an_object_value() {
let document = CosmosDbDocument::new(json!({ "id": "foo", "key": "value"}));
let data = document.as_object().unwrap();
assert_eq!(data["id"].as_str().unwrap(), "foo");
assert_eq!(data["key"].as_str().unwrap(), "value");
}
#[test]
#[should_panic(expected = "expected a single object for a Cosmos DB document")]
fn it_panics_if_constructed_without_an_object_or_array() {
CosmosDbDocument::new(json!(5));
}
#[test]
fn it_displays_as_json() {
let document = CosmosDbDocument::new(json!({ "foo": "bar"}));
assert_eq!(format!("{}", document), r#"{"foo":"bar"}"#);
}
#[test]
fn it_converts_from_str() {
let document: CosmosDbDocument = r#"{ "foo": "bar" }"#.into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
}
#[test]
fn it_converts_from_string() {
let document: CosmosDbDocument = r#"{ "foo": "bar" }"#.to_string().into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
}
#[test]
fn it_converts_from_value() {
let document: CosmosDbDocument = json!({ "foo": "bar" }).into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
}
#[test]
fn it_converts_to_string() {
let document: CosmosDbDocument = json!({ "foo": "bar" }).into();
let string: String = document.into();
assert_eq!(string, r#"{"foo":"bar"}"#);
}
#[test]
fn it_converts_to_value() {
let document: CosmosDbDocument = json!({ "foo": "bar" }).into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
let value: Value = document.into();
assert!(value.is_object());
assert_eq!(value.as_object().unwrap()["foo"].as_str().unwrap(), "bar");
}
#[test]
fn it_converts_to_body() {
let document: CosmosDbDocument = r#"{ "foo": "bar" }"#.into();
let body: Body = document.into();
assert_eq!(body.as_str().unwrap(), r#"{"foo":"bar"}"#);
let document: CosmosDbDocument = json!({"hello": "world"}).into();
let body: Body = document.into();
assert_eq!(body.as_str().unwrap(), r#"{"hello":"world"}"#);
}
#[test]
fn it_converts_from_typed_data() {
let document: CosmosDbDocument = TypedData {
data: Some(Data::Json(r#"{ "foo": "bar" }"#.to_string())),
}
.into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
}
#[test]
fn it_converts_to_typed_data() {
let document: CosmosDbDocument = json!({ "foo": "bar" }).into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
let data: TypedData = document.into();
assert_eq!(data.data, Some(Data::Json(r#"{"foo":"bar"}"#.to_string())));
}
}