1use crate::root_envelopes::serialize_named_json_output;
4use fallow_types::envelope::{SchemaVersion, ToolVersion};
5use serde::Serialize;
6
7pub const DOCTOR_SCHEMA_VERSION: u32 = 2;
14
15#[cfg(feature = "schema")]
17#[allow(dead_code, reason = "schema-only type used by the field projection")]
18#[derive(schemars::JsonSchema)]
19#[schemars(extend("const" = DOCTOR_SCHEMA_VERSION))]
20struct DoctorSchemaVersion(u32);
21
22#[cfg(feature = "schema")]
24#[allow(dead_code, reason = "schema-only type used by the field projection")]
25#[derive(schemars::JsonSchema)]
26#[schemars(extend("const" = "."))]
27struct DoctorProjectRoot(String);
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32#[serde(rename_all = "kebab-case")]
33pub enum DoctorStatus {
34 Pass,
36 Warn,
38 Fail,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
44#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
45#[serde(rename_all = "kebab-case")]
46pub enum DoctorCheckStatus {
47 Pass,
49 Warn,
51 Fail,
53 Skipped,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60#[serde(rename_all = "kebab-case")]
61pub enum DoctorCheckId {
62 Root,
64 Config,
66 Workspaces,
68 Plugins,
70 TypeAware,
72 Dependencies,
74 Cache,
76 GraphCache,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
87#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
88#[serde(rename_all = "kebab-case")]
89pub enum DoctorCheckCategory {
90 Project,
92 Configuration,
94 Workspace,
96 Plugin,
98 Companion,
100 Cache,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
106#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
107pub struct DoctorRemediation {
108 pub command: String,
110 #[cfg_attr(feature = "schema", schemars(with = "DoctorProjectRoot"))]
112 pub cwd: String,
113 pub mutating: bool,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
119#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
120pub struct DoctorCheck {
121 pub id: DoctorCheckId,
123 pub category: DoctorCheckCategory,
125 pub status: DoctorCheckStatus,
127 pub required: bool,
129 pub message: String,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub remediation: Option<DoctorRemediation>,
134}
135
136#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
138#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
139pub struct DoctorSummary {
140 pub pass: usize,
142 pub warn: usize,
144 pub fail: usize,
146 pub skipped: usize,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
152#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
153#[cfg_attr(feature = "schema", schemars(title = "fallow doctor --format json"))]
154pub struct DoctorOutput {
155 #[cfg_attr(feature = "schema", schemars(with = "DoctorSchemaVersion"))]
157 pub schema_version: SchemaVersion,
158 pub version: ToolVersion,
160 #[cfg_attr(feature = "schema", schemars(with = "DoctorProjectRoot"))]
162 pub root: String,
163 pub status: DoctorStatus,
165 pub summary: DoctorSummary,
167 pub checks: Vec<DoctorCheck>,
169}
170
171pub fn serialize_doctor_json_output(
177 output: DoctorOutput,
178) -> Result<serde_json::Value, serde_json::Error> {
179 serialize_named_json_output(output, "doctor")
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn tagged_json_uses_the_stable_privacy_safe_contract() {
188 let value = serialize_doctor_json_output(DoctorOutput {
189 schema_version: SchemaVersion(DOCTOR_SCHEMA_VERSION),
190 version: ToolVersion("1.2.3".to_string()),
191 root: ".".to_string(),
192 status: DoctorStatus::Warn,
193 summary: DoctorSummary {
194 pass: 4,
195 warn: 1,
196 fail: 0,
197 skipped: 0,
198 },
199 checks: vec![DoctorCheck {
200 id: DoctorCheckId::TypeAware,
201 category: DoctorCheckCategory::Companion,
202 status: DoctorCheckStatus::Warn,
203 required: false,
204 message: "Companion is unavailable.".to_string(),
205 remediation: Some(DoctorRemediation {
206 command: "npm install --save-dev fallow-type-aware@1.2.3".to_string(),
207 cwd: ".".to_string(),
208 mutating: true,
209 }),
210 }],
211 })
212 .expect("serialize doctor output");
213
214 assert_eq!(value["kind"], "doctor");
215 assert_eq!(value["schema_version"], DOCTOR_SCHEMA_VERSION);
216 assert_eq!(value["root"], ".");
217 assert_eq!(value["checks"][0]["remediation"]["cwd"], ".");
218 assert_eq!(value["checks"][0]["remediation"]["mutating"], true);
219 assert!(
220 value["checks"][0]["remediation"]
221 .get("mutates_project")
222 .is_none()
223 );
224 }
225}