1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use log::{debug, info};
use serde_derive::{Deserialize, Serialize};
use url::Url;

use crate::deserializers::deserializer::get_deserializer;
use crate::errors::*;
use crate::Implementation;
use crate::model::metadata::MetaData;
use crate::provider::Provider;

/// The default name used for a Library  Manifest file if none is specified
pub const DEFAULT_LIB_JSON_MANIFEST_FILENAME: &str = "manifest";
/// The default name used for a Rust Library Manifest if none is specified
pub const DEFAULT_LIB_RUST_MANIFEST_FILENAME: &str = "manifest.rs";

/*
    Implementations can be of two types - either a statically linked function referenced
    via a function reference, or WASM bytecode file that is interpreted at run-time that is
    referenced via a string pointing to the .wasm file location
*/
#[derive(Deserialize, Serialize, Clone)]
#[serde(untagged)]
/// `ImplementationLocator` describes where an implementation can be located.
pub enum ImplementationLocator {
    #[serde(skip_deserializing, skip_serializing)]
    /// A `Native` - A reference to a trait object statically linked with the library
    Native(Arc<dyn Implementation>),
    /// A path indicating where the implementation file is located within the Library directory
    /// structure, relative to the lib root
    RelativePath(String),
}

impl PartialEq for ImplementationLocator {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                ImplementationLocator::RelativePath(self_source),
                ImplementationLocator::RelativePath(other_source),
            ) => self_source == other_source,
            _ => false,
        }
    }
}

#[derive(Deserialize, Serialize, Clone)]
/// `LibraryManifest` describes the contents of a Library that can be referenced from a `flow`
/// It is provided by libraries to help load and/or find implementations of processes
pub struct LibraryManifest {
    /// the Url that this library implements
    pub lib_url: Url,
    /// `metadata` about a flow with author, version and usual fields
    pub metadata: MetaData,
    /// the `locators` map a lib reference to a `ImplementationLocator` for a function or flow
    /// that can be used to load it or reference it.
    pub locators: BTreeMap<Url, ImplementationLocator>,
    /// source_files is a map of:
    /// Key: lib reference for functions or flows, as used in locators
    /// Value: Url where the source file it was derived from is located
    #[serde(default)]
    pub source_urls: BTreeMap<String, Url>,
}

impl LibraryManifest {
    /// Create a new, empty, `LibraryManifest` with the provided `Metadata`
    pub fn new(lib_url: Url, metadata: MetaData) -> Self {
        LibraryManifest {
            lib_url,
            metadata,
            locators: BTreeMap::<Url, ImplementationLocator>::new(),
            source_urls: BTreeMap::<String, Url>::new(),
        }
    }

    /// load a `LibraryManifest` from `lib_manifest_url`, using `provider` to fetch the contents
    pub fn load(provider: &dyn Provider, lib_manifest_url: &Url) -> Result<(LibraryManifest, Url)> {
        let (resolved_url, _) = provider
            .resolve_url(
                lib_manifest_url,
                DEFAULT_LIB_JSON_MANIFEST_FILENAME,
                &["json"],
            )
            .chain_err(|| {
                format!(
                    "Could not resolve the library manifest url '{lib_manifest_url}'"
                )
            })?;

        let manifest_content = provider.get_contents(&resolved_url).chain_err(|| {
            format!(
                "Could not read contents of Library Manifest from '{resolved_url}'"
            )
        })?;

        let url = resolved_url.clone();
        let content = String::from_utf8(manifest_content)
            .chain_err(|| "Could not convert from utf8 to String")?;
        let deserializer = get_deserializer::<LibraryManifest>(&resolved_url)?;
        let manifest = deserializer
            .deserialize(&content, Some(&resolved_url))
            .chain_err(|| format!("Could not create a LibraryManifest from '{resolved_url}'"))?;

        Ok((manifest, url))
    }

    /// Add a locator to the `LibraryManifest` to allow resolving "lib://" lib reference Urls
    /// for functions or flows to where the implementation resides within the library directory
    /// structure (relative to the lib root).
    /// Also add it to the list of source files lookups in the manifest if compiling with debug info
    pub fn add_locator(
        &mut self,
        implementation_path_relative: &str,
        lib_reference_path: &str,
        #[cfg(feature = "debugger")]
        implementation_source_path: &str,
    ) -> Result<()> {
        let lib_reference = Url::parse(&format!(
            "lib://{}/{lib_reference_path}",
            self.metadata.name
        ))
        .chain_err(|| "Could not form library Url to add to the manifest")?;

        debug!(
            "Adding implementation locator to lib manifest: \n'{}' -> '{}'",
            lib_reference, implementation_path_relative
        );
        self.locators.insert(
            lib_reference,
            ImplementationLocator::RelativePath(implementation_path_relative.to_owned()),
        );

        // Match the compiled wasm file (using lib relative path) to the source file it was compiled from
        #[cfg(feature = "debugger")]
        self.source_urls.insert(
            implementation_path_relative.to_owned(),
            Url::from_file_path(implementation_source_path)
                .map_err(|_| "Could not create Url from file path")?,
        );

        Ok(())
    }

    /// Given an output directory, return a PathBuf to the json format manifest that should be
    /// generated inside it
    pub fn manifest_filename(base_dir: &Path) -> PathBuf {
        let mut filename = base_dir.to_path_buf();
        filename.push(DEFAULT_LIB_JSON_MANIFEST_FILENAME);
        filename.set_extension("json");
        filename
    }

    /// Generate a manifest for the library in JSON
    pub fn write_json(&self, json_manifest_filename: &Path) -> Result<()> {
        let mut manifest_file = File::create(json_manifest_filename)?;

        manifest_file.write_all(
            serde_json::to_string_pretty(self)
                .chain_err(|| "Could not pretty format the library manifest JSON contents")?
                .as_bytes(),
        )?;

        info!("Generated library JSON manifest at '{}'", json_manifest_filename.display());

        Ok(())
    }
}

impl PartialEq for LibraryManifest {
    fn eq(&self, other: &Self) -> bool {
        if self.metadata != other.metadata {
            return false;
        }

        if self.locators.len() != other.locators.len() {
            return false;
        }

        for locator in self.locators.iter() {
            // try and find locator with the same key in the other HashMap
            if let Some(other_impl_locator) = other.locators.get(locator.0) {
                if *other_impl_locator != *locator.1 {
                    return false;
                }
            } else {
                return false; // no such locator in the other HashMap
            }
        }

        true // if we made it here then everything is the same
    }
}

#[cfg(test)]
mod test {
    use std::sync::Arc;

    use serde_json::Value;
    use url::Url;

    use crate::errors::Result;
    use crate::Implementation;
    use crate::model::lib_manifest::{
        ImplementationLocator, ImplementationLocator::Native, ImplementationLocator::RelativePath, LibraryManifest,
    };
    use crate::model::metadata::MetaData;
    use crate::provider::Provider;

    pub struct TestProvider {
        test_content: &'static str,
    }

    fn test_meta_data() -> MetaData {
        MetaData {
            name: "test".into(),
            version: "0.0.0".into(),
            description: "a test".into(),
            authors: vec!["me".into()],
        }
    }

    fn test_meta_data2() -> MetaData {
        MetaData {
            name: "different".into(),
            version: "0.0.0".into(),
            description: "a test".into(),
            authors: vec!["me".to_string()],
        }
    }

    impl Provider for TestProvider {
        fn resolve_url(
            &self,
            source: &Url,
            _default_filename: &str,
            _extensions: &[&str],
        ) -> Result<(Url, Option<Url>)> {
            Ok((source.clone(), None))
        }

        fn get_contents(&self, _url: &Url) -> Result<Vec<u8>> {
            Ok(self.test_content.as_bytes().to_owned())
        }
    }

    #[test]
    fn create() {
        let _ = LibraryManifest::new(
            Url::parse("lib://testlib").expect("Could not parse lib url"),
            test_meta_data(),
        );
    }

    #[test]
    fn wasm_locators_match() {
        let loc0 = RelativePath("location".into());
        let loc1 = RelativePath("location".into());

        assert!(loc0 == loc1);
    }

    #[test]
    fn wasm_locators_do_not_match() {
        let loc0 = RelativePath("location0".into());
        let loc1 = RelativePath("location1".into());

        assert!(loc0 != loc1);
    }

    #[test]
    fn locators_type_mismatch() {
        #[derive(Debug)]
        struct TestImpl {}

        impl Implementation for TestImpl {
            fn run(&self, _inputs: &[Value]) -> Result<(Option<Value>, bool)> {
                unimplemented!()
            }
        }
        let wasm_loc = RelativePath("wasm_location".into());
        let native_loc = Native(Arc::new(TestImpl {}));

        assert!(wasm_loc != native_loc);
    }

    #[test]
    fn serialize() {
        let metadata = MetaData {
            name: "".to_string(),
            description: "".into(),
            version: "0.1.0".into(),
            authors: vec![],
        };

        let locator: ImplementationLocator = RelativePath("add2.wasm".to_string());
        let mut manifest = LibraryManifest::new(
            Url::parse("lib://testlib").expect("Could not parse lib url"),
            metadata,
        );
        manifest.locators.insert(
            Url::parse("lib://flowrlib/test-dyn-lib/add2").expect("Could not create Url"),
            locator,
        );
        let serialized =
            serde_json::to_string_pretty(&manifest).expect("Could not pretty print JSON");
        let expected = "{
  \"lib_url\": \"lib://testlib\",
  \"metadata\": {
    \"name\": \"\",
    \"version\": \"0.1.0\",
    \"description\": \"\",
    \"authors\": []
  },
  \"locators\": {
    \"lib://flowrlib/test-dyn-lib/add2\": \"add2.wasm\"
  },
  \"source_urls\": {}
}";
        assert_eq!(expected, serialized);
    }

    #[test]
    fn load_dyn_library() {
        let test_content = "{
  \"lib_url\": \"lib://flowrlib\",
  \"metadata\": {
    \"name\": \"\",
    \"version\": \"0.1.0\",
    \"description\": \"\",
    \"authors\": []
  },
  \"locators\": {
    \"lib://flowrlib/test-dyn-lib/add2\": \"add2.wasm\"
  },
  \"source_urls\": {}
}";
        let test_provider = &TestProvider { test_content } as &dyn Provider;
        let url = Url::parse("file://test/fake.json").expect("Could not create Url");
        let (lib_manifest, _lib_manifest_url) =
            LibraryManifest::load(test_provider, &url).expect("Could not load manifest");
        assert_eq!(lib_manifest.locators.len(), 1);
        assert!(lib_manifest
            .locators
            .get(&Url::parse("lib://flowrlib/test-dyn-lib/add2").expect("Create Url error"))
            .is_some());
        let locator = lib_manifest
            .locators
            .get(&Url::parse("lib://flowrlib/test-dyn-lib/add2").expect("Create Url error"))
            .expect("Could not get locator for Url");
        match locator {
            RelativePath(source) => assert_eq!(source, "add2.wasm"),
            _ => panic!("Expected type 'Wasm' but found another type"),
        }
    }

    #[test]
    fn add_to() {
        let mut library = LibraryManifest::new(
            Url::parse("lib://testlib").expect("Could not parse lib url"),
            test_meta_data(),
        );
        library
            .add_locator("/bin/my.wasm", "/bin",
                         #[cfg(feature = "debugger")]
                             "/users/me/myproject/bin/my.rs",
            )
            .expect("Could not add to manifest");
        assert_eq!(
            library.locators.len(),
            1,
            "There should be one implementation location in the library manifest"
        );
    }

    #[test]
    fn compare_manifests_metadata_different() {
        let library1 = LibraryManifest::new(
            Url::parse("lib://testlib1").expect("Could not parse lib url"),
            test_meta_data(),
        );
        let library2 = LibraryManifest::new(
            Url::parse("lib://testlib2").expect("Could not parse lib url"),
            test_meta_data2(),
        );

        assert!(library1 != library2);
    }

    #[test]
    fn compare_manifests_num_locators_different() {
        let mut library1 = LibraryManifest::new(
            Url::parse("lib://testlib1").expect("Could not parse lib url"),
            test_meta_data(),
        );
        library1
            .add_locator("/bin/my.wasm", "/bin",
                         #[cfg(feature = "debugger")]
                             "/users/me/myproject/bin/my.rs",
            )
            .expect("Could not add to manifest");

        let library2 = LibraryManifest::new(
            Url::parse("lib://testlib1").expect("Could not parse lib url"),
            test_meta_data(),
        );

        assert!(library1 != library2);
    }

    #[test]
    fn compare_manifests_locators_different() {
        let mut library1 = LibraryManifest::new(
            Url::parse("lib://testlib1").expect("Could not parse lib url"),
            test_meta_data(),
        );
        library1
            .add_locator("/bin/fake.wasm", "/bin",
                         #[cfg(feature = "debugger")]
                             "/users/me/myproject/bin/fake.rs",
            )
            .expect("Could not add to manifest");

        let mut library2 = LibraryManifest::new(
            Url::parse("lib://testlib2").expect("Could not parse lib url"),
            test_meta_data(),
        );
        library2
            .add_locator("/bin/my.wasm", "/bin",
                         #[cfg(feature = "debugger")]
                             "/users/me/myproject/bin/my.rs",
            )
            .expect("Could not add to manifest");

        assert!(library1 != library2);
    }

    #[test]
    fn compare_manifests_same() {
        let mut library1 = LibraryManifest::new(
            Url::parse("lib://testlib1").expect("Could not parse lib url"),
            test_meta_data(),
        );
        library1
            .add_locator("/bin/my.wasm", "/bin",
                         #[cfg(feature = "debugger")]
                             "/users/me/myproject/bin/my.rs",
            )
            .expect("Could not add to manifest");

        let mut library2 = LibraryManifest::new(
            Url::parse("lib://testlib1").expect("Could not parse lib url"),
            test_meta_data(),
        );
        library2
            .add_locator("/bin/my.wasm", "/bin",
                         #[cfg(feature = "debugger")]
                             "/users/me/myproject/bin/my.rs",
            )
            .expect("Could not add to manifest");

        assert!(library1 == library2);
    }
}