1 | use std::{error::Error, fmt}; |
2 | |
3 | /// An error related to parsing of an SPDX license expression |
4 | /// or identifier |
5 | #[derive (Debug, PartialEq, Eq)] |
6 | pub struct ParseError { |
7 | /// The string that was attempting to be parsed |
8 | pub original: String, |
9 | /// The range of characters in the original string that result |
10 | /// in this error |
11 | pub span: std::ops::Range<usize>, |
12 | /// The specific reason for the error |
13 | pub reason: Reason, |
14 | } |
15 | |
16 | /// The particular reason for a `ParseError` |
17 | #[derive (Debug, PartialEq, Eq)] |
18 | pub enum Reason { |
19 | /// The specified license short-identifier was not |
20 | /// found the SPDX list |
21 | UnknownLicense, |
22 | /// The specified exception short-identifier was not |
23 | /// found the SPDX list |
24 | UnknownException, |
25 | /// The characters are not valid in an SDPX license expression |
26 | InvalidCharacters, |
27 | /// An opening parens was unmatched with a closing parens |
28 | UnclosedParens, |
29 | /// A closing parens was unmatched with an opening parens |
30 | UnopenedParens, |
31 | /// The expression does not contain any valid terms |
32 | Empty, |
33 | /// Found an unexpected term, which wasn't one of the |
34 | /// expected terms that is listed |
35 | Unexpected(&'static [&'static str]), |
36 | /// A + was found after whitespace, which is not allowed |
37 | /// by the SPDX spec |
38 | SeparatedPlus, |
39 | /// When lexing, a term was found that was |
40 | /// 1. Not a license short-id |
41 | /// 2. Not an exception short-id |
42 | /// 3. Not a document/license ref |
43 | /// 4. Not an AND, OR, or WITH |
44 | UnknownTerm, |
45 | /// GNU suffix licenses don't allow `+` because they already have |
46 | /// the `-or-later` suffix to denote that |
47 | GnuNoPlus, |
48 | } |
49 | |
50 | impl fmt::Display for ParseError { |
51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
52 | f.write_str(&self.original)?; |
53 | f.write_str(data:" \n" )?; |
54 | |
55 | for _ in 0..self.span.start { |
56 | f.write_str(data:" " )?; |
57 | } |
58 | |
59 | // Mismatched parens have a slightly different output |
60 | // than the other errors |
61 | match &self.reason { |
62 | Reason::UnclosedParens => f.write_fmt(format_args!("- {}" , Reason::UnclosedParens)), |
63 | Reason::UnopenedParens => f.write_fmt(format_args!("^ {}" , Reason::UnopenedParens)), |
64 | other: &Reason => { |
65 | for _ in self.span.start..self.span.end { |
66 | f.write_str(data:"^" )?; |
67 | } |
68 | |
69 | f.write_fmt(format_args!(" {}" , other)) |
70 | } |
71 | } |
72 | } |
73 | } |
74 | |
75 | impl fmt::Display for Reason { |
76 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
77 | match self { |
78 | Self::UnknownLicense => f.write_str("unknown license id" ), |
79 | Self::UnknownException => f.write_str("unknown exception id" ), |
80 | Self::InvalidCharacters => f.write_str("invalid character(s)" ), |
81 | Self::UnclosedParens => f.write_str("unclosed parens" ), |
82 | Self::UnopenedParens => f.write_str("unopened parens" ), |
83 | Self::Empty => f.write_str("empty expression" ), |
84 | Self::Unexpected(expected) => { |
85 | if expected.len() > 1 { |
86 | f.write_str("expected one of " )?; |
87 | |
88 | for (i, exp) in expected.iter().enumerate() { |
89 | f.write_fmt(format_args!(" {}` {}`" , if i > 0 { ", " } else { "" }, exp))?; |
90 | } |
91 | f.write_str(" here" ) |
92 | } else if expected.is_empty() { |
93 | f.write_str("the term was not expected here" ) |
94 | } else { |
95 | f.write_fmt(format_args!("expected a ` {}` here" , expected[0])) |
96 | } |
97 | } |
98 | Self::SeparatedPlus => f.write_str("`+` must not follow whitespace" ), |
99 | Self::UnknownTerm => f.write_str("unknown term" ), |
100 | Self::GnuNoPlus => f.write_str("a GNU license was followed by a `+`" ), |
101 | } |
102 | } |
103 | } |
104 | |
105 | impl Error for ParseError { |
106 | fn description(&self) -> &str { |
107 | match self.reason { |
108 | Reason::UnknownLicense => "unknown license id" , |
109 | Reason::UnknownException => "unknown exception id" , |
110 | Reason::InvalidCharacters => "invalid character(s)" , |
111 | Reason::UnclosedParens => "unclosed parens" , |
112 | Reason::UnopenedParens => "unopened parens" , |
113 | Reason::Empty => "empty expression" , |
114 | Reason::Unexpected(_) => "unexpected term" , |
115 | Reason::SeparatedPlus => "`+` must not follow whitespace" , |
116 | Reason::UnknownTerm => "unknown term" , |
117 | Reason::GnuNoPlus => "a GNU license was followed by a `+`" , |
118 | } |
119 | } |
120 | } |
121 | |