@@ -87,101 +87,190 @@ public static SarifResults Read(string filePath)
8787 using var document = JsonDocument . Parse ( json ) ;
8888 var root = document . RootElement ;
8989
90- // Validate SARIF version
91- if ( ! root . TryGetProperty ( "version" , out _ ) )
92- {
93- throw new InvalidOperationException ( "Invalid SARIF file: missing 'version' property." ) ;
94- }
90+ var firstRun = ValidateSarifStructure ( root ) ;
91+ var ( toolName , toolVersion ) = ExtractToolInformation ( firstRun ) ;
92+ var results = ParseResults ( firstRun ) ;
9593
96- // Get runs array
97- if ( ! root . TryGetProperty ( "runs" , out var runsElement ) || runsElement . ValueKind != JsonValueKind . Array )
98- {
99- throw new InvalidOperationException ( "Invalid SARIF file: missing or invalid 'runs' array." ) ;
100- }
94+ return new SarifResults ( toolName , toolVersion , results ) ;
95+ }
96+ catch ( JsonException ex )
97+ {
98+ throw new InvalidOperationException ( $ "Invalid JSON in SARIF file: { ex . Message } ", ex ) ;
99+ }
100+ }
101101
102- var runs = runsElement . EnumerateArray ( ) ;
103- if ( ! runs . Any ( ) )
104- {
105- throw new InvalidOperationException ( "Invalid SARIF file: 'runs' array is empty." ) ;
106- }
102+ /// <summary>
103+ /// Validates the SARIF file structure and returns the first run element.
104+ /// </summary>
105+ /// <param name="root">The root JSON element.</param>
106+ /// <returns>The first run element.</returns>
107+ /// <exception cref="InvalidOperationException">Thrown when the SARIF structure is invalid.</exception>
108+ private static JsonElement ValidateSarifStructure ( JsonElement root )
109+ {
110+ if ( ! root . TryGetProperty ( "version" , out _ ) )
111+ {
112+ throw new InvalidOperationException ( "Invalid SARIF file: missing 'version' property." ) ;
113+ }
107114
108- // Get the first run
109- var firstRun = runs . First ( ) ;
115+ if ( ! root . TryGetProperty ( "runs" , out var runsElement ) || runsElement . ValueKind != JsonValueKind . Array )
116+ {
117+ throw new InvalidOperationException ( "Invalid SARIF file: missing or invalid 'runs' array." ) ;
118+ }
110119
111- // Get tool information
112- if ( ! firstRun . TryGetProperty ( "tool" , out var toolElement ) )
113- {
114- throw new InvalidOperationException ( "Invalid SARIF file: missing 'tool' property in run ." ) ;
115- }
120+ var runs = runsElement . EnumerateArray ( ) ;
121+ if ( ! runs . Any ( ) )
122+ {
123+ throw new InvalidOperationException ( "Invalid SARIF file: 'runs' array is empty ." ) ;
124+ }
116125
117- if ( ! toolElement . TryGetProperty ( "driver" , out var driverElement ) )
118- {
119- throw new InvalidOperationException ( "Invalid SARIF file: missing 'driver' property in tool." ) ;
120- }
126+ return runs . First ( ) ;
127+ }
121128
122- var toolName = driverElement . TryGetProperty ( "name" , out var nameElement )
123- ? nameElement . GetString ( ) ?? "Unknown"
124- : "Unknown" ;
129+ /// <summary>
130+ /// Extracts tool information from a run element.
131+ /// </summary>
132+ /// <param name="runElement">The run JSON element.</param>
133+ /// <returns>A tuple containing the tool name and version.</returns>
134+ /// <exception cref="InvalidOperationException">Thrown when tool information is missing.</exception>
135+ private static ( string ToolName , string ToolVersion ) ExtractToolInformation ( JsonElement runElement )
136+ {
137+ if ( ! runElement . TryGetProperty ( "tool" , out var toolElement ) )
138+ {
139+ throw new InvalidOperationException ( "Invalid SARIF file: missing 'tool' property in run." ) ;
140+ }
125141
126- var toolVersion = driverElement . TryGetProperty ( "version" , out var toolVersionElement )
127- ? toolVersionElement . GetString ( ) ?? "Unknown"
128- : "Unknown" ;
142+ if ( ! toolElement . TryGetProperty ( "driver" , out var driverElement ) )
143+ {
144+ throw new InvalidOperationException ( "Invalid SARIF file: missing 'driver' property in tool." ) ;
145+ }
129146
130- // Parse results
131- var results = new List < SarifResult > ( ) ;
132- if ( firstRun . TryGetProperty ( "results" , out var resultsElement ) &&
133- resultsElement . ValueKind == JsonValueKind . Array )
134- {
135- foreach ( var resultElement in resultsElement . EnumerateArray ( ) )
136- {
137- var ruleId = resultElement . TryGetProperty ( "ruleId" , out var ruleIdElement )
138- ? ruleIdElement . GetString ( ) ?? string . Empty
139- : string . Empty ;
140-
141- var level = resultElement . TryGetProperty ( "level" , out var levelElement )
142- ? levelElement . GetString ( ) ?? "warning"
143- : "warning" ;
144-
145- var message = string . Empty ;
146- if ( resultElement . TryGetProperty ( "message" , out var messageElement ) &&
147- messageElement . TryGetProperty ( "text" , out var messageTextElement ) )
148- {
149- message = messageTextElement . GetString ( ) ?? string . Empty ;
150- }
151-
152- string ? uri = null ;
153- int ? startLine = null ;
154- if ( resultElement . TryGetProperty ( "locations" , out var locationsElement ) &&
155- locationsElement . ValueKind == JsonValueKind . Array )
156- {
157- var firstLocation = locationsElement . EnumerateArray ( ) . FirstOrDefault ( ) ;
158- if ( firstLocation . ValueKind != JsonValueKind . Undefined &&
159- firstLocation . TryGetProperty ( "physicalLocation" , out var physicalLocationElement ) )
160- {
161- if ( physicalLocationElement . TryGetProperty ( "artifactLocation" , out var artifactLocationElement ) &&
162- artifactLocationElement . TryGetProperty ( "uri" , out var uriElement ) )
163- {
164- uri = uriElement . GetString ( ) ;
165- }
166-
167- if ( physicalLocationElement . TryGetProperty ( "region" , out var regionElement ) &&
168- regionElement . TryGetProperty ( "startLine" , out var startLineElement ) )
169- {
170- startLine = startLineElement . GetInt32 ( ) ;
171- }
172- }
173- }
174-
175- results . Add ( new SarifResult ( ruleId , level , message , uri , startLine ) ) ;
176- }
177- }
147+ var toolName = driverElement . TryGetProperty ( "name" , out var nameElement )
148+ ? nameElement . GetString ( ) ?? "Unknown"
149+ : "Unknown" ;
178150
179- return new SarifResults ( toolName , toolVersion , results ) ;
151+ var toolVersion = driverElement . TryGetProperty ( "version" , out var toolVersionElement )
152+ ? toolVersionElement . GetString ( ) ?? "Unknown"
153+ : "Unknown" ;
154+
155+ return ( toolName , toolVersion ) ;
156+ }
157+
158+ /// <summary>
159+ /// Parses all results from a run element.
160+ /// </summary>
161+ /// <param name="runElement">The run JSON element.</param>
162+ /// <returns>A list of parsed SARIF results.</returns>
163+ private static List < SarifResult > ParseResults ( JsonElement runElement )
164+ {
165+ var results = new List < SarifResult > ( ) ;
166+
167+ if ( ! runElement . TryGetProperty ( "results" , out var resultsElement ) ||
168+ resultsElement . ValueKind != JsonValueKind . Array )
169+ {
170+ return results ;
180171 }
181- catch ( JsonException ex )
172+
173+ foreach ( var resultElement in resultsElement . EnumerateArray ( ) )
182174 {
183- throw new InvalidOperationException ( $ "Invalid JSON in SARIF file: { ex . Message } ", ex ) ;
175+ results . Add ( ParseResult ( resultElement ) ) ;
176+ }
177+
178+ return results ;
179+ }
180+
181+ /// <summary>
182+ /// Parses a single result element.
183+ /// </summary>
184+ /// <param name="resultElement">The result JSON element.</param>
185+ /// <returns>A parsed SARIF result.</returns>
186+ private static SarifResult ParseResult ( JsonElement resultElement )
187+ {
188+ var ruleId = resultElement . TryGetProperty ( "ruleId" , out var ruleIdElement )
189+ ? ruleIdElement . GetString ( ) ?? string . Empty
190+ : string . Empty ;
191+
192+ var level = resultElement . TryGetProperty ( "level" , out var levelElement )
193+ ? levelElement . GetString ( ) ?? "warning"
194+ : "warning" ;
195+
196+ var message = ParseMessage ( resultElement ) ;
197+ var ( uri , startLine ) = ParseLocation ( resultElement ) ;
198+
199+ return new SarifResult ( ruleId , level , message , uri , startLine ) ;
200+ }
201+
202+ /// <summary>
203+ /// Parses the message from a result element.
204+ /// </summary>
205+ /// <param name="resultElement">The result JSON element.</param>
206+ /// <returns>The message text.</returns>
207+ private static string ParseMessage ( JsonElement resultElement )
208+ {
209+ if ( resultElement . TryGetProperty ( "message" , out var messageElement ) &&
210+ messageElement . TryGetProperty ( "text" , out var messageTextElement ) )
211+ {
212+ return messageTextElement . GetString ( ) ?? string . Empty ;
213+ }
214+
215+ return string . Empty ;
216+ }
217+
218+ /// <summary>
219+ /// Parses location information from a result element.
220+ /// </summary>
221+ /// <param name="resultElement">The result JSON element.</param>
222+ /// <returns>A tuple containing the URI and start line.</returns>
223+ private static ( string ? Uri , int ? StartLine ) ParseLocation ( JsonElement resultElement )
224+ {
225+ if ( ! resultElement . TryGetProperty ( "locations" , out var locationsElement ) ||
226+ locationsElement . ValueKind != JsonValueKind . Array )
227+ {
228+ return ( null , null ) ;
229+ }
230+
231+ var firstLocation = locationsElement . EnumerateArray ( ) . FirstOrDefault ( ) ;
232+ if ( firstLocation . ValueKind == JsonValueKind . Undefined ||
233+ ! firstLocation . TryGetProperty ( "physicalLocation" , out var physicalLocationElement ) )
234+ {
235+ return ( null , null ) ;
184236 }
237+
238+ var uri = ParseUri ( physicalLocationElement ) ;
239+ var startLine = ParseStartLine ( physicalLocationElement ) ;
240+
241+ return ( uri , startLine ) ;
242+ }
243+
244+ /// <summary>
245+ /// Parses the URI from a physical location element.
246+ /// </summary>
247+ /// <param name="physicalLocationElement">The physical location JSON element.</param>
248+ /// <returns>The URI string or null.</returns>
249+ private static string ? ParseUri ( JsonElement physicalLocationElement )
250+ {
251+ if ( physicalLocationElement . TryGetProperty ( "artifactLocation" , out var artifactLocationElement ) &&
252+ artifactLocationElement . TryGetProperty ( "uri" , out var uriElement ) )
253+ {
254+ return uriElement . GetString ( ) ;
255+ }
256+
257+ return null ;
258+ }
259+
260+ /// <summary>
261+ /// Parses the start line from a physical location element.
262+ /// </summary>
263+ /// <param name="physicalLocationElement">The physical location JSON element.</param>
264+ /// <returns>The start line number or null.</returns>
265+ private static int ? ParseStartLine ( JsonElement physicalLocationElement )
266+ {
267+ if ( physicalLocationElement . TryGetProperty ( "region" , out var regionElement ) &&
268+ regionElement . TryGetProperty ( "startLine" , out var startLineElement ) )
269+ {
270+ return startLineElement . GetInt32 ( ) ;
271+ }
272+
273+ return null ;
185274 }
186275
187276 /// <summary>
0 commit comments