Skip to content

Commit 8ca8963

Browse files
committed
Expose stream reader input completion state
1 parent b525470 commit 8ca8963

5 files changed

Lines changed: 65 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
state inspection.
3838
- Added stream adapter `engine()` and `is_padded()` helpers for dependency-free
3939
policy inspection in diagnostics and audit logs.
40+
- Added reader-side stream `has_finished_input()` helpers so callers can
41+
distinguish EOF or terminal padding from fully drained buffered output.
4042

4143
## 0.8.0 - 2026-05-16
4244

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -525,11 +525,12 @@ returned to the caller. Decoders additionally expose `has_terminal_padding()`
525525
so framed protocols can tell when a padded payload has ended and leave adjacent
526526
bytes for the next protocol layer. Reader adapters also expose `is_finished()`
527527
once EOF or terminal padding has been reached and all buffered output has been
528-
drained. Writer adapters expose `try_finish()` to finalize pending input and
529-
flush the wrapped writer without consuming the adapter, plus `is_finalized()`
530-
for explicit state inspection; after successful finalization, later writes are
531-
rejected. Their `Debug` output reports adapter state without formatting the
532-
wrapped reader or writer.
528+
drained, and `has_finished_input()` when the wrapped reader has reached EOF or
529+
terminal padding but buffered output may still remain. Writer adapters expose
530+
`try_finish()` to finalize pending input and flush the wrapped writer without
531+
consuming the adapter, plus `is_finalized()` for explicit state inspection;
532+
after successful finalization, later writes are rejected. Their `Debug` output
533+
reports adapter state without formatting the wrapped reader or writer.
533534

534535
URL-safe, no-padding encoding:
535536

docs/MIGRATION.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ Base64 input and flush the wrapped writer without immediately consuming the
188188
adapter. After successful finalization, later non-empty writes return
189189
`InvalidInput`. Stream adapters also expose non-sensitive state helpers such as
190190
`engine()`, `is_padded()`, `pending_len()`, `has_pending_input()`, reader-side
191-
`buffered_output_len()`, and decoder-side `has_terminal_padding()` for framed
192-
protocols and audit logging.
191+
`buffered_output_len()` and `has_finished_input()`, and decoder-side
192+
`has_terminal_padding()` for framed protocols and audit logging.
193193

194194
The `tokio` feature is reserved for future async wrappers. It is currently
195195
inert and dependency-free; use the explicit `stream` feature for `std::io`

src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,16 @@ pub mod stream {
12051205
self.terminal_seen
12061206
}
12071207

1208+
/// Returns whether this decoder reader has reached EOF or terminal
1209+
/// padding in the wrapped reader.
1210+
///
1211+
/// This may become `true` before [`Self::is_finished`] when decoded
1212+
/// output is still buffered for the caller.
1213+
#[must_use]
1214+
pub const fn has_finished_input(&self) -> bool {
1215+
self.finished
1216+
}
1217+
12081218
/// Returns whether this reader has reached EOF or terminal padding
12091219
/// and has no decoded output buffered for the caller.
12101220
#[must_use]
@@ -1453,6 +1463,16 @@ pub mod stream {
14531463
!self.output.is_empty()
14541464
}
14551465

1466+
/// Returns whether this encoder reader has reached EOF in the wrapped
1467+
/// reader.
1468+
///
1469+
/// This may become `true` before [`Self::is_finished`] when encoded
1470+
/// output is still buffered for the caller.
1471+
#[must_use]
1472+
pub const fn has_finished_input(&self) -> bool {
1473+
self.finished
1474+
}
1475+
14561476
/// Returns whether this reader has reached EOF and has no encoded
14571477
/// output buffered for the caller.
14581478
#[must_use]

tests/rfc4648.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,6 +2657,7 @@ fn stream_encoder_reader_handles_small_reads() {
26572657
let mut reader = EncoderReader::new(&b"hello"[..], STANDARD);
26582658
assert_eq!(reader.engine(), STANDARD);
26592659
assert!(reader.is_padded());
2660+
assert!(!reader.has_finished_input());
26602661
assert!(!reader.is_finished());
26612662
assert_eq!(reader.pending_len(), 0);
26622663
assert!(!reader.has_pending_input());
@@ -2676,6 +2677,7 @@ fn stream_encoder_reader_handles_small_reads() {
26762677
assert!(!reader.has_pending_input());
26772678
assert_eq!(reader.buffered_output_len(), 0);
26782679
assert!(!reader.has_buffered_output());
2680+
assert!(reader.has_finished_input());
26792681
assert!(reader.is_finished());
26802682
}
26812683

@@ -2691,13 +2693,40 @@ fn stream_encoder_reader_reports_buffered_output() {
26912693
assert!(reader.has_pending_input());
26922694
assert_eq!(reader.buffered_output_len(), 3);
26932695
assert!(reader.has_buffered_output());
2696+
assert!(!reader.has_finished_input());
26942697
assert!(!reader.is_finished());
26952698

26962699
let mut rest = Vec::new();
26972700
reader.read_to_end(&mut rest).unwrap();
26982701
assert_eq!(rest, b"GVsbG8=");
26992702
assert_eq!(reader.buffered_output_len(), 0);
27002703
assert!(!reader.has_buffered_output());
2704+
assert!(reader.has_finished_input());
2705+
assert!(reader.is_finished());
2706+
}
2707+
2708+
#[cfg(feature = "stream")]
2709+
#[test]
2710+
fn stream_encoder_reader_finished_input_before_buffer_drain() {
2711+
let mut reader = EncoderReader::new(&b"h"[..], STANDARD);
2712+
assert!(!reader.has_finished_input());
2713+
assert!(!reader.is_finished());
2714+
2715+
let mut first = [0u8; 1];
2716+
assert_eq!(reader.read(&mut first).unwrap(), 1);
2717+
assert_eq!(first, [b'a']);
2718+
assert_eq!(reader.pending_len(), 0);
2719+
assert_eq!(reader.buffered_output_len(), 3);
2720+
assert!(reader.has_buffered_output());
2721+
assert!(reader.has_finished_input());
2722+
assert!(!reader.is_finished());
2723+
2724+
let mut rest = Vec::new();
2725+
reader.read_to_end(&mut rest).unwrap();
2726+
assert_eq!(rest, b"A==");
2727+
assert_eq!(reader.buffered_output_len(), 0);
2728+
assert!(!reader.has_buffered_output());
2729+
assert!(reader.has_finished_input());
27012730
assert!(reader.is_finished());
27022731
}
27032732

@@ -2885,6 +2914,7 @@ fn stream_decoder_reader_handles_small_reads() {
28852914
let mut reader = DecoderReader::new(&b"aGVsbG8="[..], STANDARD);
28862915
assert_eq!(reader.engine(), STANDARD);
28872916
assert!(reader.is_padded());
2917+
assert!(!reader.has_finished_input());
28882918
assert!(!reader.is_finished());
28892919
assert_eq!(reader.pending_len(), 0);
28902920
assert!(!reader.has_pending_input());
@@ -2905,6 +2935,7 @@ fn stream_decoder_reader_handles_small_reads() {
29052935
assert_eq!(reader.buffered_output_len(), 0);
29062936
assert!(!reader.has_buffered_output());
29072937
assert!(reader.has_terminal_padding());
2938+
assert!(reader.has_finished_input());
29082939
assert!(reader.is_finished());
29092940
}
29102941

@@ -2919,6 +2950,7 @@ fn stream_decoder_reader_reports_buffered_output() {
29192950
assert_eq!(reader.buffered_output_len(), 2);
29202951
assert!(reader.has_buffered_output());
29212952
assert!(!reader.has_terminal_padding());
2953+
assert!(!reader.has_finished_input());
29222954
assert!(!reader.is_finished());
29232955

29242956
let mut rest = Vec::new();
@@ -2927,6 +2959,7 @@ fn stream_decoder_reader_reports_buffered_output() {
29272959
assert_eq!(reader.buffered_output_len(), 0);
29282960
assert!(!reader.has_buffered_output());
29292961
assert!(reader.has_terminal_padding());
2962+
assert!(reader.has_finished_input());
29302963
assert!(reader.is_finished());
29312964
}
29322965

@@ -2940,6 +2973,7 @@ fn stream_decoder_reader_terminal_padding_finishes_after_buffer_drain() {
29402973
assert_eq!(reader.buffered_output_len(), 1);
29412974
assert!(reader.has_buffered_output());
29422975
assert!(reader.has_terminal_padding());
2976+
assert!(reader.has_finished_input());
29432977
assert!(!reader.is_finished());
29442978

29452979
let mut rest = Vec::new();
@@ -2948,6 +2982,7 @@ fn stream_decoder_reader_terminal_padding_finishes_after_buffer_drain() {
29482982
assert_eq!(reader.buffered_output_len(), 0);
29492983
assert!(!reader.has_buffered_output());
29502984
assert!(reader.has_terminal_padding());
2985+
assert!(reader.has_finished_input());
29512986
assert!(reader.is_finished());
29522987
}
29532988

0 commit comments

Comments
 (0)