imgforge 0.17.0

Fast and secure image proxy and transformation server
Documentation
use crate::app::AppState;
use crate::service::{self, CacheStatus, ProcessRequest};
use axum::extract::{Path, State};
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Json};
use axum_extra::headers::{authorization::Bearer, Authorization};
use axum_extra::TypedHeader;
use serde_json::json;
use std::sync::Arc;
use tracing::error;

/// Handles the /status endpoint, returning a simple JSON status.
pub async fn status_handler() -> impl IntoResponse {
    (StatusCode::OK, Json(json!({"status": "ok"})))
}

/// Handles the /info/{*path} endpoint, returning metadata about the source image.
pub async fn info_handler(
    State(state): State<Arc<AppState>>,
    Path(path): Path<String>,
    auth_header: Option<TypedHeader<Authorization<Bearer>>>,
) -> impl IntoResponse {
    let bearer = auth_header.map(|TypedHeader(auth)| auth.token().to_string());

    match service::image_info(
        state.clone(),
        ProcessRequest {
            path: &path,
            bearer_token: bearer.as_deref(),
        },
    )
    .await
    {
        Ok(info) => {
            let response = json!({
                "width": info.width,
                "height": info.height,
                "format": info.format,
                "content_type": info.content_type,
                "size_bytes": info.size_bytes,
                "channels": info.channels,
                "has_alpha": info.has_alpha,
                "orientation": info.orientation,
            });
            (StatusCode::OK, Json(response)).into_response()
        }
        Err(err) => {
            error!(path, error = ?err, "Info handler error");
            (err.status(), err.message().into_owned()).into_response()
        }
    }
}

/// Handles the main image processing endpoint.
pub async fn image_forge_handler(
    State(state): State<Arc<AppState>>,
    Path(path): Path<String>,
    auth_header: Option<TypedHeader<Authorization<Bearer>>>,
) -> impl IntoResponse {
    let bearer = auth_header.map(|TypedHeader(auth)| auth.token().to_string());

    match service::process_path(
        state,
        ProcessRequest {
            path: &path,
            bearer_token: bearer.as_deref(),
        },
    )
    .await
    {
        Ok(result) => {
            let mut headers = header::HeaderMap::new();
            headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(result.content_type));
            if result.cache_status == CacheStatus::Hit {
                headers.insert(
                    header::CACHE_STATUS,
                    HeaderValue::from_static(CacheStatus::Hit.as_header_value()),
                );
            }
            if let Some(content_disposition) = result.content_disposition {
                match HeaderValue::from_str(&content_disposition) {
                    Ok(value) => {
                        headers.insert(header::CONTENT_DISPOSITION, value);
                    }
                    Err(err) => {
                        error!("Invalid Content-Disposition header value: {}", err);
                    }
                }
            }

            (StatusCode::OK, headers, result.bytes).into_response()
        }
        Err(err) => {
            error!(path, error = ?err, "Image handler error");
            (err.status(), err.message().into_owned()).into_response()
        }
    }
}