feat(batch5): implement jetstream error helpers and group01 constructors
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
// Copyright 2020-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//
|
||||
// Generated constructor surface for JetStream API errors.
|
||||
// Source parity: server/jetstream_errors_generated.go
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server;
|
||||
|
||||
public static partial class JsApiErrors
|
||||
{
|
||||
public static JsApiError NewJSAccountResourcesExceededError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AccountResourcesExceeded);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishContainsDuplicateMessageError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AtomicPublishContainsDuplicateMessage);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishDisabledError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AtomicPublishDisabled);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishIncompleteBatchError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AtomicPublishIncompleteBatch);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishInvalidBatchCommitError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AtomicPublishInvalidBatchCommit);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishInvalidBatchIDError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AtomicPublishInvalidBatchID);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishMissingSeqError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(AtomicPublishMissingSeq);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSBadRequestError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(BadRequest);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterIncompleteError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(ClusterIncomplete);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterNotActiveError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(ClusterNotActive);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterNotAssignedError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(ClusterNotAssigned);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterNotAvailError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(ClusterNotAvail);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterNotLeaderError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(ClusterNotLeader);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterPeerNotMemberError(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(ClusterPeerNotMember);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishTooLargeBatchError(object? size, params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return NewWithTags(AtomicPublishTooLargeBatch, "{size}", size);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSAtomicPublishUnsupportedHeaderBatchError(object? header, params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return NewWithTags(AtomicPublishUnsupportedHeaderBatch, "{header}", header);
|
||||
}
|
||||
|
||||
public static JsApiError NewJSClusterNoPeersError(Exception err, params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return NewWithTags(ClusterNoPeers, "{err}", err);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public sealed class JsApiError
|
||||
/// Pre-built <see cref="JsApiError"/> instances for all JetStream error codes.
|
||||
/// Mirrors the <c>ApiErrors</c> map in server/jetstream_errors_generated.go.
|
||||
/// </summary>
|
||||
public static class JsApiErrors
|
||||
public static partial class JsApiErrors
|
||||
{
|
||||
public delegate object? ErrorOption();
|
||||
|
||||
@@ -356,14 +356,10 @@ public static class JsApiErrors
|
||||
/// </summary>
|
||||
public static JsApiError NewJSRestoreSubscribeFailedError(Exception err, string subject, params ErrorOption[] opts)
|
||||
{
|
||||
var overridden = ParseUnless(opts);
|
||||
if (overridden != null)
|
||||
return overridden;
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return NewWithTags(
|
||||
RestoreSubscribeFailed,
|
||||
("{err}", err.Message),
|
||||
("{subject}", subject));
|
||||
return NewWithTags(RestoreSubscribeFailed, "{subject}", subject, "{err}", err);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -371,11 +367,10 @@ public static class JsApiErrors
|
||||
/// </summary>
|
||||
public static JsApiError NewJSStreamRestoreError(Exception err, params ErrorOption[] opts)
|
||||
{
|
||||
var overridden = ParseUnless(opts);
|
||||
if (overridden != null)
|
||||
return overridden;
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return NewWithTags(StreamRestore, ("{err}", err.Message));
|
||||
return NewWithTags(StreamRestore, "{err}", err);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -383,20 +378,20 @@ public static class JsApiErrors
|
||||
/// </summary>
|
||||
public static JsApiError NewJSPeerRemapError(params ErrorOption[] opts)
|
||||
{
|
||||
var overridden = ParseUnless(opts);
|
||||
return overridden ?? Clone(PeerRemap);
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(PeerRemap);
|
||||
}
|
||||
|
||||
private static JsApiError? ParseUnless(ReadOnlySpan<ErrorOption> opts)
|
||||
private static object? ParseOpts(params ErrorOption[] opts)
|
||||
{
|
||||
foreach (var opt in opts)
|
||||
{
|
||||
var value = opt();
|
||||
if (value is JsApiError apiErr)
|
||||
return Clone(apiErr);
|
||||
}
|
||||
object? value = null;
|
||||
|
||||
return null;
|
||||
foreach (var opt in opts)
|
||||
value = opt();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static JsApiError Clone(JsApiError source) => new()
|
||||
@@ -406,13 +401,43 @@ public static class JsApiErrors
|
||||
Description = source.Description,
|
||||
};
|
||||
|
||||
private static JsApiError NewWithTags(JsApiError source, params (string key, string value)[] replacements)
|
||||
private static string[] ToReplacerArgs(params object?[] replacements)
|
||||
{
|
||||
var args = new List<string>(replacements.Length);
|
||||
string key = string.Empty;
|
||||
|
||||
for (var i = 0; i < replacements.Length; i++)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
key = replacements[i] as string
|
||||
?? throw new InvalidOperationException("Replacement keys must be strings.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = replacements[i] switch
|
||||
{
|
||||
string s => s,
|
||||
Exception ex => ex.Message,
|
||||
null => string.Empty,
|
||||
var other => other.ToString() ?? string.Empty,
|
||||
};
|
||||
|
||||
args.Add(key);
|
||||
args.Add(value);
|
||||
}
|
||||
|
||||
return args.ToArray();
|
||||
}
|
||||
|
||||
private static JsApiError NewWithTags(JsApiError source, params object?[] replacements)
|
||||
{
|
||||
var clone = Clone(source);
|
||||
var description = clone.Description ?? string.Empty;
|
||||
var args = ToReplacerArgs(replacements);
|
||||
|
||||
foreach (var (key, value) in replacements)
|
||||
description = description.Replace(key, value, StringComparison.Ordinal);
|
||||
for (var i = 0; i + 1 < args.Length; i += 2)
|
||||
description = description.Replace(args[i], args[i + 1], StringComparison.Ordinal);
|
||||
|
||||
clone.Description = description;
|
||||
return clone;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
using Shouldly;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class JetStreamErrorsGeneratedConstructorsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConstructorSurface_Group01()
|
||||
{
|
||||
JsApiErrors.NewJSAccountResourcesExceededError().ErrCode.ShouldBe(JsApiErrors.AccountResourcesExceeded.ErrCode);
|
||||
JsApiErrors.NewJSAtomicPublishContainsDuplicateMessageError().ErrCode.ShouldBe(JsApiErrors.AtomicPublishContainsDuplicateMessage.ErrCode);
|
||||
JsApiErrors.NewJSAtomicPublishDisabledError().ErrCode.ShouldBe(JsApiErrors.AtomicPublishDisabled.ErrCode);
|
||||
JsApiErrors.NewJSAtomicPublishIncompleteBatchError().ErrCode.ShouldBe(JsApiErrors.AtomicPublishIncompleteBatch.ErrCode);
|
||||
JsApiErrors.NewJSAtomicPublishInvalidBatchCommitError().ErrCode.ShouldBe(JsApiErrors.AtomicPublishInvalidBatchCommit.ErrCode);
|
||||
JsApiErrors.NewJSAtomicPublishInvalidBatchIDError().ErrCode.ShouldBe(JsApiErrors.AtomicPublishInvalidBatchID.ErrCode);
|
||||
JsApiErrors.NewJSAtomicPublishMissingSeqError().ErrCode.ShouldBe(JsApiErrors.AtomicPublishMissingSeq.ErrCode);
|
||||
JsApiErrors.NewJSBadRequestError().ErrCode.ShouldBe(JsApiErrors.BadRequest.ErrCode);
|
||||
JsApiErrors.NewJSClusterIncompleteError().ErrCode.ShouldBe(JsApiErrors.ClusterIncomplete.ErrCode);
|
||||
JsApiErrors.NewJSClusterNotActiveError().ErrCode.ShouldBe(JsApiErrors.ClusterNotActive.ErrCode);
|
||||
JsApiErrors.NewJSClusterNotAssignedError().ErrCode.ShouldBe(JsApiErrors.ClusterNotAssigned.ErrCode);
|
||||
JsApiErrors.NewJSClusterNotAvailError().ErrCode.ShouldBe(JsApiErrors.ClusterNotAvail.ErrCode);
|
||||
JsApiErrors.NewJSClusterNotLeaderError().ErrCode.ShouldBe(JsApiErrors.ClusterNotLeader.ErrCode);
|
||||
JsApiErrors.NewJSClusterPeerNotMemberError().ErrCode.ShouldBe(JsApiErrors.ClusterPeerNotMember.ErrCode);
|
||||
|
||||
JsApiErrors.NewJSAtomicPublishTooLargeBatchError(512).Description.ShouldBe("atomic publish batch is too large: 512");
|
||||
JsApiErrors.NewJSAtomicPublishUnsupportedHeaderBatchError("Nats-Msg-Id").Description.ShouldBe("atomic publish unsupported header used: Nats-Msg-Id");
|
||||
JsApiErrors.NewJSClusterNoPeersError(new InvalidOperationException("no peers")).Description.ShouldBe("no peers");
|
||||
|
||||
var expected = new JsApiError { Code = 499, ErrCode = 9090, Description = "override" };
|
||||
var fromOverride = JsApiErrors.NewJSAccountResourcesExceededError(JsApiErrors.Unless(expected));
|
||||
fromOverride.Code.ShouldBe(expected.Code);
|
||||
fromOverride.ErrCode.ShouldBe(expected.ErrCode);
|
||||
fromOverride.Description.ShouldBe(expected.Description);
|
||||
ReferenceEquals(fromOverride, expected).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2020-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
using System.Reflection;
|
||||
using Shouldly;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
@@ -97,4 +98,39 @@ public sealed class JetStreamErrorsTests
|
||||
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(new Exception("other error"))),
|
||||
peerRemap).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseOpts_WithUnlessApiErrorOption_ReturnsOverride()
|
||||
{
|
||||
var parseOpts = typeof(JsApiErrors).GetMethod(
|
||||
"ParseOpts",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
parseOpts.ShouldNotBeNull();
|
||||
|
||||
var expected = new JsApiError { Code = 401, ErrCode = 2048, Description = "override" };
|
||||
var result = parseOpts.Invoke(
|
||||
null,
|
||||
[new JsApiErrors.ErrorOption[] { JsApiErrors.Unless(expected) }]);
|
||||
|
||||
result.ShouldBeAssignableTo<JsApiError>()
|
||||
.ErrCode.ShouldBe(expected.ErrCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToReplacerArgs_WithStringErrorAndObject_ConvertsToStringValues()
|
||||
{
|
||||
var toReplacerArgs = typeof(JsApiErrors).GetMethod(
|
||||
"ToReplacerArgs",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
toReplacerArgs.ShouldNotBeNull();
|
||||
|
||||
var result = toReplacerArgs.Invoke(
|
||||
null,
|
||||
[new object?[] { "{string}", "value", "{error}", new InvalidOperationException("boom"), "{number}", 42 }]);
|
||||
|
||||
result.ShouldBeAssignableTo<string[]>()
|
||||
.ShouldBe(["{string}", "value", "{error}", "boom", "{number}", "42"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user