backbone-mail 0.2.32

Odoo mail core port — message/notification/followers/activity/alias/sms queue (schema: messaging)
//! Version Negotiation Middleware
//!
//! Generated by metaphor-schema. Do not edit manually.
//!
//! Provides middleware for API version negotiation.

use axum::{
    extract::Request,
    http::{HeaderMap, HeaderValue, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
};
use super::version::{ApiVersion, CURRENT_VERSION, SUPPORTED_VERSIONS};

// =============================================================================
// Request Extension
// =============================================================================

/// Extension to carry resolved API version through request
#[derive(Debug, Clone, Copy)]
pub struct ResolvedVersion(pub ApiVersion);

// =============================================================================
// Version Negotiation Middleware
// =============================================================================

/// Middleware that negotiates API version from request
///
/// Supports multiple strategies:
/// 1. URL path: /api/v1/resource
/// 2. Accept header: Accept: application/vnd.api+json;version=1
/// 3. Query param: ?version=1
/// 4. Custom header: X-API-Version: 1
pub async fn version_negotiation_middleware(
    mut request: Request,
    next: Next,
) -> Response {
    // Try to extract version from various sources
    let version = extract_version_from_path(request.uri().path())
        .or_else(|| extract_version_from_header(request.headers()))
        .or_else(|| extract_version_from_query(request.uri().query()))
        .unwrap_or(CURRENT_VERSION);
    
    // Add version to request extensions
    request.extensions_mut().insert(ResolvedVersion(version));
    
    // Add deprecation warning if version is deprecated
    let mut response = next.run(request).await;
    
    if version.is_deprecated() {
        if let Some(msg) = version.deprecation_message() {
            response.headers_mut().insert(
                "Deprecation",
                HeaderValue::from_str(msg).unwrap_or_else(|_| HeaderValue::from_static("true")),
            );
        }
        if let Some(date) = version.sunset_date() {
            if let Ok(val) = HeaderValue::from_str(date) {
                response.headers_mut().insert("Sunset", val);
            }
        }
    }
    
    // Add API-Version header to response
    response.headers_mut().insert(
        "X-API-Version",
        HeaderValue::from_str(version.as_str()).unwrap_or_else(|_| HeaderValue::from_static("v1")),
    );
    
    response
}

// =============================================================================
// Version Extraction Helpers
// =============================================================================

/// Extract version from URL path (e.g., /api/v1/users)
fn extract_version_from_path(path: &str) -> Option<ApiVersion> {
    // Look for /v1/, /v2/, etc. in path
    for version in SUPPORTED_VERSIONS {
        let version_segment = format!("/{}/", version.as_str());
        if path.contains(&version_segment) {
            return Some(*version);
        }
        // Also check if path ends with version
        let version_end = format!("/{}", version.as_str());
        if path.ends_with(&version_end) {
            return Some(*version);
        }
    }
    None
}

/// Extract version from Accept header
fn extract_version_from_header(headers: &HeaderMap) -> Option<ApiVersion> {
    // Check X-API-Version header first
    if let Some(value) = headers.get("X-API-Version") {
        if let Ok(s) = value.to_str() {
            if let Ok(v) = s.parse() {
                return Some(v);
            }
        }
    }
    
    // Check Accept header for version parameter
    if let Some(accept) = headers.get("Accept") {
        if let Ok(s) = accept.to_str() {
            // Look for version=X in Accept header
            if let Some(version_part) = s.split(';').find(|p| p.trim().starts_with("version=")) {
                let version_str = version_part.trim().trim_start_matches("version=");
                if let Ok(v) = version_str.parse() {
                    return Some(v);
                }
            }
        }
    }
    
    None
}

/// Extract version from query parameter
fn extract_version_from_query(query: Option<&str>) -> Option<ApiVersion> {
    let query = query?;
    
    for param in query.split('&') {
        if let Some(value) = param.strip_prefix("version=") {
            if let Ok(v) = value.parse() {
                return Some(v);
            }
        }
    }
    
    None
}