Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use strongly typed errors based on thiserror #134

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
run: cargo test --locked --all-features --all-targets
# https://github.com/rust-lang/cargo/issues/6669
- name: cargo test --doc
run: cargo test --locked --all-features --doc | grep 'Missing FALCON_CLIENT_ID environment variable.'
run: cargo test --locked --all-features --doc | grep 'Could not authenticate with `CrowdStrike` API: ClientID'

nightly:
name: Rust nightly
Expand All @@ -66,7 +66,7 @@ jobs:
cargo test --lib --bins --examples --benches
- run: |
# We expect doc example to fail without Falcon Credentials
cargo test --doc | grep 'Missing FALCON_CLIENT_ID environment variable.'
cargo test --doc | grep 'Could not authenticate with `CrowdStrike` API: ClientID'
minimal:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update example code to follow the latest OpenAPI spec changes
- Fix links in the README
- Fix spelling for docs and api files
- Error hierarchy revamp to structured errors based on `thiserror` crate


## [0.3.3] - 2023-09-04

Expand Down
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.12.7", 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
93 changes: 16 additions & 77 deletions src/apis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,97 +1,36 @@
use std::error;
use std::fmt;
use std::fmt::{Debug, Display};

#[derive(Debug, Clone)]
pub struct ResponseContent<T> {
pub struct ResponseContent<T: 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: Debug> 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: 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: Debug> std::error::Error for ResponseContent<T> {}

impl<T> From<reqwest::Error> for Error<T> {
fn from(e: reqwest::Error) -> Self {
Error::Reqwest(e)
}
}

impl<T> From<serde_json::Error> for Error<T> {
fn from(e: serde_json::Error) -> Self {
Error::Serde(e)
}
}

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: Debug> {
#[error(transparent)]
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 api_integrations_api;
pub mod aspm_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, 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;