use std::hash::Hash as StdHash;
use crate::entry::{decode_entry, EntrySigned};
use crate::identity::Author;
use crate::operation::{AsVerifiedOperation, Operation, OperationEncoded, VerifiedOperationError};
use crate::Validate;
use super::OperationId;
#[derive(Debug, Clone, Eq, PartialEq, StdHash)]
pub struct VerifiedOperation {
operation_id: OperationId,
public_key: Author,
operation: Operation,
}
impl AsVerifiedOperation for VerifiedOperation {
type VerifiedOperationError = VerifiedOperationError;
fn new(
public_key: &Author,
operation_id: &OperationId,
operation: &Operation,
) -> Result<Self, VerifiedOperationError> {
let verified_operation = Self {
public_key: public_key.clone(),
operation_id: operation_id.clone(),
operation: operation.clone(),
};
verified_operation.validate()?;
Ok(verified_operation)
}
fn new_from_entry(
entry_encoded: &EntrySigned,
operation_encoded: &OperationEncoded,
) -> Result<Self, VerifiedOperationError> {
let operation = Operation::from(operation_encoded);
decode_entry(entry_encoded, Some(operation_encoded))?;
let verified_operation = Self {
operation_id: entry_encoded.hash().into(),
public_key: entry_encoded.author(),
operation,
};
verified_operation.validate()?;
Ok(verified_operation)
}
fn operation_id(&self) -> &OperationId {
&self.operation_id
}
fn public_key(&self) -> &Author {
&self.public_key
}
fn operation(&self) -> &Operation {
&self.operation
}
}
#[cfg(any(feature = "testing", test))]
impl VerifiedOperation {
pub fn new_test_operation(
id: &OperationId,
public_key: &Author,
operation: &Operation,
) -> Self {
Self {
operation_id: id.clone(),
public_key: public_key.clone(),
operation: operation.clone(),
}
}
}
impl Validate for VerifiedOperation {
type Error = VerifiedOperationError;
fn validate(&self) -> Result<(), Self::Error> {
self.operation.validate()?;
self.public_key.validate()?;
self.operation_id.validate()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::convert::TryFrom;
use rstest::rstest;
use rstest_reuse::apply;
use crate::entry::EntrySigned;
use crate::identity::{Author, KeyPair};
use crate::operation::{
AsOperation, AsVerifiedOperation, Operation, OperationEncoded, OperationId, OperationValue,
VerifiedOperation,
};
use crate::test_utils::constants::{default_fields, TEST_SCHEMA_ID};
use crate::test_utils::fixtures::{
entry_signed_encoded, key_pair, operation, operation_encoded, operation_fields,
operation_id,
};
use crate::test_utils::templates::{implements_as_operation, various_verified_operation};
use crate::Validate;
#[rstest]
#[case(operation_encoded(Some(operation_fields(default_fields())), None, Some(TEST_SCHEMA_ID.parse().unwrap())))]
#[should_panic]
#[case(operation_encoded(Some(operation_fields(vec![("message", OperationValue::Text("Not the right message".to_string()))])), None, Some(TEST_SCHEMA_ID.parse().unwrap())))]
fn create_verified_operation(
entry_signed_encoded: EntrySigned,
#[case] operation_encoded: OperationEncoded,
) {
let verified_operation =
VerifiedOperation::new_from_entry(&entry_signed_encoded, &operation_encoded);
assert!(verified_operation.is_ok())
}
#[rstest]
fn new_operation_not_from_entry(
key_pair: KeyPair,
operation_id: OperationId,
#[from(operation)] operation: Operation,
) {
let author = Author::try_from(*key_pair.public_key()).unwrap();
let verified_operation = VerifiedOperation::new(&author, &operation_id, &operation);
assert!(verified_operation.is_ok());
let verified_operation = verified_operation.unwrap();
assert_eq!(verified_operation.fields(), operation.fields());
assert_eq!(verified_operation.action(), operation.action());
assert_eq!(verified_operation.version(), operation.version());
assert_eq!(verified_operation.schema(), operation.schema());
assert_eq!(
verified_operation.previous_operations(),
operation.previous_operations()
);
assert_eq!(verified_operation.public_key(), &author);
assert_eq!(verified_operation.operation_id(), &operation_id);
}
#[apply(various_verified_operation)]
fn only_some_operations_should_contain_fields(#[case] verified_operation: VerifiedOperation) {
if verified_operation.is_create() {
assert!(verified_operation.operation().fields().is_some());
}
if verified_operation.is_update() {
assert!(verified_operation.operation().fields().is_some());
}
if verified_operation.is_delete() {
assert!(verified_operation.operation().fields().is_none());
}
}
#[apply(various_verified_operation)]
fn operations_should_validate(#[case] verified_operation: VerifiedOperation) {
assert!(verified_operation.operation().validate().is_ok());
assert!(verified_operation.validate().is_ok())
}
#[apply(various_verified_operation)]
fn trait_methods_should_match(#[case] verified_operation: VerifiedOperation) {
let operation = verified_operation.operation();
assert_eq!(verified_operation.fields(), operation.fields());
assert_eq!(verified_operation.action(), operation.action());
assert_eq!(verified_operation.version(), operation.version());
assert_eq!(verified_operation.schema(), operation.schema());
assert_eq!(
verified_operation.previous_operations(),
operation.previous_operations()
);
}
#[apply(implements_as_operation)]
fn operation_has_same_trait_methods(#[case] operation: impl AsOperation) {
operation.is_create();
operation.is_update();
operation.fields();
operation.action();
operation.version();
operation.schema();
operation.previous_operations();
operation.has_fields();
operation.has_previous_operations();
}
#[apply(various_verified_operation)]
fn it_hashes(#[case] verified_operation: VerifiedOperation) {
let mut hash_map = HashMap::new();
let key_value = "Value identified by a hash".to_string();
hash_map.insert(&verified_operation, key_value.clone());
let key_value_retrieved = hash_map.get(&verified_operation).unwrap().to_owned();
assert_eq!(key_value, key_value_retrieved)
}
}