version_number/parsers/modular/
parser.rs

1use super::component::{is_done, parse_component, parse_dot, peek_is_dot};
2use super::error::ModularParserError;
3use crate::{BaseVersion, FullVersion, Version};
4use std::iter::Peekable;
5use std::slice::Iter;
6
7// States
8
9/// A parser state of a _modular parser_.
10///
11/// This is the initial state.  
12#[derive(Debug)]
13pub struct Unparsed;
14
15/// A parser state of a _modular parser_.
16///
17/// When the parser has reached this state, a two component [`BaseVersion`]
18/// has been parsed, although no end-of-input check has taken place.
19///
20/// For example, given the hypothetical input `a.bc`, we are only aware that
21/// `a.b` is a valid two component version number, while the tokens `c` may be
22/// anything. In practice, the `c` tokens cannot be the complete set of available
23/// tokens because the component parsing implementation has to distinguish
24/// between the tokens in the component (i.e. the number) and those outside of
25/// it. This though, is only an implementation detail, and not a concern of the
26/// API.
27#[derive(Debug)]
28pub struct ParsedBase {
29    version: BaseVersion,
30}
31
32/// A parser state of a _modular parser_.
33///
34/// When the parser has reached this state, a three component [`FullVersion`]
35/// has been parsed, although no end-of-input check has taken place.
36///
37/// For example, given the hypothetical input `a.b.cd`, we are only aware that
38/// `a.b.c` is a valid three component version number, while the tokens `d` may
39/// be anything. In practice, the `d` tokens cannot be the complete set of
40/// available tokens because the component parsing implementation has to
41/// distinguish between the tokens in the component (i.e. the number)
42/// and those outside of it. This though, is only an implementation detail,
43/// and not a concern of the API.
44#[derive(Debug)]
45pub struct ParsedFull {
46    version: FullVersion,
47}
48
49/// A trait to restrict the state of the [`Parser`] to valid state instances.
50///
51/// Since this trait is public it can technically be implemented outside of this
52/// crate. Usually, this is not what you want though, although it may be useful
53/// for testing, which is why it's not sealed.
54pub trait ParsedState {}
55impl ParsedState for Unparsed {}
56impl ParsedState for ParsedBase {}
57impl ParsedState for ParsedFull {}
58
59// Parser
60
61/// A parser which may be used to parse a [`Version`] or its discriminants ([`BaseVersion`] and
62/// [`FullVersion`]), incrementally.
63#[derive(Debug)]
64pub struct Parser<'p, S: ParsedState> {
65    state: S,
66    iter: Peekable<Iter<'p, u8>>,
67}
68
69impl<'p> Parser<'p, Unparsed> {
70    /// Construct a parser from a byte slice.
71    ///
72    /// # Example
73    ///
74    /// ```
75    /// use version_number::parsers::modular::Parser;
76    /// let parser = Parser::from_slice("1.0.0".as_bytes());
77    /// ```
78    pub fn from_slice(bytes: &'p [u8]) -> Parser<'p, Unparsed> {
79        let iter = bytes.iter();
80
81        Parser {
82            state: Unparsed,
83            iter: iter.peekable(),
84        }
85    }
86}
87
88impl<'p> Parser<'p, Unparsed> {
89    /// Parse the base of a [`Version`]. The `base` are the `major` and `minor` components
90    /// of a version. An example of a `base` version which will parse, would be `1.2`.
91    ///
92    /// This method returns another [`Parser`] instance. To get the parsed version
93    /// after parsing the `base`, you may use [`Parser::finish`].
94    ///
95    /// In case you want to either parse a two or three component version, and you
96    /// don't care which one you have, you should use [`Parser::parse`] instead.
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// use version_number::BaseVersion;
102    /// use version_number::parsers::modular::{Parser};
103    ///
104    /// let parser = Parser::from_slice("1.2".as_bytes());
105    ///
106    /// let base = parser.parse_base().unwrap();
107    ///
108    /// assert_eq!(base.inner_version(), &BaseVersion::new(1, 2));
109    /// ```
110    pub fn parse_base(self) -> Result<Parser<'p, ParsedBase>, ModularParserError> {
111        let Self { mut iter, .. } = self;
112
113        let major = parse_component(iter.by_ref())?;
114        parse_dot(iter.by_ref())?;
115        let minor = parse_component(iter.by_ref())?;
116
117        let version = BaseVersion::new(major, minor);
118
119        Ok(Parser {
120            state: ParsedBase { version },
121            iter,
122        })
123    }
124
125    /// Parse a full, three component major, minor, patch [`Version`]. A two
126    /// component input, consisting of only the major and minor components, will be rejected.
127    ///
128    /// # Example
129    ///
130    /// ```
131    /// use version_number::{BaseVersion, FullVersion};
132    /// use version_number::parsers::modular::{Parser};
133    /// let parser = Parser::from_slice("1.2.3".as_bytes());
134    ///
135    /// let base = parser.parse_full().unwrap();
136    ///
137    /// assert_eq!(base.inner_version(), &FullVersion::new(1, 2, 3));
138    /// ```
139    pub fn parse_full(self) -> Result<Parser<'p, ParsedFull>, ModularParserError> {
140        let parser = self.parse_base()?;
141        parser.parse_patch()
142    }
143
144    /// Parse a `base`, two component `major.minor` [`Version`], or a `full`, three component `major.minor.patch`,
145    /// depending on the input.
146    ///
147    /// # Example 1
148    ///
149    /// ```
150    /// use version_number::{BaseVersion, FullVersion, Version};
151    /// use version_number::parsers::modular::Parser;
152    ///
153    /// let parser = Parser::from_slice("1.2".as_bytes());
154    ///
155    /// let version = parser.parse();
156    ///
157    /// assert_eq!(version.unwrap(), Version::Base(BaseVersion::new(1, 2)));
158    /// ```    
159    ///
160    /// # Example 2
161    ///
162    /// ```
163    /// use version_number::{FullVersion, Version};
164    /// use version_number::parsers::modular::Parser;
165    ///
166    /// let parser = Parser::from_slice("1.2.3".as_bytes());
167    ///
168    /// let version = parser.parse();
169    ///
170    /// assert_eq!(version.unwrap(), Version::Full(FullVersion::new(1, 2, 3)));
171    /// ```    
172    ///
173    /// # Example 3
174    ///
175    /// ```
176    /// use version_number::{FullVersion, Version};
177    /// use version_number::parsers::modular::Parser;
178    ///
179    /// let parser = Parser::from_slice("1.2.".as_bytes());
180    ///
181    /// let version = parser.parse();
182    ///
183    /// assert!(version.is_err());
184    /// ```
185    pub fn parse(self) -> Result<Version, ModularParserError> {
186        let mut parser = self.parse_base()?;
187
188        if peek_is_dot(parser.iter.by_ref()) {
189            parser.parse_patch()?.finish()
190        } else {
191            parser.finish()
192        }
193    }
194}
195
196impl<'p> Parser<'p, ParsedBase> {
197    /// Parse the patch component, to produce a [`FullVersion`].
198    ///
199    /// # Example
200    ///
201    /// ```
202    /// use version_number::{FullVersion};
203    /// use version_number::parsers::modular::Parser;
204    ///
205    /// let input = "1.2.3";
206    ///
207    /// let parser = Parser::from_slice(input.as_bytes());
208    /// let full = parser
209    ///     .parse_base()
210    ///     .unwrap()
211    ///     .parse_patch()
212    ///     .unwrap();
213    ///
214    /// assert_eq!(full.inner_version(), &FullVersion::new(1, 2, 3));
215    /// ```
216    pub fn parse_patch(self) -> Result<Parser<'p, ParsedFull>, ModularParserError> {
217        let Self {
218            mut iter,
219            state: ParsedBase {
220                version: BaseVersion { major, minor },
221            },
222        } = self;
223
224        parse_dot(iter.by_ref())?;
225        let patch = parse_component(iter.by_ref())?;
226
227        let version = FullVersion::new(major, minor, patch);
228
229        Ok(Parser {
230            state: ParsedFull { version },
231            iter,
232        })
233    }
234
235    /// Parses a `patch` component if it exists and returns a [`Version::Full`],
236    /// or if the input does not have a third component, returns the two component [`Version::Base`]
237    /// variant instead.
238    ///
239    /// Prefer [`Parser::parse`] over this method when possible, as this method clones the underlying
240    /// iterator to determine whether we do have additional content.
241    pub fn parse_patch_or_finish(self) -> Result<Version, ModularParserError> {
242        if peek_is_dot(self.iter.clone().by_ref()) {
243            self.finish()
244        } else {
245            self.parse_patch()?.finish()
246        }
247    }
248
249    /// Checks that there is no remaining input, and returns a [`Version`], which
250    /// wraps the parsed base version.
251    ///
252    /// When there is remaining input, this method will return a [`ModularParserError::ExpectedEOI`]
253    /// instead.
254    pub fn finish(self) -> Result<Version, ModularParserError> {
255        self.finish_base_version().map(Version::Base)
256    }
257
258    /// Checks that there is no remaining input, and returns a [`BaseVersion`].
259    ///
260    /// When there is remaining input, this method will return a [`ModularParserError::ExpectedEOI`]
261    /// instead.
262    pub fn finish_base_version(self) -> Result<BaseVersion, ModularParserError> {
263        let Self { mut iter, state } = self;
264
265        is_done(iter.by_ref())?;
266
267        Ok(state.version)
268    }
269
270    /// Returns the so far successfully parsed version state.
271    ///
272    /// **NB:** Unless the end of input has been reached, this version may not be valid.
273    pub fn inner_version(&self) -> &BaseVersion {
274        &self.state.version
275    }
276}
277
278impl<'p> Parser<'p, ParsedFull> {
279    /// Checks that there is no remaining input, and returns a [`Version`], which
280    /// wraps the parsed base version.
281    ///
282    /// When there is remaining input, this method will return a [`ModularParserError::ExpectedEOI`]
283    pub fn finish(self) -> Result<Version, ModularParserError> {
284        let Self { mut iter, state } = self;
285
286        is_done(iter.by_ref())?;
287
288        Ok(Version::Full(state.version))
289    }
290
291    /// Checks that there is no remaining input, and returns a [`FullVersion`].
292    ///
293    /// When there is remaining input, this method will return a [`ModularParserError::ExpectedEOI`]
294    /// instead.
295    pub fn finish_full_version(self) -> Result<FullVersion, ModularParserError> {
296        let Self { mut iter, state } = self;
297
298        is_done(iter.by_ref())?;
299
300        Ok(state.version)
301    }
302
303    /// Returns the so far successfully parsed version.
304    ///
305    /// **NB:** Unless the end of input has been reached, this version may not be valid.
306    pub fn inner_version(&self) -> &FullVersion {
307        &self.state.version
308    }
309}
310
311#[cfg(test)]
312mod tests_leading_zeros {
313    use super::*;
314    use crate::parsers::modular::NumberError;
315    use crate::BaseVersion;
316    use yare::parameterized;
317
318    #[test]
319    fn test() {}
320
321    #[parameterized(
322        zeroes = { "0.0", 0, 0 },
323        one = { "1.0", 1, 0 },
324        ones = { "1.1", 1, 1 },
325    )]
326    fn accepted(input: &str, major: u64, minor: u64) {
327        let input = input.as_bytes();
328        let parsed = Parser::from_slice(input)
329            .parse_base()
330            .and_then(|parser| parser.finish_base_version())
331            .unwrap();
332
333        assert_eq!(BaseVersion::new(major, minor), parsed);
334    }
335
336    #[parameterized(
337        no_leading_zero_component_0 = { "00.0", ModularParserError::NumberError(NumberError::LeadingZero) },
338        no_leading_zero_component_1 = { "01.0", ModularParserError::NumberError(NumberError::LeadingZero) },
339        no_leading_zero_component_2 = { "1.01", ModularParserError::NumberError(NumberError::LeadingZero) },
340    )]
341    fn rejected(input: &str, expected_err: ModularParserError) {
342        let input = input.as_bytes();
343        let err = Parser::from_slice(input)
344            .parse_base()
345            .and_then(|parser| parser.finish_base_version())
346            .unwrap_err();
347
348        assert_eq!(err, expected_err);
349    }
350}
351
352#[cfg(test)]
353mod tests_parser_base {
354    use super::*;
355    use crate::parsers::modular::NumberError;
356    use crate::BaseVersion;
357    use yare::parameterized;
358
359    #[test]
360    fn test() {}
361
362    #[parameterized(
363        zeroes = { "0.0", 0, 0 },
364        one = { "1.0", 1, 0 },
365        ones = { "1.1", 1, 1 },
366    )]
367    fn accepted(input: &str, major: u64, minor: u64) {
368        let parser = Parser::from_slice(input.as_bytes());
369
370        let base = parser.parse_base().unwrap();
371        let version = base.inner_version();
372
373        assert_eq!(&BaseVersion::new(major, minor), version);
374
375        let version = base.finish().unwrap();
376        assert_eq!(Version::new_base_version(major, minor), version);
377    }
378
379    #[test]
380    fn rejected_on_no_input() {
381        let input = "";
382        let parser = Parser::from_slice(input.as_bytes());
383        let err = parser.parse_base().unwrap_err();
384
385        assert_eq!(err, ModularParserError::ExpectedNumericToken { got: None });
386    }
387
388    #[test]
389    fn rejected_on_no_input2() {
390        let input = "1.";
391        let parser = Parser::from_slice(input.as_bytes());
392        let err = parser.parse_base().unwrap_err();
393
394        assert_eq!(err, ModularParserError::ExpectedNumericToken { got: None });
395    }
396
397    #[test]
398    fn rejected_on_overflow() {
399        // u64::MAX is accepted
400        let input = format!("{}.{}5", u64::MAX, 1844674407370955161_u64);
401        let parser = Parser::from_slice(input.as_bytes());
402        assert!(parser.parse_base().is_ok());
403
404        // but u64::MAX+1 overflows
405        let input = format!("{}6.0", 1844674407370955161_u64);
406        let parser = Parser::from_slice(input.as_bytes());
407        let err = parser.parse_base().unwrap_err();
408
409        assert_eq!(err, ModularParserError::NumberError(NumberError::Overflow));
410    }
411
412    #[test]
413    fn rejected_on_separator_expected() {
414        let input = "1";
415        let parser = Parser::from_slice(input.as_bytes());
416        let err = parser.parse_base().unwrap_err();
417
418        assert_eq!(err, ModularParserError::ExpectedSeparator { got: None });
419    }
420
421    #[test]
422    fn rejected_on_eoi_expected() {
423        let input = "1.0.0";
424        let parser = Parser::from_slice(input.as_bytes());
425        let err = parser.parse_base().unwrap().finish().unwrap_err();
426
427        assert_eq!(err, ModularParserError::ExpectedEndOfInput { got: b'.' });
428    }
429
430    #[test]
431    fn rejected_on_leading_zero_not_allowed() {
432        let input = "1.01";
433        let parser = Parser::from_slice(input.as_bytes());
434        let err = parser.parse_base().unwrap_err();
435
436        assert_eq!(
437            err,
438            ModularParserError::NumberError(NumberError::LeadingZero)
439        );
440    }
441
442    #[parameterized(
443        in_first_component_1 = { "01.9" },
444        in_first_component_2 = { "00.9" },
445        in_second_component_1 = { "9.01" },
446        in_second_component_2 = { "9.00" },
447    )]
448    fn rejected_on_leading_zero_not_allowed(input: &str) {
449        let parser = Parser::from_slice(input.as_bytes());
450        let err = parser.parse_base().unwrap_err();
451
452        assert_eq!(
453            err,
454            ModularParserError::NumberError(NumberError::LeadingZero)
455        );
456    }
457}