http_streams_core/format.rs
1//! The symmetric format abstraction: one trait for identity, one per direction.
2//!
3//! Deliberately **item-level** rather than stream-level. A stream-level trait would have to
4//! name a concrete stream error type in its signature, which is exactly what ties
5//! `axum_streams::StreamingFormat` to `axum::Error` and makes it unmovable. Encoding an item
6//! into a buffer names nothing but [`StreamError`].
7//!
8//! [`ItemEncoder::prologue`] and [`ItemEncoder::epilogue`] exist because framing is not purely
9//! per-item: a JSON array opens with `[` and closes with `]`, and Arrow IPC ends with an
10//! eight-byte end-of-stream marker. This is precisely the hook that
11//! [`tokio_util::codec::Encoder`] lacks (`FramedWrite` never calls anything at end of stream),
12//! and the reason this crate does not implement that trait.
13
14use crate::content_type::ContentType;
15use crate::error::StreamError;
16use bytes::BytesMut;
17
18/// Identity and content-type negotiation, shared by both directions.
19pub trait StreamFormat {
20 /// A short, stable name, reported as the `format` tracing field.
21 fn format_name(&self) -> &'static str;
22
23 /// The `Content-Type` this format emits when encoding.
24 fn default_content_type(&self) -> &'static str;
25
26 /// Whether an incoming `Content-Type` should be accepted as this format.
27 ///
28 /// Implementations should be lenient about parameters, which [`ContentType`] has already
29 /// stripped, and should accept the well-known aliases for their wire format.
30 fn accepts_content_type(&self, ct: &ContentType<'_>) -> bool {
31 ct.matches(self.default_content_type())
32 }
33}
34
35/// Encodes items of type `T` into bytes.
36pub trait StreamFormatEncode<T>: StreamFormat {
37 /// The per-stream encoder. A fresh one is built for every body, because framing state
38 /// (item index, Arrow's dictionary tracker, CSV's header flag) is per-stream.
39 type Encoder: ItemEncoder<T>;
40
41 /// Build an encoder for one body.
42 fn encoder(&self) -> Self::Encoder;
43}
44
45/// Per-stream encoding state.
46///
47/// `index` is passed to [`encode`] rather than tracked internally because several formats key
48/// their framing off it: the JSON array writes a separator before every item but the first,
49/// CSV writes its header row only at index 0, and Arrow emits the schema message only at
50/// index 0. Implementations that do not care simply ignore it.
51///
52/// [`encode`]: ItemEncoder::encode
53pub trait ItemEncoder<T> {
54 /// Bytes emitted before the first item, if any.
55 fn prologue(&mut self, buf: &mut BytesMut) -> Result<(), StreamError> {
56 let _ = buf;
57 Ok(())
58 }
59
60 /// Encode one item.
61 fn encode(&mut self, item: &T, index: u64, buf: &mut BytesMut) -> Result<(), StreamError>;
62
63 /// Bytes emitted after the last item, if any.
64 ///
65 /// Called on normal end of stream only, never after an error, and never if the stream is
66 /// dropped, since in both cases the body is already truncated and a well-formed terminator
67 /// would misrepresent it as complete.
68 fn epilogue(&mut self, buf: &mut BytesMut) -> Result<(), StreamError> {
69 let _ = buf;
70 Ok(())
71 }
72}
73
74/// Turns one framed record into an item.
75///
76/// Separate from the framer so that the framer need not be generic over `T`. That is not
77/// tidiness: a decoder that structurally mentions `T` forces a `T: 'b` bound onto every
78/// caller's public signature, whereas a parser mentioning `T` only in its return type does not.
79pub trait FrameParser<F, T> {
80 /// Deserialise one framed record.
81 ///
82 /// An error here is **not** terminal: the record framed correctly, so the decoder knows
83 /// exactly where the next one starts and the stream continues.
84 fn parse(&self, frame: F) -> Result<T, StreamError>;
85}
86
87/// Yields frames unchanged, for formats whose framer already produces items.
88///
89/// Used by every format whose framing cannot be separated from deserialisation. A JSON array
90/// element is not self-delimiting, so finding its end and parsing it are one operation.
91#[derive(Debug, Clone, Copy, Default)]
92pub struct IdentityParser;
93
94impl<T> FrameParser<Result<T, StreamError>, T> for IdentityParser {
95 fn parse(&self, frame: Result<T, StreamError>) -> Result<T, StreamError> {
96 frame
97 }
98}
99
100/// Decodes bytes into items of type `T`.
101pub trait StreamFormatDecode<T>: StreamFormat {
102 /// One framed record, before deserialisation.
103 ///
104 /// Formats that can separate the two use a type independent of `T`, such as `csv::ByteRecord`,
105 /// say. Formats that cannot use `Result<T, StreamError>` and an [`IdentityParser`].
106 type Frame;
107
108 /// Splits the body into records.
109 ///
110 /// Its error type is the *framing* error, and it is terminal: `FramedRead` latches its
111 /// error state and ends the stream. Correct, because a framer that has lost track of where
112 /// records begin cannot resynchronise. Per-record deserialisation failures are not
113 /// terminal and are reported by [`Self::Parser`] instead.
114 type Framer: tokio_util::codec::Decoder<Item = Self::Frame, Error = StreamError> + Send;
115
116 /// Deserialises framed records.
117 type Parser: FrameParser<Self::Frame, T> + Send;
118
119 /// Build a framer for one body.
120 fn framer(&self, options: &DecodeOptions) -> Self::Framer;
121
122 /// Build a parser for one body.
123 fn parser(&self) -> Self::Parser;
124}
125
126/// A format that can be constructed without configuration.
127///
128/// Needed by server-side extractors, which are built by the framework rather than by the user
129/// and so have nowhere to receive constructor arguments. A method rather than a `Default`
130/// supertrait on [`StreamFormat`], so that a format which genuinely cannot be defaulted is not
131/// locked out of the rest of the abstraction.
132pub trait DefaultFormat: Sized {
133 /// The configuration to use when the caller supplied none.
134 fn default_format() -> Self;
135}
136
137/// Limits applied while decoding.
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139#[non_exhaustive]
140pub struct DecodeOptions {
141 /// Maximum length of a single decoded object.
142 pub max_obj_len: usize,
143 /// Initial capacity of the read buffer.
144 pub buf_capacity: usize,
145}
146
147/// 8 KiB, matching the existing `reqwest-streams` default.
148pub const DEFAULT_BUF_CAPACITY: usize = 8 * 1024;
149
150impl DecodeOptions {
151 /// Options with no per-object limit.
152 ///
153 /// Appropriate for a client reading a server it chose. A server reading untrusted input
154 /// should set [`max_obj_len`] instead.
155 ///
156 /// [`max_obj_len`]: DecodeOptions::max_obj_len
157 pub fn new() -> Self {
158 Self {
159 max_obj_len: usize::MAX,
160 buf_capacity: DEFAULT_BUF_CAPACITY,
161 }
162 }
163
164 /// Set the maximum length of a single decoded object.
165 pub fn max_obj_len(mut self, value: usize) -> Self {
166 self.max_obj_len = value;
167 self
168 }
169
170 /// Set the initial capacity of the read buffer.
171 pub fn buf_capacity(mut self, value: usize) -> Self {
172 self.buf_capacity = value;
173 self
174 }
175}
176
177impl Default for DecodeOptions {
178 fn default() -> Self {
179 Self::new()
180 }
181}