use serde::{Deserialize, Serialize};
use crate::changes::ChangeLifecycleFilter;
use crate::errors::DomainResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimResult {
pub change_id: String,
pub holder: String,
pub expires_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReleaseResult {
pub change_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AllocateResult {
pub claim: Option<ClaimResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaseConflict {
pub change_id: String,
pub holder: String,
pub expires_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactBundle {
pub change_id: String,
pub proposal: Option<String>,
pub design: Option<String>,
pub tasks: Option<String>,
pub specs: Vec<(String, String)>,
pub revision: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushResult {
pub change_id: String,
pub new_revision: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevisionConflict {
pub change_id: String,
pub local_revision: String,
pub server_revision: String,
}
#[derive(Debug, Clone)]
pub enum BackendError {
LeaseConflict(LeaseConflict),
RevisionConflict(RevisionConflict),
Unavailable(String),
Unauthorized(String),
NotFound(String),
Other(String),
}
impl std::fmt::Display for BackendError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BackendError::LeaseConflict(c) => {
write!(
f,
"change '{}' is already claimed by '{}'",
c.change_id, c.holder
)
}
BackendError::RevisionConflict(c) => {
write!(
f,
"revision conflict for '{}': local={}, server={}",
c.change_id, c.local_revision, c.server_revision
)
}
BackendError::Unavailable(msg) => write!(f, "backend unavailable: {msg}"),
BackendError::Unauthorized(msg) => write!(f, "backend auth failed: {msg}"),
BackendError::NotFound(msg) => write!(f, "not found: {msg}"),
BackendError::Other(msg) => write!(f, "backend error: {msg}"),
}
}
}
impl std::error::Error for BackendError {}
pub trait BackendProjectStore: Send + Sync {
fn change_repository(
&self,
org: &str,
repo: &str,
) -> DomainResult<Box<dyn crate::changes::ChangeRepository + Send>>;
fn module_repository(
&self,
org: &str,
repo: &str,
) -> DomainResult<Box<dyn crate::modules::ModuleRepository + Send>>;
fn task_repository(
&self,
org: &str,
repo: &str,
) -> DomainResult<Box<dyn crate::tasks::TaskRepository + Send>>;
fn task_mutation_service(
&self,
org: &str,
repo: &str,
) -> DomainResult<Box<dyn crate::tasks::TaskMutationService + Send>>;
fn spec_repository(
&self,
org: &str,
repo: &str,
) -> DomainResult<Box<dyn crate::specs::SpecRepository + Send>>;
fn pull_artifact_bundle(
&self,
org: &str,
repo: &str,
change_id: &str,
) -> Result<ArtifactBundle, BackendError>;
fn push_artifact_bundle(
&self,
org: &str,
repo: &str,
change_id: &str,
bundle: &ArtifactBundle,
) -> Result<PushResult, BackendError>;
fn archive_change(
&self,
org: &str,
repo: &str,
change_id: &str,
) -> Result<ArchiveResult, BackendError>;
fn ensure_project(&self, org: &str, repo: &str) -> DomainResult<()>;
fn project_exists(&self, org: &str, repo: &str) -> bool;
}
pub trait BackendLeaseClient {
fn claim(&self, change_id: &str) -> Result<ClaimResult, BackendError>;
fn release(&self, change_id: &str) -> Result<ReleaseResult, BackendError>;
fn allocate(&self) -> Result<AllocateResult, BackendError>;
}
pub trait BackendSyncClient {
fn pull(&self, change_id: &str) -> Result<ArtifactBundle, BackendError>;
fn push(&self, change_id: &str, bundle: &ArtifactBundle) -> Result<PushResult, BackendError>;
}
pub trait BackendChangeReader {
fn list_changes(
&self,
filter: ChangeLifecycleFilter,
) -> DomainResult<Vec<crate::changes::ChangeSummary>>;
fn get_change(
&self,
change_id: &str,
filter: ChangeLifecycleFilter,
) -> DomainResult<crate::changes::Change>;
}
pub trait BackendModuleReader {
fn list_modules(&self) -> DomainResult<Vec<crate::modules::ModuleSummary>>;
fn get_module(&self, module_id: &str) -> DomainResult<crate::modules::Module>;
}
pub trait BackendTaskReader {
fn load_tasks_content(&self, change_id: &str) -> DomainResult<Option<String>>;
}
pub trait BackendSpecReader {
fn list_specs(&self) -> DomainResult<Vec<crate::specs::SpecSummary>>;
fn get_spec(&self, spec_id: &str) -> DomainResult<crate::specs::SpecDocument>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventBatch {
pub events: Vec<crate::audit::event::AuditEvent>,
pub idempotency_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventIngestResult {
pub accepted: usize,
pub duplicates: usize,
}
pub trait BackendEventIngestClient {
fn ingest(&self, batch: &EventBatch) -> Result<EventIngestResult, BackendError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchiveResult {
pub change_id: String,
pub archived_at: String,
}
pub trait BackendArchiveClient {
fn mark_archived(&self, change_id: &str) -> Result<ArchiveResult, BackendError>;
}
#[cfg(test)]
#[path = "backend_tests.rs"]
mod backend_tests;