lfsx-server 1.23.3

A fast, lightweight, secure Git LFS server
Documentation
use serde::Deserialize;

use crate::error::Error;
use crate::storage::s3::keyspace::Entry;

#[derive(Deserialize)]
struct EnumerationResults {
    #[serde(rename = "Blobs", default)]
    blobs: Blobs,
    #[serde(rename = "NextMarker", default)]
    next_marker: Option<String>,
}

#[derive(Deserialize, Default)]
struct Blobs {
    #[serde(rename = "Blob", default)]
    blob: Vec<Blob>,
}

#[derive(Deserialize)]
struct Blob {
    #[serde(rename = "Name")]
    name: String,
    #[serde(rename = "Properties")]
    properties: Properties,
}

#[derive(Deserialize)]
struct Properties {
    #[serde(rename = "Last-Modified")]
    last_modified: String,
    #[serde(rename = "Content-Length")]
    content_length: u64,
}

pub(crate) struct Page {
    pub(crate) entries: Vec<Entry>,
    pub(crate) next: Option<String>,
}

pub(crate) fn parse(body: &str) -> Result<Page, Error> {
    let results: EnumerationResults = quick_xml::de::from_str(body).map_err(|error| {
        Error::Storage(std::io::Error::other(format!(
            "the object store sent a listing this server could not read: {error}"
        )))
    })?;

    Ok(Page {
        entries: results
            .blobs
            .blob
            .into_iter()
            .map(|blob| Entry {
                key: blob.name,
                modified: http_date(&blob.properties.last_modified),
                size: blob.properties.content_length,
            })
            .collect(),
        next: results.next_marker.filter(|marker| !marker.is_empty()),
    })
}

fn http_date(value: &str) -> Option<time::OffsetDateTime> {
    let format = time::format_description::parse_borrowed::<1>(
        "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT",
    )
    .ok()?;

    time::PrimitiveDateTime::parse(value, &format)
        .ok()
        .map(time::PrimitiveDateTime::assume_utc)
}

#[cfg(test)]
mod tests;