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 config::OptionalStreamConfig,
15 encryption::EncryptionKey,
16 record,
17 stream::{StreamName, StreamNamePrefix, StreamNameStartAfter},
18};
19use serde::{Deserialize, Serialize};
20use time::OffsetDateTime;
21
22use super::config::{EncryptionAlgorithm, StreamConfig};
23use crate::{data::Format, mime::JsonOrProto};
24
25#[rustfmt::skip]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
28pub struct StreamInfo {
29 pub name: StreamName,
31 #[serde(with = "time::serde::rfc3339")]
33 pub created_at: OffsetDateTime,
34 #[serde(with = "time::serde::rfc3339::option")]
36 pub deleted_at: Option<OffsetDateTime>,
37 pub cipher: Option<EncryptionAlgorithm>,
39}
40
41impl From<s2_common::stream::StreamInfo> for StreamInfo {
42 fn from(value: s2_common::stream::StreamInfo) -> Self {
43 Self {
44 name: value.name,
45 created_at: value.created_at,
46 deleted_at: value.deleted_at,
47 cipher: value.cipher.map(Into::into),
48 }
49 }
50}
51
52#[rustfmt::skip]
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
55#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
56pub struct ListStreamsRequest {
57 #[cfg_attr(feature = "utoipa", param(value_type = String, default = "", required = false))]
60 pub prefix: Option<StreamNamePrefix>,
61 #[cfg_attr(feature = "utoipa", param(value_type = String, default = "", required = false))]
64 pub start_after: Option<StreamNameStartAfter>,
65 #[cfg_attr(feature = "utoipa", param(value_type = usize, maximum = 1000, default = 1000, required = false))]
67 pub limit: Option<usize>,
68}
69
70super::impl_list_request_conversions!(ListStreamsRequest, StreamNamePrefix, StreamNameStartAfter);
71
72#[rustfmt::skip]
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
75pub struct ListStreamsResponse {
76 #[cfg_attr(feature = "utoipa", schema(max_items = 1000))]
78 pub streams: Vec<StreamInfo>,
79 pub has_more: bool,
81}
82
83#[rustfmt::skip]
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
86pub struct CreateStreamRequest {
87 pub stream: StreamName,
90 pub config: Option<StreamConfig>,
92}
93
94#[rustfmt::skip]
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
97pub struct StreamPosition {
99 pub seq_num: record::SeqNum,
101 pub timestamp: record::Timestamp,
104}
105
106impl From<record::StreamPosition> for StreamPosition {
107 fn from(pos: record::StreamPosition) -> Self {
108 Self {
109 seq_num: pos.seq_num,
110 timestamp: pos.timestamp,
111 }
112 }
113}
114
115impl From<StreamPosition> for record::StreamPosition {
116 fn from(pos: StreamPosition) -> Self {
117 Self {
118 seq_num: pos.seq_num,
119 timestamp: pos.timestamp,
120 }
121 }
122}
123
124#[rustfmt::skip]
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
127pub struct TailResponse {
128 pub tail: StreamPosition,
130}
131
132#[rustfmt::skip]
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
135#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
136pub struct ReadStart {
137 #[cfg_attr(feature = "utoipa", param(value_type = record::SeqNum, required = false))]
139 pub seq_num: Option<record::SeqNum>,
140 #[cfg_attr(feature = "utoipa", param(value_type = record::Timestamp, required = false))]
142 pub timestamp: Option<record::Timestamp>,
143 #[cfg_attr(feature = "utoipa", param(value_type = u64, required = false))]
145 pub tail_offset: Option<u64>,
146 #[cfg_attr(feature = "utoipa", param(value_type = bool, required = false))]
149 pub clamp: Option<bool>,
150}
151
152impl TryFrom<ReadStart> for s2_common::stream::ReadStart {
153 type Error = s2_common::ValidationError;
154
155 fn try_from(value: ReadStart) -> Result<Self, Self::Error> {
156 let from = match (value.seq_num, value.timestamp, value.tail_offset) {
157 (Some(seq_num), None, None) => s2_common::stream::ReadFrom::SeqNum(seq_num),
158 (None, Some(timestamp), None) => s2_common::stream::ReadFrom::Timestamp(timestamp),
159 (None, None, Some(tail_offset)) => s2_common::stream::ReadFrom::TailOffset(tail_offset),
160 (None, None, None) => s2_common::stream::ReadFrom::TailOffset(0),
161 _ => {
162 return Err(s2_common::ValidationError(
163 "only one of seq_num, timestamp, or tail_offset can be provided".to_owned(),
164 ));
165 }
166 };
167 let clamp = value.clamp.unwrap_or(false);
168 Ok(Self { from, clamp })
169 }
170}
171
172#[rustfmt::skip]
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
175#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
176pub struct ReadEnd {
177 #[cfg_attr(feature = "utoipa", param(value_type = u64, required = false))]
180 pub count: Option<usize>,
181 #[cfg_attr(feature = "utoipa", param(value_type = usize, required = false))]
184 pub bytes: Option<usize>,
185 #[cfg_attr(feature = "utoipa", param(value_type = record::Timestamp, required = false))]
187 pub until: Option<record::Timestamp>,
188 #[cfg_attr(feature = "utoipa", param(value_type = u32, required = false))]
193 pub wait: Option<u32>,
194}
195
196impl From<ReadEnd> for s2_common::stream::ReadEnd {
197 fn from(value: ReadEnd) -> Self {
198 Self {
199 limit: s2_common::read_extent::ReadLimit::from_count_and_bytes(
200 value.count,
201 value.bytes,
202 ),
203 until: value.until.into(),
204 wait: value.wait.map(|w| Duration::from_secs(w as u64)),
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
210pub enum ReadRequest {
211 Unary {
213 encryption_key: Option<EncryptionKey>,
214 create_stream_config_patch: OptionalStreamConfig,
216 format: Format,
217 response_mime: JsonOrProto,
218 },
219 EventStream {
221 encryption_key: Option<EncryptionKey>,
222 create_stream_config_patch: OptionalStreamConfig,
224 format: Format,
225 last_event_id: Option<sse::LastEventId>,
226 },
227 S2s {
229 encryption_key: Option<EncryptionKey>,
230 create_stream_config_patch: OptionalStreamConfig,
232 response_compression: s2s::CompressionAlgorithm,
233 },
234}
235
236pub enum AppendRequest {
237 Unary {
239 encryption_key: Option<EncryptionKey>,
240 create_stream_config_patch: OptionalStreamConfig,
242 input: s2_common::stream::AppendInput,
243 response_mime: JsonOrProto,
244 },
245 S2s {
247 encryption_key: Option<EncryptionKey>,
248 create_stream_config_patch: OptionalStreamConfig,
250 inputs: BoxStream<'static, Result<s2_common::stream::AppendInput, AppendInputStreamError>>,
251 response_compression: s2s::CompressionAlgorithm,
252 },
253}
254
255impl std::fmt::Debug for AppendRequest {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 match self {
258 AppendRequest::Unary {
259 encryption_key,
260 create_stream_config_patch,
261 input,
262 response_mime: response,
263 } => f
264 .debug_struct("AppendRequest::Unary")
265 .field("encryption_key", encryption_key)
266 .field("create_stream_config_patch", create_stream_config_patch)
267 .field("input", input)
268 .field("response", response)
269 .finish(),
270 AppendRequest::S2s {
271 encryption_key,
272 create_stream_config_patch,
273 response_compression,
274 ..
275 } => f
276 .debug_struct("AppendRequest::S2s")
277 .field("encryption_key", encryption_key)
278 .field("create_stream_config_patch", create_stream_config_patch)
279 .field("response_compression", response_compression)
280 .finish(),
281 }
282 }
283}
284
285#[derive(Debug, thiserror::Error)]
286pub enum AppendInputStreamError {
287 #[error("Failed to decode S2S frame: {0}")]
288 FrameDecode(#[from] std::io::Error),
289 #[error(transparent)]
290 Validation(#[from] s2_common::ValidationError),
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
294#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
295pub struct Header(pub String, pub String);
296
297#[rustfmt::skip]
298#[derive(Debug, Clone, Serialize)]
300#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
301pub struct SequencedRecord {
302 pub seq_num: record::SeqNum,
304 pub timestamp: record::Timestamp,
306 #[serde(default, skip_serializing_if = "Vec::is_empty")]
308 #[cfg_attr(feature = "utoipa", schema(required = false))]
309 pub headers: Vec<Header>,
310 #[serde(default, skip_serializing_if = "String::is_empty")]
312 #[cfg_attr(feature = "utoipa", schema(required = false))]
313 pub body: String,
314}
315
316impl SequencedRecord {
317 pub fn encode(format: Format, record: record::SequencedRecord) -> Self {
318 let (record::StreamPosition { seq_num, timestamp }, record) = record.into_parts();
319 let (headers, body) = record.into_parts();
320 Self {
321 seq_num,
322 timestamp,
323 headers: headers
324 .into_iter()
325 .map(|h| Header(format.encode(&h.name), format.encode(&h.value)))
326 .collect(),
327 body: format.encode(&body),
328 }
329 }
330}
331
332#[rustfmt::skip]
333#[derive(Debug, Clone, Serialize, Deserialize)]
335#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
336pub struct AppendRecord {
337 pub timestamp: Option<record::Timestamp>,
341 #[serde(default, skip_serializing_if = "Vec::is_empty")]
343 #[cfg_attr(feature = "utoipa", schema(required = false))]
344 pub headers: Vec<Header>,
345 #[serde(default, skip_serializing_if = "String::is_empty")]
347 #[cfg_attr(feature = "utoipa", schema(required = false))]
348 pub body: String,
349}
350
351impl AppendRecord {
352 pub fn decode(
353 self,
354 format: Format,
355 ) -> Result<s2_common::stream::AppendRecord, s2_common::ValidationError> {
356 let headers = self
357 .headers
358 .into_iter()
359 .map(|Header(name, value)| {
360 Ok::<record::Header, s2_common::ValidationError>(record::Header {
361 name: format.decode(name)?,
362 value: format.decode(value)?,
363 })
364 })
365 .try_collect()?;
366
367 let body = format.decode(self.body)?;
368
369 let record = record::Record::try_from_parts(headers, body)
370 .map_err(|e| e.to_string())?
371 .into();
372
373 let parts = s2_common::stream::AppendRecordParts {
374 timestamp: self.timestamp,
375 record,
376 };
377
378 s2_common::stream::AppendRecord::try_from(parts)
379 .map_err(|e| s2_common::ValidationError(e.to_string()))
380 }
381}
382
383#[rustfmt::skip]
384#[derive(Debug, Clone, Serialize, Deserialize)]
386#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
387pub struct AppendInput {
388 pub records: Vec<AppendRecord>,
391 pub match_seq_num: Option<record::SeqNum>,
393 pub fencing_token: Option<record::FencingToken>,
395}
396
397impl AppendInput {
398 pub fn decode(
399 self,
400 format: Format,
401 ) -> Result<s2_common::stream::AppendInput, s2_common::ValidationError> {
402 let records: Vec<s2_common::stream::AppendRecord> = self
403 .records
404 .into_iter()
405 .map(|record| record.decode(format))
406 .try_collect()?;
407
408 Ok(s2_common::stream::AppendInput {
409 records: s2_common::stream::AppendRecordBatch::try_from(records)?,
410 match_seq_num: self.match_seq_num,
411 fencing_token: self.fencing_token,
412 })
413 }
414}
415
416#[rustfmt::skip]
417#[derive(Debug, Clone, Serialize)]
419#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
420pub struct AppendAck {
421 pub start: StreamPosition,
423 pub end: StreamPosition,
426 pub tail: StreamPosition,
429}
430
431impl From<s2_common::stream::AppendAck> for AppendAck {
432 fn from(ack: s2_common::stream::AppendAck) -> Self {
433 Self {
434 start: ack.start.into(),
435 end: ack.end.into(),
436 tail: ack.tail.into(),
437 }
438 }
439}
440
441#[rustfmt::skip]
442#[derive(Debug, Clone, Serialize, Deserialize)]
444#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
445#[serde(rename_all = "snake_case")]
446pub enum AppendConditionFailed {
447 #[cfg_attr(feature = "utoipa", schema(title = "fencing token"))]
450 FencingTokenMismatch(record::FencingToken),
451 #[cfg_attr(feature = "utoipa", schema(title = "seq num"))]
454 SeqNumMismatch(record::SeqNum),
455}
456
457#[rustfmt::skip]
458#[derive(Debug, Clone, Serialize)]
459#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
460pub struct ReadBatch {
461 pub records: Vec<SequencedRecord>,
464 #[serde(skip_serializing_if = "Option::is_none")]
467 pub tail: Option<StreamPosition>,
468}
469
470impl ReadBatch {
471 pub fn encode(format: Format, batch: s2_common::stream::ReadBatch) -> Self {
472 Self {
473 records: batch
474 .records
475 .into_iter()
476 .map(|record| SequencedRecord::encode(format, record))
477 .collect(),
478 tail: batch.tail.map(Into::into),
479 }
480 }
481}