Skip to main content

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}