iri_string/template/string.rs
1//! Template string types.
2
3use core::fmt;
4
5#[cfg(feature = "alloc")]
6use alloc::borrow::Cow;
7#[cfg(all(feature = "alloc", not(feature = "std")))]
8use alloc::boxed::Box;
9#[cfg(feature = "alloc")]
10use alloc::rc::Rc;
11#[cfg(feature = "alloc")]
12use alloc::string::String;
13#[cfg(feature = "alloc")]
14use alloc::sync::Arc;
15
16use crate::spec::Spec;
17use crate::template::components::{VarListIter, VarName};
18use crate::template::context::{Context, DynamicContext};
19use crate::template::error::{Error, ErrorKind};
20use crate::template::expand::{expand_whole_dynamic, Chunk, Chunks, Expanded};
21use crate::template::parser::validate_template_str;
22
23#[cfg(feature = "alloc")]
24pub use self::owned::UriTemplateString;
25
26/// Implements `PartialEq` and `PartialOrd`.
27macro_rules! impl_cmp {
28 ($ty_common:ty, $ty_lhs:ty, $ty_rhs:ty) => {
29 impl PartialEq<$ty_rhs> for $ty_lhs {
30 #[inline]
31 fn eq(&self, o: &$ty_rhs) -> bool {
32 <$ty_common as PartialEq<$ty_common>>::eq(self.as_ref(), o.as_ref())
33 }
34 }
35 impl PartialEq<$ty_lhs> for $ty_rhs {
36 #[inline]
37 fn eq(&self, o: &$ty_lhs) -> bool {
38 <$ty_common as PartialEq<$ty_common>>::eq(self.as_ref(), o.as_ref())
39 }
40 }
41 impl PartialOrd<$ty_rhs> for $ty_lhs {
42 #[inline]
43 fn partial_cmp(&self, o: &$ty_rhs) -> Option<core::cmp::Ordering> {
44 <$ty_common as PartialOrd<$ty_common>>::partial_cmp(self.as_ref(), o.as_ref())
45 }
46 }
47 impl PartialOrd<$ty_lhs> for $ty_rhs {
48 #[inline]
49 fn partial_cmp(&self, o: &$ty_lhs) -> Option<core::cmp::Ordering> {
50 <$ty_common as PartialOrd<$ty_common>>::partial_cmp(self.as_ref(), o.as_ref())
51 }
52 }
53 };
54}
55
56#[cfg(feature = "alloc")]
57mod owned;
58
59/// A borrowed slice of a URI template.
60///
61/// URI Template is defined by [RFC 6570].
62///
63/// Note that "URI Template" can also be used for IRI.
64///
65/// [RFC 6570]: https://www.rfc-editor.org/rfc/rfc6570.html
66///
67/// # Valid values
68///
69/// This type can have a URI template string.
70///
71/// # Applied errata
72///
73/// [Errata ID 6937](https://www.rfc-editor.org/errata/eid6937) is applied, so
74/// single quotes are allowed to appear in an URI template.
75///
76/// ```
77/// # use iri_string::template::Error;
78/// use iri_string::template::UriTemplateStr;
79///
80/// let template = UriTemplateStr::new("'quoted'")?;
81/// # Ok::<_, Error>(())
82/// ```
83#[cfg_attr(feature = "serde", derive(serde::Serialize))]
84#[cfg_attr(feature = "serde", serde(transparent))]
85#[repr(transparent)]
86#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct UriTemplateStr {
88 /// The raw string.
89 inner: str,
90}
91
92impl UriTemplateStr {
93 /// Creates a new string.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// # use iri_string::template::Error;
99 /// use iri_string::template::UriTemplateStr;
100 ///
101 /// let template = UriTemplateStr::new("/users/{username}")?;
102 /// # Ok::<_, Error>(())
103 /// ```
104 #[inline]
105 pub fn new(s: &str) -> Result<&Self, Error> {
106 TryFrom::try_from(s)
107 }
108
109 /// Creates a new string without validation.
110 ///
111 /// This does not validate the given string, so it is caller's
112 /// responsibility to ensure the given string is valid.
113 ///
114 /// # Safety
115 ///
116 /// The given string must be syntactically valid as `Self` type.
117 /// If not, any use of the returned value or the call of this
118 /// function itself may result in undefined behavior.
119 #[inline]
120 #[must_use]
121 pub unsafe fn new_unchecked(s: &str) -> &Self {
122 // SAFETY: `new_unchecked` requires the same precondition
123 // as `new_always_unchecked`.
124 unsafe { Self::new_always_unchecked(s) }
125 }
126
127 /// Creates a new string without any validation.
128 ///
129 /// This does not validate the given string at any time.
130 ///
131 /// Intended for internal use.
132 ///
133 /// # Safety
134 ///
135 /// The given string must be valid.
136 #[inline]
137 #[must_use]
138 unsafe fn new_always_unchecked(s: &str) -> &Self {
139 // SAFETY: the cast is safe since `Self` type has `repr(transparent)`
140 // attribute and the content is guaranteed as valid by the
141 // precondition of the function.
142 unsafe { &*(s as *const str as *const Self) }
143 }
144
145 /// Checks if the given string content is valid as `Self`.
146 pub(super) fn validate(s: &str) -> Result<(), Error> {
147 validate_template_str(s)
148 }
149
150 /// Returns the template as a plain `&str`.
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// # use iri_string::template::Error;
156 /// use iri_string::template::UriTemplateStr;
157 ///
158 /// let template = UriTemplateStr::new("/users/{username}")?;
159 /// assert_eq!(template.as_str(), "/users/{username}");
160 /// # Ok::<_, Error>(())
161 /// ```
162 #[inline]
163 #[must_use]
164 pub fn as_str(&self) -> &str {
165 self.as_ref()
166 }
167
168 /// Returns the template string length.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// # use iri_string::template::Error;
174 /// use iri_string::template::UriTemplateStr;
175 ///
176 /// let template = UriTemplateStr::new("/users/{username}")?;
177 /// assert_eq!(template.len(), "/users/{username}".len());
178 /// # Ok::<_, Error>(())
179 /// ```
180 #[inline]
181 #[must_use]
182 pub fn len(&self) -> usize {
183 self.as_str().len()
184 }
185
186 /// Returns whether the string is empty.
187 ///
188 /// # Examples
189 ///
190 /// ```
191 /// # use iri_string::template::Error;
192 /// use iri_string::template::UriTemplateStr;
193 ///
194 /// let template = UriTemplateStr::new("/users/{username}")?;
195 /// assert!(!template.is_empty());
196 ///
197 /// let empty = UriTemplateStr::new("")?;
198 /// assert!(empty.is_empty());
199 /// # Ok::<_, Error>(())
200 /// ```
201 #[inline]
202 #[must_use]
203 pub fn is_empty(&self) -> bool {
204 self.as_str().is_empty()
205 }
206}
207
208impl UriTemplateStr {
209 /// Expands the template with the given context.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// # use iri_string::template::Error;
215 /// # #[cfg(feature = "alloc")] {
216 /// use iri_string::spec::UriSpec;
217 /// use iri_string::template::UriTemplateStr;
218 /// use iri_string::template::simple_context::SimpleContext;
219 ///
220 /// let mut context = SimpleContext::new();
221 /// context.insert("username", "foo");
222 ///
223 /// let template = UriTemplateStr::new("/users/{username}")?;
224 /// let expanded = template.expand::<UriSpec, _>(&context)?;
225 ///
226 /// assert_eq!(
227 /// expanded.to_string(),
228 /// "/users/foo"
229 /// );
230 /// # }
231 /// # Ok::<_, Error>(())
232 /// ```
233 ///
234 /// You can control allowed characters in the output by changing spec type.
235 ///
236 /// ```
237 /// # use iri_string::template::Error;
238 /// # #[cfg(feature = "alloc")] {
239 /// use iri_string::spec::{IriSpec, UriSpec};
240 /// use iri_string::template::UriTemplateStr;
241 /// use iri_string::template::simple_context::SimpleContext;
242 ///
243 /// let mut context = SimpleContext::new();
244 /// context.insert("alpha", "\u{03B1}");
245 ///
246 /// let template = UriTemplateStr::new("{?alpha}")?;
247 ///
248 /// assert_eq!(
249 /// template.expand::<UriSpec, _>(&context)?.to_string(),
250 /// "?alpha=%CE%B1",
251 /// "a URI cannot contain Unicode alpha (U+03B1), so it should be escaped"
252 /// );
253 /// assert_eq!(
254 /// template.expand::<IriSpec, _>(&context)?.to_string(),
255 /// "?alpha=\u{03B1}",
256 /// "an IRI can contain Unicode alpha (U+03B1), so it written as is"
257 /// );
258 /// # }
259 /// # Ok::<_, Error>(())
260 /// ```
261 #[inline]
262 pub fn expand<'a, S: Spec, C: Context>(
263 &'a self,
264 context: &'a C,
265 ) -> Result<Expanded<'a, S, C>, Error> {
266 Expanded::new(self, context)
267 }
268
269 /// Expands the template with the given dynamic context.
270 ///
271 #[cfg_attr(
272 feature = "alloc",
273 doc = concat!(
274 "If you need the allocated [`String`], use",
275 "[`expand_dynamic_to_string`][`Self::expand_dynamic_to_string`]."
276 )
277 )]
278 ///
279 /// See the documentation for [`DynamicContext`] for usage.
280 pub fn expand_dynamic<S: Spec, W: fmt::Write, C: DynamicContext>(
281 &self,
282 writer: &mut W,
283 context: &mut C,
284 ) -> Result<(), Error> {
285 expand_whole_dynamic::<S, _, _>(self, writer, context)
286 }
287
288 /// Expands the template into a string, with the given dynamic context.
289 ///
290 /// This is basically [`expand_dynamic`][`Self::expand_dynamic`] method
291 /// that returns an owned string instead of writing to the given writer.
292 ///
293 /// See the documentation for [`DynamicContext`] for usage.
294 ///
295 /// # Examples
296 ///
297 /// ```
298 /// # #[cfg(feature = "alloc")]
299 /// # extern crate alloc;
300 /// # use iri_string::template::Error;
301 /// # #[cfg(feature = "alloc")] {
302 /// # use alloc::string::String;
303 /// use iri_string::template::UriTemplateStr;
304 /// # use iri_string::template::context::{DynamicContext, Visitor, VisitPurpose};
305 /// use iri_string::spec::UriSpec;
306 ///
307 /// struct MyContext<'a> {
308 /// // See the documentation for `DynamicContext`.
309 /// # /// Target path.
310 /// # target: &'a str,
311 /// # /// Username.
312 /// # username: Option<&'a str>,
313 /// # /// A flag to remember whether the URI template
314 /// # /// attempted to use `username` variable.
315 /// # username_visited: bool,
316 /// }
317 /// #
318 /// # impl DynamicContext for MyContext<'_> {
319 /// # fn on_expansion_start(&mut self) {
320 /// # // Reset the state.
321 /// # self.username_visited = false;
322 /// # }
323 /// # fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result {
324 /// # match visitor.var_name().as_str() {
325 /// # "target" => visitor.visit_string(self.target),
326 /// # "username" => {
327 /// # if visitor.purpose() == VisitPurpose::Expand {
328 /// # // The variable `username` is being used
329 /// # // on the template expansion.
330 /// # // Don't care whether `username` is defined or not.
331 /// # self.username_visited = true;
332 /// # }
333 /// # if let Some(username) = &self.username {
334 /// # visitor.visit_string(username)
335 /// # } else {
336 /// # visitor.visit_undefined()
337 /// # }
338 /// # }
339 /// # _ => visitor.visit_undefined(),
340 /// # }
341 /// # }
342 /// # }
343 ///
344 /// let mut context = MyContext {
345 /// target: "/posts/1",
346 /// username: Some("the_admin"),
347 /// username_visited: false,
348 /// };
349 ///
350 /// // No access to the variable `username`.
351 /// let template = UriTemplateStr::new("{+target}{?username}")?;
352 /// let s = template.expand_dynamic_to_string::<UriSpec, _>(&mut context)?;
353 /// assert_eq!(s, "/posts/1?username=the_admin");
354 /// assert!(context.username_visited);
355 /// # }
356 /// # Ok::<_, Error>(())
357 /// ```
358 #[cfg(feature = "alloc")]
359 pub fn expand_dynamic_to_string<S: Spec, C: DynamicContext>(
360 &self,
361 context: &mut C,
362 ) -> Result<String, Error> {
363 let mut buf = String::new();
364 expand_whole_dynamic::<S, _, _>(self, &mut buf, context)?;
365 Ok(buf)
366 }
367
368 /// Returns an iterator of variables in the template.
369 ///
370 /// # Examples
371 ///
372 /// ```
373 /// # use iri_string::template::Error;
374 /// use iri_string::template::UriTemplateStr;
375 ///
376 /// let template = UriTemplateStr::new("foo{/bar*,baz:4}{?qux}{&bar*}")?;
377 /// let mut vars = template.variables();
378 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("bar"));
379 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("baz"));
380 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("qux"));
381 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("bar"));
382 /// # Ok::<_, Error>(())
383 /// ```
384 #[inline]
385 #[must_use]
386 pub fn variables(&self) -> UriTemplateVariables<'_> {
387 UriTemplateVariables::new(self)
388 }
389}
390
391impl fmt::Debug for UriTemplateStr {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 f.debug_tuple("UriTemplateStr").field(&&self.inner).finish()
394 }
395}
396
397impl AsRef<str> for UriTemplateStr {
398 #[inline]
399 fn as_ref(&self) -> &str {
400 &self.inner
401 }
402}
403
404impl AsRef<UriTemplateStr> for UriTemplateStr {
405 #[inline]
406 fn as_ref(&self) -> &UriTemplateStr {
407 self
408 }
409}
410
411#[cfg(feature = "alloc")]
412impl<'a> From<&'a UriTemplateStr> for Cow<'a, UriTemplateStr> {
413 #[inline]
414 fn from(s: &'a UriTemplateStr) -> Self {
415 Cow::Borrowed(s)
416 }
417}
418
419#[cfg(feature = "alloc")]
420impl From<&UriTemplateStr> for Arc<UriTemplateStr> {
421 fn from(s: &UriTemplateStr) -> Self {
422 let inner: &str = s.as_str();
423 let buf = Arc::<str>::from(inner);
424 // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so
425 // the memory layouts of `Arc<str>` and `Arc<UriTemplateStr>` are
426 // compatible.
427 unsafe {
428 let raw: *const str = Arc::into_raw(buf);
429 Self::from_raw(raw as *const UriTemplateStr)
430 }
431 }
432}
433
434#[cfg(feature = "alloc")]
435impl From<&UriTemplateStr> for Box<UriTemplateStr> {
436 fn from(s: &UriTemplateStr) -> Self {
437 let inner: &str = s.as_str();
438 let buf = Box::<str>::from(inner);
439 // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so
440 // the memory layouts of `Box<str>` and `Box<UriTemplateStr>` are
441 // compatible.
442 unsafe {
443 let raw: *mut str = Box::into_raw(buf);
444 Self::from_raw(raw as *mut UriTemplateStr)
445 }
446 }
447}
448
449#[cfg(feature = "alloc")]
450impl From<&UriTemplateStr> for Rc<UriTemplateStr> {
451 fn from(s: &UriTemplateStr) -> Self {
452 let inner: &str = s.as_str();
453 let buf = Rc::<str>::from(inner);
454 // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so
455 // the memory layouts of `Rc<str>` and `Rc<UriTemplateStr>` are
456 // compatible.
457 unsafe {
458 let raw: *const str = Rc::into_raw(buf);
459 Self::from_raw(raw as *const UriTemplateStr)
460 }
461 }
462}
463
464impl<'a> From<&'a UriTemplateStr> for &'a str {
465 #[inline]
466 fn from(s: &'a UriTemplateStr) -> &'a str {
467 s.as_ref()
468 }
469}
470
471impl<'a> TryFrom<&'a str> for &'a UriTemplateStr {
472 type Error = Error;
473
474 #[inline]
475 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
476 match UriTemplateStr::validate(s) {
477 // SAFETY: just confirmed the string is valid.
478 Ok(()) => Ok(unsafe { UriTemplateStr::new_always_unchecked(s) }),
479 Err(e) => Err(e),
480 }
481 }
482}
483
484impl<'a> TryFrom<&'a [u8]> for &'a UriTemplateStr {
485 type Error = Error;
486
487 #[inline]
488 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
489 let s = core::str::from_utf8(bytes)
490 .map_err(|e| Error::new(ErrorKind::InvalidUtf8, e.valid_up_to()))?;
491 match UriTemplateStr::validate(s) {
492 // SAFETY: just confirmed the string is valid.
493 Ok(()) => Ok(unsafe { UriTemplateStr::new_always_unchecked(s) }),
494 Err(e) => Err(e),
495 }
496 }
497}
498
499impl_cmp!(str, str, UriTemplateStr);
500impl_cmp!(str, &str, UriTemplateStr);
501impl_cmp!(str, str, &UriTemplateStr);
502
503impl fmt::Display for UriTemplateStr {
504 #[inline]
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 f.write_str(self.as_str())
507 }
508}
509
510/// Serde deserializer implementation.
511#[cfg(feature = "serde")]
512mod __serde_slice {
513 use super::UriTemplateStr;
514
515 use core::fmt;
516
517 use serde::{
518 de::{self, Visitor},
519 Deserialize, Deserializer,
520 };
521
522 /// Custom borrowed string visitor.
523 #[derive(Debug, Clone, Copy)]
524 struct CustomStrVisitor;
525
526 impl<'de> Visitor<'de> for CustomStrVisitor {
527 type Value = &'de UriTemplateStr;
528
529 #[inline]
530 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531 f.write_str("URI template string")
532 }
533
534 #[inline]
535 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
536 where
537 E: de::Error,
538 {
539 <&'de UriTemplateStr as TryFrom<&'de str>>::try_from(v).map_err(E::custom)
540 }
541 }
542
543 // About `'de` and `'a`, see
544 // <https://serde.rs/lifetimes.html#the-deserializede-lifetime>.
545 impl<'a, 'de: 'a> Deserialize<'de> for &'a UriTemplateStr {
546 #[inline]
547 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
548 where
549 D: Deserializer<'de>,
550 {
551 deserializer.deserialize_string(CustomStrVisitor)
552 }
553 }
554}
555
556/// An iterator of variables in a URI template.
557#[derive(Debug, Clone)]
558pub struct UriTemplateVariables<'a> {
559 /// Chunks iterator.
560 chunks: Chunks<'a>,
561 /// Variables in the last chunk.
562 vars_in_chunk: Option<VarListIter<'a>>,
563}
564
565impl<'a> UriTemplateVariables<'a> {
566 /// Creates a variables iterator from the URI template.
567 #[inline]
568 #[must_use]
569 fn new(template: &'a UriTemplateStr) -> Self {
570 Self {
571 chunks: Chunks::new(template),
572 vars_in_chunk: None,
573 }
574 }
575}
576
577impl<'a> Iterator for UriTemplateVariables<'a> {
578 type Item = VarName<'a>;
579
580 fn next(&mut self) -> Option<Self::Item> {
581 loop {
582 if let Some(vars) = &mut self.vars_in_chunk {
583 match vars.next() {
584 Some((_len, spec)) => return Some(spec.name()),
585 None => self.vars_in_chunk = None,
586 }
587 }
588 let expr = self.chunks.find_map(|chunk| match chunk {
589 Chunk::Literal(_) => None,
590 Chunk::Expr(v) => Some(v),
591 });
592 self.vars_in_chunk = match expr {
593 Some(expr) => Some(expr.decompose().1.into_iter()),
594 None => return None,
595 }
596 }
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603
604 use crate::spec::IriSpec;
605 use crate::template::context::{AssocVisitor, ListVisitor, Visitor};
606
607 struct TestContext;
608 impl Context for TestContext {
609 fn visit<V: Visitor>(&self, visitor: V) -> V::Result {
610 match visitor.var_name().as_str() {
611 "str" => visitor.visit_string("string"),
612 "list" => visitor
613 .visit_list()
614 .visit_items_and_finish(["item0", "item1", "item2"]),
615 "assoc" => visitor
616 .visit_assoc()
617 .visit_entries_and_finish([("key0", "value0"), ("key1", "value1")]),
618 _ => visitor.visit_undefined(),
619 }
620 }
621 }
622
623 #[test]
624 fn expand_error_pos() {
625 {
626 let e = UriTemplateStr::new("foo{list:4}")
627 .unwrap()
628 .expand::<IriSpec, _>(&TestContext)
629 .err()
630 .map(|e| e.location());
631 assert_eq!(e, Some("foo{".len()));
632 }
633
634 {
635 let e = UriTemplateStr::new("foo{/list*,list:4}")
636 .unwrap()
637 .expand::<IriSpec, _>(&TestContext)
638 .err()
639 .map(|e| e.location());
640 assert_eq!(e, Some("foo{/list*,".len()));
641 }
642
643 {
644 let e = UriTemplateStr::new("foo{/str:3,list*,assoc:4}")
645 .unwrap()
646 .expand::<IriSpec, _>(&TestContext)
647 .err()
648 .map(|e| e.location());
649 assert_eq!(e, Some("foo{/str:3,list*,".len()));
650 }
651 }
652}