1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use miette::Diagnostic;
use node_semver::SemverError;
use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
use thiserror::Error;
use url::ParseError as UrlParseError;

#[derive(Debug, Error, Diagnostic)]
#[error("Error parsing package spec. {kind}")]
#[diagnostic(
    code(package_spec::no_parse),
    help("Please fix your spec. Go look up wherever they're documented idk.")
)]
pub struct PackageSpecError {
    pub input: String,
    pub offset: usize,
    pub kind: SpecErrorKind,
}

impl PackageSpecError {
    pub fn location(&self) -> (usize, usize) {
        // Taken partially from nom.
        let prefix = &self.input.as_bytes()[..self.offset];

        // Count the number of newlines in the first `offset` bytes of input
        let line_number = bytecount::count(prefix, b'\n');

        // Find the line that includes the subslice:
        // Find the *last* newline before the substring starts
        let line_begin = prefix
            .iter()
            .rev()
            .position(|&b| b == b'\n')
            .map(|pos| self.offset - pos)
            .unwrap_or(0);

        // Find the full line after that newline
        let line = self.input[line_begin..]
            .lines()
            .next()
            .unwrap_or(&self.input[line_begin..])
            .trim_end();

        // The (1-indexed) column number is the offset of our substring into that line
        let column_number = self.input[self.offset..].as_ptr() as usize - line.as_ptr() as usize;

        (line_number, column_number)
    }
}

#[derive(Debug, Error)]
pub enum SpecErrorKind {
    #[error("Found invalid characters: `{0}`")]
    InvalidCharacters(String),
    #[error("Drive letters on Windows can only be alphabetical. Got `{0}`.")]
    InvalidDriveLetter(char),
    #[error("Invalid git host `{0}`. Only github:, gitlab:, gist:, and bitbucket: are supported in shorthands.")]
    InvalidGitHost(String),
    #[error(transparent)]
    SemverParseError(SemverError),
    #[error(transparent)]
    UrlParseError(UrlParseError),
    #[error(transparent)]
    GitHostParseError(Box<PackageSpecError>),
    #[error("Failed to parse {0} component of semver string.")]
    Context(&'static str),
    #[error("Incomplete input to semver parser.")]
    IncompleteInput,
    #[error("An unspecified error occurred.")]
    Other,
}

#[derive(Debug)]
pub(crate) struct SpecParseError<I> {
    pub(crate) input: I,
    pub(crate) context: Option<&'static str>,
    pub(crate) kind: Option<SpecErrorKind>,
}

impl<I> ParseError<I> for SpecParseError<I> {
    fn from_error_kind(input: I, _kind: nom::error::ErrorKind) -> Self {
        Self {
            input,
            context: None,
            kind: None,
        }
    }

    fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self {
        other
    }
}

impl<I> ContextError<I> for SpecParseError<I> {
    fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self {
        other.context = Some(ctx);
        other
    }
}

// There's a few parsers that just... manually return SpecParseError in a
// map_res, so this absurd thing is actually needed. Curious? Just comment it
// out and look at all the red.
impl<'a> FromExternalError<&'a str, SpecParseError<&'a str>> for SpecParseError<&'a str> {
    fn from_external_error(_input: &'a str, _kind: ErrorKind, e: SpecParseError<&'a str>) -> Self {
        e
    }
}

impl<'a> FromExternalError<&'a str, SemverError> for SpecParseError<&'a str> {
    fn from_external_error(input: &'a str, _kind: ErrorKind, e: SemverError) -> Self {
        SpecParseError {
            input,
            context: None,
            kind: Some(SpecErrorKind::SemverParseError(e)),
        }
    }
}

impl<'a> FromExternalError<&'a str, UrlParseError> for SpecParseError<&'a str> {
    fn from_external_error(input: &'a str, _kind: ErrorKind, e: UrlParseError) -> Self {
        SpecParseError {
            input,
            context: None,
            kind: Some(SpecErrorKind::UrlParseError(e)),
        }
    }
}

impl<'a> FromExternalError<&'a str, PackageSpecError> for SpecParseError<&'a str> {
    fn from_external_error(input: &'a str, _kind: ErrorKind, e: PackageSpecError) -> Self {
        SpecParseError {
            input,
            context: None,
            kind: Some(SpecErrorKind::GitHostParseError(Box::new(e))),
        }
    }
}