p2panda_core/extensions.rs
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3//! Traits required for defining custom extensions.
4//!
5//! User-defined extensions can be added to an operation's `Header` in order to extend the basic
6//! functionality of the core p2panda data types or to encode application-specific fields which
7//! should not be contained in the [`Body`](crate::Body).
8//!
9//! At a lower level this might be information relating to capabilities or group encryption schemes
10//! which is required to enforce access-control restrictions during sync. Alternatively, extensions
11//! might be used to set expiration timestamps and deletion flags in order to facilitate garbage
12//! collection of stale data from the network. The core p2panda data types intentionally don't
13//! enforce a single approach to such areas where there are rightly many different approaches, with
14//! the most suitable being dependent on specific use-case requirements.
15//!
16//! Interfaces which use p2panda core data types can require certain extensions to be present on
17//! any headers that their APIs accept using trait bounds. `p2panda-stream`, for example, uses the
18//! [`PruneFlag`](crate::PruneFlag) in order to implement automatic network-wide garbage
19//! collection.
20//!
21//! Extensions are encoded on a header and sent over the wire. We need to satisfy all trait
22//! requirements that `Header` requires, including `Serialize` and `Deserialize`.
23//!
24//! ## Example
25//!
26//! ```
27//! use p2panda_core::{Body, Extension, Header, Operation, PrivateKey, PruneFlag};
28//! use serde::{Serialize, Deserialize};
29//!
30//! // Extend our operations with an "expiry" field we can use to implement "ephemeral messages" in
31//! // our application, which get automatically deleted after the expiration timestamp is due.
32//! #[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
33//! pub struct Expiry(u64);
34//!
35//! // Multiple extensions can be combined in a custom type.
36//! #[derive(Clone, Debug, Default, Serialize, Deserialize)]
37//! struct CustomExtensions {
38//!     expiry: Expiry,
39//! }
40//!
41//! // Implement `Extension<T>` for each extension we want to add to our `CustomExtensions`.
42//! impl Extension<Expiry> for CustomExtensions {
43//!     fn extract(&self) -> Option<Expiry> {
44//!         Some(self.expiry.to_owned())
45//!     }
46//! }
47//!
48//! // Create a custom extension instance, this can be added to an operation's header.
49//! let extensions = CustomExtensions {
50//!     expiry: Expiry(1733170246),
51//! };
52//!
53//! // Extract the extension we are interested in.
54//! let expiry: Expiry = extensions.extract().expect("expiry field should be set");
55//! ```
56use std::fmt::Debug;
57
58use serde::{Deserialize, Serialize};
59
60use crate::Header;
61
62/// Trait definition of a single header extension type.
63pub trait Extension<T>: Extensions {
64    /// Extract the value of an extension based on it's type.
65    fn extract(&self) -> Option<T> {
66        None
67    }
68}
69
70/// Super-trait defining trait bounds required by custom extensions types.
71pub trait Extensions: Clone + Debug + for<'de> Deserialize<'de> + Serialize {}
72
73/// Blanket implementation of `Extensions` trait any type with the required bounds satisfied.
74impl<T> Extensions for T where T: Clone + Debug + for<'de> Deserialize<'de> + Serialize {}
75
76/// Generic implementation of `Extension<T>` for `Header<E>` allowing access to the extension
77/// values.
78impl<T, E> Extension<T> for Header<E>
79where
80    E: Extension<T>,
81{
82    fn extract(&self) -> Option<T> {
83        match &self.extensions {
84            Some(extensions) => Extension::<T>::extract(extensions),
85            None => None,
86        }
87    }
88}