aws_types/
sdk_ua_metadata.rs1use aws_smithy_types::config_bag::{Storable, StoreAppend};
9use std::borrow::Cow;
10use std::error::Error;
11use std::fmt;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct FrameworkMetadata {
25 name: Cow<'static, str>,
26 version: Option<Cow<'static, str>>,
27}
28
29impl Storable for FrameworkMetadata {
30 type Storer = StoreAppend<Self>;
31}
32
33impl FrameworkMetadata {
34 pub fn new(
39 name: impl Into<Cow<'static, str>>,
40 version: Option<impl Into<Cow<'static, str>>>,
41 ) -> Result<Self, InvalidFrameworkMetadata> {
42 let name = name.into();
43 let version = version.map(Into::into);
44
45 if name.is_empty() {
46 return Err(InvalidFrameworkMetadata);
47 }
48 fn valid_character(c: char) -> bool {
51 match c {
52 _ if c.is_ascii_alphanumeric() => true,
53 '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`'
54 | '|' | '~' => true,
55 _ => false,
56 }
57 }
58 if !name.chars().all(valid_character) {
59 return Err(InvalidFrameworkMetadata);
60 }
61 if let Some(version) = &version {
62 if version.is_empty() {
63 return Err(InvalidFrameworkMetadata);
64 }
65 if !version.chars().all(valid_character) {
66 return Err(InvalidFrameworkMetadata);
67 }
68 }
69 Ok(Self { name, version })
70 }
71
72 pub fn name(&self) -> &str {
74 &self.name
75 }
76
77 pub fn version(&self) -> Option<&str> {
79 self.version.as_deref()
80 }
81}
82
83impl fmt::Display for FrameworkMetadata {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match &self.version {
87 Some(version) => write!(f, "lib/{}/{}", self.name, version),
88 None => write!(f, "lib/{}", self.name),
89 }
90 }
91}
92
93#[derive(Debug)]
97#[non_exhaustive]
98pub struct InvalidFrameworkMetadata;
99
100impl Error for InvalidFrameworkMetadata {}
101
102impl fmt::Display for InvalidFrameworkMetadata {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(
105 f,
106 "The framework metadata name and version can only have alphanumeric characters, or \
107 any of '!' | '#' | '$' | '%' | '&' | '\\'' | '*' | '+' | '-' | \
108 '.' | '^' | '_' | '`' | '|' | '~'"
109 )
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::FrameworkMetadata;
116
117 #[test]
118 fn valid_name_and_version() {
119 let md = FrameworkMetadata::new("some-framework", Some("1.0")).expect("valid");
120 assert_eq!("some-framework", md.name());
121 assert_eq!(Some("1.0"), md.version());
122 }
123
124 #[test]
125 fn valid_name_no_version() {
126 let md =
127 FrameworkMetadata::new("asdf1234ASDF!#$%&'*+-.^_`|~", None::<&str>).expect("valid");
128 assert_eq!(None, md.version());
129 }
130
131 #[test]
132 fn invalid_charset_name() {
133 assert!(FrameworkMetadata::new("foo bar", None::<&str>).is_err());
134 assert!(FrameworkMetadata::new("🚀", None::<&str>).is_err());
135 }
136
137 #[test]
138 fn invalid_charset_version() {
139 assert!(FrameworkMetadata::new("framework", Some("1 0")).is_err());
140 assert!(FrameworkMetadata::new("framework", Some("🚀")).is_err());
141 }
142
143 #[test]
144 fn empty_version_rejected() {
145 assert!(FrameworkMetadata::new("framework", Some("")).is_err());
146 }
147
148 #[test]
149 fn empty_name() {
150 assert!(FrameworkMetadata::new("", None::<&str>).is_err());
151 }
152
153 #[test]
154 fn rejects_header_injection_characters() {
155 for bad in [
157 "a\r\nb", "a\nb", "a\rb", "a\tb", "a b", "a/b", "a\u{0}b", "a\u{7f}b", ] {
166 assert!(
167 FrameworkMetadata::new(bad, None::<&str>).is_err(),
168 "name {bad:?} should be rejected"
169 );
170 assert!(
171 FrameworkMetadata::new("framework", Some(bad)).is_err(),
172 "version {bad:?} should be rejected"
173 );
174 }
175 }
176
177 #[test]
178 fn accepts_every_allowed_symbol_in_version() {
179 let md =
180 FrameworkMetadata::new("framework", Some("1.0-rc.1+build_2~3")).expect("valid version");
181 assert_eq!(Some("1.0-rc.1+build_2~3"), md.version());
182 }
183
184 #[test]
185 fn clone_and_equality() {
186 let a = FrameworkMetadata::new("framework", Some("1.0")).unwrap();
187 let b = a.clone();
188 assert_eq!(a, b);
189 let c = FrameworkMetadata::new("framework", Some("2.0")).unwrap();
190 assert_ne!(a, c);
191 let d = FrameworkMetadata::new("framework", None::<&str>).unwrap();
192 assert_ne!(a, d);
193 }
194}