diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc15c8a68..2cb0f0baa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,9 +40,12 @@ jobs: - name: Build run: dotnet build -c Debug - - name: Test + - name: Test Core run: dotnet test -c Debug --no-build tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj + - name: Test JetStream + run: dotnet test -c Debug --no-build tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj + memory_test: name: memory test strategy: diff --git a/NATS.Client.sln b/NATS.Client.sln index 1170e089a..b061caa90 100644 --- a/NATS.Client.sln +++ b/NATS.Client.sln @@ -53,6 +53,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Core.PublishHeaders EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Core.SubscribeHeaders", "sandbox\Example.Core.SubscribeHeaders\Example.Core.SubscribeHeaders.csproj", "{A96660DB-DAEB-4C57-8096-F236AC4FA927}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.JetStream", "src\NATS.Client.JetStream\NATS.Client.JetStream.csproj", "{56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.JetStream.Tests", "tests\NATS.Client.JetStream.Tests\NATS.Client.JetStream.Tests.csproj", "{2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.TestUtilities", "tests\NATS.Client.TestUtilities\NATS.Client.TestUtilities.csproj", "{90E5BF38-70C1-460A-9177-CE42815BDBF5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{BD234E2E-F51A-4B18-B8BE-8AF6D546BF87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schema.Generation", "tools\Schema.Generation\Schema.Generation.csproj", "{B7DD4A9C-2D24-4772-951E-86A665C59ADF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -127,6 +137,22 @@ Global {A96660DB-DAEB-4C57-8096-F236AC4FA927}.Debug|Any CPU.Build.0 = Debug|Any CPU {A96660DB-DAEB-4C57-8096-F236AC4FA927}.Release|Any CPU.ActiveCfg = Release|Any CPU {A96660DB-DAEB-4C57-8096-F236AC4FA927}.Release|Any CPU.Build.0 = Release|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB}.Release|Any CPU.Build.0 = Release|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145}.Release|Any CPU.Build.0 = Release|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90E5BF38-70C1-460A-9177-CE42815BDBF5}.Release|Any CPU.Build.0 = Release|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7DD4A9C-2D24-4772-951E-86A665C59ADF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -150,6 +176,10 @@ Global {B26DE6AC-A4D5-4427-8453-EE3514E4B513} = {C526E8AB-739A-48D7-8FC4-048978C9B650} {B0C82F24-BDEC-4420-A02A-F74E2423D755} = {95A69671-16CA-4133-981C-CC381B7AAA30} {A96660DB-DAEB-4C57-8096-F236AC4FA927} = {95A69671-16CA-4133-981C-CC381B7AAA30} + {56A9B885-6B7E-4BC6-AED0-ADC67C9A68DB} = {4827B3EC-73D8-436D-AE2A-5E29AC95FD0C} + {2D39F649-C512-4EE5-9DFA-7BD4D9E4F145} = {C526E8AB-739A-48D7-8FC4-048978C9B650} + {90E5BF38-70C1-460A-9177-CE42815BDBF5} = {C526E8AB-739A-48D7-8FC4-048978C9B650} + {B7DD4A9C-2D24-4772-951E-86A665C59ADF} = {BD234E2E-F51A-4B18-B8BE-8AF6D546BF87} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8CBB7278-D093-448E-B3DE-B5991209A1AA} diff --git a/NATS.Client.sln.DotSettings b/NATS.Client.sln.DotSettings index 96f42835b..f01f35132 100644 --- a/NATS.Client.sln.DotSettings +++ b/NATS.Client.sln.DotSettings @@ -1,6 +1,7 @@  ASCII CR + JS LF True True diff --git a/src/NATS.Client.Core/NATS.Client.Core.csproj b/src/NATS.Client.Core/NATS.Client.Core.csproj index f9499f0f8..fae4fdbf2 100644 --- a/src/NATS.Client.Core/NATS.Client.Core.csproj +++ b/src/NATS.Client.Core/NATS.Client.Core.csproj @@ -23,5 +23,6 @@ + diff --git a/src/NATS.Client.Core/NatsRequestExtensions.cs b/src/NATS.Client.Core/NatsRequestExtensions.cs index 0849993e5..591071bce 100644 --- a/src/NATS.Client.Core/NatsRequestExtensions.cs +++ b/src/NATS.Client.Core/NatsRequestExtensions.cs @@ -49,6 +49,11 @@ public static class NatsRequestExtensions throw new OperationCanceledException("Inbox subscription cancelled"); } + if (sub is { EndReason: NatsSubEndReason.Exception, Exception: not null }) + { + throw sub.Exception; + } + return null; } @@ -125,6 +130,11 @@ public static class NatsRequestExtensions throw new OperationCanceledException("Inbox subscription cancelled"); } + if (sub is { EndReason: NatsSubEndReason.Exception, Exception: not null }) + { + throw sub.Exception; + } + return null; } diff --git a/src/NATS.Client.Core/NatsSub.cs b/src/NATS.Client.Core/NatsSub.cs index e3c9f89f7..fe7c0ad16 100644 --- a/src/NATS.Client.Core/NatsSub.cs +++ b/src/NATS.Client.Core/NatsSub.cs @@ -12,6 +12,7 @@ internal enum NatsSubEndReason IdleTimeout, StartUpTimeout, Cancelled, + Exception, } public abstract class NatsSubBase : INatsSub @@ -30,6 +31,7 @@ public abstract class NatsSubBase : INatsSub private bool _endSubscription; private int _endReasonRaw; private int _pendingMsgs; + private Exception? _exception = null; internal NatsSubBase( NatsConnection connection, @@ -170,6 +172,14 @@ ValueTask INatsSub.ReceiveAsync( protected abstract ValueTask ReceiveInternalAsync(string subject, string? replyTo, ReadOnlySequence? headersBuffer, ReadOnlySequence payloadBuffer); + public Exception? Exception => Volatile.Read(ref _exception); + + protected void SetException(Exception exception) + { + Interlocked.Exchange(ref _exception, exception); + EndSubscription(NatsSubEndReason.Exception); + } + protected void ResetIdleTimeout() { _idleTimeoutTimer?.Change(dueTime: _idleTimeout, period: Timeout.InfiniteTimeSpan); @@ -275,19 +285,49 @@ protected override async ValueTask ReceiveInternalAsync(string subject, string? // deserialization exceptions. Currently only way for a user to find out is // to check the logs created by the client. If the logger isn't hooked up // they would be quietly ignored and the message would be lost either way. - var natsMsg = NatsMsg.Build( - subject, - replyTo, - headersBuffer, - payloadBuffer, - Connection, - Connection.HeaderParser, - Serializer); + try + { + var natsMsg = NatsMsg.Build( + subject, + replyTo, + headersBuffer, + payloadBuffer, + Connection, + Connection.HeaderParser, + Serializer); + + await _msgs.Writer.WriteAsync(natsMsg).ConfigureAwait(false); + + DecrementMaxMsgs(); + } + catch (Exception e) + { + var payload = new Memory(new byte[payloadBuffer.Length]); + payloadBuffer.CopyTo(payload.Span); - await _msgs.Writer.WriteAsync(natsMsg).ConfigureAwait(false); + Memory headers = default; + if (headersBuffer != null) + { + headers = new Memory(new byte[headersBuffer.Value.Length]); + } - DecrementMaxMsgs(); + SetException(new NatsSubException($"Message error: {e.Message}", e, payload, headers)); + } } protected override void TryComplete() => _msgs.Writer.TryComplete(); } + +public class NatsSubException : NatsException +{ + public NatsSubException(string message, Exception exception, Memory payload, Memory headers) + : base(message, exception) + { + Payload = payload; + Headers = headers; + } + + public Memory Payload { get; } + + public Memory Headers { get; } +} diff --git a/src/NATS.Client.JetStream/JSContext.cs b/src/NATS.Client.JetStream/JSContext.cs new file mode 100644 index 000000000..e35e9ed33 --- /dev/null +++ b/src/NATS.Client.JetStream/JSContext.cs @@ -0,0 +1,64 @@ +using System.ComponentModel.DataAnnotations; +using NATS.Client.Core; +using NATS.Client.JetStream.Models; + +namespace NATS.Client.JetStream; + +public class JSContext +{ + private readonly NatsConnection _nats; + private readonly JSOptions _options; + + public JSContext(NatsConnection nats, JSOptions options) + { + _nats = nats; + _options = options; + } + + public async ValueTask CreateStream(Action request) + { + var requestObj = new StreamCreateRequest(); + request(requestObj); + + Validator.ValidateObject(requestObj, new ValidationContext(requestObj)); + + var response = + await _nats.RequestAsync( + $"{_options.Prefix}.STREAM.CREATE.{requestObj.Name}", + requestObj); + + // TODO: Better error handling + if (response?.Data == null) + throw new NatsJetStreamException("No response received"); + + return new JSStream(response.Value.Data); + } +} + +public class NatsJetStreamException : NatsException +{ + public NatsJetStreamException(string message) + : base(message) + { + } + + public NatsJetStreamException(string message, Exception exception) + : base(message, exception) + { + } +} + +public record JSOptions +{ + public string Prefix { get; init; } = "$JS.API"; +} + +public class JSStream +{ + public JSStream(StreamCreateResponse response) + { + Response = response; + } + + public StreamCreateResponse Response { get; } +} diff --git a/src/NATS.Client.JetStream/Models/AccountInfoResponse.cs b/src/NATS.Client.JetStream/Models/AccountInfoResponse.cs new file mode 100644 index 000000000..7c6d601cb --- /dev/null +++ b/src/NATS.Client.JetStream/Models/AccountInfoResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.INFO API +/// + +public record AccountInfoResponse : AccountStats +{ +} diff --git a/src/NATS.Client.JetStream/Models/AccountLimits.cs b/src/NATS.Client.JetStream/Models/AccountLimits.cs new file mode 100644 index 000000000..aa5e69760 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/AccountLimits.cs @@ -0,0 +1,66 @@ +namespace NATS.Client.JetStream.Models; + +public record AccountLimits +{ + /// + /// The maximum amount of Memory storage Stream Messages may consume + /// + [System.Text.Json.Serialization.JsonPropertyName("max_memory")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-1, int.MaxValue)] + public int MaxMemory { get; set; } = default!; + + /// + /// The maximum amount of File storage Stream Messages may consume + /// + [System.Text.Json.Serialization.JsonPropertyName("max_storage")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-1, int.MaxValue)] + public int MaxStorage { get; set; } = default!; + + /// + /// The maximum number of Streams an account can create + /// + [System.Text.Json.Serialization.JsonPropertyName("max_streams")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-1, int.MaxValue)] + public int MaxStreams { get; set; } = default!; + + /// + /// The maximum number of Consumer an account can create + /// + [System.Text.Json.Serialization.JsonPropertyName("max_consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-1, int.MaxValue)] + public int MaxConsumers { get; set; } = default!; + + /// + /// Indicates if Streams created in this account requires the max_bytes property set + /// + [System.Text.Json.Serialization.JsonPropertyName("max_bytes_required")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool MaxBytesRequired { get; set; } = false; + + /// + /// The maximum number of outstanding ACKs any consumer may configure + /// + [System.Text.Json.Serialization.JsonPropertyName("max_ack_pending")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public int MaxAckPending { get; set; } = default!; + + /// + /// The maximum size any single memory stream may be + /// + [System.Text.Json.Serialization.JsonPropertyName("memory_max_stream_bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-1, int.MaxValue)] + public int MemoryMaxStreamBytes { get; set; } = -1; + + /// + /// The maximum size any single storage based stream may be + /// + [System.Text.Json.Serialization.JsonPropertyName("storage_max_stream_bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-1, int.MaxValue)] + public int StorageMaxStreamBytes { get; set; } = -1; +} diff --git a/src/NATS.Client.JetStream/Models/AccountPurgeResponse.cs b/src/NATS.Client.JetStream/Models/AccountPurgeResponse.cs new file mode 100644 index 000000000..340193f24 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/AccountPurgeResponse.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.ACCOUNT.PURGE API +/// + +public record AccountPurgeResponse +{ + /// + /// If the purge operation was succesfully started + /// + [System.Text.Json.Serialization.JsonPropertyName("initiated")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Initiated { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/AccountStats.cs b/src/NATS.Client.JetStream/Models/AccountStats.cs new file mode 100644 index 000000000..0c4c9d1b1 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/AccountStats.cs @@ -0,0 +1,57 @@ +namespace NATS.Client.JetStream.Models; + +public record AccountStats +{ + /// + /// Memory Storage being used for Stream Message storage + /// + [System.Text.Json.Serialization.JsonPropertyName("memory")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Memory { get; set; } = default!; + + /// + /// File Storage being used for Stream Message storage + /// + [System.Text.Json.Serialization.JsonPropertyName("storage")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Storage { get; set; } = default!; + + /// + /// Number of active Streams + /// + [System.Text.Json.Serialization.JsonPropertyName("streams")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Streams { get; set; } = default!; + + /// + /// Number of active Consumers + /// + [System.Text.Json.Serialization.JsonPropertyName("consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Consumers { get; set; } = default!; + + /// + /// The JetStream domain this account is in + /// + [System.Text.Json.Serialization.JsonPropertyName("domain")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Domain { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("limits")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public AccountLimits Limits { get; set; } = new AccountLimits(); + + [System.Text.Json.Serialization.JsonPropertyName("tiers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.IDictionary Tiers { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("api")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public ApiStats Api { get; set; } = new ApiStats(); +} diff --git a/src/NATS.Client.JetStream/Models/ApiError.cs b/src/NATS.Client.JetStream/Models/ApiError.cs new file mode 100644 index 000000000..d2815051f --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ApiError.cs @@ -0,0 +1,27 @@ +namespace NATS.Client.JetStream.Models; + +public record ApiError +{ + /// + /// HTTP like error code in the 300 to 500 range + /// + [System.Text.Json.Serialization.JsonPropertyName("code")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(300, 699)] + public int Code { get; set; } = default!; + + /// + /// A human friendly description of the error + /// + [System.Text.Json.Serialization.JsonPropertyName("description")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Description { get; set; } = default!; + + /// + /// The NATS error code unique to each kind of error + /// + [System.Text.Json.Serialization.JsonPropertyName("err_code")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, 65535)] + public int ErrCode { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ApiStats.cs b/src/NATS.Client.JetStream/Models/ApiStats.cs new file mode 100644 index 000000000..ed7b6e480 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ApiStats.cs @@ -0,0 +1,20 @@ +namespace NATS.Client.JetStream.Models; + +public record ApiStats +{ + /// + /// Total number of API requests received for this account + /// + [System.Text.Json.Serialization.JsonPropertyName("total")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Total { get; set; } = default!; + + /// + /// API requests that resulted in an error response + /// + [System.Text.Json.Serialization.JsonPropertyName("errors")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Errors { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ClusterInfo.cs b/src/NATS.Client.JetStream/Models/ClusterInfo.cs new file mode 100644 index 000000000..ba6244f4f --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ClusterInfo.cs @@ -0,0 +1,25 @@ +namespace NATS.Client.JetStream.Models; + +public record ClusterInfo +{ + /// + /// The cluster name + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Name { get; set; } = default!; + + /// + /// The server name of the RAFT leader + /// + [System.Text.Json.Serialization.JsonPropertyName("leader")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Leader { get; set; } = default!; + + /// + /// The members of the RAFT cluster + /// + [System.Text.Json.Serialization.JsonPropertyName("replicas")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Replicas { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerConfiguration.cs b/src/NATS.Client.JetStream/Models/ConsumerConfiguration.cs new file mode 100644 index 000000000..b7507b1f2 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerConfiguration.cs @@ -0,0 +1,209 @@ +namespace NATS.Client.JetStream.Models; + +public record ConsumerConfiguration +{ + [System.Text.Json.Serialization.JsonPropertyName("deliver_policy")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public ConsumerConfigurationDeliverPolicy DeliverPolicy { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("opt_start_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long OptStartSeq { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("opt_start_time")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset OptStartTime { get; set; } = default!; + + /// + /// A unique name for a durable consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("durable_name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue, MinimumLength = 1)] + [System.ComponentModel.DataAnnotations.RegularExpression(@"^[^.*>]+$")] + public string DurableName { get; set; } = default!; + + /// + /// A unique name for a consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue, MinimumLength = 1)] + [System.ComponentModel.DataAnnotations.RegularExpression(@"^[^.*>]+$")] + public string Name { get; set; } = default!; + + /// + /// A short description of the purpose of this consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("description")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(4096)] + public string Description { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("deliver_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue, MinimumLength = 1)] + public string DeliverSubject { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("ack_policy")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public ConsumerConfigurationAckPolicy AckPolicy { get; set; } = NATS.Client.JetStream.Models.ConsumerConfigurationAckPolicy.None; + + /// + /// How long (in nanoseconds) to allow messages to remain un-acknowledged before attempting redelivery + /// + [System.Text.Json.Serialization.JsonPropertyName("ack_wait")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long AckWait { get; set; } = default!; + + /// + /// The number of times a message will be redelivered to consumers if not acknowledged in time + /// + [System.Text.Json.Serialization.JsonPropertyName("max_deliver")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxDeliver { get; set; } = default!; + + /// + /// Filter the stream by a single subjects + /// + [System.Text.Json.Serialization.JsonPropertyName("filter_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string FilterSubject { get; set; } = default!; + + /// + /// Filter the stream by multiple subjects + /// + [System.Text.Json.Serialization.JsonPropertyName("filter_subjects")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection FilterSubjects { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("replay_policy")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public ConsumerConfigurationReplayPolicy ReplayPolicy { get; set; } = NATS.Client.JetStream.Models.ConsumerConfigurationReplayPolicy.Instant; + + [System.Text.Json.Serialization.JsonPropertyName("sample_freq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string SampleFreq { get; set; } = default!; + + /// + /// The rate at which messages will be delivered to clients, expressed in bit per second + /// + [System.Text.Json.Serialization.JsonPropertyName("rate_limit_bps")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long RateLimitBps { get; set; } = default!; + + /// + /// The maximum number of messages without acknowledgement that can be outstanding, once this limit is reached message delivery will be suspended + /// + [System.Text.Json.Serialization.JsonPropertyName("max_ack_pending")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxAckPending { get; set; } = default!; + + /// + /// If the Consumer is idle for more than this many nano seconds a empty message with Status header 100 will be sent indicating the consumer is still alive + /// + [System.Text.Json.Serialization.JsonPropertyName("idle_heartbeat")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long IdleHeartbeat { get; set; } = default!; + + /// + /// For push consumers this will regularly send an empty mess with Status header 100 and a reply subject, consumers must reply to these messages to control the rate of message delivery + /// + [System.Text.Json.Serialization.JsonPropertyName("flow_control")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool FlowControl { get; set; } = default!; + + /// + /// The number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored + /// + [System.Text.Json.Serialization.JsonPropertyName("max_waiting")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxWaiting { get; set; } = default!; + + /// + /// Creates a special consumer that does not touch the Raft layers, not for general use by clients, internal use only + /// + [System.Text.Json.Serialization.JsonPropertyName("direct")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Direct { get; set; } = false; + + /// + /// Delivers only the headers of messages in the stream and not the bodies. Additionally adds Nats-Msg-Size header to indicate the size of the removed payload + /// + [System.Text.Json.Serialization.JsonPropertyName("headers_only")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool HeadersOnly { get; set; } = false; + + /// + /// The largest batch property that may be specified when doing a pull on a Pull Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("max_batch")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public int MaxBatch { get; set; } = 0; + + /// + /// The maximum expires value that may be set when doing a pull on a Pull Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("max_expires")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxExpires { get; set; } = default!; + + /// + /// The maximum bytes value that maybe set when dong a pull on a Pull Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("max_bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxBytes { get; set; } = default!; + + /// + /// Duration that instructs the server to cleanup ephemeral consumers that are inactive for that long + /// + [System.Text.Json.Serialization.JsonPropertyName("inactive_threshold")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long InactiveThreshold { get; set; } = default!; + + /// + /// List of durations in Go format that represents a retry time scale for NaK'd messages + /// + [System.Text.Json.Serialization.JsonPropertyName("backoff")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Backoff { get; set; } = default!; + + /// + /// When set do not inherit the replica count from the stream but specifically set it to this amount + /// + [System.Text.Json.Serialization.JsonPropertyName("num_replicas")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumReplicas { get; set; } = default!; + + /// + /// Force the consumer state to be kept in memory rather than inherit the setting from the stream + /// + [System.Text.Json.Serialization.JsonPropertyName("mem_storage")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool MemStorage { get; set; } = false; + + /// + /// Additional metadata for the Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("metadata")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.IDictionary Metadata { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerConfigurationAckPolicy.cs b/src/NATS.Client.JetStream/Models/ConsumerConfigurationAckPolicy.cs new file mode 100644 index 000000000..db26693cf --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerConfigurationAckPolicy.cs @@ -0,0 +1,13 @@ +namespace NATS.Client.JetStream.Models; + +public enum ConsumerConfigurationAckPolicy +{ + [System.Runtime.Serialization.EnumMember(Value = @"none")] + None = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"all")] + All = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"explicit")] + Explicit = 2, +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerConfigurationDeliverPolicy.cs b/src/NATS.Client.JetStream/Models/ConsumerConfigurationDeliverPolicy.cs new file mode 100644 index 000000000..d1f807fab --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerConfigurationDeliverPolicy.cs @@ -0,0 +1,22 @@ +namespace NATS.Client.JetStream.Models; + +public enum ConsumerConfigurationDeliverPolicy +{ + [System.Runtime.Serialization.EnumMember(Value = @"all")] + All = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"last")] + Last = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"new")] + New = 2, + + [System.Runtime.Serialization.EnumMember(Value = @"by_start_sequence")] + ByStartSequence = 3, + + [System.Runtime.Serialization.EnumMember(Value = @"by_start_time")] + ByStartTime = 4, + + [System.Runtime.Serialization.EnumMember(Value = @"last_per_subject")] + LastPerSubject = 5, +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerConfigurationReplayPolicy.cs b/src/NATS.Client.JetStream/Models/ConsumerConfigurationReplayPolicy.cs new file mode 100644 index 000000000..ff6653953 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerConfigurationReplayPolicy.cs @@ -0,0 +1,10 @@ +namespace NATS.Client.JetStream.Models; + +public enum ConsumerConfigurationReplayPolicy +{ + [System.Runtime.Serialization.EnumMember(Value = @"instant")] + Instant = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"original")] + Original = 1, +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerCreateRequest.cs b/src/NATS.Client.JetStream/Models/ConsumerCreateRequest.cs new file mode 100644 index 000000000..f6a84a73f --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerCreateRequest.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.CONSUMER.CREATE and $JS.API.CONSUMER.DURABLE.CREATE APIs +/// + +public record ConsumerCreateRequest +{ + /// + /// The name of the stream to create the consumer in + /// + [System.Text.Json.Serialization.JsonPropertyName("stream_name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string StreamName { get; set; } = default!; + + /// + /// The consumer configuration + /// + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public ConsumerConfiguration Config { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerCreateResponse.cs b/src/NATS.Client.JetStream/Models/ConsumerCreateResponse.cs new file mode 100644 index 000000000..e21579972 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerCreateResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.CONSUMER.CREATE API +/// + +public record ConsumerCreateResponse : ConsumerInfo +{ +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerDeleteResponse.cs b/src/NATS.Client.JetStream/Models/ConsumerDeleteResponse.cs new file mode 100644 index 000000000..067c0b0f0 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerDeleteResponse.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.CONSUMER.DELETE API +/// + +public record ConsumerDeleteResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerGetnextRequest.cs b/src/NATS.Client.JetStream/Models/ConsumerGetnextRequest.cs new file mode 100644 index 000000000..588259513 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerGetnextRequest.cs @@ -0,0 +1,47 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.CONSUMER.MSG.NEXT API +/// + +public record ConsumerGetnextRequest +{ + /// + /// A duration from now when the pull should expire, stated in nanoseconds, 0 for no expiry + /// + [System.Text.Json.Serialization.JsonPropertyName("expires")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long Expires { get; set; } = default!; + + /// + /// How many messages the server should deliver to the requestor + /// + [System.Text.Json.Serialization.JsonPropertyName("batch")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long Batch { get; set; } = default!; + + /// + /// Sends at most this many bytes to the requestor, limited by consumer configuration max_bytes + /// + [System.Text.Json.Serialization.JsonPropertyName("max_bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxBytes { get; set; } = default!; + + /// + /// When true a response with a 404 status header will be returned when no messages are available + /// + [System.Text.Json.Serialization.JsonPropertyName("no_wait")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool NoWait { get; set; } = default!; + + /// + /// When not 0 idle heartbeats will be sent on this interval + /// + [System.Text.Json.Serialization.JsonPropertyName("idle_heartbeat")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long IdleHeartbeat { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerInfo.cs b/src/NATS.Client.JetStream/Models/ConsumerInfo.cs new file mode 100644 index 000000000..b6848f1b1 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerInfo.cs @@ -0,0 +1,98 @@ +namespace NATS.Client.JetStream.Models; + +public record ConsumerInfo +{ + /// + /// The Stream the consumer belongs to + /// + [System.Text.Json.Serialization.JsonPropertyName("stream_name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string StreamName { get; set; } = default!; + + /// + /// A unique name for the consumer, either machine generated or the durable name + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } = default!; + + /// + /// The server time the consumer info was created + /// + [System.Text.Json.Serialization.JsonPropertyName("ts")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset Ts { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public ConsumerConfiguration Config { get; set; } = default!; + + /// + /// The time the Consumer was created + /// + [System.Text.Json.Serialization.JsonPropertyName("created")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public System.DateTimeOffset Created { get; set; } = default!; + + /// + /// The last message delivered from this Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("delivered")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public SequenceInfo Delivered { get; set; } = new SequenceInfo(); + + /// + /// The highest contiguous acknowledged message + /// + [System.Text.Json.Serialization.JsonPropertyName("ack_floor")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public SequenceInfo AckFloor { get; set; } = new SequenceInfo(); + + /// + /// The number of messages pending acknowledgement + /// + [System.Text.Json.Serialization.JsonPropertyName("num_ack_pending")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumAckPending { get; set; } = default!; + + /// + /// The number of redeliveries that have been performed + /// + [System.Text.Json.Serialization.JsonPropertyName("num_redelivered")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumRedelivered { get; set; } = default!; + + /// + /// The number of pull consumers waiting for messages + /// + [System.Text.Json.Serialization.JsonPropertyName("num_waiting")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumWaiting { get; set; } = default!; + + /// + /// The number of messages left unconsumed in this Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("num_pending")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long NumPending { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("cluster")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public ClusterInfo Cluster { get; set; } = default!; + + /// + /// Indicates if any client is connected and receiving messages from a push consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("push_bound")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool PushBound { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerInfoResponse.cs b/src/NATS.Client.JetStream/Models/ConsumerInfoResponse.cs new file mode 100644 index 000000000..32dcbb40a --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerInfoResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.CONSUMER.INFO API +/// + +public record ConsumerInfoResponse : ConsumerInfo +{ +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerLeaderStepdownResponse.cs b/src/NATS.Client.JetStream/Models/ConsumerLeaderStepdownResponse.cs new file mode 100644 index 000000000..dbd0aa4f5 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerLeaderStepdownResponse.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.CONSUMER.LEADER.STEPDOWN API +/// + +public record ConsumerLeaderStepdownResponse +{ + /// + /// If the leader successfully stood down + /// + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerListRequest.cs b/src/NATS.Client.JetStream/Models/ConsumerListRequest.cs new file mode 100644 index 000000000..3b86627b3 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerListRequest.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.CONSUMER.LIST API +/// + +public record ConsumerListRequest : IterableRequest +{ +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerListResponse.cs b/src/NATS.Client.JetStream/Models/ConsumerListResponse.cs new file mode 100644 index 000000000..7261e3bd7 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerListResponse.cs @@ -0,0 +1,16 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.CONSUMER.LIST API +/// + +public record ConsumerListResponse : IterableResponse +{ + /// + /// Full Consumer information for each known Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Consumers { get; set; } = new System.Collections.ObjectModel.Collection(); +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerNamesRequest.cs b/src/NATS.Client.JetStream/Models/ConsumerNamesRequest.cs new file mode 100644 index 000000000..07ca351dc --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerNamesRequest.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.CONSUMER.NAMES API +/// + +public record ConsumerNamesRequest : IterableRequest +{ + /// + /// Filter the names to those consuming messages matching this subject or wildcard + /// + [System.Text.Json.Serialization.JsonPropertyName("subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Subject { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/ConsumerNamesResponse.cs b/src/NATS.Client.JetStream/Models/ConsumerNamesResponse.cs new file mode 100644 index 000000000..70beb4ac2 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ConsumerNamesResponse.cs @@ -0,0 +1,13 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.CONSUMER.NAMES API +/// + +public record ConsumerNamesResponse : IterableResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Consumers { get; set; } = new System.Collections.ObjectModel.Collection(); +} diff --git a/src/NATS.Client.JetStream/Models/ErrorResponse.cs b/src/NATS.Client.JetStream/Models/ErrorResponse.cs new file mode 100644 index 000000000..71b3b1e65 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ErrorResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +public record ErrorResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("error")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public ApiError Error { get; set; } = new ApiError(); +} diff --git a/src/NATS.Client.JetStream/Models/ExternalStreamSource.cs b/src/NATS.Client.JetStream/Models/ExternalStreamSource.cs new file mode 100644 index 000000000..62b94ddf2 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/ExternalStreamSource.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Configuration referencing a stream source in another account or JetStream domain +/// + +public record ExternalStreamSource +{ + /// + /// The subject prefix that imports the other account/domain $JS.API.CONSUMER.> subjects + /// + [System.Text.Json.Serialization.JsonPropertyName("api")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Api { get; set; } = default!; + + /// + /// The delivery subject to use for the push consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("deliver")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Deliver { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/IterableRequest.cs b/src/NATS.Client.JetStream/Models/IterableRequest.cs new file mode 100644 index 000000000..3e8296388 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/IterableRequest.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +public record IterableRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("offset")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Offset { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/IterableResponse.cs b/src/NATS.Client.JetStream/Models/IterableResponse.cs new file mode 100644 index 000000000..ef2c3f660 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/IterableResponse.cs @@ -0,0 +1,19 @@ +namespace NATS.Client.JetStream.Models; + +public record IterableResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("total")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Total { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("offset")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Offset { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("limit")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Limit { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/LostStreamData.cs b/src/NATS.Client.JetStream/Models/LostStreamData.cs new file mode 100644 index 000000000..af4910452 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/LostStreamData.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Records messages that were damaged and unrecoverable +/// + +public record LostStreamData +{ + /// + /// The messages that were lost + /// + [System.Text.Json.Serialization.JsonPropertyName("msgs")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection? Msgs { get; set; } = default!; + + /// + /// The number of bytes that were lost + /// + [System.Text.Json.Serialization.JsonPropertyName("bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Bytes { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/MetaLeaderStepdownRequest.cs b/src/NATS.Client.JetStream/Models/MetaLeaderStepdownRequest.cs new file mode 100644 index 000000000..6a0bbc7b5 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/MetaLeaderStepdownRequest.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.META.LEADER.STEPDOWN API +/// + +public record MetaLeaderStepdownRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("placement")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public Placement Placement { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/MetaLeaderStepdownResponse.cs b/src/NATS.Client.JetStream/Models/MetaLeaderStepdownResponse.cs new file mode 100644 index 000000000..ca795219e --- /dev/null +++ b/src/NATS.Client.JetStream/Models/MetaLeaderStepdownResponse.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.META.LEADER.STEPDOWN API +/// + +public record MetaLeaderStepdownResponse +{ + /// + /// If the leader successfully stood down + /// + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/MetaServerRemoveRequest.cs b/src/NATS.Client.JetStream/Models/MetaServerRemoveRequest.cs new file mode 100644 index 000000000..a61acd89c --- /dev/null +++ b/src/NATS.Client.JetStream/Models/MetaServerRemoveRequest.cs @@ -0,0 +1,22 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.SERVER.REMOVE API +/// + +public record MetaServerRemoveRequest +{ + /// + /// The Name of the server to remove from the meta group + /// + [System.Text.Json.Serialization.JsonPropertyName("peer")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Peer { get; set; } = default!; + + /// + /// Peer ID of the peer to be removed. If specified this is used instead of the server name + /// + [System.Text.Json.Serialization.JsonPropertyName("peer_id")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string PeerId { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/MetaServerRemoveResponse.cs b/src/NATS.Client.JetStream/Models/MetaServerRemoveResponse.cs new file mode 100644 index 000000000..ca176a4f4 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/MetaServerRemoveResponse.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.SERVER.REMOVE API +/// + +public record MetaServerRemoveResponse +{ + /// + /// If the peer was successfully removed + /// + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/PeerInfo.cs b/src/NATS.Client.JetStream/Models/PeerInfo.cs new file mode 100644 index 000000000..896832f82 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/PeerInfo.cs @@ -0,0 +1,41 @@ +namespace NATS.Client.JetStream.Models; + +public record PeerInfo +{ + /// + /// The server name of the peer + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } = default!; + + /// + /// Indicates if the server is up to date and synchronised + /// + [System.Text.Json.Serialization.JsonPropertyName("current")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Current { get; set; } = false; + + /// + /// Nanoseconds since this peer was last seen + /// + [System.Text.Json.Serialization.JsonPropertyName("active")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public double Active { get; set; } = default!; + + /// + /// Indicates the node is considered offline by the group + /// + [System.Text.Json.Serialization.JsonPropertyName("offline")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Offline { get; set; } = false; + + /// + /// How many uncommitted operations this peer is behind the leader + /// + [System.Text.Json.Serialization.JsonPropertyName("lag")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Lag { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/Placement.cs b/src/NATS.Client.JetStream/Models/Placement.cs new file mode 100644 index 000000000..6ce0f3d9e --- /dev/null +++ b/src/NATS.Client.JetStream/Models/Placement.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Placement requirements for a stream +/// + +public record Placement +{ + /// + /// The desired cluster name to place the stream + /// + [System.Text.Json.Serialization.JsonPropertyName("cluster")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Cluster { get; set; } = default!; + + /// + /// Tags required on servers hosting this stream + /// + [System.Text.Json.Serialization.JsonPropertyName("tags")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Tags { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/PubAckResponse.cs b/src/NATS.Client.JetStream/Models/PubAckResponse.cs new file mode 100644 index 000000000..17fc37019 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/PubAckResponse.cs @@ -0,0 +1,42 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response received when publishing a message +/// + +public record PubAckResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("error")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public ApiError Error { get; set; } = default!; + + /// + /// The name of the stream that received the message + /// + [System.Text.Json.Serialization.JsonPropertyName("stream")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public string Stream { get; set; } = default!; + + /// + /// If successful this will be the sequence the message is stored at + /// + [System.Text.Json.Serialization.JsonPropertyName("seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Seq { get; set; } = default!; + + /// + /// Indicates that the message was not stored due to the Nats-Msg-Id header and duplicate tracking + /// + [System.Text.Json.Serialization.JsonPropertyName("duplicate")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Duplicate { get; set; } = false; + + /// + /// If the Stream accepting the message is in a JetStream server configured for a domain this would be that domain + /// + [System.Text.Json.Serialization.JsonPropertyName("domain")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Domain { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/Republish.cs b/src/NATS.Client.JetStream/Models/Republish.cs new file mode 100644 index 000000000..53276e08e --- /dev/null +++ b/src/NATS.Client.JetStream/Models/Republish.cs @@ -0,0 +1,31 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Rules for republishing messages from a stream with subject mapping onto new subjects for partitioning and more +/// + +public record Republish +{ + /// + /// The source subject to republish + /// + [System.Text.Json.Serialization.JsonPropertyName("src")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Src { get; set; } = default!; + + /// + /// The destination to publish to + /// + [System.Text.Json.Serialization.JsonPropertyName("dest")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Dest { get; set; } = default!; + + /// + /// Only send message headers, no bodies + /// + [System.Text.Json.Serialization.JsonPropertyName("headers_only")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool HeadersOnly { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/SequenceInfo.cs b/src/NATS.Client.JetStream/Models/SequenceInfo.cs new file mode 100644 index 000000000..08f0e01f8 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/SequenceInfo.cs @@ -0,0 +1,27 @@ +namespace NATS.Client.JetStream.Models; + +public record SequenceInfo +{ + /// + /// The sequence number of the Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("consumer_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long ConsumerSeq { get; set; } = default!; + + /// + /// The sequence number of the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("stream_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long StreamSeq { get; set; } = default!; + + /// + /// The last time a message was delivered or acknowledged (for ack_floor) + /// + [System.Text.Json.Serialization.JsonPropertyName("last_active")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset LastActive { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/SequencePair.cs b/src/NATS.Client.JetStream/Models/SequencePair.cs new file mode 100644 index 000000000..8e25ad615 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/SequencePair.cs @@ -0,0 +1,20 @@ +namespace NATS.Client.JetStream.Models; + +public record SequencePair +{ + /// + /// The sequence number of the Consumer + /// + [System.Text.Json.Serialization.JsonPropertyName("consumer_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long ConsumerSeq { get; set; } = default!; + + /// + /// The sequence number of the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("stream_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long StreamSeq { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StoredMessage.cs b/src/NATS.Client.JetStream/Models/StoredMessage.cs new file mode 100644 index 000000000..296a14e90 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StoredMessage.cs @@ -0,0 +1,43 @@ +namespace NATS.Client.JetStream.Models; + +public record StoredMessage +{ + /// + /// The subject the message was originally received on + /// + [System.Text.Json.Serialization.JsonPropertyName("subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public string Subject { get; set; } = default!; + + /// + /// The sequence number of the message in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Seq { get; set; } = default!; + + /// + /// The base64 encoded payload of the message body + /// + [System.Text.Json.Serialization.JsonPropertyName("data")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue)] + public string Data { get; set; } = default!; + + /// + /// The time the message was received + /// + [System.Text.Json.Serialization.JsonPropertyName("time")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Time { get; set; } = default!; + + /// + /// Base64 encoded headers for the message + /// + [System.Text.Json.Serialization.JsonPropertyName("hdrs")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Hdrs { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamAlternate.cs b/src/NATS.Client.JetStream/Models/StreamAlternate.cs new file mode 100644 index 000000000..5e9698e1b --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamAlternate.cs @@ -0,0 +1,31 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// An alternate location to read mirrored data +/// + +public record StreamAlternate +{ + /// + /// The mirror stream name + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } = default!; + + /// + /// The name of the cluster holding the stream + /// + [System.Text.Json.Serialization.JsonPropertyName("cluster")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Cluster { get; set; } = default!; + + /// + /// The domain holding the string + /// + [System.Text.Json.Serialization.JsonPropertyName("domain")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Domain { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamConfiguration.cs b/src/NATS.Client.JetStream/Models/StreamConfiguration.cs new file mode 100644 index 000000000..ea4b50640 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamConfiguration.cs @@ -0,0 +1,228 @@ +namespace NATS.Client.JetStream.Models; + +public record StreamConfiguration +{ + /// + /// A unique name for the Stream, empty for Stream Templates. + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue)] + [System.ComponentModel.DataAnnotations.RegularExpression(@"^[^.*>]*$")] + public string Name { get; set; } = default!; + + /// + /// A short description of the purpose of this stream + /// + [System.Text.Json.Serialization.JsonPropertyName("description")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.StringLength(4096)] + public string Description { get; set; } = default!; + + /// + /// A list of subjects to consume, supports wildcards. Must be empty when a mirror is configured. May be empty when sources are configured. + /// + [System.Text.Json.Serialization.JsonPropertyName("subjects")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Subjects { get; set; } = default!; + + /// + /// Subject transform to apply to matching messages + /// + [System.Text.Json.Serialization.JsonPropertyName("subject_transform")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public SubjectTransform SubjectTransform { get; set; } = default!; + + /// + /// How messages are retained in the Stream, once this is exceeded old messages are removed. + /// + [System.Text.Json.Serialization.JsonPropertyName("retention")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public StreamConfigurationRetention Retention { get; set; } = NATS.Client.JetStream.Models.StreamConfigurationRetention.Limits; + + /// + /// How many Consumers can be defined for a given Stream. -1 for unlimited. + /// + [System.Text.Json.Serialization.JsonPropertyName("max_consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxConsumers { get; set; } = default!; + + /// + /// How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited. + /// + [System.Text.Json.Serialization.JsonPropertyName("max_msgs")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxMsgs { get; set; } = default!; + + /// + /// For wildcard streams ensure that for every unique subject this many messages are kept - a per subject retention limit + /// + [System.Text.Json.Serialization.JsonPropertyName("max_msgs_per_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxMsgsPerSubject { get; set; } = default!; + + /// + /// How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited. + /// + [System.Text.Json.Serialization.JsonPropertyName("max_bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxBytes { get; set; } = default!; + + /// + /// Maximum age of any message in the stream, expressed in nanoseconds. 0 for unlimited. + /// + [System.Text.Json.Serialization.JsonPropertyName("max_age")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long MaxAge { get; set; } = default!; + + /// + /// The largest message that will be accepted by the Stream. -1 for unlimited. + /// + [System.Text.Json.Serialization.JsonPropertyName("max_msg_size")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-2147483648, 2147483647)] + public int MaxMsgSize { get; set; } = default!; + + /// + /// The storage backend to use for the Stream. + /// + [System.Text.Json.Serialization.JsonPropertyName("storage")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public StreamConfigurationStorage Storage { get; set; } = NATS.Client.JetStream.Models.StreamConfigurationStorage.File; + + /// + /// Optional compression algorithm used for the Stream. + /// + [System.Text.Json.Serialization.JsonPropertyName("compression")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public StreamConfigurationCompression Compression { get; set; } = NATS.Client.JetStream.Models.StreamConfigurationCompression.None; + + /// + /// How many replicas to keep for each message. + /// + [System.Text.Json.Serialization.JsonPropertyName("num_replicas")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumReplicas { get; set; } = default!; + + /// + /// Disables acknowledging messages that are received by the Stream. + /// + [System.Text.Json.Serialization.JsonPropertyName("no_ack")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool NoAck { get; set; } = false; + + /// + /// When the Stream is managed by a Stream Template this identifies the template that manages the Stream. + /// + [System.Text.Json.Serialization.JsonPropertyName("template_owner")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string TemplateOwner { get; set; } = default!; + + /// + /// When a Stream reach it's limits either old messages are deleted or new ones are denied + /// + [System.Text.Json.Serialization.JsonPropertyName("discard")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public StreamConfigurationDiscard Discard { get; set; } = NATS.Client.JetStream.Models.StreamConfigurationDiscard.Old; + + /// + /// The time window to track duplicate messages for, expressed in nanoseconds. 0 for default + /// + [System.Text.Json.Serialization.JsonPropertyName("duplicate_window")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long DuplicateWindow { get; set; } = default!; + + /// + /// Placement directives to consider when placing replicas of this stream, random placement when unset + /// + [System.Text.Json.Serialization.JsonPropertyName("placement")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public Placement Placement { get; set; } = default!; + + /// + /// Maintains a 1:1 mirror of another stream with name matching this property. When a mirror is configured subjects and sources must be empty. + /// + [System.Text.Json.Serialization.JsonPropertyName("mirror")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public StreamSource Mirror { get; set; } = default!; + + /// + /// List of Stream names to replicate into this Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("sources")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Sources { get; set; } = default!; + + /// + /// Sealed streams do not allow messages to be deleted via limits or API, sealed streams can not be unsealed via configuration update. Can only be set on already created streams via the Update API + /// + [System.Text.Json.Serialization.JsonPropertyName("sealed")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Sealed { get; set; } = false; + + /// + /// Restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true + /// + [System.Text.Json.Serialization.JsonPropertyName("deny_delete")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool DenyDelete { get; set; } = false; + + /// + /// Restricts the ability to purge messages from a stream via the API. Cannot be change once set to true + /// + [System.Text.Json.Serialization.JsonPropertyName("deny_purge")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool DenyPurge { get; set; } = false; + + /// + /// Allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message + /// + [System.Text.Json.Serialization.JsonPropertyName("allow_rollup_hdrs")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool AllowRollupHdrs { get; set; } = false; + + /// + /// Allow higher performance, direct access to get individual messages + /// + [System.Text.Json.Serialization.JsonPropertyName("allow_direct")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool AllowDirect { get; set; } = false; + + /// + /// Allow higher performance, direct access for mirrors as well + /// + [System.Text.Json.Serialization.JsonPropertyName("mirror_direct")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool MirrorDirect { get; set; } = false; + + [System.Text.Json.Serialization.JsonPropertyName("republish")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public Republish Republish { get; set; } = default!; + + /// + /// When discard policy is new and the stream is one with max messages per subject set, this will apply the new behavior to every subject. Essentially turning discard new from maximum number of subjects into maximum number of messages in a subject. + /// + [System.Text.Json.Serialization.JsonPropertyName("discard_new_per_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool DiscardNewPerSubject { get; set; } = false; + + /// + /// Additional metadata for the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("metadata")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.IDictionary Metadata { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamConfigurationCompression.cs b/src/NATS.Client.JetStream/Models/StreamConfigurationCompression.cs new file mode 100644 index 000000000..3abbbbc7c --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamConfigurationCompression.cs @@ -0,0 +1,10 @@ +namespace NATS.Client.JetStream.Models; + +public enum StreamConfigurationCompression +{ + [System.Runtime.Serialization.EnumMember(Value = @"none")] + None = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"s2")] + S2 = 1, +} diff --git a/src/NATS.Client.JetStream/Models/StreamConfigurationDiscard.cs b/src/NATS.Client.JetStream/Models/StreamConfigurationDiscard.cs new file mode 100644 index 000000000..a9f7159cb --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamConfigurationDiscard.cs @@ -0,0 +1,10 @@ +namespace NATS.Client.JetStream.Models; + +public enum StreamConfigurationDiscard +{ + [System.Runtime.Serialization.EnumMember(Value = @"old")] + Old = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"new")] + New = 1, +} diff --git a/src/NATS.Client.JetStream/Models/StreamConfigurationRetention.cs b/src/NATS.Client.JetStream/Models/StreamConfigurationRetention.cs new file mode 100644 index 000000000..141992c40 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamConfigurationRetention.cs @@ -0,0 +1,13 @@ +namespace NATS.Client.JetStream.Models; + +public enum StreamConfigurationRetention +{ + [System.Runtime.Serialization.EnumMember(Value = @"limits")] + Limits = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"interest")] + Interest = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"workqueue")] + Workqueue = 2, +} diff --git a/src/NATS.Client.JetStream/Models/StreamConfigurationStorage.cs b/src/NATS.Client.JetStream/Models/StreamConfigurationStorage.cs new file mode 100644 index 000000000..b326b5bec --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamConfigurationStorage.cs @@ -0,0 +1,10 @@ +namespace NATS.Client.JetStream.Models; + +public enum StreamConfigurationStorage +{ + [System.Runtime.Serialization.EnumMember(Value = @"file")] + File = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"memory")] + Memory = 1, +} diff --git a/src/NATS.Client.JetStream/Models/StreamCreateRequest.cs b/src/NATS.Client.JetStream/Models/StreamCreateRequest.cs new file mode 100644 index 000000000..40d3c53b1 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamCreateRequest.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.CREATE API +/// + +public record StreamCreateRequest : StreamConfiguration +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamCreateResponse.cs b/src/NATS.Client.JetStream/Models/StreamCreateResponse.cs new file mode 100644 index 000000000..9ab475b19 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamCreateResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.CREATE API +/// + +public record StreamCreateResponse : StreamInfo +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamDeleteResponse.cs b/src/NATS.Client.JetStream/Models/StreamDeleteResponse.cs new file mode 100644 index 000000000..c7687a56b --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamDeleteResponse.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.DELETE API +/// + +public record StreamDeleteResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamInfo.cs b/src/NATS.Client.JetStream/Models/StreamInfo.cs new file mode 100644 index 000000000..885b29be4 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamInfo.cs @@ -0,0 +1,57 @@ +namespace NATS.Client.JetStream.Models; + +public record StreamInfo +{ + /// + /// The active configuration for the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamConfiguration Config { get; set; } = new StreamConfiguration(); + + /// + /// Detail about the current State of the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("state")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamState State { get; set; } = new StreamState(); + + /// + /// Timestamp when the stream was created + /// + [System.Text.Json.Serialization.JsonPropertyName("created")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public System.DateTimeOffset Created { get; set; } = default!; + + /// + /// The server time the stream info was created + /// + [System.Text.Json.Serialization.JsonPropertyName("ts")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset Ts { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("cluster")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public ClusterInfo Cluster { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("mirror")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public StreamSourceInfo Mirror { get; set; } = default!; + + /// + /// Streams being sourced into this Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("sources")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Sources { get; set; } = default!; + + /// + /// List of mirrors sorted by priority + /// + [System.Text.Json.Serialization.JsonPropertyName("alternates")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Alternates { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamInfoRequest.cs b/src/NATS.Client.JetStream/Models/StreamInfoRequest.cs new file mode 100644 index 000000000..35958c167 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamInfoRequest.cs @@ -0,0 +1,30 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.INFO API +/// + +public record StreamInfoRequest +{ + /// + /// When true will result in a full list of deleted message IDs being returned in the info response + /// + [System.Text.Json.Serialization.JsonPropertyName("deleted_details")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool DeletedDetails { get; set; } = default!; + + /// + /// When set will return a list of subjects and how many messages they hold for all matching subjects. Filter is a standard NATS subject wildcard pattern. + /// + [System.Text.Json.Serialization.JsonPropertyName("subjects_filter")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string SubjectsFilter { get; set; } = default!; + + /// + /// Paging offset when retrieving pages of subjet details + /// + [System.Text.Json.Serialization.JsonPropertyName("offset")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Offset { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamInfoResponse.cs b/src/NATS.Client.JetStream/Models/StreamInfoResponse.cs new file mode 100644 index 000000000..da3342c79 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamInfoResponse.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.INFO API +/// + +public record StreamInfoResponse : StreamInfo +{ + [System.Text.Json.Serialization.JsonPropertyName("total")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Total { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("offset")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Offset { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("limit")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Limit { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamLeaderStepdownResponse.cs b/src/NATS.Client.JetStream/Models/StreamLeaderStepdownResponse.cs new file mode 100644 index 000000000..3dc786726 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamLeaderStepdownResponse.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.LEADER.STEPDOWN API +/// + +public record StreamLeaderStepdownResponse +{ + /// + /// If the leader successfully stood down + /// + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/StreamListRequest.cs b/src/NATS.Client.JetStream/Models/StreamListRequest.cs new file mode 100644 index 000000000..5b3976fa5 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamListRequest.cs @@ -0,0 +1,20 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.LIST API +/// + +public record StreamListRequest +{ + /// + /// Limit the list to streams matching this subject filter + /// + [System.Text.Json.Serialization.JsonPropertyName("subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Subject { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("offset")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Offset { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamListResponse.cs b/src/NATS.Client.JetStream/Models/StreamListResponse.cs new file mode 100644 index 000000000..297c4767b --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamListResponse.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.LIST API +/// + +public record StreamListResponse : IterableResponse +{ + /// + /// Full Stream information for each known Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("streams")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Streams { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// + /// In clustered environments gathering Stream info might time out, this list would be a list of Streams for which information was not obtainable + /// + [System.Text.Json.Serialization.JsonPropertyName("missing")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Missing { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamMsgDeleteRequest.cs b/src/NATS.Client.JetStream/Models/StreamMsgDeleteRequest.cs new file mode 100644 index 000000000..27fba40cd --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamMsgDeleteRequest.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.MSG.DELETE API +/// + +public record StreamMsgDeleteRequest +{ + /// + /// Stream sequence number of the message to delete + /// + [System.Text.Json.Serialization.JsonPropertyName("seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Seq { get; set; } = default!; + + /// + /// Default will securely remove a message and rewrite the data with random data, set this to true to only remove the message + /// + [System.Text.Json.Serialization.JsonPropertyName("no_erase")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool NoErase { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamMsgDeleteResponse.cs b/src/NATS.Client.JetStream/Models/StreamMsgDeleteResponse.cs new file mode 100644 index 000000000..a616ac796 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamMsgDeleteResponse.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.MSG.DELETE API +/// + +public record StreamMsgDeleteResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamMsgGetRequest.cs b/src/NATS.Client.JetStream/Models/StreamMsgGetRequest.cs new file mode 100644 index 000000000..ebea7845a --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamMsgGetRequest.cs @@ -0,0 +1,29 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.MSG.GET API +/// + +public record StreamMsgGetRequest +{ + /// + /// Stream sequence number of the message to retrieve, cannot be combined with last_by_subj + /// + [System.Text.Json.Serialization.JsonPropertyName("seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public int Seq { get; set; } = default!; + + /// + /// Retrieves the last message for a given subject, cannot be combined with seq + /// + [System.Text.Json.Serialization.JsonPropertyName("last_by_subj")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string LastBySubj { get; set; } = default!; + + /// + /// Combined with sequence gets the next message for a subject with the given sequence or higher + /// + [System.Text.Json.Serialization.JsonPropertyName("next_by_subj")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string NextBySubj { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamMsgGetResponse.cs b/src/NATS.Client.JetStream/Models/StreamMsgGetResponse.cs new file mode 100644 index 000000000..66c0073b5 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamMsgGetResponse.cs @@ -0,0 +1,13 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.MSG.GET API +/// + +public record StreamMsgGetResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StoredMessage Message { get; set; } = new StoredMessage(); +} diff --git a/src/NATS.Client.JetStream/Models/StreamNamesRequest.cs b/src/NATS.Client.JetStream/Models/StreamNamesRequest.cs new file mode 100644 index 000000000..ebf2d6223 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamNamesRequest.cs @@ -0,0 +1,20 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.NAMES API +/// + +public record StreamNamesRequest +{ + /// + /// Limit the list to streams matching this subject filter + /// + [System.Text.Json.Serialization.JsonPropertyName("subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Subject { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("offset")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Offset { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamNamesResponse.cs b/src/NATS.Client.JetStream/Models/StreamNamesResponse.cs new file mode 100644 index 000000000..d8bc87fc5 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamNamesResponse.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.NAMES API +/// + +public record StreamNamesResponse : IterableResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Consumers { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamPurgeRequest.cs b/src/NATS.Client.JetStream/Models/StreamPurgeRequest.cs new file mode 100644 index 000000000..bd1f37303 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamPurgeRequest.cs @@ -0,0 +1,31 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.PURGE API +/// + +public record StreamPurgeRequest +{ + /// + /// Restrict purging to messages that match this subject + /// + [System.Text.Json.Serialization.JsonPropertyName("filter")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Filter { get; set; } = default!; + + /// + /// Purge all messages up to but not including the message with this sequence. Can be combined with subject filter but not the keep option + /// + [System.Text.Json.Serialization.JsonPropertyName("seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Seq { get; set; } = default!; + + /// + /// Ensures this many messages are present after the purge. Can be combined with the subject filter but not the sequence + /// + [System.Text.Json.Serialization.JsonPropertyName("keep")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Keep { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamPurgeResponse.cs b/src/NATS.Client.JetStream/Models/StreamPurgeResponse.cs new file mode 100644 index 000000000..f7f36c5d7 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamPurgeResponse.cs @@ -0,0 +1,20 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.PURGE API +/// + +public record StreamPurgeResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = default!; + + /// + /// Number of messages purged from the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("purged")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Purged { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamRemovePeerRequest.cs b/src/NATS.Client.JetStream/Models/StreamRemovePeerRequest.cs new file mode 100644 index 000000000..f83345aa0 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamRemovePeerRequest.cs @@ -0,0 +1,16 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.PEER.REMOVE API +/// + +public record StreamRemovePeerRequest +{ + /// + /// Server name of the peer to remove + /// + [System.Text.Json.Serialization.JsonPropertyName("peer")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public string Peer { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamRemovePeerResponse.cs b/src/NATS.Client.JetStream/Models/StreamRemovePeerResponse.cs new file mode 100644 index 000000000..c7a9094e6 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamRemovePeerResponse.cs @@ -0,0 +1,15 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.PEER.REMOVE API +/// + +public record StreamRemovePeerResponse +{ + /// + /// If the peer was successfully removed + /// + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/StreamRestoreRequest.cs b/src/NATS.Client.JetStream/Models/StreamRestoreRequest.cs new file mode 100644 index 000000000..eee273909 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamRestoreRequest.cs @@ -0,0 +1,18 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.RESTORE API +/// + +public record StreamRestoreRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamConfiguration Config { get; set; } = new StreamConfiguration(); + + [System.Text.Json.Serialization.JsonPropertyName("state")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamState State { get; set; } = new StreamState(); +} diff --git a/src/NATS.Client.JetStream/Models/StreamRestoreResponse.cs b/src/NATS.Client.JetStream/Models/StreamRestoreResponse.cs new file mode 100644 index 000000000..860af8f1a --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamRestoreResponse.cs @@ -0,0 +1,16 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.RESTORE API +/// + +public record StreamRestoreResponse +{ + /// + /// The Subject to send restore chunks to + /// + [System.Text.Json.Serialization.JsonPropertyName("deliver_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public string DeliverSubject { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamSnapshotRequest.cs b/src/NATS.Client.JetStream/Models/StreamSnapshotRequest.cs new file mode 100644 index 000000000..2f693faf9 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamSnapshotRequest.cs @@ -0,0 +1,38 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.SNAPSHOT API +/// + +public record StreamSnapshotRequest +{ + /// + /// The NATS subject where the snapshot will be delivered + /// + [System.Text.Json.Serialization.JsonPropertyName("deliver_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public string DeliverSubject { get; set; } = default!; + + /// + /// When true consumer states and configurations will not be present in the snapshot + /// + [System.Text.Json.Serialization.JsonPropertyName("no_consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool NoConsumers { get; set; } = default!; + + /// + /// The size of data chunks to send to deliver_subject + /// + [System.Text.Json.Serialization.JsonPropertyName("chunk_size")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long ChunkSize { get; set; } = default!; + + /// + /// Check all message's checksums prior to snapshot + /// + [System.Text.Json.Serialization.JsonPropertyName("jsck")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Jsck { get; set; } = false; +} diff --git a/src/NATS.Client.JetStream/Models/StreamSnapshotResponse.cs b/src/NATS.Client.JetStream/Models/StreamSnapshotResponse.cs new file mode 100644 index 000000000..2e31a5fed --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamSnapshotResponse.cs @@ -0,0 +1,18 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.SNAPSHOT API +/// + +public record StreamSnapshotResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamConfiguration Config { get; set; } = new StreamConfiguration(); + + [System.Text.Json.Serialization.JsonPropertyName("state")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamState State { get; set; } = new StreamState(); +} diff --git a/src/NATS.Client.JetStream/Models/StreamSource.cs b/src/NATS.Client.JetStream/Models/StreamSource.cs new file mode 100644 index 000000000..5c84a38da --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamSource.cs @@ -0,0 +1,51 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Defines a source where streams should be replicated from +/// + +public record StreamSource +{ + /// + /// Stream name + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue, MinimumLength = 1)] + [System.ComponentModel.DataAnnotations.RegularExpression(@"^[^.*>]+$")] + public string Name { get; set; } = default!; + + /// + /// Sequence to start replicating from + /// + [System.Text.Json.Serialization.JsonPropertyName("opt_start_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long OptStartSeq { get; set; } = default!; + + /// + /// Time stamp to start replicating from + /// + [System.Text.Json.Serialization.JsonPropertyName("opt_start_time")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset OptStartTime { get; set; } = default!; + + /// + /// Replicate only a subset of messages based on filter + /// + [System.Text.Json.Serialization.JsonPropertyName("filter_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string FilterSubject { get; set; } = default!; + + /// + /// Map matching subjects according to this transform destination + /// + [System.Text.Json.Serialization.JsonPropertyName("subject_transform_dest")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string SubjectTransformDest { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("external")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public ExternalStreamSource External { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamSourceInfo.cs b/src/NATS.Client.JetStream/Models/StreamSourceInfo.cs new file mode 100644 index 000000000..e2048e0ab --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamSourceInfo.cs @@ -0,0 +1,54 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Information about an upstream stream source in a mirror +/// + +public record StreamSourceInfo +{ + /// + /// The name of the Stream being replicated + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } = default!; + + /// + /// The subject filter to apply to the messages + /// + [System.Text.Json.Serialization.JsonPropertyName("filter_subject")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string FilterSubject { get; set; } = default!; + + /// + /// The subject transform destination to apply to the messages + /// + [System.Text.Json.Serialization.JsonPropertyName("subject_transform_dest")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string SubjectTransformDest { get; set; } = default!; + + /// + /// How many messages behind the mirror operation is + /// + [System.Text.Json.Serialization.JsonPropertyName("lag")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Lag { get; set; } = default!; + + /// + /// When last the mirror had activity, in nanoseconds. Value will be -1 when there has been no activity. + /// + [System.Text.Json.Serialization.JsonPropertyName("active")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long Active { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("external")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public ExternalStreamSource External { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("error")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public ApiError Error { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamState.cs b/src/NATS.Client.JetStream/Models/StreamState.cs new file mode 100644 index 000000000..0dea24174 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamState.cs @@ -0,0 +1,92 @@ +namespace NATS.Client.JetStream.Models; + +public record StreamState +{ + /// + /// Number of messages stored in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("messages")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Messages { get; set; } = default!; + + /// + /// Combined size of all messages in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("bytes")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long Bytes { get; set; } = default!; + + /// + /// Sequence number of the first message in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("first_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long FirstSeq { get; set; } = default!; + + /// + /// The timestamp of the first message in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("first_ts")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string FirstTs { get; set; } = default!; + + /// + /// Sequence number of the last message in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("last_seq")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0D, 18446744073709552000D)] + public long LastSeq { get; set; } = default!; + + /// + /// The timestamp of the last message in the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("last_ts")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string LastTs { get; set; } = default!; + + /// + /// IDs of messages that were deleted using the Message Delete API or Interest based streams removing messages out of order + /// + [System.Text.Json.Serialization.JsonPropertyName("deleted")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Deleted { get; set; } = default!; + + /// + /// Subjects and their message counts when a subjects_filter was set + /// + [System.Text.Json.Serialization.JsonPropertyName("subjects")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.IDictionary Subjects { get; set; } = default!; + + /// + /// The number of unique subjects held in the stream + /// + [System.Text.Json.Serialization.JsonPropertyName("num_subjects")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumSubjects { get; set; } = default!; + + /// + /// The number of deleted messages + /// + [System.Text.Json.Serialization.JsonPropertyName("num_deleted")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long NumDeleted { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("lost")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public LostStreamData Lost { get; set; } = default!; + + /// + /// Number of Consumers attached to the Stream + /// + [System.Text.Json.Serialization.JsonPropertyName("consumer_count")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-9223372036854776000D, 9223372036854776000D)] + public long ConsumerCount { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateConfiguration.cs b/src/NATS.Client.JetStream/Models/StreamTemplateConfiguration.cs new file mode 100644 index 000000000..8a9a7feee --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateConfiguration.cs @@ -0,0 +1,31 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// The data structure that describe the configuration of a NATS JetStream Stream Template +/// + +public record StreamTemplateConfiguration +{ + /// + /// A unique name for the Stream Template. + /// + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [System.ComponentModel.DataAnnotations.StringLength(int.MaxValue, MinimumLength = 1)] + [System.ComponentModel.DataAnnotations.RegularExpression(@"^[^.*>]+$")] + public string Name { get; set; } = default!; + + /// + /// The maximum number of Streams this Template can create, -1 for unlimited. + /// + [System.Text.Json.Serialization.JsonPropertyName("max_streams")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(-2147483648, 2147483647)] + public int MaxStreams { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamConfiguration Config { get; set; } = new StreamConfiguration(); +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateCreateRequest.cs b/src/NATS.Client.JetStream/Models/StreamTemplateCreateRequest.cs new file mode 100644 index 000000000..a960454ab --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateCreateRequest.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.TEMPLATE.CREATE API +/// + +public record StreamTemplateCreateRequest : StreamTemplateConfiguration +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateCreateResponse.cs b/src/NATS.Client.JetStream/Models/StreamTemplateCreateResponse.cs new file mode 100644 index 000000000..a1cc9dc9f --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateCreateResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.TEMPLATE.CREATE API +/// + +public record StreamTemplateCreateResponse : StreamTemplateInfo +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateDeleteResponse.cs b/src/NATS.Client.JetStream/Models/StreamTemplateDeleteResponse.cs new file mode 100644 index 000000000..a6eec13ef --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateDeleteResponse.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.TEMPLATE.DELETE API +/// + +public record StreamTemplateDeleteResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("success")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + public bool Success { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateInfo.cs b/src/NATS.Client.JetStream/Models/StreamTemplateInfo.cs new file mode 100644 index 000000000..9c0b8d1cd --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateInfo.cs @@ -0,0 +1,17 @@ +namespace NATS.Client.JetStream.Models; + +public record StreamTemplateInfo +{ + [System.Text.Json.Serialization.JsonPropertyName("config")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public StreamTemplateConfiguration Config { get; set; } = new StreamTemplateConfiguration(); + + /// + /// List of Streams managed by this Template + /// + [System.Text.Json.Serialization.JsonPropertyName("streams")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Streams { get; set; } = new System.Collections.ObjectModel.Collection(); +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateInfoResponse.cs b/src/NATS.Client.JetStream/Models/StreamTemplateInfoResponse.cs new file mode 100644 index 000000000..c04c35466 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateInfoResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.TEMPLATE.INFO API +/// + +public record StreamTemplateInfoResponse : StreamTemplateInfo +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateNamesRequest.cs b/src/NATS.Client.JetStream/Models/StreamTemplateNamesRequest.cs new file mode 100644 index 000000000..25e44c108 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateNamesRequest.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.CONSUMER.LIST API +/// + +public record StreamTemplateNamesRequest : IterableRequest +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamTemplateNamesResponse.cs b/src/NATS.Client.JetStream/Models/StreamTemplateNamesResponse.cs new file mode 100644 index 000000000..285490416 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamTemplateNamesResponse.cs @@ -0,0 +1,12 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.TEMPLATE.NAMES API +/// + +public record StreamTemplateNamesResponse : IterableResponse +{ + [System.Text.Json.Serialization.JsonPropertyName("consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.ICollection Consumers { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/StreamUpdateRequest.cs b/src/NATS.Client.JetStream/Models/StreamUpdateRequest.cs new file mode 100644 index 000000000..6eb0b995c --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamUpdateRequest.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A request to the JetStream $JS.API.STREAM.UPDATE API +/// + +public record StreamUpdateRequest : StreamConfiguration +{ +} diff --git a/src/NATS.Client.JetStream/Models/StreamUpdateResponse.cs b/src/NATS.Client.JetStream/Models/StreamUpdateResponse.cs new file mode 100644 index 000000000..ff46a3bf0 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/StreamUpdateResponse.cs @@ -0,0 +1,9 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// A response from the JetStream $JS.API.STREAM.UPDATE API +/// + +public record StreamUpdateResponse : StreamInfo +{ +} diff --git a/src/NATS.Client.JetStream/Models/SubjectTransform.cs b/src/NATS.Client.JetStream/Models/SubjectTransform.cs new file mode 100644 index 000000000..ba5034db5 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/SubjectTransform.cs @@ -0,0 +1,23 @@ +namespace NATS.Client.JetStream.Models; + +/// +/// Subject transform to apply to matching messages going into the stream +/// + +public record SubjectTransform +{ + /// + /// The subject transform source + /// + [System.Text.Json.Serialization.JsonPropertyName("src")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Src { get; set; } = default!; + + /// + /// The subject transform destination + /// + [System.Text.Json.Serialization.JsonPropertyName("dest")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Dest { get; set; } = default!; +} diff --git a/src/NATS.Client.JetStream/Models/Tier.cs b/src/NATS.Client.JetStream/Models/Tier.cs new file mode 100644 index 000000000..a05ce7a56 --- /dev/null +++ b/src/NATS.Client.JetStream/Models/Tier.cs @@ -0,0 +1,41 @@ +namespace NATS.Client.JetStream.Models; + +public record Tier +{ + /// + /// Memory Storage being used for Stream Message storage + /// + [System.Text.Json.Serialization.JsonPropertyName("memory")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Memory { get; set; } = default!; + + /// + /// File Storage being used for Stream Message storage + /// + [System.Text.Json.Serialization.JsonPropertyName("storage")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Storage { get; set; } = default!; + + /// + /// Number of active Streams + /// + [System.Text.Json.Serialization.JsonPropertyName("streams")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Streams { get; set; } = default!; + + /// + /// Number of active Consumers + /// + [System.Text.Json.Serialization.JsonPropertyName("consumers")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Consumers { get; set; } = default!; + + [System.Text.Json.Serialization.JsonPropertyName("limits")] + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public AccountLimits Limits { get; set; } = new AccountLimits(); +} diff --git a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj new file mode 100644 index 000000000..88a487a12 --- /dev/null +++ b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + + + pubsub;messaging + JetStream support for NATS.Client. + + + false + + + + + + + + + diff --git a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj index 08e6cc70f..d6a42e910 100644 --- a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj +++ b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj @@ -1,40 +1,48 @@  - - net6.0 - enable - false - $(NoWarn);CS8002 - enable - $(MSBuildProjectDirectory)\test.runsettings - false - + + net6.0 + enable + false + $(NoWarn);CS8002 + enable + $(MSBuildProjectDirectory)\test.runsettings + false + - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - - - - - - - + + + + + + + + + + + + + - - - Always - - + + + Always + + diff --git a/tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs b/tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs index 086bcbfbc..de6e89bd8 100644 --- a/tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs +++ b/tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs @@ -98,7 +98,7 @@ public async Task UserCredentialAuthTest(string name, string serverConfig, NatsO .AddServerConfig(serverConfig) .Build(); - await using var server = new NatsServer(_output, _transportType, serverOptions); + await using var server = new NatsServer(_output, serverOptions); var subject = Guid.NewGuid().ToString("N"); @@ -141,7 +141,7 @@ public async Task UserCredentialAuthTest(string name, string serverConfig, NatsO await disconnectSignal2; _output.WriteLine("START NEW SERVER"); - await using var newServer = new NatsServer(_output, _transportType, serverOptions); + await using var newServer = new NatsServer(_output, serverOptions); await subConnection.ConnectAsync(); // wait open again await pubConnection.ConnectAsync(); // wait open again diff --git a/tests/NATS.Client.Core.Tests/NatsConnectionTest.cs b/tests/NATS.Client.Core.Tests/NatsConnectionTest.cs index 28796f9f8..5db5f5443 100644 --- a/tests/NATS.Client.Core.Tests/NatsConnectionTest.cs +++ b/tests/NATS.Client.Core.Tests/NatsConnectionTest.cs @@ -152,10 +152,11 @@ public async Task ReconnectSingleTest() { using var options = new NatsServerOptions { + TransportType = _transportType, EnableWebSocket = _transportType == TransportType.WebSocket, ServerDisposeReturnsPorts = false, }; - await using var server = new NatsServer(_output, _transportType, options); + await using var server = new NatsServer(_output, options); var subject = Guid.NewGuid().ToString(); await using var subConnection = server.CreateClientConnection(); @@ -211,7 +212,7 @@ await Retry.Until( // start new nats server on same port _output.WriteLine("START NEW SERVER"); - await using var newServer = new NatsServer(_output, _transportType, options); + await using var newServer = new NatsServer(_output, options); await subConnection.ConnectAsync(); // wait open again await pubConnection.ConnectAsync(); // wait open again diff --git a/tests/NATS.Client.Core.Tests/ProtocolTest.cs b/tests/NATS.Client.Core.Tests/ProtocolTest.cs index cbcac11d2..8fd6e0732 100644 --- a/tests/NATS.Client.Core.Tests/ProtocolTest.cs +++ b/tests/NATS.Client.Core.Tests/ProtocolTest.cs @@ -100,7 +100,11 @@ await Retry.Until( [Fact] public async Task Publish_empty_message_for_notifications() { - await using var server = new NatsServer(_output, TransportType.Tcp); + void Log(string text) + { + _output.WriteLine($"[TESTS] {DateTime.Now:HH:mm:ss.fff} {text}"); + } + await using var server = new NatsServer(_output, new NatsServerOptionsBuilder().UseTransport(TransportType.Tcp).Trace().Build()); var (nats, proxy) = server.CreateProxiedClientConnection(); var sync = 0; @@ -129,7 +133,7 @@ await Retry.Until( async () => await nats.PublishAsync("foo.sync"), retryDelay: TimeSpan.FromSeconds(1)); - // PUB notifications + Log("PUB notifications"); await nats.PublishAsync("foo.signal1"); var msg1 = await signal1; Assert.Equal(0, msg1.Data.Length); @@ -139,7 +143,7 @@ await Retry.Until( var msgFrame1 = proxy.Frames.First(f => f.Message.StartsWith("MSG foo.signal1")); Assert.Matches(@"^MSG foo.signal1 \w+ 0␍␊$", msgFrame1.Message); - // HPUB notifications + Log("HPUB notifications"); await nats.PublishAsync("foo.signal2", opts: new NatsPubOpts { Headers = new NatsHeaders() }); var msg2 = await signal2; Assert.Equal(0, msg2.Data.Length); @@ -157,14 +161,22 @@ await Retry.Until( [Fact] public async Task Unsubscribe_max_msgs() { + const int maxMsgs = 10; + const int pubMsgs = 5; + const int extraMsgs = 3; + + void Log(string text) + { + _output.WriteLine($"[TESTS] {DateTime.Now:HH:mm:ss.fff} {text}"); + } + // Use a single server to test multiple scenarios to make test runs more efficient - await using var server = new NatsServer(); + await using var server = new NatsServer(_output, new NatsServerOptionsBuilder().UseTransport(TransportType.Tcp).Trace().Build()); var (nats, proxy) = server.CreateProxiedClientConnection(); var sid = 0; - // Auto-unsubscribe after consuming max-msgs + Log("### Auto-unsubscribe after consuming max-msgs"); { - const int maxMsgs = 99; var opts = new NatsSubOpts { MaxMsgs = maxMsgs }; await using var sub = await nats.SubscribeAsync("foo", opts); sid++; @@ -173,8 +185,8 @@ public async Task Unsubscribe_max_msgs() Assert.Equal($"SUB foo {sid}", proxy.Frames[0].Message); Assert.Equal($"UNSUB {sid} {maxMsgs}", proxy.Frames[1].Message); - // send more messages than max to check we only get max - for (var i = 0; i < maxMsgs + 10; i++) + Log("Send more messages than max to check we only get max"); + for (var i = 0; i < maxMsgs + extraMsgs; i++) { await nats.PublishAsync("foo", i); } @@ -192,7 +204,7 @@ public async Task Unsubscribe_max_msgs() Assert.Equal(NatsSubEndReason.MaxMsgs, sub.EndReason); } - // Manual unsubscribe + Log("### Manual unsubscribe"); { await proxy.FlushFramesAsync(nats); @@ -205,13 +217,13 @@ public async Task Unsubscribe_max_msgs() Assert.Equal($"SUB foo2 {sid}", proxy.ClientFrames[0].Message); Assert.Equal($"UNSUB {sid}", proxy.ClientFrames[1].Message); - // send messages to check we receive none since we're already unsubscribed - for (var i = 0; i < 100; i++) + Log("Send messages to check we receive none since we're already unsubscribed"); + for (var i = 0; i < pubMsgs; i++) { await nats.PublishAsync("foo2", i); } - await Retry.Until("all pub frames arrived", () => proxy.Frames.Count(f => f.Message.StartsWith("PUB foo2")) == 100); + await Retry.Until("all pub frames arrived", () => proxy.Frames.Count(f => f.Message.StartsWith("PUB foo2")) == pubMsgs); var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; @@ -225,12 +237,10 @@ public async Task Unsubscribe_max_msgs() Assert.Equal(NatsSubEndReason.None, sub.EndReason); } - // Reconnect + Log("### Reconnect"); { proxy.Reset(); - const int maxMsgs = 100; - const int pubMsgs = 10; var opts = new NatsSubOpts { MaxMsgs = maxMsgs }; var sub = await nats.SubscribeAsync("foo3", opts); sid++; @@ -243,22 +253,22 @@ public async Task Unsubscribe_max_msgs() await nats.PublishAsync("foo3", i); } - await Retry.Until("published", () => proxy.Frames.Count(f => f.Message.StartsWith("PUB foo3")) == 10); - await Retry.Until("received", () => Volatile.Read(ref count) == 10); + await Retry.Until("published", () => proxy.Frames.Count(f => f.Message.StartsWith("PUB foo3")) == pubMsgs); + await Retry.Until("received", () => Volatile.Read(ref count) == pubMsgs); var pending = maxMsgs - pubMsgs; Assert.Equal(pending, ((INatsSub)sub).PendingMsgs); proxy.Reset(); - // SUB + UNSUB + Log("Expect SUB + UNSUB"); await Retry.Until("re-subscribed", () => proxy.ClientFrames.Count == 2); - // Make sure we're still using the same SID + Log("Make sure we're still using the same SID"); Assert.Equal($"SUB foo3 {sid}", proxy.ClientFrames[0].Message); Assert.Equal($"UNSUB {sid} {pending}", proxy.ClientFrames[1].Message); - // We already published a few, this should exceed max-msgs + Log("We already published a few, this should exceed max-msgs"); for (var i = 0; i < maxMsgs; i++) { await nats.PublishAsync("foo3", i); @@ -272,7 +282,7 @@ await Retry.Until( "unsubscribed with max-msgs", () => sub.EndReason == NatsSubEndReason.MaxMsgs); - Assert.Equal(Volatile.Read(ref count), maxMsgs); + Assert.Equal(maxMsgs, Volatile.Read(ref count)); await sub.DisposeAsync(); await reg; diff --git a/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs b/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs new file mode 100644 index 000000000..1ee3a07dd --- /dev/null +++ b/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Logging; +using NATS.Client.JetStream; + +namespace NATS.Client.Core.Tests; + +public class JetStreamTest +{ + private readonly ITestOutputHelper _output; + + public JetStreamTest(ITestOutputHelper output) => _output = output; + + [Fact] + public async Task Create_stream_test() + { + await using var server = new NatsServer(new NullOutputHelper(), new NatsServerOptionsBuilder().UseTransport(TransportType.Tcp).UseJetStream().Build()); + await using var nats = server.CreateClientConnection(); + + // Create stream + { + var context = new JSContext(nats, new JSOptions()); + var stream = await context.CreateStream(request: stream => + { + stream.Name = "events"; + stream.Subjects = new[] { "events" }; + }); + _output.WriteLine($"RCV: {stream.Response}"); + } + + // Handle exceptions + { + var context = new JSContext(nats, new JSOptions()); + try + { + var stream = await context.CreateStream(request: stream => + { + stream.Name = "events"; + stream.Subjects = new[] { "events" }; + }); + _output.WriteLine($"RCV: {stream.Response}"); + } + catch (NatsSubException e) + { + var payload = e.Payload.Dump(); + var headers = e.Headers.Dump(); + _output.WriteLine($"{e}"); + _output.WriteLine($"headers: {headers}"); + _output.WriteLine($"payload: {payload}"); + } + } + } +} diff --git a/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj b/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj new file mode 100644 index 000000000..448d6e1cf --- /dev/null +++ b/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/tests/NATS.Client.Core.Tests/_FluentAssertionsExtension.cs b/tests/NATS.Client.TestUtilities/FluentAssertionsExtension.cs similarity index 100% rename from tests/NATS.Client.Core.Tests/_FluentAssertionsExtension.cs rename to tests/NATS.Client.TestUtilities/FluentAssertionsExtension.cs diff --git a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj new file mode 100644 index 000000000..5d8f93b32 --- /dev/null +++ b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/NATS.Client.Core.Tests/_NatsServer.cs b/tests/NATS.Client.TestUtilities/NatsServer.cs similarity index 94% rename from tests/NATS.Client.Core.Tests/_NatsServer.cs rename to tests/NATS.Client.TestUtilities/NatsServer.cs index 854ad707e..e405f22e4 100644 --- a/tests/NATS.Client.Core.Tests/_NatsServer.cs +++ b/tests/NATS.Client.TestUtilities/NatsServer.cs @@ -59,14 +59,14 @@ public NatsServer() } public NatsServer(ITestOutputHelper outputHelper, TransportType transportType) - : this(outputHelper, transportType, new NatsServerOptionsBuilder().UseTransport(transportType).Build()) + : this(outputHelper, new NatsServerOptionsBuilder().UseTransport(transportType).Build()) { } - public NatsServer(ITestOutputHelper outputHelper, TransportType transportType, NatsServerOptions options) + public NatsServer(ITestOutputHelper outputHelper, NatsServerOptions options) { _outputHelper = outputHelper; - _transportType = transportType; + _transportType = options.TransportType; Options = options; _configFileName = Path.GetTempFileName(); var config = options.ConfigFileContents; @@ -183,7 +183,7 @@ public async ValueTask DisposeAsync() throw new Exception("Tapped mode doesn't work wit TLS"); } - var proxy = new NatsProxy(Options.ServerPort, _outputHelper); + var proxy = new NatsProxy(Options.ServerPort, _outputHelper, Options.Trace); var client = new NatsConnection((options ?? NatsOptions.Default) with { @@ -254,16 +254,19 @@ public NatsCluster(ITestOutputHelper outputHelper, TransportType transportType) { var opts1 = new NatsServerOptions { + TransportType = transportType, EnableWebSocket = transportType == TransportType.WebSocket, EnableClustering = true, }; var opts2 = new NatsServerOptions { + TransportType = transportType, EnableWebSocket = transportType == TransportType.WebSocket, EnableClustering = true, }; var opts3 = new NatsServerOptions { + TransportType = transportType, EnableWebSocket = transportType == TransportType.WebSocket, EnableClustering = true, }; @@ -273,9 +276,9 @@ public NatsCluster(ITestOutputHelper outputHelper, TransportType transportType) opt.SetRoutes(routes); } - Server1 = new NatsServer(outputHelper, transportType, opts1); - Server2 = new NatsServer(outputHelper, transportType, opts2); - Server3 = new NatsServer(outputHelper, transportType, opts3); + Server1 = new NatsServer(outputHelper, opts1); + Server2 = new NatsServer(outputHelper, opts2); + Server3 = new NatsServer(outputHelper, opts3); } public NatsServer Server1 { get; } @@ -295,15 +298,17 @@ public async ValueTask DisposeAsync() public class NatsProxy : IDisposable { private readonly ITestOutputHelper _outputHelper; + private readonly bool _trace; private readonly TcpListener _tcpListener; private readonly List _clients = new(); private readonly List _frames = new(); private readonly Stopwatch _watch = new(); private int _syncCount; - public NatsProxy(int port, ITestOutputHelper outputHelper) + public NatsProxy(int port, ITestOutputHelper outputHelper, bool trace) { _outputHelper = outputHelper; + _trace = trace; _tcpListener = new TcpListener(IPAddress.Loopback, 0); _tcpListener.Start(); _watch.Restart(); @@ -522,12 +527,13 @@ private bool NatsProtoDump(int client, string origin, TextReader sr, TextWriter private void AddFrame(Frame frame) { - // Log($"Dump {frame}"); + if (_trace) + Log($"TRACE {frame}"); lock (_frames) _frames.Add(frame); } - private void Log(string text) => _outputHelper.WriteLine($"{DateTime.Now:HH:mm:ss.fff} [PROXY] {text}"); + private void Log(string text) => _outputHelper.WriteLine($"[PROXY] {DateTime.Now:HH:mm:ss.fff} {text}"); public record Frame(TimeSpan Timestamp, int Client, string Origin, string Message); } diff --git a/tests/NATS.Client.Core.Tests/_NatsServerOptions.cs b/tests/NATS.Client.TestUtilities/NatsServerOptions.cs similarity index 83% rename from tests/NATS.Client.Core.Tests/_NatsServerOptions.cs rename to tests/NATS.Client.TestUtilities/NatsServerOptions.cs index 9711f3e7e..7c8367801 100644 --- a/tests/NATS.Client.Core.Tests/_NatsServerOptions.cs +++ b/tests/NATS.Client.TestUtilities/NatsServerOptions.cs @@ -16,9 +16,12 @@ public sealed class NatsServerOptionsBuilder private readonly List _extraConfigs = new(); private bool _enableWebSocket; private bool _enableTls; + private bool _enableJetStream; private string? _tlsServerCertFile; private string? _tlsServerKeyFile; private string? _tlsCaFile; + private TransportType? _transportType; + private bool _trace; public NatsServerOptions Build() { @@ -26,15 +29,26 @@ public NatsServerOptions Build() { EnableWebSocket = _enableWebSocket, EnableTls = _enableTls, + EnableJetStream = _enableJetStream, TlsServerCertFile = _tlsServerCertFile, TlsServerKeyFile = _tlsServerKeyFile, TlsCaFile = _tlsCaFile, ExtraConfigs = _extraConfigs, + TransportType = _transportType ?? TransportType.Tcp, + Trace = _trace, }; } + public NatsServerOptionsBuilder Trace() + { + _trace = true; + return this; + } + public NatsServerOptionsBuilder UseTransport(TransportType transportType) { + _transportType = transportType; + if (transportType == TransportType.Tls) { _enableTls = true; @@ -50,6 +64,12 @@ public NatsServerOptionsBuilder UseTransport(TransportType transportType) return this; } + public NatsServerOptionsBuilder UseJetStream() + { + _enableJetStream = true; + return this; + } + public NatsServerOptionsBuilder AddServerConfig(string config) { _extraConfigs.Add(File.ReadAllText(config)); @@ -94,6 +114,8 @@ public NatsServerOptions() public bool EnableTls { get; init; } + public bool EnableJetStream { get; init; } + public bool ServerDisposeReturnsPorts { get; init; } = true; public string? TlsClientCertFile { get; init; } @@ -106,6 +128,10 @@ public NatsServerOptions() public string? TlsCaFile { get; init; } + public TransportType TransportType { get; init; } + + public bool Trace { get; init; } + public List ExtraConfigs { get; init; } = new(); public int ServerPort => _lazyServerPort.Value; @@ -120,6 +146,12 @@ public string ConfigFileContents { var sb = new StringBuilder(); sb.AppendLine($"port: {ServerPort}"); + + if (Trace) + { + sb.AppendLine($"trace: true"); + } + if (EnableWebSocket) { sb.AppendLine("websocket {"); @@ -155,6 +187,15 @@ public string ConfigFileContents sb.AppendLine("}"); } + if (EnableJetStream) + { + sb.AppendLine("jetstream {"); + var storeDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n")); + Directory.CreateDirectory(storeDir); + sb.AppendLine($" store_dir: '{storeDir}'"); + sb.AppendLine("}"); + } + foreach (var config in ExtraConfigs) sb.AppendLine(config); diff --git a/tests/NATS.Client.Core.Tests/_OutputHelperLogger.cs b/tests/NATS.Client.TestUtilities/OutputHelperLogger.cs similarity index 87% rename from tests/NATS.Client.Core.Tests/_OutputHelperLogger.cs rename to tests/NATS.Client.TestUtilities/OutputHelperLogger.cs index ccaafa658..494ea9a5e 100644 --- a/tests/NATS.Client.Core.Tests/_OutputHelperLogger.cs +++ b/tests/NATS.Client.TestUtilities/OutputHelperLogger.cs @@ -49,10 +49,10 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { try { - _testOutputHelper.WriteLine(formatter(state, exception)); + _testOutputHelper.WriteLine($"[NCLOG] {DateTime.Now:HH:mm:ss.fff} {logLevel}: {formatter(state, exception)}"); if (exception != null) { - _testOutputHelper.WriteLine(exception.ToString()); + _testOutputHelper.WriteLine($"[NCLOG] {DateTime.Now:HH:mm:ss.fff} Exception: {exception}"); } } catch diff --git a/tests/NATS.Client.Core.Tests/_Utils.cs b/tests/NATS.Client.TestUtilities/Utils.cs similarity index 67% rename from tests/NATS.Client.Core.Tests/_Utils.cs rename to tests/NATS.Client.TestUtilities/Utils.cs index df461fc50..7387bb9a0 100644 --- a/tests/NATS.Client.Core.Tests/_Utils.cs +++ b/tests/NATS.Client.TestUtilities/Utils.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; +using System.Text; namespace NATS.Client.Core.Tests; @@ -44,9 +45,9 @@ public static void WaitForTcpPortToClose(int port) } } -internal static class NatsMsgTestUtils +public static class NatsMsgTestUtils { - internal static Task Register(this NatsSub? sub, Action> action) + public static Task Register(this NatsSub? sub, Action> action) { if (sub == null) return Task.CompletedTask; @@ -59,7 +60,7 @@ internal static Task Register(this NatsSub? sub, Action> actio }); } - internal static Task Register(this NatsSub? sub, Func, Task> action) + public static Task Register(this NatsSub? sub, Func, Task> action) { if (sub == null) return Task.CompletedTask; @@ -72,7 +73,7 @@ internal static Task Register(this NatsSub? sub, Func, Task> a }); } - internal static Task Register(this NatsSub? sub, Action action) + public static Task Register(this NatsSub? sub, Action action) { if (sub == null) return Task.CompletedTask; @@ -85,7 +86,7 @@ internal static Task Register(this NatsSub? sub, Action action) }); } - internal static Task Register(this NatsSub? sub, Func action) + public static Task Register(this NatsSub? sub, Func action) { if (sub == null) return Task.CompletedTask; @@ -98,3 +99,33 @@ internal static Task Register(this NatsSub? sub, Func action) }); } } + +public static class BinaryUtils +{ + public static string Dump(this in Memory memory) => Dump(memory.Span); + + public static string Dump(this in ReadOnlySpan span) + { + var sb = new StringBuilder(); + foreach (char b in span) + { + switch (b) + { + case >= ' ' and <= '~': + sb.Append(b); + break; + case '\r': + sb.Append('␍'); + break; + case '\n': + sb.Append('␊'); + break; + default: + sb.Append('.'); + break; + } + } + + return sb.ToString(); + } +} diff --git a/tests/NATS.Client.Core.Tests/_WaitSignal.cs b/tests/NATS.Client.TestUtilities/WaitSignal.cs similarity index 100% rename from tests/NATS.Client.Core.Tests/_WaitSignal.cs rename to tests/NATS.Client.TestUtilities/WaitSignal.cs diff --git a/tools/Schema.Generation/Program.cs b/tools/Schema.Generation/Program.cs new file mode 100644 index 000000000..8ed4a2751 --- /dev/null +++ b/tools/Schema.Generation/Program.cs @@ -0,0 +1,97 @@ +using System.Text.RegularExpressions; +using NJsonSchema; +using NJsonSchema.CodeGeneration; +using NJsonSchema.CodeGeneration.CSharp; + +var enumNameGenerator = new ProperCaseEnumNameGenerator(); +var propertyNameGenerator = new ProperCasePropertyNameGenerator(); +var typeNameGenerator = new ProperCaseTypeNameGenerator(); + +var generatorSettings = new CSharpGeneratorSettings +{ + JsonLibrary = CSharpJsonLibrary.SystemTextJson, + GenerateNativeRecords = true, + Namespace = "NATS.Client.JetStream.Models", + GenerateNullableReferenceTypes = true, + EnumNameGenerator = enumNameGenerator, + TypeNameGenerator = typeNameGenerator, + PropertyNameGenerator = propertyNameGenerator, +}; + +var slnDirInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); +while (!File.Exists(Path.Combine(slnDirInfo.FullName, "NATS.Client.sln"))) +{ + if (slnDirInfo.Parent == default) + throw new Exception("could not locate solution root"); + + slnDirInfo = new DirectoryInfo(slnDirInfo.Parent.FullName); +} + +var slnDir = slnDirInfo.FullName; +var schemaProjDir = Path.Combine(slnDir, "tools", "Schema.Generation"); +var jsProjDir = Path.Combine(slnDir, "src", "NATS.Client.JetStream"); + +var jsDefinition = Path.Combine(schemaProjDir, "schema", "jetstream.api.v1.json"); +var jsModels = Path.Combine(jsProjDir, "Models"); + +// start with definitions +var schema = await JsonSchema.FromFileAsync(jsDefinition); + +// create an "Unknown" type containing all of the definitions at the root level +// so that all definitions will be created +foreach (var (typeName, typeDef) in schema.Definitions) +{ + // default behavior is to allow additional properties + // but if no additional property schema is defined we don't want them + if (typeDef.AdditionalPropertiesSchema == default) + typeDef.AllowAdditionalProperties = false; + + schema.Properties[typeName] = new JsonSchemaProperty { Reference = typeDef, }; +} + +// generate +var generator = new CSharpGenerator(schema, generatorSettings); +generator.GenerateFile(); + +var removeGeneratedCodeLine = new Regex(@"^\[System.CodeDom.Compiler.GeneratedCode.*$", RegexOptions.Multiline); +var removeExtraLineAfterAnnotation = new Regex(@"(\[System.Text.Json.Serialization.JsonPropertyName\(""\w+""\)])\n\n"); + +// loop through each type and write a model +foreach (var type in generator.GenerateTypes()) +{ + if (type.TypeName == "Unknown") + continue; + + var code = type.Code; + code = removeGeneratedCodeLine.Replace(code, string.Empty); + code = removeExtraLineAfterAnnotation.Replace(code, "$1\n"); + code = code.Replace("public partial record", "public record"); + code = $"namespace {generatorSettings.Namespace};\n\n{code}"; + + var fileName = $"{type.TypeName}.cs"; + Console.WriteLine($"writing {fileName}"); + await File.WriteAllTextAsync(Path.Combine(jsModels, fileName), code); +} + +public static class Utils +{ + public static string ProperCase(string str) => + string.IsNullOrEmpty(str) + ? str + : string.Concat(str.Split("_").Select(s => string.Concat(s[0].ToString().ToUpper(), s.AsSpan(1)))); +} + +public class ProperCaseEnumNameGenerator : IEnumNameGenerator +{ + public string Generate(int index, string name, object value, JsonSchema schema) => Utils.ProperCase(name); +} + +public class ProperCasePropertyNameGenerator : IPropertyNameGenerator +{ + public string Generate(JsonSchemaProperty property) => Utils.ProperCase(property.Name); +} + +public class ProperCaseTypeNameGenerator : ITypeNameGenerator +{ + public string Generate(JsonSchema schema, string typeNameHint, IEnumerable reservedTypeNames) => string.IsNullOrEmpty(typeNameHint) ? "Unknown" : Utils.ProperCase(typeNameHint); +} diff --git a/tools/Schema.Generation/README.md b/tools/Schema.Generation/README.md new file mode 100644 index 000000000..faa2e1458 --- /dev/null +++ b/tools/Schema.Generation/README.md @@ -0,0 +1,13 @@ +# Schema.Generation + +Console App to generate Models for JetStream + +Requires `node` (current LTS release is fine) for preparing JSON Schema + +1. Copy relevant files from https://github.com/nats-io/jsm.go/tree/main/schema_source + - `request` and `response` models have been edited to make use of `allOf` for cleaner inheritance + - `response` models have `oneOf` with error types removed + - in `definitions.json`: `allOf` references in 3 places have been simplified to enable cleaner model generation +2. Run `node prepare.js` to bundle the `schema_source` files into a single file in `schema` +3. Run `dotnet run` to generate models +4. Run `dotnet format ../../src/NATS.Client.JetStream` to format the generated models diff --git a/tools/Schema.Generation/Schema.Generation.csproj b/tools/Schema.Generation/Schema.Generation.csproj new file mode 100644 index 000000000..03a0db923 --- /dev/null +++ b/tools/Schema.Generation/Schema.Generation.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + false + + + + + + + diff --git a/tools/Schema.Generation/prepare.js b/tools/Schema.Generation/prepare.js new file mode 100644 index 000000000..ff0d86758 --- /dev/null +++ b/tools/Schema.Generation/prepare.js @@ -0,0 +1,22 @@ +const fs = require('fs') +const path = require('path') + +const source = path.join(__dirname, 'schema_source', 'jetstream', 'api', 'v1') +const dest = path.join(__dirname, 'schema', 'jetstream.api.v1.json') +const defs = JSON.parse(fs.readFileSync(path.join(source, 'definitions.json'), 'utf-8')) + +for (const f of fs.readdirSync(source)) { + if (f == 'definitions.json') { + continue + } + const typeName = f.split('.json')[0] + console.log(`merging ${typeName}`) + const schema = JSON.parse(fs.readFileSync(path.join(source, f), 'utf-8')) + delete schema['$id'] + delete schema['$schema'] + defs.definitions[typeName] = schema +} + +let contents = JSON.stringify(defs, null, 2).replaceAll() +console.log(`writing ${dest}`) +fs.writeFileSync(dest, JSON.stringify(defs, null, 2).replaceAll("definitions.json#/definitions", "#/definitions"), 'utf-8') diff --git a/tools/Schema.Generation/schema/jetstream.api.v1.json b/tools/Schema.Generation/schema/jetstream.api.v1.json new file mode 100644 index 000000000..26d799bc9 --- /dev/null +++ b/tools/Schema.Generation/schema/jetstream.api.v1.json @@ -0,0 +1,1984 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/definitions.json", + "title": "io.nats.jetstream.api.v1.definitions", + "description": "Shared definitions for the JetStream API", + "type": "object", + "definitions": { + "golang_duration_nanos": { + "$comment": "nanoseconds depicting a duration in time, signed 64 bit integer", + "$ref": "#/definitions/golang_int64" + }, + "golang_int": { + "$comment": "integer with a dynamic bit size depending on the platform the cluster runs on, can be up to 64bit", + "$ref": "#/definitions/golang_int64" + }, + "golang_uint64": { + "$comment": "unsigned 64 bit integer", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709552000 + }, + "golang_int32": { + "$comment": "signed 32 bit integer", + "type": "integer", + "maximum": 2147483647, + "minimum": -2147483648 + }, + "golang_int64": { + "$comment": "signed 64 bit integer", + "type": "integer", + "maximum": 9223372036854776000, + "minimum": -9223372036854776000 + }, + "golang_time": { + "$comment": "A point in time in RFC3339 format including timezone, though typically in UTC", + "type": "string", + "format": "date-time" + }, + "stream_source": { + "type": "object", + "description": "Defines a source where streams should be replicated from", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "Stream name", + "$ref": "#/definitions/basic_name" + }, + "opt_start_seq": { + "description": "Sequence to start replicating from", + "$ref": "#/definitions/golang_uint64" + }, + "opt_start_time": { + "description": "Time stamp to start replicating from", + "$ref": "#/definitions/golang_time" + }, + "filter_subject": { + "description": "Replicate only a subset of messages based on filter", + "type": "string" + }, + "subject_transform_dest": { + "description": "Map matching subjects according to this transform destination", + "type": "string" + }, + "external": { + "$ref": "#/definitions/external_stream_source" + } + } + }, + "external_stream_source": { + "required": [ + "api" + ], + "type": "object", + "description": "Configuration referencing a stream source in another account or JetStream domain", + "properties": { + "api": { + "type": "string", + "description": "The subject prefix that imports the other account/domain $JS.API.CONSUMER.> subjects" + }, + "deliver": { + "type": "string", + "description": "The delivery subject to use for the push consumer" + } + } + }, + "stream_source_info": { + "required": [ + "name", + "lag", + "active" + ], + "type": "object", + "description": "Information about an upstream stream source in a mirror", + "properties": { + "name": { + "type": "string", + "description": "The name of the Stream being replicated" + }, + "filter_subject": { + "type": "string", + "description": "The subject filter to apply to the messages" + }, + "subject_transform_dest": { + "type": "string", + "description": "The subject transform destination to apply to the messages" + }, + "lag": { + "$ref": "#/definitions/golang_uint64", + "description": "How many messages behind the mirror operation is", + "minimum": 0 + }, + "active": { + "description": "When last the mirror had activity, in nanoseconds. Value will be -1 when there has been no activity.", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": -1 + }, + "external": { + "$ref": "#/definitions/external_stream_source" + }, + "error": { + "$ref": "#/definitions/api_error" + } + } + }, + "lost_stream_data": { + "type": "object", + "description": "Records messages that were damaged and unrecoverable", + "properties": { + "msgs": { + "type": [ + "array", + "null" + ], + "description": "The messages that were lost", + "items": { + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + } + }, + "bytes": { + "description": "The number of bytes that were lost", + "$ref": "#/definitions/golang_uint64" + } + } + }, + "placement": { + "type": "object", + "description": "Placement requirements for a stream", + "required": [ + "cluster" + ], + "properties": { + "cluster": { + "type": "string", + "description": "The desired cluster name to place the stream" + }, + "tags": { + "description": "Tags required on servers hosting this stream", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "peer_info": { + "type": "object", + "required": [ + "name", + "current", + "active" + ], + "properties": { + "name": { + "description": "The server name of the peer", + "type": "string", + "minimum": 1 + }, + "current": { + "description": "Indicates if the server is up to date and synchronised", + "type": "boolean", + "default": false + }, + "active": { + "description": "Nanoseconds since this peer was last seen", + "type": "number" + }, + "offline": { + "description": "Indicates the node is considered offline by the group", + "type": "boolean", + "default": false + }, + "lag": { + "description": "How many uncommitted operations this peer is behind the leader", + "type": "integer", + "minimum": 0 + } + } + }, + "cluster_info": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The cluster name" + }, + "leader": { + "type": "string", + "description": "The server name of the RAFT leader" + }, + "replicas": { + "type": "array", + "description": "The members of the RAFT cluster", + "items": { + "$ref": "#/definitions/peer_info" + } + } + } + }, + "api_stats": { + "type": "object", + "required": [ + "total", + "errors" + ], + "properties": { + "total": { + "description": "Total number of API requests received for this account", + "minimum": 0, + "type": "integer" + }, + "errors": { + "description": "API requests that resulted in an error response", + "minimum": 0, + "type": "integer" + } + } + }, + "tier": { + "type": "object", + "required": [ + "memory", + "storage", + "streams", + "limits", + "consumers" + ], + "properties": { + "memory": { + "description": "Memory Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "storage": { + "description": "File Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "streams": { + "description": "Number of active Streams", + "type": "integer", + "minimum": 0 + }, + "consumers": { + "description": "Number of active Consumers", + "type": "integer", + "minimum": 0 + }, + "limits": { + "$ref": "#/definitions/account_limits" + } + } + }, + "account_stats": { + "type": "object", + "required": [ + "memory", + "storage", + "streams", + "limits", + "api", + "consumers" + ], + "properties": { + "memory": { + "description": "Memory Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "storage": { + "description": "File Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "streams": { + "description": "Number of active Streams", + "type": "integer", + "minimum": 0 + }, + "consumers": { + "description": "Number of active Consumers", + "type": "integer", + "minimum": 0 + }, + "domain": { + "description": "The JetStream domain this account is in", + "type": "string" + }, + "limits": { + "$ref": "#/definitions/account_limits" + }, + "tiers": { + "type": "object", + "patternProperties": { + ".*": { + "$ref": "#/definitions/tier" + } + } + }, + "api": { + "$ref": "#/definitions/api_stats" + } + } + }, + "account_limits": { + "type": "object", + "additionalProperties": false, + "required": [ + "max_consumers", + "max_memory", + "max_storage", + "max_streams" + ], + "properties": { + "max_memory": { + "type": "integer", + "description": "The maximum amount of Memory storage Stream Messages may consume", + "minimum": -1 + }, + "max_storage": { + "type": "integer", + "description": "The maximum amount of File storage Stream Messages may consume", + "minimum": -1 + }, + "max_streams": { + "type": "integer", + "description": "The maximum number of Streams an account can create", + "minimum": -1 + }, + "max_consumers": { + "type": "integer", + "description": "The maximum number of Consumer an account can create", + "minimum": -1 + }, + "max_bytes_required": { + "type": "boolean", + "description": "Indicates if Streams created in this account requires the max_bytes property set", + "default": false + }, + "max_ack_pending": { + "type": "integer", + "description": "The maximum number of outstanding ACKs any consumer may configure" + }, + "memory_max_stream_bytes": { + "type": "integer", + "description": "The maximum size any single memory stream may be", + "minimum": -1, + "default": -1 + }, + "storage_max_stream_bytes": { + "type": "integer", + "description": "The maximum size any single storage based stream may be", + "minimum": -1, + "default": -1 + } + } + }, + "stored_message": { + "type": "object", + "additionalProperties": false, + "required": [ + "subject", + "seq", + "time" + ], + "properties": { + "subject": { + "type": "string", + "description": "The subject the message was originally received on", + "minLength": 1 + }, + "seq": { + "description": "The sequence number of the message in the Stream", + "$ref": "#/definitions/golang_uint64" + }, + "data": { + "type": "string", + "description": "The base64 encoded payload of the message body", + "minLength": 0 + }, + "time": { + "type": "string", + "description": "The time the message was received" + }, + "hdrs": { + "type": "string", + "description": "Base64 encoded headers for the message" + } + } + }, + "iterable_request": { + "type": "object", + "additionalProperties": false, + "required": [ + "offset" + ], + "properties": { + "offset": { + "type": "integer", + "minimum": 0 + } + } + }, + "iterable_response": { + "type": "object", + "required": [ + "total", + "offset", + "limit" + ], + "properties": { + "total": { + "type": "integer", + "minimum": 0 + }, + "offset": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 0 + } + } + }, + "error_response": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/api_error" + } + } + }, + "api_error": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "integer", + "description": "HTTP like error code in the 300 to 500 range", + "minimum": 300, + "maximum": 699 + }, + "description": { + "type": "string", + "description": "A human friendly description of the error" + }, + "err_code": { + "type": "integer", + "description": "The NATS error code unique to each kind of error", + "minimum": 0, + "maximum": 65535 + } + } + }, + "basic_name": { + "type": "string", + "pattern": "^[^.*>]+$", + "minLength": 1 + }, + "sequence_info": { + "type": "object", + "additionalProperties": false, + "required": [ + "consumer_seq", + "stream_seq" + ], + "properties": { + "consumer_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Consumer" + }, + "stream_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Stream" + }, + "last_active": { + "description": "The last time a message was delivered or acknowledged (for ack_floor)", + "$ref": "#/definitions/golang_time" + } + } + }, + "sequence_pair": { + "type": "object", + "additionalProperties": false, + "required": [ + "consumer_seq", + "stream_seq" + ], + "properties": { + "consumer_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Consumer" + }, + "stream_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Stream" + } + } + }, + "consumer_info": { + "required": [ + "stream_name", + "name", + "config", + "created", + "delivered", + "ack_floor", + "num_ack_pending", + "num_redelivered", + "num_waiting", + "num_pending" + ], + "type": "object", + "properties": { + "stream_name": { + "type": "string", + "description": "The Stream the consumer belongs to" + }, + "name": { + "type": "string", + "description": "A unique name for the consumer, either machine generated or the durable name" + }, + "ts": { + "description": "The server time the consumer info was created", + "$ref": "#/definitions/golang_time" + }, + "config": { + "$ref": "#/definitions/consumer_configuration" + }, + "created": { + "description": "The time the Consumer was created", + "$ref": "#/definitions/golang_time" + }, + "delivered": { + "description": "The last message delivered from this Consumer", + "$ref": "#/definitions/sequence_info" + }, + "ack_floor": { + "description": "The highest contiguous acknowledged message", + "$ref": "#/definitions/sequence_info" + }, + "num_ack_pending": { + "description": "The number of messages pending acknowledgement", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_redelivered": { + "description": "The number of redeliveries that have been performed", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_waiting": { + "description": "The number of pull consumers waiting for messages", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_pending": { + "description": "The number of messages left unconsumed in this Consumer", + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + }, + "cluster": { + "$ref": "#/definitions/cluster_info" + }, + "push_bound": { + "description": "Indicates if any client is connected and receiving messages from a push consumer", + "type": "boolean" + } + } + }, + "consumer_configuration": { + "required": [ + "deliver_policy", + "ack_policy", + "replay_policy" + ], + "properties": { + "deliver_policy": { + "type": "string", + "enum": [ + "all", + "last", + "new", + "by_start_sequence", + "by_start_time", + "last_per_subject" + ] + }, + "opt_start_seq": { + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + }, + "opt_start_time": { + "$ref": "#/definitions/golang_time" + }, + "durable_name": { + "description": "A unique name for a durable consumer", + "deprecationMessage": "Durable is deprecated. All consumers will have names. picked by clients.", + "$ref": "#/definitions/basic_name" + }, + "name": { + "description": "A unique name for a consumer", + "$ref": "#/definitions/basic_name" + }, + "description": { + "description": "A short description of the purpose of this consumer", + "type": "string", + "maxLength": 4096 + }, + "deliver_subject": { + "type": "string", + "minLength": 1 + }, + "ack_policy": { + "type": "string", + "enum": [ + "none", + "all", + "explicit" + ], + "default": "none" + }, + "ack_wait": { + "description": "How long (in nanoseconds) to allow messages to remain un-acknowledged before attempting redelivery", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 1, + "default": "30000000000" + }, + "max_deliver": { + "$ref": "#/definitions/golang_int", + "description": "The number of times a message will be redelivered to consumers if not acknowledged in time", + "default": -1 + }, + "filter_subject": { + "description": "Filter the stream by a single subjects", + "type": "string" + }, + "filter_subjects": { + "description": "Filter the stream by multiple subjects", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "replay_policy": { + "type": "string", + "enum": [ + "instant", + "original" + ], + "default": "instant" + }, + "sample_freq": { + "type": "string" + }, + "rate_limit_bps": { + "$ref": "#/definitions/golang_uint64", + "description": "The rate at which messages will be delivered to clients, expressed in bit per second", + "minimum": 0 + }, + "max_ack_pending": { + "$ref": "#/definitions/golang_int", + "description": "The maximum number of messages without acknowledgement that can be outstanding, once this limit is reached message delivery will be suspended", + "default": 1000 + }, + "idle_heartbeat": { + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 0, + "description": "If the Consumer is idle for more than this many nano seconds a empty message with Status header 100 will be sent indicating the consumer is still alive" + }, + "flow_control": { + "type": "boolean", + "description": "For push consumers this will regularly send an empty mess with Status header 100 and a reply subject, consumers must reply to these messages to control the rate of message delivery" + }, + "max_waiting": { + "$ref": "#/definitions/golang_int", + "description": "The number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored", + "minimum": 0, + "default": 512 + }, + "direct": { + "type": "boolean", + "description": "Creates a special consumer that does not touch the Raft layers, not for general use by clients, internal use only", + "default": false + }, + "headers_only": { + "type": "boolean", + "default": false, + "description": "Delivers only the headers of messages in the stream and not the bodies. Additionally adds Nats-Msg-Size header to indicate the size of the removed payload" + }, + "max_batch": { + "type": "integer", + "description": "The largest batch property that may be specified when doing a pull on a Pull Consumer", + "default": 0 + }, + "max_expires": { + "description": "The maximum expires value that may be set when doing a pull on a Pull Consumer", + "$ref": "#/definitions/golang_duration_nanos", + "default": 0 + }, + "max_bytes": { + "description": "The maximum bytes value that maybe set when dong a pull on a Pull Consumer", + "$ref": "#/definitions/golang_int", + "minimum": 0, + "default": 0 + }, + "inactive_threshold": { + "description": "Duration that instructs the server to cleanup ephemeral consumers that are inactive for that long", + "$ref": "#/definitions/golang_duration_nanos", + "default": 0 + }, + "backoff": { + "description": "List of durations in Go format that represents a retry time scale for NaK'd messages", + "type": "array", + "items": { + "$ref": "#/definitions/golang_duration_nanos" + } + }, + "num_replicas": { + "description": "When set do not inherit the replica count from the stream but specifically set it to this amount", + "type": "integer", + "$ref": "#/definitions/golang_int", + "minimum": 0, + "maximum": 5 + }, + "mem_storage": { + "description": "Force the consumer state to be kept in memory rather than inherit the setting from the stream", + "type": "boolean", + "default": false + }, + "metadata": { + "description": "Additional metadata for the Consumer", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "anyOf": [ + { + "if": { + "properties": { + "deliver_policy": { + "const": "by_start_sequence" + } + } + }, + "then": { + "required": [ + "opt_start_seq" + ] + } + }, + { + "if": { + "properties": { + "deliver_policy": { + "const": "opt_start_time" + } + } + }, + "then": { + "required": [ + "opt_start_seq" + ] + } + } + ] + }, + "stream_info": { + "type": "object", + "required": [ + "config", + "state", + "created" + ], + "properties": { + "config": { + "type": "object", + "description": "The active configuration for the Stream", + "$ref": "#/definitions/stream_configuration" + }, + "state": { + "type": "object", + "description": "Detail about the current State of the Stream", + "$ref": "#/definitions/stream_state" + }, + "created": { + "description": "Timestamp when the stream was created", + "$ref": "#/definitions/golang_time" + }, + "ts": { + "description": "The server time the stream info was created", + "$ref": "#/definitions/golang_time" + }, + "cluster": { + "$ref": "#/definitions/cluster_info" + }, + "mirror": { + "$ref": "#/definitions/stream_source_info" + }, + "sources": { + "type": "array", + "description": "Streams being sourced into this Stream", + "items": { + "$ref": "#/definitions/stream_source_info" + } + }, + "alternates": { + "type": "array", + "description": "List of mirrors sorted by priority", + "items": { + "$ref": "#/definitions/stream_alternate" + } + } + } + }, + "stream_alternate": { + "type": "object", + "description": "An alternate location to read mirrored data", + "required": [ + "name", + "cluster" + ], + "properties": { + "name": { + "type": "string", + "description": "The mirror stream name" + }, + "cluster": { + "type": "string", + "description": "The name of the cluster holding the stream" + }, + "domain": { + "type": "string", + "description": "The domain holding the string" + } + } + }, + "stream_state": { + "type": "object", + "additionalProperties": false, + "required": [ + "messages", + "bytes", + "first_seq", + "last_seq", + "consumer_count" + ], + "properties": { + "messages": { + "$ref": "#/definitions/golang_uint64", + "description": "Number of messages stored in the Stream", + "minimum": 0 + }, + "bytes": { + "$ref": "#/definitions/golang_uint64", + "description": "Combined size of all messages in the Stream", + "minimum": 0 + }, + "first_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "Sequence number of the first message in the Stream", + "minimum": 0 + }, + "first_ts": { + "type": "string", + "description": "The timestamp of the first message in the Stream" + }, + "last_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "Sequence number of the last message in the Stream", + "minimum": 0 + }, + "last_ts": { + "type": "string", + "description": "The timestamp of the last message in the Stream" + }, + "deleted": { + "description": "IDs of messages that were deleted using the Message Delete API or Interest based streams removing messages out of order", + "type": "array", + "minLength": 0, + "items": { + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + } + }, + "subjects": { + "description": "Subjects and their message counts when a subjects_filter was set", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/golang_uint64" + } + }, + "num_subjects": { + "description": "The number of unique subjects held in the stream", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_deleted": { + "description": "The number of deleted messages", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "lost": { + "$ref": "#/definitions/lost_stream_data" + }, + "consumer_count": { + "$ref": "#/definitions/golang_int", + "description": "Number of Consumers attached to the Stream", + "minimum": 0 + } + } + }, + "subject_transform": { + "type": "object", + "description": "Subject transform to apply to matching messages going into the stream", + "required": [ + "dest" + ], + "properties": { + "src": { + "type": "string", + "description": "The subject transform source" + }, + "dest": { + "type": "string", + "description": "The subject transform destination" + } + } + }, + "republish": { + "type": "object", + "description": "Rules for republishing messages from a stream with subject mapping onto new subjects for partitioning and more", + "required": [ + "src", + "dest" + ], + "properties": { + "src": { + "type": "string", + "description": "The source subject to republish" + }, + "dest": { + "type": "string", + "description": "The destination to publish to" + }, + "headers_only": { + "type": "boolean", + "description": "Only send message headers, no bodies", + "default": false + } + } + }, + "stream_configuration": { + "type": "object", + "required": [ + "retention", + "max_consumers", + "max_msgs", + "max_bytes", + "max_age", + "storage", + "num_replicas" + ], + "additionalProperties": false, + "properties": { + "name": { + "description": "A unique name for the Stream, empty for Stream Templates.", + "type": "string", + "pattern": "^[^.*>]*$", + "minLength": 0 + }, + "description": { + "description": "A short description of the purpose of this stream", + "type": "string", + "maxLength": 4096 + }, + "subjects": { + "description": "A list of subjects to consume, supports wildcards. Must be empty when a mirror is configured. May be empty when sources are configured.", + "type": "array", + "minLength": 0, + "items": { + "type": "string" + } + }, + "subject_transform": { + "description": "Subject transform to apply to matching messages", + "$ref": "#/definitions/subject_transform" + }, + "retention": { + "description": "How messages are retained in the Stream, once this is exceeded old messages are removed.", + "type": "string", + "enum": [ + "limits", + "interest", + "workqueue" + ], + "default": "limits" + }, + "max_consumers": { + "description": "How many Consumers can be defined for a given Stream. -1 for unlimited.", + "$ref": "#/definitions/golang_int", + "minimum": -1, + "default": -1 + }, + "max_msgs": { + "description": "How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.", + "$ref": "#/definitions/golang_int64", + "minimum": -1, + "default": -1 + }, + "max_msgs_per_subject": { + "description": "For wildcard streams ensure that for every unique subject this many messages are kept - a per subject retention limit", + "$ref": "#/definitions/golang_int64", + "minimum": -1, + "default": -1 + }, + "max_bytes": { + "description": "How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.", + "$ref": "#/definitions/golang_int64", + "minimum": -1, + "default": -1 + }, + "max_age": { + "description": "Maximum age of any message in the stream, expressed in nanoseconds. 0 for unlimited.", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 0, + "default": 0 + }, + "max_msg_size": { + "description": "The largest message that will be accepted by the Stream. -1 for unlimited.", + "$ref": "#/definitions/golang_int32", + "minimum": -1, + "default": -1 + }, + "storage": { + "description": "The storage backend to use for the Stream.", + "type": "string", + "enum": [ + "file", + "memory" + ], + "default": "file" + }, + "compression": { + "description": "Optional compression algorithm used for the Stream.", + "type": "string", + "enum": [ + "none", + "s2" + ], + "default": "none" + }, + "num_replicas": { + "description": "How many replicas to keep for each message.", + "$ref": "#/definitions/golang_int", + "minimum": 1, + "default": 1, + "maximum": 5 + }, + "no_ack": { + "description": "Disables acknowledging messages that are received by the Stream.", + "type": "boolean", + "default": false + }, + "template_owner": { + "description": "When the Stream is managed by a Stream Template this identifies the template that manages the Stream.", + "type": "string" + }, + "discard": { + "description": "When a Stream reach it's limits either old messages are deleted or new ones are denied", + "type": "string", + "enum": [ + "old", + "new" + ], + "default": "old" + }, + "duplicate_window": { + "description": "The time window to track duplicate messages for, expressed in nanoseconds. 0 for default", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 0, + "default": 0 + }, + "placement": { + "description": "Placement directives to consider when placing replicas of this stream, random placement when unset", + "$ref": "#/definitions/placement" + }, + "mirror": { + "description": "Maintains a 1:1 mirror of another stream with name matching this property. When a mirror is configured subjects and sources must be empty.", + "$ref": "#/definitions/stream_source" + }, + "sources": { + "type": "array", + "description": "List of Stream names to replicate into this Stream", + "items": { + "$ref": "#/definitions/stream_source" + } + }, + "sealed": { + "type": "boolean", + "default": false, + "description": "Sealed streams do not allow messages to be deleted via limits or API, sealed streams can not be unsealed via configuration update. Can only be set on already created streams via the Update API" + }, + "deny_delete": { + "type": "boolean", + "default": false, + "description": "Restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true" + }, + "deny_purge": { + "type": "boolean", + "default": false, + "description": "Restricts the ability to purge messages from a stream via the API. Cannot be change once set to true" + }, + "allow_rollup_hdrs": { + "type": "boolean", + "default": false, + "description": "Allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message" + }, + "allow_direct": { + "type": "boolean", + "default": false, + "description": "Allow higher performance, direct access to get individual messages" + }, + "mirror_direct": { + "type": "boolean", + "default": false, + "description": "Allow higher performance, direct access for mirrors as well" + }, + "republish": { + "$ref": "#/definitions/republish" + }, + "discard_new_per_subject": { + "type": "boolean", + "description": "When discard policy is new and the stream is one with max messages per subject set, this will apply the new behavior to every subject. Essentially turning discard new from maximum number of subjects into maximum number of messages in a subject.", + "default": false + }, + "metadata": { + "description": "Additional metadata for the Stream", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "stream_template_info": { + "type": "object", + "required": [ + "config", + "streams" + ], + "properties": { + "config": { + "$ref": "#/definitions/stream_template_configuration" + }, + "streams": { + "description": "List of Streams managed by this Template", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "stream_template_configuration": { + "description": "The data structure that describe the configuration of a NATS JetStream Stream Template", + "title": "io.nats.jetstream.api.v1.stream_template_configuration", + "type": "object", + "required": [ + "name", + "config", + "max_streams" + ], + "additionalProperties": false, + "properties": { + "name": { + "description": "A unique name for the Stream Template.", + "$ref": "#/definitions/basic_name" + }, + "max_streams": { + "description": "The maximum number of Streams this Template can create, -1 for unlimited.", + "minimum": -1, + "default": -1, + "$ref": "#/definitions/golang_int32" + }, + "config": { + "$ref": "#/definitions/stream_configuration" + } + } + }, + "account_info_response": { + "description": "A response from the JetStream $JS.API.INFO API", + "title": "io.nats.jetstream.api.v1.account_info_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/account_stats" + } + ] + }, + "account_purge_response": { + "description": "A response from the JetStream $JS.API.ACCOUNT.PURGE API", + "title": "io.nats.jetstream.api.v1.account_purge_response", + "type": "object", + "properties": { + "initiated": { + "type": "boolean", + "description": "If the purge operation was succesfully started", + "default": false + } + } + }, + "consumer_create_request": { + "description": "A request to the JetStream $JS.API.CONSUMER.CREATE and $JS.API.CONSUMER.DURABLE.CREATE APIs", + "title": "io.nats.jetstream.api.v1.consumer_create_request", + "type": "object", + "required": [ + "stream_name", + "config" + ], + "properties": { + "stream_name": { + "type": "string", + "description": "The name of the stream to create the consumer in" + }, + "config": { + "type": "object", + "description": "The consumer configuration", + "$ref": "#/definitions/consumer_configuration" + } + } + }, + "consumer_create_response": { + "description": "A response from the JetStream $JS.API.CONSUMER.CREATE API", + "title": "io.nats.jetstream.api.v1.consumer_create_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/consumer_info" + } + ] + }, + "consumer_delete_response": { + "description": "A response from the JetStream $JS.API.CONSUMER.DELETE API", + "title": "io.nats.jetstream.api.v1.consumer_delete_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + } + }, + "consumer_getnext_request": { + "description": "A request to the JetStream $JS.API.CONSUMER.MSG.NEXT API", + "title": "io.nats.jetstream.api.v1.consumer_getnext_request", + "type": "object", + "properties": { + "expires": { + "$ref": "#/definitions/golang_duration_nanos", + "description": "A duration from now when the pull should expire, stated in nanoseconds, 0 for no expiry" + }, + "batch": { + "$ref": "#/definitions/golang_int", + "description": "How many messages the server should deliver to the requestor", + "minimum": 0, + "maximum": 256 + }, + "max_bytes": { + "$ref": "#/definitions/golang_int", + "description": "Sends at most this many bytes to the requestor, limited by consumer configuration max_bytes", + "minimum": 0 + }, + "no_wait": { + "type": "boolean", + "description": "When true a response with a 404 status header will be returned when no messages are available" + }, + "idle_heartbeat": { + "$ref": "#/definitions/golang_duration_nanos", + "description": "When not 0 idle heartbeats will be sent on this interval" + } + } + }, + "consumer_info_response": { + "description": "A response from the JetStream $JS.API.CONSUMER.INFO API", + "title": "io.nats.jetstream.api.v1.consumer_info_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/consumer_info" + } + ] + }, + "consumer_leader_stepdown_response": { + "description": "A response from the JetStream $JS.API.CONSUMER.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.consumer_leader_stepdown_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "description": "If the leader successfully stood down", + "default": false + } + } + }, + "consumer_list_request": { + "description": "A request to the JetStream $JS.API.CONSUMER.LIST API", + "title": "io.nats.jetstream.api.v1.consumer_list_request", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_request" + } + ] + }, + "consumer_list_response": { + "description": "A response from the JetStream $JS.API.CONSUMER.LIST API", + "title": "io.nats.jetstream.api.v1.consumer_list_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_response" + } + ], + "required": [ + "consumers" + ], + "properties": { + "consumers": { + "description": "Full Consumer information for each known Consumer", + "type": "array", + "items": { + "$ref": "#/definitions/consumer_info" + }, + "missing": { + "description": "In clustered environments gathering Consumer info might time out, this list would be a list of Consumers for which information was not obtainable", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "consumer_names_request": { + "description": "A request to the JetStream $JS.API.CONSUMER.NAMES API", + "title": "io.nats.jetstream.api.v1.consumer_names_request", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_request" + } + ], + "properties": { + "subject": { + "type": "string", + "description": "Filter the names to those consuming messages matching this subject or wildcard" + } + } + }, + "consumer_names_response": { + "description": "A response from the JetStream $JS.API.CONSUMER.NAMES API", + "title": "io.nats.jetstream.api.v1.consumer_names_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_response" + } + ], + "required": [ + "consumers" + ], + "properties": { + "consumers": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "meta_leader_stepdown_request": { + "description": "A request to the JetStream $JS.API.META.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.meta_leader_stepdown_request", + "type": "object", + "properties": { + "placement": { + "$ref": "#/definitions/placement" + } + } + }, + "meta_leader_stepdown_response": { + "description": "A response from the JetStream $JS.API.META.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.meta_leader_stepdown_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "description": "If the leader successfully stood down", + "default": false + } + } + }, + "meta_server_remove_request": { + "description": "A request to the JetStream $JS.API.SERVER.REMOVE API", + "title": "io.nats.jetstream.api.v1.meta_server_remove_request", + "type": "object", + "properties": { + "peer": { + "type": "string", + "description": "The Name of the server to remove from the meta group" + }, + "peer_id": { + "type": "string", + "description": "Peer ID of the peer to be removed. If specified this is used instead of the server name" + } + } + }, + "meta_server_remove_response": { + "description": "A response from the JetStream $JS.API.SERVER.REMOVE API", + "title": "io.nats.jetstream.api.v1.meta_server_remove_response", + "required": [ + "success" + ], + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "If the peer was successfully removed", + "default": false + } + } + }, + "pub_ack_response": { + "description": "A response received when publishing a message", + "title": "io.nats.jetstream.api.v1.pub_ack_response", + "type": "object", + "required": [ + "stream" + ], + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/api_error" + }, + "stream": { + "type": "string", + "description": "The name of the stream that received the message", + "minLength": 1 + }, + "seq": { + "type": "integer", + "description": "If successful this will be the sequence the message is stored at", + "$ref": "#/definitions/golang_uint64" + }, + "duplicate": { + "type": "boolean", + "description": "Indicates that the message was not stored due to the Nats-Msg-Id header and duplicate tracking", + "default": false + }, + "domain": { + "type": "string", + "description": "If the Stream accepting the message is in a JetStream server configured for a domain this would be that domain" + } + } + }, + "stream_create_request": { + "description": "A request to the JetStream $JS.API.STREAM.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_create_request", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_configuration" + } + ] + }, + "stream_create_response": { + "description": "A response from the JetStream $JS.API.STREAM.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_create_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_info" + } + ] + }, + "stream_delete_response": { + "description": "A response from the JetStream $JS.API.STREAM.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_delete_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + } + }, + "stream_info_request": { + "description": "A request to the JetStream $JS.API.STREAM.INFO API", + "title": "io.nats.jetstream.api.v1.stream_info_request", + "type": "object", + "properties": { + "deleted_details": { + "type": "boolean", + "description": "When true will result in a full list of deleted message IDs being returned in the info response" + }, + "subjects_filter": { + "type": "string", + "description": "When set will return a list of subjects and how many messages they hold for all matching subjects. Filter is a standard NATS subject wildcard pattern." + }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Paging offset when retrieving pages of subjet details" + } + } + }, + "stream_info_response": { + "description": "A response from the JetStream $JS.API.STREAM.INFO API", + "title": "io.nats.jetstream.api.v1.stream_info_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_info" + } + ], + "properties": { + "total": { + "type": "integer", + "minimum": 0 + }, + "offset": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 0 + } + } + }, + "stream_leader_stepdown_response": { + "description": "A response from the JetStream $JS.API.STREAM.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.stream_leader_stepdown_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "description": "If the leader successfully stood down", + "default": false + } + } + }, + "stream_list_request": { + "description": "A request to the JetStream $JS.API.STREAM.LIST API", + "title": "io.nats.jetstream.api.v1.stream_list_request", + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Limit the list to streams matching this subject filter" + }, + "offset": { + "type": "integer", + "minimum": 0 + } + } + }, + "stream_list_response": { + "description": "A response from the JetStream $JS.API.STREAM.LIST API", + "title": "io.nats.jetstream.api.v1.stream_list_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_response" + } + ], + "required": [ + "streams" + ], + "properties": { + "streams": { + "description": "Full Stream information for each known Stream", + "type": "array", + "items": { + "$ref": "#/definitions/stream_info" + } + }, + "missing": { + "description": "In clustered environments gathering Stream info might time out, this list would be a list of Streams for which information was not obtainable", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "stream_msg_delete_request": { + "description": "A request to the JetStream $JS.API.STREAM.MSG.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_msg_delete_request", + "type": "object", + "required": [ + "seq" + ], + "properties": { + "seq": { + "description": "Stream sequence number of the message to delete", + "$ref": "#/definitions/golang_uint64" + }, + "no_erase": { + "type": "boolean", + "description": "Default will securely remove a message and rewrite the data with random data, set this to true to only remove the message" + } + } + }, + "stream_msg_delete_response": { + "description": "A response from the JetStream $JS.API.STREAM.MSG.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_msg_delete_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + } + }, + "stream_msg_get_request": { + "description": "A request to the JetStream $JS.API.STREAM.MSG.GET API", + "title": "io.nats.jetstream.api.v1.stream_msg_get_request", + "type": "object", + "properties": { + "seq": { + "type": "integer", + "description": "Stream sequence number of the message to retrieve, cannot be combined with last_by_subj" + }, + "last_by_subj": { + "type": "string", + "description": "Retrieves the last message for a given subject, cannot be combined with seq" + }, + "next_by_subj": { + "type": "string", + "description": "Combined with sequence gets the next message for a subject with the given sequence or higher" + } + } + }, + "stream_msg_get_response": { + "description": "A response from the JetStream $JS.API.STREAM.MSG.GET API", + "title": "io.nats.jetstream.api.v1.stream_msg_get_response", + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "$ref": "#/definitions/stored_message" + } + } + }, + "stream_names_request": { + "description": "A request to the JetStream $JS.API.STREAM.NAMES API", + "title": "io.nats.jetstream.api.v1.stream_names_request", + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Limit the list to streams matching this subject filter" + }, + "offset": { + "type": "integer", + "minimum": 0 + } + } + }, + "stream_names_response": { + "description": "A response from the JetStream $JS.API.STREAM.NAMES API", + "title": "io.nats.jetstream.api.v1.stream_names_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_response" + } + ], + "required": [ + "streams" + ], + "properties": { + "consumers": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "stream_purge_request": { + "description": "A request to the JetStream $JS.API.STREAM.PURGE API", + "title": "io.nats.jetstream.api.v1.stream_purge_request", + "type": "object", + "properties": { + "filter": { + "type": "string", + "description": "Restrict purging to messages that match this subject" + }, + "seq": { + "description": "Purge all messages up to but not including the message with this sequence. Can be combined with subject filter but not the keep option", + "$ref": "#/definitions/golang_uint64" + }, + "keep": { + "description": "Ensures this many messages are present after the purge. Can be combined with the subject filter but not the sequence", + "$ref": "#/definitions/golang_uint64" + } + } + }, + "stream_purge_response": { + "description": "A response from the JetStream $JS.API.STREAM.PURGE API", + "title": "io.nats.jetstream.api.v1.stream_purge_response", + "type": "object", + "required": [ + "success", + "purged" + ], + "properties": { + "success": { + "type": "boolean" + }, + "purged": { + "description": "Number of messages purged from the Stream", + "$ref": "#/definitions/golang_uint64" + } + } + }, + "stream_remove_peer_request": { + "description": "A request to the JetStream $JS.API.STREAM.PEER.REMOVE API", + "title": "io.nats.jetstream.api.v1.stream_remove_peer_request", + "type": "object", + "required": [ + "peer" + ], + "additionalProperties": false, + "properties": { + "peer": { + "type": "string", + "description": "Server name of the peer to remove", + "minLength": 1 + } + } + }, + "stream_remove_peer_response": { + "description": "A response from the JetStream $JS.API.STREAM.PEER.REMOVE API", + "title": "io.nats.jetstream.api.v1.stream_remove_peer_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean", + "description": "If the peer was successfully removed", + "default": false + } + } + }, + "stream_restore_request": { + "description": "A response from the JetStream $JS.API.STREAM.RESTORE API", + "title": "io.nats.jetstream.api.v1.stream_restore_request", + "type": "object", + "required": [ + "config", + "state" + ], + "properties": { + "config": { + "$ref": "#/definitions/stream_configuration" + }, + "state": { + "$ref": "#/definitions/stream_state" + } + } + }, + "stream_restore_response": { + "description": "A response from the JetStream $JS.API.STREAM.RESTORE API", + "title": "io.nats.jetstream.api.v1.stream_restore_response", + "type": "object", + "required": [ + "deliver_subject" + ], + "properties": { + "deliver_subject": { + "type": "string", + "description": "The Subject to send restore chunks to", + "minLength": 1 + } + } + }, + "stream_snapshot_request": { + "description": "A request to the JetStream $JS.API.STREAM.SNAPSHOT API", + "title": "io.nats.jetstream.api.v1.stream_snapshot_request", + "type": "object", + "required": [ + "deliver_subject" + ], + "additionalProperties": false, + "properties": { + "deliver_subject": { + "type": "string", + "description": "The NATS subject where the snapshot will be delivered", + "minLength": 1 + }, + "no_consumers": { + "type": "boolean", + "description": "When true consumer states and configurations will not be present in the snapshot" + }, + "chunk_size": { + "type": "integer", + "description": "The size of data chunks to send to deliver_subject", + "minimum": 1024, + "$ref": "#/definitions/golang_int" + }, + "jsck": { + "type": "boolean", + "description": "Check all message's checksums prior to snapshot", + "default": false + } + } + }, + "stream_snapshot_response": { + "description": "A response from the JetStream $JS.API.STREAM.SNAPSHOT API", + "title": "io.nats.jetstream.api.v1.stream_snapshot_response", + "type": "object", + "required": [ + "config", + "state" + ], + "properties": { + "config": { + "$ref": "#/definitions/stream_configuration" + }, + "state": { + "$ref": "#/definitions/stream_state" + } + } + }, + "stream_template_create_request": { + "description": "A request to the JetStream $JS.API.STREAM.TEMPLATE.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_template_create_request", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_template_configuration" + } + ] + }, + "stream_template_create_response": { + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_template_create_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_template_info" + } + ] + }, + "stream_template_delete_response": { + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_template_delete_response", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + } + }, + "stream_template_info_response": { + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.INFO API", + "title": "io.nats.jetstream.api.v1.stream_template_info_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_template_info" + } + ] + }, + "stream_template_names_request": { + "description": "A request to the JetStream $JS.API.CONSUMER.LIST API", + "title": "io.nats.jetstream.api.v1.stream_template_names_request", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_request" + } + ] + }, + "stream_template_names_response": { + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.NAMES API", + "title": "io.nats.jetstream.api.v1.stream_template_names_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/iterable_response" + } + ], + "required": [ + "streams" + ], + "properties": { + "consumers": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "stream_update_request": { + "description": "A request to the JetStream $JS.API.STREAM.UPDATE API", + "title": "io.nats.jetstream.api.v1.stream_update_request", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_configuration" + } + ] + }, + "stream_update_response": { + "description": "A response from the JetStream $JS.API.STREAM.UPDATE API", + "title": "io.nats.jetstream.api.v1.stream_update_response", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/stream_info" + } + ] + } + } +} \ No newline at end of file diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/account_info_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/account_info_response.json new file mode 100644 index 000000000..babc7c7d6 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/account_info_response.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/account_info_response.json", + "description": "A response from the JetStream $JS.API.INFO API", + "title": "io.nats.jetstream.api.v1.account_info_response", + "type": "object", + "allOf": [{ + "$ref": "definitions.json#/definitions/account_stats" + }] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/account_purge_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/account_purge_response.json new file mode 100644 index 000000000..4a6887c6b --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/account_purge_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/meta_account_purge_response.json", + "description": "A response from the JetStream $JS.API.ACCOUNT.PURGE API", + "title": "io.nats.jetstream.api.v1.account_purge_response", + "type": "object", + "properties": { + "initiated": { + "type": "boolean", + "description": "If the purge operation was succesfully started", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_create_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_create_request.json new file mode 100644 index 000000000..f56bae0b6 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_create_request.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_create_request.json", + "description": "A request to the JetStream $JS.API.CONSUMER.CREATE and $JS.API.CONSUMER.DURABLE.CREATE APIs", + "title": "io.nats.jetstream.api.v1.consumer_create_request", + "type": "object", + "required": [ + "stream_name", + "config" + ], + "properties": { + "stream_name": { + "type": "string", + "description": "The name of the stream to create the consumer in" + }, + "config": { + "type": "object", + "description": "The consumer configuration", + "$ref": "definitions.json#/definitions/consumer_configuration" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_create_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_create_response.json new file mode 100644 index 000000000..a5d9235c3 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_create_response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_create_response.json", + "description": "A response from the JetStream $JS.API.CONSUMER.CREATE API", + "title": "io.nats.jetstream.api.v1.consumer_create_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/consumer_info" + } + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_delete_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_delete_response.json new file mode 100644 index 000000000..602b7b8a6 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_delete_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_delete_response.json", + "description": "A response from the JetStream $JS.API.CONSUMER.DELETE API", + "title": "io.nats.jetstream.api.v1.consumer_delete_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_getnext_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_getnext_request.json new file mode 100644 index 000000000..214f9fd8c --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_getnext_request.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_getnext_request.json", + "description": "A request to the JetStream $JS.API.CONSUMER.MSG.NEXT API", + "title": "io.nats.jetstream.api.v1.consumer_getnext_request", + "type": "object", + "properties": { + "expires": { + "$ref": "definitions.json#/definitions/golang_duration_nanos", + "description": "A duration from now when the pull should expire, stated in nanoseconds, 0 for no expiry" + }, + "batch": { + "$ref": "definitions.json#/definitions/golang_int", + "description": "How many messages the server should deliver to the requestor", + "minimum": 0, + "maximum": 256 + }, + "max_bytes": { + "$ref": "definitions.json#/definitions/golang_int", + "description": "Sends at most this many bytes to the requestor, limited by consumer configuration max_bytes", + "minimum": 0 + }, + "no_wait": { + "type": "boolean", + "description": "When true a response with a 404 status header will be returned when no messages are available" + }, + "idle_heartbeat": { + "$ref": "definitions.json#/definitions/golang_duration_nanos", + "description": "When not 0 idle heartbeats will be sent on this interval" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_info_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_info_response.json new file mode 100644 index 000000000..5996e5617 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_info_response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_info_response.json", + "description": "A response from the JetStream $JS.API.CONSUMER.INFO API", + "title": "io.nats.jetstream.api.v1.consumer_info_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/consumer_info" + } + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_leader_stepdown_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_leader_stepdown_response.json new file mode 100644 index 000000000..f76970da7 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_leader_stepdown_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_leader_stepdown_response.json", + "description": "A response from the JetStream $JS.API.CONSUMER.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.consumer_leader_stepdown_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean", + "description": "If the leader successfully stood down", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_list_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_list_request.json new file mode 100644 index 000000000..440c8b1c7 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_list_request.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_list_request.json", + "description": "A request to the JetStream $JS.API.CONSUMER.LIST API", + "title": "io.nats.jetstream.api.v1.consumer_list_request", + "type": "object", + "allOf": [{ + "$ref": "definitions.json#/definitions/iterable_request" + }] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_list_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_list_response.json new file mode 100644 index 000000000..7bdfb2d6c --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_list_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_list_response.json", + "description": "A response from the JetStream $JS.API.CONSUMER.LIST API", + "title": "io.nats.jetstream.api.v1.consumer_list_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/iterable_response" + } + ], + "required": ["consumers"], + "properties": { + "consumers": { + "description": "Full Consumer information for each known Consumer", + "type": "array", + "items": { + "$ref": "definitions.json#/definitions/consumer_info" + }, + "missing": { + "description": "In clustered environments gathering Consumer info might time out, this list would be a list of Consumers for which information was not obtainable", + "type": "array", + "items": { + "type": "string" + } + } + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_names_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_names_request.json new file mode 100644 index 000000000..6ca9ac5d3 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_names_request.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_names_request.json", + "description": "A request to the JetStream $JS.API.CONSUMER.NAMES API", + "title": "io.nats.jetstream.api.v1.consumer_names_request", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/iterable_request" + } + ], + "properties": { + "subject": { + "type": "string", + "description": "Filter the names to those consuming messages matching this subject or wildcard" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_names_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_names_response.json new file mode 100644 index 000000000..02976f285 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/consumer_names_response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/consumer_names_response.json", + "description": "A response from the JetStream $JS.API.CONSUMER.NAMES API", + "title": "io.nats.jetstream.api.v1.consumer_names_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/iterable_response" + } + ], + "required": ["consumers"], + "properties": { + "consumers": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/definitions.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/definitions.json new file mode 100644 index 000000000..3542fbe17 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/definitions.json @@ -0,0 +1,1097 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/definitions.json", + "title": "io.nats.jetstream.api.v1.definitions", + "description": "Shared definitions for the JetStream API", + "type": "object", + "definitions": { + "golang_duration_nanos": { + "$comment": "nanoseconds depicting a duration in time, signed 64 bit integer", + "$ref": "#/definitions/golang_int64" + }, + "golang_int": { + "$comment": "integer with a dynamic bit size depending on the platform the cluster runs on, can be up to 64bit", + "$ref": "#/definitions/golang_int64" + }, + "golang_uint64": { + "$comment": "unsigned 64 bit integer", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "golang_int32": { + "$comment": "signed 32 bit integer", + "type": "integer", + "maximum": 2147483647, + "minimum": -2147483648 + }, + "golang_int64": { + "$comment": "signed 64 bit integer", + "type": "integer", + "maximum": 9223372036854775807, + "minimum": -9223372036854775808 + }, + "golang_time": { + "$comment": "A point in time in RFC3339 format including timezone, though typically in UTC", + "type": "string", + "format": "date-time" + }, + "stream_source": { + "type": "object", + "description": "Defines a source where streams should be replicated from", + "required": ["name"], + "properties": { + "name": { + "description": "Stream name", + "$ref": "#/definitions/basic_name" + }, + "opt_start_seq": { + "description": "Sequence to start replicating from", + "$ref": "#/definitions/golang_uint64" + }, + "opt_start_time": { + "description": "Time stamp to start replicating from", + "$ref": "#/definitions/golang_time" + }, + "filter_subject": { + "description": "Replicate only a subset of messages based on filter", + "type": "string" + }, + "subject_transform_dest" : { + "description": "Map matching subjects according to this transform destination", + "type": "string" + }, + "external": { + "$ref": "#/definitions/external_stream_source" + } + } + }, + "external_stream_source": { + "required": ["api"], + "type": "object", + "description": "Configuration referencing a stream source in another account or JetStream domain", + "properties": { + "api": { + "type": "string", + "description": "The subject prefix that imports the other account/domain $JS.API.CONSUMER.> subjects" + }, + "deliver": { + "type": "string", + "description": "The delivery subject to use for the push consumer" + } + } + }, + "stream_source_info": { + "required": ["name","lag","active"], + "type": "object", + "description": "Information about an upstream stream source in a mirror", + "properties": { + "name": { + "type": "string", + "description": "The name of the Stream being replicated" + }, + "filter_subject": { + "type": "string", + "description": "The subject filter to apply to the messages" + }, + "subject_transform_dest": { + "type": "string", + "description": "The subject transform destination to apply to the messages" + }, + "lag": { + "$ref": "#/definitions/golang_uint64", + "description": "How many messages behind the mirror operation is", + "minimum": 0 + }, + "active": { + "description": "When last the mirror had activity, in nanoseconds. Value will be -1 when there has been no activity.", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": -1 + }, + "external": { + "$ref": "#/definitions/external_stream_source" + }, + "error": { + "$ref": "#/definitions/api_error" + } + } + }, + "lost_stream_data": { + "type": "object", + "description": "Records messages that were damaged and unrecoverable", + "properties": { + "msgs": { + "type": ["array","null"], + "description": "The messages that were lost", + "items": { + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + } + }, + "bytes": { + "description": "The number of bytes that were lost", + "$ref": "#/definitions/golang_uint64" + } + } + }, + "placement": { + "type": "object", + "description": "Placement requirements for a stream", + "required": ["cluster"], + "properties": { + "cluster": { + "type": "string", + "description": "The desired cluster name to place the stream" + }, + "tags": { + "description": "Tags required on servers hosting this stream", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "peer_info": { + "type": "object", + "required": ["name", "current", "active"], + "properties": { + "name": { + "description": "The server name of the peer", + "type": "string", + "minimum": 1 + }, + "current": { + "description": "Indicates if the server is up to date and synchronised", + "type": "boolean", + "default": false + }, + "active": { + "description": "Nanoseconds since this peer was last seen", + "type": "number" + }, + "offline": { + "description": "Indicates the node is considered offline by the group", + "type": "boolean", + "default": false + }, + "lag": { + "description": "How many uncommitted operations this peer is behind the leader", + "type": "integer", + "minimum": 0 + } + } + }, + "cluster_info": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The cluster name" + }, + "leader": { + "type": "string", + "description": "The server name of the RAFT leader" + }, + "replicas": { + "type": "array", + "description": "The members of the RAFT cluster", + "items": { + "$ref": "#/definitions/peer_info" + } + } + } + }, + "api_stats": { + "type": "object", + "required": ["total", "errors"], + "properties": { + "total": { + "description": "Total number of API requests received for this account", + "minimum": 0, + "type": "integer" + }, + "errors": { + "description": "API requests that resulted in an error response", + "minimum": 0, + "type": "integer" + } + } + }, + "tier": { + "type": "object", + "required": [ + "memory", + "storage", + "streams", + "limits", + "consumers" + ], + "properties": { + "memory": { + "description": "Memory Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "storage": { + "description": "File Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "streams": { + "description": "Number of active Streams", + "type": "integer", + "minimum": 0 + }, + "consumers": { + "description": "Number of active Consumers", + "type": "integer", + "minimum": 0 + }, + "limits": { + "$ref": "#/definitions/account_limits" + } + } + }, + "account_stats": { + "type": "object", + "required": ["memory", "storage", "streams", "limits", "api", "consumers"], + "properties": { + "memory": { + "description": "Memory Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "storage": { + "description": "File Storage being used for Stream Message storage", + "type": "integer", + "minimum": 0 + }, + "streams": { + "description": "Number of active Streams", + "type": "integer", + "minimum": 0 + }, + "consumers": { + "description": "Number of active Consumers", + "type": "integer", + "minimum": 0 + }, + "domain": { + "description": "The JetStream domain this account is in", + "type": "string" + }, + "limits": { + "$ref": "#/definitions/account_limits" + }, + "tiers": { + "type": "object", + "patternProperties": { + ".*": { + "$ref": "#/definitions/tier" + } + } + }, + "api": { + "$ref": "#/definitions/api_stats" + } + } + }, + "account_limits": { + "type": "object", + "additionalProperties": false, + "required": ["max_consumers", "max_memory", "max_storage", "max_streams"], + "properties": { + "max_memory": { + "type": "integer", + "description": "The maximum amount of Memory storage Stream Messages may consume", + "minimum": -1 + }, + "max_storage": { + "type": "integer", + "description": "The maximum amount of File storage Stream Messages may consume", + "minimum": -1 + }, + "max_streams": { + "type": "integer", + "description": "The maximum number of Streams an account can create", + "minimum": -1 + }, + "max_consumers": { + "type": "integer", + "description": "The maximum number of Consumer an account can create", + "minimum": -1 + }, + "max_bytes_required": { + "type": "boolean", + "description": "Indicates if Streams created in this account requires the max_bytes property set", + "default": false + }, + "max_ack_pending": { + "type": "integer", + "description": "The maximum number of outstanding ACKs any consumer may configure" + }, + "memory_max_stream_bytes": { + "type": "integer", + "description": "The maximum size any single memory stream may be", + "minimum": -1, + "default": -1 + }, + "storage_max_stream_bytes": { + "type": "integer", + "description": "The maximum size any single storage based stream may be", + "minimum": -1, + "default": -1 + } + } + }, + "stored_message": { + "type": "object", + "additionalProperties": false, + "required": ["subject", "seq", "time"], + "properties": { + "subject": { + "type": "string", + "description": "The subject the message was originally received on", + "minLength": 1 + }, + "seq": { + "description": "The sequence number of the message in the Stream", + "$ref": "#/definitions/golang_uint64" + }, + "data": { + "type": "string", + "description": "The base64 encoded payload of the message body", + "minLength": 0 + }, + "time": { + "type": "string", + "description": "The time the message was received" + }, + "hdrs": { + "type": "string", + "description": "Base64 encoded headers for the message" + } + } + }, + "iterable_request": { + "type": "object", + "additionalProperties": false, + "required": ["offset"], + "properties": { + "offset": { + "type": "integer", + "minimum": 0 + } + } + }, + "iterable_response": { + "type": "object", + "required": ["total", "offset", "limit"], + "properties": { + "total": { + "type": "integer", + "minimum": 0 + }, + "offset": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 0 + } + } + }, + "error_response": { + "type": "object", + "required": ["error"], + "properties": { + "error": { + "$ref": "#/definitions/api_error" + } + } + }, + "api_error": { + "type": "object", + "required": ["code"], + "properties": { + "code": { + "type": "integer", + "description": "HTTP like error code in the 300 to 500 range", + "minimum": 300, + "maximum": 699 + }, + "description": { + "type": "string", + "description": "A human friendly description of the error" + }, + "err_code": { + "type": "integer", + "description": "The NATS error code unique to each kind of error", + "minimum": 0, + "maximum": 65535 + } + } + }, + "basic_name": { + "type": "string", + "pattern": "^[^.*>]+$", + "minLength": 1 + }, + "sequence_info": { + "type": "object", + "additionalProperties": false, + "required": ["consumer_seq", "stream_seq"], + "properties": { + "consumer_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Consumer" + }, + "stream_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Stream" + }, + "last_active": { + "description": "The last time a message was delivered or acknowledged (for ack_floor)", + "$ref": "#/definitions/golang_time" + } + } + }, + "sequence_pair": { + "type": "object", + "additionalProperties": false, + "required": ["consumer_seq", "stream_seq"], + "properties": { + "consumer_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Consumer" + }, + "stream_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "The sequence number of the Stream" + } + } + }, + "consumer_info": { + "required": ["stream_name", "name", "config", "created", "delivered", "ack_floor", "num_ack_pending", "num_redelivered", "num_waiting", "num_pending"], + "type": "object", + "properties": { + "stream_name": { + "type": "string", + "description": "The Stream the consumer belongs to" + }, + "name": { + "type": "string", + "description": "A unique name for the consumer, either machine generated or the durable name" + }, + "ts": { + "description": "The server time the consumer info was created", + "$ref": "#/definitions/golang_time" + }, + "config": { + "$ref": "#/definitions/consumer_configuration" + }, + "created": { + "description": "The time the Consumer was created", + "$ref": "#/definitions/golang_time" + }, + "delivered": { + "description": "The last message delivered from this Consumer", + "$ref": "#/definitions/sequence_info" + }, + "ack_floor": { + "description": "The highest contiguous acknowledged message", + "$ref": "#/definitions/sequence_info" + }, + "num_ack_pending": { + "description": "The number of messages pending acknowledgement", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_redelivered": { + "description": "The number of redeliveries that have been performed", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_waiting": { + "description": "The number of pull consumers waiting for messages", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_pending": { + "description": "The number of messages left unconsumed in this Consumer", + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + }, + "cluster": { + "$ref": "#/definitions/cluster_info" + }, + "push_bound": { + "description": "Indicates if any client is connected and receiving messages from a push consumer", + "type": "boolean" + } + } + }, + "consumer_configuration": { + "required":[ + "deliver_policy", + "ack_policy", + "replay_policy" + ], + "properties": { + "deliver_policy": { + "type": "string", + "enum": [ + "all", + "last", + "new", + "by_start_sequence", + "by_start_time", + "last_per_subject" + ] + }, + "opt_start_seq": { + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + }, + "opt_start_time": { + "$ref": "#/definitions/golang_time" + }, + "durable_name": { + "description": "A unique name for a durable consumer", + "deprecationMessage": "Durable is deprecated. All consumers will have names. picked by clients.", + "$ref": "#/definitions/basic_name" + }, + "name": { + "description": "A unique name for a consumer", + "$ref": "#/definitions/basic_name" + }, + "description": { + "description": "A short description of the purpose of this consumer", + "type": "string", + "maxLength": 4096 + }, + "deliver_subject": { + "type": "string", + "minLength": 1 + }, + "ack_policy": { + "type": "string", + "enum": ["none", "all", "explicit"], + "default": "none" + }, + "ack_wait": { + "description": "How long (in nanoseconds) to allow messages to remain un-acknowledged before attempting redelivery", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 1, + "default": "30000000000" + }, + "max_deliver": { + "$ref": "#/definitions/golang_int", + "description": "The number of times a message will be redelivered to consumers if not acknowledged in time", + "default": -1 + }, + "filter_subject": { + "description": "Filter the stream by a single subjects", + "type": "string" + }, + "filter_subjects": { + "description": "Filter the stream by multiple subjects", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "replay_policy": { + "type": "string", + "enum": ["instant", "original"], + "default": "instant" + }, + "sample_freq": { + "type": "string" + }, + "rate_limit_bps": { + "$ref": "#/definitions/golang_uint64", + "description": "The rate at which messages will be delivered to clients, expressed in bit per second", + "minimum": 0 + }, + "max_ack_pending": { + "$ref": "#/definitions/golang_int", + "description": "The maximum number of messages without acknowledgement that can be outstanding, once this limit is reached message delivery will be suspended", + "default": 1000 + }, + "idle_heartbeat": { + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 0, + "description": "If the Consumer is idle for more than this many nano seconds a empty message with Status header 100 will be sent indicating the consumer is still alive" + }, + "flow_control": { + "type": "boolean", + "description": "For push consumers this will regularly send an empty mess with Status header 100 and a reply subject, consumers must reply to these messages to control the rate of message delivery" + }, + "max_waiting": { + "$ref": "#/definitions/golang_int", + "description": "The number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored", + "minimum": 0, + "default": 512 + }, + "direct": { + "type": "boolean", + "description": "Creates a special consumer that does not touch the Raft layers, not for general use by clients, internal use only", + "default": false + }, + "headers_only": { + "type": "boolean", + "default": false, + "description": "Delivers only the headers of messages in the stream and not the bodies. Additionally adds Nats-Msg-Size header to indicate the size of the removed payload" + }, + "max_batch": { + "type": "integer", + "description": "The largest batch property that may be specified when doing a pull on a Pull Consumer", + "default": 0 + }, + "max_expires": { + "description": "The maximum expires value that may be set when doing a pull on a Pull Consumer", + "$ref": "#/definitions/golang_duration_nanos", + "default": 0 + }, + "max_bytes": { + "description": "The maximum bytes value that maybe set when dong a pull on a Pull Consumer", + "$ref": "definitions.json#/definitions/golang_int", + "minimum": 0, + "default": 0 + }, + "inactive_threshold": { + "description": "Duration that instructs the server to cleanup ephemeral consumers that are inactive for that long", + "$ref": "#/definitions/golang_duration_nanos", + "default": 0 + }, + "backoff": { + "description": "List of durations in Go format that represents a retry time scale for NaK'd messages", + "type": "array", + "items": { + "$ref": "#/definitions/golang_duration_nanos" + } + }, + "num_replicas": { + "description": "When set do not inherit the replica count from the stream but specifically set it to this amount", + "type": "integer", + "$ref": "#/definitions/golang_int", + "minimum": 0, + "maximum": 5 + }, + "mem_storage": { + "description": "Force the consumer state to be kept in memory rather than inherit the setting from the stream", + "type": "boolean", + "default": false + }, + "metadata": { + "description": "Additional metadata for the Consumer", + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "anyOf": [ + { + "if": { + "properties": { + "deliver_policy": { "const": "by_start_sequence" } + } + }, + "then": { "required": ["opt_start_seq"] } + }, + { + "if": { + "properties": { + "deliver_policy": { "const": "opt_start_time" } + } + }, + "then": { "required": ["opt_start_seq"] } + } + ] + }, + "stream_info": { + "type": "object", + "required": ["config", "state", "created"], + "properties": { + "config": { + "type": "object", + "description": "The active configuration for the Stream", + "$ref": "#/definitions/stream_configuration" + }, + "state": { + "type": "object", + "description": "Detail about the current State of the Stream", + "$ref": "#/definitions/stream_state" + }, + "created": { + "description": "Timestamp when the stream was created", + "$ref": "#/definitions/golang_time" + }, + "ts": { + "description": "The server time the stream info was created", + "$ref": "#/definitions/golang_time" + }, + "cluster": { + "$ref": "#/definitions/cluster_info" + }, + "mirror": { + "$ref": "#/definitions/stream_source_info" + }, + "sources": { + "type": "array", + "description": "Streams being sourced into this Stream", + "items": { + "$ref": "#/definitions/stream_source_info" + } + }, + "alternates": { + "type": "array", + "description": "List of mirrors sorted by priority", + "items": { + "$ref": "#/definitions/stream_alternate" + } + } + } + }, + "stream_alternate": { + "type": "object", + "description": "An alternate location to read mirrored data", + "required": ["name", "cluster"], + "properties": { + "name": { + "type": "string", + "description": "The mirror stream name" + }, + "cluster": { + "type": "string", + "description": "The name of the cluster holding the stream" + }, + "domain": { + "type": "string", + "description": "The domain holding the string" + } + } + }, + "stream_state": { + "type": "object", + "additionalProperties": false, + "required": ["messages", "bytes", "first_seq", "last_seq", "consumer_count"], + "properties": { + "messages": { + "$ref": "#/definitions/golang_uint64", + "description": "Number of messages stored in the Stream", + "minimum": 0 + }, + "bytes": { + "$ref": "#/definitions/golang_uint64", + "description": "Combined size of all messages in the Stream", + "minimum": 0 + }, + "first_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "Sequence number of the first message in the Stream", + "minimum": 0 + }, + "first_ts": { + "type": "string", + "description": "The timestamp of the first message in the Stream" + }, + "last_seq": { + "$ref": "#/definitions/golang_uint64", + "description": "Sequence number of the last message in the Stream", + "minimum": 0 + }, + "last_ts": { + "type": "string", + "description": "The timestamp of the last message in the Stream" + }, + "deleted": { + "description": "IDs of messages that were deleted using the Message Delete API or Interest based streams removing messages out of order", + "type": "array", + "minLength": 0, + "items": { + "$ref": "#/definitions/golang_uint64", + "minimum": 0 + } + }, + "subjects": { + "description": "Subjects and their message counts when a subjects_filter was set", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/golang_uint64" + } + }, + "num_subjects": { + "description": "The number of unique subjects held in the stream", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "num_deleted": { + "description": "The number of deleted messages", + "$ref": "#/definitions/golang_int", + "minimum": 0 + }, + "lost": { + "$ref": "#/definitions/lost_stream_data" + }, + "consumer_count": { + "$ref": "#/definitions/golang_int", + "description": "Number of Consumers attached to the Stream", + "minimum": 0 + } + } + }, + "subject_transform" : { + "type": "object", + "description": "Subject transform to apply to matching messages going into the stream", + "required": ["dest"], + "properties": { + "src": { + "type": "string", + "description": "The subject transform source" + }, + "dest": { + "type": "string", + "description": "The subject transform destination" + } + } + }, + "republish": { + "type": "object", + "description": "Rules for republishing messages from a stream with subject mapping onto new subjects for partitioning and more", + "required": ["src","dest"], + "properties": { + "src": { + "type": "string", + "description": "The source subject to republish" + }, + "dest": { + "type": "string", + "description": "The destination to publish to" + }, + "headers_only": { + "type": "boolean", + "description": "Only send message headers, no bodies", + "default": false + } + } + }, + "stream_configuration": { + "type": "object", + "required":[ + "retention", + "max_consumers", + "max_msgs", + "max_bytes", + "max_age", + "storage", + "num_replicas" + ], + "additionalProperties": false, + "properties": { + "name": { + "description": "A unique name for the Stream, empty for Stream Templates.", + "type": "string", + "pattern": "^[^.*>]*$", + "minLength": 0 + }, + "description": { + "description": "A short description of the purpose of this stream", + "type": "string", + "maxLength": 4096 + }, + "subjects": { + "description": "A list of subjects to consume, supports wildcards. Must be empty when a mirror is configured. May be empty when sources are configured.", + "type": "array", + "minLength": 0, + "items": { + "type": "string" + } + }, + "subject_transform" : { + "description": "Subject transform to apply to matching messages", + "$ref": "#/definitions/subject_transform" + }, + "retention": { + "description": "How messages are retained in the Stream, once this is exceeded old messages are removed.", + "type": "string", + "enum": ["limits", "interest", "workqueue"], + "default": "limits" + }, + "max_consumers": { + "description": "How many Consumers can be defined for a given Stream. -1 for unlimited.", + "$ref": "#/definitions/golang_int", + "minimum": -1, + "default": -1 + }, + "max_msgs": { + "description": "How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.", + "$ref": "#/definitions/golang_int64", + "minimum": -1, + "default": -1 + }, + "max_msgs_per_subject": { + "description": "For wildcard streams ensure that for every unique subject this many messages are kept - a per subject retention limit", + "$ref": "#/definitions/golang_int64", + "minimum": -1, + "default": -1 + }, + "max_bytes": { + "description": "How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.", + "$ref": "#/definitions/golang_int64", + "minimum": -1, + "default": -1 + }, + "max_age": { + "description": "Maximum age of any message in the stream, expressed in nanoseconds. 0 for unlimited.", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 0, + "default": 0 + }, + "max_msg_size": { + "description": "The largest message that will be accepted by the Stream. -1 for unlimited.", + "$ref": "#/definitions/golang_int32", + "minimum": -1, + "default": -1 + }, + "storage": { + "description": "The storage backend to use for the Stream.", + "type": "string", + "enum": ["file", "memory"], + "default": "file" + }, + "compression": { + "description": "Optional compression algorithm used for the Stream.", + "type": "string", + "enum": ["none", "s2"], + "default": "none" + }, + "num_replicas": { + "description": "How many replicas to keep for each message.", + "$ref": "#/definitions/golang_int", + "minimum": 1, + "default": 1, + "maximum": 5 + }, + "no_ack": { + "description": "Disables acknowledging messages that are received by the Stream.", + "type": "boolean", + "default": false + }, + "template_owner": { + "description": "When the Stream is managed by a Stream Template this identifies the template that manages the Stream.", + "type": "string" + }, + "discard": { + "description": "When a Stream reach it's limits either old messages are deleted or new ones are denied", + "type": "string", + "enum": ["old", "new"], + "default": "old" + }, + "duplicate_window": { + "description": "The time window to track duplicate messages for, expressed in nanoseconds. 0 for default", + "$ref": "#/definitions/golang_duration_nanos", + "minimum": 0, + "default": 0 + }, + "placement": { + "description": "Placement directives to consider when placing replicas of this stream, random placement when unset", + "$ref": "#/definitions/placement" + }, + "mirror": { + "description": "Maintains a 1:1 mirror of another stream with name matching this property. When a mirror is configured subjects and sources must be empty.", + "$ref": "#/definitions/stream_source" + }, + "sources": { + "type": "array", + "description": "List of Stream names to replicate into this Stream", + "items": { + "$ref": "#/definitions/stream_source" + } + }, + "sealed": { + "type": "boolean", + "default": false, + "description": "Sealed streams do not allow messages to be deleted via limits or API, sealed streams can not be unsealed via configuration update. Can only be set on already created streams via the Update API" + }, + "deny_delete": { + "type": "boolean", + "default": false, + "description": "Restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true" + }, + "deny_purge": { + "type": "boolean", + "default": false, + "description": "Restricts the ability to purge messages from a stream via the API. Cannot be change once set to true" + }, + "allow_rollup_hdrs": { + "type": "boolean", + "default": false, + "description": "Allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message" + }, + "allow_direct": { + "type": "boolean", + "default": false, + "description": "Allow higher performance, direct access to get individual messages" + }, + "mirror_direct": { + "type": "boolean", + "default": false, + "description": "Allow higher performance, direct access for mirrors as well" + }, + "republish": { + "$ref": "#/definitions/republish" + }, + "discard_new_per_subject": { + "type": "boolean", + "description": "When discard policy is new and the stream is one with max messages per subject set, this will apply the new behavior to every subject. Essentially turning discard new from maximum number of subjects into maximum number of messages in a subject.", + "default": false + }, + "metadata": { + "description": "Additional metadata for the Stream", + "type": "object", + "additionalProperties": { "type": "string" } + } + } + }, + "stream_template_info": { + "type": "object", + "required": ["config", "streams"], + "properties": { + "config": { + "$ref": "#/definitions/stream_template_configuration" + }, + "streams": { + "description": "List of Streams managed by this Template", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "stream_template_configuration": { + "type": "object", + "additionalProperties": false, + "required": ["name", "config", "max_streams"], + "properties": { + "name": { + "type": "string", + "description": "A unique name for the Template" + }, + "config": { + "description": "The template configuration to create Streams with", + "$ref": "#/definitions/stream_configuration" + }, + "max_streams": { + "type": "integer", + "description": "The maximum number of streams to allow using this Template", + "minimum": -1 + } + } + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_leader_stepdown_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_leader_stepdown_request.json new file mode 100644 index 000000000..f6c0f99d5 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_leader_stepdown_request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/meta_leader_stepdown_request.json", + "description": "A request to the JetStream $JS.API.META.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.meta_leader_stepdown_request", + "type": "object", + "properties": { + "placement": { + "$ref": "definitions.json#/definitions/placement" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_leader_stepdown_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_leader_stepdown_response.json new file mode 100644 index 000000000..0686828d3 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_leader_stepdown_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/meta_leader_stepdown_response.json", + "description": "A response from the JetStream $JS.API.META.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.meta_leader_stepdown_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean", + "description": "If the leader successfully stood down", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_server_remove_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_server_remove_request.json new file mode 100644 index 000000000..aa1f61821 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_server_remove_request.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/meta_server_remove_request.json", + "description": "A request to the JetStream $JS.API.SERVER.REMOVE API", + "title": "io.nats.jetstream.api.v1.meta_server_remove_request", + "type": "object", + "properties": { + "peer": { + "type": "string", + "description": "The Name of the server to remove from the meta group" + + }, + "peer_id": { + "type": "string", + "description": "Peer ID of the peer to be removed. If specified this is used instead of the server name" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_server_remove_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_server_remove_response.json new file mode 100644 index 000000000..eed8df7b9 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/meta_server_remove_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/meta_server_remove_response.json", + "description": "A response from the JetStream $JS.API.SERVER.REMOVE API", + "title": "io.nats.jetstream.api.v1.meta_server_remove_response", + "required": ["success"], + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "If the peer was successfully removed", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/pub_ack_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/pub_ack_response.json new file mode 100644 index 000000000..0f1a11340 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/pub_ack_response.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/pub_ack_response.json", + "description": "A response received when publishing a message", + "title": "io.nats.jetstream.api.v1.pub_ack_response", + "type": "object", + "required": ["stream"], + "additionalProperties": false, + "properties": { + "error": { + "$ref": "definitions.json#/definitions/api_error" + }, + "stream": { + "type": "string", + "description": "The name of the stream that received the message", + "minLength": 1 + }, + "seq": { + "type": "integer", + "description": "If successful this will be the sequence the message is stored at", + "$ref": "definitions.json#/definitions/golang_uint64" + }, + "duplicate":{ + "type": "boolean", + "description": "Indicates that the message was not stored due to the Nats-Msg-Id header and duplicate tracking", + "default": false + }, + "domain": { + "type": "string", + "description": "If the Stream accepting the message is in a JetStream server configured for a domain this would be that domain" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_create_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_create_request.json new file mode 100644 index 000000000..429b0935c --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_create_request.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_create_request.json", + "description": "A request to the JetStream $JS.API.STREAM.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_create_request", + "type": "object", + "allOf": [ + {"$ref": "definitions.json#/definitions/stream_configuration"} + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_create_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_create_response.json new file mode 100644 index 000000000..00c7313a5 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_create_response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_create_response.json", + "description": "A response from the JetStream $JS.API.STREAM.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_create_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/stream_info" + } + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_delete_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_delete_response.json new file mode 100644 index 000000000..8f3d4cf9f --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_delete_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_delete_response.json", + "description": "A response from the JetStream $JS.API.STREAM.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_delete_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_info_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_info_request.json new file mode 100644 index 000000000..00ec4f84c --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_info_request.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_info_request.json", + "description": "A request to the JetStream $JS.API.STREAM.INFO API", + "title": "io.nats.jetstream.api.v1.stream_info_request", + "type": "object", + "properties": { + "deleted_details": { + "type": "boolean", + "description": "When true will result in a full list of deleted message IDs being returned in the info response" + }, + "subjects_filter": { + "type": "string", + "description": "When set will return a list of subjects and how many messages they hold for all matching subjects. Filter is a standard NATS subject wildcard pattern." + }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Paging offset when retrieving pages of subjet details" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_info_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_info_response.json new file mode 100644 index 000000000..d4debfcd1 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_info_response.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_info_response.json", + "description": "A response from the JetStream $JS.API.STREAM.INFO API", + "title": "io.nats.jetstream.api.v1.stream_info_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/stream_info" + } + ], + "properties": { + "total": { + "type": "integer", + "minimum": 0 + }, + "offset": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 0 + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_leader_stepdown_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_leader_stepdown_response.json new file mode 100644 index 000000000..db845b8e1 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_leader_stepdown_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_leader_stepdown_response.json", + "description": "A response from the JetStream $JS.API.STREAM.LEADER.STEPDOWN API", + "title": "io.nats.jetstream.api.v1.stream_leader_stepdown_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean", + "description": "If the leader successfully stood down", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_list_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_list_request.json new file mode 100644 index 000000000..2b3122b9a --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_list_request.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_list_request.json", + "description": "A request to the JetStream $JS.API.STREAM.LIST API", + "title": "io.nats.jetstream.api.v1.stream_list_request", + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Limit the list to streams matching this subject filter" + }, + "offset": { + "type": "integer", + "minimum": 0 + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_list_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_list_response.json new file mode 100644 index 000000000..0cb3cf360 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_list_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_list_response.json", + "description": "A response from the JetStream $JS.API.STREAM.LIST API", + "title": "io.nats.jetstream.api.v1.stream_list_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/iterable_response" + } + ], + "required": ["streams"], + "properties": { + "streams": { + "description": "Full Stream information for each known Stream", + "type": "array", + "items": { + "$ref": "definitions.json#/definitions/stream_info" + } + }, + "missing": { + "description": "In clustered environments gathering Stream info might time out, this list would be a list of Streams for which information was not obtainable", + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_delete_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_delete_request.json new file mode 100644 index 000000000..703018c85 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_delete_request.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_msg_delete_request.json", + "description": "A request to the JetStream $JS.API.STREAM.MSG.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_msg_delete_request", + "type": "object", + "required": ["seq"], + "properties": { + "seq": { + "description": "Stream sequence number of the message to delete", + "$ref": "definitions.json#/definitions/golang_uint64" + }, + "no_erase": { + "type": "boolean", + "description": "Default will securely remove a message and rewrite the data with random data, set this to true to only remove the message" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_delete_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_delete_response.json new file mode 100644 index 000000000..e40ef54f5 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_delete_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_msg_delete_response.json", + "description": "A response from the JetStream $JS.API.STREAM.MSG.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_msg_delete_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_get_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_get_request.json new file mode 100644 index 000000000..bd2c9243f --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_get_request.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_msg_get_request.json", + "description": "A request to the JetStream $JS.API.STREAM.MSG.GET API", + "title": "io.nats.jetstream.api.v1.stream_msg_get_request", + "type": "object", + "properties": { + "seq": { + "type": "integer", + "description": "Stream sequence number of the message to retrieve, cannot be combined with last_by_subj" + }, + "last_by_subj": { + "type": "string", + "description": "Retrieves the last message for a given subject, cannot be combined with seq" + }, + "next_by_subj": { + "type": "string", + "description": "Combined with sequence gets the next message for a subject with the given sequence or higher" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_get_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_get_response.json new file mode 100644 index 000000000..3e9a08c8c --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_msg_get_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_msg_get_response.json", + "description": "A response from the JetStream $JS.API.STREAM.MSG.GET API", + "title": "io.nats.jetstream.api.v1.stream_msg_get_response", + "type": "object", + "required": ["message"], + "properties": { + "message": { + "$ref": "definitions.json#/definitions/stored_message" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_names_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_names_request.json new file mode 100644 index 000000000..68cb21556 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_names_request.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_names_request.json", + "description": "A request to the JetStream $JS.API.STREAM.NAMES API", + "title": "io.nats.jetstream.api.v1.stream_names_request", + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Limit the list to streams matching this subject filter" + }, + "offset": { + "type": "integer", + "minimum": 0 + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_names_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_names_response.json new file mode 100644 index 000000000..4528c32d5 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_names_response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_names_response.json", + "description": "A response from the JetStream $JS.API.STREAM.NAMES API", + "title": "io.nats.jetstream.api.v1.stream_names_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/iterable_response" + } + ], + "required": ["streams"], + "properties": { + "consumers": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_purge_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_purge_request.json new file mode 100644 index 000000000..a28ffd37f --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_purge_request.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_purge_request.json", + "description": "A request to the JetStream $JS.API.STREAM.PURGE API", + "title": "io.nats.jetstream.api.v1.stream_purge_request", + "type": "object", + "properties": { + "filter": { + "type": "string", + "description": "Restrict purging to messages that match this subject" + }, + "seq": { + "description": "Purge all messages up to but not including the message with this sequence. Can be combined with subject filter but not the keep option", + "$ref": "definitions.json#/definitions/golang_uint64" + }, + "keep": { + "description": "Ensures this many messages are present after the purge. Can be combined with the subject filter but not the sequence", + "$ref": "definitions.json#/definitions/golang_uint64" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_purge_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_purge_response.json new file mode 100644 index 000000000..0a533048c --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_purge_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_purge_response.json", + "description": "A response from the JetStream $JS.API.STREAM.PURGE API", + "title": "io.nats.jetstream.api.v1.stream_purge_response", + "type": "object", + "required": ["success", "purged"], + "properties": { + "success": { + "type": "boolean" + }, + "purged": { + "description": "Number of messages purged from the Stream", + "$ref": "definitions.json#/definitions/golang_uint64" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_remove_peer_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_remove_peer_request.json new file mode 100644 index 000000000..f69a25762 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_remove_peer_request.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_remove_peer_request.json", + "description": "A request to the JetStream $JS.API.STREAM.PEER.REMOVE API", + "title": "io.nats.jetstream.api.v1.stream_remove_peer_request", + "type": "object", + "required": ["peer"], + "additionalProperties": false, + "properties": { + "peer": { + "type": "string", + "description": "Server name of the peer to remove", + "minLength": 1 + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_remove_peer_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_remove_peer_response.json new file mode 100644 index 000000000..d7282c10b --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_remove_peer_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_remove_peer_response.json", + "description": "A response from the JetStream $JS.API.STREAM.PEER.REMOVE API", + "title": "io.nats.jetstream.api.v1.stream_remove_peer_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean", + "description": "If the peer was successfully removed", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_restore_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_restore_request.json new file mode 100644 index 000000000..f64b319a2 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_restore_request.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_restore_request.json", + "description": "A response from the JetStream $JS.API.STREAM.RESTORE API", + "title": "io.nats.jetstream.api.v1.stream_restore_request", + "type": "object", + "required": ["config", "state"], + "properties": { + "config": { + "$ref": "definitions.json#/definitions/stream_configuration" + }, + "state": { + "$ref": "definitions.json#/definitions/stream_state" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_restore_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_restore_response.json new file mode 100644 index 000000000..c3765f8d3 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_restore_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_restore_response.json", + "description": "A response from the JetStream $JS.API.STREAM.RESTORE API", + "title": "io.nats.jetstream.api.v1.stream_restore_response", + "type": "object", + "required": ["deliver_subject"], + "properties": { + "deliver_subject": { + "type": "string", + "description": "The Subject to send restore chunks to", + "minLength": 1 + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_snapshot_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_snapshot_request.json new file mode 100644 index 000000000..76f1d9997 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_snapshot_request.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_snapshot_request.json", + "description": "A request to the JetStream $JS.API.STREAM.SNAPSHOT API", + "title": "io.nats.jetstream.api.v1.stream_snapshot_request", + "type": "object", + "required": ["deliver_subject"], + "additionalProperties": false, + "properties": { + "deliver_subject": { + "type": "string", + "description": "The NATS subject where the snapshot will be delivered", + "minLength": 1 + }, + "no_consumers": { + "type": "boolean", + "description": "When true consumer states and configurations will not be present in the snapshot" + }, + "chunk_size": { + "type": "integer", + "description": "The size of data chunks to send to deliver_subject", + "minimum": 1024, + "$ref": "definitions.json#/definitions/golang_int" + }, + "jsck": { + "type": "boolean", + "description": "Check all message's checksums prior to snapshot", + "default": false + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_snapshot_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_snapshot_response.json new file mode 100644 index 000000000..cd8a15f38 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_snapshot_response.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_snapshot_response.json", + "description": "A response from the JetStream $JS.API.STREAM.SNAPSHOT API", + "title": "io.nats.jetstream.api.v1.stream_snapshot_response", + "type": "object", + "required": ["config", "state"], + "properties": { + "config": { + "$ref": "definitions.json#/definitions/stream_configuration" + }, + "state": { + "$ref": "definitions.json#/definitions/stream_state" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_configuration.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_configuration.json new file mode 100644 index 000000000..e6f797036 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_configuration.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_configuration.json", + "description": "The data structure that describe the configuration of a NATS JetStream Stream Template", + "title": "io.nats.jetstream.api.v1.stream_template_configuration", + "type":"object", + "required":[ + "name", + "config", + "max_streams" + ], + "additionalProperties": false, + "properties": { + "name": { + "description": "A unique name for the Stream Template.", + "$ref": "definitions.json#/definitions/basic_name" + }, + "max_streams": { + "description": "The maximum number of Streams this Template can create, -1 for unlimited.", + "minimum": -1, + "default": -1, + "$ref": "definitions.json#/definitions/golang_int32" + }, + "config": { + "$ref": "definitions.json#/definitions/stream_configuration" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_create_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_create_request.json new file mode 100644 index 000000000..06041e93d --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_create_request.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_create_request.json", + "description": "A request to the JetStream $JS.API.STREAM.TEMPLATE.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_template_create_request", + "type": "object", + "allOf": [ + {"$ref": "definitions.json#/definitions/stream_template_configuration"} + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_create_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_create_response.json new file mode 100644 index 000000000..0fd1f8d78 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_create_response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_create_response.json", + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.CREATE API", + "title": "io.nats.jetstream.api.v1.stream_template_create_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/stream_template_info" + } + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_delete_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_delete_response.json new file mode 100644 index 000000000..39513a34f --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_delete_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_delete_response.json", + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.DELETE API", + "title": "io.nats.jetstream.api.v1.stream_template_delete_response", + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean" + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_info_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_info_response.json new file mode 100644 index 000000000..935b232f3 --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_info_response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_info_response.json", + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.INFO API", + "title": "io.nats.jetstream.api.v1.stream_template_info_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/stream_template_info" + } + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_names_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_names_request.json new file mode 100644 index 000000000..afd316e1a --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_names_request.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_names_request.json", + "description": "A request to the JetStream $JS.API.CONSUMER.LIST API", + "title": "io.nats.jetstream.api.v1.stream_template_names_request", + "type": "object", + "allOf": [ + {"$ref": "definitions.json#/definitions/iterable_request"} + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_names_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_names_response.json new file mode 100644 index 000000000..26048f57d --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_template_names_response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_template_names_response.json", + "description": "A response from the JetStream $JS.API.STREAM.TEMPLATE.NAMES API", + "title": "io.nats.jetstream.api.v1.stream_template_names_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/iterable_response" + } + ], + "required": ["streams"], + "properties": { + "consumers": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_update_request.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_update_request.json new file mode 100644 index 000000000..fadecdfba --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_update_request.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_update_request.json", + "description": "A request to the JetStream $JS.API.STREAM.UPDATE API", + "title": "io.nats.jetstream.api.v1.stream_update_request", + "type": "object", + "allOf": [ + {"$ref": "definitions.json#/definitions/stream_configuration"} + ] +} diff --git a/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_update_response.json b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_update_response.json new file mode 100644 index 000000000..f9f22d8af --- /dev/null +++ b/tools/Schema.Generation/schema_source/jetstream/api/v1/stream_update_response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://nats.io/schemas/jetstream/api/v1/stream_update_response.json", + "description": "A response from the JetStream $JS.API.STREAM.UPDATE API", + "title": "io.nats.jetstream.api.v1.stream_update_response", + "type": "object", + "allOf": [ + { + "$ref": "definitions.json#/definitions/stream_info" + } + ] +}