fix: Dependency vs dependents (#1462)

* fix: Dependency vs dependents

* chore: Remove the debugging
This commit is contained in:
J M
2022-05-26 15:39:46 -06:00
committed by GitHub
parent 7f2494a26b
commit 4829637b46
7 changed files with 191 additions and 116 deletions

View File

@@ -14,6 +14,7 @@ use serde_json::Value;
use tracing::instrument;
use crate::context::RpcContext;
use crate::db::model::{CurrentDependencies, CurrentDependents};
use crate::dependencies::{
add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive,
BreakageRes, DependencyConfig, DependencyError, DependencyErrors, TaggedDependencyError,
@@ -276,7 +277,8 @@ pub struct ConfigReceipts {
version: LockReceipt<crate::util::Version, String>,
manifest: LockReceipt<Manifest, String>,
system_pointers: LockReceipt<Vec<spec::SystemPointerSpec>, String>,
pub current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
pub current_dependents: LockReceipt<CurrentDependents, String>,
pub current_dependencies: LockReceipt<CurrentDependencies, String>,
dependency_errors: LockReceipt<DependencyErrors, String>,
manifest_dependencies_config: LockReceipt<DependencyConfig, (String, String)>,
}
@@ -360,6 +362,14 @@ impl ConfigReceipts {
.make_locker(LockType::Write)
.add_to_keys(locks);
let current_dependencies = crate::db::DatabaseModel::new()
.package_data()
.star()
.installed()
.map(|x| x.current_dependencies())
.make_locker(LockType::Write)
.add_to_keys(locks);
let dependency_errors = crate::db::DatabaseModel::new()
.package_data()
.star()
@@ -391,6 +401,7 @@ impl ConfigReceipts {
manifest: manifest.verify(skeleton_key)?,
system_pointers: system_pointers.verify(skeleton_key)?,
current_dependents: current_dependents.verify(skeleton_key)?,
current_dependencies: current_dependencies.verify(skeleton_key)?,
dependency_errors: dependency_errors.verify(skeleton_key)?,
manifest_dependencies_config: manifest_dependencies_config.verify(skeleton_key)?,
})
@@ -544,26 +555,28 @@ pub fn configure_rec<'a, Db: DbHandle>(
.await?
.ok_or_else(not_found)?;
sys.truncate(0);
let mut current_dependencies: BTreeMap<PackageId, CurrentDependencyInfo> = dependencies
.0
.iter()
.filter_map(|(id, info)| {
if info.requirement.required() {
Some((id.clone(), CurrentDependencyInfo::default()))
} else {
None
}
})
.collect();
let mut current_dependencies: CurrentDependencies = CurrentDependencies(
dependencies
.0
.iter()
.filter_map(|(id, info)| {
if info.requirement.required() {
Some((id.clone(), CurrentDependencyInfo::default()))
} else {
None
}
})
.collect(),
);
for ptr in spec.pointers(&config)? {
match ptr {
ValueSpecPointer::Package(pkg_ptr) => {
if let Some(current_dependency) =
current_dependencies.get_mut(pkg_ptr.package_id())
current_dependencies.0.get_mut(pkg_ptr.package_id())
{
current_dependency.pointers.push(pkg_ptr);
} else {
current_dependencies.insert(
current_dependencies.0.insert(
pkg_ptr.package_id().to_owned(),
CurrentDependencyInfo {
pointers: vec![pkg_ptr],
@@ -585,10 +598,10 @@ pub fn configure_rec<'a, Db: DbHandle>(
// track dependencies with no pointers
for (package_id, health_checks) in res.depends_on.into_iter() {
if let Some(current_dependency) = current_dependencies.get_mut(&package_id) {
if let Some(current_dependency) = current_dependencies.0.get_mut(&package_id) {
current_dependency.health_checks.extend(health_checks);
} else {
current_dependencies.insert(
current_dependencies.0.insert(
package_id,
CurrentDependencyInfo {
pointers: Vec::new(),
@@ -599,17 +612,18 @@ pub fn configure_rec<'a, Db: DbHandle>(
}
// track dependency health checks
current_dependencies = current_dependencies
.into_iter()
.filter(|(dep_id, _)| {
if dep_id != id && !manifest.dependencies.0.contains_key(dep_id) {
tracing::warn!("Illegal dependency specified: {}", dep_id);
false
} else {
true
}
})
.collect();
current_dependencies = current_dependencies.map(|x| {
x.into_iter()
.filter(|(dep_id, _)| {
if dep_id != id && !manifest.dependencies.0.contains_key(dep_id) {
tracing::warn!("Illegal dependency specified: {}", dep_id);
false
} else {
true
}
})
.collect()
});
res.signal
} else {
None
@@ -619,7 +633,7 @@ pub fn configure_rec<'a, Db: DbHandle>(
remove_from_current_dependents_lists(
db,
id,
current_dependencies.keys(),
&current_dependencies,
&receipts.current_dependents,
)
.await?; // remove previous
@@ -630,9 +644,9 @@ pub fn configure_rec<'a, Db: DbHandle>(
&receipts.current_dependents,
)
.await?; // add new
current_dependencies.remove(id);
current_dependencies.0.remove(id);
receipts
.current_dependents
.current_dependencies
.set(db, current_dependencies.clone(), &id)
.await?;
@@ -656,12 +670,16 @@ pub fn configure_rec<'a, Db: DbHandle>(
overrides.insert(id.clone(), config.clone());
// handle dependents
let dependents = current_dependencies;
let dependents = receipts
.current_dependents
.get(db, id)
.await?
.ok_or_else(not_found)?;
let prev = if is_needs_config { None } else { old_config }
.map(Value::Object)
.unwrap_or_default();
let next = Value::Object(config.clone());
for (dependent, dep_info) in dependents.iter().filter(|(dep_id, _)| dep_id != &id) {
for (dependent, dep_info) in dependents.0.iter().filter(|(dep_id, _)| dep_id != &id) {
// check if config passes dependent check
if let Some(cfg) = receipts
.manifest_dependencies_config

View File

@@ -265,13 +265,61 @@ pub struct InstalledPackageDataEntry {
#[model]
pub dependency_info: BTreeMap<PackageId, StaticDependencyInfo>,
#[model]
pub current_dependents: BTreeMap<PackageId, CurrentDependencyInfo>,
pub current_dependents: CurrentDependents,
#[model]
pub current_dependencies: BTreeMap<PackageId, CurrentDependencyInfo>,
pub current_dependencies: CurrentDependencies,
#[model]
pub interface_addresses: InterfaceAddressMap,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CurrentDependents(pub BTreeMap<PackageId, CurrentDependencyInfo>);
impl CurrentDependents {
pub fn map(
mut self,
transform: impl Fn(
BTreeMap<PackageId, CurrentDependencyInfo>,
) -> BTreeMap<PackageId, CurrentDependencyInfo>,
) -> Self {
self.0 = transform(self.0);
self
}
}
impl Map for CurrentDependents {
type Key = PackageId;
type Value = CurrentDependencyInfo;
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
self.0.get(key)
}
}
impl HasModel for CurrentDependents {
type Model = MapModel<Self>;
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CurrentDependencies(pub BTreeMap<PackageId, CurrentDependencyInfo>);
impl CurrentDependencies {
pub fn map(
mut self,
transform: impl Fn(
BTreeMap<PackageId, CurrentDependencyInfo>,
) -> BTreeMap<PackageId, CurrentDependencyInfo>,
) -> Self {
self.0 = transform(self.0);
self
}
}
impl Map for CurrentDependencies {
type Key = PackageId;
type Value = CurrentDependencyInfo;
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
self.0.get(key)
}
}
impl HasModel for CurrentDependencies {
type Model = MapModel<Self>;
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "kebab-case")]
pub struct StaticDependencyInfo {

View File

@@ -17,7 +17,9 @@ use tracing::instrument;
use crate::config::spec::PackagePointerSpec;
use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec};
use crate::context::RpcContext;
use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntry};
use crate::db::model::{
CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageDataEntry,
};
use crate::procedure::{NoOutput, PackageProcedure};
use crate::s9pk::manifest::{Manifest, PackageId};
use crate::status::health_check::{HealthCheckId, HealthCheckResult};
@@ -64,7 +66,7 @@ pub struct TryHealReceipts {
status: LockReceipt<Status, String>,
manifest: LockReceipt<Manifest, String>,
manifest_version: LockReceipt<Version, String>,
current_dependencies: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
current_dependencies: LockReceipt<CurrentDependencies, String>,
dependency_errors: LockReceipt<DependencyErrors, String>,
}
@@ -541,6 +543,7 @@ impl DependencyConfig {
}
pub struct DependencyConfigReceipts {
config: ConfigReceipts,
dependencies: LockReceipt<Dependencies, ()>,
dependency_volumes: LockReceipt<Volumes, ()>,
dependency_version: LockReceipt<Version, ()>,
@@ -550,65 +553,76 @@ pub struct DependencyConfigReceipts {
}
impl DependencyConfigReceipts {
pub async fn new(
db: &'_ mut impl DbHandle,
pub async fn new<'a>(
db: &'a mut impl DbHandle,
package_id: &PackageId,
dependency_id: &PackageId,
) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks, package_id, dependency_id);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<LockTargetId>,
package_id: &PackageId,
dependency_id: &PackageId,
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
let config = ConfigReceipts::setup(locks);
let dependencies = crate::db::DatabaseModel::new()
.package_data()
.idx_model(package_id)
.and_then(|x| x.installed())
.map(|x| x.manifest().dependencies())
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
.add_to_keys(locks);
let dependency_volumes = crate::db::DatabaseModel::new()
.package_data()
.idx_model(dependency_id)
.and_then(|x| x.installed())
.map(|x| x.manifest().volumes())
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
.add_to_keys(locks);
let dependency_version = crate::db::DatabaseModel::new()
.package_data()
.idx_model(dependency_id)
.and_then(|x| x.installed())
.map(|x| x.manifest().version())
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
.add_to_keys(locks);
let dependency_config_action = crate::db::DatabaseModel::new()
.package_data()
.idx_model(dependency_id)
.and_then(|x| x.installed())
.and_then(|x| x.manifest().config())
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
.add_to_keys(locks);
let package_volumes = crate::db::DatabaseModel::new()
.package_data()
.idx_model(package_id)
.and_then(|x| x.installed())
.map(|x| x.manifest().volumes())
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
.add_to_keys(locks);
let package_version = crate::db::DatabaseModel::new()
.package_data()
.idx_model(package_id)
.and_then(|x| x.installed())
.map(|x| x.manifest().version())
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let skeleton_key = db.lock_all(locks).await?;
Ok(Self {
dependencies: dependencies.verify(&skeleton_key)?,
dependency_volumes: dependency_volumes.verify(&skeleton_key)?,
dependency_version: dependency_version.verify(&skeleton_key)?,
dependency_config_action: dependency_config_action.verify(&skeleton_key)?,
package_volumes: package_volumes.verify(&skeleton_key)?,
package_version: package_version.verify(&skeleton_key)?,
})
.add_to_keys(locks);
move |skeleton_key| {
Ok(Self {
config: config(skeleton_key)?,
dependencies: dependencies.verify(&skeleton_key)?,
dependency_volumes: dependency_volumes.verify(&skeleton_key)?,
dependency_version: dependency_version.verify(&skeleton_key)?,
dependency_config_action: dependency_config_action.verify(&skeleton_key)?,
package_volumes: package_volumes.verify(&skeleton_key)?,
package_version: package_version.verify(&skeleton_key)?,
})
}
}
}
@@ -635,7 +649,7 @@ pub async fn configure_impl(
spec: _,
} = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone()), &receipts).await?;
let locks = ConfigReceipts::new(&mut db).await?;
let locks = &receipts.config;
Ok(crate::config::configure(
&ctx,
&mut db,
@@ -645,7 +659,7 @@ pub async fn configure_impl(
false,
&mut BTreeMap::new(),
&mut BTreeMap::new(),
&locks,
locks,
)
.await?)
}
@@ -749,21 +763,19 @@ pub async fn configure_logic(
})
}
#[instrument(skip(db, current_dependencies, current_dependent_receipt))]
pub async fn add_dependent_to_current_dependents_lists<
'a,
Db: DbHandle,
I: IntoIterator<Item = (&'a PackageId, &'a CurrentDependencyInfo)>,
>(
pub async fn add_dependent_to_current_dependents_lists<'a, Db: DbHandle>(
db: &mut Db,
dependent_id: &PackageId,
current_dependencies: I,
current_dependent_receipt: &LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
current_dependencies: &CurrentDependencies,
current_dependent_receipt: &LockReceipt<CurrentDependents, String>,
) -> Result<(), Error> {
for (dependency, dep_info) in current_dependencies {
for (dependency, dep_info) in &current_dependencies.0 {
if let Some(mut dependency_dependents) =
current_dependent_receipt.get(db, dependency).await?
{
dependency_dependents.insert(dependent_id.clone(), dep_info.clone());
dependency_dependents
.0
.insert(dependent_id.clone(), dep_info.clone());
current_dependent_receipt
.set(db, dependency_dependents, dependency)
.await?;
@@ -789,11 +801,11 @@ impl DependencyErrors {
ctx: &RpcContext,
db: &mut Db,
manifest: &Manifest,
current_dependencies: &BTreeMap<PackageId, CurrentDependencyInfo>,
current_dependencies: &CurrentDependencies,
receipts: &TryHealReceipts,
) -> Result<DependencyErrors, Error> {
let mut res = BTreeMap::new();
for (dependency_id, info) in current_dependencies.keys().filter_map(|dependency_id| {
for (dependency_id, info) in current_dependencies.0.keys().filter_map(|dependency_id| {
manifest
.dependencies
.0
@@ -836,7 +848,7 @@ pub async fn break_all_dependents_transitive<'a, Db: DbHandle>(
.get(db, id)
.await?
.iter()
.flat_map(|x| x.keys())
.flat_map(|x| x.0.keys())
.filter(|dependent| id != *dependent)
{
break_transitive(db, dependent, id, error.clone(), breakages, receipts).await?;
@@ -848,7 +860,7 @@ pub async fn break_all_dependents_transitive<'a, Db: DbHandle>(
pub struct BreakTransitiveReceipts {
pub dependency_receipt: DependencyReceipt,
dependency_errors: LockReceipt<DependencyErrors, String>,
current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
current_dependents: LockReceipt<CurrentDependents, String>,
}
impl BreakTransitiveReceipts {
@@ -964,7 +976,7 @@ pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>(
.get(db, id)
.await?
.ok_or_else(not_found)?;
for dependent in dependents.keys().filter(|dependent| id != *dependent) {
for dependent in dependents.0.keys().filter(|dependent| id != *dependent) {
heal_transitive(ctx, db, dependent, id, locks).await?;
}
Ok(())
@@ -1014,7 +1026,7 @@ pub async fn reconfigure_dependents_with_live_pointers(
) -> Result<(), Error> {
let dependents = &pde.current_dependents;
let me = &pde.manifest.id;
for (dependent_id, dependency_info) in dependents {
for (dependent_id, dependency_info) in &dependents.0 {
if dependency_info.pointers.iter().any(|ptr| match ptr {
// dependency id matches the package being uninstalled
PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == me && dependent_id != me,
@@ -1043,7 +1055,7 @@ pub async fn reconfigure_dependents_with_live_pointers(
#[derive(Clone)]
pub struct DependencyReceipt {
pub try_heal: TryHealReceipts,
current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
current_dependents: LockReceipt<CurrentDependents, String>,
status: LockReceipt<Status, String>,
dependency: LockReceipt<DepInfo, (String, String)>,
}

View File

@@ -9,7 +9,8 @@ use super::{PKG_ARCHIVE_DIR, PKG_DOCKER_DIR};
use crate::config::{not_found, ConfigReceipts};
use crate::context::RpcContext;
use crate::db::model::{
AllPackageData, CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry,
AllPackageData, CurrentDependencies, CurrentDependencyInfo, CurrentDependents,
InstalledPackageDataEntry, PackageDataEntry,
};
use crate::dependencies::{
reconfigure_dependents_with_live_pointers, DependencyErrors, TryHealReceipts,
@@ -60,18 +61,14 @@ impl UpdateDependencyReceipts {
}
#[instrument(skip(ctx, db, deps, receipts))]
pub async fn update_dependency_errors_of_dependents<
'a,
Db: DbHandle,
I: IntoIterator<Item = &'a PackageId>,
>(
pub async fn update_dependency_errors_of_dependents<'a, Db: DbHandle>(
ctx: &RpcContext,
db: &mut Db,
id: &PackageId,
deps: I,
deps: &CurrentDependents,
receipts: &UpdateDependencyReceipts,
) -> Result<(), Error> {
for dep in deps {
for dep in deps.0.keys() {
if let Some(man) = receipts.manifest.get(db, dep).await? {
if let Err(e) = if let Some(info) = man.dependencies.0.get(id) {
info.satisfied(ctx, db, id, None, dep, &receipts.try_heal)
@@ -269,19 +266,15 @@ pub async fn cleanup_failed<Db: DbHandle>(
}
#[instrument(skip(db, current_dependencies, current_dependent_receipt))]
pub async fn remove_from_current_dependents_lists<
'a,
Db: DbHandle,
I: IntoIterator<Item = &'a PackageId>,
>(
pub async fn remove_from_current_dependents_lists<'a, Db: DbHandle>(
db: &mut Db,
id: &'a PackageId,
current_dependencies: I,
current_dependent_receipt: &LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
current_dependencies: &'a CurrentDependencies,
current_dependent_receipt: &LockReceipt<CurrentDependents, String>,
) -> Result<(), Error> {
for dep in current_dependencies.into_iter().chain(std::iter::once(id)) {
for dep in current_dependencies.0.keys().chain(std::iter::once(id)) {
if let Some(mut current_dependents) = current_dependent_receipt.get(db, dep).await? {
if current_dependents.remove(id).is_some() {
if current_dependents.0.remove(id).is_some() {
current_dependent_receipt
.set(db, current_dependents, dep)
.await?;
@@ -294,7 +287,7 @@ pub struct UninstallReceipts {
config: ConfigReceipts,
removing: LockReceipt<InstalledPackageDataEntry, ()>,
packages: LockReceipt<AllPackageData, ()>,
current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
current_dependents: LockReceipt<CurrentDependents, String>,
update_depenency_receipts: UpdateDependencyReceipts,
}
impl UninstallReceipts {
@@ -367,7 +360,7 @@ where
remove_from_current_dependents_lists(
&mut tx,
&entry.manifest.id,
entry.current_dependencies.keys(),
&entry.current_dependencies,
&receipts.current_dependents,
)
.await?;
@@ -375,7 +368,7 @@ where
ctx,
&mut tx,
&entry.manifest.id,
entry.current_dependents.keys(),
&entry.current_dependents,
&receipts.update_depenency_receipts,
)
.await?;

View File

@@ -29,8 +29,8 @@ use crate::config::ConfigReceipts;
use crate::context::{CliContext, RpcContext};
use crate::core::rpc_continuations::{RequestGuid, RpcContinuation};
use crate::db::model::{
CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, RecoveredPackageInfo,
StaticDependencyInfo, StaticFiles,
CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageDataEntry,
PackageDataEntry, RecoveredPackageInfo, StaticDependencyInfo, StaticFiles,
};
use crate::db::util::WithRevision;
use crate::dependencies::{
@@ -1168,18 +1168,20 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
tracing::info!("Install {}@{}: Created manager", pkg_id, version);
let static_files = StaticFiles::local(pkg_id, version, manifest.assets.icon_type());
let current_dependencies: BTreeMap<_, _> = manifest
.dependencies
.0
.iter()
.filter_map(|(id, info)| {
if info.requirement.required() {
Some((id.clone(), CurrentDependencyInfo::default()))
} else {
None
}
})
.collect();
let current_dependencies: CurrentDependencies = CurrentDependencies(
manifest
.dependencies
.0
.iter()
.filter_map(|(id, info)| {
if info.requirement.required() {
Some((id.clone(), CurrentDependencyInfo::default()))
} else {
None
}
})
.collect(),
);
let current_dependents = {
let mut deps = BTreeMap::new();
for package in crate::db::DatabaseModel::new()
@@ -1225,7 +1227,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
deps.insert(package, dep);
}
}
deps
CurrentDependents(deps)
};
let mut pde = model
.clone()
@@ -1360,7 +1362,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
remove_from_current_dependents_lists(
&mut tx,
pkg_id,
prev.current_dependencies.keys(),
&prev.current_dependencies,
&receipts.config.current_dependents,
)
.await?; // remove previous
@@ -1375,10 +1377,11 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
ctx,
&mut tx,
pkg_id,
current_dependents
.keys()
.chain(prev.current_dependents.keys())
.collect::<BTreeSet<_>>(),
&CurrentDependents({
let mut current_dependents = current_dependents.0.clone();
current_dependents.append(&mut prev.current_dependents.0.clone());
current_dependents
}),
&receipts.config.update_dependency_receipts,
)
.await?;
@@ -1409,7 +1412,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
ctx,
&mut tx,
pkg_id,
current_dependents.keys(),
&current_dependents,
&receipts.config.update_dependency_receipts,
)
.await?;
@@ -1441,7 +1444,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
ctx,
&mut tx,
pkg_id,
current_dependents.keys(),
&current_dependents,
&receipts.config.update_dependency_receipts,
)
.await?;
@@ -1457,7 +1460,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
ctx,
&mut tx,
pkg_id,
current_dependents.keys(),
&current_dependents,
&receipts.config.update_dependency_receipts,
)
.await?;

View File

@@ -102,7 +102,7 @@ pub async fn check<Db: DbHandle>(
let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?;
tracing::debug!("Got receipts {}", id);
for (dependent, info) in &*current_dependents {
for (dependent, info) in (*current_dependents).0.iter() {
let failures: BTreeMap<HealthCheckId, HealthCheckResult> = health_results
.iter()
.filter(|(_, hc_res)| !matches!(hc_res, HealthCheckResult::Success { .. }))

View File

@@ -159,6 +159,7 @@ impl PackageProcedure {
}
}
#[derive(Debug)]
pub struct NoOutput;
impl<'de> Deserialize<'de> for NoOutput {
fn deserialize<D>(_: D) -> Result<Self, D::Error>