1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use crate::{chain_header::ChainHeader, entry::Entry, link::link_data::LinkData};
use holochain_json_api::{error::JsonError, json::JsonString};
use holochain_persistence_api::cas::content::{Address, AddressableContent, Content};
use std::{
    convert::{Into, TryFrom},
    fmt,
    hash::{Hash, Hasher},
};

impl AddressableContent for EntryAspect {
    fn content(&self) -> Content {
        self.to_owned().into()
    }

    fn try_from_content(content: &Content) -> Result<Self, JsonError> {
        Self::try_from(content.to_owned())
    }
}

#[derive(Serialize, Deserialize, PartialEq, Eq, DefaultJson, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum EntryAspect {
    // Basic case: entry content is communicated
    // with its header.
    // Content alone never makes sense
    // (receiveing node needs header and especially
    // source to run validation)
    Content(Entry, ChainHeader),

    // Communicating only the header makes sense if an
    // entry was deleted but we need to remember that
    // there was an entry that got deleted (sacrileged)
    Header(ChainHeader),

    // This is the meta item for adding a link.
    // The ChainHeader is needed for validation of
    // this LinkAdd statement/entry.
    // (Instead of type `LinkData` we could also have
    // an `Entry` that would always be expected to the be
    // `Entry::LinkAdd` specialisation, but in order to make
    // impossible states impossible we choose `LinkData`.
    // Putting that `LinkData` in an `Entry::LinkAdd` should
    // result in the exact same entry the `ChainHeader` is
    // a header for)
    LinkAdd(LinkData, ChainHeader),

    // Same as LinkAdd but for removal of links
    // TODO: can this tuple be combined with EntryType::LinkRemove's data, which is the same?
    LinkRemove((LinkData, Vec<Address>), ChainHeader),

    // TODO this looks wrong to me.  I don't think we actually want to
    // send the updated Entry as part of the meta item.  That would mean the
    // new entry is getting stored two places on the dht.  I think this
    // should look the same same as Deletion
    // AND, we don't actually need to even have the Address as part of the
    // Variant because the correct value is already in the Chain Header
    // as the link_update_delete attribute
    // Meta item for updating an entry.
    // The given Entry is the new version and ChainHeader
    // the header of the new version.
    // The header's CRUD link must reference the base address
    // of the EntryData this is in.
    //  Update(Entry, ChainHeader),
    Update(Entry, ChainHeader),

    // Meta item for removing an entry.
    // Address is the address of the deleted entry.
    // ChainHeader is the header of that deletion entry that
    // could be assembled by putting the address in an
    // `Entry::Deletion(address)`.
    // Deletion(Address, ChainHeader),
    Deletion(ChainHeader),
}

impl EntryAspect {
    pub fn type_hint(&self) -> String {
        match self {
            EntryAspect::Content(_, _) => String::from("content"),
            EntryAspect::Header(_) => String::from("header"),
            EntryAspect::LinkAdd(_, _) => String::from("link_add"),
            EntryAspect::LinkRemove(_, _) => String::from("link_remove"),
            EntryAspect::Update(_, _) => String::from("update"),
            EntryAspect::Deletion(_) => String::from("deletion"),
        }
    }
    pub fn header(&self) -> &ChainHeader {
        match self {
            EntryAspect::Content(_, header) => header,
            EntryAspect::Header(header) => header,
            EntryAspect::LinkAdd(_, header) => header,
            EntryAspect::LinkRemove(_, header) => header,
            EntryAspect::Update(_, header) => header,
            EntryAspect::Deletion(header) => header,
        }
    }
    pub fn entry_address(&self) -> &Address {
        self.header().entry_address()
    }
}

fn format_header(header: &ChainHeader) -> String {
    format!(
        "Header[type: {}, crud_link: {:?}]",
        header.entry_type(),
        header.link_update_delete()
    )
}
impl fmt::Debug for EntryAspect {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            EntryAspect::Content(entry, header) => write!(
                f,
                "EntryAspect::Content({}, {})",
                entry.address(),
                format_header(header)
            ),
            EntryAspect::Header(header) => {
                write!(f, "EntryAspect::Header({})", format_header(header))
            }
            EntryAspect::LinkAdd(link_data, header) => write!(
                f,
                "EntryAspect::LinkAdd({} -> {} [tag: {}, type: {}], {})",
                link_data.link.base(),
                link_data.link.target(),
                link_data.link.tag(),
                link_data.link.link_type(),
                format_header(header)
            ),
            EntryAspect::LinkRemove((link_data, _), header) => write!(
                f,
                "EntryAspect::LinkRemove({} -> {} [tag: {}, type: {}], {})",
                link_data.link.base(),
                link_data.link.target(),
                link_data.link.tag(),
                link_data.link.link_type(),
                format_header(header)
            ),
            EntryAspect::Update(entry, header) => write!(
                f,
                "EntryAspect::Update({}, {})",
                entry.address(),
                format_header(header)
            ),
            EntryAspect::Deletion(header) => {
                write!(f, "EntryAspect::Deletion({})", format_header(header))
            }
        }
    }
}

#[allow(clippy::derive_hash_xor_eq)]
// This clippy lint stresses the point that impls of Hash and PartialEq have to agree,
// that is ensure that: k1 == k2 ⇒ hash(k1) == hash(k2).
// In this custom Hash impl I'm just taking the entry address into account.
// The derived PartialEq takes all fields into account. If all fields are the same, so must
// the entry addresses which is part of all. QED.
impl Hash for EntryAspect {
    fn hash<H: Hasher>(&self, state: &mut H) {
        let address: String = self.header().entry_address().to_owned().into();
        state.write(address.as_bytes());
    }
}