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}