Mountain/ApplicationState/DTO/MarkerDataDTO.rs
1//! # MarkerDataDTO
2//!
3//! # RESPONSIBILITY
4//! - Data transfer object for diagnostic markers (errors, warnings, etc.)
5//! - Serializable format for gRPC/IPC transmission
6//! - Used by Mountain to display diagnostics in the UI
7//!
8//! # FIELDS
9//! - Severity: Marker severity level (Error, Warning, Info, Hint)
10//! - Message: Diagnostic message text
11//! - StartLineNumber/StartColumn: Position start (1-based, matches workbench
12//! `IMarkerData` - Cocoon's `LanguagesNamespace.ts` `NormaliseDiagnostic`
13//! adds the `+ 1` from vscode.Position 0-based before sending to Mountain.
14//! The MarkerService sanitiser at `markerService.ts:243` clamps `n > 0 ? n :
15//! 1`, so 0-based values collapse line-0 entries onto line 1 and shift every
16//! other line up by one - rendering squiggles on the wrong row.)
17//! - EndLineNumber/EndColumn: Position end (1-based, same convention)
18//! - Source: Diagnostic source (e.g., compiler, linter)
19//! - Code: Diagnostic code for quick fix lookup
20//! - ModelVersionIdentifier: Document version for tracking
21//! - RelatedInformation: Related diagnostic information
22//! - Tags: Additional marker tags (deprecated, unnecessary)
23
24use serde::{Deserialize, Serialize};
25use serde_json::Value;
26
27use super::MarkerSeverity::MarkerSeverity;
28
29/// Maximum message length for a marker
30const MAX_MARKER_MESSAGE_LENGTH:usize = 10_000;
31
32/// Maximum source string length
33const MAX_SOURCE_LENGTH:usize = 256;
34
35/// Represents a single diagnostic marker, such as a compiler error or a linter
36/// warning. This structure is compatible with VS Code's `IMarkerData`
37/// interface and is used by the Diagnostic service.
38#[derive(Serialize, Deserialize, Debug, Clone, Default)]
39#[serde(rename_all = "camelCase")]
40pub struct MarkerDataDTO {
41 /// Severity level of the marker
42 pub Severity:u32,
43
44 /// Human-readable diagnostic message
45 #[serde(skip_serializing_if = "String::is_empty")]
46 pub Message:String,
47
48 /// Start line number (1-based, mirrors workbench `IMarkerData`).
49 pub StartLineNumber:u32,
50
51 /// Start column number (1-based, mirrors workbench `IMarkerData`).
52 pub StartColumn:u32,
53
54 /// End line number (1-based).
55 pub EndLineNumber:u32,
56
57 /// End column number (1-based).
58 pub EndColumn:u32,
59
60 /// Diagnostic source (e.g., "typescript", "rustc")
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub Source:Option<String>,
63
64 /// Diagnostic code for quick fix lookup (string or object)
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub Code:Option<Value>,
67
68 /// Document version marker is associated with
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub ModelVersionIdentifier:Option<u64>,
71
72 /// Related diagnostic information
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub RelatedInformation:Option<Value>,
75
76 /// Additional marker tags (deprecated, unnecessary)
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub Tags:Option<Vec<u32>>,
79}
80
81impl MarkerDataDTO {
82 /// Creates a new MarkerDataDTO with validation.
83 ///
84 /// # Arguments
85 /// * `Severity` - Marker severity level
86 /// * `Message` - Diagnostic message
87 /// * `StartLineNumber` - Start line (0-based)
88 /// * `StartColumn` - Start column (0-based)
89 /// * `EndLineNumber` - End line (0-based)
90 /// * `EndColumn` - End column (0-based)
91 ///
92 /// # Returns
93 /// Result containing the DTO or validation error
94 pub fn New(
95 Severity:u32,
96
97 Message:String,
98
99 StartLineNumber:u32,
100
101 StartColumn:u32,
102
103 EndLineNumber:u32,
104
105 EndColumn:u32,
106 ) -> Result<Self, String> {
107 // Validate severity range
108 if Severity > 8 || Severity == 0 {
109 return Err("Invalid severity value: must be 1, 2, 4, or 8".to_string());
110 }
111
112 // Validate message length
113 if Message.len() > MAX_MARKER_MESSAGE_LENGTH {
114 return Err(format!("Message exceeds maximum length of {} bytes", MAX_MARKER_MESSAGE_LENGTH));
115 }
116
117 // Validate position consistency
118 if StartLineNumber > EndLineNumber {
119 return Err("Start line number cannot be greater than end line number".to_string());
120 }
121
122 // Validate column consistency within same line
123 if StartLineNumber == EndLineNumber && StartColumn > EndColumn {
124 return Err("Start column cannot be greater than end column on the same line".to_string());
125 }
126
127 Ok(Self {
128 Severity,
129 Message,
130 StartLineNumber,
131 StartColumn,
132 EndLineNumber,
133 EndColumn,
134 Source:None,
135 Code:None,
136 ModelVersionIdentifier:None,
137 RelatedInformation:None,
138 Tags:None,
139 })
140 }
141
142 /// Validates the marker's position data.
143 ///
144 /// # Returns
145 /// Result indicating valid position or error with reason
146 pub fn ValidatePosition(&self) -> Result<(), String> {
147 if self.StartLineNumber > self.EndLineNumber {
148 return Err("Start line number cannot be greater than end line number".to_string());
149 }
150
151 if self.StartLineNumber == self.EndLineNumber && self.StartColumn > self.EndColumn {
152 return Err("Start column cannot be greater than end column on the same line".to_string());
153 }
154
155 Ok(())
156 }
157
158 /// Sets the source with length validation.
159 ///
160 /// # Arguments
161 /// * `Source` - Diagnostic source string
162 ///
163 /// # Returns
164 /// Result indicating success or error if source too long
165 pub fn SetSource(&mut self, Source:String) -> Result<(), String> {
166 if Source.len() > MAX_SOURCE_LENGTH {
167 return Err(format!("Source exceeds maximum length of {} bytes", MAX_SOURCE_LENGTH));
168 }
169
170 self.Source = Some(Source);
171
172 Ok(())
173 }
174
175 /// Gets the severity as a MarkerSeverity enum if valid.
176 ///
177 /// # Returns
178 /// Option containing MarkerSeverity or None if invalid
179 pub fn GetSeverity(&self) -> Option<MarkerSeverity> {
180 match self.Severity {
181 8 => Some(MarkerSeverity::Error),
182
183 4 => Some(MarkerSeverity::Warning),
184
185 2 => Some(MarkerSeverity::Information),
186
187 1 => Some(MarkerSeverity::Hint),
188
189 _ => None,
190 }
191 }
192
193 /// Creates a simple error marker.
194 ///
195 /// # Arguments
196 /// * `Message` - Error message
197 /// * `LineNumber` - Line number (0-based)
198 /// * `Column` - Column number (0-based)
199 ///
200 /// # Returns
201 /// New MarkerDataDTO configured as an error
202 pub fn Error(Message:String, LineNumber:u32, Column:u32) -> Self {
203 Self {
204 Severity:MarkerSeverity::Error as u32,
205
206 Message,
207
208 StartLineNumber:LineNumber,
209
210 StartColumn:Column,
211
212 EndLineNumber:LineNumber,
213
214 EndColumn:Column,
215 ..Default::default()
216 }
217 }
218
219 /// Creates a simple warning marker.
220 ///
221 /// # Arguments
222 /// * `Message` - Warning message
223 /// * `LineNumber` - Line number (0-based)
224 /// * `Column` - Column number (0-based)
225 ///
226 /// # Returns
227 /// New MarkerDataDTO configured as a warning
228 pub fn Warning(Message:String, LineNumber:u32, Column:u32) -> Self {
229 Self {
230 Severity:MarkerSeverity::Warning as u32,
231
232 Message,
233
234 StartLineNumber:LineNumber,
235
236 StartColumn:Column,
237
238 EndLineNumber:LineNumber,
239
240 EndColumn:Column,
241 ..Default::default()
242 }
243 }
244}