Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,28 +128,37 @@ var allDevices = await pages.FlattenToListAsync();
To resume pagination later, store `pagination.NextPageCursor` and pass it to
`NextPageAsync` on a new pager with the same request parameters.

### Errors
### Error Handling

Seam API errors raise a typed exception carrying the Seam error code, HTTP
status code, and the `seam-request-id` to include in support requests:
status code, and the `seam-request-id` to include in support requests.

#### Validation errors

When the API rejects a request because a parameter is invalid, it throws a
`SeamHttpInvalidInputException`. Look up messages for a parameter you are
already rendering, for example a field in a form:

```csharp
try
{
await seam.Devices.GetAsync(new() { DeviceId = deviceId });
await seam.Devices.ListAsync(new() { DeviceIds = ["not-a-uuid"] });
}
catch (SeamHttpInvalidInputException exception)
{
foreach (var message in exception.GetValidationErrorMessages("device_id"))
foreach (var message in exception.GetValidationErrorMessages("device_ids"))
Console.WriteLine(message);
}
catch (SeamHttpUnauthorizedException)
{
// Invalid or expired credentials.
}
catch (SeamHttpApiException exception)
```

Or read every parameter that failed validation to summarize the request:

```csharp
foreach (var validationError in exception.ValidationErrors)
{
Console.WriteLine($"{exception.Code} ({exception.RequestId})");
Console.WriteLine(
$"{validationError.ParameterName}: {string.Join(", ", validationError.ErrorMessages)}"
);
}
```

Expand Down
36 changes: 36 additions & 0 deletions src/Seam/Exceptions/SeamHttpInvalidInputException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

namespace Seam
{
/// <summary>
/// A request parameter that failed validation and its error messages.
/// </summary>
public sealed record SeamValidationError(
string ParameterName,
IReadOnlyList<string> ErrorMessages
);

/// <summary>
/// Raised when the Seam API rejects the request parameters.
/// </summary>
Expand All @@ -23,6 +31,34 @@ public SeamHttpInvalidInputException(
_validationErrors = validationErrors;
}

/// <summary>
/// Validation errors, one entry per failed request parameter.
/// </summary>
public IReadOnlyList<SeamValidationError> ValidationErrors
{
get
{
if (_validationErrors is not { ValueKind: JsonValueKind.Object } validationErrors)
return Array.Empty<SeamValidationError>();

var errors = new List<SeamValidationError>();
foreach (var parameter in validationErrors.EnumerateObject())
{
if (parameter.Name != "_errors")
{
errors.Add(
new SeamValidationError(
parameter.Name,
GetValidationErrorMessages(parameter.Name)
)
);
}
}

return errors;
}
}

/// <summary>
/// The validation messages for a request parameter, or an empty list when that parameter
/// has none.
Expand Down
4 changes: 4 additions & 0 deletions test/Seam.Test/HttpErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class InvalidInputTests
"type": "invalid_input",
"message": "Invalid input",
"validation_errors": {
"_errors": ["Request is invalid"],
"device_ids": { "_errors": ["Expected array, received number"] }
},
"request_id": "request1"
Expand Down Expand Up @@ -104,6 +105,9 @@ public async Task ThrowsInvalidInputExceptionWithValidationMessages()
new[] { "Expected array, received number" },
exception.GetValidationErrorMessages("device_ids")
);
var validationError = Assert.Single(exception.ValidationErrors);
Assert.Equal("device_ids", validationError.ParameterName);
Assert.Equal(new[] { "Expected array, received number" }, validationError.ErrorMessages);
}

[Fact]
Expand Down
Loading