1use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use semver::Version;
8
9use crate::flow::FlowKind;
10use crate::{ComponentId, FlowId, SecretRequirement};
11
12#[cfg(feature = "schemars")]
13use schemars::JsonSchema;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17#[derive(Clone, Debug, PartialEq)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[cfg_attr(feature = "schemars", derive(JsonSchema))]
24pub struct ComponentDevFlow {
25 #[cfg_attr(feature = "serde", serde(default = "dev_flow_default_format"))]
27 pub format: String,
28 pub graph: serde_json::Value,
30}
31
32fn dev_flow_default_format() -> String {
33 "flow-ir-json".to_owned()
34}
35
36#[derive(Clone, Debug, PartialEq)]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39#[cfg_attr(feature = "schemars", derive(JsonSchema))]
40pub struct ComponentManifest {
41 pub id: ComponentId,
43 #[cfg_attr(
45 feature = "schemars",
46 schemars(with = "String", description = "SemVer version")
47 )]
48 pub version: Version,
49 #[cfg_attr(feature = "serde", serde(default))]
51 pub supports: Vec<FlowKind>,
52 pub world: String,
54 pub profiles: ComponentProfiles,
56 pub capabilities: ComponentCapabilities,
58 #[cfg_attr(
60 feature = "serde",
61 serde(default, skip_serializing_if = "Option::is_none")
62 )]
63 pub configurators: Option<ComponentConfigurators>,
64 #[cfg_attr(feature = "serde", serde(default))]
66 pub operations: Vec<ComponentOperation>,
67 #[cfg_attr(
69 feature = "serde",
70 serde(default, skip_serializing_if = "Option::is_none")
71 )]
72 pub config_schema: Option<serde_json::Value>,
73 #[cfg_attr(feature = "serde", serde(default))]
75 pub resources: ResourceHints,
76 #[cfg_attr(
80 feature = "serde",
81 serde(default, skip_serializing_if = "BTreeMap::is_empty")
82 )]
83 pub dev_flows: BTreeMap<FlowId, ComponentDevFlow>,
84}
85
86impl ComponentManifest {
87 pub fn supports_kind(&self, kind: FlowKind) -> bool {
89 self.supports.iter().copied().any(|entry| entry == kind)
90 }
91
92 pub fn select_profile<'a>(
95 &'a self,
96 requested: Option<&str>,
97 ) -> Result<Option<&'a str>, ComponentProfileError> {
98 if let Some(name) = requested {
99 let matched = self
100 .profiles
101 .supported
102 .iter()
103 .find(|candidate| candidate.as_str() == name)
104 .ok_or_else(|| ComponentProfileError::UnsupportedProfile {
105 requested: name.to_owned(),
106 supported: self.profiles.supported.clone(),
107 })?;
108 Ok(Some(matched.as_str()))
109 } else {
110 Ok(self.profiles.default.as_deref())
111 }
112 }
113
114 pub fn basic_configurator(&self) -> Option<&FlowId> {
116 self.configurators
117 .as_ref()
118 .and_then(|cfg| cfg.basic.as_ref())
119 }
120
121 pub fn full_configurator(&self) -> Option<&FlowId> {
123 self.configurators
124 .as_ref()
125 .and_then(|cfg| cfg.full.as_ref())
126 }
127}
128
129#[derive(Clone, Debug, PartialEq, Eq, Default)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132#[cfg_attr(feature = "schemars", derive(JsonSchema))]
133pub struct ComponentProfiles {
134 #[cfg_attr(
136 feature = "serde",
137 serde(default, skip_serializing_if = "Option::is_none")
138 )]
139 pub default: Option<String>,
140 #[cfg_attr(feature = "serde", serde(default))]
142 pub supported: Vec<String>,
143}
144
145#[derive(Clone, Debug, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
148#[cfg_attr(feature = "schemars", derive(JsonSchema))]
149pub struct ComponentConfigurators {
150 #[cfg_attr(
152 feature = "serde",
153 serde(default, skip_serializing_if = "Option::is_none")
154 )]
155 pub basic: Option<FlowId>,
156 #[cfg_attr(
158 feature = "serde",
159 serde(default, skip_serializing_if = "Option::is_none")
160 )]
161 pub full: Option<FlowId>,
162}
163
164#[derive(Clone, Debug, PartialEq)]
166#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
167#[cfg_attr(feature = "schemars", derive(JsonSchema))]
168pub struct ComponentOperation {
169 pub name: String,
171 pub input_schema: serde_json::Value,
173 pub output_schema: serde_json::Value,
175}
176
177#[derive(Clone, Debug, PartialEq, Eq, Default)]
179#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
180#[cfg_attr(feature = "schemars", derive(JsonSchema))]
181pub struct ResourceHints {
182 #[cfg_attr(
184 feature = "serde",
185 serde(default, skip_serializing_if = "Option::is_none")
186 )]
187 pub cpu_millis: Option<u32>,
188 #[cfg_attr(
190 feature = "serde",
191 serde(default, skip_serializing_if = "Option::is_none")
192 )]
193 pub memory_mb: Option<u32>,
194 #[cfg_attr(
196 feature = "serde",
197 serde(default, skip_serializing_if = "Option::is_none")
198 )]
199 pub average_latency_ms: Option<u32>,
200}
201
202#[derive(Clone, Debug, PartialEq, Eq, Default)]
204#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
205#[cfg_attr(feature = "schemars", derive(JsonSchema))]
206pub struct ComponentCapabilities {
207 pub wasi: WasiCapabilities,
209 pub host: HostCapabilities,
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, Default)]
215#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
216#[cfg_attr(feature = "schemars", derive(JsonSchema))]
217pub struct WasiCapabilities {
218 #[cfg_attr(
220 feature = "serde",
221 serde(default, skip_serializing_if = "Option::is_none")
222 )]
223 pub filesystem: Option<FilesystemCapabilities>,
224 #[cfg_attr(
226 feature = "serde",
227 serde(default, skip_serializing_if = "Option::is_none")
228 )]
229 pub env: Option<EnvCapabilities>,
230 #[cfg_attr(feature = "serde", serde(default))]
232 pub random: bool,
233 #[cfg_attr(feature = "serde", serde(default))]
235 pub clocks: bool,
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
240#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241#[cfg_attr(feature = "schemars", derive(JsonSchema))]
242pub struct FilesystemCapabilities {
243 pub mode: FilesystemMode,
245 #[cfg_attr(feature = "serde", serde(default))]
247 pub mounts: Vec<FilesystemMount>,
248}
249
250impl Default for FilesystemCapabilities {
251 fn default() -> Self {
252 Self {
253 mode: FilesystemMode::None,
254 mounts: Vec::new(),
255 }
256 }
257}
258
259#[derive(Clone, Debug, PartialEq, Eq, Default)]
261#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
262#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
263#[cfg_attr(feature = "schemars", derive(JsonSchema))]
264pub enum FilesystemMode {
265 #[default]
267 None,
268 ReadOnly,
270 Sandbox,
272}
273
274#[derive(Clone, Debug, PartialEq, Eq)]
276#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
277#[cfg_attr(feature = "schemars", derive(JsonSchema))]
278pub struct FilesystemMount {
279 pub name: String,
281 pub host_class: String,
283 pub guest_path: String,
285}
286
287#[derive(Clone, Debug, PartialEq, Eq, Default)]
289#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
290#[cfg_attr(feature = "schemars", derive(JsonSchema))]
291pub struct EnvCapabilities {
292 #[cfg_attr(feature = "serde", serde(default))]
294 pub allow: Vec<String>,
295}
296
297#[derive(Clone, Debug, PartialEq, Eq, Default)]
299#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
300#[cfg_attr(feature = "schemars", derive(JsonSchema))]
301pub struct HostCapabilities {
302 #[cfg_attr(
304 feature = "serde",
305 serde(default, skip_serializing_if = "Option::is_none")
306 )]
307 pub secrets: Option<SecretsCapabilities>,
308 #[cfg_attr(
310 feature = "serde",
311 serde(default, skip_serializing_if = "Option::is_none")
312 )]
313 pub state: Option<StateCapabilities>,
314 #[cfg_attr(
316 feature = "serde",
317 serde(default, skip_serializing_if = "Option::is_none")
318 )]
319 pub messaging: Option<MessagingCapabilities>,
320 #[cfg_attr(
322 feature = "serde",
323 serde(default, skip_serializing_if = "Option::is_none")
324 )]
325 pub events: Option<EventsCapabilities>,
326 #[cfg_attr(
328 feature = "serde",
329 serde(default, skip_serializing_if = "Option::is_none")
330 )]
331 pub http: Option<HttpCapabilities>,
332 #[cfg_attr(
334 feature = "serde",
335 serde(default, skip_serializing_if = "Option::is_none")
336 )]
337 pub telemetry: Option<TelemetryCapabilities>,
338 #[cfg_attr(
340 feature = "serde",
341 serde(default, skip_serializing_if = "Option::is_none")
342 )]
343 pub iac: Option<IaCCapabilities>,
344}
345
346#[derive(Clone, Debug, PartialEq, Eq, Default)]
348#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
349#[cfg_attr(feature = "schemars", derive(JsonSchema))]
350pub struct SecretsCapabilities {
351 #[cfg_attr(feature = "serde", serde(default))]
353 pub required: Vec<SecretRequirement>,
354}
355
356#[derive(Clone, Debug, PartialEq, Eq, Default)]
358#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
359#[cfg_attr(feature = "schemars", derive(JsonSchema))]
360pub struct StateCapabilities {
361 #[cfg_attr(feature = "serde", serde(default))]
363 pub read: bool,
364 #[cfg_attr(feature = "serde", serde(default))]
366 pub write: bool,
367}
368
369#[derive(Clone, Debug, PartialEq, Eq, Default)]
371#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
372#[cfg_attr(feature = "schemars", derive(JsonSchema))]
373pub struct MessagingCapabilities {
374 #[cfg_attr(feature = "serde", serde(default))]
376 pub inbound: bool,
377 #[cfg_attr(feature = "serde", serde(default))]
379 pub outbound: bool,
380}
381
382#[derive(Clone, Debug, PartialEq, Eq, Default)]
384#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
385#[cfg_attr(feature = "schemars", derive(JsonSchema))]
386pub struct EventsCapabilities {
387 #[cfg_attr(feature = "serde", serde(default))]
389 pub inbound: bool,
390 #[cfg_attr(feature = "serde", serde(default))]
392 pub outbound: bool,
393}
394
395#[derive(Clone, Debug, PartialEq, Eq, Default)]
397#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
398#[cfg_attr(feature = "schemars", derive(JsonSchema))]
399pub struct HttpCapabilities {
400 #[cfg_attr(feature = "serde", serde(default))]
402 pub client: bool,
403 #[cfg_attr(feature = "serde", serde(default))]
405 pub server: bool,
406}
407
408#[derive(Clone, Debug, PartialEq, Eq, Hash)]
410#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
411#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
412#[cfg_attr(feature = "schemars", derive(JsonSchema))]
413pub enum TelemetryScope {
414 Tenant,
416 Pack,
418 Node,
420}
421
422#[derive(Clone, Debug, PartialEq, Eq)]
424#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
425#[cfg_attr(feature = "schemars", derive(JsonSchema))]
426pub struct TelemetryCapabilities {
427 pub scope: TelemetryScope,
429}
430
431#[derive(Clone, Debug, PartialEq, Eq)]
433#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
434#[cfg_attr(feature = "schemars", derive(JsonSchema))]
435pub struct IaCCapabilities {
436 pub write_templates: bool,
438 #[cfg_attr(feature = "serde", serde(default))]
440 pub execute_plans: bool,
441}
442
443#[derive(Clone, Debug, PartialEq, Eq)]
445pub enum ComponentProfileError {
446 UnsupportedProfile {
448 requested: String,
450 supported: Vec<String>,
452 },
453}
454
455impl core::fmt::Display for ComponentProfileError {
456 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
457 match self {
458 ComponentProfileError::UnsupportedProfile {
459 requested,
460 supported,
461 } => {
462 write!(
463 f,
464 "profile `{requested}` is not supported; known profiles: {supported:?}"
465 )
466 }
467 }
468 }
469}
470
471#[cfg(feature = "std")]
472impl std::error::Error for ComponentProfileError {}