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))]
58 pub prefix: Option<StreamNamePrefix>,
59 #[cfg_attr(feature = "utoipa", param(value_type = String, default = "", required = false))]
61 pub start_after: Option<StreamNameStartAfter>,
62 #[cfg_attr(feature = "utoipa", param(value_type = usize, maximum = 1000, default = 1000, required = false))]
64 pub limit: Option<usize>,
65}
66
67super::impl_list_request_conversions!(ListStreamsRequest, StreamNamePrefix, StreamNameStartAfter);
68
69#[rustfmt::skip]
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
72pub struct ListStreamsResponse {
73 #[cfg_attr(feature = "utoipa", schema(max_items = 1000))]
75 pub streams: Vec<StreamInfo>,
76 pub has_more: bool,
78}
79
80#[rustfmt::skip]
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
83pub struct CreateStreamRequest {
84 pub stream: StreamName,
87 pub config: Option<StreamConfig>,
89}
90
91#[rustfmt::skip]
92#[derive(Debug, Clone, Serialize, Deserialize)]
93#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
94pub struct StreamPosition {
96 pub seq_num: record::SeqNum,
98 pub timestamp: record::Timestamp,
101}
102
103impl From<record::StreamPosition> for StreamPosition {
104 fn from(pos: record::StreamPosition) -> Self {
105 Self {
106 seq_num: pos.seq_num,
107 timestamp: pos.timestamp,
108 }
109 }
110}
111
112impl From<StreamPosition> for record::StreamPosition {
113 fn from(pos: StreamPosition) -> Self {
114 Self {
115 seq_num: pos.seq_num,
116 timestamp: pos.timestamp,
117 }
118 }
119}
120
121#[rustfmt::skip]
122#[derive(Debug, Clone, Serialize, Deserialize)]
123#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
124pub struct TailResponse {
125 pub tail: StreamPosition,
127}
128
129#[rustfmt::skip]
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
132#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
133pub struct ReadStart {
134 #[cfg_attr(feature = "utoipa", param(value_type = record::SeqNum, required = false))]
136 pub seq_num: Option<record::SeqNum>,
137 #[cfg_attr(feature = "utoipa", param(value_type = record::Timestamp, required = false))]
139 pub timestamp: Option<record::Timestamp>,
140 #[cfg_attr(feature = "utoipa", param(value_type = u64, required = false))]
142 pub tail_offset: Option<u64>,
143 #[cfg_attr(feature = "utoipa", param(value_type = bool, required = false))]
146 pub clamp: Option<bool>,
147}
148
149impl TryFrom<ReadStart> for s2_common::stream::ReadStart {
150 type Error = s2_common::ValidationError;
151
152 fn try_from(value: ReadStart) -> Result<Self, Self::Error> {
153 let from = match (value.seq_num, value.timestamp, value.tail_offset) {
154 (Some(seq_num), None, None) => s2_common::stream::ReadFrom::SeqNum(seq_num),
155 (None, Some(timestamp), None) => s2_common::stream::ReadFrom::Timestamp(timestamp),
156 (None, None, Some(tail_offset)) => s2_common::stream::ReadFrom::TailOffset(tail_offset),
157 (None, None, None) => s2_common::stream::ReadFrom::TailOffset(0),
158 _ => {
159 return Err(s2_common::ValidationError(
160 "only one of seq_num, timestamp, or tail_offset can be provided".to_owned(),
161 ));
162 }
163 };
164 let clamp = value.clamp.unwrap_or(false);
165 Ok(Self { from, clamp })
166 }
167}
168
169#[rustfmt::skip]
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))]
172#[cfg_attr(feature = "utoipa", into_params(parameter_in = Query))]
173pub struct ReadEnd {
174 #[cfg_attr(feature = "utoipa", param(value_type = u64, required = false))]
177 pub count: Option<usize>,
178 #[cfg_attr(feature = "utoipa", param(value_type = usize, required = false))]
181 pub bytes: Option<usize>,
182 #[cfg_attr(feature = "utoipa", param(value_type = record::Timestamp, required = false))]
184 pub until: Option<record::Timestamp>,
185 #[cfg_attr(feature = "utoipa", param(value_type = u32, required = false))]
190 pub wait: Option<u32>,
191}
192
193impl From<ReadEnd> for s2_common::stream::ReadEnd {
194 fn from(value: ReadEnd) -> Self {
195 Self {
196 limit: s2_common::read_extent::ReadLimit::from_count_and_bytes(
197 value.count,
198 value.bytes,
199 ),
200 until: value.until.into(),
201 wait: value.wait.map(|w| Duration::from_secs(w as u64)),
202 }
203 }
204}
205
206#[derive(Debug, Clone)]
207pub enum ReadRequest {
208 Unary {
210 encryption_key: Option<EncryptionKey>,
211 format: Format,
212 response_mime: JsonOrProto,
213 },
214 EventStream {
216 encryption_key: Option<EncryptionKey>,
217 format: Format,
218 last_event_id: Option<sse::LastEventId>,
219 },
220 S2s {
222 encryption_key: Option<EncryptionKey>,
223 response_compression: s2s::CompressionAlgorithm,
224 },
225}
226
227pub enum AppendRequest {
228 Unary {
230 encryption_key: Option<EncryptionKey>,
231 input: s2_common::stream::AppendInput,
232 response_mime: JsonOrProto,
233 },
234 S2s {
236 encryption_key: Option<EncryptionKey>,
237 inputs: BoxStream<'static, Result<s2_common::stream::AppendInput, AppendInputStreamError>>,
238 response_compression: s2s::CompressionAlgorithm,
239 },
240}
241
242impl std::fmt::Debug for AppendRequest {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 match self {
245 AppendRequest::Unary {
246 encryption_key,
247 input,
248 response_mime: response,
249 } => f
250 .debug_struct("AppendRequest::Unary")
251 .field("encryption_key", encryption_key)
252 .field("input", input)
253 .field("response", response)
254 .finish(),
255 AppendRequest::S2s {
256 encryption_key,
257 response_compression,
258 ..
259 } => f
260 .debug_struct("AppendRequest::S2s")
261 .field("encryption_key", encryption_key)
262 .field("response_compression", response_compression)
263 .finish(),
264 }
265 }
266}
267
268#[derive(Debug, thiserror::Error)]
269pub enum AppendInputStreamError {
270 #[error("Failed to decode S2S frame: {0}")]
271 FrameDecode(#[from] std::io::Error),
272 #[error(transparent)]
273 Validation(#[from] s2_common::ValidationError),
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
278pub struct Header(pub String, pub String);
279
280#[rustfmt::skip]
281#[derive(Debug, Clone, Serialize)]
283#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
284pub struct SequencedRecord {
285 pub seq_num: record::SeqNum,
287 pub timestamp: record::Timestamp,
289 #[serde(default, skip_serializing_if = "Vec::is_empty")]
291 #[cfg_attr(feature = "utoipa", schema(required = false))]
292 pub headers: Vec<Header>,
293 #[serde(default, skip_serializing_if = "String::is_empty")]
295 #[cfg_attr(feature = "utoipa", schema(required = false))]
296 pub body: String,
297}
298
299impl SequencedRecord {
300 pub fn encode(format: Format, record: record::SequencedRecord) -> Self {
301 let (record::StreamPosition { seq_num, timestamp }, record) = record.into_parts();
302 let (headers, body) = record.into_parts();
303 Self {
304 seq_num,
305 timestamp,
306 headers: headers
307 .into_iter()
308 .map(|h| Header(format.encode(&h.name), format.encode(&h.value)))
309 .collect(),
310 body: format.encode(&body),
311 }
312 }
313}
314
315#[rustfmt::skip]
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
319pub struct AppendRecord {
320 pub timestamp: Option<record::Timestamp>,
324 #[serde(default, skip_serializing_if = "Vec::is_empty")]
326 #[cfg_attr(feature = "utoipa", schema(required = false))]
327 pub headers: Vec<Header>,
328 #[serde(default, skip_serializing_if = "String::is_empty")]
330 #[cfg_attr(feature = "utoipa", schema(required = false))]
331 pub body: String,
332}
333
334impl AppendRecord {
335 pub fn decode(
336 self,
337 format: Format,
338 ) -> Result<s2_common::stream::AppendRecord, s2_common::ValidationError> {
339 let headers = self
340 .headers
341 .into_iter()
342 .map(|Header(name, value)| {
343 Ok::<record::Header, s2_common::ValidationError>(record::Header {
344 name: format.decode(name)?,
345 value: format.decode(value)?,
346 })
347 })
348 .try_collect()?;
349
350 let body = format.decode(self.body)?;
351
352 let record = record::Record::try_from_parts(headers, body)
353 .map_err(|e| e.to_string())?
354 .into();
355
356 let parts = s2_common::stream::AppendRecordParts {
357 timestamp: self.timestamp,
358 record,
359 };
360
361 s2_common::stream::AppendRecord::try_from(parts)
362 .map_err(|e| s2_common::ValidationError(e.to_string()))
363 }
364}
365
366#[rustfmt::skip]
367#[derive(Debug, Clone, Serialize, Deserialize)]
369#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
370pub struct AppendInput {
371 pub records: Vec<AppendRecord>,
374 pub match_seq_num: Option<record::SeqNum>,
376 pub fencing_token: Option<record::FencingToken>,
378}
379
380impl AppendInput {
381 pub fn decode(
382 self,
383 format: Format,
384 ) -> Result<s2_common::stream::AppendInput, s2_common::ValidationError> {
385 let records: Vec<s2_common::stream::AppendRecord> = self
386 .records
387 .into_iter()
388 .map(|record| record.decode(format))
389 .try_collect()?;
390
391 Ok(s2_common::stream::AppendInput {
392 records: s2_common::stream::AppendRecordBatch::try_from(records)?,
393 match_seq_num: self.match_seq_num,
394 fencing_token: self.fencing_token,
395 })
396 }
397}
398
399#[rustfmt::skip]
400#[derive(Debug, Clone, Serialize)]
402#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
403pub struct AppendAck {
404 pub start: StreamPosition,
406 pub end: StreamPosition,
409 pub tail: StreamPosition,
412}
413
414impl From<s2_common::stream::AppendAck> for AppendAck {
415 fn from(ack: s2_common::stream::AppendAck) -> Self {
416 Self {
417 start: ack.start.into(),
418 end: ack.end.into(),
419 tail: ack.tail.into(),
420 }
421 }
422}
423
424#[rustfmt::skip]
425#[derive(Debug, Clone, Serialize, Deserialize)]
427#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
428#[serde(rename_all = "snake_case")]
429pub enum AppendConditionFailed {
430 #[cfg_attr(feature = "utoipa", schema(title = "fencing token"))]
433 FencingTokenMismatch(record::FencingToken),
434 #[cfg_attr(feature = "utoipa", schema(title = "seq num"))]
437 SeqNumMismatch(record::SeqNum),
438}
439
440#[rustfmt::skip]
441#[derive(Debug, Clone, Serialize)]
442#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
443pub struct ReadBatch {
444 pub records: Vec<SequencedRecord>,
447 #[serde(skip_serializing_if = "Option::is_none")]
450 pub tail: Option<StreamPosition>,
451}
452
453impl ReadBatch {
454 pub fn encode(format: Format, batch: s2_common::stream::ReadBatch) -> Self {
455 Self {
456 records: batch
457 .records
458 .into_iter()
459 .map(|record| SequencedRecord::encode(format, record))
460 .collect(),
461 tail: batch.tail.map(Into::into),
462 }
463 }
464}