greentic_types/pack/extensions/
capabilities.rs1use alloc::string::String;
4use alloc::vec::Vec;
5
6#[cfg(feature = "serde")]
7use ciborium::{de::from_reader, ser::into_writer};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11pub const EXT_CAPABILITIES_V1: &str = "greentic.ext.capabilities.v1";
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17pub struct CapabilitiesExtensionV1 {
18 pub schema_version: u32,
20 pub offers: Vec<CapabilityOfferV1>,
22}
23
24impl CapabilitiesExtensionV1 {
25 pub fn new(offers: Vec<CapabilityOfferV1>) -> Self {
27 Self {
28 schema_version: 1,
29 offers,
30 }
31 }
32
33 pub fn validate(&self) -> Result<(), CapabilitiesExtensionError> {
35 if self.schema_version != 1 {
36 return Err(CapabilitiesExtensionError::UnsupportedSchemaVersion(
37 self.schema_version,
38 ));
39 }
40 for offer in &self.offers {
41 if offer.requires_setup && offer.setup.is_none() {
42 return Err(CapabilitiesExtensionError::MissingSetup {
43 offer_id: offer.offer_id.clone(),
44 });
45 }
46 if let Some(setup) = offer.setup.as_ref()
47 && setup.qa_ref.trim().is_empty()
48 {
49 return Err(CapabilitiesExtensionError::InvalidSetupQaRef {
50 offer_id: offer.offer_id.clone(),
51 });
52 }
53 }
54 Ok(())
55 }
56
57 #[cfg(feature = "serde")]
59 pub fn to_extension_value(&self) -> Result<serde_json::Value, CapabilitiesExtensionError> {
60 serde_json::to_value(self)
61 .map_err(|err| CapabilitiesExtensionError::Serialize(err.to_string()))
62 }
63
64 #[cfg(feature = "serde")]
66 pub fn from_extension_value(
67 value: &serde_json::Value,
68 ) -> Result<Self, CapabilitiesExtensionError> {
69 let decoded: Self = serde_json::from_value(value.clone())
70 .map_err(|err| CapabilitiesExtensionError::Deserialize(err.to_string()))?;
71 decoded.validate()?;
72 Ok(decoded)
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
79pub struct CapabilityOfferV1 {
80 pub offer_id: String,
82 pub cap_id: String,
84 pub version: String,
86 pub provider: CapabilityProviderRefV1,
88 #[cfg_attr(
90 feature = "serde",
91 serde(default, skip_serializing_if = "Option::is_none")
92 )]
93 pub scope: Option<CapabilityScopeV1>,
94 #[cfg_attr(feature = "serde", serde(default))]
96 pub priority: i32,
97 #[cfg_attr(feature = "serde", serde(default))]
99 pub requires_setup: bool,
100 #[cfg_attr(
102 feature = "serde",
103 serde(default, skip_serializing_if = "Option::is_none")
104 )]
105 pub setup: Option<CapabilitySetupV1>,
106 #[cfg_attr(
108 feature = "serde",
109 serde(default, skip_serializing_if = "Option::is_none")
110 )]
111 pub applies_to: Option<CapabilityHookAppliesToV1>,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq)]
116#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
117pub struct CapabilityProviderRefV1 {
118 pub component_ref: String,
120 pub op: String,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq, Default)]
126#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
127pub struct CapabilityScopeV1 {
128 #[cfg_attr(
130 feature = "serde",
131 serde(default, skip_serializing_if = "Vec::is_empty")
132 )]
133 pub envs: Vec<String>,
134 #[cfg_attr(
136 feature = "serde",
137 serde(default, skip_serializing_if = "Vec::is_empty")
138 )]
139 pub tenants: Vec<String>,
140 #[cfg_attr(
142 feature = "serde",
143 serde(default, skip_serializing_if = "Vec::is_empty")
144 )]
145 pub teams: Vec<String>,
146}
147
148#[derive(Clone, Debug, PartialEq, Eq)]
150#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
151pub struct CapabilitySetupV1 {
152 pub qa_ref: String,
154}
155
156#[derive(Clone, Debug, PartialEq, Eq, Default)]
158#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
159pub struct CapabilityHookAppliesToV1 {
160 #[cfg_attr(
162 feature = "serde",
163 serde(default, skip_serializing_if = "Vec::is_empty")
164 )]
165 pub op_names: Vec<String>,
166}
167
168#[derive(Debug, thiserror::Error)]
170pub enum CapabilitiesExtensionError {
171 #[error("capabilities extension serialize failed: {0}")]
173 Serialize(String),
174 #[error("capabilities extension deserialize failed: {0}")]
176 Deserialize(String),
177 #[error("unsupported capabilities extension schema_version {0}")]
179 UnsupportedSchemaVersion(u32),
180 #[error("capabilities extension offer `{offer_id}` requires setup but setup is missing")]
182 MissingSetup {
183 offer_id: String,
185 },
186 #[error("capabilities extension offer `{offer_id}` has empty setup.qa_ref")]
188 InvalidSetupQaRef {
189 offer_id: String,
191 },
192 #[error("capabilities extension missing inline payload")]
194 MissingInline,
195 #[error("capabilities extension inline payload has unexpected type")]
197 UnexpectedInline,
198}
199
200#[cfg(feature = "serde")]
202pub fn encode_capabilities_extension_v1_to_cbor_bytes(
203 payload: &CapabilitiesExtensionV1,
204) -> Result<Vec<u8>, CapabilitiesExtensionError> {
205 let mut buf = Vec::new();
206 into_writer(payload, &mut buf)
207 .map_err(|err| CapabilitiesExtensionError::Serialize(err.to_string()))?;
208 Ok(buf)
209}
210
211#[cfg(feature = "serde")]
213pub fn decode_capabilities_extension_v1_from_cbor_bytes(
214 bytes: &[u8],
215) -> Result<CapabilitiesExtensionV1, CapabilitiesExtensionError> {
216 let decoded: CapabilitiesExtensionV1 = from_reader(bytes)
217 .map_err(|err| CapabilitiesExtensionError::Deserialize(err.to_string()))?;
218 decoded.validate()?;
219 Ok(decoded)
220}