Skip to main content

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}