#![allow(missing_docs, unused_doc_comments)]
use reqwest::StatusCode;
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::io;
use std::path::PathBuf;
use std::result;
use thiserror::Error;
use url::Url;
pub type Result<T, E = Error> = result::Result<T, E>;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[non_exhaustive]
#[error("error accessing '{url}': {source}")]
CouldNotAccessUrl { url: Url, source: Box<Error> },
#[non_exhaustive]
#[error("could not get WhizzML output '{name}': {source}")]
CouldNotGetOutput { name: String, source: Box<Error> },
#[non_exhaustive]
#[error("could not parse a URL with the domain '{domain}': {source}")]
CouldNotParseUrlWithDomain {
domain: String,
source: Box<url::ParseError>,
},
#[non_exhaustive]
#[error("could not read file {path:?}: {source}")]
CouldNotReadFile { path: PathBuf, source: Box<Error> },
#[non_exhaustive]
#[error("must specify {var}")]
MissingEnvVar { var: String },
#[non_exhaustive]
#[error("WhizzML output is not (yet?) available")]
OutputNotAvailable {},
#[non_exhaustive]
#[error("BigML payment required for {url} ({body})")]
PaymentRequired { url: Url, body: String },
#[non_exhaustive]
#[error("The operation timed out")]
Timeout {},
#[non_exhaustive]
#[error("{status} for {url} ({body})")]
UnexpectedHttpStatus {
url: Url,
status: StatusCode,
body: String,
},
#[non_exhaustive]
#[error("unknown BigML type {type_name:?}")]
UnknownBigMlType { type_name: String },
#[non_exhaustive]
#[error("https://bigml.com/dashboard/{id} failed ({message})")]
WaitFailed {
id: String,
message: String,
},
#[non_exhaustive]
#[error("Expected BigML resource ID starting with '{expected}', found '{found}'")]
WrongResourceType {
expected: &'static str,
found: String,
},
#[non_exhaustive]
#[error("{source}")]
Other {
#[from]
source: Box<dyn StdError + Send + Sync + 'static>,
},
}
impl Error {
pub(crate) fn could_not_access_url<E>(url: &Url, error: E) -> Error
where
E: Into<Error>,
{
Error::CouldNotAccessUrl {
url: url_without_api_key(url),
source: Box::new(error.into()),
}
}
pub(crate) fn could_not_get_output<E>(name: &str, error: E) -> Error
where
E: Into<Error>,
{
Error::CouldNotGetOutput {
name: name.to_owned(),
source: Box::new(error.into()),
}
}
pub(crate) fn could_not_parse_url_with_domain<S>(
domain: S,
error: url::ParseError,
) -> Error
where
S: Into<String>,
{
Error::CouldNotParseUrlWithDomain {
domain: domain.into(),
source: Box::new(error),
}
}
pub(crate) fn could_not_read_file<P, E>(path: P, error: E) -> Error
where
P: Into<PathBuf>,
E: Into<Error>,
{
Error::CouldNotReadFile {
path: path.into(),
source: Box::new(error.into()),
}
}
pub fn might_be_temporary(&self) -> bool {
match self {
Error::CouldNotAccessUrl { source, .. } => source.might_be_temporary(),
Error::CouldNotGetOutput { source, .. } => source.might_be_temporary(),
Error::CouldNotReadFile { source, .. } => source.might_be_temporary(),
Error::PaymentRequired { .. } => true,
Error::UnexpectedHttpStatus { status, .. } => matches!(
*status,
StatusCode::INTERNAL_SERVER_ERROR | StatusCode::SERVICE_UNAVAILABLE
| StatusCode::GATEWAY_TIMEOUT
),
_ => false,
}
}
pub(crate) fn missing_env_var<S: Into<String>>(var: S) -> Self {
Error::MissingEnvVar { var: var.into() }
}
pub fn original_bigml_error(&self) -> &Error {
match self {
Error::CouldNotAccessUrl { source, .. } => source.original_bigml_error(),
Error::CouldNotGetOutput { source, .. } => source.original_bigml_error(),
Error::CouldNotReadFile { source, .. } => source.original_bigml_error(),
Error::CouldNotParseUrlWithDomain { .. }
| Error::MissingEnvVar { .. }
| Error::Other { .. }
| Error::OutputNotAvailable { .. }
| Error::PaymentRequired { .. }
| Error::Timeout { .. }
| Error::UnexpectedHttpStatus { .. }
| Error::UnknownBigMlType { .. }
| Error::WaitFailed { .. }
| Error::WrongResourceType { .. } => self,
}
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Error {
Error::Other {
source: error.into(),
}
}
}
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Error {
Error::Other {
source: error.into(),
}
}
}
impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Error {
Error::Other {
source: error.into(),
}
}
}
pub(crate) fn url_without_api_key(url: &Url) -> Url {
let mut query = BTreeMap::new();
for (k, v) in url.query_pairs() {
query.insert(k.into_owned(), v.into_owned());
}
let mut new_url = url.to_owned();
{
let mut serializer = new_url.query_pairs_mut();
serializer.clear();
for (k, v) in query.iter() {
if k == "api_key" {
serializer.append_pair(k, "*****");
} else {
serializer.append_pair(k, v);
}
}
}
new_url
}
#[test]
fn url_without_api_key_is_sanitized() {
let url = Url::parse("https://www.example.com/foo?a=b&api_key=12345")
.expect("could not parse URL");
let cleaned = url_without_api_key(&url);
assert_eq!(
cleaned.as_str(),
"https://www.example.com/foo?a=b&api_key=*****"
);
}