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 Message:String,
97 StartLineNumber:u32,
98 StartColumn:u32,
99 EndLineNumber:u32,
100 EndColumn:u32,
101 ) -> Result<Self, String> {
102 // Validate severity range
103 if Severity > 8 || Severity == 0 {
104 return Err("Invalid severity value: must be 1, 2, 4, or 8".to_string());
105 }
106
107 // Validate message length
108 if Message.len() > MAX_MARKER_MESSAGE_LENGTH {
109 return Err(format!("Message exceeds maximum length of {} bytes", MAX_MARKER_MESSAGE_LENGTH));
110 }
111
112 // Validate position consistency
113 if StartLineNumber > EndLineNumber {
114 return Err("Start line number cannot be greater than end line number".to_string());
115 }
116
117 // Validate column consistency within same line
118 if StartLineNumber == EndLineNumber && StartColumn > EndColumn {
119 return Err("Start column cannot be greater than end column on the same line".to_string());
120 }
121
122 Ok(Self {
123 Severity,
124 Message,
125 StartLineNumber,
126 StartColumn,
127 EndLineNumber,
128 EndColumn,
129 Source:None,
130 Code:None,
131 ModelVersionIdentifier:None,
132 RelatedInformation:None,
133 Tags:None,
134 })
135 }
136
137 /// Validates the marker's position data.
138 ///
139 /// # Returns
140 /// Result indicating valid position or error with reason
141 pub fn ValidatePosition(&self) -> Result<(), String> {
142 if self.StartLineNumber > self.EndLineNumber {
143 return Err("Start line number cannot be greater than end line number".to_string());
144 }
145
146 if self.StartLineNumber == self.EndLineNumber && self.StartColumn > self.EndColumn {
147 return Err("Start column cannot be greater than end column on the same line".to_string());
148 }
149
150 Ok(())
151 }
152
153 /// Sets the source with length validation.
154 ///
155 /// # Arguments
156 /// * `Source` - Diagnostic source string
157 ///
158 /// # Returns
159 /// Result indicating success or error if source too long
160 pub fn SetSource(&mut self, Source:String) -> Result<(), String> {
161 if Source.len() > MAX_SOURCE_LENGTH {
162 return Err(format!("Source exceeds maximum length of {} bytes", MAX_SOURCE_LENGTH));
163 }
164
165 self.Source = Some(Source);
166 Ok(())
167 }
168
169 /// Gets the severity as a MarkerSeverity enum if valid.
170 ///
171 /// # Returns
172 /// Option containing MarkerSeverity or None if invalid
173 pub fn GetSeverity(&self) -> Option<MarkerSeverity> {
174 match self.Severity {
175 8 => Some(MarkerSeverity::Error),
176 4 => Some(MarkerSeverity::Warning),
177 2 => Some(MarkerSeverity::Information),
178 1 => Some(MarkerSeverity::Hint),
179 _ => None,
180 }
181 }
182
183 /// Creates a simple error marker.
184 ///
185 /// # Arguments
186 /// * `Message` - Error message
187 /// * `LineNumber` - Line number (0-based)
188 /// * `Column` - Column number (0-based)
189 ///
190 /// # Returns
191 /// New MarkerDataDTO configured as an error
192 pub fn Error(Message:String, LineNumber:u32, Column:u32) -> Self {
193 Self {
194 Severity:MarkerSeverity::Error as u32,
195 Message,
196 StartLineNumber:LineNumber,
197 StartColumn:Column,
198 EndLineNumber:LineNumber,
199 EndColumn:Column,
200 ..Default::default()
201 }
202 }
203
204 /// Creates a simple warning marker.
205 ///
206 /// # Arguments
207 /// * `Message` - Warning message
208 /// * `LineNumber` - Line number (0-based)
209 /// * `Column` - Column number (0-based)
210 ///
211 /// # Returns
212 /// New MarkerDataDTO configured as a warning
213 pub fn Warning(Message:String, LineNumber:u32, Column:u32) -> Self {
214 Self {
215 Severity:MarkerSeverity::Warning as u32,
216 Message,
217 StartLineNumber:LineNumber,
218 StartColumn:Column,
219 EndLineNumber:LineNumber,
220 EndColumn:Column,
221 ..Default::default()
222 }
223 }
224}