use crate::root_envelopes::{RootEnvelopeMode, serialize_named_json_output};
use fallow_types::envelope::{SchemaVersion, ToolVersion};
use serde::Serialize;
pub const DOCTOR_SCHEMA_VERSION: u32 = 1;
#[cfg(feature = "schema")]
#[allow(dead_code, reason = "schema-only type used by the field projection")]
#[derive(schemars::JsonSchema)]
#[schemars(extend("const" = DOCTOR_SCHEMA_VERSION))]
struct DoctorSchemaVersion(u32);
#[cfg(feature = "schema")]
#[allow(dead_code, reason = "schema-only type used by the field projection")]
#[derive(schemars::JsonSchema)]
#[schemars(extend("const" = "."))]
struct DoctorProjectRoot(String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum DoctorStatus {
Pass,
Warn,
Fail,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum DoctorCheckStatus {
Pass,
Warn,
Fail,
Skipped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum DoctorCheckId {
Root,
Config,
Workspaces,
Plugins,
TypeAware,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum DoctorCheckCategory {
Project,
Configuration,
Workspace,
Plugin,
Companion,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DoctorRemediation {
pub command: String,
#[cfg_attr(feature = "schema", schemars(with = "DoctorProjectRoot"))]
pub cwd: String,
pub mutating: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DoctorCheck {
pub id: DoctorCheckId,
pub category: DoctorCheckCategory,
pub status: DoctorCheckStatus,
pub required: bool,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub remediation: Option<DoctorRemediation>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DoctorSummary {
pub pass: usize,
pub warn: usize,
pub fail: usize,
pub skipped: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(title = "fallow doctor --format json"))]
pub struct DoctorOutput {
#[cfg_attr(feature = "schema", schemars(with = "DoctorSchemaVersion"))]
pub schema_version: SchemaVersion,
pub version: ToolVersion,
#[cfg_attr(feature = "schema", schemars(with = "DoctorProjectRoot"))]
pub root: String,
pub status: DoctorStatus,
pub summary: DoctorSummary,
pub checks: Vec<DoctorCheck>,
}
pub fn serialize_doctor_json_output(
output: DoctorOutput,
mode: RootEnvelopeMode,
) -> Result<serde_json::Value, serde_json::Error> {
serialize_named_json_output(output, "doctor", mode)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tagged_json_uses_the_stable_privacy_safe_contract() {
let value = serialize_doctor_json_output(
DoctorOutput {
schema_version: SchemaVersion(DOCTOR_SCHEMA_VERSION),
version: ToolVersion("1.2.3".to_string()),
root: ".".to_string(),
status: DoctorStatus::Warn,
summary: DoctorSummary {
pass: 4,
warn: 1,
fail: 0,
skipped: 0,
},
checks: vec![DoctorCheck {
id: DoctorCheckId::TypeAware,
category: DoctorCheckCategory::Companion,
status: DoctorCheckStatus::Warn,
required: false,
message: "Companion is unavailable.".to_string(),
remediation: Some(DoctorRemediation {
command: "npm install --save-dev fallow-type-aware@1.2.3".to_string(),
cwd: ".".to_string(),
mutating: true,
}),
}],
},
RootEnvelopeMode::Tagged,
)
.expect("serialize doctor output");
assert_eq!(value["kind"], "doctor");
assert_eq!(value["schema_version"], DOCTOR_SCHEMA_VERSION);
assert_eq!(value["root"], ".");
assert_eq!(value["checks"][0]["remediation"]["cwd"], ".");
assert_eq!(value["checks"][0]["remediation"]["mutating"], true);
assert!(
value["checks"][0]["remediation"]
.get("mutates_project")
.is_none()
);
}
}