use core::fmt::{self, Write as _};
use core::marker::PhantomData;
use core::mem;
use core::ops::ControlFlow;
use crate::parser::str::{find_split, find_split_hole};
use crate::parser::str::{process_percent_encoded_best_effort, PctEncodedFragments};
use crate::percent_encode::PercentEncoded;
use crate::spec::Spec;
use crate::template::components::{ExprBody, Modifier, Operator, VarName, VarSpec};
use crate::template::context::{
private::Sealed as VisitorSealed, AssocVisitor, Context, ListVisitor, Visitor,
};
use crate::template::error::{Error, ErrorKind};
use crate::template::{UriTemplateStr, ValueType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Chunk<'a> {
Literal(&'a str),
Expr(ExprBody<'a>),
}
#[derive(Debug, Clone)]
struct Chunks<'a> {
template: &'a str,
}
impl<'a> Chunks<'a> {
#[inline]
#[must_use]
fn new(template: &'a UriTemplateStr) -> Self {
Self {
template: template.as_str(),
}
}
}
impl<'a> Iterator for Chunks<'a> {
type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.template.is_empty() {
return None;
}
match find_split(self.template, b'{') {
Some(("", _)) => {
let (expr_body, rest) = find_split_hole(&self.template[1..], b'}')
.expect("[validity] expression inside a template must be closed");
self.template = rest;
Some(Chunk::Expr(ExprBody::new(expr_body)))
}
Some((lit, rest)) => {
self.template = rest;
Some(Chunk::Literal(lit))
}
None => Some(Chunk::Literal(mem::take(&mut self.template))),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Expanded<'a, S, C> {
template: &'a UriTemplateStr,
context: &'a C,
_spec: PhantomData<fn() -> S>,
}
impl<'a, S: Spec, C: Context> Expanded<'a, S, C> {
#[inline]
pub(super) fn new(template: &'a UriTemplateStr, context: &'a C) -> Result<Self, Error> {
Self::typecheck_context(template, context)?;
Ok(Self {
template,
context,
_spec: PhantomData,
})
}
fn typecheck_context(template: &UriTemplateStr, context: &C) -> Result<(), Error> {
let mut pos = 0;
for chunk in Chunks::new(template) {
let (_op, varlist) = match chunk {
Chunk::Expr(expr_body) => expr_body.decompose(),
Chunk::Literal(lit) => {
pos += lit.len();
continue;
}
};
for varspec in varlist {
let ty = context.visit(TypeVisitor::new(varspec.name()));
let modifier = varspec.modifier();
if matches!(modifier, Modifier::MaxLen(_))
&& matches!(ty, ValueType::List | ValueType::Assoc)
{
return Err(Error::new(ErrorKind::UnexpectedValueType, pos));
}
}
}
Ok(())
}
}
impl<S: Spec, C: Context> fmt::Display for Expanded<'_, S, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for chunk in Chunks::new(self.template) {
let expr = match chunk {
Chunk::Literal(lit) => {
f.write_str(lit)?;
continue;
}
Chunk::Expr(body) => body,
};
expand::<S, _>(f, expr, self.context)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
struct OpProps {
first: &'static str,
sep: &'static str,
named: bool,
ifemp: &'static str,
allow_reserved: bool,
}
impl OpProps {
const PROPS: [Self; 8] = [
Self {
first: "",
sep: ",",
named: false,
ifemp: "",
allow_reserved: false,
},
Self {
first: "",
sep: ",",
named: false,
ifemp: "",
allow_reserved: true,
},
Self {
first: "#",
sep: ",",
named: false,
ifemp: "",
allow_reserved: true,
},
Self {
first: ".",
sep: ".",
named: false,
ifemp: "",
allow_reserved: false,
},
Self {
first: "/",
sep: "/",
named: false,
ifemp: "",
allow_reserved: false,
},
Self {
first: ";",
sep: ";",
named: true,
ifemp: "",
allow_reserved: false,
},
Self {
first: "?",
sep: "&",
named: true,
ifemp: "=",
allow_reserved: false,
},
Self {
first: "&",
sep: "&",
named: true,
ifemp: "=",
allow_reserved: false,
},
];
#[must_use]
#[inline]
pub(super) fn from_op(op: Operator) -> &'static Self {
let index = match op {
Operator::String => 0,
Operator::Reserved => 1,
Operator::Fragment => 2,
Operator::Label => 3,
Operator::PathSegments => 4,
Operator::PathParams => 5,
Operator::FormQuery => 6,
Operator::FormQueryCont => 7,
};
&Self::PROPS[index]
}
}
fn expand<S: Spec, C: Context>(
f: &mut fmt::Formatter<'_>,
expr: ExprBody<'_>,
context: &C,
) -> fmt::Result {
let (op, varlist) = expr.decompose();
let mut is_first_varspec = true;
for varspec in varlist {
let visitor = ValueVisitor::<S>::new(f, varspec, op, &mut is_first_varspec);
let token = context.visit(visitor)?;
let formatter_ptr = token.formatter_ptr();
if formatter_ptr != f as *mut _ {
panic!("invalid `VisitDoneToken` was returned");
}
}
Ok(())
}
#[inline]
fn escape_write<S: Spec, T: fmt::Display>(
f: &mut fmt::Formatter<'_>,
v: T,
allow_reserved: bool,
) -> fmt::Result {
use core::fmt::Display;
if allow_reserved {
let result = process_percent_encoded_best_effort(v, |frag| {
let result = match frag {
PctEncodedFragments::Char(s, _) => f.write_str(s),
PctEncodedFragments::NoPctStr(s) => PercentEncoded::<_, S>::characters(s).fmt(f),
PctEncodedFragments::StrayPercent => f.write_str("%25"),
PctEncodedFragments::InvalidUtf8PctTriplets(s) => f.write_str(s),
};
if result.is_err() {
return ControlFlow::Break(result);
}
ControlFlow::Continue(())
});
match result {
Ok(ControlFlow::Break(Ok(_)) | ControlFlow::Continue(_)) => Ok(()),
Ok(ControlFlow::Break(Err(e))) | Err(e) => Err(e),
}
} else {
struct UnreservePercentEncodeWriter<'a, 'b, S> {
writer: &'a mut fmt::Formatter<'b>,
_spec: PhantomData<fn() -> S>,
}
impl<S: Spec> fmt::Write for UnreservePercentEncodeWriter<'_, '_, S> {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
fmt::Display::fmt(&PercentEncoded::<_, S>::unreserve(s), self.writer)
}
}
let mut writer = UnreservePercentEncodeWriter::<S> {
writer: f,
_spec: PhantomData,
};
write!(writer, "{v}")
}
}
fn escape_write_with_maxlen<S: Spec, T: fmt::Display>(
writer: &mut PrefixOnceWriter<'_, '_>,
v: T,
allow_reserved: bool,
max_len: Option<u16>,
) -> fmt::Result {
if allow_reserved {
let mut max_len = max_len.map_or(usize::MAX, usize::from);
let result = process_percent_encoded_best_effort(v, |frag| {
if max_len == 0 {
return ControlFlow::Break(Ok(()));
}
let result =
match frag {
PctEncodedFragments::Char(s, _) => {
max_len -= 1;
writer.write_str(s)
}
PctEncodedFragments::NoPctStr(s) => {
let mut chars = s.char_indices();
let count =
chars.by_ref().take(max_len).last().map(|(i, _)| i).expect(
"[consistency] decomposed string fragment must not be empty",
);
let sub_len = s.len() - chars.as_str().len();
max_len -= count;
write!(
writer,
"{}",
PercentEncoded::<_, S>::characters(&s[..sub_len])
)
}
PctEncodedFragments::StrayPercent => {
max_len -= 1;
writer.write_str("%25")
}
PctEncodedFragments::InvalidUtf8PctTriplets(s) => {
let count = max_len.min(s.len() / 3);
let sub_len = count * 3;
max_len -= count;
writer.write_str(&s[..sub_len])
}
};
if result.is_err() {
return ControlFlow::Break(result);
}
ControlFlow::Continue(())
});
match result {
Ok(ControlFlow::Break(Ok(_)) | ControlFlow::Continue(_)) => Ok(()),
Ok(ControlFlow::Break(Err(e))) | Err(e) => Err(e),
}
} else {
match max_len {
Some(max_len) => {
let mut writer = TruncatePercentEncodeWriter::<S, _> {
inner: writer,
rest_num_chars: usize::from(max_len),
_spec: PhantomData,
};
write!(writer, "{v}")
}
None => write!(writer, "{}", PercentEncoded::<_, S>::unreserve(v)),
}
}
}
struct TruncatePercentEncodeWriter<'a, S, W> {
inner: &'a mut W,
rest_num_chars: usize,
_spec: PhantomData<fn() -> S>,
}
impl<S: Spec, W: fmt::Write> fmt::Write for TruncatePercentEncodeWriter<'_, S, W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.rest_num_chars == 0 {
return Ok(());
}
let mut chars = s.char_indices();
let skip_count = chars
.by_ref()
.take(self.rest_num_chars)
.last()
.map_or(0, |(i, _)| i + 1);
let len = s.len() - chars.as_str().len();
let truncated = &s[..len];
write!(
self.inner,
"{}",
PercentEncoded::<_, S>::unreserve(truncated)
)?;
self.rest_num_chars -= skip_count;
Ok(())
}
}
struct PrefixOnceWriter<'a, 'b> {
inner: &'a mut fmt::Formatter<'b>,
prefix: Option<&'a str>,
}
impl<'a, 'b> PrefixOnceWriter<'a, 'b> {
#[inline]
#[must_use]
fn new(inner: &'a mut fmt::Formatter<'b>) -> Self {
Self {
inner,
prefix: None,
}
}
#[inline]
#[must_use]
fn with_prefix(inner: &'a mut fmt::Formatter<'b>, prefix: &'a str) -> Self {
Self {
inner,
prefix: Some(prefix),
}
}
#[inline]
#[must_use]
fn has_unwritten_prefix(&self) -> bool {
self.prefix.is_some()
}
}
impl fmt::Write for PrefixOnceWriter<'_, '_> {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
if let Some(prefix) = self.prefix.take() {
self.inner.write_str(prefix)?;
}
self.inner.write_str(s)
}
}
struct VisitDoneToken<'a, 'b, S>(ValueVisitor<'a, 'b, S>);
impl<'a, 'b, S: Spec> VisitDoneToken<'a, 'b, S> {
#[inline]
#[must_use]
fn new(visitor: ValueVisitor<'a, 'b, S>) -> Self {
Self(visitor)
}
#[inline]
#[must_use]
fn formatter_ptr(&self) -> *const fmt::Formatter<'b> {
self.0.formatter_ptr()
}
}
impl<S: Spec> fmt::Debug for VisitDoneToken<'_, '_, S> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("VisitDoneToken")
}
}
struct ValueVisitor<'a, 'b, S> {
f: &'a mut fmt::Formatter<'b>,
varspec: VarSpec<'a>,
op: Operator,
is_first_varspec: &'a mut bool,
_spec: PhantomData<fn() -> S>,
}
impl<'a, 'b, S: Spec> ValueVisitor<'a, 'b, S> {
#[inline]
#[must_use]
fn new(
f: &'a mut fmt::Formatter<'b>,
varspec: VarSpec<'a>,
op: Operator,
is_first_varspec: &'a mut bool,
) -> Self {
Self {
f,
varspec,
op,
is_first_varspec,
_spec: PhantomData,
}
}
#[inline]
#[must_use]
fn formatter_ptr(&self) -> *const fmt::Formatter<'b> {
self.f as &_ as *const _
}
}
impl<S: Spec> VisitorSealed for ValueVisitor<'_, '_, S> {}
impl<'a, 'b, S: Spec> Visitor for ValueVisitor<'a, 'b, S> {
type Result = Result<VisitDoneToken<'a, 'b, S>, fmt::Error>;
type ListVisitor = ListValueVisitor<'a, 'b, S>;
type AssocVisitor = AssocValueVisitor<'a, 'b, S>;
#[inline]
#[must_use]
fn var_name(&self) -> VarName<'a> {
self.varspec.name()
}
#[inline]
fn visit_undefined(self) -> Self::Result {
Ok(VisitDoneToken::new(self))
}
#[inline]
fn visit_string<T: fmt::Display>(self, v: T) -> Self::Result {
let oppr = OpProps::from_op(self.op);
if mem::replace(self.is_first_varspec, false) {
self.f.write_str(oppr.first)?;
} else {
self.f.write_str(oppr.sep)?;
}
let mut writer = if oppr.named {
self.f.write_str(self.varspec.name().as_str())?;
PrefixOnceWriter::with_prefix(self.f, "=")
} else {
PrefixOnceWriter::new(self.f)
};
let max_len = match self.varspec.modifier() {
Modifier::None | Modifier::Explode => None,
Modifier::MaxLen(max_len) => Some(max_len),
};
escape_write_with_maxlen::<S, T>(&mut writer, v, oppr.allow_reserved, max_len)?;
if writer.has_unwritten_prefix() {
self.f.write_str(oppr.ifemp)?;
}
Ok(VisitDoneToken::new(self))
}
#[inline]
#[must_use]
fn visit_list(self) -> Self::ListVisitor {
let oppr = OpProps::from_op(self.op);
ListValueVisitor {
visitor: self,
num_elems: 0,
oppr,
}
}
#[inline]
#[must_use]
fn visit_assoc(self) -> Self::AssocVisitor {
let oppr = OpProps::from_op(self.op);
AssocValueVisitor {
visitor: self,
num_elems: 0,
oppr,
}
}
}
struct ListValueVisitor<'a, 'b, S> {
visitor: ValueVisitor<'a, 'b, S>,
num_elems: usize,
oppr: &'static OpProps,
}
impl<'a, 'b, S: Spec> ListValueVisitor<'a, 'b, S> {
fn visit_item_impl<T: fmt::Display>(&mut self, item: T) -> fmt::Result {
let modifier = self.visitor.varspec.modifier();
let is_explode = match modifier {
Modifier::MaxLen(_) => panic!(
"value type changed since `UriTemplateStr::expand()`: \
prefix modifier is not applicable to a list"
),
Modifier::None => false,
Modifier::Explode => true,
};
if self.num_elems == 0 {
if mem::replace(self.visitor.is_first_varspec, false) {
self.visitor.f.write_str(self.oppr.first)?;
} else {
self.visitor.f.write_str(self.oppr.sep)?;
}
if self.oppr.named {
self.visitor
.f
.write_str(self.visitor.varspec.name().as_str())?;
self.visitor.f.write_char('=')?;
}
} else {
match (self.oppr.named, is_explode) {
(_, false) => self.visitor.f.write_char(',')?,
(false, true) => self.visitor.f.write_str(self.oppr.sep)?,
(true, true) => {
self.visitor.f.write_str(self.oppr.sep)?;
escape_write::<S, _>(
self.visitor.f,
self.visitor.varspec.name().as_str(),
self.oppr.allow_reserved,
)?;
self.visitor.f.write_char('=')?;
}
}
}
escape_write::<S, _>(self.visitor.f, item, self.oppr.allow_reserved)?;
self.num_elems += 1;
Ok(())
}
}
impl<S: Spec> VisitorSealed for ListValueVisitor<'_, '_, S> {}
impl<'a, 'b, S: Spec> ListVisitor for ListValueVisitor<'a, 'b, S> {
type Result = Result<VisitDoneToken<'a, 'b, S>, fmt::Error>;
#[inline]
fn visit_item<T: fmt::Display>(&mut self, item: T) -> ControlFlow<Self::Result> {
match self.visit_item_impl(item) {
Ok(_) => ControlFlow::Continue(()),
Err(e) => ControlFlow::Break(Err(e)),
}
}
#[inline]
fn finish(self) -> Self::Result {
Ok(VisitDoneToken::new(self.visitor))
}
}
struct AssocValueVisitor<'a, 'b, S> {
visitor: ValueVisitor<'a, 'b, S>,
num_elems: usize,
oppr: &'static OpProps,
}
impl<'a, 'b, S: Spec> AssocValueVisitor<'a, 'b, S> {
fn visit_entry_impl<K: fmt::Display, V: fmt::Display>(
&mut self,
key: K,
value: V,
) -> fmt::Result {
let modifier = self.visitor.varspec.modifier();
let is_explode = match modifier {
Modifier::MaxLen(_) => panic!(
"value type changed since `UriTemplateStr::expand()`: \
prefix modifier is not applicable to an associative array"
),
Modifier::None => false,
Modifier::Explode => true,
};
if self.num_elems == 0 {
if mem::replace(self.visitor.is_first_varspec, false) {
self.visitor.f.write_str(self.oppr.first)?;
} else {
self.visitor.f.write_str(self.oppr.sep)?;
}
if is_explode {
escape_write::<S, _>(self.visitor.f, key, self.oppr.allow_reserved)?;
self.visitor.f.write_char('=')?;
} else {
if self.oppr.named {
escape_write::<S, _>(
self.visitor.f,
self.visitor.varspec.name().as_str(),
self.oppr.allow_reserved,
)?;
self.visitor.f.write_char('=')?;
}
escape_write::<S, _>(self.visitor.f, key, self.oppr.allow_reserved)?;
self.visitor.f.write_char(',')?;
}
} else {
match (self.oppr.named, is_explode) {
(_, false) => {
self.visitor.f.write_char(',')?;
escape_write::<S, _>(self.visitor.f, key, self.oppr.allow_reserved)?;
self.visitor.f.write_char(',')?;
}
(false, true) => {
self.visitor.f.write_str(self.oppr.sep)?;
escape_write::<S, _>(self.visitor.f, key, self.oppr.allow_reserved)?;
self.visitor.f.write_char('=')?;
}
(true, true) => {
self.visitor.f.write_str(self.oppr.sep)?;
escape_write::<S, _>(self.visitor.f, key, self.oppr.allow_reserved)?;
self.visitor.f.write_char('=')?;
}
}
}
escape_write::<S, _>(self.visitor.f, value, self.oppr.allow_reserved)?;
self.num_elems += 1;
Ok(())
}
}
impl<S: Spec> VisitorSealed for AssocValueVisitor<'_, '_, S> {}
impl<'a, 'b, S: Spec> AssocVisitor for AssocValueVisitor<'a, 'b, S> {
type Result = Result<VisitDoneToken<'a, 'b, S>, fmt::Error>;
#[inline]
fn visit_entry<K: fmt::Display, V: fmt::Display>(
&mut self,
key: K,
value: V,
) -> ControlFlow<Self::Result> {
match self.visit_entry_impl(key, value) {
Ok(_) => ControlFlow::Continue(()),
Err(e) => ControlFlow::Break(Err(e)),
}
}
#[inline]
fn finish(self) -> Self::Result {
Ok(VisitDoneToken::new(self.visitor))
}
}
struct TypeVisitor<'a> {
var_name: VarName<'a>,
}
impl<'a> TypeVisitor<'a> {
#[inline]
#[must_use]
fn new(var_name: VarName<'a>) -> Self {
Self { var_name }
}
}
impl VisitorSealed for TypeVisitor<'_> {}
impl<'a> Visitor for TypeVisitor<'a> {
type Result = ValueType;
type ListVisitor = ListTypeVisitor;
type AssocVisitor = AssocTypeVisitor;
#[inline]
fn var_name(&self) -> VarName<'a> {
self.var_name
}
#[inline]
fn visit_undefined(self) -> Self::Result {
ValueType::undefined()
}
#[inline]
fn visit_string<T: fmt::Display>(self, _: T) -> Self::Result {
ValueType::string()
}
#[inline]
fn visit_list(self) -> Self::ListVisitor {
ListTypeVisitor
}
#[inline]
fn visit_assoc(self) -> Self::AssocVisitor {
AssocTypeVisitor
}
}
struct ListTypeVisitor;
impl VisitorSealed for ListTypeVisitor {}
impl ListVisitor for ListTypeVisitor {
type Result = ValueType;
#[inline]
fn visit_item<T: fmt::Display>(&mut self, _item: T) -> ControlFlow<Self::Result> {
ControlFlow::Break(ValueType::nonempty_list())
}
#[inline]
fn finish(self) -> Self::Result {
ValueType::empty_list()
}
}
struct AssocTypeVisitor;
impl VisitorSealed for AssocTypeVisitor {}
impl AssocVisitor for AssocTypeVisitor {
type Result = ValueType;
#[inline]
fn visit_entry<K: fmt::Display, V: fmt::Display>(
&mut self,
_key: K,
_value: V,
) -> ControlFlow<Self::Result> {
ControlFlow::Break(ValueType::nonempty_assoc())
}
#[inline]
fn finish(self) -> Self::Result {
ValueType::empty_assoc()
}
}