use std::fmt;
use crate::contracts::ContractDescriptor;
use crate::identity::{CapabilityId, NodeId};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CapabilityRequirement {
capability_id: CapabilityId,
contract: Option<ContractDescriptor>,
}
impl CapabilityRequirement {
pub fn new(capability_id: CapabilityId) -> Self {
Self {
capability_id,
contract: None,
}
}
pub fn with_contract(
mut self,
contract: ContractDescriptor,
) -> Result<Self, CapabilityRequirementError> {
let required_capability = self.capability_id.clone();
let contract_capability = contract.capability_id.clone();
if required_capability != contract_capability {
return Err(CapabilityRequirementError::ContractCapabilityMismatch {
required_capability,
contract_capability,
});
}
self.contract = Some(contract);
Ok(self)
}
pub fn capability_id(&self) -> &CapabilityId {
&self.capability_id
}
pub fn contract(&self) -> Option<&ContractDescriptor> {
self.contract.as_ref()
}
pub fn validate(&self) -> Result<(), CapabilityRequirementError> {
if let Some(contract) = &self.contract {
let required_capability = self.capability_id.clone();
let contract_capability = contract.capability_id.clone();
if required_capability != contract_capability {
return Err(CapabilityRequirementError::ContractCapabilityMismatch {
required_capability,
contract_capability,
});
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CapabilityRequirementError {
ContractCapabilityMismatch {
required_capability: CapabilityId,
contract_capability: CapabilityId,
},
}
impl fmt::Display for CapabilityRequirementError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ContractCapabilityMismatch {
required_capability,
contract_capability,
} => write!(
f,
"contract capability does not match required capability: \
required={required_capability:?}, contract={contract_capability:?}"
),
}
}
}
impl std::error::Error for CapabilityRequirementError {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ConditionReference {
value: String,
}
impl ConditionReference {
pub fn new(value: impl Into<String>) -> Result<Self, ConditionReferenceError> {
let value = value.into();
if value.trim().is_empty() {
return Err(ConditionReferenceError::Empty);
}
Ok(Self { value })
}
pub fn as_str(&self) -> &str {
&self.value
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ConditionReferenceError {
Empty,
}
impl fmt::Display for ConditionReferenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => f.write_str("condition reference cannot be empty"),
}
}
}
impl std::error::Error for ConditionReferenceError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DependencyKind {
Blocking,
NonBlocking,
Conditional,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DependencyTarget {
Node(NodeId),
Capability(CapabilityRequirement),
}
impl DependencyTarget {
pub fn validate(&self) -> Result<(), DependencyValidationError> {
match self {
Self::Node(_) => Ok(()),
Self::Capability(requirement) => requirement
.validate()
.map_err(DependencyValidationError::InvalidCapabilityRequirement),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Dependency {
source: NodeId,
target: DependencyTarget,
kind: DependencyKind,
condition: Option<ConditionReference>,
}
impl Dependency {
pub fn blocking(
source: NodeId,
target: DependencyTarget,
) -> Result<Self, DependencyValidationError> {
Self::new(source, target, DependencyKind::Blocking, None)
}
pub fn non_blocking(
source: NodeId,
target: DependencyTarget,
) -> Result<Self, DependencyValidationError> {
Self::new(source, target, DependencyKind::NonBlocking, None)
}
pub fn conditional(
source: NodeId,
target: DependencyTarget,
condition: ConditionReference,
) -> Result<Self, DependencyValidationError> {
Self::new(source, target, DependencyKind::Conditional, Some(condition))
}
fn new(
source: NodeId,
target: DependencyTarget,
kind: DependencyKind,
condition: Option<ConditionReference>,
) -> Result<Self, DependencyValidationError> {
let dependency = Self {
source,
target,
kind,
condition,
};
dependency.validate()?;
Ok(dependency)
}
pub fn source(&self) -> &NodeId {
&self.source
}
pub fn target(&self) -> &DependencyTarget {
&self.target
}
pub fn kind(&self) -> DependencyKind {
self.kind
}
pub fn condition(&self) -> Option<&ConditionReference> {
self.condition.as_ref()
}
pub fn validate(&self) -> Result<(), DependencyValidationError> {
match &self.target {
DependencyTarget::Node(target) if &self.source == target => {
return Err(DependencyValidationError::SelfDependency {
node_id: self.source.clone(),
});
}
_ => {}
}
self.target.validate()?;
match (self.kind, self.condition.is_some()) {
(DependencyKind::Blocking, true) | (DependencyKind::NonBlocking, true) => {
Err(DependencyValidationError::ConditionNotAllowed)
}
(DependencyKind::Conditional, false) => {
Err(DependencyValidationError::ConditionRequired)
}
_ => Ok(()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DependencyValidationError {
SelfDependency { node_id: NodeId },
ConditionRequired,
ConditionNotAllowed,
InvalidCapabilityRequirement(CapabilityRequirementError),
}
impl fmt::Display for DependencyValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SelfDependency { node_id } => {
write!(
f,
"dependency cannot target its own source node: {node_id:?}"
)
}
Self::ConditionRequired => {
f.write_str("conditional dependency requires a condition reference")
}
Self::ConditionNotAllowed => {
f.write_str("blocking and non-blocking dependencies cannot have a condition")
}
Self::InvalidCapabilityRequirement(error) => {
write!(f, "invalid capability requirement: {error}")
}
}
}
}
impl std::error::Error for DependencyValidationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn condition_reference_preserves_value() {
let condition = ConditionReference::new("creed-sensitive").unwrap();
assert_eq!(condition.as_str(), "creed-sensitive");
}
#[test]
fn empty_condition_reference_is_rejected() {
assert_eq!(
ConditionReference::new(""),
Err(ConditionReferenceError::Empty)
);
}
#[test]
fn whitespace_condition_reference_is_rejected() {
assert_eq!(
ConditionReference::new(" \t\n"),
Err(ConditionReferenceError::Empty)
);
}
#[test]
fn dependency_kind_variants_are_distinct() {
assert_ne!(DependencyKind::Blocking, DependencyKind::NonBlocking);
assert_ne!(DependencyKind::NonBlocking, DependencyKind::Conditional);
assert_ne!(DependencyKind::Blocking, DependencyKind::Conditional);
}
}