use ciborium::Value;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::sync::Arc;
use super::{Cbor, Fv, IndexedFieldValues, Schema, SchemaError};
pub type DocumentId = u64;
#[derive(Clone, Debug)]
pub struct Document {
fields: IndexedFieldValues,
schema: Arc<Schema>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DocumentOwned {
#[serde(rename = "f")]
pub fields: IndexedFieldValues,
}
#[derive(Clone, Debug, Serialize)]
struct DocumentRef<'a> {
#[serde(rename = "f")]
pub fields: &'a IndexedFieldValues,
}
impl From<Document> for DocumentOwned {
fn from(doc: Document) -> Self {
Self { fields: doc.fields }
}
}
impl Document {
pub fn new(schema: Arc<Schema>) -> Self {
Self {
fields: IndexedFieldValues::new(),
schema,
}
}
pub fn try_from_doc(schema: Arc<Schema>, doc: DocumentOwned) -> Result<Self, SchemaError> {
schema.validate(&doc.fields)?;
Ok(Self {
fields: doc.fields,
schema,
})
}
pub fn try_from<T>(schema: Arc<Schema>, doc: &T) -> Result<Self, SchemaError>
where
T: Serialize,
{
let doc = Value::serialized(doc).map_err(|err| {
SchemaError::Serialization(format!("failed to serialize document: {err:?}"))
})?;
let doc = doc.into_map().map_err(|err| {
SchemaError::Validation(format!(
"invalid document, expected CBOR map value, got {err:?}"
))
})?;
let mut doc_owned = DocumentOwned::default();
for (k, v) in doc {
let k = k.into_text().map_err(|err| {
SchemaError::Validation(format!(
"invalid document field key, expected CBOR text value, got {err:?}"
))
})?;
let field = schema.get_field_or_err(&k)?;
let value = field.extract(v, false)?;
doc_owned.fields.insert(field.idx(), value);
}
Self::try_from_doc(schema, doc_owned)
}
pub fn try_into<T>(mut self) -> Result<T, SchemaError>
where
T: DeserializeOwned,
{
let mut doc: Vec<(Cbor, Cbor)> = Vec::with_capacity(self.schema.len());
for field in self.schema.iter() {
if let Some(value) = self.fields.remove(&field.idx()) {
doc.push((field.name().into(), value.into()));
} else if field.required() {
return Err(SchemaError::Validation(format!(
"field {:?} is required",
field.name()
)));
} else {
doc.push((field.name().into(), Cbor::Null));
}
}
Cbor::Map(doc)
.deserialized()
.map_err(|err| SchemaError::Serialization(format!("Failed to deserialize: {err}")))
}
pub fn id(&self) -> DocumentId {
match self.fields.get(&0) {
Some(Fv::U64(id)) => *id,
_ => 0,
}
}
pub fn set_id(&mut self, id: DocumentId) -> &mut Self {
self.fields.insert(0, Fv::U64(id));
self
}
pub fn fields(&self) -> &IndexedFieldValues {
&self.fields
}
pub fn get_field(&self, name: &str) -> Option<&Fv> {
if let Some(field) = self.schema.get_field(name) {
self.fields.get(&field.idx())
} else {
None
}
}
pub fn get_field_or_err(&self, name: &str) -> Result<&Fv, SchemaError> {
if let Some(field) = self.schema.get_field(name) {
self.fields.get(&field.idx()).ok_or_else(|| {
SchemaError::Validation(format!(
"field {:?} at {} not found in document",
name,
field.idx()
))
})
} else {
Err(SchemaError::Validation(format!(
"field {name:?} not found in schema"
)))
}
}
pub fn set_field(&mut self, name: &str, value: Fv) -> Result<&mut Self, SchemaError> {
if let Some(field) = self.schema.get_field(name) {
field.validate(&value)?;
self.fields.insert(field.idx(), value);
return Ok(self);
}
Err(SchemaError::Validation(format!(
"field {name:?} not found in schema"
)))
}
pub fn remove_field(&mut self, name: &str) -> Option<Fv> {
if let Some(field) = self.schema.get_field(name) {
return self.fields.remove(&field.idx());
}
None
}
pub fn get_field_as<T>(&self, name: &str) -> Result<T, SchemaError>
where
T: DeserializeOwned,
{
if let Some(field) = self.schema.get_field(name) {
if let Some(value) = self.fields.get(&field.idx()) {
return value.to_owned().deserialized();
} else {
return Err(SchemaError::Validation(format!(
"field {name:?} not found in document"
)));
}
}
Err(SchemaError::Validation(format!(
"field {name:?} not found in schema"
)))
}
pub fn set_field_as<T>(&mut self, name: &str, value: &T) -> Result<&mut Self, SchemaError>
where
T: Serialize,
{
let field = self.schema.get_field_or_err(name)?;
let value = Fv::serialized(value, Some(field.r#type()))?;
field.validate(&value)?;
self.fields.insert(field.idx(), value);
Ok(self)
}
pub fn set_doc(&mut self, doc: DocumentOwned) -> Result<(), SchemaError> {
self.schema.validate(&doc.fields)?;
self.fields = doc.fields;
Ok(())
}
}
impl Serialize for Document {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let doc = DocumentRef {
fields: &self.fields,
};
doc.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AndaDBSchema, FieldEntry, FieldKey, FieldType, Fv, Resource};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Serialize, Deserialize, PartialEq, AndaDBSchema)]
struct TestUser {
_id: u64,
name: String,
age: u64,
active: Option<bool>,
tags: Option<Vec<String>>,
meta: Option<BTreeMap<String, u64>>,
picture: Option<Resource>,
}
#[test]
fn test_document_with_id() {
let schema = Arc::new(TestUser::schema().unwrap());
let id = 99u64;
println!("Schema: {schema:#?}");
let mut doc = Document::new(schema);
assert!(doc.fields.is_empty());
assert_eq!(doc.id(), 0);
doc.set_id(id);
assert_eq!(doc.id(), id);
}
#[test]
fn test_document_try_from_doc() {
let schema = Arc::new(TestUser::schema().unwrap());
let id = 99u64;
let mut fields = IndexedFieldValues::new();
fields.insert(1, Fv::Text("John Doe".to_string()));
fields.insert(2, Fv::U64(30));
let mut owned_doc = DocumentOwned { fields };
assert!(Document::try_from_doc(schema.clone(), owned_doc.clone()).is_err());
owned_doc.fields.insert(0, Fv::U64(id));
let doc = Document::try_from_doc(schema.clone(), owned_doc).unwrap();
assert_eq!(doc.id(), id);
assert_eq!(doc.fields.len(), 3);
assert_eq!(doc.get_field("age").unwrap(), &Fv::U64(30));
}
#[test]
fn test_document_try_from() {
let schema = Arc::new(TestUser::schema().unwrap());
let test_user = TestUser {
_id: 99,
name: "John Doe".to_string(),
age: 30,
active: Some(true),
tags: Some(vec!["user".to_string(), "admin".to_string()]),
meta: Some(BTreeMap::from([
("created".to_string(), 1625097600),
("updated".to_string(), 1625097600),
])),
picture: None,
};
let doc = Document::try_from(schema.clone(), &test_user).unwrap();
assert_eq!(doc.id(), 99);
assert_eq!(
doc.get_field("name").unwrap(),
&Fv::Text("John Doe".to_string())
);
assert_eq!(doc.get_field("age").unwrap(), &Fv::U64(30));
assert_eq!(doc.get_field("active").unwrap(), &Fv::Bool(true));
if let Fv::Array(tags) = doc.get_field("tags").unwrap() {
assert_eq!(tags.len(), 2);
assert_eq!(tags[0], Fv::Text("user".to_string()));
assert_eq!(tags[1], Fv::Text("admin".to_string()));
} else {
panic!("Expected Array field");
}
if let Fv::Map(meta) = doc.get_field("meta").unwrap() {
assert_eq!(meta.len(), 2);
assert_eq!(meta.get(&"created".into()).unwrap(), &Fv::U64(1625097600));
assert_eq!(meta.get(&"updated".into()).unwrap(), &Fv::U64(1625097600));
} else {
panic!("Expected Map field");
}
}
#[test]
fn test_document_try_as() {
let schema = Arc::new(TestUser::schema().unwrap());
let test_user = TestUser {
_id: 99,
name: "John Doe".to_string(),
age: 30,
active: Some(true),
tags: Some(vec!["user".to_string(), "admin".to_string()]),
meta: Some(BTreeMap::from([
("created".to_string(), 1625097600),
("updated".to_string(), 1625097600),
])),
picture: None,
};
let doc = Document::try_from(schema.clone(), &test_user).unwrap();
let deserialized: TestUser = doc.try_into().unwrap();
assert_eq!(deserialized, test_user);
}
#[test]
fn test_document_get_set_field() {
let schema = Arc::new(TestUser::schema().unwrap());
let mut doc = Document::new(schema.clone());
doc.set_field("name", Fv::Text("John Doe".to_string()))
.unwrap()
.set_field("age", Fv::U64(30))
.unwrap();
assert_eq!(
doc.get_field("name").unwrap(),
&Fv::Text("John Doe".to_string())
);
assert_eq!(doc.get_field("age").unwrap(), &Fv::U64(30));
assert!(
doc.set_field("unknown", Fv::Text("value".to_string()))
.is_err()
);
assert!(doc.get_field("unknown").is_none());
}
#[test]
fn test_document_get_set_field_as() {
let schema = Arc::new(TestUser::schema().unwrap());
let mut doc = Document::new(schema.clone());
doc.set_field_as("name", &"John Doe".to_string()).unwrap();
doc.set_field_as("age", &30u64).unwrap();
doc.set_field_as("active", &true).unwrap();
let name: String = doc.get_field_as("name").unwrap();
let age: u64 = doc.get_field_as("age").unwrap();
let active: bool = doc.get_field_as("active").unwrap();
assert_eq!(name, "John Doe");
assert_eq!(age, 30);
assert!(active);
assert!(doc.set_field_as("unknown", &"value".to_string()).is_err());
let result: Result<String, _> = doc.get_field_as("unknown");
assert!(result.is_err());
}
#[test]
fn test_document_set_doc() {
let schema = Arc::new(TestUser::schema().unwrap());
let mut doc = Document::new(schema.clone());
let mut fields = IndexedFieldValues::new();
fields.insert(1, Fv::Text("John Doe".to_string())); fields.insert(2, Fv::U64(30));
let mut owned_doc = DocumentOwned { fields };
assert!(doc.set_doc(owned_doc.clone()).is_err());
owned_doc.fields.insert(
0,
Fv::U64(99), ); doc.set_doc(owned_doc).unwrap();
assert_eq!(doc.id(), 99);
assert_eq!(doc.fields.len(), 3);
assert_eq!(
doc.get_field("name").unwrap(),
&Fv::Text("John Doe".to_string())
);
assert_eq!(doc.get_field("age").unwrap(), &Fv::U64(30));
}
#[test]
fn test_document_from_to_owned() {
let schema = Arc::new(TestUser::schema().unwrap());
let mut doc = Document::new(schema.clone());
doc.set_id(99);
doc.set_field("name", Fv::Text("John Doe".to_string()))
.unwrap();
doc.set_field("age", Fv::U64(30)).unwrap();
let owned: DocumentOwned = doc.into();
assert_eq!(owned.fields.len(), 3);
let doc2 = Document::try_from_doc(schema.clone(), owned).unwrap();
assert_eq!(doc2.id(), 99);
assert_eq!(doc2.fields.len(), 3);
assert_eq!(
doc2.get_field("name").unwrap(),
&Fv::Text("John Doe".to_string())
);
assert_eq!(doc2.get_field("age").unwrap(), &Fv::U64(30));
}
#[test]
fn test_document_validation_errors() {
let schema = Arc::new(TestUser::schema().unwrap());
let test_user_missing_required = serde_json::json!({
"_id": 18,
"name": "John Doe",
"active": true
});
let result = Document::try_from(schema.clone(), &test_user_missing_required);
assert!(result.is_err());
let test_user_wrong_type = serde_json::json!({
"_id": "18",
"name": "John Doe",
"age": "thirty", "active": true
});
let result = Document::try_from(schema.clone(), &test_user_wrong_type);
assert!(result.is_err());
}
}