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,
50 pub head: String,
52 pub remote: Option<Remote>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "lowercase")]
57pub enum SourceKind {
58 Commit,
59 Range,
60 Mr,
61 Pr,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct Remote {
66 pub forge: String,
67 pub project: String,
68 pub id: String,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct Stats {
73 pub files: u32,
74 pub hunks: u32,
75 pub classes: u32,
76 pub binary_files: u32,
77 pub submodules: u32,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct FileEntry {
84 pub path: String,
85 pub disposition: Disposition,
86 pub mode: Option<String>,
88 pub old_mode: Option<String>,
90 pub old_path: Option<String>,
92 pub new_path: Option<String>,
95 pub rename_similarity: Option<u8>,
99 pub binary: bool,
101 pub submodule: Option<SubmoduleChange>,
102 pub generated: bool,
105 pub generated_by: Option<GeneratedBy>,
106 pub hunk_ids: Vec<String>,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum Disposition {
112 A,
113 D,
114 M,
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct SubmoduleChange {
119 pub old: Option<String>,
120 pub new: Option<String>,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(rename_all = "lowercase")]
125pub enum GeneratedBy {
126 Builtin,
128 Attr,
130 Config,
132}
133
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct HunkEntry {
138 pub id: String,
139 pub file: String,
140 pub old_start: u32,
141 pub old_count: u32,
142 pub new_start: u32,
143 pub new_count: u32,
144 pub class: String,
146 pub digest: String,
149 pub nonl_old: bool,
151 pub nonl_new: bool,
153 pub forge_position: ForgePosition,
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub struct ForgePosition {
159 pub new_line: Option<u32>,
161 pub old_line: Option<u32>,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
169pub struct ClassEntry {
170 pub id: String,
171 pub hunk_ids: Vec<String>,
172 pub exemplar: String,
174 pub pure_substitution: bool,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct Group {
182 pub id: String,
183 pub label: String,
184 pub description: String,
185 pub reason: String,
186 pub effort: Effort,
187 pub role: Option<Role>,
189 pub class_ids: Vec<String>,
190 pub depends_on: Vec<String>,
192 pub rank: u32,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(rename_all = "lowercase")]
198pub enum Effort {
199 Close,
201 Skim,
203 Noise,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(rename_all = "lowercase")]
209pub enum Role {
210 Foundation,
211 Consumer,
212 Mechanical,
213 Noise,
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct ReadingStep {
218 pub group: String,
219 pub action: ReadAction,
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223#[serde(rename_all = "lowercase")]
224pub enum ReadAction {
225 Read,
227 Exemplars,
229 Skip,
231 Fold,
233}
234
235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238pub struct Audit {
239 pub applier_exact: String,
241 pub tree_assertion: String,
243 pub hunks_carried: u32,
244 pub recount: u32,
246 pub coverage: Option<f64>,
247 pub classes_missing: Option<u32>,
248 pub classes_duplicated: Option<Vec<String>>,
249 pub classes_hallucinated: Option<Vec<String>>,
250 pub read_hunks: Option<u32>,
252 pub skipped_hunks: Option<u32>,
254}
255
256#[derive(Debug)]
257pub enum SchemaError {
258 UnsupportedVersion { found: u32 },
259 Json(serde_json::Error),
260}
261
262impl std::fmt::Display for SchemaError {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 match self {
265 SchemaError::UnsupportedVersion { found } => write!(
266 f,
267 "unsupported schema_version {found} (this reader understands {SCHEMA_VERSION})"
268 ),
269 SchemaError::Json(e) => write!(f, "invalid plan document: {e}"),
270 }
271 }
272}
273
274impl std::error::Error for SchemaError {
275 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
276 match self {
277 SchemaError::Json(e) => Some(e),
278 _ => None,
279 }
280 }
281}
282
283impl From<serde_json::Error> for SchemaError {
284 fn from(e: serde_json::Error) -> Self {
285 SchemaError::Json(e)
286 }
287}
288
289impl PlanDocument {
290 pub fn from_json(s: &str) -> Result<Self, SchemaError> {
292 #[derive(Deserialize)]
293 struct VersionProbe {
294 schema_version: u32,
295 }
296 let probe: VersionProbe = serde_json::from_str(s)?;
297 if probe.schema_version != SCHEMA_VERSION {
298 return Err(SchemaError::UnsupportedVersion {
299 found: probe.schema_version,
300 });
301 }
302 Ok(serde_json::from_str(s)?)
303 }
304
305 pub fn to_json_pretty(&self) -> Result<String, SchemaError> {
306 Ok(serde_json::to_string_pretty(self)?)
307 }
308
309 pub fn to_json(&self) -> Result<String, SchemaError> {
310 Ok(serde_json::to_string(self)?)
311 }
312}