use base64::{Engine, engine::general_purpose::STANDARD};
use bytes::Bytes;
use crate::{Error, ErrorKind, Source};
pub(crate) mod escape;
#[cfg(feature = "client")]
mod stream;
#[cfg(feature = "client")]
pub use stream::BodyStream;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Encoding {
Base64,
JsonString,
}
#[derive(Debug, Clone)]
#[cfg_attr(not(feature = "client"), allow(dead_code))]
pub(crate) enum Segment {
Bytes(Bytes),
Data {
source: Source,
size: u64,
encoded: u64,
encoding: Encoding,
},
}
#[derive(Debug, Clone)]
#[cfg_attr(not(feature = "client"), allow(dead_code))]
pub struct Body {
segments: Vec<Segment>,
length: u64,
block: usize,
}
impl Body {
pub(crate) fn new(segments: Vec<Segment>, length: u64, block: usize) -> Self {
Self {
segments,
length,
block,
}
}
pub fn len(&self) -> u64 {
self.length
}
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn into_bytes(self) -> Result<Bytes, Error> {
let mut out = Vec::with_capacity(usize::try_from(self.length).unwrap_or(0));
for segment in self.segments {
match segment {
Segment::Bytes(bytes) => out.extend_from_slice(&bytes),
Segment::Data {
source: Source::Bytes(data),
encoding,
..
} => encode(&data, encoding, &mut out),
Segment::Data { .. } => {
return Err(attachment(
"a file attachment is read while the body streams",
));
}
}
}
Ok(Bytes::from(out))
}
#[cfg(feature = "client")]
pub fn into_stream(self) -> BodyStream {
BodyStream::new(self.segments, self.block)
}
}
fn encode(block: &[u8], encoding: Encoding, out: &mut Vec<u8>) {
match encoding {
Encoding::Base64 => out.extend_from_slice(STANDARD.encode(block).as_bytes()),
Encoding::JsonString => escape::escape_into(block, out),
}
}
pub(crate) fn attachment(detail: &'static str) -> Error {
Error::new(ErrorKind::Attachment).with_detail(detail)
}