1#[cfg(feature = "axum")]
2pub mod extract;
3
4pub mod json;
5pub mod proto;
6pub mod s2s;
7pub mod sse;
8
9use std::time::Duration;
10
11use futures_core::stream::BoxStream;
12use itertools::Itertools as _;
13use s2_common::{
14 encryption::EncryptionKey,
15 record,
16 stream::{StreamName, StreamNamePrefix, StreamNameStartAfter},
17};
18use serde::{Deserialize, Serialize};
19use time::OffsetDateTime;
20
21use super::config::{EncryptionAlgorithm, StreamConfig};
22use crate::{data::Format, mime::JsonOrProto};
23
24#[rustfmt::skip]
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
27pub struct StreamInfo {
28 pub name: StreamName,
30 #[serde(with = "time::serde::rfc3339")]
32 pub created_at: OffsetDateTime,
33 #[serde(with = "time::serde::rfc3339::option")]
35 pub deleted_at: Option<OffsetDateTime>,
36 pub cipher: Option<EncryptionAlgorithm>,
38}
39
40impl From<s2_common::stream::StreamInfo> for StreamInfo {
41 fn from(value: s2_common::stream::StreamInfo) -> Self {
42 Self {
43 name: value.name,
44 created_at: value.created_at,
45 deleted_at: value.deleted_at,
46 cipher: value.cipher.map(Into::into),
47 }
48 }
49}
50
51#[rustfmt::skip]
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
54#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
55pub struct ListStreamsRequest {
56 #[cfg_attr(feature = "utoipa", param(value_type = String, default = "", required = false))]
59 pub prefix: Option<StreamNamePrefix>,
60 #[cfg_attr(feature = "utoipa", param(value_type = String, default = "", required = false))]
63 pub start_after: Option<StreamNameStartAfter>,
64 #[cfg_attr(feature = "utoipa", param(value_type = usize, maximum = 1000, default = 1000, required = false))]
66 pub limit: Option<usize>,
67}
68
69super::impl_list_request_conversions!(ListStreamsRequest, StreamNamePrefix, StreamNameStartAfter);
70
71#[rustfmt::skip]
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
74pub struct ListStreamsResponse {
75 #[cfg_attr(feature = "utoipa", schema(max_items = 1000))]
77 pub streams: Vec<StreamInfo>,
78 pub has_more: bool,
80}
81
82#[rustfmt::skip]
83#[derive(Debug, Clone, Serialize, Deserialize)]
84#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
85pub struct CreateStreamRequest {
86 pub stream: StreamName,
89 pub config: Option<StreamConfig>,
91}
92
93#[rustfmt::skip]
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
96pub struct StreamPosition {
98 pub seq_num: record::SeqNum,
100 pub timestamp: record::Timestamp,
103}
104
105impl From<record::StreamPosition> for StreamPosition {
106 fn from(pos: record::StreamPosition) -> Self {
107 Self {
108 seq_num: pos.seq_num,
109 timestamp: pos.timestamp,
110 }
111 }
112}
113
114impl From<StreamPosition> for record::StreamPosition {
115 fn from(pos: StreamPosition) -> Self {
116 Self {
117 seq_num: pos.seq_num,
118 timestamp: pos.timestamp,
119 }
120 }
121}
122
123#[rustfmt::skip]
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
126pub struct TailResponse {
127 pub tail: StreamPosition,
129}
130
131#[rustfmt::skip]
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
134#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
135pub struct ReadStart {
136 #[cfg_attr(feature = "utoipa", param(value_type = record::SeqNum, required = false))]
138 pub seq_num: Option<record::SeqNum>,
139 #[cfg_attr(feature = "utoipa", param(value_type = record::Timestamp, required = false))]
141 pub timestamp: Option<record::Timestamp>,
142 #[cfg_attr(feature = "utoipa", param(value_type = u64, required = false))]
144 pub tail_offset: Option<u64>,
145 #[cfg_attr(feature = "utoipa", param(value_type = bool, required = false))]
148 pub clamp: Option<bool>,
149}
150
151impl TryFrom<ReadStart> for s2_common::stream::ReadStart {
152 type Error = s2_common::ValidationError;
153
154 fn try_from(value: ReadStart) -> Result<Self, Self::Error> {
155 let from = match (value.seq_num, value.timestamp, value.tail_offset) {
156 (Some(seq_num), None, None) => s2_common::stream::ReadFrom::SeqNum(seq_num),
157 (None, Some(timestamp), None) => s2_common::stream::ReadFrom::Timestamp(timestamp),
158 (None, None, Some(tail_offset)) => s2_common::stream::ReadFrom::TailOffset(tail_offset),
159 (None, None, None) => s2_common::stream::ReadFrom::TailOffset(0),
160 _ => {
161 return Err(s2_common::ValidationError(
162 "only one of seq_num, timestamp, or tail_offset can be provided".to_owned(),
163 ));
164 }
165 };
166 let clamp = value.clamp.unwrap_or(false);
167 Ok(Self { from, clamp })
168 }
169}
170
171#[rustfmt::skip]
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
174#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
175pub struct ReadEnd {
176 #[cfg_attr(feature = "utoipa", param(value_type = u64, required = false))]
179 pub count: Option<usize>,
180 #[cfg_attr(feature = "utoipa", param(value_type = usize, required = false))]
183 pub bytes: Option<usize>,
184 #[cfg_attr(feature = "utoipa", param(value_type = record::Timestamp, required = false))]
186 pub until: Option<record::Timestamp>,
187 #[cfg_attr(feature = "utoipa", param(value_type = u32, required = false))]
192 pub wait: Option<u32>,
193}
194
195impl From<ReadEnd> for s2_common::stream::ReadEnd {
196 fn from(value: ReadEnd) -> Self {
197 Self {
198 limit: s2_common::read_extent::ReadLimit::from_count_and_bytes(
199 value.count,
200 value.bytes,
201 ),
202 until: value.until.into(),
203 wait: value.wait.map(|w| Duration::from_secs(w as u64)),
204 }
205 }
206}
207
208#[derive(Debug, Clone)]
209pub enum ReadRequest {
210 Unary {
212 encryption_key: Option<EncryptionKey>,
213 format: Format,
214 response_mime: JsonOrProto,
215 },
216 EventStream {
218 encryption_key: Option<EncryptionKey>,
219 format: Format,
220 last_event_id: Option<sse::LastEventId>,
221 },
222 S2s {
224 encryption_key: Option<EncryptionKey>,
225 response_compression: s2s::CompressionAlgorithm,
226 },
227}
228
229pub enum AppendRequest {
230 Unary {
232 encryption_key: Option<EncryptionKey>,
233 input: s2_common::stream::AppendInput,
234 response_mime: JsonOrProto,
235 },
236 S2s {
238 encryption_key: Option<EncryptionKey>,
239 inputs: BoxStream<'static, Result<s2_common::stream::AppendInput, AppendInputStreamError>>,
240 response_compression: s2s::CompressionAlgorithm,
241 },
242}
243
244impl std::fmt::Debug for AppendRequest {
245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246 match self {
247 AppendRequest::Unary {
248 encryption_key,
249 input,
250 response_mime: response,
251 } => f
252 .debug_struct("AppendRequest::Unary")
253 .field("encryption_key", encryption_key)
254 .field("input", input)
255 .field("response", response)
256 .finish(),
257 AppendRequest::S2s {
258 encryption_key,
259 response_compression,
260 ..
261 } => f
262 .debug_struct("AppendRequest::S2s")
263 .field("encryption_key", encryption_key)
264 .field("response_compression", response_compression)
265 .finish(),
266 }
267 }
268}
269
270#[derive(Debug, thiserror::Error)]
271pub enum AppendInputStreamError {
272 #[error("Failed to decode S2S frame: {0}")]
273 FrameDecode(#[from] std::io::Error),
274 #[error(transparent)]
275 Validation(#[from] s2_common::ValidationError),
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
280pub struct Header(pub String, pub String);
281
282#[rustfmt::skip]
283#[derive(Debug, Clone, Serialize)]
285#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
286pub struct SequencedRecord {
287 pub seq_num: record::SeqNum,
289 pub timestamp: record::Timestamp,
291 #[serde(default, skip_serializing_if = "Vec::is_empty")]
293 #[cfg_attr(feature = "utoipa", schema(required = false))]
294 pub headers: Vec<Header>,
295 #[serde(default, skip_serializing_if = "String::is_empty")]
297 #[cfg_attr(feature = "utoipa", schema(required = false))]
298 pub body: String,
299}
300
301impl SequencedRecord {
302 pub fn encode(format: Format, record: record::SequencedRecord) -> Self {
303 let (record::StreamPosition { seq_num, timestamp }, record) = record.into_parts();
304 let (headers, body) = record.into_parts();
305 Self {
306 seq_num,
307 timestamp,
308 headers: headers
309 .into_iter()
310 .map(|h| Header(format.encode(&h.name), format.encode(&h.value)))
311 .collect(),
312 body: format.encode(&body),
313 }
314 }
315}
316
317#[rustfmt::skip]
318#[derive(Debug, Clone, Serialize, Deserialize)]
320#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
321pub struct AppendRecord {
322 pub timestamp: Option<record::Timestamp>,
326 #[serde(default, skip_serializing_if = "Vec::is_empty")]
328 #[cfg_attr(feature = "utoipa", schema(required = false))]
329 pub headers: Vec<Header>,
330 #[serde(default, skip_serializing_if = "String::is_empty")]
332 #[cfg_attr(feature = "utoipa", schema(required = false))]
333 pub body: String,
334}
335
336impl AppendRecord {
337 pub fn decode(
338 self,
339 format: Format,
340 ) -> Result<s2_common::stream::AppendRecord, s2_common::ValidationError> {
341 let headers = self
342 .headers
343 .into_iter()
344 .map(|Header(name, value)| {
345 Ok::<record::Header, s2_common::ValidationError>(record::Header {
346 name: format.decode(name)?,
347 value: format.decode(value)?,
348 })
349 })
350 .try_collect()?;
351
352 let body = format.decode(self.body)?;
353
354 let record = record::Record::try_from_parts(headers, body)
355 .map_err(|e| e.to_string())?
356 .into();
357
358 let parts = s2_common::stream::AppendRecordParts {
359 timestamp: self.timestamp,
360 record,
361 };
362
363 s2_common::stream::AppendRecord::try_from(parts)
364 .map_err(|e| s2_common::ValidationError(e.to_string()))
365 }
366}
367
368#[rustfmt::skip]
369#[derive(Debug, Clone, Serialize, Deserialize)]
371#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
372pub struct AppendInput {
373 pub records: Vec<AppendRecord>,
376 pub match_seq_num: Option<record::SeqNum>,
378 pub fencing_token: Option<record::FencingToken>,
380}
381
382impl AppendInput {
383 pub fn decode(
384 self,
385 format: Format,
386 ) -> Result<s2_common::stream::AppendInput, s2_common::ValidationError> {
387 let records: Vec<s2_common::stream::AppendRecord> = self
388 .records
389 .into_iter()
390 .map(|record| record.decode(format))
391 .try_collect()?;
392
393 Ok(s2_common::stream::AppendInput {
394 records: s2_common::stream::AppendRecordBatch::try_from(records)?,
395 match_seq_num: self.match_seq_num,
396 fencing_token: self.fencing_token,
397 })
398 }
399}
400
401#[rustfmt::skip]
402#[derive(Debug, Clone, Serialize)]
404#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
405pub struct AppendAck {
406 pub start: StreamPosition,
408 pub end: StreamPosition,
411 pub tail: StreamPosition,
414}
415
416impl From<s2_common::stream::AppendAck> for AppendAck {
417 fn from(ack: s2_common::stream::AppendAck) -> Self {
418 Self {
419 start: ack.start.into(),
420 end: ack.end.into(),
421 tail: ack.tail.into(),
422 }
423 }
424}
425
426#[rustfmt::skip]
427#[derive(Debug, Clone, Serialize, Deserialize)]
429#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
430#[serde(rename_all = "snake_case")]
431pub enum AppendConditionFailed {
432 #[cfg_attr(feature = "utoipa", schema(title = "fencing token"))]
435 FencingTokenMismatch(record::FencingToken),
436 #[cfg_attr(feature = "utoipa", schema(title = "seq num"))]
439 SeqNumMismatch(record::SeqNum),
440}
441
442#[rustfmt::skip]
443#[derive(Debug, Clone, Serialize)]
444#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
445pub struct ReadBatch {
446 pub records: Vec<SequencedRecord>,
449 #[serde(skip_serializing_if = "Option::is_none")]
452 pub tail: Option<StreamPosition>,
453}
454
455impl ReadBatch {
456 pub fn encode(format: Format, batch: s2_common::stream::ReadBatch) -> Self {
457 Self {
458 records: batch
459 .records
460 .into_iter()
461 .map(|record| SequencedRecord::encode(format, record))
462 .collect(),
463 tail: batch.tail.map(Into::into),
464 }
465 }
466}