1use crate::root_envelopes::{RootEnvelopeMode, 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 mode: RootEnvelopeMode,
179) -> Result<serde_json::Value, serde_json::Error> {
180 serialize_named_json_output(output, "doctor", mode)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn tagged_json_uses_the_stable_privacy_safe_contract() {
189 let value = serialize_doctor_json_output(
190 DoctorOutput {
191 schema_version: SchemaVersion(DOCTOR_SCHEMA_VERSION),
192 version: ToolVersion("1.2.3".to_string()),
193 root: ".".to_string(),
194 status: DoctorStatus::Warn,
195 summary: DoctorSummary {
196 pass: 4,
197 warn: 1,
198 fail: 0,
199 skipped: 0,
200 },
201 checks: vec![DoctorCheck {
202 id: DoctorCheckId::TypeAware,
203 category: DoctorCheckCategory::Companion,
204 status: DoctorCheckStatus::Warn,
205 required: false,
206 message: "Companion is unavailable.".to_string(),
207 remediation: Some(DoctorRemediation {
208 command: "npm install --save-dev fallow-type-aware@1.2.3".to_string(),
209 cwd: ".".to_string(),
210 mutating: true,
211 }),
212 }],
213 },
214 RootEnvelopeMode::Tagged,
215 )
216 .expect("serialize doctor output");
217
218 assert_eq!(value["kind"], "doctor");
219 assert_eq!(value["schema_version"], DOCTOR_SCHEMA_VERSION);
220 assert_eq!(value["root"], ".");
221 assert_eq!(value["checks"][0]["remediation"]["cwd"], ".");
222 assert_eq!(value["checks"][0]["remediation"]["mutating"], true);
223 assert!(
224 value["checks"][0]["remediation"]
225 .get("mutates_project")
226 .is_none()
227 );
228 }
229}