oro_package_spec/
error.rs

1use miette::Diagnostic;
2use node_semver::SemverError;
3use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
4use thiserror::Error;
5use url::ParseError as UrlParseError;
6
7/// An invalid package specifier was provided.
8///
9/// The syntax for package specifiers is documented here:
10/// https://orogene.dev/book/guide/node_modules.html#specifier-syntax
11#[derive(Debug, Error, Diagnostic)]
12#[error("Error parsing package spec. {kind}")]
13#[diagnostic(
14    code(package_spec::no_parse),
15    url(docsrs),
16    help("The syntax for package specifiers is documented here: https://orogene.dev/book/guide/node_modules.html#specifier-syntax")
17)]
18pub struct PackageSpecError {
19    pub input: String,
20    pub offset: usize,
21    pub kind: SpecErrorKind,
22}
23
24impl PackageSpecError {
25    pub fn location(&self) -> (usize, usize) {
26        // Taken partially from nom.
27        let prefix = &self.input.as_bytes()[..self.offset];
28
29        // Count the number of newlines in the first `offset` bytes of input
30        let line_number = bytecount::count(prefix, b'\n');
31
32        // Find the line that includes the subslice:
33        // Find the *last* newline before the substring starts
34        let line_begin = prefix
35            .iter()
36            .rev()
37            .position(|&b| b == b'\n')
38            .map(|pos| self.offset - pos)
39            .unwrap_or(0);
40
41        // Find the full line after that newline
42        let line = self.input[line_begin..]
43            .lines()
44            .next()
45            .unwrap_or(&self.input[line_begin..])
46            .trim_end();
47
48        // The (1-indexed) column number is the offset of our substring into that line
49        let column_number = self.input[self.offset..].as_ptr() as usize - line.as_ptr() as usize;
50
51        (line_number, column_number)
52    }
53}
54
55#[derive(Debug, Error)]
56pub enum SpecErrorKind {
57    #[error("Found invalid characters: `{0}`")]
58    InvalidCharacters(String),
59    #[error("Drive letters on Windows can only be alphabetical. Got `{0}`.")]
60    InvalidDriveLetter(char),
61    #[error("Invalid git host `{0}`. Only github:, gitlab:, gist:, and bitbucket: are supported in shorthands.")]
62    InvalidGitHost(String),
63    #[error(transparent)]
64    SemverParseError(SemverError),
65    #[error(transparent)]
66    UrlParseError(UrlParseError),
67    #[error(transparent)]
68    GitHostParseError(Box<PackageSpecError>),
69    #[error("Failed to parse {0} component of semver string.")]
70    Context(&'static str),
71    #[error("Incomplete input to semver parser.")]
72    IncompleteInput,
73    #[error("An unspecified error occurred.")]
74    Other,
75}
76
77#[derive(Debug)]
78pub(crate) struct SpecParseError<I> {
79    pub(crate) input: I,
80    pub(crate) context: Option<&'static str>,
81    pub(crate) kind: Option<SpecErrorKind>,
82}
83
84impl<I> ParseError<I> for SpecParseError<I> {
85    fn from_error_kind(input: I, _kind: nom::error::ErrorKind) -> Self {
86        Self {
87            input,
88            context: None,
89            kind: None,
90        }
91    }
92
93    fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self {
94        other
95    }
96}
97
98impl<I> ContextError<I> for SpecParseError<I> {
99    fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self {
100        other.context = Some(ctx);
101        other
102    }
103}
104
105// There's a few parsers that just... manually return SpecParseError in a
106// map_res, so this absurd thing is actually needed. Curious? Just comment it
107// out and look at all the red.
108impl<'a> FromExternalError<&'a str, SpecParseError<&'a str>> for SpecParseError<&'a str> {
109    fn from_external_error(_input: &'a str, _kind: ErrorKind, e: SpecParseError<&'a str>) -> Self {
110        e
111    }
112}
113
114impl<'a> FromExternalError<&'a str, SemverError> for SpecParseError<&'a str> {
115    fn from_external_error(input: &'a str, _kind: ErrorKind, e: SemverError) -> Self {
116        SpecParseError {
117            input,
118            context: None,
119            kind: Some(SpecErrorKind::SemverParseError(e)),
120        }
121    }
122}
123
124impl<'a> FromExternalError<&'a str, UrlParseError> for SpecParseError<&'a str> {
125    fn from_external_error(input: &'a str, _kind: ErrorKind, e: UrlParseError) -> Self {
126        SpecParseError {
127            input,
128            context: None,
129            kind: Some(SpecErrorKind::UrlParseError(e)),
130        }
131    }
132}
133
134impl<'a> FromExternalError<&'a str, PackageSpecError> for SpecParseError<&'a str> {
135    fn from_external_error(input: &'a str, _kind: ErrorKind, e: PackageSpecError) -> Self {
136        SpecParseError {
137            input,
138            context: None,
139            kind: Some(SpecErrorKind::GitHostParseError(Box::new(e))),
140        }
141    }
142}