Files
SpacetimeDB/sdks/csharp/src/Exceptions.cs
rekhoff 023a7d31e3 Add a SpacetimeDBException to C# SDK to aid in debugging (#3386)
# Description of Changes

This implements a fix to an issue with debugging using the C# SDK, by
adding new exception types:
* `SpacetimeDBException`, 
* `SpacetimeDBArgumentException`,
* `SpacetimeDBEmptyReducerNameException`

Additional, regenerating bindings will now allow clients to report an
`SpacetimeDBEmptyReducerNameException` rather than an
`ArgumentOutOfRangeException` when the client receives an empty reducer
name from the server.

An example of the generated code currently, that results in the
exception:
https://github.com/clockworklabs/SpacetimeDB/blob/544e2edc2d1f7d1dd118832a815b6dbd7a6c1d82/sdks/csharp/examples~/quickstart-chat/client/module_bindings/SpacetimeDBClient.g.cs#L475

Note: Normally this is not an issue for a client, because the
`SpacetimeDBEmptyReducerNameException` would be caught by the
[Try/Catch](https://github.com/clockworklabs/SpacetimeDB/blob/9f59118e24449cdd2d3e182bd44fdb26078e921b/sdks/csharp/src/SpacetimeDBClient.cs#L421C25-L421C42)
statement, but a debugger will still catch the exception and halt
operation. This can be annoying to a developer when debugging.

By separating out the exception into a custom `Exception` type, we allow
a developer to flag the new exception type as something it can ignore,
without ignoring other relevant exceptions.

# API and ABI breaking changes

Not a breaking change.
Clients will need to regenerate bindings to get the new exceptions

# Expected complexity level and risk

1

# Testing

- [X] Tested regenerating bindings and confirmed intended output.
- [X] Tested debugging and receiving
`SpacetimeDBEmptyReducerNameException` when expected.

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2025-10-24 20:59:19 +00:00

139 lines
4.5 KiB
C#

using System;
using System.Runtime.Serialization;
namespace SpacetimeDB
{
/// <summary>
/// The base class for all SpacetimeDB SDK exceptions.
/// This allows users to catch all SpacetimeDB-specific exceptions in one catch block
/// or configure their debugger to ignore them.
/// </summary>
[Serializable]
public class SpacetimeDBException : Exception
{
// Base HRESULT for SpacetimeDB exceptions (0x8A000000 is in the user-defined range)
private const int SPACETIMEDB_HRESULT_BASE = unchecked((int)0x8A000000);
// Specific HRESULTs for different exception types
public const int SPACETIMEDB_EMPTY_REDUCER_NAME = SPACETIMEDB_HRESULT_BASE + 1;
public SpacetimeDBException()
: base()
{
HResult = SPACETIMEDB_HRESULT_BASE;
}
public SpacetimeDBException(string? message)
: base(message)
{
HResult = SPACETIMEDB_HRESULT_BASE;
}
public SpacetimeDBException(string? message, Exception? innerException)
: base(message, innerException)
{
HResult = SPACETIMEDB_HRESULT_BASE;
}
protected SpacetimeDBException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
/// <summary>
/// The exception that is thrown when one of the arguments provided to a method is not valid.
/// This is the base class for all SpacetimeDB argument exceptions.
/// </summary>
[Serializable]
public class SpacetimeDBArgumentException : SpacetimeDBException
{
private readonly string? _paramName;
public SpacetimeDBArgumentException()
: base("Value does not fall within the expected range.") { }
public SpacetimeDBArgumentException(string? message)
: base(message) { }
public SpacetimeDBArgumentException(string? message, Exception? innerException)
: base(message, innerException) { }
public SpacetimeDBArgumentException(
string? message,
string? paramName,
Exception? innerException
)
: base(message, innerException)
{
_paramName = paramName;
}
public SpacetimeDBArgumentException(string? message, string? paramName)
: base(message)
{
_paramName = paramName;
}
protected SpacetimeDBArgumentException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
_paramName = info.GetString("ParamName");
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("ParamName", _paramName, typeof(string));
}
public virtual string? ParamName => _paramName;
}
/// <summary>
/// The exception that is thrown when an empty reducer name is received from the server.
/// This is a known condition that is handled internally by the SpacetimeDB client.
/// </summary>
[Serializable]
public class SpacetimeDBEmptyReducerNameException : SpacetimeDBArgumentException
{
public SpacetimeDBEmptyReducerNameException()
: base("Empty reducer name received from server", (string?)null)
{
HResult = SPACETIMEDB_EMPTY_REDUCER_NAME;
}
public SpacetimeDBEmptyReducerNameException(string? paramName)
: base("Empty reducer name received from server", paramName)
{
HResult = SPACETIMEDB_EMPTY_REDUCER_NAME;
}
public SpacetimeDBEmptyReducerNameException(string? message, string? paramName)
: base(message, paramName)
{
HResult = SPACETIMEDB_EMPTY_REDUCER_NAME;
}
public SpacetimeDBEmptyReducerNameException(string? message, Exception? innerException)
: base(message, null, innerException)
{
HResult = SPACETIMEDB_EMPTY_REDUCER_NAME;
}
public SpacetimeDBEmptyReducerNameException(
string? message,
string? paramName,
Exception? innerException
)
: base(message, paramName, innerException)
{
HResult = SPACETIMEDB_EMPTY_REDUCER_NAME;
}
protected SpacetimeDBEmptyReducerNameException(
SerializationInfo info,
StreamingContext context
)
: base(info, context) { }
}
}