use appcore_contracts::{ProviderConfig, ProviderId};
use std::collections::{BTreeMap, BTreeSet};
pub const STORAGE_CAPABILITY_DESCRIPTOR_VERSION_V1: u16 = 1;
pub const STORAGE_REQUIRED_CAPABILITIES_SETTING: &str = "required_capabilities";
pub const STORAGE_CAPABILITY_COUNT_V1: usize = 7;
pub const MAX_STORAGE_CAPABILITY_PROVIDERS_V1: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StorageCapabilityV1 {
Transactions,
Locking,
Snapshot,
Streaming,
OnlineBackup,
MultiProcess,
MultiHost,
}
impl StorageCapabilityV1 {
pub const fn as_str(self) -> &'static str {
match self {
Self::Transactions => "transactions",
Self::Locking => "locking",
Self::Snapshot => "snapshot",
Self::Streaming => "streaming",
Self::OnlineBackup => "online_backup",
Self::MultiProcess => "multi_process",
Self::MultiHost => "multi_host",
}
}
fn parse(value: &str) -> Result<Self, StorageCapabilityError> {
match value {
"transactions" => Ok(Self::Transactions),
"locking" => Ok(Self::Locking),
"snapshot" => Ok(Self::Snapshot),
"streaming" => Ok(Self::Streaming),
"online_backup" => Ok(Self::OnlineBackup),
"multi_process" => Ok(Self::MultiProcess),
"multi_host" => Ok(Self::MultiHost),
_ => Err(StorageCapabilityError::UnknownRequirement),
}
}
}
impl std::fmt::Display for StorageCapabilityV1 {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StorageCapabilityError {
InvalidDescriptor,
UnknownRequirement,
DuplicateRequirement(StorageCapabilityV1),
CatalogFull,
DuplicateProvider(ProviderId),
ProviderUnavailable(ProviderId),
MissingCapability {
provider_id: ProviderId,
capability: StorageCapabilityV1,
},
}
impl std::fmt::Display for StorageCapabilityError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidDescriptor => {
formatter.write_str("storage capability descriptor identity is invalid")
}
Self::UnknownRequirement => {
formatter.write_str("storage capability requirement is unknown")
}
Self::DuplicateRequirement(capability) => write!(
formatter,
"storage capability requirement is duplicated: {capability}"
),
Self::CatalogFull => formatter.write_str("storage capability provider catalog is full"),
Self::DuplicateProvider(provider_id) => write!(
formatter,
"storage capability descriptor is duplicated for provider: {provider_id}"
),
Self::ProviderUnavailable(provider_id) => write!(
formatter,
"storage capability descriptor is unavailable for provider: {provider_id}"
),
Self::MissingCapability {
provider_id,
capability,
} => write!(
formatter,
"storage provider {provider_id} does not support required capability {capability}"
),
}
}
}
impl std::error::Error for StorageCapabilityError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StorageCapabilityDescriptorV1 {
provider_id: ProviderId,
capabilities: BTreeSet<StorageCapabilityV1>,
}
impl StorageCapabilityDescriptorV1 {
pub fn new(
provider_id: ProviderId,
capabilities: impl IntoIterator<Item = StorageCapabilityV1>,
) -> Self {
Self {
provider_id,
capabilities: capabilities.into_iter().collect(),
}
}
pub const fn descriptor_version(&self) -> u16 {
STORAGE_CAPABILITY_DESCRIPTOR_VERSION_V1
}
pub fn provider_id(&self) -> &ProviderId {
&self.provider_id
}
pub fn capabilities(&self) -> &BTreeSet<StorageCapabilityV1> {
&self.capabilities
}
pub fn supports(&self, capability: StorageCapabilityV1) -> bool {
self.capabilities.contains(&capability)
}
pub fn validate(
&self,
requirements: &StorageCapabilityRequirementsV1,
) -> Result<(), StorageCapabilityError> {
for capability in requirements.capabilities() {
if !self.supports(*capability) {
return Err(StorageCapabilityError::MissingCapability {
provider_id: self.provider_id.clone(),
capability: *capability,
});
}
}
Ok(())
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StorageCapabilityRequirementsV1 {
capabilities: BTreeSet<StorageCapabilityV1>,
}
impl StorageCapabilityRequirementsV1 {
pub fn new() -> Self {
Self::default()
}
pub fn from_provider_config(config: &ProviderConfig) -> Result<Self, StorageCapabilityError> {
let Some(value) = config.settings().get(STORAGE_REQUIRED_CAPABILITIES_SETTING) else {
return Ok(Self::new());
};
let mut requirements = Self::new();
if value.trim().is_empty() {
return Err(StorageCapabilityError::UnknownRequirement);
}
for raw in value.split(',') {
let capability = StorageCapabilityV1::parse(raw.trim())?;
requirements.require(capability)?;
}
Ok(requirements)
}
pub fn require(
&mut self,
capability: StorageCapabilityV1,
) -> Result<(), StorageCapabilityError> {
if !self.capabilities.insert(capability) {
return Err(StorageCapabilityError::DuplicateRequirement(capability));
}
Ok(())
}
pub fn include(&mut self, capability: StorageCapabilityV1) {
self.capabilities.insert(capability);
}
pub fn capabilities(&self) -> &BTreeSet<StorageCapabilityV1> {
&self.capabilities
}
}
pub trait StorageCapabilityProviderV1 {
fn storage_capabilities_v1(
&self,
) -> Result<StorageCapabilityDescriptorV1, StorageCapabilityError>;
}
#[derive(Debug, Clone, Default)]
pub struct StorageCapabilityCatalogV1 {
descriptors: BTreeMap<ProviderId, StorageCapabilityDescriptorV1>,
}
impl StorageCapabilityCatalogV1 {
pub fn new() -> Self {
Self::default()
}
pub fn register(
&mut self,
descriptor: StorageCapabilityDescriptorV1,
) -> Result<(), StorageCapabilityError> {
if self.descriptors.contains_key(descriptor.provider_id()) {
return Err(StorageCapabilityError::DuplicateProvider(
descriptor.provider_id().clone(),
));
}
if self.descriptors.len() >= MAX_STORAGE_CAPABILITY_PROVIDERS_V1 {
return Err(StorageCapabilityError::CatalogFull);
}
self.descriptors
.insert(descriptor.provider_id().clone(), descriptor);
Ok(())
}
pub fn validate(
&self,
provider_id: &ProviderId,
requirements: &StorageCapabilityRequirementsV1,
) -> Result<(), StorageCapabilityError> {
self.descriptors
.get(provider_id)
.ok_or_else(|| StorageCapabilityError::ProviderUnavailable(provider_id.clone()))?
.validate(requirements)
}
}