httplz 0.0.0

A sans-io, no-std HTTP implementation
Documentation
use core::fmt::Arguments;

use crate::{Error, ErrorKind, Header, HeaderOther, HeaderSpecial, RequestLine, StatusLine};

pub struct FmtWriteAdapter<T> {
	inner: T,
	err: Written,
}
impl<T> FmtWriteAdapter<T> {
	pub fn new(inner: T) -> Self {
		Self { inner, err: Ok(()) }
	}
}

impl<T> core::fmt::Write for FmtWriteAdapter<T>
where
	T: Write + Sized,
{
	fn write_str(&mut self, s: &str) -> core::fmt::Result {
		self.inner.write(s.as_bytes()).map_err(|e| {
			self.err = Err(e);
			core::fmt::Error
		})
	}
}

pub trait Write {
	fn write(&mut self, buf: &[u8]) -> Written;

	fn write_fmt(&mut self, args: Arguments<'_>) -> Written
	where
		Self: Sized,
	{
		let mut adp = FmtWriteAdapter::new(self);
		let _ = core::fmt::Write::write_fmt(&mut adp, args);

		adp.err
	}
}

impl<T> Write for &mut T
where
	T: Write,
{
	fn write(&mut self, buf: &[u8]) -> Written {
		(**self).write(buf)
	}
}

pub struct WriteCursor<T> {
	at: usize,
	buf: T,
}
impl<T> WriteCursor<T> {
	pub fn new(buf: T) -> Self {
		Self { at: 0, buf }
	}

	pub fn reset(&mut self) {
		self.at = 0;
	}

	pub fn remaining_mut(&mut self) -> &mut [u8]
	where
		T: AsMut<[u8]>,
	{
		&mut self.buf.as_mut()[self.at..]
	}

	pub fn written(&mut self) -> &[u8]
	where
		T: AsRef<[u8]>,
	{
		&self.buf.as_ref()[..self.at]
	}
}

impl<T> Write for WriteCursor<T>
where
	T: AsMut<[u8]>,
{
	fn write(&mut self, buf: &[u8]) -> Written {
		let remaining = self.remaining_mut();
		if buf.len() > remaining.len() {
			return Err(Error::from(ErrorKind::BufNotBigEnough));
		}

		remaining[..buf.len()].copy_from_slice(buf);
		self.at += buf.len();

		Ok(())
	}
}

pub type Written = Result<(), Error>;

pub fn status_line(sl: &StatusLine, mut w: impl Write) -> Written {
	write!(
		w,
		"{} {} {}\r\n",
		sl.version, sl.status_code, sl.status_text
	)
}

pub fn request_line(rl: &RequestLine, mut w: impl Write) -> Written {
	write!(w, "{} {} {}\r\n", rl.method, rl.target, rl.version)
}

pub fn header(h: &Header, mut w: impl Write) -> Written {
	match h {
		Header::Special(h) => match h {
			HeaderSpecial::TransferEncodingChunked => write!(w, "Transfer-Encoding: Chunked"),
			HeaderSpecial::ContentLength(cl) => write!(w, "Content-Length: {}", cl),
		},
		Header::Other(HeaderOther { name, value }) => {
			write!(w, "{name}: ")?;
			w.write(value)
		},
	}?;

	write!(w, "\r\n")
}