mrapids 0.1.31

Your OpenAPI, but executable
Documentation
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

/// Complete security analysis of an OpenAPI specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityAnalysis {
    /// All security schemes defined in the spec
    pub schemes: HashMap<String, SecuritySchemeDetails>,
    /// Global security requirements (apply to all operations unless overridden)
    pub global_requirements: SecurityRequirement,
    /// Per-operation security requirements (overrides global)
    pub operation_requirements: HashMap<String, SecurityRequirement>,
    /// Summary statistics and insights
    pub summary: AuthSummary,
}

/// Detailed information about a security scheme
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecuritySchemeDetails {
    pub scheme_type: SchemeType,
    pub location: Option<AuthLocation>,
    pub name: Option<String>,
    pub bearer_format: Option<String>,
    pub flows: Option<OAuth2Flows>,
    pub openid_connect_url: Option<String>,
    pub description: Option<String>,
}

/// Type of authentication scheme
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SchemeType {
    ApiKey,
    Http,
    OAuth2,
    OpenIdConnect,
    MutualTls,
}

/// Location where auth credential is sent
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum AuthLocation {
    Query,
    Header,
    Cookie,
}

/// OAuth2 flow configurations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuth2Flows {
    pub implicit: Option<OAuth2Flow>,
    pub password: Option<OAuth2Flow>,
    pub client_credentials: Option<OAuth2Flow>,
    pub authorization_code: Option<OAuth2Flow>,
    pub device_code: Option<OAuth2Flow>, // Extension for device flow
}

/// Individual OAuth2 flow configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuth2Flow {
    pub authorization_url: Option<String>,
    pub token_url: Option<String>,
    pub refresh_url: Option<String>,
    pub scopes: HashMap<String, String>,
    pub device_url: Option<String>, // Extension for device flow
}

/// Security requirement with OR semantics between options
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SecurityRequirement {
    /// Each option is a valid way to authenticate (OR relationship)
    pub options: Vec<RequirementOption>,
}

/// A single authentication option (AND relationship between schemes)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequirementOption {
    /// All schemes in this option must be satisfied (AND relationship)
    pub schemes: Vec<SchemeRequirement>,
}

/// Requirement for a specific security scheme
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemeRequirement {
    pub name: String,
    pub scopes: Vec<String>,
}

/// Summary of authentication requirements across the API
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthSummary {
    pub total_schemes: usize,
    pub required_schemes: HashSet<String>,
    pub optional_schemes: HashSet<String>,
    pub operations_with_custom_auth: usize,
    pub operations_without_auth: usize,
    pub most_common_scheme: Option<String>,
    pub complexity_score: AuthComplexity,
}

/// Authentication complexity assessment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthComplexity {
    /// No auth required
    None,
    /// Single auth method for all operations
    Simple,
    /// Multiple auth options but consistent
    Moderate,
    /// Different auth per operation or complex requirements
    Complex,
}

impl SecurityRequirement {
    /// Check if this requirement is satisfied by the provided credentials
    #[allow(dead_code)]
    pub fn is_satisfied_by(&self, available_schemes: &HashSet<String>) -> bool {
        // At least one option must be fully satisfied
        self.options.iter().any(|option| {
            // All schemes in the option must be available
            option
                .schemes
                .iter()
                .all(|scheme| available_schemes.contains(&scheme.name))
        })
    }

    /// Get the simplest option (fewest required schemes)
    pub fn simplest_option(&self) -> Option<&RequirementOption> {
        self.options.iter().min_by_key(|opt| opt.schemes.len())
    }

    /// Check if authentication is optional (empty requirement)
    pub fn is_optional(&self) -> bool {
        self.options.is_empty() || self.options.iter().any(|opt| opt.schemes.is_empty())
    }
}