Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ name = "common"
path = "src/lib.rs"

[dependencies]
axum = { workspace = true }
anyhow = { workspace = true }
sea-orm = { workspace = true }
thiserror = { workspace = true }
Expand Down
39 changes: 37 additions & 2 deletions common/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use anyhow::Result;
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use thiserror::Error;

use anyhow::Result;
use crate::model::CommonResult;

pub type MegaResult = Result<(), MegaError>;

Expand Down Expand Up @@ -75,7 +81,6 @@ pub enum GitLFSError {
GeneralError(String),
}


#[derive(Debug, Error)]
pub enum ProtocolError {
#[error("{0}")]
Expand All @@ -84,6 +89,36 @@ pub enum ProtocolError {
Deny(String),
#[error("Repository not found: {0}")]
NotFound(String),
#[error("Invalid Input: {0}")]
InvalidInput(String),
#[error("HTTP Push Has Been Disabled")]
Disabled,
}

impl IntoResponse for ProtocolError {
fn into_response(self) -> Response {
let (status, message) = match self {
ProtocolError::Deny(err) => {
// This error is caused by bad user input so don't log it
(StatusCode::UNAUTHORIZED, err)
}
ProtocolError::NotFound(err) => {
// Because `TraceLayer` wraps each request in a span that contains the request
// method, uri, etc we don't need to include those details here
// tracing::error!(%err, "error");

// Don't expose any details about the error to the client
(StatusCode::NOT_FOUND, err)
}
ProtocolError::InvalidInput(err) => (StatusCode::BAD_REQUEST, err),
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
"Something went wrong".to_owned(),
),
};

(status, Json(CommonResult::<String>::failed(&message))).into_response()
}
}

#[cfg(test)]
Expand Down
28 changes: 18 additions & 10 deletions mono/src/api/api_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ceres::{
tree::{LatestCommitInfo, TreeBriefItem, TreeCommitItem},
},
};
use common::model::CommonResult;
use common::{errors::ProtocolError, model::CommonResult};
use http::StatusCode;
use taurus::event::api_request::{ApiRequestEvent, ApiType};

Expand All @@ -32,8 +32,8 @@ pub fn routers() -> Router<MonoApiServiceState> {
.route("/tree", get(get_tree_info))
.route("/blob", get(get_blob_string))
.route("/file/blob/:object_id", get(get_blob_file))
.route("/file/tree", get(get_tree_file));

.route("/file/tree", get(get_tree_file))
.route("/path-can-clone", get(path_can_be_cloned));
Router::new()
.merge(router)
.merge(mr_router::routers())
Expand All @@ -47,7 +47,7 @@ async fn get_blob_string(
ApiRequestEvent::notify(ApiType::Blob, &state.0.context.config);
let res = state
.api_handler(query.path.clone().into())
.await
.await?
.get_blob_as_string(query.path.into())
.await;

Expand All @@ -69,7 +69,7 @@ async fn create_file(
ApiRequestEvent::notify(ApiType::CreateFile, &state.0.context.config);
let res = state
.api_handler(json.path.clone().into())
.await
.await?
.create_monorepo_file(json.clone())
.await;
let res = match res {
Expand All @@ -86,7 +86,7 @@ async fn get_latest_commit(
ApiRequestEvent::notify(ApiType::LastestCommit, &state.0.context.config);
let res = state
.api_handler(query.path.clone().into())
.await
.await?
.get_latest_commit(query.path.into())
.await?;
Ok(Json(res))
Expand All @@ -99,7 +99,7 @@ async fn get_tree_info(
ApiRequestEvent::notify(ApiType::TreeInfo, &state.0.context.config);
let res = state
.api_handler(query.path.clone().into())
.await
.await?
.get_tree_info(query.path.into())
.await;
let res = match res {
Expand All @@ -112,11 +112,11 @@ async fn get_tree_info(
async fn get_tree_commit_info(
Query(query): Query<CodePreviewQuery>,
state: State<MonoApiServiceState>,
) -> Result<Json<CommonResult<Vec<TreeCommitItem>>>, ApiError> {
) -> Result<Json<CommonResult<Vec<TreeCommitItem>>>, ProtocolError> {
ApiRequestEvent::notify(ApiType::CommitInfo, &state.0.context.config);
let res = state
.api_handler(query.path.clone().into())
.await
.await?
.get_tree_commit_info(query.path.into())
.await;
let res = match res {
Expand Down Expand Up @@ -155,7 +155,7 @@ pub async fn get_tree_file(
) -> Result<Response, ApiError> {
let res = state
.api_handler(query.path.clone().into())
.await
.await?
.get_tree_as_data(std::path::Path::new(&query.path))
.await;

Expand All @@ -174,3 +174,11 @@ pub async fn get_tree_file(
}),
}
}

async fn path_can_be_cloned(
Query(query): Query<BlobContentQuery>,
state: State<MonoApiServiceState>,
) -> Result<Json<CommonResult<bool>>, ApiError> {
let res = state.api_handler(query.path.clone().into()).await.is_ok();
Ok(Json(CommonResult::success(Some(res))))
}
16 changes: 10 additions & 6 deletions mono/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use ceres::{
},
protocol::repo::Repo,
};
use common::model::CommonOptions;
use common::{errors::ProtocolError, model::CommonOptions};
use jupiter::{context::Context, storage::user_storage::UserStorage};

pub mod api_router;
Expand Down Expand Up @@ -54,7 +54,7 @@ impl MonoApiServiceState {
}
}

async fn api_handler(&self, path: PathBuf) -> Box<dyn ApiHandler> {
async fn api_handler(&self, path: PathBuf) -> Result<Box<dyn ApiHandler>, ProtocolError> {
let import_dir = self.context.config.monorepo.import_dir.clone();
if path.starts_with(&import_dir) && path != import_dir {
if let Some(model) = self
Expand All @@ -66,14 +66,18 @@ impl MonoApiServiceState {
.unwrap()
{
let repo: Repo = model.into();
return Box::new(ImportApiService {
return Ok(Box::new(ImportApiService {
context: self.context.clone(),
repo,
});
}));
}
return Err(ProtocolError::InvalidInput(format!(
"Invalid Path{}",
path.to_str().unwrap()
)));
}
Box::new(MonoApiService {
Ok(Box::new(MonoApiService {
context: self.context.clone(),
})
}))
}
}
94 changes: 36 additions & 58 deletions mono/src/git_protocol/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::convert::Infallible;

use anyhow::Result;
use axum::body::Body;
use axum::http::{HeaderValue, Request, Response, StatusCode};
use axum::http::{HeaderValue, Request, Response};
use bytes::{Bytes, BytesMut};
use futures::{stream, TryStreamExt};
use tokio::io::AsyncReadExt;
Expand All @@ -21,19 +21,19 @@ use common::model::InfoRefsParams;
pub async fn git_info_refs(
params: InfoRefsParams,
mut pack_protocol: SmartProtocol,
) -> Result<Response<Body>, (StatusCode, String)> {
) -> Result<Response<Body>, ProtocolError> {
let service_name = params.service.unwrap();
pack_protocol.service_type = Some(service_name.parse::<ServiceType>().unwrap());

let response = match pack_protocol.git_info_refs().await {
Ok(pkt_line_stream) => Response::builder()
.body(Body::from(pkt_line_stream.freeze()))
.unwrap(),
Err(err) => return Ok(handler_err(err)),
};
let pkt_line_stream = pack_protocol.git_info_refs().await?;

let content_type = format!("application/x-{}-advertisement", service_name);
let response = add_default_header(content_type, response);
let response = add_default_header(
content_type,
Response::builder()
.body(Body::from(pkt_line_stream.freeze()))
.unwrap(),
);
Ok(response)
}

Expand All @@ -60,7 +60,7 @@ pub async fn git_info_refs(
pub async fn git_upload_pack(
req: Request<Body>,
mut pack_protocol: SmartProtocol,
) -> Result<Response<Body>, (StatusCode, String)> {
) -> Result<Response<Body>, ProtocolError> {
let upload_request: BytesMut = req
.into_body()
.into_data_stream()
Expand All @@ -71,42 +71,37 @@ pub async fn git_upload_pack(
.await
.unwrap();
tracing::debug!("bytes from client: {:?}", upload_request);
let response = match pack_protocol
let (mut send_pack_data, protocol_buf) = pack_protocol
.git_upload_pack(&mut upload_request.freeze())
.await
{
Ok((mut send_pack_data, protocol_buf)) => {
let body_stream = async_stream::stream! {
tracing::info!("send ack/nak message buf: {:?}", &protocol_buf);
yield Ok::<_, Infallible>(Bytes::copy_from_slice(&protocol_buf));
// send packdata with sideband64k
while let Some(chunk) = send_pack_data.next().await {
let mut reader = chunk.as_slice();
loop {
let mut temp = BytesMut::new();
temp.reserve(65500);
let length = reader.read_buf(&mut temp).await.unwrap();
if length == 0 {
break;
}
let bytes_out = pack_protocol.build_side_band_format(temp, length);
// tracing::info!("send pack file: length: {:?}", bytes_out.len());
yield Ok::<_, Infallible>(bytes_out.freeze());
}
.await?;

let body_stream = async_stream::stream! {
tracing::info!("send ack/nak message buf: {:?}", &protocol_buf);
yield Ok::<_, Infallible>(Bytes::copy_from_slice(&protocol_buf));
// send packdata with sideband64k
while let Some(chunk) = send_pack_data.next().await {
let mut reader = chunk.as_slice();
loop {
let mut temp = BytesMut::new();
temp.reserve(65500);
let length = reader.read_buf(&mut temp).await.unwrap();
if length == 0 {
break;
}
let bytes_out = Bytes::from_static(smart::PKT_LINE_END_MARKER);
tracing::info!("send back pkt-flush line '0000', actually: {:?}", bytes_out);
yield Ok::<_, Infallible>(bytes_out);
};
Response::builder()
.body(Body::from_stream(body_stream))
.unwrap()
let bytes_out = pack_protocol.build_side_band_format(temp, length);
// tracing::info!("send pack file: length: {:?}", bytes_out.len());
yield Ok::<_, Infallible>(bytes_out.freeze());
}
}
Err(err) => return Ok(handler_err(err)),
let bytes_out = Bytes::from_static(smart::PKT_LINE_END_MARKER);
tracing::info!("send back pkt-flush line '0000', actually: {:?}", bytes_out);
yield Ok::<_, Infallible>(bytes_out);
};
let response = add_default_header(
String::from("application/x-git-upload-pack-result"),
response,
Response::builder()
.body(Body::from_stream(body_stream))
.unwrap(),
);
Ok(response)
}
Expand All @@ -129,7 +124,7 @@ pub async fn git_upload_pack(
pub async fn git_receive_pack(
req: Request<Body>,
mut pack_protocol: SmartProtocol,
) -> Result<Response<Body>, (StatusCode, String)> {
) -> Result<Response<Body>, ProtocolError> {
// Convert the request body into a data stream.
let mut data_stream = req.into_body().into_data_stream();
let mut report_status = Bytes::new();
Expand Down Expand Up @@ -179,22 +174,5 @@ fn add_default_header<T>(content_type: String, mut response: Response<T>) -> Res
response
}

fn handler_err(err: ProtocolError) -> Response<Body> {
match err {
ProtocolError::NotFound(err) => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from(err.to_string()))
.unwrap(),
ProtocolError::Deny(err) => Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(Body::from(err.to_string()))
.unwrap(),
_ => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::empty())
.unwrap(),
}
}

#[cfg(test)]
mod tests {}
Loading