use std::sync::Arc;
use super::{
CreateReviewRequestInput, ForgeCommandRunner, ForgeFuture, ForgeKind, ForgeRemote,
GitHubReviewRequestAdapter, GitLabReviewRequestAdapter, RealForgeCommandRunner,
ReviewCommentSnapshot, ReviewRequestError, ReviewRequestMetadata, ReviewRequestSummary,
UpdateReviewRequestInput, detect_remote,
};
#[cfg_attr(any(test, feature = "test-utils"), mockall::automock)]
pub trait ReviewRequestClient: Send + Sync {
fn detect_remote(&self, repo_url: String) -> Result<ForgeRemote, ReviewRequestError>;
fn find_by_source_branch(
&self,
remote: ForgeRemote,
source_branch: String,
) -> ForgeFuture<Result<Option<ReviewRequestSummary>, ReviewRequestError>>;
fn create_review_request(
&self,
remote: ForgeRemote,
input: CreateReviewRequestInput,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>>;
fn refresh_review_request(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>>;
fn review_request_metadata(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewRequestMetadata, ReviewRequestError>>;
fn sync_review_request_metadata(
&self,
remote: ForgeRemote,
display_id: String,
input: UpdateReviewRequestInput,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>>;
fn review_request_web_url(
&self,
review_request: &ReviewRequestSummary,
) -> Result<String, ReviewRequestError>;
fn fetch_review_comment_snapshot(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewCommentSnapshot, ReviewRequestError>>;
fn reply_to_thread(
&self,
remote: ForgeRemote,
display_id: String,
thread_id: String,
body: String,
) -> ForgeFuture<Result<(), ReviewRequestError>>;
fn resolve_thread(
&self,
remote: ForgeRemote,
display_id: String,
thread_id: String,
) -> ForgeFuture<Result<(), ReviewRequestError>>;
}
pub struct RealReviewRequestClient {
command_runner: Arc<dyn ForgeCommandRunner>,
}
impl RealReviewRequestClient {
pub(crate) fn new(command_runner: Arc<dyn ForgeCommandRunner>) -> Self {
Self { command_runner }
}
fn call_with_authenticated_adapter<T>(
&self,
remote: ForgeRemote,
call: impl FnOnce(
Arc<dyn ReviewRequestAdapter>,
ForgeRemote,
) -> ForgeFuture<Result<T, ReviewRequestError>>
+ Send
+ 'static,
) -> ForgeFuture<Result<T, ReviewRequestError>>
where
T: Send + 'static,
{
let adapter = self.adapter_for(remote.forge_kind);
Box::pin(async move {
adapter.ensure_authenticated(&remote).await?;
call(adapter, remote).await
})
}
fn adapter_for(&self, forge_kind: ForgeKind) -> Arc<dyn ReviewRequestAdapter> {
match forge_kind {
ForgeKind::GitHub => Arc::new(GitHubReviewRequestAdapter::new(Arc::clone(
&self.command_runner,
))),
ForgeKind::GitLab => Arc::new(GitLabReviewRequestAdapter::new(Arc::clone(
&self.command_runner,
))),
}
}
}
impl Default for RealReviewRequestClient {
fn default() -> Self {
Self::new(Arc::new(RealForgeCommandRunner))
}
}
impl ReviewRequestClient for RealReviewRequestClient {
fn detect_remote(&self, repo_url: String) -> Result<ForgeRemote, ReviewRequestError> {
detect_remote(&repo_url)
}
fn find_by_source_branch(
&self,
remote: ForgeRemote,
source_branch: String,
) -> ForgeFuture<Result<Option<ReviewRequestSummary>, ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.find_authenticated_by_source_branch(remote, source_branch)
})
}
fn create_review_request(
&self,
remote: ForgeRemote,
input: CreateReviewRequestInput,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.create_authenticated_review_request(remote, input)
})
}
fn refresh_review_request(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.refresh_authenticated_review_request(remote, display_id)
})
}
fn review_request_metadata(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewRequestMetadata, ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.authenticated_review_request_metadata(remote, display_id)
})
}
fn sync_review_request_metadata(
&self,
remote: ForgeRemote,
display_id: String,
input: UpdateReviewRequestInput,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.sync_authenticated_review_request_metadata(remote, display_id, input)
})
}
fn review_request_web_url(
&self,
review_request: &ReviewRequestSummary,
) -> Result<String, ReviewRequestError> {
if review_request.web_url.trim().is_empty() {
return Err(ReviewRequestError::OperationFailed {
forge_kind: review_request.forge_kind,
message: "review request summary is missing a web URL".to_string(),
});
}
Ok(review_request.web_url.clone())
}
fn fetch_review_comment_snapshot(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewCommentSnapshot, ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.fetch_authenticated_review_comment_snapshot(remote, display_id)
})
}
fn reply_to_thread(
&self,
remote: ForgeRemote,
display_id: String,
thread_id: String,
body: String,
) -> ForgeFuture<Result<(), ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.reply_to_authenticated_thread(remote, display_id, thread_id, body)
})
}
fn resolve_thread(
&self,
remote: ForgeRemote,
display_id: String,
thread_id: String,
) -> ForgeFuture<Result<(), ReviewRequestError>> {
self.call_with_authenticated_adapter(remote, move |adapter, remote| {
adapter.resolve_authenticated_thread(remote, display_id, thread_id)
})
}
}
pub(crate) trait ReviewRequestAdapter: Send + Sync {
fn ensure_authenticated(
&self,
remote: &ForgeRemote,
) -> ForgeFuture<Result<(), ReviewRequestError>>;
fn find_authenticated_by_source_branch(
&self,
remote: ForgeRemote,
source_branch: String,
) -> ForgeFuture<Result<Option<ReviewRequestSummary>, ReviewRequestError>>;
fn create_authenticated_review_request(
&self,
remote: ForgeRemote,
input: CreateReviewRequestInput,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>>;
fn refresh_authenticated_review_request(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>>;
fn authenticated_review_request_metadata(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewRequestMetadata, ReviewRequestError>>;
fn sync_authenticated_review_request_metadata(
&self,
remote: ForgeRemote,
display_id: String,
input: UpdateReviewRequestInput,
) -> ForgeFuture<Result<ReviewRequestSummary, ReviewRequestError>>;
fn fetch_authenticated_review_comment_snapshot(
&self,
remote: ForgeRemote,
display_id: String,
) -> ForgeFuture<Result<ReviewCommentSnapshot, ReviewRequestError>>;
fn reply_to_authenticated_thread(
&self,
remote: ForgeRemote,
display_id: String,
thread_id: String,
body: String,
) -> ForgeFuture<Result<(), ReviewRequestError>>;
fn resolve_authenticated_thread(
&self,
remote: ForgeRemote,
display_id: String,
thread_id: String,
) -> ForgeFuture<Result<(), ReviewRequestError>>;
}
#[cfg(test)]
#[path = "client_test.rs"]
mod tests;