The whatwaf library uses a custom ScanError enum to represent different failure modes during WAF scanning. Proper error handling ensures your application can gracefully handle network issues, configuration problems, and request failures.
ScanError enum
The ScanError type defines all possible errors:
#[derive(Debug)]
pub enum ScanError {
InvalidProxy {
proxy: String,
source: reqwest::Error,
},
ClientBuild(reqwest::Error),
Request {
url: String,
source: reqwest::Error,
},
}
Error variants
InvalidProxy
Occurs when the proxy configuration is invalid or cannot be used.
The proxy URL that was provided in the configuration.
The underlying error from the reqwest HTTP client.
Common causes:
- Malformed proxy URL format
- Unsupported proxy protocol
- Invalid host or port
Example:
ScanError::InvalidProxy {
proxy: "invalid://proxy:8080".to_string(),
source: /* reqwest error */,
}
ClientBuild
Occurs when the HTTP client fails to build, typically due to system or TLS configuration issues.
The underlying error from reqwest when building the HTTP client.
Common causes:
- TLS/SSL initialization failure
- System configuration issues
- Resource limitations
Example:
ScanError::ClientBuild(/* reqwest error */)
Request
Occurs when an HTTP request fails during probe execution.
The URL that was being requested when the error occurred.
The underlying network or HTTP error from reqwest.
Common causes:
- Network connectivity issues
- DNS resolution failure
- Connection timeout
- TLS/SSL errors
- Server unreachable
Example:
ScanError::Request {
url: "https://example.com?q=test".to_string(),
source: /* reqwest error */,
}
Error display
The ScanError type implements Display, providing human-readable error messages:
- InvalidProxy:
"invalid proxy 'http://bad:8080': <error details>"
- ClientBuild:
"failed to build HTTP client: <error details>"
- Request:
"request failed for https://example.com: <error details>"
Error handling patterns
Basic error handling
use whatwaf::{scan_url, ScanConfig, ScanError};
fn main() {
let config = ScanConfig {
timeout: 10,
follow_redirects: true,
proxy: None,
};
match scan_url("https://example.com", config, None) {
Ok(result) => {
if let Some(probe_result) = result {
println!("Scan complete: {:?}", probe_result);
}
}
Err(e) => {
eprintln!("Scan failed: {}", e);
std::process::exit(1);
}
}
}
Detailed error matching
use whatwaf::{scan_url, ScanConfig, ScanError};
fn perform_scan(url: &str) -> Result<(), Box<dyn std::error::Error>> {
let config = ScanConfig {
timeout: 10,
follow_redirects: true,
proxy: Some("http://127.0.0.1:8080".to_string()),
};
match scan_url(url, config, None) {
Ok(result) => {
if let Some(probe_result) = result {
println!("Success: {:?}", probe_result.detected_wafs);
}
Ok(())
}
Err(ScanError::InvalidProxy { proxy, source }) => {
eprintln!("Proxy '{}' is invalid: {}", proxy, source);
eprintln!("Please check your proxy configuration.");
Err(Box::new(source))
}
Err(ScanError::ClientBuild(e)) => {
eprintln!("Failed to initialize HTTP client: {}", e);
eprintln!("This may be a TLS or system configuration issue.");
Err(Box::new(e))
}
Err(ScanError::Request { url, source }) => {
eprintln!("Request to {} failed: {}", url, source);
eprintln!("Check network connectivity and target availability.");
Err(Box::new(source))
}
}
}
fn main() {
if let Err(e) = perform_scan("https://example.com") {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
Retry logic for request errors
use whatwaf::{scan_url, ScanConfig, ScanError};
use std::thread;
use std::time::Duration;
fn scan_with_retry(url: &str, max_retries: u32) -> Result<(), Box<dyn std::error::Error>> {
let config = ScanConfig {
timeout: 10,
follow_redirects: true,
proxy: None,
};
for attempt in 1..=max_retries {
match scan_url(url, config.clone(), None) {
Ok(result) => {
println!("Scan succeeded on attempt {}", attempt);
if let Some(probe_result) = result {
println!("Result: {:?}", probe_result.detected_wafs);
}
return Ok(());
}
Err(ScanError::Request { url: failed_url, source }) => {
eprintln!("Attempt {}: Request to {} failed: {}", attempt, failed_url, source);
if attempt < max_retries {
let wait_time = Duration::from_secs(2u64.pow(attempt));
eprintln!("Retrying in {:?}...", wait_time);
thread::sleep(wait_time);
} else {
eprintln!("Max retries reached. Giving up.");
return Err(Box::new(source));
}
}
Err(e) => {
// Don't retry proxy or client build errors
eprintln!("Non-retriable error: {}", e);
return Err(Box::new(e));
}
}
}
unreachable!()
}
fn main() {
if let Err(e) = scan_with_retry("https://example.com", 3) {
eprintln!("Scan failed: {}", e);
std::process::exit(1);
}
}
Graceful fallback without proxy
use whatwaf::{scan_url, ScanConfig, ScanError};
fn scan_with_fallback(url: &str, proxy: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
let config = ScanConfig {
timeout: 10,
follow_redirects: true,
proxy: proxy.clone(),
};
match scan_url(url, config, None) {
Ok(result) => {
println!("Scan completed successfully");
if let Some(probe_result) = result {
println!("Result: {:?}", probe_result.detected_wafs);
}
Ok(())
}
Err(ScanError::InvalidProxy { .. }) if proxy.is_some() => {
eprintln!("Proxy failed. Retrying without proxy...");
let fallback_config = ScanConfig {
timeout: 10,
follow_redirects: true,
proxy: None,
};
match scan_url(url, fallback_config, None) {
Ok(result) => {
println!("Scan succeeded without proxy");
if let Some(probe_result) = result {
println!("Result: {:?}", probe_result.detected_wafs);
}
Ok(())
}
Err(e) => {
eprintln!("Fallback also failed: {}", e);
Err(Box::new(e))
}
}
}
Err(e) => {
eprintln!("Scan error: {}", e);
Err(Box::new(e))
}
}
}
fn main() {
let proxy = Some("http://127.0.0.1:8080".to_string());
if let Err(e) = scan_with_fallback("https://example.com", proxy) {
eprintln!("Final error: {}", e);
std::process::exit(1);
}
}
Best practices
- Always handle errors - Use
match or ? operator to handle Result types
- Match specific variants - Different error types require different handling strategies
- Provide context - Include the error details in logs and user messages
- Don’t retry everything - Only retry transient errors like
Request, not configuration errors
- Log for debugging - Use the
Debug trait to get full error details during development
- Implement
From conversions - Convert ScanError to your application’s error type if needed
Converting to custom error types
use whatwaf::ScanError;
use std::fmt;
#[derive(Debug)]
enum AppError {
Scan(ScanError),
Config(String),
Other(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Scan(e) => write!(f, "Scan error: {}", e),
AppError::Config(msg) => write!(f, "Config error: {}", msg),
AppError::Other(msg) => write!(f, "Error: {}", msg),
}
}
}
impl std::error::Error for AppError {}
impl From<ScanError> for AppError {
fn from(e: ScanError) -> Self {
AppError::Scan(e)
}
}
fn main() -> Result<(), AppError> {
let config = whatwaf::ScanConfig {
timeout: 10,
follow_redirects: true,
proxy: None,
};
// The ? operator automatically converts ScanError to AppError
let result = whatwaf::scan_url("https://example.com", config, None)?;
println!("Scan result: {:?}", result);
Ok(())
}