holochain_types/
link.rs

1//! Links interrelate entries in a source chain.
2
3use holo_hash::ActionHash;
4use holo_hash::AgentPubKey;
5use holo_hash::AnyLinkableHash;
6use holochain_serialized_bytes::prelude::*;
7use holochain_zome_types::prelude::*;
8use regex::Regex;
9
10use crate::dht_op::ChainOpType;
11use crate::dht_op::DhtOpError;
12use crate::dht_op::DhtOpResult;
13use crate::dht_op::RenderedOp;
14use crate::dht_op::RenderedOps;
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
17/// Link key for sending across the wire for get links requests.
18pub struct WireLinkKey {
19    /// Base the links are on.
20    pub base: AnyLinkableHash,
21    /// The zome the links are in.
22    pub type_query: LinkTypeFilter,
23    /// Optionally specify a tag for more specific queries.
24    pub tag: Option<LinkTag>,
25    /// Specify a minimum action timestamp to filter results.
26    pub after: Option<Timestamp>,
27    /// Specify a maximum action timestamp to filter results.
28    pub before: Option<Timestamp>,
29    /// Only get links created by this author.
30    pub author: Option<AgentPubKey>,
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes, Default)]
34/// Condensed link ops for sending across the wire in response to get links.
35pub struct WireLinkOps {
36    /// create links that match this query.
37    pub creates: Vec<WireCreateLink>,
38    /// delete links that match this query.
39    pub deletes: Vec<WireDeleteLink>,
40}
41
42impl WireLinkOps {
43    /// Create an empty wire response.
44    pub fn new() -> Self {
45        Default::default()
46    }
47    /// Render these ops to their full types.
48    pub fn render(self, key: &WireLinkKey) -> DhtOpResult<RenderedOps> {
49        let Self { creates, deletes } = self;
50        let mut ops = Vec::with_capacity(creates.len() + deletes.len());
51        // We silently ignore ops that fail to render as they come from the network.
52        ops.extend(creates.into_iter().filter_map(|op| op.render(key).ok()));
53        ops.extend(deletes.into_iter().filter_map(|op| op.render(key).ok()));
54        Ok(RenderedOps {
55            ops,
56            ..Default::default()
57        })
58    }
59}
60
61#[allow(missing_docs)]
62#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
63/// Condensed version of a [`CreateLink`]
64pub struct WireCreateLink {
65    pub author: AgentPubKey,
66    pub timestamp: Timestamp,
67    pub action_seq: u32,
68    pub prev_action: ActionHash,
69
70    pub target_address: AnyLinkableHash,
71    pub zome_index: ZomeIndex,
72    pub link_type: LinkType,
73    pub tag: Option<LinkTag>,
74    pub signature: Signature,
75    pub validation_status: ValidationStatus,
76    pub weight: RateWeight,
77}
78
79#[allow(missing_docs)]
80#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
81/// Condensed version of a [`DeleteLink`]
82pub struct WireDeleteLink {
83    pub author: AgentPubKey,
84    pub timestamp: Timestamp,
85    pub action_seq: u32,
86    pub prev_action: ActionHash,
87
88    pub link_add_address: ActionHash,
89    pub signature: Signature,
90    pub validation_status: ValidationStatus,
91}
92
93impl WireCreateLink {
94    fn new(
95        h: CreateLink,
96        signature: Signature,
97        validation_status: ValidationStatus,
98        tag: bool,
99    ) -> Self {
100        Self {
101            author: h.author,
102            timestamp: h.timestamp,
103            action_seq: h.action_seq,
104            prev_action: h.prev_action,
105            target_address: h.target_address,
106            zome_index: h.zome_index,
107            link_type: h.link_type,
108            tag: if tag { Some(h.tag) } else { None },
109            signature,
110            validation_status,
111            weight: h.weight,
112        }
113    }
114    /// Condense down a create link op for the wire without a tag.
115    pub fn condense_base_only(
116        h: CreateLink,
117        signature: Signature,
118        validation_status: ValidationStatus,
119    ) -> Self {
120        Self::new(h, signature, validation_status, false)
121    }
122    /// Condense down a create link op for the wire with a tag.
123    pub fn condense(
124        h: CreateLink,
125        signature: Signature,
126        validation_status: ValidationStatus,
127    ) -> Self {
128        Self::new(h, signature, validation_status, true)
129    }
130    /// Render these ops to their full types.
131    pub fn render(self, key: &WireLinkKey) -> DhtOpResult<RenderedOp> {
132        let tag = self
133            .tag
134            .or_else(|| key.tag.clone())
135            .ok_or(DhtOpError::LinkKeyTagMissing)?;
136        let action = Action::CreateLink(CreateLink {
137            author: self.author,
138            timestamp: self.timestamp,
139            action_seq: self.action_seq,
140            prev_action: self.prev_action,
141            base_address: key.base.clone(),
142            target_address: self.target_address,
143            zome_index: self.zome_index,
144            link_type: self.link_type,
145            weight: self.weight,
146            tag,
147        });
148        let signature = self.signature;
149        let validation_status = Some(self.validation_status);
150        RenderedOp::new(
151            action,
152            signature,
153            validation_status,
154            ChainOpType::RegisterAddLink,
155        )
156    }
157}
158
159impl WireDeleteLink {
160    /// Condense down a delete link op for the wire.
161    pub fn condense(
162        h: DeleteLink,
163        signature: Signature,
164        validation_status: ValidationStatus,
165    ) -> Self {
166        Self {
167            author: h.author,
168            timestamp: h.timestamp,
169            action_seq: h.action_seq,
170            prev_action: h.prev_action,
171            signature,
172            validation_status,
173            link_add_address: h.link_add_address,
174        }
175    }
176    /// Render these ops to their full types.
177    pub fn render(self, key: &WireLinkKey) -> DhtOpResult<RenderedOp> {
178        let action = Action::DeleteLink(DeleteLink {
179            author: self.author,
180            timestamp: self.timestamp,
181            action_seq: self.action_seq,
182            prev_action: self.prev_action,
183            base_address: key.base.clone(),
184            link_add_address: self.link_add_address,
185        });
186        let signature = self.signature;
187        let validation_status = Some(self.validation_status);
188        RenderedOp::new(
189            action,
190            signature,
191            validation_status,
192            ChainOpType::RegisterRemoveLink,
193        )
194    }
195}
196// TODO: Probably don't want to send the whole actions.
197// We could probably come up with a more compact
198// network Wire type in the future
199/// Link response to get links
200#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
201pub struct GetLinksResponse {
202    /// All the link adds on the key you searched for
203    pub link_adds: Vec<(CreateLink, Signature)>,
204    /// All the link removes on the key you searched for
205    pub link_removes: Vec<(DeleteLink, Signature)>,
206}
207
208/// How do we match this link in queries?
209pub enum LinkMatch<S: Into<String>> {
210    /// Match all/any links.
211    Any,
212
213    /// Match exactly by string.
214    Exactly(S),
215
216    /// Match by regular expression.
217    Regex(S),
218}
219
220impl<S: Into<String>> LinkMatch<S> {
221    /// Build a regular expression string for this link match.
222    #[allow(clippy::wrong_self_convention)]
223    pub fn to_regex_string(self) -> Result<String, String> {
224        let re_string: String = match self {
225            LinkMatch::Any => ".*".into(),
226            LinkMatch::Exactly(s) => "^".to_owned() + &regex::escape(&s.into()) + "$",
227            LinkMatch::Regex(s) => s.into(),
228        };
229        // check that it is a valid regex
230        match Regex::new(&re_string) {
231            Ok(_) => Ok(re_string),
232            Err(_) => Err("Invalid regex passed to get_links".into()),
233        }
234    }
235}
236
237/// Query for links to be sent over the network.
238#[derive(serde::Serialize, serde::Deserialize, SerializedBytes, PartialEq, Clone, Debug)]
239pub struct WireLinkQuery {
240    /// The base to find links from.
241    pub base: AnyLinkableHash,
242
243    /// Filter by the link type.
244    pub link_type: LinkTypeFilter,
245
246    /// Filter by tag prefix.
247    pub tag_prefix: Option<LinkTag>,
248
249    /// Only include links created before this time.
250    pub before: Option<Timestamp>,
251
252    /// Only include links created after this time.
253    pub after: Option<Timestamp>,
254
255    /// Only include links created by this author.
256    pub author: Option<AgentPubKey>,
257}
258
259/// Response type for a `WireLinkQuery`.
260#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SerializedBytes)]
261pub struct CountLinksResponse(Vec<ActionHash>);
262
263impl CountLinksResponse {
264    /// Create a new response from the action hashes of the matched links
265    pub fn new(create_link_actions: Vec<ActionHash>) -> Self {
266        CountLinksResponse(create_link_actions)
267    }
268
269    /// Get the action hashes of the matched links
270    pub fn create_link_actions(&self) -> Vec<ActionHash> {
271        self.0.clone()
272    }
273}