loonfs-server 0.2.0

The reference LoonFS HTTP server.
Documentation
//! The `admin/v0` routes whose subject is the backing store itself rather
//! than one namespace.

use super::{AppState, OptionalAppJson};
use crate::http::error::ApiResponseError;
use axum::extract::State;
use axum::Json;
use loonfs_api::v0::{
    StoreProbeCheckOutcome, StoreProbeCheckResult, StoreProbeRequest, StoreProbeResponse,
};
#[cfg(feature = "openapi")]
use loonfs_api::ApiError;
use loonfs_objectstore::probe::{run_store_contract_probe, StoreProbeOutcome};

#[cfg_attr(
    feature = "openapi",
    utoipa::path(
        post,
        path = "/v0/admin/store/probe",
        tag = "admin",
        summary = "Probe the store contract",
        description = "Proves the configured object store honours the create-if-absent, compare-and-swap, visibility, listing, and ranged-read semantics LoonFS depends on, and reports what it found check by check. Nothing runs this implicitly: a probe writes and deletes objects, all of them under a scratch prefix that is not a durable object family, and its last check deletes them and proves the prefix empty. A store that fails a check answers 200 with that check reported `failed` — the probe ran, and the answer is that the store is wrong. Optional capabilities a store declares it lacks answer `unsupported`, which is an answer rather than a fault. This route does not decide whether the deployment may serve presigned direct uploads: that trust comes from the endpoint allowlist, because a probe exercises the server's own request path and never a presigned capability handed to a client.",
        request_body(content = StoreProbeRequest, description = "Probe options; send `{}` for defaults"),
        responses(
            (status = 200, description = "Probe completed; per-check outcomes are in the body", body = StoreProbeResponse),
            (status = 400, description = "Malformed request body", body = ApiError),
            (status = 401, description = "Unauthorized", body = ApiError)
        )
    )
)]
pub(super) async fn probe_store(
    State(state): State<AppState>,
    OptionalAppJson(request): OptionalAppJson<StoreProbeRequest>,
) -> Result<Json<StoreProbeResponse>, ApiResponseError> {
    let StoreProbeRequest {} = request.unwrap_or_default();
    // The run id scopes the objects this run writes, so two probes against
    // one store never collide, and a provider's own log names the run.
    let run_id = loonfs_api::generated_id("probe");
    let report = run_store_contract_probe(state.probe_store.as_ref(), &run_id).await;
    Ok(Json(StoreProbeResponse {
        run_id: report.run_id,
        checks: report
            .checks
            .into_iter()
            .map(|check| {
                let (outcome, message) = match check.outcome {
                    StoreProbeOutcome::Passed => (StoreProbeCheckOutcome::Passed, None),
                    StoreProbeOutcome::Unsupported => (StoreProbeCheckOutcome::Unsupported, None),
                    StoreProbeOutcome::Failed { message } => {
                        (StoreProbeCheckOutcome::Failed, Some(message))
                    }
                };
                StoreProbeCheckResult {
                    name: check.name.to_owned(),
                    outcome,
                    message,
                }
            })
            .collect(),
    }))
}