daml_lf/
manifest.rs

1use crate::error::{DamlLfError, DamlLfResult};
2use itertools::Itertools;
3use std::convert::Into;
4use std::fmt::{Display, Formatter};
5use yaml_rust::YamlLoader;
6
7const MANIFEST_VERSION_KEY: &str = "Manifest-Version";
8const CREATED_BY_KEY: &str = "Created-By";
9const DALF_MAIN_KEY: &str = "Main-Dalf";
10const DALFS_KEY: &str = "Dalfs";
11const FORMAT_KEY: &str = "Format";
12const ENCRYPTION_KEY: &str = "Encryption";
13const VERSION_1_VALUE: &str = "1.0";
14const NON_ENCRYPTED_VALUE: &str = "non-encrypted";
15const DAML_LF_VALUE: &str = "daml-lf";
16
17/// The version of a dar file manifest.
18#[derive(Copy, Clone, Debug, Eq, PartialEq)]
19pub enum DarManifestVersion {
20    Unknown,
21    V1,
22}
23
24impl Display for DarManifestVersion {
25    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
26        match self {
27            DarManifestVersion::V1 | DarManifestVersion::Unknown => VERSION_1_VALUE.fmt(f),
28        }
29    }
30}
31
32/// The format of the archives in a dar file.
33#[derive(Copy, Clone, Debug, Eq, PartialEq)]
34pub enum DarManifestFormat {
35    Unknown,
36    DamlLf,
37}
38
39impl Display for DarManifestFormat {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        match self {
42            DarManifestFormat::DamlLf | DarManifestFormat::Unknown => DAML_LF_VALUE.fmt(f),
43        }
44    }
45}
46
47/// The encryption type of the archives in a dar file.
48#[derive(Copy, Clone, Debug, Eq, PartialEq)]
49pub enum DarEncryptionType {
50    Unknown,
51    NotEncrypted,
52}
53
54impl Display for DarEncryptionType {
55    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56        match self {
57            DarEncryptionType::NotEncrypted | DarEncryptionType::Unknown => NON_ENCRYPTED_VALUE.fmt(f),
58        }
59    }
60}
61
62/// Represents a manifest file found inside `dar` files.
63///
64/// A `dar` `manifest` file contains the following fields:
65///
66/// - `Manifest-Version`: the version of the manifest file (optional, defaults to [`Unknown`])
67/// - `Created-By`: describes what created the `dar` file containing this manifest file (optional, default to empty
68///   string)
69/// - `Main-Dalf`: the name of the `main` `dalf` file within the `dar` file (mandatory)
70/// - `Dalfs`: a comma separated list of `dalf` files within this `dar` file (mandatory)
71/// - `Format`: the format of the `dalf` files in this `dar` archive (mandatory)
72/// - `Encryption`: the encryption type of the `dalf` files in this `dar` archive (mandatory)
73///
74/// Note that the `main` `dalf` file MUST also be provided in the `Dalfs` attribute and so that attribute will never
75/// be empty.
76///
77/// [`Unknown`]: DarManifestVersion::Unknown
78#[derive(Debug, Clone)]
79pub struct DarManifest {
80    version: DarManifestVersion,
81    created_by: String,
82    dalf_main: String,
83    dalf_dependencies: Vec<String>,
84    format: DarManifestFormat,
85    encryption: DarEncryptionType,
86}
87
88impl DarManifest {
89    /// Crate a `DarManifest`.
90    pub fn new(
91        version: impl Into<DarManifestVersion>,
92        created_by: impl Into<String>,
93        dalf_main: impl Into<String>,
94        dalf_dependencies: Vec<String>,
95        format: impl Into<DarManifestFormat>,
96        encryption: impl Into<DarEncryptionType>,
97    ) -> Self {
98        Self {
99            version: version.into(),
100            created_by: created_by.into(),
101            dalf_main: dalf_main.into(),
102            dalf_dependencies,
103            format: format.into(),
104            encryption: encryption.into(),
105        }
106    }
107
108    /// Create a `DarManifest` from the supplied `main` and `dalf_dependencies` `dalf` files.
109    pub fn new_implied(dalf_main: impl Into<String>, dalf_dependencies: Vec<String>) -> Self {
110        Self::new(
111            DarManifestVersion::Unknown,
112            "implied",
113            dalf_main,
114            dalf_dependencies,
115            DarManifestFormat::Unknown,
116            DarEncryptionType::Unknown,
117        )
118    }
119
120    /// Create a `DarManifest` from the supplied `manifest` string.
121    ///
122    /// Note that all `dalf` names are stripped of all whitespace.
123    ///
124    /// # Errors
125    ///
126    /// If the provided `manifest` string cannot be parsed into newline `key: value` pairs then [`IoError`] will be
127    /// returned.
128    ///
129    /// If the parsed `manifest` has an invalid format (such as missing a mandatory key) then [`DarParseError`] will
130    /// be returned.
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// # use daml_lf::{DarManifestVersion, DarManifestFormat, DarEncryptionType, DarManifest};
136    /// # use daml_lf::DamlLfResult;
137    /// # fn main() -> DamlLfResult<()> {
138    /// let manifest_str = "
139    ///            Main-Dalf: A.dalf
140    ///            Dalfs: A.dalf
141    ///            Format: daml-lf
142    ///            Encryption: non-encrypted";
143    /// let manifest = DarManifest::parse(&manifest_str[..])?;
144    /// assert_eq!(DarManifestVersion::Unknown, manifest.version());
145    /// assert_eq!("", manifest.created_by());
146    /// assert_eq!("A.dalf", manifest.dalf_main());
147    /// assert_eq!(&Vec::<String>::new(), manifest.dalf_dependencies());
148    /// assert_eq!(DarManifestFormat::DamlLf, manifest.format());
149    /// assert_eq!(DarEncryptionType::NotEncrypted, manifest.encryption());
150    /// # Ok(())
151    /// # }
152    /// ```
153    /// [`IoError`]: DamlLfError::IoError
154    /// [`DarParseError`]: DamlLfError::DarParseError
155    pub fn parse(manifest: &str) -> DamlLfResult<Self> {
156        let docs = YamlLoader::load_from_str(manifest)?;
157        let doc = docs.first().ok_or_else(|| DamlLfError::new_dar_parse_error("unexpected manifest format"))?;
158
159        let manifest_version = match doc[MANIFEST_VERSION_KEY].as_f64() {
160            Some(s) if format!("{:.*}", 1, s) == VERSION_1_VALUE => Ok(DarManifestVersion::V1),
161            Some(s) => Err(DamlLfError::new_dar_parse_error(format!(
162                "unexpected value for {}, found {}",
163                MANIFEST_VERSION_KEY, s
164            ))),
165            None => Ok(DarManifestVersion::Unknown),
166        }?;
167
168        let created_by = doc[CREATED_BY_KEY].as_str().map_or_else(|| "", |s| s);
169
170        let dalf_main = doc[DALF_MAIN_KEY]
171            .as_str()
172            .map(strip_string)
173            .ok_or_else(|| DamlLfError::new_dar_parse_error(format!("key {} not found", DALF_MAIN_KEY)))?;
174
175        let dalf_dependencies = match doc[DALFS_KEY].as_str() {
176            Some(s) => Ok(s
177                .split(',')
178                .filter_map(|dalf: &str| {
179                    let stripped_dalf = strip_string(dalf);
180                    (stripped_dalf != dalf_main).then(|| stripped_dalf)
181                })
182                .collect()),
183            None => Err(DamlLfError::new_dar_parse_error(format!("key {} not found", DALFS_KEY))),
184        }?;
185
186        let format = match doc[FORMAT_KEY].as_str() {
187            Some(s) if s.to_lowercase() == DAML_LF_VALUE => Ok(DarManifestFormat::DamlLf),
188            Some(s) =>
189                Err(DamlLfError::new_dar_parse_error(format!("unexpected value for {}, found {}", DAML_LF_VALUE, s))),
190            None => Err(DamlLfError::new_dar_parse_error(format!("key {} not found", DAML_LF_VALUE))),
191        }?;
192
193        let encryption = match doc[ENCRYPTION_KEY].as_str() {
194            Some(s) if s.to_lowercase() == NON_ENCRYPTED_VALUE => Ok(DarEncryptionType::NotEncrypted),
195            Some(s) => Err(DamlLfError::new_dar_parse_error(format!(
196                "unexpected value for {}, found {}",
197                NON_ENCRYPTED_VALUE, s
198            ))),
199            None => Err(DamlLfError::new_dar_parse_error(format!("key {} not found", NON_ENCRYPTED_VALUE))),
200        }?;
201
202        Ok(Self::new(manifest_version, created_by, dalf_main, dalf_dependencies, format, encryption))
203    }
204
205    /// Render this `DarManifest`
206    pub fn render(&self) -> String {
207        vec![
208            make_manifest_entry(MANIFEST_VERSION_KEY, self.version().to_string()),
209            make_manifest_entry(CREATED_BY_KEY, self.created_by()),
210            make_manifest_entry(DALF_MAIN_KEY, self.dalf_main()),
211            make_manifest_entry(DALFS_KEY, self.dalf_dependencies().iter().join(", ")),
212            make_manifest_entry(FORMAT_KEY, self.format().to_string()),
213            make_manifest_entry(ENCRYPTION_KEY, self.encryption().to_string()),
214        ]
215        .join("\n")
216    }
217
218    /// The version of the manifest.
219    pub const fn version(&self) -> DarManifestVersion {
220        self.version
221    }
222
223    /// Describes who created the `dar` file which contains this manifest file.
224    pub fn created_by(&self) -> &str {
225        &self.created_by
226    }
227
228    /// The name of the `main` `dalf` archive within the `dar` file containing this manifest file.
229    pub fn dalf_main(&self) -> &str {
230        &self.dalf_main
231    }
232
233    /// A list of names of the `dalf_dependencies` `dalf` archives within the `dar` file containing this manifest file.
234    pub const fn dalf_dependencies(&self) -> &Vec<String> {
235        &self.dalf_dependencies
236    }
237
238    /// The format of the `dar` which contains this manifest file.
239    pub const fn format(&self) -> DarManifestFormat {
240        self.format
241    }
242
243    /// The encryption type of the `dar` which contains this manifest file.
244    pub const fn encryption(&self) -> DarEncryptionType {
245        self.encryption
246    }
247}
248
249fn strip_string(s: impl AsRef<str>) -> String {
250    s.as_ref().chars().filter(|&c| !char::is_whitespace(c)).collect()
251}
252
253fn make_manifest_entry(key: impl AsRef<str>, value: impl AsRef<str>) -> String {
254    split_manifest_string(format!("{}: {}", key.as_ref(), value.as_ref()))
255}
256
257// see https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JAR_Manifest
258// TODO split Jar manifest handling to a utility module or crate
259fn split_manifest_string(s: impl AsRef<str>) -> String {
260    let split_lines: Vec<String> =
261        s.as_ref().as_bytes().chunks(71).map(String::from_utf8_lossy).map(String::from).collect();
262    match split_lines.as_slice() {
263        [] => "".to_owned(),
264        [head] => head.clone(),
265        [head, tail @ ..] => {
266            let new_tail: String = tail.iter().map(|s| format!(" {}", s)).join("\n");
267            format!("{}\n{}", head, new_tail)
268        },
269    }
270}
271
272#[cfg(test)]
273mod test {
274    use crate::error::{DamlLfError, DamlLfResult};
275    use crate::manifest::{
276        split_manifest_string, DarEncryptionType, DarManifest, DarManifestFormat, DarManifestVersion,
277    };
278    use trim_margin::MarginTrimmable;
279
280    #[test]
281    fn test_split_manifest_line() {
282        let long = "Main-Dalf: \
283                    TestingTypes-1.0.0-6c314cb04bcb26cb62aa6ebf0f8ed4bdc3cbf709847be908c9920df5574daacc/\
284                    TestingTypes-1.0.0-6c314cb04bcb26cb62aa6ebf0f8ed4bdc3cbf709847be908c9920df5574daacc.dalf";
285        let expected = "
286            |Main-Dalf: TestingTypes-1.0.0-6c314cb04bcb26cb62aa6ebf0f8ed4bdc3cbf7098
287            | 47be908c9920df5574daacc/TestingTypes-1.0.0-6c314cb04bcb26cb62aa6ebf0f8e
288            | d4bdc3cbf709847be908c9920df5574daacc.dalf"
289            .trim_margin()
290            .expect("invalid test string");
291        let split = split_manifest_string(long);
292        assert_eq!(split, expected);
293    }
294
295    #[test]
296    pub fn test_split_dalfs() -> DamlLfResult<()> {
297        let manifest_str = "
298            |Manifest-Version: 1.0
299            |Created-By: damlc
300            |Main-Dalf: com.daml.lf.archive:DarReaderTest:0.1.dalf
301            |Dalfs: com.daml.lf.archive:DarReaderTest:0.1.dalf, daml-pri
302            | m.dalf
303            |Format: daml-lf
304            |Encryption: non-encrypted"
305            .trim_margin()
306            .expect("invalid test string");
307        let manifest = DarManifest::parse(&manifest_str[..])?;
308        assert_eq!(DarManifestVersion::V1, manifest.version());
309        assert_eq!("damlc", manifest.created_by());
310        assert_eq!("com.daml.lf.archive:DarReaderTest:0.1.dalf", manifest.dalf_main());
311        assert_eq!(&vec!["daml-prim.dalf"], manifest.dalf_dependencies());
312        assert_eq!(DarManifestFormat::DamlLf, manifest.format());
313        assert_eq!(DarEncryptionType::NotEncrypted, manifest.encryption());
314        Ok(())
315    }
316
317    #[test]
318    pub fn test_split_all_dalf() -> DamlLfResult<()> {
319        let manifest_str = "
320            |Manifest-Version: 1.0
321            |Created-By: damlc
322            |Sdk-Version: 0.13.16
323            |Main-Dalf: test-0.0.1-7390c3f7a0f5c4aed2cf8da2dc757885ac20ab8f2eb616a1ed
324            | b7cf57f8161d3a/test.dalf
325            |Dalfs: test-0.0.1-7390c3f7a0f5c4aed2cf8da2dc757885ac20ab8f2eb616a1edb7cf
326            | 57f8161d3a/test.dalf, test-0.0.1-7390c3f7a0f5c4aed2cf8da2dc757885ac20ab
327            | 8f2eb616a1edb7cf57f8161d3a/daml-prim.dalf, test-0.0.1-7390c3f7a0f5c4aed
328            | 2cf8da2dc757885ac20ab8f2eb616a1edb7cf57f8161d3a/daml-stdlib.dalf
329            |Format: daml-lf
330            |Encryption: non-encrypted"
331            .trim_margin()
332            .expect("invalid test string");
333        let manifest = DarManifest::parse(&manifest_str[..])?;
334        assert_eq!(DarManifestVersion::V1, manifest.version());
335        assert_eq!("damlc", manifest.created_by());
336        assert_eq!(
337            "test-0.0.1-7390c3f7a0f5c4aed2cf8da2dc757885ac20ab8f2eb616a1edb7cf57f8161d3a/test.dalf",
338            manifest.dalf_main()
339        );
340        assert_eq!(
341            &vec![
342                "test-0.0.1-7390c3f7a0f5c4aed2cf8da2dc757885ac20ab8f2eb616a1edb7cf57f8161d3a/daml-prim.dalf",
343                "test-0.0.1-7390c3f7a0f5c4aed2cf8da2dc757885ac20ab8f2eb616a1edb7cf57f8161d3a/daml-stdlib.dalf"
344            ],
345            manifest.dalf_dependencies()
346        );
347        assert_eq!(DarManifestFormat::DamlLf, manifest.format());
348        assert_eq!(DarEncryptionType::NotEncrypted, manifest.encryption());
349        Ok(())
350    }
351
352    #[test]
353    pub fn test_multiple_dalfs() -> DamlLfResult<()> {
354        let manifest_str = "
355            |Main-Dalf: A.dalf
356            |Dalfs: B.dalf, C.dalf, A.dalf, E.dalf
357            |Format: daml-lf
358            |Encryption: non-encrypted"
359            .trim_margin()
360            .expect("invalid test string");
361        let manifest = DarManifest::parse(&manifest_str[..])?;
362        assert_eq!(DarManifestVersion::Unknown, manifest.version());
363        assert_eq!("", manifest.created_by());
364        assert_eq!("A.dalf", manifest.dalf_main());
365        assert_eq!(&vec!["B.dalf", "C.dalf", "E.dalf"], manifest.dalf_dependencies());
366        assert_eq!(DarManifestFormat::DamlLf, manifest.format());
367        assert_eq!(DarEncryptionType::NotEncrypted, manifest.encryption());
368        Ok(())
369    }
370
371    #[test]
372    pub fn test_single_main_dalf() -> DamlLfResult<()> {
373        let manifest_str = "
374            |Main-Dalf: A.dalf
375            |Dalfs: A.dalf
376            |Format: daml-lf
377            |Encryption: non-encrypted"
378            .trim_margin()
379            .expect("invalid test string");
380        let manifest = DarManifest::parse(&manifest_str[..])?;
381        assert_eq!(DarManifestVersion::Unknown, manifest.version());
382        assert_eq!("", manifest.created_by());
383        assert_eq!("A.dalf", manifest.dalf_main());
384        assert_eq!(&Vec::<String>::new(), manifest.dalf_dependencies());
385        assert_eq!(DarManifestFormat::DamlLf, manifest.format());
386        assert_eq!(DarEncryptionType::NotEncrypted, manifest.encryption());
387        Ok(())
388    }
389
390    #[test]
391    pub fn test_invalid_format() {
392        let manifest_str = "
393            |Main-Dalf: A.dalf
394            |Dalfs: B.dalf, C.dalf, A.dalf, E.dalf
395            |Format: anything-different-from-daml-lf
396            |Encryption: non-encrypted"
397            .trim_margin()
398            .expect("invalid test string");
399        let manifest = DarManifest::parse(&manifest_str[..]);
400        match manifest.err().expect("expected failure") {
401            DamlLfError::DarParseError(s) =>
402                assert_eq!("unexpected value for daml-lf, found anything-different-from-daml-lf", s),
403            _ => panic!("expected failure"),
404        }
405    }
406}