1use std::fmt;
2
3use serde::Deserialize;
4use serde::Serialize;
5use ursula_shard::ShardPlacement;
6use ursula_stream::StreamCommand;
7use ursula_stream::StreamSnapshot;
8
9use crate::request::AppendBatchRequest;
10use crate::request::AppendExternalRequest;
11use crate::request::AppendRequest;
12use crate::request::CloseStreamRequest;
13use crate::request::CreateStreamExternalRequest;
14use crate::request::CreateStreamRequest;
15use crate::request::DeleteStreamRequest;
16use crate::request::FlushColdRequest;
17use crate::request::PublishSnapshotRequest;
18use crate::request::StreamAppendCount;
19use crate::request::UpdateStreamAttrsRequest;
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct GroupSnapshot {
23 pub placement: ShardPlacement,
24 pub group_commit_index: u64,
25 pub stream_snapshot: StreamSnapshot,
26 pub stream_append_counts: Vec<StreamAppendCount>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[expect(
35 clippy::large_enum_variant,
36 reason = "commands are transient write-path values; boxing `Stream` would \
37 add a heap allocation to every replicated write"
38)]
39pub enum GroupWriteCommand {
40 Stream(StreamCommand),
41 Batch { commands: Vec<StreamCommand> },
42}
43
44impl From<StreamCommand> for GroupWriteCommand {
45 fn from(command: StreamCommand) -> Self {
46 Self::Stream(command)
47 }
48}
49
50impl From<CreateStreamRequest> for StreamCommand {
51 fn from(request: CreateStreamRequest) -> Self {
52 Self::CreateStream {
53 stream_id: request.stream_id,
54 content_type: request.content_type,
55 initial_payload: request.initial_payload,
56 close_after: request.close_after,
57 stream_seq: request.stream_seq,
58 producer: request.producer,
59 stream_ttl_seconds: request.stream_ttl_seconds,
60 stream_expires_at_ms: request.stream_expires_at_ms,
61 attrs: request.attrs,
62 now_ms: request.now_ms,
63 }
64 }
65}
66
67impl From<CreateStreamExternalRequest> for StreamCommand {
68 fn from(request: CreateStreamExternalRequest) -> Self {
69 Self::CreateExternal {
70 stream_id: request.stream_id,
71 content_type: request.content_type,
72 initial_payload: request.initial_payload,
73 record_ends: request.record_ends,
74 close_after: request.close_after,
75 stream_seq: request.stream_seq,
76 producer: request.producer,
77 stream_ttl_seconds: request.stream_ttl_seconds,
78 stream_expires_at_ms: request.stream_expires_at_ms,
79 attrs: request.attrs,
80 now_ms: request.now_ms,
81 }
82 }
83}
84
85impl From<UpdateStreamAttrsRequest> for StreamCommand {
86 fn from(request: UpdateStreamAttrsRequest) -> Self {
87 Self::UpdateStreamAttrs {
88 stream_id: request.stream_id,
89 attrs: request.attrs,
90 now_ms: request.now_ms,
91 }
92 }
93}
94
95impl From<AppendRequest> for StreamCommand {
96 fn from(request: AppendRequest) -> Self {
97 Self::Append {
98 stream_id: request.stream_id,
99 content_type: Some(request.content_type),
100 payload: request.payload,
101 close_after: request.close_after,
102 stream_seq: request.stream_seq,
103 producer: request.producer,
104 now_ms: request.now_ms,
105 record_match: request.record_match,
106 }
107 }
108}
109
110impl From<AppendExternalRequest> for StreamCommand {
111 fn from(request: AppendExternalRequest) -> Self {
112 Self::AppendExternal {
113 stream_id: request.stream_id,
114 content_type: Some(request.content_type),
115 payload: request.payload,
116 record_ends: request.record_ends,
117 close_after: request.close_after,
118 stream_seq: request.stream_seq,
119 producer: request.producer,
120 now_ms: request.now_ms,
121 record_match: request.record_match,
122 }
123 }
124}
125
126impl From<AppendBatchRequest> for StreamCommand {
127 fn from(request: AppendBatchRequest) -> Self {
128 Self::AppendBatch {
129 stream_id: request.stream_id,
130 content_type: Some(request.content_type),
131 payloads: request.payloads,
132 producer: request.producer,
133 now_ms: request.now_ms,
134 }
135 }
136}
137
138impl From<PublishSnapshotRequest> for StreamCommand {
139 fn from(request: PublishSnapshotRequest) -> Self {
140 Self::PublishSnapshot {
141 stream_id: request.stream_id,
142 snapshot_offset: request.snapshot_offset,
143 content_type: request.content_type,
144 payload: request.payload,
145 now_ms: request.now_ms,
146 }
147 }
148}
149
150impl From<CloseStreamRequest> for StreamCommand {
151 fn from(request: CloseStreamRequest) -> Self {
152 Self::Close {
153 stream_id: request.stream_id,
154 stream_seq: request.stream_seq,
155 producer: request.producer,
156 now_ms: request.now_ms,
157 }
158 }
159}
160
161impl From<DeleteStreamRequest> for StreamCommand {
162 fn from(request: DeleteStreamRequest) -> Self {
163 Self::DeleteStream {
164 stream_id: request.stream_id,
165 }
166 }
167}
168
169impl From<FlushColdRequest> for StreamCommand {
170 fn from(request: FlushColdRequest) -> Self {
171 Self::FlushCold {
172 stream_id: request.stream_id,
173 chunk: request.chunk,
174 }
175 }
176}
177
178macro_rules! group_write_from_request {
179 ($($request:ty),+ $(,)?) => {
180 $(impl From<$request> for GroupWriteCommand {
181 fn from(request: $request) -> Self {
182 Self::Stream(StreamCommand::from(request))
183 }
184 })+
185 };
186}
187
188group_write_from_request!(
189 CreateStreamRequest,
190 CreateStreamExternalRequest,
191 UpdateStreamAttrsRequest,
192 AppendRequest,
193 AppendExternalRequest,
194 AppendBatchRequest,
195 PublishSnapshotRequest,
196 CloseStreamRequest,
197 DeleteStreamRequest,
198 FlushColdRequest,
199);
200
201impl fmt::Display for GroupWriteCommand {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match self {
204 Self::Stream(command) => command.fmt(f),
205 Self::Batch { commands } => write!(f, "batch:{} commands", commands.len()),
206 }
207 }
208}