use std::convert::TryFrom;
use std::hash::Hash as StdHash;
use serde::{Deserialize, Serialize};
use crate::hash::Hash;
use crate::operation::{Operation, OperationEncodedError};
use crate::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, StdHash)]
pub struct OperationEncoded(String);
impl OperationEncoded {
pub fn new(value: &str) -> Result<OperationEncoded, OperationEncodedError> {
let inner = Self(value.to_owned());
inner.validate()?;
Ok(inner)
}
pub fn hash(&self) -> Hash {
Hash::new_from_bytes(self.to_bytes()).unwrap()
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn to_bytes(&self) -> Vec<u8> {
hex::decode(&self.0).unwrap()
}
pub fn size(&self) -> u64 {
self.0.len() as u64 / 2
}
}
impl TryFrom<&Operation> for OperationEncoded {
type Error = OperationEncodedError;
fn try_from(operation: &Operation) -> Result<Self, Self::Error> {
let encoded = hex::encode(&operation.to_cbor());
OperationEncoded::new(&encoded)
}
}
impl Validate for OperationEncoded {
type Error = OperationEncodedError;
fn validate(&self) -> Result<(), Self::Error> {
hex::decode(&self.0).map_err(|_| OperationEncodedError::InvalidHexEncoding)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use rstest::rstest;
use rstest_reuse::apply;
use crate::document::DocumentId;
use crate::operation::{AsOperation, Operation, OperationValue, Relation, RelationList};
use crate::schema::SchemaId;
use crate::test_utils::fixtures::{
encoded_create_string, operation, operation_encoded_invalid_relation_fields,
operation_fields, random_document_id, random_document_view_id, schema, Fixture,
};
use crate::test_utils::templates::version_fixtures;
use crate::Validate;
use super::OperationEncoded;
#[rstest]
fn validate(encoded_create_string: String) {
assert!(OperationEncoded::new("123456789Z").is_err());
assert!(OperationEncoded::new(&encoded_create_string).is_ok());
}
#[rstest]
fn decode_invalid_relation_fields(operation_encoded_invalid_relation_fields: OperationEncoded) {
let operation = Operation::try_from(&operation_encoded_invalid_relation_fields).unwrap();
assert!(operation.validate().is_err());
}
#[apply(version_fixtures)]
fn decode(#[case] fixture: Fixture) {
let operation = Operation::try_from(&fixture.operation_encoded).unwrap();
assert!(operation.validate().is_ok());
assert!(operation.is_create());
let fields = operation.fields().unwrap();
assert_eq!(
fields.get("name").unwrap(),
&OperationValue::Text("chess".to_owned())
);
assert_eq!(
fields.get("description").unwrap(),
&OperationValue::Text("for playing chess".to_owned())
);
}
#[rstest]
fn encode_decode_all_field_types(
schema: SchemaId,
#[from(random_document_id)] picture_document: DocumentId,
#[from(random_document_id)] friend_document_1: DocumentId,
#[from(random_document_id)] friend_document_2: DocumentId,
#[from(operation)]
#[with(
Some(operation_fields(vec![
("username", OperationValue::Text("bubu".to_owned())),
("age", OperationValue::Integer(28)),
("height", OperationValue::Float(3.5)),
("is_admin", OperationValue::Boolean(false)),
("profile_picture", OperationValue::Relation(Relation::new(picture_document.clone()))),
("my_friends", OperationValue::RelationList(RelationList::new(vec![
friend_document_1.clone(),
friend_document_2.clone(),
]))),
])),
Some(random_document_view_id()),
)]
update_operation: Operation,
) {
let operation_encoded = OperationEncoded::try_from(&update_operation).unwrap();
let operation = Operation::try_from(&operation_encoded).unwrap();
assert!(operation.is_update());
assert_eq!(operation.schema(), schema);
let fields = operation.fields().unwrap();
assert_eq!(
fields.get("username").unwrap(),
&OperationValue::Text("bubu".to_owned())
);
assert_eq!(fields.get("age").unwrap(), &OperationValue::Integer(28));
assert_eq!(fields.get("height").unwrap(), &OperationValue::Float(3.5));
assert_eq!(
fields.get("is_admin").unwrap(),
&OperationValue::Boolean(false)
);
assert_eq!(
fields.get("profile_picture").unwrap(),
&OperationValue::Relation(Relation::new(picture_document))
);
assert_eq!(
fields.get("my_friends").unwrap(),
&OperationValue::RelationList(RelationList::new(vec![
friend_document_1,
friend_document_2,
]))
);
}
}