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_always_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 /// Returns the template as a plain `&str`.
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// # use iri_string::template::Error;
151 /// use iri_string::template::UriTemplateStr;
152 ///
153 /// let template = UriTemplateStr::new("/users/{username}")?;
154 /// assert_eq!(template.as_str(), "/users/{username}");
155 /// # Ok::<_, Error>(())
156 /// ```
157 #[inline]
158 #[must_use]
159 pub fn as_str(&self) -> &str {
160 self.as_ref()
161 }
162
163 /// Returns the template string length.
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// # use iri_string::template::Error;
169 /// use iri_string::template::UriTemplateStr;
170 ///
171 /// let template = UriTemplateStr::new("/users/{username}")?;
172 /// assert_eq!(template.len(), "/users/{username}".len());
173 /// # Ok::<_, Error>(())
174 /// ```
175 #[inline]
176 #[must_use]
177 pub fn len(&self) -> usize {
178 self.as_str().len()
179 }
180
181 /// Returns whether the string is empty.
182 ///
183 /// # Examples
184 ///
185 /// ```
186 /// # use iri_string::template::Error;
187 /// use iri_string::template::UriTemplateStr;
188 ///
189 /// let template = UriTemplateStr::new("/users/{username}")?;
190 /// assert!(!template.is_empty());
191 ///
192 /// let empty = UriTemplateStr::new("")?;
193 /// assert!(empty.is_empty());
194 /// # Ok::<_, Error>(())
195 /// ```
196 #[inline]
197 #[must_use]
198 pub fn is_empty(&self) -> bool {
199 self.as_str().is_empty()
200 }
201}
202
203impl UriTemplateStr {
204 /// Expands the template with the given context.
205 ///
206 /// # Examples
207 ///
208 /// ```
209 /// # use iri_string::template::Error;
210 /// # #[cfg(feature = "alloc")] {
211 /// use iri_string::spec::UriSpec;
212 /// use iri_string::template::UriTemplateStr;
213 /// use iri_string::template::simple_context::SimpleContext;
214 ///
215 /// let mut context = SimpleContext::new();
216 /// context.insert("username", "foo");
217 ///
218 /// let template = UriTemplateStr::new("/users/{username}")?;
219 /// let expanded = template.expand::<UriSpec, _>(&context)?;
220 ///
221 /// assert_eq!(
222 /// expanded.to_string(),
223 /// "/users/foo"
224 /// );
225 /// # }
226 /// # Ok::<_, Error>(())
227 /// ```
228 ///
229 /// You can control allowed characters in the output by changing spec type.
230 ///
231 /// ```
232 /// # use iri_string::template::Error;
233 /// # #[cfg(feature = "alloc")] {
234 /// use iri_string::spec::{IriSpec, UriSpec};
235 /// use iri_string::template::UriTemplateStr;
236 /// use iri_string::template::simple_context::SimpleContext;
237 ///
238 /// let mut context = SimpleContext::new();
239 /// context.insert("alpha", "\u{03B1}");
240 ///
241 /// let template = UriTemplateStr::new("{?alpha}")?;
242 ///
243 /// assert_eq!(
244 /// template.expand::<UriSpec, _>(&context)?.to_string(),
245 /// "?alpha=%CE%B1",
246 /// "a URI cannot contain Unicode alpha (U+03B1), so it should be escaped"
247 /// );
248 /// assert_eq!(
249 /// template.expand::<IriSpec, _>(&context)?.to_string(),
250 /// "?alpha=\u{03B1}",
251 /// "an IRI can contain Unicode alpha (U+03B1), so it written as is"
252 /// );
253 /// # }
254 /// # Ok::<_, Error>(())
255 /// ```
256 #[inline]
257 pub fn expand<'a, S: Spec, C: Context>(
258 &'a self,
259 context: &'a C,
260 ) -> Result<Expanded<'a, S, C>, Error> {
261 Expanded::new(self, context)
262 }
263
264 /// Expands the template with the given dynamic context.
265 ///
266 #[cfg_attr(
267 feature = "alloc",
268 doc = concat!(
269 "If you need the allocated [`String`], use",
270 "[`expand_dynamic_to_string`][`Self::expand_dynamic_to_string`]."
271 )
272 )]
273 ///
274 /// See the documentation for [`DynamicContext`] for usage.
275 pub fn expand_dynamic<S: Spec, W: fmt::Write, C: DynamicContext>(
276 &self,
277 writer: &mut W,
278 context: &mut C,
279 ) -> Result<(), Error> {
280 expand_whole_dynamic::<S, _, _>(self, writer, context)
281 }
282
283 /// Expands the template into a string, with the given dynamic context.
284 ///
285 /// This is basically [`expand_dynamic`][`Self::expand_dynamic`] method
286 /// that returns an owned string instead of writing to the given writer.
287 ///
288 /// See the documentation for [`DynamicContext`] for usage.
289 ///
290 /// # Examples
291 ///
292 /// ```
293 /// # #[cfg(feature = "alloc")]
294 /// # extern crate alloc;
295 /// # use iri_string::template::Error;
296 /// # #[cfg(feature = "alloc")] {
297 /// # use alloc::string::String;
298 /// use iri_string::template::UriTemplateStr;
299 /// # use iri_string::template::context::{DynamicContext, Visitor, VisitPurpose};
300 /// use iri_string::spec::UriSpec;
301 ///
302 /// struct MyContext<'a> {
303 /// // See the documentation for `DynamicContext`.
304 /// # /// Target path.
305 /// # target: &'a str,
306 /// # /// Username.
307 /// # username: Option<&'a str>,
308 /// # /// A flag to remember whether the URI template
309 /// # /// attempted to use `username` variable.
310 /// # username_visited: bool,
311 /// }
312 /// #
313 /// # impl DynamicContext for MyContext<'_> {
314 /// # fn on_expansion_start(&mut self) {
315 /// # // Reset the state.
316 /// # self.username_visited = false;
317 /// # }
318 /// # fn visit_dynamic<V: Visitor>(&mut self, visitor: V) -> V::Result {
319 /// # match visitor.var_name().as_str() {
320 /// # "target" => visitor.visit_string(self.target),
321 /// # "username" => {
322 /// # if visitor.purpose() == VisitPurpose::Expand {
323 /// # // The variable `username` is being used
324 /// # // on the template expansion.
325 /// # // Don't care whether `username` is defined or not.
326 /// # self.username_visited = true;
327 /// # }
328 /// # if let Some(username) = &self.username {
329 /// # visitor.visit_string(username)
330 /// # } else {
331 /// # visitor.visit_undefined()
332 /// # }
333 /// # }
334 /// # _ => visitor.visit_undefined(),
335 /// # }
336 /// # }
337 /// # }
338 ///
339 /// let mut context = MyContext {
340 /// target: "/posts/1",
341 /// username: Some("the_admin"),
342 /// username_visited: false,
343 /// };
344 ///
345 /// // No access to the variable `username`.
346 /// let template = UriTemplateStr::new("{+target}{?username}")?;
347 /// let s = template.expand_dynamic_to_string::<UriSpec, _>(&mut context)?;
348 /// assert_eq!(s, "/posts/1?username=the_admin");
349 /// assert!(context.username_visited);
350 /// # }
351 /// # Ok::<_, Error>(())
352 /// ```
353 #[cfg(feature = "alloc")]
354 pub fn expand_dynamic_to_string<S: Spec, C: DynamicContext>(
355 &self,
356 context: &mut C,
357 ) -> Result<String, Error> {
358 let mut buf = String::new();
359 expand_whole_dynamic::<S, _, _>(self, &mut buf, context)?;
360 Ok(buf)
361 }
362
363 /// Returns an iterator of variables in the template.
364 ///
365 /// # Examples
366 ///
367 /// ```
368 /// # use iri_string::template::Error;
369 /// use iri_string::template::UriTemplateStr;
370 ///
371 /// let template = UriTemplateStr::new("foo{/bar*,baz:4}{?qux}{&bar*}")?;
372 /// let mut vars = template.variables();
373 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("bar"));
374 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("baz"));
375 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("qux"));
376 /// assert_eq!(vars.next().map(|var| var.as_str()), Some("bar"));
377 /// # Ok::<_, Error>(())
378 /// ```
379 #[inline]
380 #[must_use]
381 pub fn variables(&self) -> UriTemplateVariables<'_> {
382 UriTemplateVariables::new(self)
383 }
384}
385
386impl fmt::Debug for UriTemplateStr {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 f.debug_tuple("UriTemplateStr").field(&&self.inner).finish()
389 }
390}
391
392impl AsRef<str> for UriTemplateStr {
393 #[inline]
394 fn as_ref(&self) -> &str {
395 &self.inner
396 }
397}
398
399impl AsRef<UriTemplateStr> for UriTemplateStr {
400 #[inline]
401 fn as_ref(&self) -> &UriTemplateStr {
402 self
403 }
404}
405
406#[cfg(feature = "alloc")]
407impl<'a> From<&'a UriTemplateStr> for Cow<'a, UriTemplateStr> {
408 #[inline]
409 fn from(s: &'a UriTemplateStr) -> Self {
410 Cow::Borrowed(s)
411 }
412}
413
414#[cfg(feature = "alloc")]
415impl From<&UriTemplateStr> for Arc<UriTemplateStr> {
416 fn from(s: &UriTemplateStr) -> Self {
417 let inner: &str = s.as_str();
418 let buf = Arc::<str>::from(inner);
419 // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so
420 // the memory layouts of `Arc<str>` and `Arc<UriTemplateStr>` are
421 // compatible.
422 unsafe {
423 let raw: *const str = Arc::into_raw(buf);
424 Self::from_raw(raw as *const UriTemplateStr)
425 }
426 }
427}
428
429#[cfg(feature = "alloc")]
430impl From<&UriTemplateStr> for Box<UriTemplateStr> {
431 fn from(s: &UriTemplateStr) -> Self {
432 let inner: &str = s.as_str();
433 let buf = Box::<str>::from(inner);
434 // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so
435 // the memory layouts of `Box<str>` and `Box<UriTemplateStr>` are
436 // compatible.
437 unsafe {
438 let raw: *mut str = Box::into_raw(buf);
439 Self::from_raw(raw as *mut UriTemplateStr)
440 }
441 }
442}
443
444#[cfg(feature = "alloc")]
445impl From<&UriTemplateStr> for Rc<UriTemplateStr> {
446 fn from(s: &UriTemplateStr) -> Self {
447 let inner: &str = s.as_str();
448 let buf = Rc::<str>::from(inner);
449 // SAFETY: `UriTemplateStr` has `repr(transparent)` attribute, so
450 // the memory layouts of `Rc<str>` and `Rc<UriTemplateStr>` are
451 // compatible.
452 unsafe {
453 let raw: *const str = Rc::into_raw(buf);
454 Self::from_raw(raw as *const UriTemplateStr)
455 }
456 }
457}
458
459impl<'a> From<&'a UriTemplateStr> for &'a str {
460 #[inline]
461 fn from(s: &'a UriTemplateStr) -> &'a str {
462 s.as_ref()
463 }
464}
465
466impl<'a> TryFrom<&'a str> for &'a UriTemplateStr {
467 type Error = Error;
468
469 #[inline]
470 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
471 match validate_template_str(s) {
472 // SAFETY: just checked the string is valid.
473 Ok(()) => Ok(unsafe { UriTemplateStr::new_always_unchecked(s) }),
474 Err(e) => Err(e),
475 }
476 }
477}
478
479impl<'a> TryFrom<&'a [u8]> for &'a UriTemplateStr {
480 type Error = Error;
481
482 #[inline]
483 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
484 let s = core::str::from_utf8(bytes)
485 .map_err(|e| Error::new(ErrorKind::InvalidUtf8, e.valid_up_to()))?;
486 match validate_template_str(s) {
487 // SAFETY: just checked the string is valid.
488 Ok(()) => Ok(unsafe { UriTemplateStr::new_always_unchecked(s) }),
489 Err(e) => Err(e),
490 }
491 }
492}
493
494impl_cmp!(str, str, UriTemplateStr);
495impl_cmp!(str, &str, UriTemplateStr);
496impl_cmp!(str, str, &UriTemplateStr);
497
498impl fmt::Display for UriTemplateStr {
499 #[inline]
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 f.write_str(self.as_str())
502 }
503}
504
505/// Serde deserializer implementation.
506#[cfg(feature = "serde")]
507mod __serde_slice {
508 use super::UriTemplateStr;
509
510 use core::fmt;
511
512 use serde::{
513 de::{self, Visitor},
514 Deserialize, Deserializer,
515 };
516
517 /// Custom borrowed string visitor.
518 #[derive(Debug, Clone, Copy)]
519 struct CustomStrVisitor;
520
521 impl<'de> Visitor<'de> for CustomStrVisitor {
522 type Value = &'de UriTemplateStr;
523
524 #[inline]
525 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 f.write_str("URI template string")
527 }
528
529 #[inline]
530 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
531 where
532 E: de::Error,
533 {
534 <&'de UriTemplateStr as TryFrom<&'de str>>::try_from(v).map_err(E::custom)
535 }
536 }
537
538 // About `'de` and `'a`, see
539 // <https://serde.rs/lifetimes.html#the-deserializede-lifetime>.
540 impl<'a, 'de: 'a> Deserialize<'de> for &'a UriTemplateStr {
541 #[inline]
542 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
543 where
544 D: Deserializer<'de>,
545 {
546 deserializer.deserialize_string(CustomStrVisitor)
547 }
548 }
549}
550
551/// An iterator of variables in a URI template.
552#[derive(Debug, Clone)]
553pub struct UriTemplateVariables<'a> {
554 /// Chunks iterator.
555 chunks: Chunks<'a>,
556 /// Variables in the last chunk.
557 vars_in_chunk: Option<VarListIter<'a>>,
558}
559
560impl<'a> UriTemplateVariables<'a> {
561 /// Creates a variables iterator from the URI template.
562 #[inline]
563 #[must_use]
564 fn new(template: &'a UriTemplateStr) -> Self {
565 Self {
566 chunks: Chunks::new(template),
567 vars_in_chunk: None,
568 }
569 }
570}
571
572impl<'a> Iterator for UriTemplateVariables<'a> {
573 type Item = VarName<'a>;
574
575 fn next(&mut self) -> Option<Self::Item> {
576 loop {
577 if let Some(vars) = &mut self.vars_in_chunk {
578 match vars.next() {
579 Some((_len, spec)) => return Some(spec.name()),
580 None => self.vars_in_chunk = None,
581 }
582 }
583 let expr = self.chunks.find_map(|chunk| match chunk {
584 Chunk::Literal(_) => None,
585 Chunk::Expr(v) => Some(v),
586 });
587 self.vars_in_chunk = match expr {
588 Some(expr) => Some(expr.decompose().1.into_iter()),
589 None => return None,
590 }
591 }
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 use crate::spec::IriSpec;
600 use crate::template::context::{AssocVisitor, ListVisitor, Visitor};
601
602 struct TestContext;
603 impl Context for TestContext {
604 fn visit<V: Visitor>(&self, visitor: V) -> V::Result {
605 match visitor.var_name().as_str() {
606 "str" => visitor.visit_string("string"),
607 "list" => visitor
608 .visit_list()
609 .visit_items_and_finish(["item0", "item1", "item2"]),
610 "assoc" => visitor
611 .visit_assoc()
612 .visit_entries_and_finish([("key0", "value0"), ("key1", "value1")]),
613 _ => visitor.visit_undefined(),
614 }
615 }
616 }
617
618 #[test]
619 fn expand_error_pos() {
620 {
621 let e = UriTemplateStr::new("foo{list:4}")
622 .unwrap()
623 .expand::<IriSpec, _>(&TestContext)
624 .err()
625 .map(|e| e.location());
626 assert_eq!(e, Some("foo{".len()));
627 }
628
629 {
630 let e = UriTemplateStr::new("foo{/list*,list:4}")
631 .unwrap()
632 .expand::<IriSpec, _>(&TestContext)
633 .err()
634 .map(|e| e.location());
635 assert_eq!(e, Some("foo{/list*,".len()));
636 }
637
638 {
639 let e = UriTemplateStr::new("foo{/str:3,list*,assoc:4}")
640 .unwrap()
641 .expand::<IriSpec, _>(&TestContext)
642 .err()
643 .map(|e| e.location());
644 assert_eq!(e, Some("foo{/str:3,list*,".len()));
645 }
646 }
647}