1pub use sn_interface::types::register::{Entry, EntryHash};
10
11use crate::safeurl::{ContentType, SafeUrl, XorUrl};
12use crate::{Error, Result, Safe};
13
14use sn_client::Error as ClientError;
15use sn_interface::{
16 messaging::data::Error as ErrorMsg,
17 types::{
18 register::{Permissions, Policy, User},
19 DataAddress, Error as SafeNdError, RegisterAddress,
20 },
21};
22
23use rand::Rng;
24use std::collections::{BTreeMap, BTreeSet};
25use tracing::{debug, info};
26use xor_name::XorName;
27
28impl Safe {
29 pub async fn register_create(
32 &self,
33 name: Option<XorName>,
34 tag: u64,
35 content_type: ContentType,
36 ) -> Result<XorUrl> {
37 debug!(
38 "Storing Register data with tag type: {}, xorname: {:?}, dry_run: {}",
39 tag, name, self.dry_run_mode
40 );
41
42 let xorname = name.unwrap_or_else(xor_name::rand::random);
43 info!("Xorname for new Register storage: {:?}", &xorname);
44
45 let xorurl = SafeUrl::from_register(xorname, tag, content_type)?.encode(self.xorurl_base);
46
47 if self.dry_run_mode {
49 return Ok(xorurl);
50 }
51
52 let client = self.get_safe_client()?;
54 let owner = User::Key(client.public_key());
55
56 let (_, op_batch) = client
58 .create_register(xorname, tag, policy(owner))
59 .await
60 .map_err(|e| {
61 Error::NetDataError(format!("Failed to prepare store Register operation: {e:?}",))
62 })?;
63
64 client.publish_register_ops(op_batch).await?;
65
66 Ok(xorurl)
67 }
68
69 pub async fn register_read(&self, url: &str) -> Result<BTreeSet<(EntryHash, Entry)>> {
71 debug!("Getting Register data from: {:?}", url);
72 let safeurl = self.parse_and_resolve_url(url).await?;
73
74 self.register_fetch_entries(&safeurl).await
75 }
76
77 pub async fn register_read_entry(&self, url: &str, hash: EntryHash) -> Result<Entry> {
79 debug!("Getting Register data from: {:?}", url);
80 let safeurl = self.parse_and_resolve_url(url).await?;
81
82 self.register_fetch_entry(&safeurl, hash).await
83 }
84
85 pub(crate) async fn register_fetch_entries(
89 &self,
90 url: &SafeUrl,
91 ) -> Result<BTreeSet<(EntryHash, Entry)>> {
92 debug!("Fetching Register entries from {}", url);
93 let result = match url.content_version() {
94 Some(v) => {
95 let hash = v.entry_hash();
96 debug!("Take entry with version hash: {:?}", hash);
97 self.register_fetch_entry(url, hash)
98 .await
99 .map(|entry| vec![(hash, entry)].into_iter().collect())
100 }
101 None => {
102 debug!("No version so take latest entry from Register at: {}", url);
103 let address = self.get_register_address(url)?;
104 let client = self.get_safe_client()?;
105 match client.read_register(address).await {
106 Ok(entry) => Ok(entry),
107 Err(ClientError::NetworkDataError(SafeNdError::NoSuchEntry(_))) => Err(
108 Error::EmptyContent(format!("Empty Register found at \"{url}\"")),
109 ),
110 Err(ClientError::ErrorMsg {
111 source: ErrorMsg::AccessDenied(_),
112 ..
113 }) => Err(Error::AccessDenied(format!(
114 "Couldn't read entry from Register found at \"{url}\"",
115 ))),
116 Err(err) => Err(Error::NetDataError(format!(
117 "Failed to read latest value from Register data: {err:?}",
118 ))),
119 }
120 }
121 };
122
123 match result {
124 Ok(data) => {
125 debug!("Register retrieved from {}...", url);
126 Ok(data)
127 }
128 Err(Error::EmptyContent(_)) => Err(Error::EmptyContent(format!(
129 "Register found at \"{url}\" was empty",
130 ))),
131 Err(Error::ContentNotFound(_)) => Err(Error::ContentNotFound(format!(
132 "No Register found at \"{url}\"",
133 ))),
134 other_err => other_err,
135 }
136 }
137
138 pub(crate) async fn register_fetch_entry(
140 &self,
141 url: &SafeUrl,
142 hash: EntryHash,
143 ) -> Result<Entry> {
144 let address = self.get_register_address(url)?;
147 let client = self.get_safe_client()?;
148 client
149 .get_register_entry(address, hash)
150 .await
151 .map_err(|err| {
152 if let ClientError::ErrorMsg {
153 source: sn_interface::messaging::data::Error::NoSuchEntry(_),
154 ..
155 } = err
156 {
157 Error::HashNotFound(hash)
158 } else {
159 Error::NetDataError(format!(
160 "Failed to retrieve entry with hash '{}' from Register data: {err:?}",
161 hex::encode(hash.0),
162 ))
163 }
164 })
165 }
166
167 pub async fn register_write(
169 &self,
170 url: &str,
171 entry: Entry,
172 parents: BTreeSet<EntryHash>,
173 ) -> Result<EntryHash> {
174 let reg_url = self.parse_and_resolve_url(url).await?;
175 let address = self.get_register_address(®_url)?;
176 if self.dry_run_mode {
177 return Ok(EntryHash(rand::thread_rng().gen::<[u8; 32]>()));
178 }
179
180 let client = self.get_safe_client()?;
181 let (entry_hash, op_batch) = match client
182 .write_to_local_register(address, entry, parents)
183 .await
184 {
185 Ok(data) => data,
186 Err(
187 ClientError::NetworkDataError(SafeNdError::AccessDenied(_))
188 | ClientError::ErrorMsg {
189 source: ErrorMsg::AccessDenied(_),
190 ..
191 },
192 ) => {
193 return Err(Error::AccessDenied(format!(
194 "Couldn't write data on Register found at \"{url}\"",
195 )));
196 }
197 Err(err) => {
198 return Err(Error::NetDataError(format!(
199 "Failed to write data on Register: {err:?}"
200 )));
201 }
202 };
203
204 client.publish_register_ops(op_batch).await?;
205
206 Ok(entry_hash)
207 }
208
209 pub(crate) fn get_register_address(&self, url: &SafeUrl) -> Result<RegisterAddress> {
210 let address = match url.address() {
211 DataAddress::Register(reg_address) => reg_address,
212 other => {
213 return Err(Error::ContentError(format!(
214 "The url {url} has an {other:?} address. \
215 To fetch register entries, this url must refer to a register.",
216 )))
217 }
218 };
219 Ok(address)
220 }
221}
222
223fn policy(owner: User) -> Policy {
224 let mut permissions = BTreeMap::new();
225 let _ = permissions.insert(owner, Permissions::new(true));
226 Policy { owner, permissions }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::{app::test_helpers::new_safe_instance, ContentType, Error};
232 use anyhow::{bail, Result};
233
234 #[tokio::test]
235 async fn test_register_create() -> Result<()> {
236 let safe = new_safe_instance().await?;
237
238 let xorurl = safe.register_create(None, 25_000, ContentType::Raw).await?;
239
240 let received_data = safe.register_read(&xorurl).await?;
241
242 assert!(received_data.is_empty());
243
244 let initial_data = "initial data bytes".as_bytes().to_vec();
245 let hash = safe
246 .register_write(&xorurl, initial_data.clone(), Default::default())
247 .await?;
248
249 let received_entry = safe.register_read_entry(&xorurl, hash).await?;
250
251 assert_eq!(received_entry, initial_data.clone());
252
253 Ok(())
254 }
255
256 #[tokio::test]
257 async fn test_register_owner_permissions() -> Result<()> {
258 let safe = new_safe_instance().await?;
259
260 let xorname = xor_name::rand::random();
261
262 let xorurl = safe
263 .register_create(Some(xorname), 25_000, ContentType::Raw)
264 .await?;
265
266 let received_data = safe.register_read(&xorurl).await?;
267
268 assert!(received_data.is_empty());
269
270 let safe = new_safe_instance().await?;
272
273 match safe
274 .register_write(&xorurl, b"dummy-pub-data".to_vec(), Default::default())
275 .await
276 {
277 Err(Error::AccessDenied(msg)) => {
278 assert_eq!(
279 msg,
280 format!("Couldn't write data on Register found at \"{xorurl}\"")
281 );
282 }
283 Err(err) => bail!("Error returned is not the expected: {:?}", err),
284 Ok(_) => bail!("Creation of Register succeeded unexpectedly".to_string()),
285 }
286
287 let _ = safe.register_read(&xorurl).await?;
289 Ok(())
290 }
291}