use crate::GfError;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactCategory {
TopologyNodes,
TopologyEdges,
Properties,
UuidAndSurrogates,
Adjacency,
CatalogAndManifests,
ConstructionStaging,
PortablePackage,
CleanImportedProject,
Other,
}
impl ArtifactCategory {
pub const ALL: [Self; 10] = [
Self::TopologyNodes,
Self::TopologyEdges,
Self::Properties,
Self::UuidAndSurrogates,
Self::Adjacency,
Self::CatalogAndManifests,
Self::ConstructionStaging,
Self::PortablePackage,
Self::CleanImportedProject,
Self::Other,
];
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactStorageTotals {
pub logical_references: u64,
pub logical_bytes: u64,
pub physical_objects: u64,
pub physical_logical_bytes: u64,
pub allocated_bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StorageAttributionReceipt {
pub contract: String,
pub categories: BTreeMap<ArtifactCategory, ArtifactStorageTotals>,
pub logical_references: u64,
pub logical_bytes: u64,
pub retained_logical_eof_bytes: u64,
pub allocated_physical_bytes: u64,
pub physical_objects: u64,
}
impl StorageAttributionReceipt {
pub fn validate_reconciliation(&self) -> Result<(), GfError> {
if self.contract != "graphforge-storage-attribution/1"
|| ArtifactCategory::ALL
.iter()
.any(|category| !self.categories.contains_key(category))
|| self.categories.len() != ArtifactCategory::ALL.len()
{
return Err(validation(
"storage attribution receipt contract is incomplete",
));
}
let mut total = ArtifactStorageTotals::default();
for category in ArtifactCategory::ALL {
add_totals(&mut total, &self.categories[&category])?;
}
if total.logical_references != self.logical_references
|| total.logical_bytes != self.logical_bytes
|| total.physical_objects != self.physical_objects
|| total.physical_logical_bytes != self.retained_logical_eof_bytes
|| total.allocated_bytes != self.allocated_physical_bytes
{
return Err(validation(
"storage attribution receipt totals do not reconcile",
));
}
Ok(())
}
}
fn add_totals(
target: &mut ArtifactStorageTotals,
value: &ArtifactStorageTotals,
) -> Result<(), GfError> {
target.logical_references = checked_add(target.logical_references, value.logical_references)?;
target.logical_bytes = checked_add(target.logical_bytes, value.logical_bytes)?;
target.physical_objects = checked_add(target.physical_objects, value.physical_objects)?;
target.physical_logical_bytes =
checked_add(target.physical_logical_bytes, value.physical_logical_bytes)?;
target.allocated_bytes = checked_add(target.allocated_bytes, value.allocated_bytes)?;
Ok(())
}
fn checked_add(left: u64, right: u64) -> Result<u64, GfError> {
left.checked_add(right)
.ok_or_else(|| validation("storage attribution counter overflow"))
}
fn validation(message: impl Into<String>) -> GfError {
GfError::Validation(message.into())
}