use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchKind {
Namespace,
Work,
}
impl fmt::Display for BranchKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Namespace => formatter.write_str("Namespace"),
Self::Work => formatter.write_str("Work"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BranchPolicyError {
ForkAcrossNamespaceBoundary {
source: String,
source_kind: BranchKind,
source_lineage: Option<String>,
destination: String,
destination_kind: BranchKind,
destination_lineage: Option<String>,
},
MergeAcrossNamespaceBoundary {
source: String,
source_kind: BranchKind,
source_lineage: Option<String>,
destination: String,
destination_kind: BranchKind,
destination_lineage: Option<String>,
},
NamespaceFork {
source: String,
source_kind: BranchKind,
destination: String,
destination_kind: BranchKind,
},
NamespaceMerge {
source: String,
source_kind: BranchKind,
destination: String,
destination_kind: BranchKind,
},
}
impl fmt::Display for BranchPolicyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ForkAcrossNamespaceBoundary {
source,
source_kind,
destination,
destination_kind,
..
}
| Self::NamespaceFork {
source,
source_kind,
destination,
destination_kind,
} => write!(
formatter,
"policy refused fork-into from branch '{source}' ({source_kind}) into branch \
'{destination}' ({destination_kind})",
),
Self::MergeAcrossNamespaceBoundary {
source,
source_kind,
destination,
destination_kind,
..
}
| Self::NamespaceMerge {
source,
source_kind,
destination,
destination_kind,
} => write!(
formatter,
"policy refused merge from branch '{source}' ({source_kind}) into branch \
'{destination}' ({destination_kind})",
),
}
}
}
impl std::error::Error for BranchPolicyError {}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub(crate) fn check_fork_into(
source: &str,
source_kind: BranchKind,
source_lineage: Option<&str>,
destination: &str,
destination_kind: BranchKind,
destination_lineage: Option<&str>,
) -> Result<(), BranchPolicyError> {
if destination_kind == BranchKind::Namespace {
return Err(BranchPolicyError::NamespaceFork {
source: source.to_owned(),
source_kind,
destination: destination.to_owned(),
destination_kind,
});
}
if crosses_namespace_boundary(source_lineage, destination_lineage) {
return Err(BranchPolicyError::ForkAcrossNamespaceBoundary {
source: source.to_owned(),
source_kind,
source_lineage: source_lineage.map(str::to_owned),
destination: destination.to_owned(),
destination_kind,
destination_lineage: destination_lineage.map(str::to_owned),
});
}
Ok(())
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub(crate) fn check_merge(
source: &str,
source_kind: BranchKind,
source_lineage: Option<&str>,
destination: &str,
destination_kind: BranchKind,
destination_lineage: Option<&str>,
) -> Result<(), BranchPolicyError> {
if source_kind == BranchKind::Namespace {
return Err(BranchPolicyError::NamespaceMerge {
source: source.to_owned(),
source_kind,
destination: destination.to_owned(),
destination_kind,
});
}
if crosses_namespace_boundary(source_lineage, destination_lineage) {
return Err(BranchPolicyError::MergeAcrossNamespaceBoundary {
source: source.to_owned(),
source_kind,
source_lineage: source_lineage.map(str::to_owned),
destination: destination.to_owned(),
destination_kind,
destination_lineage: destination_lineage.map(str::to_owned),
});
}
Ok(())
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
fn crosses_namespace_boundary(source: Option<&str>, destination: Option<&str>) -> bool {
source != destination && (source.is_some() || destination.is_some())
}