qubit_fs/read/async_file_reader.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8// facade tests.
9//! Concrete asynchronous file reader handle.
10
11use std::fmt::Debug;
12use std::fmt::Formatter;
13use std::fmt::Result as FmtResult;
14use std::io::Result as IoResult;
15use std::pin::Pin;
16use std::task::Context;
17use std::task::Poll;
18
19use qubit_io::AsyncInput;
20use qubit_io::BoxAsyncInput;
21
22use crate::metadata::OpenedFileInfo;
23
24/// Type-erased asynchronous byte input associated with an opened file.
25///
26/// # Examples
27///
28/// This example uses an isolated in-memory provider fixture.
29///
30/// ```rust
31/// # mod support { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/common/rustdoc_support.rs")); }
32/// # use support::*;
33/// # let (filesystem, _) = async_recording_spi::async_recording_file_system(Default::default());
34/// # poll_support::ready(async {
35/// use qubit_fs::Path;
36/// use qubit_fs::read::ReadOptions;
37/// use qubit_io::AsyncInput;
38///
39/// let mut reader = filesystem.open_reader(&Path::parse("/report")?, ReadOptions::default()).await?;
40/// let mut prefix = [0; 3];
41/// assert_eq!(3, reader.read_fully_async(&mut prefix).await?);
42/// assert_eq!(*b"byt", prefix);
43/// # Ok::<(), Box<dyn std::error::Error>>(())
44/// # }).unwrap();
45/// ```
46pub struct AsyncFileReader {
47 /// Pinned provider byte input.
48 inner: BoxAsyncInput<dyn AsyncInput<Item = u8> + Send>,
49 /// Stable identity and metadata captured at open time.
50 info: OpenedFileInfo,
51}
52
53impl AsyncFileReader {
54 /// Wraps an already-open asynchronous provider byte input.
55 ///
56 /// # Parameters
57 /// - `inner`: Runtime-neutral asynchronous byte input.
58 /// - `info`: File identity and optional open-time metadata snapshot.
59 ///
60 /// # Returns
61 /// A pinned, type-erased asynchronous file reader.
62 #[inline]
63 #[must_use]
64 pub(crate) fn new(info: OpenedFileInfo, inner: Box<dyn AsyncInput<Item = u8> + Send>) -> Self {
65 Self {
66 inner: BoxAsyncInput::new(inner),
67 info,
68 }
69 }
70
71 /// Returns the fixed identity and open-time metadata snapshot.
72 ///
73 /// # Returns
74 /// Information captured when the reader was opened.
75 #[inline]
76 #[must_use]
77 pub fn info(&self) -> &OpenedFileInfo {
78 &self.info
79 }
80}
81
82impl AsyncInput for AsyncFileReader {
83 type Item = u8;
84
85 #[inline]
86 fn is_buffered(&self) -> bool {
87 self.inner.is_buffered()
88 }
89
90 #[inline]
91 unsafe fn poll_read_unchecked(
92 self: Pin<&mut Self>,
93 cx: &mut Context<'_>,
94 output: &mut [u8],
95 index: usize,
96 count: usize,
97 ) -> Poll<IoResult<usize>> {
98 let this = self.get_mut();
99 // SAFETY: The caller guarantees the same range contract required by
100 // the wrapped asynchronous input.
101 unsafe {
102 Pin::new(&mut this.inner)
103 .get_pin_mut()
104 .poll_read_unchecked(cx, output, index, count)
105 }
106 }
107}
108
109impl Debug for AsyncFileReader {
110 #[inline]
111 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
112 formatter
113 .debug_struct("AsyncFileReader")
114 .field("info", &self.info)
115 .finish_non_exhaustive()
116 }
117}