use std::convert::TryFrom;
use std::slice::Iter;
use serde::{Deserialize, Serialize};
use crate::document::error::DocumentIdError;
use crate::document::{DocumentId, DocumentViewId};
use crate::operation::error::{
PinnedRelationError, PinnedRelationListError, RelationError, RelationListError,
};
use crate::operation::OperationId;
use crate::Validate;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Relation(DocumentId);
impl Relation {
pub fn new(document: DocumentId) -> Self {
Self(document)
}
pub fn document_id(&self) -> &DocumentId {
&self.0
}
}
impl Validate for Relation {
type Error = RelationError;
fn validate(&self) -> Result<(), Self::Error> {
self.0.validate()?;
Ok(())
}
}
impl<'de> Deserialize<'de> for Relation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let document_id: DocumentId = Deserialize::deserialize(deserializer)?;
document_id
.validate()
.map_err(|err| serde::de::Error::custom(format!("invalid document id, {}", err)))?;
Ok(Self(document_id))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct PinnedRelation(DocumentViewId);
impl PinnedRelation {
pub fn new(document_view_id: DocumentViewId) -> Self {
Self(document_view_id)
}
pub fn view_id(&self) -> &DocumentViewId {
&self.0
}
pub fn iter(&self) -> Iter<OperationId> {
self.0.iter()
}
}
impl Validate for PinnedRelation {
type Error = PinnedRelationError;
fn validate(&self) -> Result<(), Self::Error> {
self.0.validate()?;
Ok(())
}
}
impl<'de> Deserialize<'de> for PinnedRelation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let document_view_id: DocumentViewId = Deserialize::deserialize(deserializer)?;
document_view_id.validate().map_err(|err| {
serde::de::Error::custom(format!("invalid document view id, {}", err))
})?;
Ok(Self(document_view_id))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[allow(clippy::len_without_is_empty)]
pub struct RelationList(Vec<DocumentId>);
impl RelationList {
pub fn new(relations: Vec<DocumentId>) -> Self {
Self(relations)
}
pub fn document_ids(&self) -> &[DocumentId] {
self.0.as_slice()
}
pub fn iter(&self) -> Iter<DocumentId> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl Validate for RelationList {
type Error = RelationListError;
fn validate(&self) -> Result<(), Self::Error> {
for document_id in &self.0 {
document_id.validate()?;
}
Ok(())
}
}
impl TryFrom<&[String]> for RelationList {
type Error = RelationListError;
fn try_from(str_list: &[String]) -> Result<Self, Self::Error> {
let document_ids: Result<Vec<DocumentId>, DocumentIdError> = str_list
.iter()
.map(|document_id_str| document_id_str.parse::<DocumentId>())
.collect();
Ok(Self(document_ids?))
}
}
impl<'de> Deserialize<'de> for RelationList {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let document_ids: Vec<DocumentId> = Deserialize::deserialize(deserializer)?;
let relation_list = Self(document_ids);
relation_list
.validate()
.map_err(|err| serde::de::Error::custom(format!("invalid document id, {}", err)))?;
Ok(relation_list)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[allow(clippy::len_without_is_empty)]
pub struct PinnedRelationList(Vec<DocumentViewId>);
impl PinnedRelationList {
pub fn new(relations: Vec<DocumentViewId>) -> Self {
Self(relations)
}
pub fn document_view_ids(&self) -> &[DocumentViewId] {
self.0.as_slice()
}
pub fn iter(&self) -> Iter<DocumentViewId> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl Validate for PinnedRelationList {
type Error = PinnedRelationListError;
fn validate(&self) -> Result<(), Self::Error> {
for document_view_id in &self.0 {
document_view_id.validate()?;
}
Ok(())
}
}
impl<'de> Deserialize<'de> for PinnedRelationList {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let document_view_ids: Vec<DocumentViewId> = Deserialize::deserialize(deserializer)?;
let pinned_relation_list = Self(document_view_ids);
pinned_relation_list.validate().map_err(|err| {
serde::de::Error::custom(format!("invalid document view id, {}", err))
})?;
Ok(pinned_relation_list)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use ciborium::cbor;
use rstest::rstest;
use crate::document::{DocumentId, DocumentViewId};
use crate::hash::Hash;
use crate::operation::OperationId;
use crate::serde::{deserialize_into, serialize_from, serialize_value};
use crate::test_utils::fixtures::random_document_id;
use crate::test_utils::fixtures::random_hash;
use crate::Validate;
use super::{PinnedRelation, PinnedRelationList, Relation, RelationList};
#[rstest]
fn validation(
#[from(random_document_id)] document_1: DocumentId,
#[from(random_document_id)] document_2: DocumentId,
#[from(random_hash)] operation_id_1: Hash,
#[from(random_hash)] operation_id_2: Hash,
) {
let relation = Relation::new(document_1.clone());
assert!(relation.validate().is_ok());
let pinned_relation = PinnedRelation::new(DocumentViewId::from(operation_id_1.clone()));
assert!(pinned_relation.validate().is_ok());
let relation_list = RelationList::new(vec![document_1, document_2]);
assert!(relation_list.validate().is_ok());
let pinned_relation_list =
PinnedRelationList::new(vec![operation_id_1.into(), operation_id_2.into()]);
assert!(pinned_relation_list.validate().is_ok());
}
#[rstest]
fn iterates(#[from(random_hash)] hash_1: Hash, #[from(random_hash)] hash_2: Hash) {
let pinned_relation = PinnedRelation::new(DocumentViewId::new(&[
hash_1.clone().into(),
hash_2.clone().into(),
]));
for hash in pinned_relation.iter() {
assert!(hash.validate().is_ok());
}
let relation_list = RelationList::new(vec![
DocumentId::new(&hash_1.clone().into()),
DocumentId::new(&hash_2.clone().into()),
]);
for document_id in relation_list.iter() {
assert!(document_id.validate().is_ok());
}
let pinned_relation_list = PinnedRelationList::new(vec![
DocumentViewId::from(hash_1),
DocumentViewId::from(hash_2),
]);
for pinned_relation in pinned_relation_list.iter() {
for hash in pinned_relation.graph_tips() {
assert!(hash.validate().is_ok());
}
}
}
#[rstest]
fn list_equality(
#[from(random_document_id)] document_1: DocumentId,
#[from(random_document_id)] document_2: DocumentId,
#[from(random_hash)] operation_id_1: Hash,
#[from(random_hash)] operation_id_2: Hash,
) {
let relation_list = RelationList::new(vec![document_1.clone(), document_2.clone()]);
let relation_list_different_order = RelationList::new(vec![document_2, document_1]);
assert_ne!(relation_list, relation_list_different_order);
let pinned_relation_list = PinnedRelationList::new(vec![
operation_id_1.clone().into(),
operation_id_2.clone().into(),
]);
let pinned_relation_list_different_order =
PinnedRelationList::new(vec![operation_id_2.into(), operation_id_1.into()]);
assert_ne!(pinned_relation_list, pinned_relation_list_different_order);
}
#[test]
fn serialize_relation() {
let hash_str = "0020b50b06774f909483c9c18e31b3bb17ff8f7d23088e9cc5a39260392259f34d42";
let bytes = serialize_from(Relation::new(DocumentId::from_str(hash_str).unwrap()));
assert_eq!(bytes, serialize_value(cbor!(hash_str)));
}
#[test]
fn deserialize_relation() {
let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
let relation: Relation = deserialize_into(&serialize_value(cbor!(hash_str))).unwrap();
assert_eq!(
Relation::new(DocumentId::from_str(hash_str).unwrap()),
relation
);
let invalid_hash = deserialize_into::<Relation>(&serialize_value(cbor!("1234")));
assert!(invalid_hash.is_err());
let empty_hash = deserialize_into::<Relation>(&serialize_value(cbor!("")));
assert!(empty_hash.is_err());
}
#[test]
fn serialize_pinned_relation() {
let hash_str = "00208b050b24273b397f91a41e7f5030a853435dee0abbdc507dfc75a13809e7ba5f";
let bytes = serialize_from(PinnedRelation::new(
DocumentViewId::from_str(hash_str).unwrap(),
));
assert_eq!(bytes, serialize_value(cbor!([hash_str])));
}
#[test]
fn deserialize_pinned_relation() {
let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
let pinned_relation: PinnedRelation = deserialize_into(&serialize_value(cbor!([
"0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805"
])))
.unwrap();
assert_eq!(
PinnedRelation::new(DocumentViewId::from_str(hash_str).unwrap()),
pinned_relation
);
let invalid_hash = deserialize_into::<PinnedRelation>(&serialize_value(cbor!(["1234"])));
assert!(invalid_hash.is_err());
let empty_hash = deserialize_into::<PinnedRelation>(&serialize_value(cbor!([])));
assert!(empty_hash.is_err());
let unordered = deserialize_into::<PinnedRelation>(&serialize_value(cbor!([
"0020f1ab6d8114c0e7ab0af3bfd6862daf6ee0c510bbdf129e1780edfa505e860ff7",
"0020a19353e7dfeb2f9031087c3428a2467bb684e25321f09298c64ce1a2fd5787d1",
])));
assert!(unordered.is_err());
let duplicate = deserialize_into::<PinnedRelation>(&serialize_value(cbor!([
"05018634222cc8c9d49c5f48e8aecf0412c2cd2082a6712676373eaa1660e7af",
"05018634222cc8c9d49c5f48e8aecf0412c2cd2082a6712676373eaa1660e7af",
])));
assert!(duplicate.is_err());
}
#[test]
fn serialize_relation_list() {
let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
let bytes = serialize_from(RelationList::new(vec![
DocumentId::from_str(hash_str).unwrap()
]));
assert_eq!(bytes, serialize_value(cbor!([hash_str])));
}
#[test]
fn deserialize_relation_list() {
let hash_str_1 = "0020deb1356bcdec02e05ce4f1fce51561bbfda68d1c4537c98c592b9e2bf9917122";
let hash_str_2 = "002051044a3cfec6fea09759133dbae95dce9b49aa172df7fbb085c9b932694b2805";
let relation_list: RelationList =
deserialize_into(&serialize_value(cbor!([hash_str_1, hash_str_2]))).unwrap();
assert_eq!(
RelationList::new(vec![
DocumentId::from_str(hash_str_1).unwrap(),
DocumentId::from_str(hash_str_2).unwrap()
]),
relation_list
);
let invalid_hash = deserialize_into::<RelationList>(&serialize_value(cbor!(["1234"])));
assert!(invalid_hash.is_err());
}
#[test]
fn serialize_pinned_relation_list() {
let hash_str_1 = "002051044a3cfec6fea09759133dbae95dce9b49aa172df7fbb085c9b932694b2805";
let hash_str_2 = "0020deb1356bcdec02e05ce4f1fce51561bbfda68d1c4537c98c592b9e2bf9917122";
let hash_str_3 = "002084d3c7eb7085c920879da6ea6c94cf89777e8f427a32f49d441fcda80cd39483";
let bytes = serialize_from(PinnedRelationList::new(vec![
DocumentViewId::new(&[
OperationId::from_str(hash_str_1).unwrap(),
OperationId::from_str(hash_str_2).unwrap(),
]),
DocumentViewId::new(&[OperationId::from_str(hash_str_3).unwrap()]),
]));
assert_eq!(
bytes,
serialize_value(cbor!([[hash_str_1, hash_str_2], [hash_str_3]]))
);
}
#[test]
fn deserialize_pinned_relation_list() {
let hash_str_1 = "002051044a3cfec6fea09759133dbae95dce9b49aa172df7fbb085c9b932694b2805";
let hash_str_2 = "0020deb1356bcdec02e05ce4f1fce51561bbfda68d1c4537c98c592b9e2bf9917122";
let hash_str_3 = "002084d3c7eb7085c920879da6ea6c94cf89777e8f427a32f49d441fcda80cd39483";
let pinned_relation_list: PinnedRelationList = deserialize_into(&serialize_value(cbor!([
[hash_str_1, hash_str_2],
[hash_str_3]
])))
.unwrap();
assert_eq!(
PinnedRelationList::new(vec![
DocumentViewId::new(&[
OperationId::from_str(hash_str_1).unwrap(),
OperationId::from_str(hash_str_2).unwrap(),
]),
DocumentViewId::new(&[OperationId::from_str(hash_str_3).unwrap()]),
]),
pinned_relation_list
);
let invalid_hash =
deserialize_into::<PinnedRelationList>(&serialize_value(cbor!([["1234"]])));
assert!(invalid_hash.is_err());
let unordered = deserialize_into::<PinnedRelationList>(&serialize_value(cbor!([[
"0020f1ab6d8114c0e7ab0af3bfd6862daf6ee0c510bbdf129e1780edfa505e860ff7",
"0020a19353e7dfeb2f9031087c3428a2467bb684e25321f09298c64ce1a2fd5787d1",
]])));
assert!(unordered.is_err());
let duplicate = deserialize_into::<PinnedRelationList>(&serialize_value(cbor!([[
"05018634222cc8c9d49c5f48e8aecf0412c2cd2082a6712676373eaa1660e7af",
"05018634222cc8c9d49c5f48e8aecf0412c2cd2082a6712676373eaa1660e7af",
]])));
assert!(duplicate.is_err());
}
}