use std::fmt::{self, Write};
use std::marker::PhantomData;
use smallvec::SmallVec;
use uri::{UriPart, Path, Query, UriDisplay, Origin};
pub struct Formatter<'i, P: UriPart> {
prefixes: SmallVec<[&'static str; 3]>,
inner: &'i mut (dyn Write + 'i),
previous: bool,
fresh: bool,
_marker: PhantomData<P>,
}
impl<'i, P: UriPart> Formatter<'i, P> {
#[inline(always)]
pub(crate) fn new(inner: &'i mut (dyn Write + 'i)) -> Self {
Formatter {
inner,
prefixes: SmallVec::new(),
previous: false,
fresh: true,
_marker: PhantomData,
}
}
#[inline(always)]
fn refreshed<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, f: F) -> fmt::Result {
self.refresh();
let result = f(self);
self.refresh();
result
}
pub fn write_raw<S: AsRef<str>>(&mut self, string: S) -> fmt::Result {
if self.fresh && P::DELIMITER == '/' {
if self.previous {
self.inner.write_char(P::DELIMITER)?;
}
} else if self.fresh && P::DELIMITER == '&' {
if self.previous {
self.inner.write_char(P::DELIMITER)?;
}
if !self.prefixes.is_empty() {
for (i, prefix) in self.prefixes.iter().enumerate() {
self.inner.write_str(prefix)?;
if i < self.prefixes.len() - 1 {
self.inner.write_str(".")?;
}
}
self.inner.write_str("=")?;
}
}
self.fresh = false;
self.previous = true;
self.inner.write_str(string.as_ref())
}
#[inline]
pub fn write_value<T: UriDisplay<P>>(&mut self, value: T) -> fmt::Result {
self.refreshed(|f| UriDisplay::fmt(&value, f))
}
#[inline(always)]
pub fn refresh(&mut self) {
self.fresh = true;
}
}
impl<'i> Formatter<'i, Query> {
fn with_prefix<F>(&mut self, prefix: &str, f: F) -> fmt::Result
where F: FnOnce(&mut Self) -> fmt::Result
{
struct PrefixGuard<'f, 'i>(&'f mut Formatter<'i, Query>);
impl<'f, 'i> PrefixGuard<'f, 'i> {
fn new(prefix: &str, f: &'f mut Formatter<'i, Query>) -> Self {
let prefix = unsafe { std::mem::transmute(prefix) };
f.prefixes.push(prefix);
PrefixGuard(f)
}
}
impl Drop for PrefixGuard<'_, '_> {
fn drop(&mut self) {
self.0.prefixes.pop();
}
}
f(&mut PrefixGuard::new(prefix, self).0)
}
#[inline]
pub fn write_named_value<T: UriDisplay<Query>>(&mut self, name: &str, value: T) -> fmt::Result {
self.refreshed(|f| f.with_prefix(name, |f| f.write_value(value)))
}
}
impl<'i, P: UriPart> fmt::Write for Formatter<'i, P> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_raw(s)
}
}
#[doc(hidden)]
pub enum UriArgumentsKind<A> {
Static(&'static str),
Dynamic(A)
}
#[doc(hidden)]
pub enum UriQueryArgument<'a> {
Raw(&'a str),
NameValue(&'a str, &'a dyn UriDisplay<Query>),
Value(&'a dyn UriDisplay<Query>)
}
#[doc(hidden)]
pub struct UriArguments<'a> {
pub path: UriArgumentsKind<&'a [&'a dyn UriDisplay<Path>]>,
pub query: Option<UriArgumentsKind<&'a [UriQueryArgument<'a>]>>,
}
impl<'a> UriArguments<'a> {
#[doc(hidden)]
pub fn into_origin(self) -> Origin<'static> {
use std::borrow::Cow;
use self::{UriArgumentsKind::*, UriQueryArgument::*};
let path: Cow<'static, str> = match self.path {
Static(path) => path.into(),
Dynamic(args) => {
let mut string = String::from("/");
{
let mut formatter = Formatter::<Path>::new(&mut string);
for value in args {
let _ = formatter.write_value(value);
}
}
string.into()
}
};
let query: Option<Cow<'_, str>> = self.query.and_then(|q| match q {
Static(query) => Some(query.into()),
Dynamic(args) if args.is_empty() => None,
Dynamic(args) => {
let mut string = String::new();
{
let mut f = Formatter::<Query>::new(&mut string);
for arg in args {
let _ = match arg {
Raw(v) => f.write_raw(v),
NameValue(n, v) => f.write_named_value(n, v),
Value(v) => f.write_value(v),
};
}
}
Some(string.into())
}
});
Origin::new(path, query)
}
}
#[cfg(test)]
mod prefix_soundness_test {
use crate::uri::{Formatter, Query, UriDisplay};
struct MyValue;
impl UriDisplay<Query> for MyValue {
fn fmt(&self, _f: &mut Formatter<'_, Query>) -> std::fmt::Result {
panic!()
}
}
struct MyDisplay;
impl UriDisplay<Query> for MyDisplay {
fn fmt(&self, formatter: &mut Formatter<'_, Query>) -> std::fmt::Result {
struct Wrapper<'a, 'b>(&'a mut Formatter<'b, Query>);
impl<'a, 'b> Drop for Wrapper<'a, 'b> {
fn drop(&mut self) {
let _overlap = String::from("12345");
self.0.write_raw("world").ok();
assert!(self.0.prefixes.is_empty());
}
}
let wrapper = Wrapper(formatter);
let temporary_string = String::from("hello");
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
wrapper.0.write_named_value(&temporary_string, MyValue)
}));
Ok(())
}
}
#[test]
fn check_consistency() {
let string = format!("{}", &MyDisplay as &dyn UriDisplay<Query>);
assert_eq!(string, "world");
}
}