From bc709f0f23be345a1e2ccd06acd36bd5dac40bde Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Fri, 12 Jul 2024 00:29:57 +0200 Subject: Restructure server --- gitolfs3-server/src/api.rs | 213 ++++++++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 101 deletions(-) (limited to 'gitolfs3-server/src/api.rs') diff --git a/gitolfs3-server/src/api.rs b/gitolfs3-server/src/api.rs index dba7ada..d71d188 100644 --- a/gitolfs3-server/src/api.rs +++ b/gitolfs3-server/src/api.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use axum::{ async_trait, extract::{rejection, FromRequest, FromRequestParts, Request}, - http::{header, request::Parts, HeaderValue, StatusCode}, + http, response::{IntoResponse, Response}, Extension, Json, }; @@ -11,79 +11,21 @@ use chrono::{DateTime, Utc}; use gitolfs3_common::{Oid, Operation}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -pub const REPO_NOT_FOUND: GitLfsErrorResponse = - make_error_resp(StatusCode::NOT_FOUND, "Repository not found"); - -#[derive(Clone)] -pub struct RepositoryName(pub String); - -pub struct RepositoryNameRejection; - -impl IntoResponse for RepositoryNameRejection { - fn into_response(self) -> Response { - (StatusCode::INTERNAL_SERVER_ERROR, "Missing repository name").into_response() - } -} - -#[async_trait] -impl FromRequestParts for RepositoryName { - type Rejection = RepositoryNameRejection; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let Ok(Extension(repo_name)) = Extension::::from_request_parts(parts, state).await - else { - return Err(RepositoryNameRejection); - }; - Ok(repo_name) - } -} +// ----------------------- Generic facilities ---------------------- -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -pub enum TransferAdapter { - #[serde(rename = "basic")] - Basic, - #[serde(other)] - Unknown, -} +pub type GitLfsErrorResponse<'a> = (http::StatusCode, GitLfsJson>); -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] -pub enum HashAlgo { - #[serde(rename = "sha256")] - Sha256, - #[serde(other)] - Unknown, -} - -impl Default for HashAlgo { - fn default() -> Self { - Self::Sha256 - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -pub struct BatchRequestObject { - pub oid: Oid, - pub size: i64, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -struct BatchRef { - name: String, +#[derive(Debug, Serialize)] +pub struct GitLfsErrorData<'a> { + pub message: &'a str, } -fn default_transfers() -> Vec { - vec![TransferAdapter::Basic] +pub const fn make_error_resp(code: http::StatusCode, message: &str) -> GitLfsErrorResponse { + (code, GitLfsJson(Json(GitLfsErrorData { message }))) } -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -pub struct BatchRequest { - pub operation: Operation, - #[serde(default = "default_transfers")] - pub transfers: Vec, - pub objects: Vec, - #[serde(default)] - pub hash_algo: HashAlgo, -} +pub const REPO_NOT_FOUND: GitLfsErrorResponse = + make_error_resp(http::StatusCode::NOT_FOUND, "Repository not found"); #[derive(Debug, Clone)] pub struct GitLfsJson(pub Json); @@ -100,7 +42,7 @@ impl IntoResponse for GitLfsJsonRejection { match self { Self::Json(rej) => rej.into_response(), Self::MissingGitLfsJsonContentType => make_error_resp( - StatusCode::UNSUPPORTED_MEDIA_TYPE, + http::StatusCode::UNSUPPORTED_MEDIA_TYPE, &format!("Expected request with `Content-Type: {LFS_MIME}`"), ) .into_response(), @@ -125,7 +67,7 @@ pub fn is_git_lfs_json_mimetype(mimetype: &str) -> bool { } fn has_git_lfs_json_content_type(req: &Request) -> bool { - let Some(content_type) = req.headers().get(header::CONTENT_TYPE) else { + let Some(content_type) = req.headers().get(http::header::CONTENT_TYPE) else { return false; }; let Ok(content_type) = content_type.to_str() else { @@ -158,46 +100,98 @@ impl IntoResponse for GitLfsJson { let GitLfsJson(json) = self; let mut resp = json.into_response(); resp.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("application/vnd.git-lfs+json; charset=utf-8"), + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("application/vnd.git-lfs+json; charset=utf-8"), ); resp } } -#[derive(Debug, Serialize)] -pub struct GitLfsErrorData<'a> { - pub message: &'a str, +#[derive(Clone)] +pub struct RepositoryName(pub String); + +pub struct RepositoryNameRejection; + +impl IntoResponse for RepositoryNameRejection { + fn into_response(self) -> Response { + ( + http::StatusCode::INTERNAL_SERVER_ERROR, + "Missing repository name", + ) + .into_response() + } } -pub type GitLfsErrorResponse<'a> = (StatusCode, GitLfsJson>); +#[async_trait] +impl FromRequestParts for RepositoryName { + type Rejection = RepositoryNameRejection; -pub const fn make_error_resp(code: StatusCode, message: &str) -> GitLfsErrorResponse { - (code, GitLfsJson(Json(GitLfsErrorData { message }))) + async fn from_request_parts( + parts: &mut http::request::Parts, + state: &S, + ) -> Result { + let Ok(Extension(repo_name)) = Extension::::from_request_parts(parts, state).await + else { + return Err(RepositoryNameRejection); + }; + Ok(repo_name) + } } -#[derive(Debug, Serialize, Clone)] -pub struct BatchResponseObjectAction { - pub href: String, - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub header: HashMap, - pub expires_at: DateTime, +// ----------------------- Git LFS Batch API ----------------------- + +#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] +pub struct BatchRequest { + pub operation: Operation, + #[serde(default = "default_transfers")] + pub transfers: Vec, + pub objects: Vec, + #[serde(default)] + pub hash_algo: HashAlgo, } -#[derive(Default, Debug, Serialize, Clone)] -pub struct BatchResponseObjectActions { - #[serde(skip_serializing_if = "Option::is_none")] - pub upload: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub download: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub verify: Option, +#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] +pub struct BatchRequestObject { + pub oid: Oid, + pub size: i64, } -#[derive(Debug, Clone, Serialize)] -pub struct BatchResponseObjectError { - pub code: u16, - pub message: String, +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] +pub enum TransferAdapter { + #[serde(rename = "basic")] + Basic, + #[serde(other)] + Unknown, +} + +fn default_transfers() -> Vec { + vec![TransferAdapter::Basic] +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)] +pub enum HashAlgo { + #[serde(rename = "sha256")] + Sha256, + #[serde(other)] + Unknown, +} + +impl Default for HashAlgo { + fn default() -> Self { + Self::Sha256 + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +struct BatchRef { + name: String, +} + +#[derive(Debug, Serialize, Clone)] +pub struct BatchResponse { + pub transfer: TransferAdapter, + pub objects: Vec, + pub hash_algo: HashAlgo, } #[derive(Debug, Serialize, Clone)] @@ -211,10 +205,16 @@ pub struct BatchResponseObject { pub error: Option, } +#[derive(Debug, Clone, Serialize)] +pub struct BatchResponseObjectError { + pub code: u16, + pub message: String, +} + impl BatchResponseObject { pub fn error( obj: &BatchRequestObject, - code: StatusCode, + code: http::StatusCode, message: String, ) -> BatchResponseObject { BatchResponseObject { @@ -231,10 +231,21 @@ impl BatchResponseObject { } #[derive(Debug, Serialize, Clone)] -pub struct BatchResponse { - pub transfer: TransferAdapter, - pub objects: Vec, - pub hash_algo: HashAlgo, +pub struct BatchResponseObjectAction { + pub href: String, + #[serde(skip_serializing_if = "HashMap::is_empty")] + pub header: HashMap, + pub expires_at: DateTime, +} + +#[derive(Default, Debug, Serialize, Clone)] +pub struct BatchResponseObjectActions { + #[serde(skip_serializing_if = "Option::is_none")] + pub upload: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub download: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub verify: Option, } #[test] -- cgit v1.2.3