1use std::{fmt, str::FromStr};
2
3use anyhow::{bail, Result};
4use url::Url;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct MetadataUri(Option<Url>);
8
9impl MetadataUri {
10 pub fn empty() -> Self {
11 Self(None)
12 }
13}
14
15impl TryFrom<Url> for MetadataUri {
16 type Error = anyhow::Error;
17
18 fn try_from(url: Url) -> Result<Self> {
19 if url.as_str().len() > 2048 {
20 bail!("metadata URI cannot exceed 2048 bytes");
21 }
22 Ok(Self(Some(url)))
23 }
24}
25
26impl FromStr for MetadataUri {
27 type Err = anyhow::Error;
28
29 fn from_str(s: &str) -> Result<Self> {
30 let url = Url::parse(s)?;
31 MetadataUri::try_from(url)
32 }
33}
34
35impl fmt::Display for MetadataUri {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match &self.0 {
38 Some(url) => write!(f, "{}", url),
39 None => Ok(()),
40 }
41 }
42}
43
44#[cfg(test)]
45mod test {
46 use super::*;
47
48 #[test]
49 fn test_empty_metadata_uri() {
50 let empty = MetadataUri::empty();
51 assert_eq!(empty.to_string(), "");
52 }
53
54 #[test]
55 fn test_valid_metadata_uri() -> Result<()> {
56 let uri: MetadataUri = "https://example.com/metadata".parse()?;
57 assert_eq!(uri.to_string(), "https://example.com/metadata");
58 Ok(())
59 }
60
61 #[test]
62 fn test_metadata_uri_max_length() -> Result<()> {
63 let long_path = "a".repeat(2000);
64 let url_str = format!("https://example.com/{}", long_path);
65 let uri: MetadataUri = url_str.parse()?;
66 assert_eq!(uri.to_string(), url_str);
67 Ok(())
68 }
69
70 #[test]
71 fn test_metadata_uri_too_long() {
72 let long_path = "a".repeat(2100);
73 let url_str = format!("https://example.com/{}", long_path);
74 let result = url_str.parse::<MetadataUri>();
75 assert!(result.is_err());
76 assert!(result
77 .unwrap_err()
78 .to_string()
79 .contains("cannot exceed 2048 bytes"));
80 }
81}