Skip to content

Releases: anvilco/dotnet-anvil

v0.6.0

03 Mar 18:22
970cdb9

Choose a tag to compare

0.6.0

Breaking Changes

GraphQL errors now throw AnvilClientException

Previously, HTTP-level errors from the GraphQL client threw GraphQLHttpRequestException. They now throw AnvilClientException with the original preserved as InnerException.

Before:

try
{
    var result = await graphqlClient.SendQuery(query, variables);
}
catch (GraphQLHttpRequestException ex)
{
    // handle error
}

After:

try
{
    var result = await graphqlClient.SendQuery(query, variables);
}
catch (AnvilClientException ex)
{
    Console.WriteLine(ex.HttpStatusCode);    // e.g. 429
    Console.WriteLine(ex.ResponseContent);   // raw response body
    Console.WriteLine(ex.ResponseHeaders);   // dictionary of headers
    Console.WriteLine(ex.InnerException);    // original GraphQLHttpRequestException
}

SendGetRequest now throws on error responses

Previously, SendGetRequest silently returned failed HttpResponseMessage objects without throwing. It now throws AnvilClientException, consistent with SendPostRequest. This affects DownloadDocuments and any direct calls to SendGetRequest.

Before:

var response = await restClient.SendGetRequest(uri);
if (!response.IsSuccessStatusCode) { /* manual check */ }

After:

try
{
    var response = await restClient.SendGetRequest(uri);
}
catch (AnvilClientException ex)
{
    // error details available on ex
}

GraphQL error message format changed

Multiple GraphQL errors are now joined with "; " instead of concatenated directly.

Before: "Field required""Email invalid"
After: "Field required; Email invalid"

Individual messages are also available via ex.Data["Message1"], ex.Data["Message2"], etc.

Target framework simplified

The library now targets netstandard2.0 only, replacing net5.0;netstandard2.0;net461. Since netstandard2.0 is compatible with .NET Framework 4.6.1+, .NET Core 2+, and all modern .NET versions, most consumers are unaffected. Consumers that pinned to net5.0 or net461 should verify compatibility.

New Features

AnvilClientException properties

All error paths now populate these properties:

Property Type Description
HttpStatusCode HttpStatusCode? The HTTP status code (e.g. 429, 401, 500)
ResponseHeaders Dictionary<string, IEnumerable<string>>? Response headers (case-insensitive lookup)
ResponseContent string? Raw response body
InnerException Exception? Original exception (GraphQL path only)
Data["Message1"], etc. object? Individual error messages from the API

IDisposable support

RestClient and GraphQLClient now implement IDisposable to clean up their underlying HttpClient. Existing code that doesn't call Dispose() will continue to work.

using var client = new RestClient("your-api-key");

Bug Fixes

  • Non-JSON error responses no longer crash the client. Both REST and GraphQL paths now catch JsonException and fall back gracefully, preserving the raw response content on the exception.
  • RestClient.CreateExceptionFromResponse is now async. The previous implementation used .Result to block on ReadAsStringAsync(), which could deadlock in synchronous contexts or UI threads.
  • EtchSigner.SignerType no longer throws NullReferenceException on null input.
  • DocumentMarkup.Markup and HtmlCssMarkup.Html nullable annotations fixed. These properties were annotated as non-nullable but could be null after JSON deserialization.
  • Error messages with null message fields are now skipped. Previously, JSON error entries without a message key would insert null values into the exception's Data dictionary.

Dependencies

  • Upgraded Newtonsoft.Json from 12.0.3 to 13.0.3 to resolve a high-severity vulnerability (GHSA-5crp-9r3c-p9vr).

Internal

  • Added GitHub Actions CI workflow (build, format check, tests on PRs to main).
  • Test project updated to target net10.0.
  • InternalsVisibleTo added to support unit testing of internal error-wrapping logic.
  • WrapGraphQLException extracted as an internal static method from SendQuery<T> to make GraphQL error-wrapping logic unit-testable without mocking HTTP calls.
  • ResponseHeaders dictionaries use StringComparer.OrdinalIgnoreCase in both REST and GraphQL paths, since HTTP headers are case-insensitive.
  • Fixes #17