hc_crud/
entities.rs

1use std::convert::TryFrom;
2use hdk::prelude::*;
3use crate::errors::{
4    UtilsResult, UtilsError,
5};
6
7
8/// An Entity categorization format that required the name and model values
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct EntityType {
11    /// An identifier for the type of data
12    pub name: String,
13
14    /// An identifier for the data's structure
15    pub model: String,
16}
17
18/// Identifies a struct as an Entity model type
19pub trait EntryModel<T>
20where
21    ScopedEntryDefIndex: for<'a> TryFrom<&'a T, Error = WasmError>,
22{
23    fn name() -> &'static str;
24    fn get_type(&self) -> EntityType;
25    fn to_input(&self) -> T;
26}
27
28impl EntityType {
29    pub fn new(name: &'static str, model: &'static str) -> Self {
30	EntityType {
31	    name: name.into(),
32	    model: model.into(),
33	}
34    }
35}
36
37
38/// The context and content of a specific entry
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct Entity<T> {
41    /// The address of the original created entry
42    pub id: EntryHash,
43
44    /// The create/update action of the current entry
45    pub action: ActionHash,
46
47    /// The address of the current entry
48    pub address: EntryHash,
49
50    #[serde(rename = "type")]
51    /// An identifier for the content's type and structure
52    pub ctype: EntityType,
53
54    /// The entity's current value
55    pub content: T,
56}
57
58impl<T> Entity<T> {
59
60    /// Link this entity to the given base with a specific tag.  Shortcut for [`hdk::prelude::create_link`]
61    pub fn link_from<L,E>(&self, base: &EntryHash, link_type: L, tag_input: Option<Vec<u8>>) -> UtilsResult<ActionHash>
62    where
63	ScopedLinkType: TryFrom<L, Error = E>,
64        WasmError: From<E>,
65    {
66        Ok( match tag_input {
67	    None => create_link( base.to_owned(), self.id.to_owned(), link_type, () )?,
68	    Some(input) => create_link( base.to_owned(), self.id.to_owned(), link_type, input )?,
69	})
70    }
71
72    /// Link the given target to this entity with a specific tag.  Shortcut for [`hdk::prelude::create_link`]
73    pub fn link_to<L,E>(&self, target: &EntryHash, link_type: L, tag_input: Option<Vec<u8>>) -> UtilsResult<ActionHash>
74    where
75	ScopedLinkType: TryFrom<L, Error = E>,
76        WasmError: From<E>,
77    {
78        Ok( match tag_input {
79	    None => create_link( self.id.to_owned(), target.to_owned(), link_type, () )?,
80	    Some(input) => create_link( self.id.to_owned(), target.to_owned(), link_type, input )?,
81	})
82    }
83
84    /// Delete an existing link from the 'current_base' and create a new link from the 'new_base'
85    pub fn move_link_from<LT,E>(&self, link_type: LT, tag_input: Option<Vec<u8>>, current_base: &EntryHash, new_base: &EntryHash) -> UtilsResult<ActionHash>
86    where
87	LT: LinkTypeFilterExt + Clone + std::fmt::Debug,
88        ScopedLinkType: TryFrom<LT, Error = E>,
89        WasmError: From<E>,
90    {
91	let tag_filter = tag_input.to_owned().map( |tag| LinkTag::new( tag ) );
92	let all_links = get_links(
93	    current_base.clone(),
94	    link_type.to_owned(),
95	    tag_filter.to_owned(),
96	)?;
97
98	if let Some(current_link) = all_links.into_iter().find(|link| {
99	    link.target == self.id.to_owned().into()
100	}) {
101            delete_link( current_link.create_link_hash )?;
102	}
103	else {
104	    Err(UtilsError::UnexpectedState(format!("Aborting 'move_from_link' because existing link was not found")))?;
105	};
106
107	let new_links = get_links(
108	    new_base.clone(),
109	    link_type.to_owned(),
110	    tag_filter.to_owned(),
111	)?;
112
113	if let Some(existing_link) = new_links.into_iter().find(|link| {
114	    link.target == self.id.to_owned().into()
115	}) {
116            Ok( existing_link.create_link_hash )
117	}
118	else {
119            self.link_from( new_base, link_type, tag_input )
120	}
121    }
122}
123
124
125#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct Empty {}
127
128/// A general use entity definition for deserializing any entity input when the content is not
129/// relevant.
130pub type EmptyEntity = Entity<Empty>;
131
132
133
134#[cfg(test)]
135pub mod tests {
136    use super::*;
137    use rand::Rng;
138
139    #[test]
140    fn entity_test() {
141	let bytes = rand::thread_rng().gen::<[u8; 32]>();
142	let ehash = holo_hash::EntryHash::from_raw_32( bytes.to_vec() );
143	let hhash = holo_hash::ActionHash::from_raw_32( bytes.to_vec() );
144
145	let item = Entity {
146	    id: ehash.clone(),
147	    action: hhash,
148	    address: ehash,
149	    ctype: EntityType::new( "boolean", "primitive" ),
150	    content: true,
151	};
152
153	assert_eq!( item.ctype.name, "boolean" );
154	assert_eq!( item.ctype.model, "primitive" );
155    }
156}