daml_lf/
dar.rs

1use crate::archive::DamlLfArchive;
2use crate::convert;
3use crate::element::DamlArchive;
4use crate::error::{DamlLfError, DamlLfResult};
5use crate::manifest::DarManifest;
6use crate::DEFAULT_ARCHIVE_NAME;
7use std::ffi::OsStr;
8use std::fs::File;
9use std::io::Read;
10use std::path::{Path, PathBuf};
11use zip::ZipArchive;
12
13const MANIFEST_FILE_PATH: &str = "META-INF/MANIFEST.MF";
14const DALF_FILE_EXTENSION: &str = "dalf";
15const DALF_PRIM_FILE_SUFFIX: &str = "-prim";
16
17/// A collection of `Daml LF` archives combined with a manifest file (aka a `dar` file).
18///
19/// A `DarFile` contains a `main` [`DamlLfArchive`] and collection of `dependencies` [`DamlLfArchive`] combined with
20/// a [`DarManifest`].
21#[derive(Debug, Clone)]
22pub struct DarFile {
23    pub manifest: DarManifest,
24    pub main: DamlLfArchive,
25    pub dependencies: Vec<DamlLfArchive>,
26}
27
28impl DarFile {
29    /// Create a new `DarFile` from an existing `manifest` file, main and `dependencies` [`DamlLfArchive`].
30    ///
31    /// Note that this method does not validate that the supplied `manifest` correctly reflects the `main` and
32    /// `dependencies` [`DamlLfArchive`] provided and so may yield an invalid `DarFile`.
33    pub fn new(
34        manifest: impl Into<DarManifest>,
35        main: impl Into<DamlLfArchive>,
36        dependencies: impl Into<Vec<DamlLfArchive>>,
37    ) -> Self {
38        Self {
39            manifest: manifest.into(),
40            main: main.into(),
41            dependencies: dependencies.into(),
42        }
43    }
44
45    /// Create a `DarFile` from the supplied `dar` file.
46    ///
47    /// There are currently two supported `dar` formats supported by this module, `legacy` and `fat`.  Parsing will
48    /// first attempt to parse a `fat` `dar`.  If parsing fails an attempt will be made to parse a `legacy` `dar`
49    /// instead.
50    ///
51    /// # Dar Format
52    ///
53    /// Both formats are compressed zip archives with a `dar` extension which contain a `META-INF/MANIFEST.MF` file and
54    /// one or more `dalf` files, potentially nested in a sub folders.
55    ///
56    /// If `dar` file provided does not contain a manifest or if the manifest does not contain all mandatory fields then
57    /// parsing will fail.
58    ///
59    /// The manifest file of `legacy` `dar` files will not be read, instead it will be inferred from the set of `dalf`
60    /// files within the file.  The following combinations are considered valid for `legacy` `dar` files:
61    ///
62    /// - A `dar` file containing only a single non-prim `dalf` file (anywhere)
63    /// - A `dar` file containing a single non-prim `dalf` file and a single prim `dalf` file (ending with the `-prim`
64    /// suffix)
65    /// - A `dar` file containing only a single prim (ending with the `-prim` suffix) file
66    ///
67    /// # Errors
68    ///
69    /// If the file cannot be read then an [`IoError`] will be returned.
70    ///
71    /// If the file cannot be interpreted as a `zip` archive then a [`DarParseError`] will be returned.
72    ///
73    /// Should both `fat` and `legacy` parsing attempts fail then a [`DarParseError`] will be returned.
74    ///
75    /// # Examples
76    ///
77    /// ```no_run
78    /// # use daml_lf::DarFile;
79    /// # use daml_lf::DamlLfResult;
80    /// # use daml_lf::DamlLfHashFunction;
81    /// # fn main() -> DamlLfResult<()> {
82    /// let dar = DarFile::from_file("Example.dar")?;
83    /// assert_eq!(&DamlLfHashFunction::Sha256, dar.main().hash_function());
84    /// # Ok(())
85    /// # }
86    /// ```
87    /// [`IoError`]: DamlLfError::IoError
88    /// [`DarParseError`]: DamlLfError::DarParseError
89    pub fn from_file(path: impl AsRef<Path>) -> DamlLfResult<Self> {
90        let dar_file = std::fs::File::open(path)?;
91        let mut zip_archive = zip::ZipArchive::new(dar_file)?;
92        let manifest = match Self::parse_dar_manifest_from_file(&mut zip_archive) {
93            Ok(manifest) => Ok(manifest),
94            Err(_) => Self::make_manifest_from_archive(&mut zip_archive),
95        }?;
96        let dalf_main = Self::parse_dalf_from_archive(&mut zip_archive, manifest.dalf_main())?;
97        let dalf_dependencies = Self::parse_dalfs_from_archive(&mut zip_archive, manifest.dalf_dependencies())?;
98        Ok(Self::new(manifest, dalf_main, dalf_dependencies))
99    }
100
101    /// Create a [`DamlArchive`] from this [`DarFile`] and apply it to `f`.
102    ///
103    /// The created [`DamlArchive`] borrows all interned string data from this [`DarFile`] and is therefore tied to the
104    /// lifetime of the [`DarFile`] and so cannot be returned from this scope.  The [`DamlArchive`] can be accessed from
105    /// the supplied closure `f` which may return owned data.
106    ///
107    /// Use [`DarFile::to_owned_archive`] to create a [`DamlArchive`] which does not borrow any data from the generating
108    /// [`DarFile`].
109    ///
110    /// # Examples
111    ///
112    /// ```no_run
113    /// # use daml_lf::DarFile;
114    /// # use daml_lf::DamlLfResult;
115    /// # fn main() -> DamlLfResult<()> {
116    /// let dar = DarFile::from_file("Example.dar")?;
117    /// // create a DamlArchive from this DarFile and extract the (owned) name.
118    /// let name = dar.apply(|archive| archive.name().to_owned())?;
119    /// assert_eq!("Example-1.0.0", name);
120    /// Ok(())
121    /// # }
122    /// ```
123    pub fn apply<R, F>(&self, f: F) -> DamlLfResult<R>
124    where
125        F: FnOnce(&DamlArchive<'_>) -> R,
126    {
127        convert::apply_dar(self, f)
128    }
129
130    /// Create an owned [`DamlArchive`] from this [`DarFile`].
131    ///
132    /// This is an expensive operation as it involves both a conversion of the [`DarFile`] to a [`DamlArchive`] (which
133    /// borrows all interned strings) and a subsequent conversion to an owned [`DamlArchive`] which clones all interned
134    /// strings.
135    ///
136    /// Use this when an owned instance of a [`DamlArchive`] is required, such as for passing to a thread.  For other
137    /// cases consider using the [`DarFile::apply`] method which does not require the second conversion.
138    ///
139    /// # Examples
140    ///
141    /// ```no_run
142    /// # use daml_lf::DarFile;
143    /// # use daml_lf::DamlLfResult;
144    /// # fn main() -> DamlLfResult<()> {
145    /// let dar = DarFile::from_file("Example.dar")?;
146    /// let archive = dar.to_owned_archive()?;
147    /// assert_eq!("TestingTypes-1.0.0", archive.name());
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub fn to_owned_archive(&self) -> DamlLfResult<DamlArchive<'static>> {
152        convert::to_owned_archive(self)
153    }
154
155    /// The `manifest` information contained within this `DarFile`.
156    pub const fn manifest(&self) -> &DarManifest {
157        &self.manifest
158    }
159
160    /// The `main` [`DamlLfArchive`] contained within this `DarFile`.
161    pub const fn main(&self) -> &DamlLfArchive {
162        &self.main
163    }
164
165    /// A collection of `dependencies` [`DamlLfArchive`] contained within this `DarFile`.
166    pub const fn dependencies(&self) -> &Vec<DamlLfArchive> {
167        &self.dependencies
168    }
169
170    fn is_dalf(path: &Path) -> bool {
171        path.extension().and_then(OsStr::to_str).map(str::to_lowercase).map_or(false, |q| q == DALF_FILE_EXTENSION)
172    }
173
174    fn is_prim_dalf(path: &Path) -> bool {
175        path.file_stem()
176            .and_then(OsStr::to_str)
177            .map(str::to_lowercase)
178            .map_or(false, |p| p.ends_with(DALF_PRIM_FILE_SUFFIX))
179    }
180
181    fn make_manifest_from_archive(zip_archive: &mut ZipArchive<File>) -> DamlLfResult<DarManifest> {
182        let dalf_paths = zip_archive.paths();
183        let (prim, main): (Vec<PathBuf>, Vec<PathBuf>) =
184            dalf_paths.into_iter().filter(|d| Self::is_dalf(d)).partition(|d| Self::is_prim_dalf(d));
185        let (dalf_main_path, dalf_dependencies_paths) = match (prim.as_slice(), main.as_slice()) {
186            ([p], [m]) => Ok((p, vec![m])),
187            ([p], []) => Ok((p, vec![])),
188            ([], [m]) => Ok((m, vec![])),
189            _ => Err(DamlLfError::new_dar_parse_error("invalid legacy Dar")),
190        }?;
191
192        let manifest = DarManifest::new_implied(
193            dalf_main_path.display().to_string(),
194            dalf_dependencies_paths.into_iter().map(|d| d.display().to_string()).collect(),
195        );
196        Ok(manifest)
197    }
198
199    fn parse_dalfs_from_archive(
200        zip_archive: &mut ZipArchive<File>,
201        paths: &[String],
202    ) -> DamlLfResult<Vec<DamlLfArchive>> {
203        paths
204            .iter()
205            .map(|dalf_path| Self::parse_dalf_from_archive(zip_archive, dalf_path))
206            .collect::<DamlLfResult<Vec<DamlLfArchive>>>()
207    }
208
209    #[allow(clippy::cast_possible_truncation)]
210    fn parse_dalf_from_archive(zip_archive: &mut ZipArchive<File>, location: &str) -> DamlLfResult<DamlLfArchive> {
211        let mut file = zip_archive.by_name(location)?;
212        let mut buf = Vec::with_capacity(file.size() as usize);
213        file.read_to_end(&mut buf)?;
214        let archive_name_buffer = PathBuf::from(location);
215        let archive_name_stem = archive_name_buffer.file_stem().and_then(OsStr::to_str).unwrap_or(DEFAULT_ARCHIVE_NAME);
216        DamlLfArchive::from_bytes_named(archive_name_stem, buf)
217    }
218
219    fn parse_dar_manifest_from_file(zip_archive: &mut ZipArchive<File>) -> DamlLfResult<DarManifest> {
220        let mut file = zip_archive.by_name(MANIFEST_FILE_PATH)?;
221        let mut contents = String::new();
222        file.read_to_string(&mut contents)?;
223        DarManifest::parse(&contents)
224    }
225}
226
227trait ZipArchiveEx<T> {
228    fn paths(&mut self) -> Vec<PathBuf>;
229    fn contains(&mut self, path: &str) -> bool;
230}
231
232impl ZipArchiveEx<File> for ZipArchive<File> {
233    fn paths(&mut self) -> Vec<PathBuf> {
234        let mut paths = Vec::with_capacity(self.len());
235        for i in 0..self.len() {
236            if let Ok(file) = self.by_index(i) {
237                paths.push(PathBuf::from(file.name()));
238            }
239        }
240        paths
241    }
242
243    fn contains(&mut self, path: &str) -> bool {
244        self.by_name(path).is_ok()
245    }
246}