use serde::{Deserialize, Serialize};
use super::capability::CapabilitySet;
use super::tag::{Tag, TaxonomyAxis};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum TopologyScope {
Node,
Zone,
Region,
#[default]
Mesh,
}
impl TopologyScope {
pub fn as_wire_str(self) -> &'static str {
match self {
Self::Node => "node",
Self::Zone => "zone",
Self::Region => "region",
Self::Mesh => "mesh",
}
}
pub fn parse_wire(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"node" => Some(Self::Node),
"zone" => Some(Self::Zone),
"region" => Some(Self::Region),
"mesh" => Some(Self::Mesh),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct BlobCapability {
pub storage: bool,
pub disk_total_gb: u64,
pub disk_free_gb: u64,
pub overflow_enabled: bool,
}
impl BlobCapability {
pub fn storage_participating(disk_total_gb: u64, disk_free_gb: u64) -> Self {
Self {
storage: true,
disk_total_gb,
disk_free_gb,
overflow_enabled: false,
}
}
pub fn with_overflow_enabled(mut self, enabled: bool) -> Self {
self.overflow_enabled = enabled;
self
}
pub fn write_into(self, caps: CapabilitySet) -> CapabilitySet {
let mut tags = caps.tags;
if self.storage {
tags.insert(Tag::AxisPresent {
axis: TaxonomyAxis::Dataforts,
key: "blob.storage".to_string(),
});
}
if self.disk_total_gb > 0 {
tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Dataforts,
key: "blob.disk_total_gb".to_string(),
value: self.disk_total_gb.to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
}
if self.disk_free_gb > 0 {
tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Dataforts,
key: "blob.disk_free_gb".to_string(),
value: self.disk_free_gb.to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
}
if self.overflow_enabled {
tags.insert(Tag::AxisPresent {
axis: TaxonomyAxis::Dataforts,
key: "blob.overflow".to_string(),
});
}
CapabilitySet { tags, ..caps }
}
pub fn from_capability_set(caps: &CapabilitySet) -> Self {
let mut out = Self::default();
for tag in &caps.tags {
match tag {
Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "blob.storage" =>
{
out.storage = true;
}
Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "blob.overflow" =>
{
out.overflow_enabled = true;
}
Tag::AxisValue {
axis, key, value, ..
} if *axis == TaxonomyAxis::Dataforts => match key.as_str() {
"blob.disk_total_gb" => {
out.disk_total_gb = value.parse().unwrap_or(0);
}
"blob.disk_free_gb" => {
out.disk_free_gb = value.parse().unwrap_or(0);
}
_ => {}
},
_ => {}
}
}
out
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct GreedyCapability {
pub enabled: bool,
pub scope: TopologyScope,
pub proximity: u8,
}
impl GreedyCapability {
pub fn write_into(self, caps: CapabilitySet) -> CapabilitySet {
let mut tags = caps.tags;
if self.enabled {
tags.insert(Tag::AxisPresent {
axis: TaxonomyAxis::Dataforts,
key: "greedy.enabled".to_string(),
});
tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Dataforts,
key: "greedy.scope".to_string(),
value: self.scope.as_wire_str().to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
}
if self.proximity > 0 {
tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Dataforts,
key: "greedy.proximity".to_string(),
value: self.proximity.to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
}
CapabilitySet { tags, ..caps }
}
pub fn from_capability_set(caps: &CapabilitySet) -> Self {
let mut out = Self::default();
for tag in &caps.tags {
match tag {
Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "greedy.enabled" =>
{
out.enabled = true;
}
Tag::AxisValue {
axis, key, value, ..
} if *axis == TaxonomyAxis::Dataforts => match key.as_str() {
"greedy.scope" => {
if let Some(s) = TopologyScope::parse_wire(value) {
out.scope = s;
} else {
tracing::warn!(
tag = "dataforts.greedy.scope",
value = value.as_str(),
"dataforts capability parse: unknown scope token; falling back to \
default (Mesh). Valid wire values: node / zone / region / mesh."
);
}
}
"greedy.proximity" => {
out.proximity = value.parse().unwrap_or(0);
}
_ => {}
},
_ => {}
}
}
out
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct GravityCapability {
pub enabled: bool,
pub scope: TopologyScope,
pub proximity: u8,
}
impl GravityCapability {
pub fn write_into(self, caps: CapabilitySet) -> CapabilitySet {
let mut tags = caps.tags;
if self.enabled {
tags.insert(Tag::AxisPresent {
axis: TaxonomyAxis::Dataforts,
key: "gravity.enabled".to_string(),
});
tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Dataforts,
key: "gravity.scope".to_string(),
value: self.scope.as_wire_str().to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
}
if self.proximity > 0 {
tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Dataforts,
key: "gravity.proximity".to_string(),
value: self.proximity.to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
}
CapabilitySet { tags, ..caps }
}
pub fn from_capability_set(caps: &CapabilitySet) -> Self {
let mut out = Self::default();
for tag in &caps.tags {
match tag {
Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "gravity.enabled" =>
{
out.enabled = true;
}
Tag::AxisValue {
axis, key, value, ..
} if *axis == TaxonomyAxis::Dataforts => match key.as_str() {
"gravity.scope" => {
if let Some(s) = TopologyScope::parse_wire(value) {
out.scope = s;
} else {
tracing::warn!(
tag = "dataforts.gravity.scope",
value = value.as_str(),
"dataforts capability parse: unknown scope token; falling back to \
default (Mesh). Valid wire values: node / zone / region / mesh."
);
}
}
"gravity.proximity" => {
out.proximity = value.parse().unwrap_or(0);
}
_ => {}
},
_ => {}
}
}
out
}
}
pub const BLOB_STORAGE_UNHEALTHY_TAG: &str = "dataforts:blob-storage-unhealthy";
pub fn is_blob_storage_unhealthy(caps: &CapabilitySet) -> bool {
caps.tags.iter().any(|t| match t {
Tag::Reserved { prefix, body } => {
prefix == "dataforts:" && body == "blob-storage-unhealthy"
}
_ => false,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapter::net::behavior::capability::CapabilitySet;
#[test]
fn topology_scope_wire_round_trip() {
for s in [
TopologyScope::Node,
TopologyScope::Zone,
TopologyScope::Region,
TopologyScope::Mesh,
] {
assert_eq!(TopologyScope::parse_wire(s.as_wire_str()), Some(s));
}
assert_eq!(TopologyScope::parse_wire("ZONE"), Some(TopologyScope::Zone));
assert_eq!(TopologyScope::parse_wire("galaxy"), None);
}
#[test]
fn topology_scope_default_is_mesh() {
assert_eq!(TopologyScope::default(), TopologyScope::Mesh);
}
#[test]
fn blob_capability_default_is_non_participating() {
let bc = BlobCapability::default();
assert!(!bc.storage);
assert_eq!(bc.disk_total_gb, 0);
assert_eq!(bc.disk_free_gb, 0);
}
#[test]
fn blob_capability_reads_storage_present_tag() {
let caps = CapabilitySet::new().add_tag("dataforts.blob.storage");
let bc = BlobCapability::from_capability_set(&caps);
assert!(bc.storage);
}
#[test]
fn blob_capability_reads_disk_gb_tags() {
let caps = CapabilitySet::new()
.add_tag("dataforts.blob.storage")
.add_tag("dataforts.blob.disk_total_gb=64")
.add_tag("dataforts.blob.disk_free_gb=12");
let bc = BlobCapability::from_capability_set(&caps);
assert!(bc.storage);
assert_eq!(bc.disk_total_gb, 64);
assert_eq!(bc.disk_free_gb, 12);
}
#[test]
fn greedy_capability_default_is_disabled() {
let g = GreedyCapability::default();
assert!(!g.enabled);
assert_eq!(g.scope, TopologyScope::Mesh);
assert_eq!(g.proximity, 0);
}
#[test]
fn greedy_capability_reads_all_fields() {
let caps = CapabilitySet::new()
.add_tag("dataforts.greedy.enabled")
.add_tag("dataforts.greedy.scope=zone")
.add_tag("dataforts.greedy.proximity=200");
let g = GreedyCapability::from_capability_set(&caps);
assert!(g.enabled);
assert_eq!(g.scope, TopologyScope::Zone);
assert_eq!(g.proximity, 200);
}
#[test]
fn gravity_capability_reads_all_fields() {
let caps = CapabilitySet::new()
.add_tag("dataforts.gravity.enabled")
.add_tag("dataforts.gravity.scope=region")
.add_tag("dataforts.gravity.proximity=64");
let g = GravityCapability::from_capability_set(&caps);
assert!(g.enabled);
assert_eq!(g.scope, TopologyScope::Region);
assert_eq!(g.proximity, 64);
}
#[test]
fn greedy_and_gravity_dont_cross_read() {
let caps = CapabilitySet::new()
.add_tag("dataforts.greedy.enabled")
.add_tag("dataforts.greedy.proximity=255");
let g = GravityCapability::from_capability_set(&caps);
assert!(!g.enabled);
assert_eq!(g.proximity, 0);
}
#[test]
fn parse_handles_garbage_values_gracefully() {
let caps = CapabilitySet::new()
.add_tag("dataforts.blob.disk_total_gb=not-a-number")
.add_tag("dataforts.greedy.proximity=oops");
let bc = BlobCapability::from_capability_set(&caps);
let g = GreedyCapability::from_capability_set(&caps);
assert_eq!(bc.disk_total_gb, 0);
assert_eq!(g.proximity, 0);
}
#[test]
fn unknown_scope_value_falls_back_to_default() {
let caps = CapabilitySet::new()
.add_tag("dataforts.greedy.enabled")
.add_tag("dataforts.greedy.scope=galaxy");
let g = GreedyCapability::from_capability_set(&caps);
assert_eq!(g.scope, TopologyScope::Mesh);
}
#[test]
fn blob_capability_write_into_round_trips() {
let original = BlobCapability::storage_participating(100, 42);
let caps = original.write_into(CapabilitySet::new());
let read_back = BlobCapability::from_capability_set(&caps);
assert_eq!(read_back, original);
}
#[test]
fn blob_capability_write_into_skips_zero_fields() {
let bc = BlobCapability {
storage: true,
disk_total_gb: 0,
disk_free_gb: 0,
overflow_enabled: false,
};
let caps = bc.write_into(CapabilitySet::new());
assert!(caps
.tags
.iter()
.any(|t| matches!(t, Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "blob.storage")));
assert!(!caps.tags.iter().any(|t| matches!(t,
Tag::AxisValue { axis, key, .. }
if *axis == TaxonomyAxis::Dataforts && key == "blob.disk_total_gb")));
assert!(!caps.tags.iter().any(|t| matches!(t,
Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "blob.overflow")));
}
#[test]
fn blob_capability_overflow_tag_round_trips() {
let original = BlobCapability {
storage: true,
disk_total_gb: 100,
disk_free_gb: 50,
overflow_enabled: true,
};
let caps = original.write_into(CapabilitySet::new());
assert!(caps
.tags
.iter()
.any(|t| matches!(t, Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "blob.overflow")));
let read_back = BlobCapability::from_capability_set(&caps);
assert_eq!(read_back, original);
}
#[test]
fn blob_capability_overflow_absent_when_disabled() {
let bc = BlobCapability::storage_participating(100, 50);
assert!(!bc.overflow_enabled);
let caps = bc.write_into(CapabilitySet::new());
assert!(!caps.tags.iter().any(|t| matches!(t,
Tag::AxisPresent { axis, key }
if *axis == TaxonomyAxis::Dataforts && key == "blob.overflow")));
let read_back = BlobCapability::from_capability_set(&caps);
assert!(!read_back.overflow_enabled);
}
#[test]
fn greedy_capability_write_into_round_trips() {
let original = GreedyCapability {
enabled: true,
scope: TopologyScope::Zone,
proximity: 128,
};
let caps = original.write_into(CapabilitySet::new());
let read_back = GreedyCapability::from_capability_set(&caps);
assert_eq!(read_back, original);
}
#[test]
fn greedy_capability_disabled_skips_all_tags() {
let g = GreedyCapability::default();
let caps = g.write_into(CapabilitySet::new());
assert!(caps.tags.is_empty(), "disabled greedy must emit no tags");
}
#[test]
fn gravity_capability_write_into_round_trips() {
let original = GravityCapability {
enabled: true,
scope: TopologyScope::Region,
proximity: 64,
};
let caps = original.write_into(CapabilitySet::new());
let read_back = GravityCapability::from_capability_set(&caps);
assert_eq!(read_back, original);
}
#[cfg(feature = "dataforts")]
#[test]
fn capability_set_with_typed_builders_round_trip() {
let blob = BlobCapability::storage_participating(100, 50);
let greedy = GreedyCapability {
enabled: true,
scope: TopologyScope::Mesh,
proximity: 128,
};
let gravity = GravityCapability {
enabled: true,
scope: TopologyScope::Region,
proximity: 64,
};
let caps = CapabilitySet::new()
.with_blob_capability(blob)
.with_greedy_capability(greedy)
.with_gravity_capability(gravity);
assert_eq!(BlobCapability::from_capability_set(&caps), blob);
assert_eq!(GreedyCapability::from_capability_set(&caps), greedy);
assert_eq!(GravityCapability::from_capability_set(&caps), gravity);
}
}