nifty_asset_types/extensions/
links.rs

1use podded::types::{U8PrefixStr, U8PrefixStrMut};
2use std::{fmt::Debug, ops::Deref};
3
4use super::{
5    ExtensionBuilder, ExtensionData, ExtensionDataMut, ExtensionType, Lifecycle, DEFAULT_CAPACITY,
6};
7
8/// Extension to add external links.
9///
10/// Links are used to attach external (off-chain) resources to an asset. They are
11/// specified as name-uri pair of strings.
12///   * `name` - name of the link.
13///   * `uri` - URI value.
14pub struct Links<'a> {
15    values: Vec<Link<'a>>,
16}
17
18impl<'a> Deref for Links<'a> {
19    type Target = Vec<Link<'a>>;
20
21    fn deref(&self) -> &Self::Target {
22        &self.values
23    }
24}
25
26impl<'a> ExtensionData<'a> for Links<'a> {
27    const TYPE: ExtensionType = ExtensionType::Links;
28
29    fn from_bytes(bytes: &'a [u8]) -> Self {
30        let mut cursor = 0;
31        let mut values = Vec::with_capacity(DEFAULT_CAPACITY);
32
33        while cursor < bytes.len() {
34            let link = Link::from_bytes(&bytes[cursor..]);
35            cursor += link.length();
36            values.push(link);
37        }
38        Self { values }
39    }
40
41    fn length(&self) -> usize {
42        self.values.iter().map(|link| link.length()).sum()
43    }
44}
45
46impl Debug for Links<'_> {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("Links")
49            .field("values", &self.values)
50            .finish()
51    }
52}
53
54/// Link information.
55pub struct Link<'a> {
56    /// Name of the link.
57    pub name: U8PrefixStr<'a>,
58
59    /// URI value.
60    pub uri: U8PrefixStr<'a>,
61}
62
63impl<'a> Link<'a> {
64    pub fn from_bytes(bytes: &'a [u8]) -> Self {
65        let name = U8PrefixStr::from_bytes(bytes);
66        let uri = U8PrefixStr::from_bytes(&bytes[name.size()..]);
67        Self { name, uri }
68    }
69
70    pub fn length(&self) -> usize {
71        self.name.size() + self.uri.size()
72    }
73}
74
75impl Debug for Link<'_> {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.debug_struct("Link")
78            .field("name", &self.name.as_str())
79            .field("uri", &self.uri.as_str())
80            .finish()
81    }
82}
83
84pub struct LinksMut<'a> {
85    pub values: Vec<LinkMut<'a>>,
86}
87
88impl<'a> Deref for LinksMut<'a> {
89    type Target = Vec<LinkMut<'a>>;
90
91    fn deref(&self) -> &Self::Target {
92        &self.values
93    }
94}
95
96impl<'a> ExtensionDataMut<'a> for LinksMut<'a> {
97    const TYPE: ExtensionType = ExtensionType::Links;
98
99    fn from_bytes_mut(bytes: &'a mut [u8]) -> Self {
100        let mut values = Vec::with_capacity(DEFAULT_CAPACITY);
101        // mutable reference to the current bytes
102        let mut bytes = bytes;
103
104        while !bytes.is_empty() {
105            let link = Link::from_bytes(bytes);
106            let cursor = link.length();
107
108            let (current, remainder) = bytes.split_at_mut(cursor);
109            let link = LinkMut::from_bytes_mut(current);
110            bytes = remainder;
111
112            values.push(link);
113        }
114        Self { values }
115    }
116}
117
118impl Lifecycle for LinksMut<'_> {}
119
120pub struct LinkMut<'a> {
121    /// Name of the link.
122    pub name: U8PrefixStrMut<'a>,
123
124    /// URI value.
125    pub uri: U8PrefixStrMut<'a>,
126}
127
128impl<'a> LinkMut<'a> {
129    pub fn from_bytes_mut(bytes: &'a mut [u8]) -> Self {
130        let name = U8PrefixStr::from_bytes(bytes);
131        let name_size = name.size();
132
133        let (name, uri) = bytes.split_at_mut(name_size);
134
135        let name = U8PrefixStrMut::from_bytes_mut(name);
136        let uri = U8PrefixStrMut::from_bytes_mut(uri);
137        Self { name, uri }
138    }
139
140    pub fn length(&self) -> usize {
141        self.name.size() + self.uri.size()
142    }
143}
144
145/// Builder for a `Links` extension.
146#[derive(Default)]
147pub struct LinksBuilder(Vec<u8>);
148
149impl LinksBuilder {
150    pub fn with_capacity(capacity: usize) -> Self {
151        Self(Vec::with_capacity(capacity))
152    }
153
154    pub fn with_buffer(buffer: Vec<u8>) -> Self {
155        let mut s = Self(buffer);
156        s.0.clear();
157        s
158    }
159
160    /// Add a new attribute to the extension.
161    pub fn add(&mut self, name: &str, uri: &str) -> &mut Self {
162        // add the length of the name + prefix to the data buffer.
163        let cursor = self.0.len();
164        self.0.append(&mut vec![0u8; name.len() + 1]);
165        let mut name_str = U8PrefixStrMut::new(&mut self.0[cursor..]);
166        name_str.copy_from_str(name);
167
168        // add the length of the value + prefix to the data buffer.
169        let cursor = self.0.len();
170        self.0.append(&mut vec![0u8; uri.len() + 1]);
171        let mut value_str = U8PrefixStrMut::new(&mut self.0[cursor..]);
172        value_str.copy_from_str(uri);
173
174        self
175    }
176}
177
178impl<'a> ExtensionBuilder<'a, Links<'a>> for LinksBuilder {
179    fn build(&'a self) -> Links<'a> {
180        Links::from_bytes(&self.0)
181    }
182
183    fn data(&mut self) -> Vec<u8> {
184        std::mem::take(&mut self.0)
185    }
186}
187
188impl Deref for LinksBuilder {
189    type Target = Vec<u8>;
190
191    fn deref(&self) -> &Self::Target {
192        &self.0
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use crate::extensions::{ExtensionBuilder, LinksBuilder};
199
200    #[test]
201    fn test_add() {
202        let mut builder = LinksBuilder::default();
203        builder.add(
204            "metadata",
205            "https://arweave.net/2eyYRZpFXeXrNyA17Y8QvSfQV9rNkzAqXZa7ko7MBNA",
206        );
207        builder.add(
208            "image",
209            "https://arweave.net/aFnc6QVyRR-gVx6pKYSFu0MiwijQzFdU4fMSuApJqms",
210        );
211        let links = builder.build();
212
213        assert_eq!(links.values.len(), 2);
214        assert_eq!(links.values[0].name.as_str(), "metadata");
215        assert_eq!(
216            links.values[0].uri.as_str(),
217            "https://arweave.net/2eyYRZpFXeXrNyA17Y8QvSfQV9rNkzAqXZa7ko7MBNA"
218        );
219        assert_eq!(links.values[1].name.as_str(), "image");
220        assert_eq!(
221            links.values[1].uri.as_str(),
222            "https://arweave.net/aFnc6QVyRR-gVx6pKYSFu0MiwijQzFdU4fMSuApJqms"
223        );
224    }
225}