1use crate::backport::*;
2use crate::error::{ErrorKind, Position};
3use crate::identifier::Identifier;
4use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
5use core::str::FromStr;
6
7pub struct Error {
22 pub(crate) kind: ErrorKind,
23}
24
25impl FromStr for Version {
26 type Err = Error;
27
28 fn from_str(text: &str) -> Result<Self, Self::Err> {
29 if text.is_empty() {
30 return Err(Error::new(ErrorKind::Empty));
31 }
32
33 let mut pos = Position::Major;
34 let (major, text) = numeric_identifier(text, pos)?;
35 let text = dot(text, pos)?;
36
37 pos = Position::Minor;
38 let (minor, text) = numeric_identifier(text, pos)?;
39 let text = dot(text, pos)?;
40
41 pos = Position::Patch;
42 let (patch, text) = numeric_identifier(text, pos)?;
43
44 if text.is_empty() {
45 return Ok(Version::new(major, minor, patch));
46 }
47
48 let (pre, text) = if let Some(text) = text.strip_prefix('-') {
49 pos = Position::Pre;
50 let (pre, text) = prerelease_identifier(text)?;
51 if pre.is_empty() {
52 return Err(Error::new(ErrorKind::EmptySegment(pos)));
53 }
54 (pre, text)
55 } else {
56 (Prerelease::EMPTY, text)
57 };
58
59 let (build, text) = if let Some(text) = text.strip_prefix('+') {
60 pos = Position::Build;
61 let (build, text) = build_identifier(text)?;
62 if build.is_empty() {
63 return Err(Error::new(ErrorKind::EmptySegment(pos)));
64 }
65 (build, text)
66 } else {
67 (BuildMetadata::EMPTY, text)
68 };
69
70 if let Some(unexpected) = text.chars().next() {
71 return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
72 }
73
74 Ok(Version {
75 major,
76 minor,
77 patch,
78 pre,
79 build,
80 })
81 }
82}
83
84impl FromStr for VersionReq {
85 type Err = Error;
86
87 fn from_str(text: &str) -> Result<Self, Self::Err> {
88 let text = text.trim_start_matches(' ');
89 if let Some((ch, text)) = wildcard(text) {
90 let rest = text.trim_start_matches(' ');
91 if rest.is_empty() {
92 #[cfg(not(no_const_vec_new))]
93 return Ok(VersionReq::STAR);
94 #[cfg(no_const_vec_new)] return Ok(VersionReq {
96 comparators: Vec::new(),
97 });
98 } else if rest.starts_with(',') {
99 return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
100 } else {
101 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
102 }
103 }
104
105 let depth = 0;
106 let mut comparators = Vec::new();
107 let len = version_req(text, &mut comparators, depth)?;
108 unsafe { comparators.set_len(len) }
109 Ok(VersionReq { comparators })
110 }
111}
112
113impl FromStr for Comparator {
114 type Err = Error;
115
116 fn from_str(text: &str) -> Result<Self, Self::Err> {
117 let text = text.trim_start_matches(' ');
118 let (comparator, pos, rest) = comparator(text)?;
119 if !rest.is_empty() {
120 let unexpected = rest.chars().next().unwrap();
121 return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
122 }
123 Ok(comparator)
124 }
125}
126
127impl FromStr for Prerelease {
128 type Err = Error;
129
130 fn from_str(text: &str) -> Result<Self, Self::Err> {
131 let (pre, rest) = prerelease_identifier(text)?;
132 if !rest.is_empty() {
133 return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre)));
134 }
135 Ok(pre)
136 }
137}
138
139impl FromStr for BuildMetadata {
140 type Err = Error;
141
142 fn from_str(text: &str) -> Result<Self, Self::Err> {
143 let (build, rest) = build_identifier(text)?;
144 if !rest.is_empty() {
145 return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build)));
146 }
147 Ok(build)
148 }
149}
150
151impl Error {
152 fn new(kind: ErrorKind) -> Self {
153 Error { kind }
154 }
155}
156
157impl Op {
158 const DEFAULT: Self = Op::Exact;
159}
160
161fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
162 let mut len = 0;
163 let mut value = 0u64;
164
165 while let Some(&digit) = input.as_bytes().get(len) {
166 if digit < b'0' || digit > b'9' {
167 break;
168 }
169 if value == 0 && len > 0 {
170 return Err(Error::new(ErrorKind::LeadingZero(pos)));
171 }
172 match value
173 .checked_mul(10)
174 .and_then(|value| value.checked_add((digit - b'0') as u64))
175 {
176 Some(sum) => value = sum,
177 None => return Err(Error::new(ErrorKind::Overflow(pos))),
178 }
179 len += 1;
180 }
181
182 if len > 0 {
183 Ok((value, &input[len..]))
184 } else if let Some(unexpected) = input[len..].chars().next() {
185 Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
186 } else {
187 Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
188 }
189}
190
191fn wildcard(input: &str) -> Option<(char, &str)> {
192 if let Some(rest) = input.strip_prefix('*') {
193 Some(('*', rest))
194 } else if let Some(rest) = input.strip_prefix('x') {
195 Some(('x', rest))
196 } else if let Some(rest) = input.strip_prefix('X') {
197 Some(('X', rest))
198 } else {
199 None
200 }
201}
202
203fn dot(input: &str, pos: Position) -> Result<&str, Error> {
204 if let Some(rest) = input.strip_prefix('.') {
205 Ok(rest)
206 } else if let Some(unexpected) = input.chars().next() {
207 Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)))
208 } else {
209 Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
210 }
211}
212
213fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
214 let (string, rest) = identifier(input, Position::Pre)?;
215 let identifier = unsafe { Identifier::new_unchecked(string) };
216 Ok((Prerelease { identifier }, rest))
217}
218
219fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
220 let (string, rest) = identifier(input, Position::Build)?;
221 let identifier = unsafe { Identifier::new_unchecked(string) };
222 Ok((BuildMetadata { identifier }, rest))
223}
224
225fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
226 let mut accumulated_len = 0;
227 let mut segment_len = 0;
228 let mut segment_has_nondigit = false;
229
230 loop {
231 match input.as_bytes().get(accumulated_len + segment_len) {
232 Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
233 segment_len += 1;
234 segment_has_nondigit = true;
235 }
236 Some(b'0'..=b'9') => {
237 segment_len += 1;
238 }
239 boundary => {
240 if segment_len == 0 {
241 if accumulated_len == 0 && boundary != Some(&b'.') {
242 return Ok(("", input));
243 } else {
244 return Err(Error::new(ErrorKind::EmptySegment(pos)));
245 }
246 }
247 if pos == Position::Pre
248 && segment_len > 1
249 && !segment_has_nondigit
250 && input[accumulated_len..].starts_with('0')
251 {
252 return Err(Error::new(ErrorKind::LeadingZero(pos)));
253 }
254 accumulated_len += segment_len;
255 if boundary == Some(&b'.') {
256 accumulated_len += 1;
257 segment_len = 0;
258 segment_has_nondigit = false;
259 } else {
260 return Ok(input.split_at(accumulated_len));
261 }
262 }
263 }
264 }
265}
266
267fn op(input: &str) -> (Op, &str) {
268 let bytes = input.as_bytes();
269 if bytes.first() == Some(&b'=') {
270 (Op::Exact, &input[1..])
271 } else if bytes.first() == Some(&b'>') {
272 if bytes.get(1) == Some(&b'=') {
273 (Op::GreaterEq, &input[2..])
274 } else {
275 (Op::Greater, &input[1..])
276 }
277 } else if bytes.first() == Some(&b'<') {
278 if bytes.get(1) == Some(&b'=') {
279 (Op::LessEq, &input[2..])
280 } else {
281 (Op::Less, &input[1..])
282 }
283 } else if bytes.first() == Some(&b'~') {
284 if bytes.get(1) == Some(&b'>') {
285 (Op::Tilde, &input[2..])
286 } else {
287 (Op::Tilde, &input[1..])
288 }
289 } else if bytes.first() == Some(&b'^') {
290 (Op::Caret, &input[1..])
291 } else {
292 (Op::DEFAULT, input)
293 }
294}
295
296fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
297 let (mut op, text) = op(input);
298 let default_op = input.len() == text.len();
299 let text = text.trim_start_matches(' ');
300
301 let mut pos = Position::Major;
302 let (major, text) = numeric_identifier(text, pos)?;
303 let mut has_wildcard = false;
304
305 let (minor, text) = if let Some(text) = text.strip_prefix('.') {
306 pos = Position::Minor;
307 if let Some((_, text)) = wildcard(text) {
308 has_wildcard = true;
309 if default_op {
310 op = Op::Wildcard;
311 }
312 (None, text)
313 } else {
314 let (minor, text) = numeric_identifier(text, pos)?;
315 (Some(minor), text)
316 }
317 } else {
318 (None, text)
319 };
320
321 let (patch, text) = if let Some(text) = text.strip_prefix('.') {
322 pos = Position::Patch;
323 if let Some((_, text)) = wildcard(text) {
324 if default_op {
325 op = Op::Wildcard;
326 }
327 (None, text)
328 } else if has_wildcard {
329 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
330 } else {
331 let (patch, text) = numeric_identifier(text, pos)?;
332 (Some(patch), text)
333 }
334 } else {
335 (None, text)
336 };
337
338 let (pre, text) = if patch.is_some() && text.starts_with('-') {
339 pos = Position::Pre;
340 let text = &text[1..];
341 let (pre, text) = prerelease_identifier(text)?;
342 if pre.is_empty() {
343 return Err(Error::new(ErrorKind::EmptySegment(pos)));
344 }
345 (pre, text)
346 } else {
347 (Prerelease::EMPTY, text)
348 };
349
350 let text = if patch.is_some() && text.starts_with('+') {
351 pos = Position::Build;
352 let text = &text[1..];
353 let (build, text) = build_identifier(text)?;
354 if build.is_empty() {
355 return Err(Error::new(ErrorKind::EmptySegment(pos)));
356 }
357 text
358 } else {
359 text
360 };
361
362 let text = text.trim_start_matches(' ');
363
364 let comparator = Comparator {
365 op,
366 major,
367 minor,
368 patch,
369 pre,
370 };
371
372 Ok((comparator, pos, text))
373}
374
375fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
376 let (comparator, pos, text) = match comparator(input) {
377 Ok(success) => success,
378 Err(mut error) => {
379 if let Some((ch, mut rest)) = wildcard(input) {
380 rest = rest.trim_start_matches(' ');
381 if rest.is_empty() || rest.starts_with(',') {
382 error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
383 }
384 }
385 return Err(error);
386 }
387 };
388
389 if text.is_empty() {
390 out.reserve_exact(depth + 1);
391 unsafe { out.as_mut_ptr().add(depth).write(comparator) }
392 return Ok(depth + 1);
393 }
394
395 let text = if let Some(text) = text.strip_prefix(',') {
396 text.trim_start_matches(' ')
397 } else {
398 let unexpected = text.chars().next().unwrap();
399 return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
400 };
401
402 const MAX_COMPARATORS: usize = 32;
403 if depth + 1 == MAX_COMPARATORS {
404 return Err(Error::new(ErrorKind::ExcessiveComparators));
405 }
406
407 let len = version_req(text, out, depth + 1)?;
411 unsafe { out.as_mut_ptr().add(depth).write(comparator) }
412 Ok(len)
413}