use super::MedicOptions;
use serde::Serialize;
const SCHEMA_VERSION: u8 = 1;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub(super) struct MedicReport {
pub(super) schema_version: u8,
pub(super) command: String,
pub(super) scope: MedicScope,
pub(super) network: Option<String>,
pub(super) deployment: Option<String>,
pub(super) status: MedicStatus,
pub(super) checks: Vec<MedicCheck>,
}
impl MedicReport {
pub(super) fn new(options: &MedicOptions, checks: Vec<MedicCheck>) -> Self {
let network = match options.scope {
MedicScope::Project => options.network.clone(),
MedicScope::Deployment => Some(options.deployment_network()),
};
Self::with_network(options, network, checks)
}
pub(super) fn with_network(
options: &MedicOptions,
network: Option<String>,
checks: Vec<MedicCheck>,
) -> Self {
let status = aggregate_status(&checks);
Self {
schema_version: SCHEMA_VERSION,
command: options.command_label(),
scope: options.scope,
network,
deployment: options.deployment.clone(),
status,
checks: ordered_checks(&checks).into_iter().cloned().collect(),
}
}
}
pub(super) fn aggregate_status(checks: &[MedicCheck]) -> MedicStatus {
if checks.is_empty()
|| checks
.iter()
.all(|check| check.status == MedicStatus::NotEvaluated)
{
return MedicStatus::NotEvaluated;
}
if checks.iter().any(|check| check.status == MedicStatus::Fail) {
return MedicStatus::Fail;
}
if checks.iter().any(|check| check.status == MedicStatus::Warn) {
return MedicStatus::Warn;
}
MedicStatus::Pass
}
pub(super) fn ordered_checks(checks: &[MedicCheck]) -> Vec<&MedicCheck> {
let mut checks = checks.iter().collect::<Vec<_>>();
checks.sort_by_key(|check| check.category.order());
checks
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub(super) struct MedicCheck {
pub(super) category: MedicCategory,
pub(super) code: String,
pub(super) status: MedicStatus,
pub(super) subject: String,
pub(super) detail: String,
pub(super) next: String,
pub(super) source: MedicSource,
}
impl MedicCheck {
pub(super) fn pass(
category: MedicCategory,
code: impl Into<String>,
subject: impl Into<String>,
detail: impl Into<String>,
next: impl Into<String>,
source: MedicSource,
) -> Self {
Self::new(
category,
code,
MedicStatus::Pass,
subject,
detail,
next,
source,
)
}
pub(super) fn warn(
category: MedicCategory,
code: impl Into<String>,
subject: impl Into<String>,
detail: impl Into<String>,
next: impl Into<String>,
source: MedicSource,
) -> Self {
Self::new(
category,
code,
MedicStatus::Warn,
subject,
detail,
next,
source,
)
}
pub(super) fn fail(
category: MedicCategory,
code: impl Into<String>,
subject: impl Into<String>,
detail: impl Into<String>,
next: impl Into<String>,
source: MedicSource,
) -> Self {
Self::new(
category,
code,
MedicStatus::Fail,
subject,
detail,
next,
source,
)
}
pub(super) fn not_evaluated(
category: MedicCategory,
code: impl Into<String>,
subject: impl Into<String>,
detail: impl Into<String>,
next: impl Into<String>,
source: MedicSource,
) -> Self {
Self::new(
category,
code,
MedicStatus::NotEvaluated,
subject,
detail,
next,
source,
)
}
pub(super) fn new(
category: MedicCategory,
code: impl Into<String>,
status: MedicStatus,
subject: impl Into<String>,
detail: impl Into<String>,
next: impl Into<String>,
source: MedicSource,
) -> Self {
Self {
category,
code: code.into(),
status,
subject: subject.into(),
detail: detail.into(),
next: next.into(),
source,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum MedicScope {
Project,
Deployment,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum MedicStatus {
Pass,
Warn,
Fail,
NotEvaluated,
}
impl MedicStatus {
pub(super) const fn label(self) -> &'static str {
match self {
Self::Pass => "pass",
Self::Warn => "warn",
Self::Fail => "fail",
Self::NotEvaluated => "not_evaluated",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum MedicCategory {
Environment,
ProjectConfig,
Network,
DeploymentState,
Topology,
Auth,
BlobStorage,
Runtime,
}
impl MedicCategory {
pub(super) const fn label(self) -> &'static str {
match self {
Self::Environment => "environment",
Self::ProjectConfig => "project_config",
Self::Network => "network",
Self::DeploymentState => "deployment_state",
Self::Topology => "topology",
Self::Auth => "auth",
Self::BlobStorage => "blob_storage",
Self::Runtime => "runtime",
}
}
const fn order(self) -> usize {
match self {
Self::Environment => 0,
Self::ProjectConfig => 1,
Self::Network => 2,
Self::DeploymentState => 3,
Self::Topology => 4,
Self::Auth => 5,
Self::BlobStorage => 6,
Self::Runtime => 7,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum MedicSource {
Command,
IcpCli,
IcpConfig,
FleetConfig,
InstalledDeployment,
DeploymentTruth,
LocalReplica,
BlobStorageReadiness,
AuthRenewal,
StateManifest,
}
impl MedicSource {
pub(super) const fn label(self) -> &'static str {
match self {
Self::Command => "command",
Self::IcpCli => "icp_cli",
Self::IcpConfig => "icp_config",
Self::FleetConfig => "fleet_config",
Self::InstalledDeployment => "installed_deployment",
Self::DeploymentTruth => "deployment_truth",
Self::LocalReplica => "local_replica",
Self::BlobStorageReadiness => "blob_storage_readiness",
Self::AuthRenewal => "auth_renewal",
Self::StateManifest => "state_manifest",
}
}
}