1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12pub struct ComponentMetadata {
13 pub component_type: String,
14 pub name: String,
15 pub status: String,
16 pub description: String,
17 pub fields: Vec<FieldMetadata>,
18 #[serde(default, skip_serializing_if = "Vec::is_empty")]
20 pub metrics: Vec<MetricMetadata>,
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
23 pub outputs: Vec<OutputFieldMetadata>,
24 #[serde(default, skip_serializing_if = "Vec::is_empty")]
26 pub env_vars: Vec<EnvVarMetadata>,
27 #[serde(default, skip_serializing_if = "Vec::is_empty")]
29 pub permissions: Vec<PermissionMetadata>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct FieldMetadata {
35 pub name: String,
36 pub rust_type: String,
37 pub user_type: String,
38 pub required: bool,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub default: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub example: Option<String>,
43 pub secret: bool,
44 pub description: String,
45 #[serde(default, skip_serializing_if = "Vec::is_empty")]
47 pub children: Vec<FieldMetadata>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub category: Option<String>,
51 #[serde(default, skip_serializing_if = "Vec::is_empty")]
53 pub enum_values: Vec<EnumValueMetadata>,
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
56 pub variants: Vec<VariantMetadata>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub reference: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub reference_type: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67pub struct EnumValueMetadata {
68 pub name: String,
70 pub description: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75pub struct VariantMetadata {
76 pub name: String,
78 pub description: String,
79 pub fields: Vec<FieldMetadata>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub example: Option<String>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
88pub struct MetricMetadata {
89 pub name: String,
90 pub metric_type: String, pub description: String,
92 #[serde(default, skip_serializing_if = "Vec::is_empty")]
94 pub tags: Vec<String>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
99pub struct OutputFieldMetadata {
100 pub name: String,
101 pub field_type: String,
102 pub description: String,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107pub struct EnvVarMetadata {
108 pub name: String,
109 pub description: String,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
114pub struct PermissionMetadata {
115 pub action: String,
116 pub service: String,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub notes: Option<String>,
119}
120
121pub trait HasFieldsMetadata {
123 fn fields_metadata() -> Vec<FieldMetadata>;
125}
126
127impl<T: HasFieldsMetadata> HasFieldsMetadata for Option<T> {
129 fn fields_metadata() -> Vec<FieldMetadata> {
130 T::fields_metadata()
131 }
132}
133
134impl<T: HasFieldsMetadata> HasFieldsMetadata for Box<T> {
136 fn fields_metadata() -> Vec<FieldMetadata> {
137 T::fields_metadata()
138 }
139}
140
141pub trait HasEnumMetadata {
143 fn enum_metadata() -> Vec<String>;
145}
146
147pub trait HasVariantMetadata {
149 fn variant_metadata() -> Vec<VariantMetadata>;
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, Default)]
155pub struct ComponentRegistry {
156 pub components: Vec<ComponentMetadata>,
157}
158
159impl ComponentRegistry {
160 pub fn sort(&mut self) {
162 self.components
163 .sort_by(|a, b| (&a.component_type, &a.name).cmp(&(&b.component_type, &b.name)));
164 }
165
166 pub fn to_json(&self) -> serde_json::Result<String> {
172 serde_json::to_string_pretty(self)
173 }
174
175 pub fn from_json(json: &str) -> serde_json::Result<Self> {
181 serde_json::from_str(json)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_roundtrip_json() {
191 let registry = ComponentRegistry {
192 components: vec![ComponentMetadata {
193 component_type: "sink".to_string(),
194 name: "kafka".to_string(),
195 status: "stable".to_string(),
196 description: "Produces events to Kafka.".to_string(),
197 fields: vec![FieldMetadata {
198 name: "topic".to_string(),
199 rust_type: "String".to_string(),
200 user_type: "string".to_string(),
201 required: true,
202 default: None,
203 example: Some("events".to_string()),
204 secret: false,
205 description: "The Kafka topic.".to_string(),
206 children: vec![],
207 category: None,
208 enum_values: vec![],
209 variants: vec![],
210 reference: None,
211 reference_type: None,
212 }],
213 metrics: vec![],
214 outputs: vec![],
215 env_vars: vec![],
216 permissions: vec![],
217 }],
218 };
219 let json = registry.to_json().unwrap();
220 let roundtripped = ComponentRegistry::from_json(&json).unwrap();
221 assert_eq!(registry.components, roundtripped.components);
222 }
223
224 #[test]
225 fn test_sort_deterministic() {
226 let mut registry = ComponentRegistry {
227 components: vec![
228 ComponentMetadata {
229 component_type: "transform".to_string(),
230 name: "filter".to_string(),
231 status: "stable".to_string(),
232 description: String::new(),
233 fields: vec![],
234 metrics: vec![],
235 outputs: vec![],
236 env_vars: vec![],
237 permissions: vec![],
238 },
239 ComponentMetadata {
240 component_type: "source".to_string(),
241 name: "kafka".to_string(),
242 status: "stable".to_string(),
243 description: String::new(),
244 fields: vec![],
245 metrics: vec![],
246 outputs: vec![],
247 env_vars: vec![],
248 permissions: vec![],
249 },
250 ComponentMetadata {
251 component_type: "sink".to_string(),
252 name: "s3".to_string(),
253 status: "stable".to_string(),
254 description: String::new(),
255 fields: vec![],
256 metrics: vec![],
257 outputs: vec![],
258 env_vars: vec![],
259 permissions: vec![],
260 },
261 ],
262 };
263 registry.sort();
264 let types: Vec<&str> = registry
265 .components
266 .iter()
267 .map(|c| c.component_type.as_str())
268 .collect();
269 assert_eq!(types, vec!["sink", "source", "transform"]);
270 }
271}