1use crate::io::util::read_until::read_until_internal;
2use crate::io::AsyncBufRead;
3
4use pin_project_lite::pin_project;
5use std::future::Future;
6use std::io;
7use std::marker::PhantomPinned;
8use std::mem;
9use std::pin::Pin;
10use std::string::FromUtf8Error;
11use std::task::{Context, Poll};
12
13pin_project! {
14 /// Future for the [`read_line`](crate::io::AsyncBufReadExt::read_line) method.
15 #[derive(Debug)]
16 #[must_use = "futures do nothing unless you `.await` or poll them"]
17 pub struct ReadLine<'a, R: ?Sized> {
18 reader: &'a mut R,
19 // This is the buffer we were provided. It will be replaced with an empty string
20 // while reading to postpone utf-8 handling until after reading.
21 output: &'a mut String,
22 // The actual allocation of the string is moved into this vector instead.
23 buf: Vec<u8>,
24 // The number of bytes appended to buf. This can be less than buf.len() if
25 // the buffer was not empty when the operation was started.
26 read: usize,
27 // Make this future `!Unpin` for compatibility with async trait methods.
28 #[pin]
29 _pin: PhantomPinned,
30 }
31}
32
33pub(crate) fn read_line<'a, R>(reader: &'a mut R, string: &'a mut String) -> ReadLine<'a, R>
34where
35 R: AsyncBufRead + ?Sized + Unpin,
36{
37 ReadLine {
38 reader,
39 buf: mem::take(string).into_bytes(),
40 output: string,
41 read: 0,
42 _pin: PhantomPinned,
43 }
44}
45
46fn put_back_original_data(output: &mut String, mut vector: Vec<u8>, num_bytes_read: usize) {
47 let original_len = vector.len() - num_bytes_read;
48 vector.truncate(original_len);
49 *output = String::from_utf8(vector).expect("The original data must be valid utf-8.");
50}
51
52/// This handles the various failure cases and puts the string back into `output`.
53///
54/// The `truncate_on_io_error` bool is necessary because `read_to_string` and `read_line`
55/// disagree on what should happen when an IO error occurs.
56pub(super) fn finish_string_read(
57 io_res: io::Result<usize>,
58 utf8_res: Result<String, FromUtf8Error>,
59 read: usize,
60 output: &mut String,
61 truncate_on_io_error: bool,
62) -> Poll<io::Result<usize>> {
63 match (io_res, utf8_res) {
64 (Ok(num_bytes), Ok(string)) => {
65 debug_assert_eq!(read, 0);
66 *output = string;
67 Poll::Ready(Ok(num_bytes))
68 }
69 (Err(io_err), Ok(string)) => {
70 *output = string;
71 if truncate_on_io_error {
72 let original_len = output.len() - read;
73 output.truncate(original_len);
74 }
75 Poll::Ready(Err(io_err))
76 }
77 (Ok(num_bytes), Err(utf8_err)) => {
78 debug_assert_eq!(read, 0);
79 put_back_original_data(output, utf8_err.into_bytes(), num_bytes);
80
81 Poll::Ready(Err(io::Error::new(
82 io::ErrorKind::InvalidData,
83 "stream did not contain valid UTF-8",
84 )))
85 }
86 (Err(io_err), Err(utf8_err)) => {
87 put_back_original_data(output, utf8_err.into_bytes(), read);
88
89 Poll::Ready(Err(io_err))
90 }
91 }
92}
93
94pub(super) fn read_line_internal<R: AsyncBufRead + ?Sized>(
95 reader: Pin<&mut R>,
96 cx: &mut Context<'_>,
97 output: &mut String,
98 buf: &mut Vec<u8>,
99 read: &mut usize,
100) -> Poll<io::Result<usize>> {
101 let io_res = ready!(read_until_internal(reader, cx, b'\n', buf, read));
102 let utf8_res = String::from_utf8(mem::take(buf));
103
104 // At this point both buf and output are empty. The allocation is in utf8_res.
105
106 debug_assert!(buf.is_empty());
107 debug_assert!(output.is_empty());
108 finish_string_read(io_res, utf8_res, *read, output, false)
109}
110
111impl<R: AsyncBufRead + ?Sized + Unpin> Future for ReadLine<'_, R> {
112 type Output = io::Result<usize>;
113
114 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
115 let me = self.project();
116
117 read_line_internal(Pin::new(*me.reader), cx, me.output, me.buf, me.read)
118 }
119}
120