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