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		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}