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"]);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
77
tools/generate-jetstream-errors.sh
Executable file
77
tools/generate-jetstream-errors.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
output_file="$repo_root/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamErrors.GeneratedConstructors.cs"
|
||||
|
||||
simple_methods=(
|
||||
"NewJSAccountResourcesExceededError|AccountResourcesExceeded"
|
||||
"NewJSAtomicPublishContainsDuplicateMessageError|AtomicPublishContainsDuplicateMessage"
|
||||
"NewJSAtomicPublishDisabledError|AtomicPublishDisabled"
|
||||
"NewJSAtomicPublishIncompleteBatchError|AtomicPublishIncompleteBatch"
|
||||
"NewJSAtomicPublishInvalidBatchCommitError|AtomicPublishInvalidBatchCommit"
|
||||
"NewJSAtomicPublishInvalidBatchIDError|AtomicPublishInvalidBatchID"
|
||||
"NewJSAtomicPublishMissingSeqError|AtomicPublishMissingSeq"
|
||||
"NewJSBadRequestError|BadRequest"
|
||||
"NewJSClusterIncompleteError|ClusterIncomplete"
|
||||
"NewJSClusterNotActiveError|ClusterNotActive"
|
||||
"NewJSClusterNotAssignedError|ClusterNotAssigned"
|
||||
"NewJSClusterNotAvailError|ClusterNotAvail"
|
||||
"NewJSClusterNotLeaderError|ClusterNotLeader"
|
||||
"NewJSClusterPeerNotMemberError|ClusterPeerNotMember"
|
||||
)
|
||||
|
||||
templated_methods=(
|
||||
"NewJSAtomicPublishTooLargeBatchError|object?|size|AtomicPublishTooLargeBatch|{size}"
|
||||
"NewJSAtomicPublishUnsupportedHeaderBatchError|object?|header|AtomicPublishUnsupportedHeaderBatch|{header}"
|
||||
"NewJSClusterNoPeersError|Exception|err|ClusterNoPeers|{err}"
|
||||
)
|
||||
|
||||
{
|
||||
cat <<'EOF'
|
||||
// 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
|
||||
{
|
||||
EOF
|
||||
|
||||
for entry in "${simple_methods[@]}"; do
|
||||
IFS='|' read -r method field <<<"$entry"
|
||||
cat <<EOF
|
||||
public static JsApiError ${method}(params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return Clone(${field});
|
||||
}
|
||||
|
||||
EOF
|
||||
done
|
||||
|
||||
for entry in "${templated_methods[@]}"; do
|
||||
IFS='|' read -r method arg_type arg_name field placeholder <<<"$entry"
|
||||
cat <<EOF
|
||||
public static JsApiError ${method}(${arg_type} ${arg_name}, params ErrorOption[] opts)
|
||||
{
|
||||
if (ParseOpts(opts) is JsApiError overridden)
|
||||
return Clone(overridden);
|
||||
|
||||
return NewWithTags(${field}, "${placeholder}", ${arg_name});
|
||||
}
|
||||
|
||||
EOF
|
||||
done
|
||||
|
||||
cat <<'EOF'
|
||||
}
|
||||
EOF
|
||||
} >"$output_file"
|
||||
|
||||
echo "Generated $output_file"
|
||||
Reference in New Issue
Block a user