Skip to main content

munin_msbuild/
fields.rs

1// Copyright (c) Michael Grier
2
3//! `BuildEventArgsFields` — the common field block that prefixes every
4//! serialized build event.
5//!
6//! The field block starts with a `BuildEventArgsFieldFlags` bitmask that
7//! indicates which fields are present, followed by the fields themselves
8//! in a fixed order.
9
10use std::io::{Read, Write};
11
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    context::{BuildEventContext, read_build_event_context, write_build_event_context},
16    error::MuninError,
17    field_flags::BuildEventArgsFieldFlags,
18    primitives::{BinlogDateTime, read_7bit_count, read_7bit_int, read_datetime},
19    string_table::StringTable,
20    writers::{WriteContext, write_7bit_int, write_datetime, write_dedup_string},
21};
22
23/// Extended data fields for custom/extended event types.
24#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
25pub struct ExtendedDataFields {
26    pub extended_type: Option<String>,
27    pub extended_metadata_index: i32,
28    pub extended_data: Option<String>,
29}
30
31/// The common fields decoded from the flag-driven prefix of a build event.
32#[derive(Debug, Clone, Default, Serialize, Deserialize)]
33pub struct BuildEventArgsFields {
34    pub flags: BuildEventArgsFieldFlags,
35    pub message: Option<String>,
36    pub build_event_context: Option<BuildEventContext>,
37    pub thread_id: i32,
38    pub help_keyword: Option<String>,
39    pub sender_name: Option<String>,
40    pub timestamp: Option<BinlogDateTime>,
41    pub extended: Option<ExtendedDataFields>,
42
43    // Location fields (filled by diagnostic readers, not by ReadBuildEventArgsFields)
44    pub subcategory: Option<String>,
45    pub code: Option<String>,
46    pub file: Option<String>,
47    pub project_file: Option<String>,
48    pub line_number: i32,
49    pub column_number: i32,
50    pub end_line_number: i32,
51    pub end_column_number: i32,
52
53    // Arguments
54    pub arguments: Option<Vec<Option<String>>>,
55
56    // Importance (for message-derived events)
57    pub importance: i32,
58}
59
60/// Read the deduplicated-string index and resolve it against the string table.
61fn read_dedup_string(
62    reader: &mut impl Read,
63    strings: &StringTable,
64) -> Result<Option<String>, MuninError> {
65    let index = read_7bit_int(reader)?;
66    Ok(strings.get(index)?.map(|s| s.to_string()))
67}
68
69/// Read the common `BuildEventArgsFields` prefix from the stream.
70///
71/// `read_importance`: if true, importance is read even when the IMPORTANCE
72/// flag is not set (for format versions < 13 where importance was always
73/// written for message events).
74pub fn read_build_event_args_fields(
75    reader: &mut impl Read,
76    strings: &StringTable,
77    file_format_version: i32,
78    read_importance: bool,
79) -> Result<BuildEventArgsFields, MuninError> {
80    let flags_raw = read_7bit_int(reader)? as u32;
81    let flags = BuildEventArgsFieldFlags::from_raw(flags_raw);
82    let mut fields = BuildEventArgsFields {
83        flags,
84        ..Default::default()
85    };
86
87    // Base fields — order matches the C# reference exactly.
88
89    if flags.contains(BuildEventArgsFieldFlags::MESSAGE) {
90        fields.message = read_dedup_string(reader, strings)?;
91    }
92
93    if flags.contains(BuildEventArgsFieldFlags::BUILD_EVENT_CONTEXT) {
94        fields.build_event_context = Some(read_build_event_context(reader, file_format_version)?);
95    }
96
97    if flags.contains(BuildEventArgsFieldFlags::THREAD_ID) {
98        fields.thread_id = read_7bit_int(reader)?;
99    }
100
101    if flags.contains(BuildEventArgsFieldFlags::HELP_KEYWORD) {
102        fields.help_keyword = read_dedup_string(reader, strings)?;
103    }
104
105    if flags.contains(BuildEventArgsFieldFlags::SENDER_NAME) {
106        fields.sender_name = read_dedup_string(reader, strings)?;
107    }
108
109    if flags.contains(BuildEventArgsFieldFlags::TIMESTAMP) {
110        fields.timestamp = Some(read_datetime(reader)?);
111    }
112
113    if flags.contains(BuildEventArgsFieldFlags::EXTENDED) {
114        let extended_type = read_dedup_string(reader, strings)?;
115        // Extended metadata is a name-value list index — we store the raw
116        // index here; resolution happens when the event is fully decoded.
117        let extended_metadata_index = read_7bit_int(reader)?;
118        let extended_data = read_dedup_string(reader, strings)?;
119        fields.extended = Some(ExtendedDataFields {
120            extended_type,
121            extended_metadata_index,
122            extended_data,
123        });
124    }
125
126    // End of base fields. Diagnostic location fields follow.
127
128    if flags.contains(BuildEventArgsFieldFlags::SUBCATEGORY) {
129        fields.subcategory = read_dedup_string(reader, strings)?;
130    }
131
132    if flags.contains(BuildEventArgsFieldFlags::CODE) {
133        fields.code = read_dedup_string(reader, strings)?;
134    }
135
136    if flags.contains(BuildEventArgsFieldFlags::FILE) {
137        fields.file = read_dedup_string(reader, strings)?;
138    }
139
140    if flags.contains(BuildEventArgsFieldFlags::PROJECT_FILE) {
141        fields.project_file = read_dedup_string(reader, strings)?;
142    }
143
144    if flags.contains(BuildEventArgsFieldFlags::LINE_NUMBER) {
145        fields.line_number = read_7bit_int(reader)?;
146    }
147
148    if flags.contains(BuildEventArgsFieldFlags::COLUMN_NUMBER) {
149        fields.column_number = read_7bit_int(reader)?;
150    }
151
152    if flags.contains(BuildEventArgsFieldFlags::END_LINE_NUMBER) {
153        fields.end_line_number = read_7bit_int(reader)?;
154    }
155
156    if flags.contains(BuildEventArgsFieldFlags::END_COLUMN_NUMBER) {
157        fields.end_column_number = read_7bit_int(reader)?;
158    }
159
160    if flags.contains(BuildEventArgsFieldFlags::ARGUMENTS) {
161        let count = read_7bit_count(reader, "event arguments count")?;
162        let mut args = Vec::with_capacity(count);
163        for _ in 0..count {
164            args.push(read_dedup_string(reader, strings)?);
165        }
166        fields.arguments = Some(args);
167    }
168
169    // Importance: in format < 13, importance was unconditionally written for
170    // message events (caller sets read_importance=true). In format >= 13,
171    // presence is controlled by the IMPORTANCE flag.
172    if (file_format_version < 13 && read_importance)
173        || (file_format_version >= 13 && flags.contains(BuildEventArgsFieldFlags::IMPORTANCE))
174    {
175        fields.importance = read_7bit_int(reader)?;
176    }
177
178    Ok(fields)
179}
180
181/// Write a `BuildEventArgsFields` block to the stream. Inverse of
182/// [`read_build_event_args_fields`].
183///
184/// `write_importance`: if true, importance is written even when the
185/// IMPORTANCE flag is not set (for format versions < 13 where importance
186/// was always written for message events). Must match the value passed to
187/// the corresponding reader.
188pub fn write_build_event_args_fields<W: Write>(
189    w: &mut W,
190    ctx: &mut WriteContext,
191    fields: &BuildEventArgsFields,
192    write_importance: bool,
193) -> std::io::Result<()> {
194    let flags = fields.flags;
195    write_7bit_int(w, flags.bits() as i32)?;
196
197    if flags.contains(BuildEventArgsFieldFlags::MESSAGE) {
198        write_dedup_string(w, ctx, fields.message.as_deref())?;
199    }
200
201    if flags.contains(BuildEventArgsFieldFlags::BUILD_EVENT_CONTEXT) {
202        let bec = fields.build_event_context.unwrap_or_default();
203        write_build_event_context(w, &bec, ctx.file_format_version)?;
204    }
205
206    if flags.contains(BuildEventArgsFieldFlags::THREAD_ID) {
207        write_7bit_int(w, fields.thread_id)?;
208    }
209
210    if flags.contains(BuildEventArgsFieldFlags::HELP_KEYWORD) {
211        write_dedup_string(w, ctx, fields.help_keyword.as_deref())?;
212    }
213
214    if flags.contains(BuildEventArgsFieldFlags::SENDER_NAME) {
215        write_dedup_string(w, ctx, fields.sender_name.as_deref())?;
216    }
217
218    if flags.contains(BuildEventArgsFieldFlags::TIMESTAMP) {
219        let ts = fields.timestamp.unwrap_or_default();
220        write_datetime(w, ts)?;
221    }
222
223    if flags.contains(BuildEventArgsFieldFlags::EXTENDED) {
224        let ext = fields.extended.clone().unwrap_or_default();
225        write_dedup_string(w, ctx, ext.extended_type.as_deref())?;
226        write_7bit_int(w, ext.extended_metadata_index)?;
227        write_dedup_string(w, ctx, ext.extended_data.as_deref())?;
228    }
229
230    if flags.contains(BuildEventArgsFieldFlags::SUBCATEGORY) {
231        write_dedup_string(w, ctx, fields.subcategory.as_deref())?;
232    }
233
234    if flags.contains(BuildEventArgsFieldFlags::CODE) {
235        write_dedup_string(w, ctx, fields.code.as_deref())?;
236    }
237
238    if flags.contains(BuildEventArgsFieldFlags::FILE) {
239        write_dedup_string(w, ctx, fields.file.as_deref())?;
240    }
241
242    if flags.contains(BuildEventArgsFieldFlags::PROJECT_FILE) {
243        write_dedup_string(w, ctx, fields.project_file.as_deref())?;
244    }
245
246    if flags.contains(BuildEventArgsFieldFlags::LINE_NUMBER) {
247        write_7bit_int(w, fields.line_number)?;
248    }
249
250    if flags.contains(BuildEventArgsFieldFlags::COLUMN_NUMBER) {
251        write_7bit_int(w, fields.column_number)?;
252    }
253
254    if flags.contains(BuildEventArgsFieldFlags::END_LINE_NUMBER) {
255        write_7bit_int(w, fields.end_line_number)?;
256    }
257
258    if flags.contains(BuildEventArgsFieldFlags::END_COLUMN_NUMBER) {
259        write_7bit_int(w, fields.end_column_number)?;
260    }
261
262    if flags.contains(BuildEventArgsFieldFlags::ARGUMENTS) {
263        let args = fields.arguments.as_deref().unwrap_or(&[]);
264        write_7bit_int(w, args.len() as i32)?;
265        for a in args {
266            write_dedup_string(w, ctx, a.as_deref())?;
267        }
268    }
269
270    if (ctx.file_format_version < 13 && write_importance)
271        || (ctx.file_format_version >= 13 && flags.contains(BuildEventArgsFieldFlags::IMPORTANCE))
272    {
273        write_7bit_int(w, fields.importance)?;
274    }
275
276    Ok(())
277}
278
279#[cfg(test)]
280mod tests;