1use std::{
2 collections::BTreeMap,
3 ops::{Deref, DerefMut},
4};
5
6use sdf_parser_core::config::{
7 dev::DevConfig,
8 import::{PackageImport, PackageMetadata},
9 transform::TypedState,
10 types::MetadataTypesMap,
11};
12use serde::{Deserialize, Serialize};
13use serde_with::{serde_as, KeyValueMap};
14
15use super::functions::Function;
16
17pub fn parse_package(data_pipeline: &str) -> anyhow::Result<PackageConfig> {
18 let config: PackageConfig = serde_yaml::from_str(data_pipeline)?;
19
20 Ok(config)
21}
22
23pub type CurentPkgConfig = PackageWrapperV0_5_0;
24pub type DevPkgConfig = PackageWrapperV0_5_0;
25
26#[derive(Serialize, Deserialize, Debug)]
27#[serde(tag = "apiVersion")]
28pub enum PackageConfig {
29 #[serde(rename = "0.4.0")]
30 V0_4_0(CurentPkgConfig),
31 #[serde(rename = "0.5.0")]
32 V0_5_0(DevPkgConfig),
33}
34
35impl PackageConfig {
36 pub fn is_v5(&self) -> bool {
37 matches!(self, Self::V0_5_0(_))
38 }
39
40 pub fn is_v4(&self) -> bool {
41 matches!(self, Self::V0_4_0(_))
42 }
43
44 pub fn imports(&self) -> &Vec<PackageImport> {
45 match self {
46 Self::V0_4_0(wrapper) => &wrapper.imports,
47 Self::V0_5_0(wrapper) => &wrapper.imports,
48 }
49 }
50
51 pub fn types(&self) -> &MetadataTypesMap {
52 match self {
53 Self::V0_4_0(wrapper) => &wrapper.types,
54 Self::V0_5_0(wrapper) => &wrapper.types,
55 }
56 }
57
58 pub fn states(&self) -> &BTreeMap<String, TypedState> {
59 match self {
60 Self::V0_4_0(wrapper) => &wrapper.states,
61 Self::V0_5_0(wrapper) => &wrapper.states,
62 }
63 }
64
65 pub fn functions(&self) -> &Vec<Function> {
66 match self {
67 Self::V0_4_0(wrapper) => &wrapper.functions,
68 Self::V0_5_0(wrapper) => &wrapper.functions,
69 }
70 }
71
72 pub fn dev(&self) -> Option<&DevConfig> {
73 match self {
74 Self::V0_4_0(_) => None,
75 Self::V0_5_0(wrapper) => wrapper.dev.as_ref(),
76 }
77 }
78}
79
80impl Deref for PackageConfig {
81 type Target = CurentPkgConfig;
82
83 fn deref(&self) -> &Self::Target {
84 match self {
85 Self::V0_4_0(wrapper) => wrapper,
86 Self::V0_5_0(wrapper) => wrapper,
87 }
88 }
89}
90
91impl DerefMut for PackageConfig {
92 fn deref_mut(&mut self) -> &mut Self::Target {
93 match self {
94 Self::V0_4_0(wrapper) => wrapper,
95 Self::V0_5_0(wrapper) => wrapper,
96 }
97 }
98}
99
100#[serde_as]
101#[derive(Serialize, Deserialize, Debug, Clone, Default)]
102pub struct PackageWrapperV0_5_0 {
103 pub meta: PackageMetadata,
104 #[serde(skip_serializing_if = "Vec::is_empty", default)]
105 pub imports: Vec<PackageImport>,
106 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
107 pub types: MetadataTypesMap,
108 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
109 pub states: BTreeMap<String, TypedState>,
110 #[serde(skip_serializing_if = "Vec::is_empty", default)]
111 #[serde_as(as = "KeyValueMap<_>")]
112 pub functions: Vec<Function>,
113 pub dev: Option<DevConfig>,
114}
115
116#[derive(Serialize, Deserialize, Debug, Clone, Default)]
117pub struct PackageUnsupportVersion {
118 pub meta: PackageMetadata,
119}
120#[cfg(test)]
121mod tests {
122
123 use sdf_parser_core::config::{
124 transform::{Lang, StepInvocationDefinition},
125 types::{MetadataTypeInner, MetadataTypeTagged, NamedType},
126 SerdeConverter,
127 };
128
129 use super::*;
130
131 #[test]
132 fn test_parse_package() {
133 let yaml = "
134apiVersion: 0.5.0
135meta:
136 name: my-package
137 version: 0.1.0
138 namespace: example
139
140types:
141 sentence:
142 type: string
143
144states:
145 count-per-model:
146 type: keyed-state
147 properties:
148 key:
149 type: string
150 value:
151 type: u32
152
153functions:
154 my-hello-fn:
155 operator: filter-map
156 language: rust
157 inputs:
158 - name: input
159 type: sentence
160 output:
161 type: string
162
163dev:
164 converter: raw # options: raw, json
165"
166 .to_string();
167
168 let config = parse_package(&yaml).expect("should validate");
169
170 assert_eq!(config.meta.name, "my-package");
171 assert_eq!(config.meta.version, "0.1.0");
172 assert_eq!(config.meta.namespace, "example");
173
174 let types = &config.types;
175 let sentence_ty = types.map.get("sentence").expect("type to be found");
176 assert_eq!(
177 sentence_ty,
178 &MetadataTypeInner::MetadataTypeTagged(MetadataTypeTagged::String).into()
179 );
180
181 let states = &config.states;
182 assert_eq!(states.len(), 1);
183
184 config
185 .states
186 .iter()
187 .find(|(state_name, _)| *state_name == "count-per-model")
188 .expect("State to have parsed");
189
190 let function = config.functions.first().expect("should have a function");
191
192 match &function.inner().definition {
193 StepInvocationDefinition::Function(function) => {
194 assert_eq!(Lang::Rust, function.lang);
195 assert_eq!(function.uses, "my-hello-fn");
196 assert_eq!(function.inputs.len(), 1);
197 assert_eq!(function.inputs[0].name, "input");
198 assert_eq!(
199 function.inputs[0].ty,
200 MetadataTypeInner::NamedType(NamedType {
201 ty: "sentence".to_string()
202 })
203 .into()
204 );
205 assert_eq!(
206 function.output.as_ref().unwrap().ty,
207 MetadataTypeInner::MetadataTypeTagged(MetadataTypeTagged::String).into()
208 );
209 }
210 _ => panic!("incorrect function type parsed"),
211 }
212
213 assert_eq!(
214 config.dev.as_ref().unwrap().converter,
215 Some(SerdeConverter::Raw)
216 );
217 }
218
219 #[test]
220 fn test_valid_imports_validate() {
221 let yaml = "
222apiVersion: 0.5.0
223meta:
224 name: my-package
225 version: 0.1.0
226 namespace: example
227
228imports:
229 - pkg: example/bank-types@0.1.0
230 types:
231 - name: bank-event
232 states:
233 - name: account-balance
234
235functions:
236 update-bank-account:
237 operator: update-state
238 language: rust
239 states:
240 - name: account-balance
241 inputs:
242 - name: input
243 type: string
244
245dev:
246 converter: json
247 imports:
248 - pkg: example/bank-types@0.1.0
249 path: ../bank-types
250"
251 .to_string();
252
253 let config: PackageConfig = serde_yaml::from_str(&yaml).expect("function to parse");
254
255 let import = config.imports.first().expect("Should have an import");
256
257 assert_eq!(import.package.namespace, "example");
258 assert_eq!(import.package.name, "bank-types");
259 assert_eq!(import.package.version, "0.1.0");
260 assert_eq!(import.types[0].name, "bank-event");
261 assert_eq!(import.states[0].name, "account-balance");
262
263 let function = config.functions.first().expect("Should have a function");
264
265 match &function.inner().definition {
266 StepInvocationDefinition::Function(function) => {
267 assert_eq!(Lang::Rust, function.lang);
268 assert_eq!(function.uses, "update-bank-account");
269 assert_eq!(function.state_imports[0].name, "account-balance");
270 assert_eq!(function.inputs[0].name, "input");
271 assert_eq!(
272 function.inputs[0].ty,
273 MetadataTypeInner::MetadataTypeTagged(MetadataTypeTagged::String).into()
274 );
275 }
276 _ => panic!("incorrect function type parsed"),
277 }
278
279 let dev_config = config.dev.as_ref().expect("Should have dev config");
280
281 assert_eq!(dev_config.imports[0].package.namespace, "example");
282 assert_eq!(dev_config.imports[0].package.name, "bank-types");
283 assert_eq!(dev_config.imports[0].package.version, "0.1.0");
284 assert_eq!(
285 dev_config.imports[0].path,
286 Some(String::from("../bank-types"))
287 );
288 }
289
290 #[test]
291 fn test_import_names_are_validated() {
292 let yaml = "
293apiVersion: 0.5.0
294meta:
295 name: my-package
296 version: 0.1.0
297 namespace: example
298
299imports:
300 - pkg: bank-types@0.1.0
301 types:
302 - name: bank-event
303 states:
304 - name: account-balance
305
306functions:
307 update-bank-account:
308 operator: update-state
309 language: rust
310 states:
311 - name: account-balance
312 inputs:
313 - name: input
314 type: string
315
316dev:
317 converter: json
318 imports:
319 - pkg: example/bank-types@0.1.0
320 path: ../bank-types
321"
322 .to_string();
323
324 let error = serde_yaml::from_str::<PackageConfig>(&yaml).unwrap_err();
325
326 assert_eq!(
327 error.to_string(),
328 "invalid value: string \"bank-types@0.1.0\", expected a string of the form `<namespace>/<name>@<version>`"
329 );
330 }
331
332 #[test]
333 fn test_api_version() {
334 let v5_yaml = "
335apiVersion: 0.5.0
336meta:
337 name: my-package
338 version: 0.1.0
339 namespace: example
340"
341 .to_string();
342
343 let config: PackageConfig = serde_yaml::from_str(&v5_yaml).expect("function to parse");
344 assert!(config.is_v5());
345 assert!(!config.is_v4());
346 drop(config);
347
348 let v4_yaml = "
349 apiVersion: 0.4.0
350 meta:
351 name: my-package
352 version: 0.1.0
353 namespace: example
354
355 "
356 .to_string();
357
358 let config: PackageConfig = serde_yaml::from_str(&v4_yaml).expect("function to parse");
359 assert!(!config.is_v5());
360 assert!(config.is_v4());
361 }
362}