Skip to content

Commit 701a9e4

Browse files
authored
error handling (#16)
1 parent 949616c commit 701a9e4

File tree

5 files changed

+169
-15
lines changed

5 files changed

+169
-15
lines changed

README.md

+30
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,36 @@ var svg = wrapper.RenderDiagram(script);
5959
// You can save this to a file, display it in a web page, or process it further as needed
6060
```
6161

62+
## Error Handling
63+
64+
The `RenderDiagram` method now returns a `RenderResult` object, which includes both the rendered SVG (if successful) and detailed error information (if rendering failed). Here's how you can use it:
65+
66+
```csharp
67+
var wrapper = new D2Wrapper();
68+
string script = @"
69+
A -> B
70+
B -> // This line has an error
71+
C -> D
72+
";
73+
74+
var result = wrapper.RenderDiagram(script);
75+
76+
if (result.IsSuccess)
77+
{
78+
Console.WriteLine("Diagram rendered successfully:");
79+
Console.WriteLine(result.Svg);
80+
}
81+
else
82+
{
83+
Console.WriteLine("Error rendering diagram:");
84+
Console.WriteLine($"Message: {result.Error.Message}");
85+
if (result.Error.LineNumber.HasValue)
86+
{
87+
Console.WriteLine($"Line {result.Error.LineNumber}: {result.Error.LineContent}");
88+
}
89+
}
90+
```
91+
6292
Running the web demo:
6393

6494
```

README.nuget.md

+30
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,36 @@ catch (Exception ex)
4949
}
5050
```
5151

52+
## Error Handling
53+
54+
The `RenderDiagram` method now returns a `RenderResult` object, which includes both the rendered SVG (if successful) and detailed error information (if rendering failed). Here's how you can use it:
55+
56+
```csharp
57+
var wrapper = new D2Wrapper();
58+
string script = @"
59+
A -> B
60+
B -> // This line has an error
61+
C -> D
62+
";
63+
64+
var result = wrapper.RenderDiagram(script);
65+
66+
if (result.IsSuccess)
67+
{
68+
Console.WriteLine("Diagram rendered successfully:");
69+
Console.WriteLine(result.Svg);
70+
}
71+
else
72+
{
73+
Console.WriteLine("Error rendering diagram:");
74+
Console.WriteLine($"Message: {result.Error.Message}");
75+
if (result.Error.LineNumber.HasValue)
76+
{
77+
Console.WriteLine($"Line {result.Error.LineNumber}: {result.Error.LineContent}");
78+
}
79+
}
80+
```
81+
5282
## Features
5383

5484
- Render D2 diagrams as SVG

examples/D2Sharp.Web/Program.cs

+20-5
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,29 @@
1919
return Results.BadRequest("Script is required");
2020
}
2121

22-
try
22+
var result = d2Wrapper.RenderDiagram(request.Script);
23+
24+
if (result.IsSuccess)
2325
{
24-
var svg = d2Wrapper.RenderDiagram(request.Script);
25-
return Results.Content(svg, "image/svg+xml");
26+
return Results.Content(result.Svg, "image/svg+xml");
2627
}
27-
catch (Exception ex)
28+
else
2829
{
29-
return Results.BadRequest($"Error rendering diagram: {ex.Message}");
30+
var highlightedParts = result.Error.GetHighlightedLineParts();
31+
var errorResponse = new
32+
{
33+
message = result.Error.Message,
34+
lineNumber = result.Error.LineNumber,
35+
column = result.Error.Column,
36+
lineContent = result.Error.LineContent,
37+
highlightedLineParts = new
38+
{
39+
beforeError = highlightedParts.beforeError,
40+
errorPart = highlightedParts.errorPart,
41+
afterError = highlightedParts.afterError
42+
}
43+
};
44+
return Results.BadRequest(errorResponse);
3045
}
3146
});
3247

examples/D2Sharp.Web/wwwroot/index.html

+13-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
99
textarea { width: 100%; height: 200px; }
1010
#result { margin-top: 20px; }
11+
.error { color: red; }
12+
.error-line { font-family: monospace; white-space: pre; background-color: #ffeeee; padding: 5px; border-radius: 3px; }
13+
.error-highlight { background-color: #ff6666; color: white; font-weight: bold; }
1114
</style>
1215
</head>
1316
<body>
1417
<h1>D2Sharp Web Demo</h1>
1518
<textarea id="script" placeholder="Enter your D2 script here...">direction: right
16-
A -> B -> C</textarea>
19+
A -> B ->
20+
C -> D</textarea>
1721
<button onclick="renderDiagram()">Render Diagram</button>
1822
<div id="result"></div>
1923

@@ -33,11 +37,16 @@ <h1>D2Sharp Web Demo</h1>
3337
const svg = await response.text();
3438
result.innerHTML = svg;
3539
} else {
36-
const error = await response.text();
37-
result.textContent = `Error: ${error}`;
40+
const error = await response.json();
41+
let errorMessage = `<p class="error">Error: ${error.message}</p>`;
42+
if (error.lineNumber && error.column) {
43+
errorMessage += `<p class="error">At line ${error.lineNumber}, column ${error.column}:</p>`;
44+
errorMessage += `<div class="error-line">${error.highlightedLineParts.beforeError}<span class="error-highlight">${error.highlightedLineParts.errorPart}</span>${error.highlightedLineParts.afterError}</div>`;
45+
}
46+
result.innerHTML = errorMessage;
3847
}
3948
} catch (error) {
40-
result.textContent = `Error: ${error.message}`;
49+
result.innerHTML = `<p class="error">Error: ${error.message}</p>`;
4150
}
4251
}
4352
</script>

src/D2Sharp/D2Wrapper.cs

+76-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Runtime.InteropServices;
22
using Microsoft.Extensions.Logging;
33
using Microsoft.Extensions.Logging.Abstractions;
4+
using System.Text.RegularExpressions;
45

56
namespace D2Sharp;
67

@@ -19,9 +20,10 @@ public D2Wrapper(ILogger<D2Wrapper>? logger = null)
1920
[LibraryImport("d2wrapper", EntryPoint = "FreeDiagram")]
2021
private static partial void FreeDiagram(IntPtr ptr);
2122

22-
public string? RenderDiagram(string script)
23+
public RenderResult RenderDiagram(string script)
2324
{
24-
_logger.LogDebug("Calling RenderDiagram with script {Script}", script);
25+
_logger.LogDebug("Calling RenderDiagram with script");
26+
2527
IntPtr errorPtr;
2628
var svgPtr = RenderDiagramInternal(script, out errorPtr);
2729

@@ -30,19 +32,87 @@ public D2Wrapper(ILogger<D2Wrapper>? logger = null)
3032
var errorMessage = Marshal.PtrToStringUTF8(errorPtr);
3133
FreeDiagram(errorPtr);
3234
_logger.LogError("Diagram rendering failed: {ErrorMessage}", errorMessage);
33-
throw new Exception($"Diagram rendering failed: {errorMessage}");
35+
return new RenderResult { Error = ParseError(errorMessage, script) };
3436
}
3537

3638
if (svgPtr == IntPtr.Zero)
3739
{
3840
_logger.LogError("RenderDiagramInternal returned null pointer");
39-
return null;
41+
return new RenderResult { Error = new D2Error { Message = "Rendering failed with null result" } };
4042
}
4143

4244
var svg = Marshal.PtrToStringUTF8(svgPtr);
4345
FreeDiagram(svgPtr);
4446

45-
_logger.LogDebug("Rendered diagram");
46-
return svg;
47+
_logger.LogDebug("Rendered diagram successfully");
48+
return new RenderResult { Svg = svg };
49+
}
50+
51+
private D2Error ParseError(string errorMessage, string script)
52+
{
53+
var error = new D2Error { Message = errorMessage };
54+
55+
// Match the format: "Compilation error: line:column: specific error message"
56+
var match = Regex.Match(errorMessage, @"Compilation error: (\d+):(\d+): (.+)");
57+
if (match.Success)
58+
{
59+
if (int.TryParse(match.Groups[1].Value, out int lineNumber))
60+
{
61+
error.LineNumber = lineNumber;
62+
error.LineContent = GetLineContent(script, lineNumber);
63+
}
64+
if (int.TryParse(match.Groups[2].Value, out int column))
65+
{
66+
error.Column = column;
67+
}
68+
error.Message = match.Groups[3].Value.Trim();
69+
}
70+
71+
return error;
72+
}
73+
74+
private string GetLineContent(string script, int lineNumber)
75+
{
76+
var lines = script.Split('\n');
77+
if (lineNumber > 0 && lineNumber <= lines.Length)
78+
{
79+
return lines[lineNumber - 1];
80+
}
81+
return string.Empty;
82+
}
83+
}
84+
85+
public class RenderResult
86+
{
87+
public string? Svg { get; set; }
88+
public D2Error? Error { get; set; }
89+
public bool IsSuccess => Error == null;
90+
}
91+
92+
public class D2Error
93+
{
94+
public string Message { get; set; } = "";
95+
public int? LineNumber { get; set; }
96+
public int? Column { get; set; }
97+
public string? LineContent { get; set; }
98+
99+
public (string beforeError, string errorPart, string afterError) GetHighlightedLineParts()
100+
{
101+
if (string.IsNullOrEmpty(LineContent) || !Column.HasValue || Column.Value <= 0)
102+
{
103+
return (LineContent ?? "", "", "");
104+
}
105+
106+
int highlightIndex = Column.Value - 1;
107+
if (highlightIndex >= LineContent.Length)
108+
{
109+
highlightIndex = LineContent.Length - 1;
110+
}
111+
112+
string beforeError = LineContent.Substring(0, highlightIndex);
113+
string errorPart = LineContent.Substring(highlightIndex, 1);
114+
string afterError = highlightIndex + 1 < LineContent.Length ? LineContent.Substring(highlightIndex + 1) : "";
115+
116+
return (beforeError, errorPart, afterError);
47117
}
48118
}

0 commit comments

Comments
 (0)