1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// =============================================================================
// Copyright (c) 2026 Haixing Hu.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0.
// =============================================================================
use std::{
io,
pin::Pin,
task::{
Context,
Poll,
},
};
use crate::{
AsyncClose,
AsyncOutput,
traits::normalize_async_error,
};
/// Asynchronous output that accepts at most a fixed number of items.
///
/// # Type Parameters
///
/// - `O`: Wrapped asynchronous output type.
#[must_use]
#[derive(Debug)]
pub struct AsyncLimitOutput<O> {
/// Output constrained by this wrapper.
inner: O,
/// Number of items still accepted.
remaining: u64,
}
impl<O> AsyncClose for AsyncLimitOutput<O>
where
O: AsyncClose,
{
/// Polls closing through the wrapped output.
///
/// # Parameters
///
/// - `cx`: Task context used to register a wake-up.
///
/// # Returns
///
/// Returns [`Poll::Pending`] while closing is incomplete, otherwise a
/// ready success result.
///
/// # Errors
///
/// Returns an error reported by the wrapped output. Invalid asynchronous
/// error kinds are normalized to [`io::ErrorKind::InvalidData`].
#[inline(always)]
fn poll_close(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
// SAFETY: `inner` is never moved while projecting this pinned wrapper.
let this = unsafe { self.get_unchecked_mut() };
// SAFETY: The pinned wrapper keeps `inner` at a stable address.
unsafe { Pin::new_unchecked(&mut this.inner) }
.poll_close(cx)
.map(|result| result.map_err(normalize_async_error))
}
}
impl<O> AsyncLimitOutput<O> {
/// Creates a limited asynchronous output.
///
/// # Parameters
///
/// - `inner`: Asynchronous output to wrap.
/// - `limit`: Maximum item count accepted by this wrapper.
///
/// # Returns
///
/// Returns an output with `limit` items remaining.
#[inline(always)]
pub const fn new(inner: O, limit: u64) -> Self {
Self {
inner,
remaining: limit,
}
}
/// Returns the remaining accepted item count.
///
/// # Returns
///
/// Returns zero after the configured limit has been consumed.
#[inline(always)]
#[must_use]
pub const fn remaining(&self) -> u64 {
self.remaining
}
/// Returns a shared reference to the wrapped output.
///
/// # Returns
///
/// Returns the wrapped asynchronous output.
#[inline(always)]
#[must_use]
pub const fn inner(&self) -> &O {
&self.inner
}
/// Returns a mutable reference to the wrapped output.
///
/// Writes performed directly on the returned output bypass this wrapper's
/// remaining-item limit.
///
/// # Returns
///
/// Returns the wrapped asynchronous output.
#[inline(always)]
#[must_use]
pub fn inner_mut(&mut self) -> &mut O {
&mut self.inner
}
/// Consumes this wrapper and returns the wrapped output.
///
/// # Returns
///
/// Returns the asynchronous output.
#[inline(always)]
#[must_use]
pub fn into_inner(self) -> O {
self.inner
}
}
impl<O> AsyncOutput for AsyncLimitOutput<O>
where
O: AsyncOutput,
{
/// Item type accepted by the limited output.
type Item = O::Item;
/// Preserves the wrapped output's buffering declaration.
///
/// # Returns
///
/// Returns the wrapped output's buffering declaration.
#[inline(always)]
fn is_buffered(&self) -> bool {
self.inner.is_buffered()
}
/// Polls a write bounded by the remaining item count.
///
/// The method completes with zero items without polling `inner` when the
/// limit is exhausted or `count` is zero.
///
/// # Parameters
///
/// - `cx`: Task context used to register a wake-up.
/// - `input`: Source item slice.
/// - `index`: Starting source index.
/// - `count`: Maximum number of items offered.
///
/// # Returns
///
/// Returns [`Poll::Pending`] when the output is not ready. A ready success
/// contains the number of items accepted within the remaining limit.
///
/// # Errors
///
/// Returns an I/O error reported by the wrapped output without consuming
/// the remaining limit.
///
/// # Safety
///
/// The range `index..index + count` must be valid for `input`.
unsafe fn poll_write_unchecked(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
input: &[Self::Item],
index: usize,
count: usize,
) -> Poll<io::Result<usize>> {
// SAFETY: `inner` is never moved while projecting this pinned wrapper.
let this = unsafe { self.as_mut().get_unchecked_mut() };
if this.remaining == 0 || count == 0 {
return Poll::Ready(Ok(0));
}
let requested = usize::try_from(this.remaining)
.unwrap_or(usize::MAX)
.min(count);
let source = &input[index..index + requested];
// SAFETY: The pinned wrapper never moves `inner`.
let inner = unsafe { Pin::new_unchecked(&mut this.inner) };
match inner.poll_write(cx, source) {
Poll::Ready(Ok(written)) => {
this.remaining -= written as u64;
Poll::Ready(Ok(written))
}
Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
Poll::Pending => Poll::Pending,
}
}
/// Polls the wrapped output's flush operation.
///
/// # Parameters
///
/// - `cx`: Task context used to register a wake-up.
///
/// # Returns
///
/// Returns [`Poll::Pending`] while flushing is incomplete, otherwise a
/// ready success result.
///
/// # Errors
///
/// Returns an error reported by the wrapped output. Invalid asynchronous
/// error kinds are normalized to [`io::ErrorKind::InvalidData`].
#[inline(always)]
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
// SAFETY: `inner` is never moved while projecting this pinned wrapper.
let this = unsafe { self.get_unchecked_mut() };
// SAFETY: The pinned wrapper keeps `inner` at a stable address.
unsafe { Pin::new_unchecked(&mut this.inner) }
.poll_flush(cx)
.map(|result| result.map_err(normalize_async_error))
}
}