use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "serde")]
use ciborium::{de::from_reader, ser::into_writer};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub const EXT_CAPABILITIES_V1: &str = "greentic.ext.capabilities.v1";
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapabilitiesExtensionV1 {
pub schema_version: u32,
pub offers: Vec<CapabilityOfferV1>,
}
impl CapabilitiesExtensionV1 {
pub fn new(offers: Vec<CapabilityOfferV1>) -> Self {
Self {
schema_version: 1,
offers,
}
}
pub fn validate(&self) -> Result<(), CapabilitiesExtensionError> {
if self.schema_version != 1 {
return Err(CapabilitiesExtensionError::UnsupportedSchemaVersion(
self.schema_version,
));
}
for offer in &self.offers {
if offer.requires_setup && offer.setup.is_none() {
return Err(CapabilitiesExtensionError::MissingSetup {
offer_id: offer.offer_id.clone(),
});
}
if let Some(setup) = offer.setup.as_ref()
&& setup.qa_ref.trim().is_empty()
{
return Err(CapabilitiesExtensionError::InvalidSetupQaRef {
offer_id: offer.offer_id.clone(),
});
}
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn to_extension_value(&self) -> Result<serde_json::Value, CapabilitiesExtensionError> {
serde_json::to_value(self)
.map_err(|err| CapabilitiesExtensionError::Serialize(err.to_string()))
}
#[cfg(feature = "serde")]
pub fn from_extension_value(
value: &serde_json::Value,
) -> Result<Self, CapabilitiesExtensionError> {
let decoded: Self = serde_json::from_value(value.clone())
.map_err(|err| CapabilitiesExtensionError::Deserialize(err.to_string()))?;
decoded.validate()?;
Ok(decoded)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapabilityOfferV1 {
pub offer_id: String,
pub cap_id: String,
pub version: String,
pub provider: CapabilityProviderRefV1,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub scope: Option<CapabilityScopeV1>,
#[cfg_attr(feature = "serde", serde(default))]
pub priority: i32,
#[cfg_attr(feature = "serde", serde(default))]
pub requires_setup: bool,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub setup: Option<CapabilitySetupV1>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub applies_to: Option<CapabilityHookAppliesToV1>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapabilityProviderRefV1 {
pub component_ref: String,
pub op: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapabilityScopeV1 {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub envs: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub tenants: Vec<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub teams: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapabilitySetupV1 {
pub qa_ref: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CapabilityHookAppliesToV1 {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub op_names: Vec<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum CapabilitiesExtensionError {
#[error("capabilities extension serialize failed: {0}")]
Serialize(String),
#[error("capabilities extension deserialize failed: {0}")]
Deserialize(String),
#[error("unsupported capabilities extension schema_version {0}")]
UnsupportedSchemaVersion(u32),
#[error("capabilities extension offer `{offer_id}` requires setup but setup is missing")]
MissingSetup {
offer_id: String,
},
#[error("capabilities extension offer `{offer_id}` has empty setup.qa_ref")]
InvalidSetupQaRef {
offer_id: String,
},
#[error("capabilities extension missing inline payload")]
MissingInline,
#[error("capabilities extension inline payload has unexpected type")]
UnexpectedInline,
}
#[cfg(feature = "serde")]
pub fn encode_capabilities_extension_v1_to_cbor_bytes(
payload: &CapabilitiesExtensionV1,
) -> Result<Vec<u8>, CapabilitiesExtensionError> {
let mut buf = Vec::new();
into_writer(payload, &mut buf)
.map_err(|err| CapabilitiesExtensionError::Serialize(err.to_string()))?;
Ok(buf)
}
#[cfg(feature = "serde")]
pub fn decode_capabilities_extension_v1_from_cbor_bytes(
bytes: &[u8],
) -> Result<CapabilitiesExtensionV1, CapabilitiesExtensionError> {
let decoded: CapabilitiesExtensionV1 = from_reader(bytes)
.map_err(|err| CapabilitiesExtensionError::Deserialize(err.to_string()))?;
decoded.validate()?;
Ok(decoded)
}