media_type_version/
lib.rs1#![deny(missing_docs)]
2#![deny(clippy::missing_docs_in_private_items)]
3#![no_std]
4#![doc(html_root_url = "https://docs.rs/media-type-version/0.1.2")]
49#![expect(clippy::pub_use, reason = "re-export common symbols")]
50
51use core::str::FromStr as _;
52
53use log::debug;
54
55pub mod defs;
56
57pub use defs::{Config, Error, Version};
58
59#[inline]
71pub fn extract<'data>(
72 cfg: &'data Config<'data>,
73 value: &'data str,
74) -> Result<Version, Error<'data>> {
75 debug!(
76 "Parsing a media type string '{value}', expecting prefix '{prefix}' and suffix '{suffix}'",
77 prefix = cfg.prefix(),
78 suffix = cfg.suffix()
79 );
80 let no_prefix = value
81 .strip_prefix(cfg.prefix())
82 .ok_or_else(|| Error::NoPrefix(value, cfg.prefix()))?;
83 let no_suffix = no_prefix
84 .strip_suffix(cfg.suffix())
85 .ok_or_else(|| Error::NoSuffix(value, cfg.suffix()))?;
86 let no_vdot = no_suffix
87 .strip_prefix(".v")
88 .ok_or(Error::NoVDot(no_suffix))?;
89 let (first, second) = {
90 let mut parts_it = no_vdot.split('.');
91 let first = parts_it.next().ok_or(Error::TwoComponentsExpected(value))?;
92 let second = parts_it.next().ok_or(Error::TwoComponentsExpected(value))?;
93 if parts_it.next().is_some() {
94 return Err(Error::TwoComponentsExpected(value));
95 }
96 (first, second)
97 };
98 let major = u32::from_str(first).map_err(|err| Error::UIntExpected(value, first, err))?;
99 let minor = u32::from_str(second).map_err(|err| Error::UIntExpected(value, second, err))?;
100 Ok(Version::from((major, minor)))
101}
102
103#[cfg(test)]
104mod tests {
105 extern crate alloc;
106
107 use alloc::format;
108 use alloc::string::String;
109
110 use eyre::WrapErr as _;
111 use facet_testhelpers::test;
112
113 #[cfg(feature = "facet-unstable")]
114 use facet_pretty::FacetPretty as _;
115
116 use crate::{Config, Error, Version};
117
118 static CFG: Config<'_> = Config::from_parts("this/and", "+that");
120
121 #[cfg(feature = "facet-unstable")]
122 fn pretty_res(res: &Result<Version, Error<'_>>) -> String {
123 match *res {
124 Ok(ref ver) => format!("OK: {ver}", ver = ver.pretty()),
125 Err(ref err) => format!("Error: {err}"),
126 }
127 }
128
129 #[cfg(not(feature = "facet-unstable"))]
130 fn pretty_res(res: &Result<Version, Error<'_>>) -> String {
131 match *res {
132 Ok(ref ver) => format!(
133 "OK: Version {{ major: {major}, minor: {minor} }}",
134 major = ver.major(),
135 minor = ver.minor(),
136 ),
137 Err(ref err) => format!("Error: {err}"),
138 }
139 }
140
141 #[test]
143 fn extract_fail_no_prefix() {
144 let res = crate::extract(&CFG, "nothing");
145 assert!(
146 matches!(res, Err(Error::NoPrefix(_, _))),
147 "expected Error::NoPrefix, got {res}",
148 res = pretty_res(&res)
149 );
150 }
151
152 #[test]
154 fn extract_fail_no_suffix() {
155 let res = crate::extract(&CFG, "this/andnothing");
156 assert!(
157 matches!(res, Err(Error::NoSuffix(_, _))),
158 "expected Error::NoSuffix, got {res}",
159 res = pretty_res(&res)
160 );
161 }
162
163 #[test]
165 fn extract_fail_no_vdot() {
166 let res = crate::extract(&CFG, "this/andnothing+that");
167 assert!(
168 matches!(res, Err(Error::NoVDot(_))),
169 "expected Error::NoVDot, got {res}",
170 res = pretty_res(&res)
171 );
172 }
173
174 #[test]
176 fn extract_fail_two_expected() {
177 let res = crate::extract(&CFG, "this/and.vnothing+that");
178 assert!(
179 matches!(res, Err(Error::TwoComponentsExpected(_))),
180 "expected Error::TwoComponentsExpected, got {res}",
181 res = pretty_res(&res)
182 );
183 }
184
185 #[test]
187 fn extract_fail_uint_expected() {
188 let res_first = crate::extract(&CFG, "this/and.va.42+that");
189 assert!(
190 matches!(res_first, Err(Error::UIntExpected(_, _, _))),
191 "expected Error::UIntExpected, got {res_first}",
192 res_first = pretty_res(&res_first)
193 );
194
195 let res_second = crate::extract(&CFG, "this/and.v42.+that");
196 assert!(
197 matches!(res_second, Err(Error::UIntExpected(_, _, _))),
198 "expected Error::UIntExpected, got {res_second}",
199 res_second = pretty_res(&res_second)
200 );
201 }
202
203 #[test]
205 fn extract_ok() {
206 let ver = crate::extract(&CFG, "this/and.v616.42+that").context("extract")?;
207 assert_eq!(ver.as_tuple(), (616, 42));
208 }
209}