compose_lens/validation/
version.rs1use std::error::Error;
4use std::fmt;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct ImplementationVersion {
13 major: u32,
14 minor: u32,
15 patch: u32,
16}
17
18impl ImplementationVersion {
19 #[must_use]
21 pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
22 Self { major, minor, patch }
23 }
24
25 #[must_use]
27 pub const fn major(self) -> u32 {
28 self.major
29 }
30
31 #[must_use]
33 pub const fn minor(self) -> u32 {
34 self.minor
35 }
36
37 #[must_use]
39 pub const fn patch(self) -> u32 {
40 self.patch
41 }
42}
43
44impl fmt::Display for ImplementationVersion {
45 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
47 }
48}
49
50impl FromStr for ImplementationVersion {
51 type Err = VersionParseError;
52
53 fn from_str(value: &str) -> Result<Self, Self::Err> {
54 let value = value.strip_prefix('v').unwrap_or(value);
55 let mut components = value.split('.');
56 let major = parse_component(components.next())?;
57 let minor = parse_component(components.next())?;
58 let patch = parse_component(components.next())?;
59 if components.next().is_some() {
60 return Err(VersionParseError);
61 }
62 Ok(Self::new(major, minor, patch))
63 }
64}
65
66fn parse_component(component: Option<&str>) -> Result<u32, VersionParseError> {
67 let component = component
68 .filter(|component| !component.is_empty())
69 .ok_or(VersionParseError)?;
70 if !component.bytes().all(|byte| byte.is_ascii_digit()) {
71 return Err(VersionParseError);
72 }
73 component.parse().map_err(|_| VersionParseError)
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct VersionParseError;
82
83impl fmt::Display for VersionParseError {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter.write_str("expected a three-component numeric implementation version")
86 }
87}
88
89impl Error for VersionParseError {}
90
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
93pub struct VersionRange {
94 minimum: Option<ImplementationVersion>,
95 maximum: Option<ImplementationVersion>,
96}
97
98impl VersionRange {
99 #[must_use]
101 pub const fn unbounded() -> Self {
102 Self {
103 minimum: None,
104 maximum: None,
105 }
106 }
107
108 #[must_use]
110 pub const fn from_minimum(minimum: ImplementationVersion) -> Self {
111 Self {
112 minimum: Some(minimum),
113 maximum: None,
114 }
115 }
116
117 #[must_use]
119 pub const fn exact(version: ImplementationVersion) -> Self {
120 Self {
121 minimum: Some(version),
122 maximum: Some(version),
123 }
124 }
125
126 pub fn new(
133 minimum: Option<ImplementationVersion>,
134 maximum: Option<ImplementationVersion>,
135 ) -> Result<Self, InvalidVersionRange> {
136 if minimum.zip(maximum).is_some_and(|(minimum, maximum)| minimum > maximum) {
137 return Err(InvalidVersionRange);
138 }
139 Ok(Self { minimum, maximum })
140 }
141
142 #[must_use]
144 pub const fn minimum(self) -> Option<ImplementationVersion> {
145 self.minimum
146 }
147
148 #[must_use]
150 pub const fn maximum(self) -> Option<ImplementationVersion> {
151 self.maximum
152 }
153
154 #[must_use]
156 pub fn contains(self, version: ImplementationVersion) -> bool {
157 self.minimum.is_none_or(|minimum| version >= minimum) && self.maximum.is_none_or(|maximum| version <= maximum)
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub struct InvalidVersionRange;
164
165impl fmt::Display for InvalidVersionRange {
166 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
167 formatter.write_str("minimum implementation version is newer than maximum")
168 }
169}
170
171impl Error for InvalidVersionRange {}