Skip to main content

interprex/
error.rs

1use std::fmt;
2
3use thiserror::Error;
4
5#[derive(Clone, Debug, Eq, Error, PartialEq)]
6pub enum ModelError {
7    #[error("{field} must not be empty")]
8    Empty { field: &'static str },
9    #[error("{field} must not contain '/' or ASCII control characters")]
10    InvalidSegment { field: &'static str },
11    #[error("repository must have the form owner/name")]
12    InvalidRepository,
13    #[error("number must be greater than zero")]
14    InvalidNumber,
15    #[error(
16        "{field} must begin and end with an ASCII letter or digit and contain only ASCII letters, digits, '.', '_' or single '-' characters"
17    )]
18    InvalidProviderTextIdentifier { field: &'static str },
19    #[error("provider text record value must contain a positive integer 'version' field")]
20    InvalidProviderTextRecordVersion,
21    #[error("reviewer application actor must be a bot")]
22    ReviewerApplicationActorNotBot,
23    #[error("required check {name} appears more than once")]
24    DuplicateRequiredCheck { name: String },
25}
26
27pub(crate) fn segment(
28    value: impl Into<String>,
29    field: &'static str,
30) -> std::result::Result<String, ModelError> {
31    let value = value.into();
32    if value.is_empty() {
33        return Err(ModelError::Empty { field });
34    }
35    if value.contains('/') || value.chars().any(char::is_control) {
36        return Err(ModelError::InvalidSegment { field });
37    }
38    Ok(value)
39}
40
41#[derive(Clone, Debug, Eq, Error, PartialEq)]
42pub enum ProviderError {
43    #[error("{provider} does not support {operation}")]
44    Unsupported {
45        provider: &'static str,
46        operation: &'static str,
47    },
48    #[error("unrepresentable {provider} data: {fact}")]
49    Unrepresentable {
50        provider: &'static str,
51        fact: String,
52    },
53    #[error("{entity} was not found")]
54    NotFound { entity: String },
55    /// A credential the operation needs is absent from the configuration the
56    /// provider was built from. `entry` names the declaration that would
57    /// supply it, so the message a stuck caller reads points at the place to
58    /// edit rather than only at what was missing.
59    #[error("missing {kind} credential for identity {identity}: {entry} is absent from {origin}")]
60    MissingCredential {
61        identity: String,
62        kind: &'static str,
63        entry: String,
64        origin: ConfigurationSource,
65    },
66    #[error("provider configuration from {origin} failed: {reason}")]
67    Configuration {
68        origin: ConfigurationSource,
69        reason: String,
70    },
71    #[error("{provider} {operation} failed: {message}")]
72    External {
73        provider: &'static str,
74        operation: &'static str,
75        message: String,
76    },
77    /// The caller's request contradicts itself; correcting the request, not
78    /// retrying it, resolves this error.
79    #[error("invalid input for {provider}: {fact}")]
80    InvalidInput {
81        provider: &'static str,
82        fact: String,
83    },
84}
85
86#[derive(Clone, Debug, Eq, PartialEq)]
87pub enum ConfigurationSource {
88    Direct,
89    File(String),
90}
91
92impl fmt::Display for ConfigurationSource {
93    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94        match self {
95            Self::Direct => formatter.write_str("direct construction"),
96            Self::File(path) => write!(formatter, "file {path}"),
97        }
98    }
99}
100
101pub type Result<T> = std::result::Result<T, ProviderError>;