1use serde::{Deserialize, Serialize};
16
17pub const SCHEMA_VERSION: u32 = 1;
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct PlanDocument {
22 pub schema_version: u32,
23 pub generator: Generator,
24 pub source: Source,
25 pub stats: Stats,
26 pub files: Vec<FileEntry>,
27 pub hunks: Vec<HunkEntry>,
28 pub classes: Vec<ClassEntry>,
29 pub groups: Option<Vec<Group>>,
31 pub reading_plan: Option<Vec<ReadingStep>>,
33 pub audit: Audit,
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct Generator {
38 pub tool: String,
39 pub version: String,
40 pub stages: Vec<String>,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct Source {
47 pub kind: SourceKind,
48 pub base: String,
52 pub head: String,
55 pub remote: Option<Remote>,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "lowercase")]
60pub enum SourceKind {
61 Commit,
62 Range,
63 Mr,
64 Pr,
65 Staged,
67 Worktree,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct Remote {
73 pub forge: String,
74 pub project: String,
75 pub id: String,
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct Stats {
80 pub files: u32,
81 pub hunks: u32,
82 pub classes: u32,
83 pub binary_files: u32,
84 pub submodules: u32,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct FileEntry {
91 pub path: String,
92 pub disposition: Disposition,
93 pub mode: Option<String>,
95 pub old_mode: Option<String>,
97 pub old_path: Option<String>,
99 pub new_path: Option<String>,
102 pub rename_similarity: Option<u8>,
106 pub binary: bool,
108 pub submodule: Option<SubmoduleChange>,
109 pub generated: bool,
112 pub generated_by: Option<GeneratedBy>,
113 pub hunk_ids: Vec<String>,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
118pub enum Disposition {
119 A,
120 D,
121 M,
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct SubmoduleChange {
126 pub old: Option<String>,
127 pub new: Option<String>,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "lowercase")]
132pub enum GeneratedBy {
133 Builtin,
135 Attr,
137 Config,
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct HunkEntry {
145 pub id: String,
146 pub file: String,
147 pub old_start: u32,
148 pub old_count: u32,
149 pub new_start: u32,
150 pub new_count: u32,
151 pub class: String,
153 pub digest: String,
156 pub nonl_old: bool,
158 pub nonl_new: bool,
160 pub forge_position: ForgePosition,
162}
163
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub struct ForgePosition {
166 pub new_line: Option<u32>,
168 pub old_line: Option<u32>,
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct ClassEntry {
177 pub id: String,
178 pub hunk_ids: Vec<String>,
179 pub exemplar: String,
181 pub pure_substitution: bool,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub struct Group {
189 pub id: String,
190 pub label: String,
191 pub description: String,
192 pub reason: String,
193 pub effort: Effort,
194 pub role: Option<Role>,
196 pub class_ids: Vec<String>,
197 pub depends_on: Vec<String>,
199 pub rank: u32,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(rename_all = "lowercase")]
205pub enum Effort {
206 Close,
208 Skim,
210 Noise,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "lowercase")]
216pub enum Role {
217 Foundation,
218 Consumer,
219 Mechanical,
220 Noise,
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224pub struct ReadingStep {
225 pub group: String,
226 pub action: ReadAction,
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "lowercase")]
231pub enum ReadAction {
232 Read,
234 Exemplars,
236 Skip,
238 Fold,
240}
241
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct Audit {
246 pub applier_exact: String,
248 pub tree_assertion: String,
250 pub hunks_carried: u32,
251 pub recount: u32,
253 pub coverage: Option<f64>,
254 pub classes_missing: Option<u32>,
255 pub classes_duplicated: Option<Vec<String>>,
256 pub classes_hallucinated: Option<Vec<String>>,
257 pub read_hunks: Option<u32>,
259 pub skipped_hunks: Option<u32>,
261}
262
263#[derive(Debug)]
264pub enum SchemaError {
265 UnsupportedVersion { found: u32 },
266 Json(serde_json::Error),
267}
268
269impl std::fmt::Display for SchemaError {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 match self {
272 SchemaError::UnsupportedVersion { found } => write!(
273 f,
274 "unsupported schema_version {found} (this reader understands {SCHEMA_VERSION})"
275 ),
276 SchemaError::Json(e) => write!(f, "invalid plan document: {e}"),
277 }
278 }
279}
280
281impl std::error::Error for SchemaError {
282 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
283 match self {
284 SchemaError::Json(e) => Some(e),
285 _ => None,
286 }
287 }
288}
289
290impl From<serde_json::Error> for SchemaError {
291 fn from(e: serde_json::Error) -> Self {
292 SchemaError::Json(e)
293 }
294}
295
296impl PlanDocument {
297 pub fn from_json(s: &str) -> Result<Self, SchemaError> {
299 #[derive(Deserialize)]
300 struct VersionProbe {
301 schema_version: u32,
302 }
303 let probe: VersionProbe = serde_json::from_str(s)?;
304 if probe.schema_version != SCHEMA_VERSION {
305 return Err(SchemaError::UnsupportedVersion {
306 found: probe.schema_version,
307 });
308 }
309 Ok(serde_json::from_str(s)?)
310 }
311
312 pub fn to_json_pretty(&self) -> Result<String, SchemaError> {
313 Ok(serde_json::to_string_pretty(self)?)
314 }
315
316 pub fn to_json(&self) -> Result<String, SchemaError> {
317 Ok(serde_json::to_string(self)?)
318 }
319}