sdf_parser_package/pkg/
config.rs

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}