qcs_api_client_common/configuration/
error.rs

1use std::{error::Error, path::PathBuf};
2
3use crate::configuration::{oidc::DISCOVERY_REQUIRED_SCOPE, tokens::PkceFlowError};
4
5use super::ClientConfigurationBuilderError;
6
7/// Errors that can occur when loading a configuration.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum LoadError {
11    /// Failed to load config from a file.
12    #[error("Failed to load settings: {0}")]
13    Load(Box<dyn Error + Send + Sync + 'static>),
14    /// Failed to access or parse an environment variable.
15    #[error("Failed to load value from the environment variable {variable_name}: {message}")]
16    EnvVar {
17        /// The name of the environment variable.
18        variable_name: String,
19        /// The error message.
20        message: String,
21    },
22    /// Failed to load a file from a path.
23    #[error("Failed to load file from path {path:?}: {message}")]
24    Path {
25        /// The path that could not be loaded.
26        path: PathBuf,
27        /// The error message.
28        message: String,
29    },
30    /// The file could not be read or written to.
31    #[error(transparent)]
32    Io(#[from] std::io::Error),
33    /// Failed to use the builder to build a configuration.
34    #[error("Failed to build the ClientConfiguration: {0}")]
35    Build(#[from] ClientConfigurationBuilderError),
36    /// Provided profile not found.
37    #[error("Expected profile {0} in settings.profiles but it does not exist")]
38    ProfileNotFound(String),
39    /// Provided authorization server not found.
40    #[error("Expected auth server {0} in settings.auth_servers but it does not exist")]
41    AuthServerNotFound(String),
42    /// Failed to complete a PKCE login flow.
43    #[error("Failed to complete PKCE login: {0}")]
44    PkceFlow(#[from] PkceFlowError),
45    #[cfg(feature = "tracing-config")]
46    /// Failed to parse tracing filter. These should be a comma separated list of URL patterns. See
47    /// <https://wicg.github.io/urlpattern> for reference.
48    #[error("Could not parse tracing filter: {0}")]
49    TracingFilterParseError(#[from] crate::tracing_configuration::TracingFilterError),
50}
51
52impl<E: Error + 'static> From<shellexpand::LookupError<E>> for LoadError {
53    fn from(value: shellexpand::LookupError<E>) -> Self {
54        Self::EnvVar {
55            variable_name: value.var_name,
56            message: value.cause.to_string(),
57        }
58    }
59}
60
61impl From<figment::Error> for LoadError {
62    fn from(value: figment::Error) -> Self {
63        Self::Load(Box::new(value))
64    }
65}
66
67/// Errors that can occur when managing authorization tokens.
68#[derive(Debug, thiserror::Error)]
69pub enum TokenError {
70    /// No QCS API refresh token to use.
71    #[error("No refresh token is configured within the selected QCS credential.")]
72    NoRefreshToken,
73    /// No access token to use.
74    #[error("No access token has been requested.")]
75    NoAccessToken,
76    /// No access token to use.
77    #[error("Requested an access token for a configuration without credentials.")]
78    NoCredentials,
79    /// Access token is invalid.
80    #[error("The access token is invalid: {0}")]
81    InvalidAccessToken(jsonwebtoken::errors::Error),
82    /// No QCS API refresh token to use.
83    #[error("No auth server is configured within the selected QCS credential.")]
84    NoAuthServer,
85    /// Failure fetching a refreshed access token from the QCS API.
86    #[error("Error fetching new token from the QCS API: {0}")]
87    Fetch(#[from] reqwest::Error),
88    /// Catch all for errors returned from an [`super::ExternallyManaged`] refresh function.
89    #[error("Failed to request an externally managed access token: {0}")]
90    ExternallyManaged(String),
91    /// Failure writing the new access token to the secrets file.
92    #[error("Failed to write the new access token to the secrets file: {0}")]
93    Write(#[from] WriteError),
94    /// Failure fetching the OIDC discovery document.
95    #[error("Failed to fetch the OIDC discovery document: {0}")]
96    Discovery(#[from] DiscoveryError),
97}
98
99/// Errors that can occur when attempting to fetch and process an OIDC discovery document.
100#[derive(Debug, thiserror::Error)]
101pub enum DiscoveryError {
102    #[error("invalid issuer URL: {0}")]
103    Url(#[from] url::ParseError),
104    #[error("error fetching discovery document: {0}")]
105    Fetch(#[from] reqwest::Error),
106    #[error("failed to parse discovery document: {0}")]
107    Json(#[from] serde_json::Error),
108    #[error("issuer URL ({issuer}) is invalid: {reason}")]
109    InvalidIssuer { issuer: String, reason: String },
110    #[error("discovery document is invalid: {reason}")]
111    InvalidDocument { reason: String },
112    #[error("discovery document issuer ({document}) does not match the queried issuer ({query})")]
113    IssuerMismatch { document: String, query: String },
114    #[error("discovery document `supported_scopes` does not include the required minimum scope \"{expected}\", received: {0:?}", expected = DISCOVERY_REQUIRED_SCOPE)]
115    InvalidScopes(Vec<String>),
116}
117
118/// Errors that can occur when trying to write or update a configuration file.
119#[derive(Debug, thiserror::Error)]
120pub enum WriteError {
121    /// There was an IO error while updating the secrets file.
122    #[error(transparent)]
123    IoWithPath(#[from] IoErrorWithPath),
124    /// The file's contents are not valid TOML
125    #[error("File could not be read as TOML: {0}")]
126    InvalidToml(#[from] toml_edit::TomlError),
127    /// TOML table could not be found.
128    #[error("The table `{0}` does not exist.")]
129    MissingTable(String),
130    /// There was an error with time formatting
131    #[error("Error formatting time: {0}.")]
132    TimeFormat(#[from] time::error::Format),
133    /// There was an error writing or persisting the temporary secrets file during access token refresh.
134    #[error("Error writing or persisting temporary secrets file during access token refresh: {0}")]
135    TempFile(#[from] async_tempfile::Error),
136}
137
138/// A fallible IO operation that can result in a [`IoErrorWithPath`]
139#[derive(Debug)]
140pub enum IoOperation {
141    Open,
142    Read,
143    Write,
144    Rename { dest: PathBuf },
145    GetMetadata,
146    SetPermissions,
147    Flush,
148}
149
150/// An error wrapping [`std::io::Error`] that includes the path and operation as additional context.
151#[derive(Debug, thiserror::Error)]
152#[error("Io error while error performing {operation:?} on {path}: {error}")]
153pub struct IoErrorWithPath {
154    #[source]
155    pub error: std::io::Error,
156    pub path: PathBuf,
157    pub operation: IoOperation,
158}