Skip to main content

flowcore/model/
flow_manifest.rs

1#[cfg(feature = "debugger")]
2use std::collections::BTreeMap;
3use std::collections::BTreeSet;
4use std::collections::HashMap;
5use std::fmt;
6use std::fmt::Display;
7
8use serde_derive::{Deserialize, Serialize};
9use url::Url;
10
11use crate::deserializers::deserializer::get;
12use crate::errors::{Result, ResultExt};
13use crate::model::flow_definition::FlowDefinition;
14use crate::model::metadata::MetaData;
15use crate::model::runtime_function::RuntimeFunction;
16use crate::provider::Provider;
17
18/// The default name used for a flow Manifest file if none is specified
19pub const DEFAULT_MANIFEST_FILENAME: &str = "manifest";
20
21impl From<&FlowDefinition> for MetaData {
22    fn from(flow: &FlowDefinition) -> Self {
23        flow.metadata.clone()
24    }
25}
26
27#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
28/// `Cargo` meta-data that can be used as a source of meta-data
29pub struct Cargo {
30    /// We are only interested in the `package` part - as a source of meta-data
31    pub package: MetaData,
32}
33
34#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Debug)]
35/// Describes a flow's direct children: which sub-flow IDs it contains
36pub struct FlowInfo {
37    /// The unique process ID of this flow
38    pub process_id: usize,
39    /// The ID of the parent flow, if any
40    pub parent_id: Option<usize>,
41    /// IDs of direct child sub-flows
42    pub sub_flow_ids: Vec<usize>,
43}
44
45#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Debug)]
46/// A `flows` `Manifest` describes it and describes all the `Functions` it uses as well as
47/// a list of references to libraries.
48pub struct FlowManifest {
49    /// The `MetaData` about this flow
50    metadata: MetaData,
51    /// A list of the `lib_references` used by this flow
52    lib_references: BTreeSet<Url>,
53    /// A list of the `context_references` used by this flow
54    context_references: BTreeSet<Url>,
55    /// A list of `RuntimeFunctions` in this flow
56    functions: HashMap<usize, RuntimeFunction>,
57    /// Flow hierarchy: which sub-flows each flow contains
58    #[serde(default)]
59    flows: HashMap<usize, FlowInfo>,
60    #[cfg(feature = "debugger")]
61    /// A list of the source files used to build this `flow`
62    source_urls: BTreeMap<String, Url>,
63}
64
65impl Display for FlowManifest {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        for function in self.functions.values() {
68            writeln!(
69                f,
70                "         Function #{} Implementation: {}",
71                function.id(),
72                function.get_implementation_url()
73            )?;
74        }
75        write!(f, "")
76    }
77}
78
79impl FlowManifest {
80    /// Create a new manifest that can then be added to, and used in serialization
81    #[must_use]
82    pub fn new(metadata: MetaData) -> Self {
83        FlowManifest {
84            metadata,
85            lib_references: BTreeSet::<Url>::new(),
86            context_references: BTreeSet::<Url>::new(),
87            functions: HashMap::new(),
88            flows: HashMap::new(),
89            #[cfg(feature = "debugger")]
90            source_urls: BTreeMap::<String, Url>::new(),
91        }
92    }
93
94    /// Add a run-time Function to the manifest for use in serialization
95    pub fn add_function(&mut self, function: RuntimeFunction) {
96        self.functions.insert(function.id(), function);
97    }
98
99    /// Add flow hierarchy info to the manifest
100    pub fn add_flow_info(&mut self, flow_info: FlowInfo) {
101        self.flows.insert(flow_info.process_id, flow_info);
102    }
103
104    /// Get the flow hierarchy
105    #[must_use]
106    pub fn flows(&self) -> &HashMap<usize, FlowInfo> {
107        &self.flows
108    }
109
110    /// Get the list of functions in this manifest
111    #[must_use]
112    pub fn functions(&self) -> &HashMap<usize, RuntimeFunction> {
113        &self.functions
114    }
115
116    /// Get the list of functions in this manifest
117    pub fn get_functions(&mut self) -> &mut HashMap<usize, RuntimeFunction> {
118        &mut self.functions
119    }
120
121    /// Take the map of functions out of this manifest
122    #[must_use]
123    pub fn take_functions(self) -> HashMap<usize, RuntimeFunction> {
124        self.functions
125    }
126
127    /// Get the metadata structure for this manifest
128    #[must_use]
129    pub fn get_metadata(&self) -> &MetaData {
130        &self.metadata
131    }
132
133    /// get the list of all library references in this manifest
134    #[must_use]
135    pub fn get_lib_references(&self) -> &BTreeSet<Url> {
136        &self.lib_references
137    }
138
139    /// get the list of all context references in this manifest
140    #[must_use]
141    pub fn get_context_references(&self) -> &BTreeSet<Url> {
142        &self.context_references
143    }
144
145    /// set the list of all library references in this manifest
146    pub fn set_lib_references(&mut self, lib_references: &BTreeSet<Url>) {
147        self.lib_references.clone_from(lib_references);
148    }
149
150    /// set the list of all context references in this manifest
151    pub fn set_context_references(&mut self, context_references: &BTreeSet<Url>) {
152        self.context_references.clone_from(context_references);
153    }
154
155    /// Add a new library reference (the name of a library) into the manifest
156    pub fn add_lib_reference(&mut self, lib_reference: &Url) {
157        self.lib_references.insert(lib_reference.clone());
158    }
159
160    /// Add a new context reference (the name of a library) into the manifest
161    pub fn add_context_reference(&mut self, context_reference: &Url) {
162        self.context_references.insert(context_reference.clone());
163    }
164
165    /// set the list of all source urls used in the flow
166    #[cfg(feature = "debugger")]
167    pub fn set_source_urls(&mut self, source_urls: BTreeMap<String, Url>) {
168        self.source_urls = source_urls;
169    }
170
171    /// Get the list of source files used in the flow
172    #[cfg(feature = "debugger")]
173    #[must_use]
174    pub fn get_source_urls(&self) -> &BTreeMap<String, Url> {
175        &self.source_urls
176    }
177
178    /// Load, or Deserialize, a manifest from a `source` Url using `provider`
179    /// Sets all `location_url` fields to be URLs, a file URL for provided implementations
180    ///
181    /// # Errors
182    ///
183    /// Returns `Err`if `manifest_url` cannot be resolved to a real url, the contents cannot be
184    /// read from the resolved url, if the contents are not valid Utf8, or if the implementation
185    /// url for the function definition is invalid
186    pub fn load(provider: &dyn Provider, manifest_url: &Url) -> Result<(FlowManifest, Url)> {
187        let (resolved_url, _) = provider
188            .resolve_url(manifest_url, DEFAULT_MANIFEST_FILENAME, &["json"])
189            .chain_err(|| "Could not resolve url for manifest.json")?;
190
191        let contents = provider
192            .get_contents(&resolved_url)
193            .chain_err(|| "Could not get contents while attempting to load manifest")?;
194
195        let url = resolved_url.clone();
196        let content =
197            String::from_utf8(contents).chain_err(|| "Could not convert from utf8 to String")?;
198        let deserializer = get::<FlowManifest>(&resolved_url)?;
199        let mut manifest = deserializer
200            .deserialize(&content, Some(&resolved_url))
201            .chain_err(|| format!("Could not create a FlowManifest from '{manifest_url}'"))?;
202
203        // normalize the implementation_locations into URLs.
204        // context: and lib: URLs will be untouched
205        // relative path locations to the manifest_url to file:// using the manifest Url as the base
206        for function in manifest.functions.values_mut() {
207            function.set_implementation_url(&resolved_url)?;
208        }
209
210        Ok((manifest, url))
211    }
212}
213
214#[cfg(test)]
215#[allow(clippy::unwrap_used, clippy::expect_used)]
216mod test {
217    use url::Url;
218
219    use crate::errors::Result;
220    use crate::model::input::Input;
221    use crate::model::runtime_function::RuntimeFunction;
222    use crate::provider::Provider;
223
224    use super::{FlowManifest, MetaData};
225
226    fn test_meta_data() -> MetaData {
227        MetaData {
228            name: "test".into(),
229            version: "0.0.0".into(),
230            description: "a test".into(),
231            authors: vec!["me".into()],
232        }
233    }
234
235    #[allow(clippy::module_name_repetitions)]
236    pub struct TestProvider {
237        test_content: &'static str,
238    }
239
240    impl Provider for TestProvider {
241        fn resolve_url(
242            &self,
243            source: &Url,
244            _default_filename: &str,
245            _extensions: &[&str],
246        ) -> Result<(Url, Option<Url>)> {
247            Ok((source.clone(), None))
248        }
249
250        fn get_contents(&self, _url: &Url) -> Result<Vec<u8>> {
251            Ok(self.test_content.as_bytes().to_owned())
252        }
253    }
254
255    #[test]
256    fn create() {
257        let _ = FlowManifest::new(test_meta_data());
258    }
259
260    fn test_function() -> RuntimeFunction {
261        RuntimeFunction::new(
262            #[cfg(feature = "debugger")]
263            "test",
264            #[cfg(feature = "debugger")]
265            "/test",
266            "file://fake/test",
267            vec![Input::new(
268                #[cfg(feature = "debugger")]
269                "",
270                0,
271                false,
272                None,
273                None,
274            )],
275            0,
276            0,
277            &[],
278            false,
279        )
280    }
281
282    #[test]
283    fn add_function() {
284        let function = test_function();
285
286        let mut manifest = FlowManifest::new(test_meta_data());
287        manifest.add_function(function);
288        assert_eq!(manifest.functions.len(), 1);
289    }
290
291    #[test]
292    fn load_manifest() {
293        let test_content = "{
294            \"metadata\": {
295                \"name\": \"\",
296                \"version\": \"0.1.0\",
297                \"description\": \"\",
298                \"authors\": []
299                },
300            \"manifest_dir\": \"fake dir\",
301            \"lib_references\": [
302             ],
303            \"context_references\": [
304                \"context://\"
305             ],
306            \"functions\": {
307                \"0\": {
308                    \"name\": \"print\",
309                    \"route\": \"/print\",
310                    \"process_id\": 0,
311                    \"parent_id\": 0,
312                    \"implementation_location\": \"context://stdio/stdout\",
313                    \"inputs\": [ {} ]
314                }
315             },
316            \"source_urls\": {}
317            }";
318        let provider = TestProvider { test_content };
319
320        FlowManifest::load(
321            &provider,
322            &Url::parse("http://ibm.com/fake.json").expect("Could not parse URL"),
323        )
324        .expect("Could not load manifest");
325    }
326}