Skip to main content
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.
proxy
String
The proxy URL that was provided in the configuration.
source
reqwest::Error
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.
ClientBuild
reqwest::Error
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.
url
String
The URL that was being requested when the error occurred.
source
reqwest::Error
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

  1. Always handle errors - Use match or ? operator to handle Result types
  2. Match specific variants - Different error types require different handling strategies
  3. Provide context - Include the error details in logs and user messages
  4. Don’t retry everything - Only retry transient errors like Request, not configuration errors
  5. Log for debugging - Use the Debug trait to get full error details during development
  6. 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(())
}

Build docs developers (and LLMs) love