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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This file contains crucial context for AI agents working in this repository.
- **Execution Flow**: `src/main.rs` initializes an `axum` router and spawns two decoupled background `tokio` tasks:
1. `polling/`: Periodically checks remote git repositories for updates.
2. `trigger/`: Receives update events from the polling engine via `mpsc` channels and triggers GitHub Action workflows on target repositories.
- **Error Handling**: Use domain-specific error enums (`HandlerError`, `FatalError`) defined in `src/error.rs` using the `thiserror` crate. Ensure `IntoResponse` is implemented for any errors that bubble up to Axum handlers.
- **Error Handling**: Use `thiserror` for domain-specific error enums, defined in the layer they belong to: `HandlerError` in `src/http/error.rs`, repository errors in `src/repository/error.rs`, engine errors in `src/{polling,trigger}/error.rs`, and value-validation errors in `src/domain/`. Boot-time errors (`FatalError`, `SetupError`) live in `src/error.rs`. Implement `IntoResponse` for any error that bubbles up to an Axum handler.

## Reviews

Expand Down
2 changes: 1 addition & 1 deletion src/domain/accept_header.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent an HTTP Accept header.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use http::header::HeaderValue;
use serde::{Deserialize, Serialize};

Expand Down
2 changes: 1 addition & 1 deletion src/domain/api_version.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a GitHub API version in YYYY-MM-DD format.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};

Expand Down
2 changes: 1 addition & 1 deletion src/domain/branch_name.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a Git branch name.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use validator::Validate;
Expand Down
2 changes: 1 addition & 1 deletion src/domain/commit_hash.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a git commit hash.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand Down
2 changes: 1 addition & 1 deletion src/domain/event_type.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a GitHub's `repository_dispatch` `event_type`.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use validator::Validate;
Expand Down
2 changes: 2 additions & 0 deletions src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod event_type;
pub mod non_empty_string;
pub mod repo_url;
pub mod target_repo;
pub mod validation_error;

pub use accept_header::AcceptHeader;
pub use api_version::ApiVersion;
Expand All @@ -17,6 +18,7 @@ pub use event_type::EventType;
pub use non_empty_string::NonEmptyString;
pub use repo_url::RepoUrl;
pub use target_repo::TargetRepo;
pub use validation_error::ValidationError;

/// Derives `sqlx` trait implementations for a type that implements `TryFrom<String>`.
///
Expand Down
2 changes: 1 addition & 1 deletion src/domain/non_empty_string.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a non-empty string.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use serde::{Deserialize, Serialize};
use validator::Validate;

Expand Down
2 changes: 1 addition & 1 deletion src/domain/repo_url.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a GitHub repository URL.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use validator::Validate;
Expand Down
2 changes: 1 addition & 1 deletion src/domain/target_repo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Domain type to represent a target repository hosted on GitHub.

use crate::error::ValidationError;
use crate::domain::ValidationError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand Down
11 changes: 11 additions & 0 deletions src/domain/validation_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Validation error type for domain values.

use thiserror::Error;

/// Validation error.
#[derive(Debug, Error)]
pub enum ValidationError {
/// Invalid field value.
#[error("Validation error: {0}")]
InvalidValue(String),
}
126 changes: 1 addition & 125 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,10 @@
//! Definitions for common error types.
//! Definitions for fatal and setup errors.

use crate::repository::RepositoryError;
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use config::ConfigError;
use rovo::aide::OperationOutput;
use thiserror::Error;
use validator::ValidationErrors;

/// Validation error.
#[derive(Debug, Error)]
pub enum ValidationError {
/// Invalid field value.
#[error("Validation error: {0}")]
InvalidValue(String),
}

impl IntoResponse for ValidationError {
fn into_response(self) -> Response {
(StatusCode::UNPROCESSABLE_ENTITY, self.to_string()).into_response()
}
}

/// An error happened inside an Axum handler.
#[derive(Debug, Error)]
pub enum HandlerError {
/// Database query execution failure.
#[error("Repository Error: {0}")]
DbQuery(RepositoryError),

/// Requested resource not found.
#[error("Not Found")]
NotFound,
}

impl From<RepositoryError> for HandlerError {
fn from(err: RepositoryError) -> Self {
match err {
RepositoryError::NotFound => HandlerError::NotFound,
other => HandlerError::DbQuery(other),
}
}
}

impl OperationOutput for HandlerError {
type Inner = ();
}

impl IntoResponse for HandlerError {
fn into_response(self) -> Response {
let status = match self {
HandlerError::DbQuery(_) => StatusCode::INTERNAL_SERVER_ERROR,
HandlerError::NotFound => StatusCode::NOT_FOUND,
};
(status, self.to_string()).into_response()
}
}

/// An error that requires the server to be shut down.
#[derive(Debug, Error)]
pub enum FatalError {
Expand Down Expand Up @@ -147,73 +93,3 @@ impl From<dotenvy::Error> for FatalError {
#[derive(Debug, Error)]
#[error(transparent)]
pub struct ClientCreationError(#[from] reqwest::Error);

/// Error in retrieving a commit or its info using `git ls-remote`.
#[derive(Debug, Error)]
pub enum CommitHashError {
/// Validation error.
#[error("Validation error: {0}")]
Validation(#[from] ValidationError),

/// I/O error while spawning the process.
#[error("I/O error in `git ls-remote`: {0}")]
Io(#[from] std::io::Error),

/// Unexpected exit status.
#[error("Unexpected `git ls-remote` exit status: {0}")]
UnexpectedStatus(String),

/// Unexpected output format.
#[error(
"Unexpected `git ls-remote` output format. Repo: {repo_url}; Branch: {branch}; Stdout: {stdout}"
)]
UnexpectedOutput {
/// The process output text.
stdout: String,
/// The relevant git repository URL.
repo_url: String,
/// The relevant git branch.
branch: String,
},

/// Failed to find remote.
#[error("Failed to find remote: {0}")]
// Error is boxed because it is very large
RemoteAt(Box<gix::remote::init::Error>),

/// Failed to connect to remote.
#[error("Failed to connect to remote: {0}")]
// Error is boxed because it is very large
Connect(Box<gix::remote::connect::Error>),

/// Failed to map refs.
#[error("Failed to map refs: {0}")]
// Error is boxed because it is very large
RefMap(Box<gix::remote::ref_map::Error>),

/// Failed to parse refspec.
#[error("Failed to parse refspec: {0}")]
RefSpecParse(#[from] gix::refspec::parse::Error),

/// Git operation failed using gix.
#[error("Git operation failed: {0}")]
Git(String),
}

impl From<gix::remote::init::Error> for CommitHashError {
fn from(e: gix::remote::init::Error) -> Self {
CommitHashError::RemoteAt(Box::new(e))
}
}

impl From<gix::remote::connect::Error> for CommitHashError {
fn from(e: gix::remote::connect::Error) -> Self {
CommitHashError::Connect(Box::new(e))
}
}

impl From<gix::remote::ref_map::Error> for CommitHashError {
fn from(e: gix::remote::ref_map::Error) -> Self {
CommitHashError::RefMap(Box::new(e))
}
}
1 change: 1 addition & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! HTTP networking: router wiring, server runtime, and the outbound HTTP client.

pub mod error;
pub mod handler;
pub mod router;
pub(crate) mod server;
Expand Down
45 changes: 45 additions & 0 deletions src/http/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Error types for the HTTP layer.

use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use rovo::aide::OperationOutput;
use thiserror::Error;

use crate::repository::RepositoryError;

/// An error happened inside an Axum handler.
#[derive(Debug, Error)]
pub enum HandlerError {
/// Database query execution failure.
#[error("Repository Error: {0}")]
DbQuery(RepositoryError),

/// Requested resource not found.
#[error("Not Found")]
NotFound,
}

impl From<RepositoryError> for HandlerError {
fn from(err: RepositoryError) -> Self {
match err {
RepositoryError::NotFound => HandlerError::NotFound,
other => HandlerError::DbQuery(other),
}
}
}

impl OperationOutput for HandlerError {
type Inner = ();
}

impl IntoResponse for HandlerError {
fn into_response(self) -> Response {
let status = match self {
HandlerError::DbQuery(_) => StatusCode::INTERNAL_SERVER_ERROR,
HandlerError::NotFound => StatusCode::NOT_FOUND,
};
(status, self.to_string()).into_response()
}
}
2 changes: 1 addition & 1 deletion src/http/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ mod tests {
use super::list::{ListSubscriptionsQuery, list_subscriptions_inner};
use super::update::update_subscription_inner;
use crate::domain::{BranchName, EventType, RepoUrl, TargetRepo};
use crate::error::HandlerError;
use crate::http::error::HandlerError;
use crate::http::state::AppState;
use crate::model::{CreateSubscription, UpdateSubscription};
use crate::test_utils::create_test_db;
Expand Down
2 changes: 1 addition & 1 deletion src/http/handler/create.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Create a new subscription handler.

use super::map_to_hal;
use crate::error::HandlerError;
use crate::http::error::HandlerError;
use crate::http::state::AppState;
use crate::model::{CreateSubscription, SubscriptionHal};
use crate::repository::subscription::SubscriptionRepository;
Expand Down
2 changes: 1 addition & 1 deletion src/http/handler/delete.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Delete a subscription handler.

use crate::error::HandlerError;
use crate::http::error::HandlerError;
use crate::http::state::AppState;
use crate::repository::subscription::SubscriptionRepository;
use axum::extract::{Path, State};
Expand Down
2 changes: 1 addition & 1 deletion src/http/handler/get.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Get a single subscription handler.

use super::map_to_hal;
use crate::error::HandlerError;
use crate::http::error::HandlerError;
use crate::http::state::AppState;
use crate::model::SubscriptionHal;
use crate::repository::subscription::SubscriptionRepository;
Expand Down
2 changes: 1 addition & 1 deletion src/http/handler/list.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! List subscriptions handler.

use super::map_to_hal;
use crate::error::HandlerError;
use crate::http::error::HandlerError;
use crate::http::state::AppState;
use crate::model::{HalLink, SubscriptionHal, SubscriptionPage, SubscriptionPageLinks};
use crate::repository::subscription::SubscriptionRepository;
Expand Down
2 changes: 1 addition & 1 deletion src/http/handler/update.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Update an existing subscription handler.

use super::map_to_hal;
use crate::error::HandlerError;
use crate::http::error::HandlerError;
use crate::http::state::AppState;
use crate::model::{SubscriptionHal, UpdateSubscription};
use crate::repository::subscription::SubscriptionRepository;
Expand Down
6 changes: 5 additions & 1 deletion src/polling/branch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Utilities for checking whether a branch has updated.

use crate::{domain::CommitHash, error::CommitHashError, model::Branch, polling::git::GitFetcher};
use crate::{
domain::CommitHash,
model::Branch,
polling::{CommitHashError, git::GitFetcher},
};

/// Enables comparison between a git branch row, and the newly fetched branch.
pub(super) struct BranchInfo {
Expand Down
Loading
Loading