daml_util/
package.rs

1use daml_grpc::data::package::DamlPackage;
2use daml_grpc::data::{DamlError, DamlResult};
3use daml_grpc::DamlGrpcClient;
4use daml_lf::{DamlLfArchive, DamlLfArchivePayload, DamlLfHashFunction, DarFile, DarManifest};
5use futures::stream::FuturesUnordered;
6use futures::StreamExt;
7use uuid::Uuid;
8
9/// Convenience methods for working with a collection of [`DamlPackage`].
10///
11/// In the following example a [`DamlPackages`] is created from all known [`DamlPackage`] on a Daml ledger and then
12/// converted into [`DarFile`] using the [`ArchiveAutoNamingStyle::Uuid`] naming style:
13///
14/// ```no_run
15/// # use daml_lf::DarFile;
16/// # use daml_grpc::DamlGrpcClientBuilder;
17/// # use std::thread;
18/// # use daml_util::package::{DamlPackages, ArchiveAutoNamingStyle};
19/// # fn main() {
20/// # futures::executor::block_on(async {
21/// let ledger_client = DamlGrpcClientBuilder::uri("http://127.0.0.1").connect().await.unwrap();
22/// let packages = DamlPackages::from_ledger(&ledger_client).await.unwrap();
23/// let dar = packages.into_dar(ArchiveAutoNamingStyle::Uuid).unwrap();
24/// # })
25/// # }
26/// ```
27#[derive(Debug)]
28pub struct DamlPackages {
29    packages: Vec<DamlPackage>,
30}
31
32impl DamlPackages {
33    pub fn new(packages: Vec<DamlPackage>) -> Self {
34        Self {
35            packages,
36        }
37    }
38
39    /// Create a [`DamlPackages`] from all known [`DamlPackage`] on a Daml ledger.
40    pub async fn from_ledger(ledger_client: &DamlGrpcClient) -> DamlResult<Self> {
41        let packages = ledger_client.package_service().list_packages().await?;
42        let handles = packages
43            .iter()
44            .map(|pd| async move { ledger_client.package_service().get_package(pd).await })
45            .collect::<FuturesUnordered<_>>();
46        let all_packages =
47            handles.collect::<Vec<DamlResult<_>>>().await.into_iter().collect::<DamlResult<Vec<DamlPackage>>>()?;
48        Ok(Self::new(all_packages))
49    }
50
51    /// Return the hash of the [`DamlPackage`] which contains a given module or en error if no such package exists.
52    ///
53    /// The supplied `module_name` name is assumed to be in `DottedName` format, i.e. `TopModule.SubModule.Module`.
54    pub async fn find_module(self, module_name: &str) -> DamlResult<String> {
55        self.into_payloads()?
56            .iter()
57            .find(|(_, payload)| payload.contains_module(module_name))
58            .map_or_else(|| Err("package could not be found".into()), |(package_id, _)| Ok((*package_id).to_string()))
59    }
60
61    /// Package all contained [`DamlPackage`] into a single [`DarFile`].
62    ///
63    /// Note that an arbitrary package is selected as the main and the remainder are the dependencies.  No attempt is
64    /// made to ensure that the dependencies do in fact depend on the main.
65    // TODO allow specifying the package id to use as the main package
66    // TODO allow filtering packages such that only actual dependencies of main are included
67    pub fn into_dar(self, auto_naming_style: ArchiveAutoNamingStyle) -> DamlResult<DarFile> {
68        let all_archives = self.into_archives(auto_naming_style)?;
69        Self::archives_to_dar(all_archives)
70    }
71
72    /// Convert all contained [`DamlPackage`] into [`DamlLfArchive`].
73    ///
74    /// Note that the created archive is not named.
75    pub fn into_archives(self, auto_naming_style: ArchiveAutoNamingStyle) -> DamlResult<Vec<DamlLfArchive>> {
76        self.packages
77            .into_iter()
78            .map(|p| {
79                let hash = p.hash().to_owned();
80                let payload = Self::package_into_payload(p)?;
81                let name = match auto_naming_style {
82                    ArchiveAutoNamingStyle::Empty => String::default(),
83                    ArchiveAutoNamingStyle::Hash => hash.clone(),
84                    ArchiveAutoNamingStyle::Uuid => Uuid::new_v4().to_string(),
85                };
86                Ok(DamlLfArchive::new(name, payload, DamlLfHashFunction::Sha256, hash))
87            })
88            .collect()
89    }
90
91    /// Convert all contained [`DamlPackage`] into [`DamlLfArchivePayload`].
92    pub fn into_payloads(self) -> DamlResult<Vec<(String, DamlLfArchivePayload)>> {
93        self.packages
94            .into_iter()
95            .map(|p| {
96                let hash = p.hash().to_owned();
97                Self::package_into_payload(p).map(|pl| (hash, pl))
98            })
99            .collect::<DamlResult<Vec<_>>>()
100    }
101
102    fn package_into_payload(package: DamlPackage) -> DamlResult<DamlLfArchivePayload> {
103        DamlLfArchivePayload::from_bytes(package.take_payload()).map_err(|e| DamlError::Other(e.to_string()))
104    }
105
106    fn archives_to_dar(mut all_packages: Vec<DamlLfArchive>) -> DamlResult<DarFile> {
107        if all_packages.is_empty() {
108            Err("expected at least one archive".into())
109        } else {
110            let (first, rest) = all_packages.try_swap_remove(0).map(|removed| (removed, all_packages)).unwrap();
111            let manifest = DarManifest::new_implied(first.name.clone(), rest.iter().map(|n| n.name.clone()).collect());
112            Ok(DarFile::new(manifest, first, rest))
113        }
114    }
115}
116
117/// The automatic naming style to use when creating a `DamlLfArchive` from an unnamed `DamlPackage`.
118#[derive(Clone, Copy, Debug)]
119pub enum ArchiveAutoNamingStyle {
120    /// Name the `DamlLfArchive` with an empty String.
121    Empty,
122    /// Name the `DamlLfArchive` with the archive hash.
123    Hash,
124    /// Name the `DamlLfArchive` with a `uuid`.
125    Uuid,
126}
127
128/// Return the id of a package which contains a given module name or en error if no such package exists.
129///
130/// The supplied `module_name` name is assumed to be in `DottedName` format, i.e. `TopModule.SubModule.Module`.
131pub async fn find_module_package_id(ledger_client: &DamlGrpcClient, module_name: &str) -> DamlResult<String> {
132    DamlPackages::from_ledger(ledger_client).await?.find_module(module_name).await
133}
134
135trait TrySwapRemove<T>: Sized {
136    fn try_swap_remove(&mut self, index: usize) -> Option<T>;
137}
138
139impl<T> TrySwapRemove<T> for Vec<T> {
140    fn try_swap_remove(&mut self, index: usize) -> Option<T> {
141        (index < self.len()).then(|| self.swap_remove(index))
142    }
143}