Skip to content

Commit

Permalink
Use strongly typed errors based on thiserror
Browse files Browse the repository at this point in the history
Dyn<error> is generally discouraged in libraries.
  • Loading branch information
tomaszjonak committed Aug 20, 2024
1 parent 0f09747 commit ea02116
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 107 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ reqwest = { version = "0.11.18", features = ["json", "multipart"] }
serde = "1.0.183"
serde_derive = "1.0.183"
serde_json = "1.0.104"
thiserror = "1.0.63"
url = "2.4.0"

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion examples/falcon_discover_hosts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async fn main() {
break;
}

offset = offset + response.resources.len();
offset += response.resources.len();

let batch_details = get_hosts(&falcon.cfg, response.resources)
.await
Expand Down
93 changes: 15 additions & 78 deletions src/apis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,97 +1,34 @@
use std::error;
use std::fmt;

#[derive(Debug, Clone)]
pub struct ResponseContent<T> {
pub struct ResponseContent<T: std::fmt::Debug> {
pub status: reqwest::StatusCode,
pub content: String,
pub entity: Option<T>,
}

#[derive(Debug)]
pub enum Error<T> {
Reqwest(reqwest::Error),
Serde(serde_json::Error),
Io(std::io::Error),
ResponseError(ResponseContent<T>),
}

impl<T> fmt::Display for Error<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (module, e) = match self {
Error::Reqwest(e) => ("reqwest", e.to_string()),
Error::Serde(e) => ("serde", e.to_string()),
Error::Io(e) => ("IO", e.to_string()),
Error::ResponseError(e) => ("response", format!("status code {}", e.status)),
};
write!(f, "error in {}: {}", module, e)
}
}

impl<T: fmt::Debug> error::Error for Error<T> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(match self {
Error::Reqwest(e) => e,
Error::Serde(e) => e,
Error::Io(e) => e,
Error::ResponseError(_) => return None,
})
}
}

impl<T> From<reqwest::Error> for Error<T> {
fn from(e: reqwest::Error) -> Self {
Error::Reqwest(e)
impl<T: std::fmt::Debug> std::fmt::Display for ResponseContent<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {:?} {:?}", self.status, self.content, self.entity)
}
}

impl<T> From<serde_json::Error> for Error<T> {
fn from(e: serde_json::Error) -> Self {
Error::Serde(e)
}
}
impl<T: std::fmt::Debug> std::error::Error for ResponseContent<T> {}

impl<T> From<std::io::Error> for Error<T> {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
#[derive(Debug, thiserror::Error)]
pub enum Error<T: std::fmt::Debug> {
#[error("reqwest error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("serde error: {0}")]
Serde(#[from] serde_json::Error),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("Response error: {0}")]
ResponseError(#[from] ResponseContent<T>),
}

pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];

for (key, value) in object {
match value {
serde_json::Value::Object(_) => params.append(&mut parse_deep_object(
&format!("{}[{}]", prefix, key),
value,
)),
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.append(&mut parse_deep_object(
&format!("{}[{}][{}]", prefix, key, i),
value,
));
}
}
serde_json::Value::String(s) => {
params.push((format!("{}[{}]", prefix, key), s.clone()))
}
_ => params.push((format!("{}[{}]", prefix, key), value.to_string())),
}
}

return params;
}

unimplemented!("Only objects are supported with style=deepObject")
}

pub mod alerts_api;
pub mod cloud_connect_aws_api;
pub mod configuration_assessment_api;
Expand Down
19 changes: 5 additions & 14 deletions src/easy/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::apis::configuration::Configuration;
use crate::apis::oauth2_api::{oauth2_access_token, Oauth2AccessTokenError};
use crate::apis::Error;
use crate::easy::cloud::FalconCloud;
use crate::easy::errors::CredentialsError;
use crate::error::CredentialsError;
use std::env;

#[derive(Clone)]
Expand Down Expand Up @@ -39,7 +39,7 @@ impl FalconHandle {
Ok(())
}

pub async fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
pub async fn from_env() -> Result<Self, crate::error::CredentialsError> {
Ok(FalconHandle::from_cfg(Credentials::from_env()?).await?)
}
}
Expand All @@ -54,19 +54,10 @@ pub struct Credentials {

impl Credentials {
pub fn from_env() -> Result<Self, CredentialsError> {
let client_id = env::var("FALCON_CLIENT_ID").map_err(|_| {
CredentialsError(
"Missing FALCON_CLIENT_ID environment variable. Please provide your OAuth2 API Client Secret for authentication with CrowdStrike Falcon platform. Establishing and retrieving OAuth2 API credentials can be performed at https://falcon.crowdstrike.com/support/api-clients-and-keys."
.to_string(),
)
})?;
let client_id = env::var("FALCON_CLIENT_ID").map_err(|_| CredentialsError::ClientID)?;

let client_secret = env::var("FALCON_CLIENT_SECRET").map_err(|_| {
CredentialsError(
"Missing FALCON_CLIENT_SECRET environment variable. Please provide your OAuth2 API Client Secret for authentication with CrowdStrike Falcon platform. Establishing and retrieving OAuth2 API credentials can be performed at https://falcon.crowdstrike.com/support/api-clients-and-keys."
.to_string(),
)
})?;
let client_secret =
env::var("FALCON_CLIENT_SECRET").map_err(|_| CredentialsError::Secret)?;
let member_cid = env::var("FALCON_MEMBER_CID").ok();

Ok(Credentials {
Expand Down
7 changes: 4 additions & 3 deletions src/easy/cloud.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::easy::errors::CredentialsError;
use std::{env, str::FromStr};

use crate::error::CredentialsError;

#[derive(Debug, Clone, Copy)]
pub enum FalconCloud {
Us1,
Expand All @@ -24,7 +25,7 @@ impl FalconCloud {
}

pub fn from_env() -> Result<Self, CredentialsError> {
let cloud_str = env::var("FALCON_CLOUD").map_err(|_| CredentialsError("Missing FALCON_CLOUD environment variable. Please provide your Falcon Cloud region".to_string()))?;
let cloud_str = env::var("FALCON_CLOUD").map_err(|_| CredentialsError::CloudEnv)?;
Self::from_str(cloud_str.as_str())
}
}
Expand All @@ -38,7 +39,7 @@ impl FromStr for FalconCloud {
"us-2" => Ok(Self::Us2),
"eu-1" => Ok(Self::Eu1),
"us-gov-1" => Ok(Self::UsGov1),
_ => Err(CredentialsError(format!("Invalid FALCON_CLOUD specifier: '{s}'. Supported values are: us-1, us-2, eu-1, us-gov-1"))),
_ => Err(CredentialsError::Cloud(s.to_string())),
}
}
}
10 changes: 0 additions & 10 deletions src/easy/errors.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/easy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//! Easy interfaces to get started!
pub mod client;
pub mod cloud;
pub mod errors;
23 changes: 23 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::apis::oauth2_api::Oauth2AccessTokenError;

#[derive(Debug, thiserror::Error)]
pub enum CredentialsError {
#[error("Missing FALCON_CLIENT_ID environment variable. Please provide your OAuth2 API Client Secret for authentication with CrowdStrike Falcon platform. Establishing and retrieving OAuth2 API credentials can be performed at https://falcon.crowdstrike.com/support/api-clients-and-keys.")]
ClientID,
#[error("Missing FALCON_CLIENT_SECRET environment variable. Please provide your OAuth2 API Client Secret for authentication with CrowdStrike Falcon platform. Establishing and retrieving OAuth2 API credentials can be performed at https://falcon.crowdstrike.com/support/api-clients-and-keys.")]
Secret,
#[error(
"Invalid FALCON_CLOUD specifier: '{0}'. Supported values are: us-1, us-2, eu-1, us-gov-1"
)]
Cloud(String),
#[error("FALCON_CLOUD env variable is not set")]
CloudEnv,
#[error("Oauth error: {0}")]
Oauth(#[from] crate::apis::Error<Oauth2AccessTokenError>),
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Credentials error: {0}")]
Credentials(#[from] CredentialsError),
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ extern crate url;
#[allow(clippy::all, clippy::pedantic)]
pub mod apis;
pub mod easy;
pub mod error;
// Disable lints in this module as it's autogenerated, we can run it manually if required
#[allow(clippy::all, clippy::pedantic)]
pub mod models;

0 comments on commit ea02116

Please sign in to comment.