use std::collections::{HashMap, HashSet, VecDeque};
use daml_grpc::DamlGrpcClient;
use daml_grpc::data::package::DamlPackage;
use daml_grpc::data::{DamlError, DamlResult};
use daml_lf::element::{
DamlAbsoluteTyCon, DamlElementVisitor, DamlNonLocalTyCon, DamlNonLocalValueName, DamlPackage as DamlLfPackage,
DamlVisitableElement,
};
use daml_lf::{DamlLfArchive, DamlLfArchivePayload, DamlLfHashFunction, DarFile, DarManifest};
use futures::StreamExt;
use futures::stream::FuturesUnordered;
use uuid::Uuid;
#[derive(Debug)]
pub struct DamlPackages {
packages: Vec<DamlPackage>,
}
impl DamlPackages {
pub fn new(packages: Vec<DamlPackage>) -> Self {
Self {
packages,
}
}
pub async fn from_ledger(ledger_client: &DamlGrpcClient) -> DamlResult<Self> {
let packages = ledger_client.package_service().list_packages().await?;
let handles = packages
.iter()
.map(|pd| async move { ledger_client.package_service().get_package(pd).await })
.collect::<FuturesUnordered<_>>();
let all_packages =
handles.collect::<Vec<DamlResult<_>>>().await.into_iter().collect::<DamlResult<Vec<DamlPackage>>>()?;
Ok(Self::new(all_packages))
}
pub fn find_module(self, module_name: &str) -> DamlResult<String> {
self.into_payloads()?
.iter()
.find(|(_, payload)| payload.contains_module(module_name))
.map_or_else(|| Err("package could not be found".into()), |(package_id, _)| Ok((*package_id).clone()))
}
pub fn into_dar(
self,
main_package_id: Option<&str>,
filter_deps: bool,
auto_naming_style: ArchiveAutoNamingStyle,
) -> DamlResult<DarFile> {
let main_id = self.resolve_main_id(main_package_id)?;
let keep = if filter_deps {
let payloads: HashMap<String, DamlLfArchivePayload> = self.payloads_by_id()?;
let reachable = Self::reachable_from(&main_id, &payloads)?;
self.packages.into_iter().filter(|p| reachable.contains(p.hash())).collect()
} else {
self.packages
};
let all_archives = Self::packages_to_archives(keep, auto_naming_style)?;
Self::archives_to_dar(all_archives, &main_id)
}
pub fn into_archives(self, auto_naming_style: ArchiveAutoNamingStyle) -> DamlResult<Vec<DamlLfArchive>> {
Self::packages_to_archives(self.packages, auto_naming_style)
}
fn packages_to_archives(
packages: Vec<DamlPackage>,
auto_naming_style: ArchiveAutoNamingStyle,
) -> DamlResult<Vec<DamlLfArchive>> {
packages
.into_iter()
.map(|p| {
let hash = p.hash().to_owned();
let payload = Self::package_into_payload(p)?;
let name = match auto_naming_style {
ArchiveAutoNamingStyle::Empty => String::default(),
ArchiveAutoNamingStyle::Hash => hash.clone(),
ArchiveAutoNamingStyle::Uuid => Uuid::new_v4().to_string(),
};
Ok(DamlLfArchive::new(name, payload, DamlLfHashFunction::Sha256, hash))
})
.collect()
}
pub fn into_payloads(self) -> DamlResult<Vec<(String, DamlLfArchivePayload)>> {
self.packages
.into_iter()
.map(|p| {
let hash = p.hash().to_owned();
Self::package_into_payload(p).map(|pl| (hash, pl))
})
.collect::<DamlResult<Vec<_>>>()
}
fn package_into_payload(package: DamlPackage) -> DamlResult<DamlLfArchivePayload> {
DamlLfArchivePayload::from_bytes(package.take_payload()).map_err(|e| DamlError::Other(e.to_string()))
}
fn archives_to_dar(mut all_packages: Vec<DamlLfArchive>, main_id: &str) -> DamlResult<DarFile> {
if all_packages.is_empty() {
return Err("expected at least one archive".into());
}
let main_idx = all_packages
.iter()
.position(|a| a.hash == main_id)
.ok_or_else(|| DamlError::Other(format!("main package {main_id} not present in archive set")))?;
let first = all_packages.swap_remove(main_idx);
let rest = all_packages;
let manifest = DarManifest::new_implied(first.name.clone(), rest.iter().map(|n| n.name.clone()).collect());
Ok(DarFile::new(manifest, first, rest))
}
fn resolve_main_id(&self, hint: Option<&str>) -> DamlResult<String> {
match hint {
Some(id) => {
if self.packages.iter().any(|p| p.hash() == id) {
Ok(id.to_owned())
} else {
Err(DamlError::Other(format!("main package {id} not present in package set")))
}
},
None => self
.packages
.first()
.map(|p| p.hash().to_owned())
.ok_or_else(|| DamlError::Other("expected at least one package".to_owned())),
}
}
fn payloads_by_id(&self) -> DamlResult<HashMap<String, DamlLfArchivePayload>> {
self.packages
.iter()
.map(|p| {
let hash = p.hash().to_owned();
let payload = DamlLfArchivePayload::from_bytes(p.payload().to_vec())
.map_err(|e| DamlError::Other(e.to_string()))?;
Ok((hash, payload))
})
.collect()
}
fn reachable_from(root: &str, payloads: &HashMap<String, DamlLfArchivePayload>) -> DamlResult<HashSet<String>> {
let mut visited: HashSet<String> = HashSet::new();
let mut queue: VecDeque<String> = VecDeque::new();
queue.push_back(root.to_owned());
while let Some(id) = queue.pop_front() {
if !visited.insert(id.clone()) {
continue;
}
let Some(payload) = payloads.get(&id) else {
continue;
};
let refs = collect_referenced_package_ids(payload).map_err(|e| DamlError::Other(e.to_string()))?;
for r in refs {
if !visited.contains(&r) {
queue.push_back(r);
}
}
}
Ok(visited)
}
}
fn collect_referenced_package_ids(payload: &DamlLfArchivePayload) -> daml_lf::DamlLfResult<HashSet<String>> {
payload.clone().apply(|package: &DamlLfPackage<'_>| {
let mut visitor = ReferencedPackagesVisitor {
self_package_id: package.package_id().to_owned(),
referenced: HashSet::new(),
};
package.accept(&mut visitor);
visitor.referenced
})
}
struct ReferencedPackagesVisitor {
self_package_id: String,
referenced: HashSet<String>,
}
impl ReferencedPackagesVisitor {
fn record(&mut self, pkg: &str) {
if !pkg.is_empty() && pkg != self.self_package_id {
self.referenced.insert(pkg.to_owned());
}
}
}
impl DamlElementVisitor for ReferencedPackagesVisitor {
fn pre_visit_absolute_tycon<'a>(&mut self, abs: &'a DamlAbsoluteTyCon<'a>) {
self.record(abs.package_id());
}
fn pre_visit_non_local_tycon<'a>(&mut self, non_local: &'a DamlNonLocalTyCon<'a>) {
self.record(non_local.target_package_id());
}
fn pre_visit_non_local_value_name<'a>(&mut self, non_local: &'a DamlNonLocalValueName<'a>) {
self.record(non_local.target_package_id());
}
}
#[derive(Clone, Copy, Debug)]
pub enum ArchiveAutoNamingStyle {
Empty,
Hash,
Uuid,
}
pub async fn find_module_package_id(ledger_client: &DamlGrpcClient, module_name: &str) -> DamlResult<String> {
DamlPackages::from_ledger(ledger_client).await?.find_module(module_name)
}