Skip to main content

qubit_text_io/adapters/
encoded_text_reader.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::io::{
11    self,
12    Read,
13};
14
15use encoding_rs::Encoding;
16
17use crate::{
18    CodingErrorPolicy,
19    StringTextReader,
20    TextLineRead,
21    TextRead,
22};
23
24/// Text reader that decodes a byte reader with an explicit encoding.
25///
26/// This adapter currently decodes the complete byte input during construction
27/// and then serves text from memory. It is useful for bounded resources such as
28/// configuration files, database fields, and already-limited payloads.
29#[derive(Debug)]
30pub struct EncodedTextReader {
31    inner: StringTextReader,
32}
33
34impl EncodedTextReader {
35    /// Reads and decodes all bytes from `reader`.
36    ///
37    /// # Parameters
38    /// - `reader`: Byte reader to decode.
39    /// - `encoding`: Encoding used by the byte reader.
40    /// - `policy`: Malformed input handling policy.
41    ///
42    /// # Returns
43    /// A text reader over the decoded content.
44    ///
45    /// # Errors
46    /// Returns an I/O error when reading fails or when strict decoding finds
47    /// malformed input bytes.
48    pub fn new<R>(
49        mut reader: R,
50        encoding: &'static Encoding,
51        policy: CodingErrorPolicy,
52    ) -> io::Result<Self>
53    where
54        R: Read,
55    {
56        let mut bytes = Vec::new();
57        reader.read_to_end(&mut bytes)?;
58        let text = decode_bytes(bytes.as_slice(), encoding, policy)?;
59        Ok(Self {
60            inner: StringTextReader::new(text),
61        })
62    }
63
64    /// Returns the inner decoded string reader.
65    ///
66    /// # Returns
67    /// The decoded string reader.
68    #[must_use]
69    pub fn into_inner(self) -> StringTextReader {
70        self.inner
71    }
72}
73
74impl TextRead for EncodedTextReader {
75    type Error = io::Error;
76
77    #[inline]
78    fn read_char(&mut self) -> Result<Option<char>, Self::Error> {
79        self.inner.read_char().map_err(|error| match error {})
80    }
81
82    #[inline]
83    fn read_chars(&mut self, output: &mut Vec<char>, max: usize) -> Result<usize, Self::Error> {
84        self.inner
85            .read_chars(output, max)
86            .map_err(|error| match error {})
87    }
88
89    #[inline]
90    fn read_to_string(&mut self, output: &mut String) -> Result<usize, Self::Error> {
91        self.inner
92            .read_to_string(output)
93            .map_err(|error| match error {})
94    }
95}
96
97impl TextLineRead for EncodedTextReader {
98    #[inline]
99    fn read_line(&mut self, output: &mut String) -> Result<bool, Self::Error> {
100        self.inner.read_line(output).map_err(|error| match error {})
101    }
102}
103
104/// Decodes bytes into a string according to the requested policy.
105///
106/// # Parameters
107/// - `bytes`: Encoded input bytes.
108/// - `encoding`: Encoding used by `bytes`.
109/// - `policy`: Malformed input handling policy.
110///
111/// # Returns
112/// Decoded Unicode text.
113///
114/// # Errors
115/// Returns [`io::ErrorKind::InvalidData`] when strict mode detects malformed
116/// input bytes.
117fn decode_bytes(
118    bytes: &[u8],
119    encoding: &'static Encoding,
120    policy: CodingErrorPolicy,
121) -> io::Result<String> {
122    let (text, _, had_errors) = encoding.decode(bytes);
123    if had_errors && policy == CodingErrorPolicy::Strict {
124        return Err(io::Error::new(
125            io::ErrorKind::InvalidData,
126            format!("input is not valid {}", encoding.name()),
127        ));
128    }
129    Ok(text.into_owned())
130}