nix_uri/
error.rs

1use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
2use std::fmt;
3use thiserror::Error;
4
5pub type NixUriResult<T> = Result<T, NixUriError>;
6
7/// Custom error tree type compatible with nom 8.0
8/// This provides similar functionality to nom-supreme's ErrorTree
9#[derive(Debug, Clone, PartialEq)]
10pub enum ErrorTree<I> {
11    /// A base error - a leaf node in the error tree
12    Base {
13        /// The location in the input where the error occurred
14        location: I,
15        /// The kind of error that occurred
16        kind: BaseErrorKind,
17    },
18    /// A stack of errors with context
19    Stack {
20        /// The base error
21        base: Box<ErrorTree<I>>,
22        /// The context stack
23        contexts: Vec<(I, StackContext)>,
24    },
25    /// A collection of alternative errors from `alt`
26    Alt(Vec<ErrorTree<I>>),
27}
28
29/// The kind of base error
30#[derive(Debug, Clone, PartialEq)]
31pub enum BaseErrorKind {
32    /// A nom ErrorKind
33    Kind(ErrorKind),
34    /// An expected value
35    Expected(Expectation),
36    /// An external error
37    External(String),
38}
39
40/// What was expected when parsing
41#[derive(Debug, Clone, PartialEq)]
42pub enum Expectation {
43    /// Expected a specific tag
44    Tag(&'static str),
45    /// Expected a specific character
46    Char(char),
47    /// Expected end of input
48    Eof,
49    /// Expected alpha character
50    Alpha,
51    /// Expected digit
52    Digit,
53    /// Expected hex digit
54    HexDigit,
55    /// Expected alphanumeric
56    AlphaNumeric,
57    /// Expected space
58    Space,
59    /// Expected multispace
60    Multispace,
61    /// Expected something else
62    Something,
63}
64
65/// Context added to a stack
66#[derive(Debug, Clone, PartialEq)]
67pub enum StackContext {
68    /// A static context string
69    Context(&'static str),
70    /// A nom ErrorKind
71    Kind(ErrorKind),
72}
73
74impl<I> ErrorTree<I> {
75    /// Map the locations in this error tree
76    pub fn map_locations<O>(self, f: impl Fn(I) -> O + Copy) -> ErrorTree<O> {
77        match self {
78            ErrorTree::Base { location, kind } => ErrorTree::Base {
79                location: f(location),
80                kind,
81            },
82            ErrorTree::Stack { base, contexts } => ErrorTree::Stack {
83                base: Box::new(base.map_locations(f)),
84                contexts: contexts.into_iter().map(|(i, c)| (f(i), c)).collect(),
85            },
86            ErrorTree::Alt(alts) => {
87                ErrorTree::Alt(alts.into_iter().map(|e| e.map_locations(f)).collect())
88            }
89        }
90    }
91}
92
93impl<I: Clone> ParseError<I> for ErrorTree<I> {
94    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
95        ErrorTree::Base {
96            location: input,
97            kind: BaseErrorKind::Kind(kind),
98        }
99    }
100
101    fn append(input: I, kind: ErrorKind, other: Self) -> Self {
102        let context = (input, StackContext::Kind(kind));
103        match other {
104            ErrorTree::Stack { base, mut contexts } => {
105                contexts.push(context);
106                ErrorTree::Stack { base, contexts }
107            }
108            base => ErrorTree::Stack {
109                base: Box::new(base),
110                contexts: vec![context],
111            },
112        }
113    }
114
115    fn from_char(input: I, c: char) -> Self {
116        ErrorTree::Base {
117            location: input,
118            kind: BaseErrorKind::Expected(Expectation::Char(c)),
119        }
120    }
121
122    fn or(self, other: Self) -> Self {
123        // Combine alternatives
124        let mut alts = match self {
125            ErrorTree::Alt(v) => v,
126            e => vec![e],
127        };
128        match other {
129            ErrorTree::Alt(v) => alts.extend(v),
130            e => alts.push(e),
131        }
132        ErrorTree::Alt(alts)
133    }
134}
135
136impl<I: Clone> ContextError<I> for ErrorTree<I> {
137    fn add_context(input: I, ctx: &'static str, other: Self) -> Self {
138        let context = (input, StackContext::Context(ctx));
139        match other {
140            ErrorTree::Stack { base, mut contexts } => {
141                contexts.push(context);
142                ErrorTree::Stack { base, contexts }
143            }
144            base => ErrorTree::Stack {
145                base: Box::new(base),
146                contexts: vec![context],
147            },
148        }
149    }
150}
151
152impl<I: Clone, E: std::fmt::Display> FromExternalError<I, E> for ErrorTree<I> {
153    fn from_external_error(input: I, _kind: ErrorKind, e: E) -> Self {
154        ErrorTree::Base {
155            location: input,
156            kind: BaseErrorKind::External(e.to_string()),
157        }
158    }
159}
160
161impl<I: fmt::Display> fmt::Display for ErrorTree<I> {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            ErrorTree::Base { location, kind } => {
165                write!(f, "error {:?} at: {}", kind, location)
166            }
167            ErrorTree::Stack { base, contexts } => {
168                write!(f, "{}", base)?;
169                for (input, context) in contexts {
170                    write!(f, "\n  in {:?} at: {}", context, input)?;
171                }
172                Ok(())
173            }
174            ErrorTree::Alt(alts) => {
175                writeln!(f, "one of:")?;
176                for alt in alts {
177                    writeln!(f, "  {}", alt)?;
178                }
179                Ok(())
180            }
181        }
182    }
183}
184
185impl<I: fmt::Debug + fmt::Display> std::error::Error for ErrorTree<I> {}
186
187pub type IErr<E> = ErrorTree<E>;
188
189#[derive(Debug, Error)]
190#[non_exhaustive]
191pub enum NixUriError {
192    /// Generic nix uri error
193    #[error("Error: {0}")]
194    Error(String),
195    /// Generic parsing fail
196    #[error("Error parsing: {0}")]
197    ParseError(String),
198    /// Invalid Url
199    #[error("Not a valid Url: {0}")]
200    InvalidUrl(String),
201    /// The path to directories must be absolute
202    #[error("The path is not absolute: {0}")]
203    NotAbsolute(String),
204    /// Contained an Illegal Path Character
205    #[error("Contains an illegal path character: {0}")]
206    PathCharacter(String),
207    /// The type doesn't have the required default parameter set
208    /// Example: Github needs to have an owner and a repo
209    // TODO collect multiple potentially missing parameters
210    #[error("FlakeRef Type: {0} is missing the following required parameter: {1}")]
211    MissingTypeParameter(String, String),
212    /// The type of the uri itself, for example `github`
213    #[error("The type is not known: {0}")]
214    UnknownUriType(String),
215    /// The type of the uri extensions for a uri type, for example `git+ssh`
216    /// the ssh part is the type here.
217    #[error("The type is not known: {0}")]
218    UnknownTransportLayer(String),
219    /// Invalid Type
220    #[error("Invalid FlakeRef Type: {0}")]
221    InvalidType(String),
222    #[error("The parameter: {0} is not supported by the flakeref type.")]
223    UnsupportedParam(String),
224    #[error("field: `{0}` only supported by: `{1}`.")]
225    UnsupportedByType(String, String),
226    #[error("The parameter: {0} invalid.")]
227    UnknownUriParameter(String),
228    /// Nom Error
229    /// TODO: Implement real conversion instead of this hack.
230    #[error("Nom Error: {0}")]
231    Nom(String),
232    #[error(transparent)]
233    NomParseError(#[from] IErr<String>),
234    // #[error("{} {}", 0.0, 0.1)]
235    // Parser((String, VerboseErrorKind)),
236    #[error("Servo Url Parsing Error: {0}")]
237    ServoUrl(#[from] url::ParseError),
238}
239
240impl From<IErr<&str>> for NixUriError {
241    fn from(value: IErr<&str>) -> Self {
242        let new_errs = value.map_locations(|i| i.to_string());
243        Self::NomParseError(new_errs)
244    }
245}
246
247/// A tag combinator that produces nice error messages with `Expected(Tag(...))`
248/// This is similar to nom-supreme's tag functionality
249pub fn tag<'a>(
250    expected: &'static str,
251) -> impl FnMut(&'a str) -> nom::IResult<&'a str, &'a str, ErrorTree<&'a str>> {
252    move |input: &'a str| {
253        if let Some(rest) = input.strip_prefix(expected) {
254            Ok((rest, &input[..expected.len()]))
255        } else {
256            Err(nom::Err::Error(ErrorTree::Base {
257                location: input,
258                kind: BaseErrorKind::Expected(Expectation::Tag(expected)),
259            }))
260        }
261    }
262}