1use std::collections::{BTreeMap, BTreeSet};
11use std::fmt;
12use std::str::FromStr;
13
14pub const MAX_URI_TEMPLATE_BYTES: usize = 16 * 1024;
16pub const MAX_URI_TEMPLATE_PARTS: usize = 256;
18pub const MAX_URI_TEMPLATE_EXPRESSIONS: usize = 128;
20pub const MAX_URI_TEMPLATE_VARIABLES_PER_EXPRESSION: usize = 64;
22pub const MAX_URI_TEMPLATE_VARIABLE_NAME_BYTES: usize = 512;
24pub const MAX_URI_TEMPLATE_PREFIX_LENGTH: usize = 9_999;
26pub const MAX_URI_TEMPLATE_VALUE_BYTES: usize = 16 * 1024;
28pub const MAX_URI_TEMPLATE_COMPOSITE_ITEMS: usize = 1_024;
30pub const MAX_URI_TEMPLATE_EXPANSION_OUTPUT_BYTES: usize = 64 * 1024;
32pub const MAX_URI_TEMPLATE_MATCH_INPUT_BYTES: usize = MAX_URI_TEMPLATE_EXPANSION_OUTPUT_BYTES;
37
38pub type TemplateValues = BTreeMap<String, TemplateValue>;
40
41#[derive(Clone, Debug, Eq, PartialEq)]
48pub enum TemplateValue {
49 Scalar(String),
51 List(Vec<Option<String>>),
53 Associative(Vec<(String, Option<String>)>),
55}
56
57impl TemplateValue {
58 #[must_use]
60 pub fn scalar(value: impl Into<String>) -> Self {
61 Self::Scalar(value.into())
62 }
63
64 #[must_use]
66 pub fn list(values: Vec<String>) -> Self {
67 Self::List(values.into_iter().map(Some).collect())
68 }
69
70 #[must_use]
72 pub fn list_with_undefined(values: Vec<Option<String>>) -> Self {
73 Self::List(values)
74 }
75
76 #[must_use]
78 pub fn associative(values: Vec<(String, String)>) -> Self {
79 Self::Associative(
80 values
81 .into_iter()
82 .map(|(key, value)| (key, Some(value)))
83 .collect(),
84 )
85 }
86
87 #[must_use]
89 pub fn associative_with_undefined(values: Vec<(String, Option<String>)>) -> Self {
90 Self::Associative(values)
91 }
92}
93
94impl From<String> for TemplateValue {
95 fn from(value: String) -> Self {
96 Self::Scalar(value)
97 }
98}
99
100impl From<&str> for TemplateValue {
101 fn from(value: &str) -> Self {
102 Self::Scalar(value.to_owned())
103 }
104}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub struct UriTemplateExpansionLimits {
112 output_bytes: usize,
113 composite_items: usize,
114 value_bytes: usize,
115}
116
117impl UriTemplateExpansionLimits {
118 pub fn new(
120 max_output_bytes: usize,
121 max_composite_items: usize,
122 max_value_bytes: usize,
123 ) -> Result<Self, UriTemplateError> {
124 if max_output_bytes == 0 || max_output_bytes > MAX_URI_TEMPLATE_EXPANSION_OUTPUT_BYTES {
125 return Err(UriTemplateError::InvalidExpansionLimit {
126 field: "max_output_bytes",
127 actual: max_output_bytes,
128 maximum: MAX_URI_TEMPLATE_EXPANSION_OUTPUT_BYTES,
129 });
130 }
131 if max_composite_items == 0 || max_composite_items > MAX_URI_TEMPLATE_COMPOSITE_ITEMS {
132 return Err(UriTemplateError::InvalidExpansionLimit {
133 field: "max_composite_items",
134 actual: max_composite_items,
135 maximum: MAX_URI_TEMPLATE_COMPOSITE_ITEMS,
136 });
137 }
138 if max_value_bytes == 0 || max_value_bytes > MAX_URI_TEMPLATE_VALUE_BYTES {
139 return Err(UriTemplateError::InvalidExpansionLimit {
140 field: "max_value_bytes",
141 actual: max_value_bytes,
142 maximum: MAX_URI_TEMPLATE_VALUE_BYTES,
143 });
144 }
145 Ok(Self {
146 output_bytes: max_output_bytes,
147 composite_items: max_composite_items,
148 value_bytes: max_value_bytes,
149 })
150 }
151
152 #[must_use]
154 pub const fn max_output_bytes(self) -> usize {
155 self.output_bytes
156 }
157
158 #[must_use]
160 pub const fn max_composite_items(self) -> usize {
161 self.composite_items
162 }
163
164 #[must_use]
166 pub const fn max_value_bytes(self) -> usize {
167 self.value_bytes
168 }
169}
170
171impl Default for UriTemplateExpansionLimits {
172 fn default() -> Self {
173 Self {
174 output_bytes: MAX_URI_TEMPLATE_EXPANSION_OUTPUT_BYTES,
175 composite_items: MAX_URI_TEMPLATE_COMPOSITE_ITEMS,
176 value_bytes: MAX_URI_TEMPLATE_VALUE_BYTES,
177 }
178 }
179}
180
181#[derive(Clone, Debug, Eq, PartialEq)]
183pub struct UriTemplate {
184 source: String,
185 parts: Vec<UriTemplatePart>,
186}
187
188impl UriTemplate {
189 pub fn parse(source: impl AsRef<str>) -> Result<Self, UriTemplateError> {
191 let source = source.as_ref();
192 if source.len() > MAX_URI_TEMPLATE_BYTES {
193 return Err(UriTemplateError::SourceTooLong {
194 actual: source.len(),
195 maximum: MAX_URI_TEMPLATE_BYTES,
196 });
197 }
198
199 let bytes = source.as_bytes();
200 let mut index = 0;
201 let mut literal_start = 0;
202 let mut expressions = 0;
203 let mut parts = Vec::new();
204
205 while index < bytes.len() {
206 match bytes[index] {
207 b'{' => {
208 push_literal(&mut parts, &source[literal_start..index], literal_start)?;
209 let expression_start = index + 1;
210 let Some(relative_end) = bytes[expression_start..]
211 .iter()
212 .position(|byte| *byte == b'}')
213 else {
214 return Err(UriTemplateError::UnclosedExpression { offset: index });
215 };
216 let expression_end = expression_start + relative_end;
217 if let Some(relative_nested) = bytes[expression_start..expression_end]
218 .iter()
219 .position(|byte| *byte == b'{')
220 {
221 return Err(UriTemplateError::NestedExpression {
222 offset: expression_start + relative_nested,
223 });
224 }
225 if expressions == MAX_URI_TEMPLATE_EXPRESSIONS {
226 return Err(UriTemplateError::TooManyExpressions {
227 maximum: MAX_URI_TEMPLATE_EXPRESSIONS,
228 });
229 }
230 push_part(
231 &mut parts,
232 UriTemplatePart::Expression(parse_expression(
233 &source[expression_start..expression_end],
234 expression_start,
235 )?),
236 )?;
237 expressions += 1;
238 index = expression_end + 1;
239 literal_start = index;
240 }
241 b'}' => return Err(UriTemplateError::UnexpectedCloseBrace { offset: index }),
242 _ => index += 1,
243 }
244 }
245 push_literal(&mut parts, &source[literal_start..], literal_start)?;
246
247 Ok(Self {
248 source: source.to_owned(),
249 parts,
250 })
251 }
252
253 #[must_use]
255 pub fn source(&self) -> &str {
256 &self.source
257 }
258
259 #[must_use]
261 pub fn parts(&self) -> &[UriTemplatePart] {
262 &self.parts
263 }
264
265 pub fn expand(&self, values: &TemplateValues) -> Result<String, UriTemplateError> {
267 self.expand_with_limits(values, UriTemplateExpansionLimits::default())
268 }
269
270 pub fn expand_with_limits(
272 &self,
273 values: &TemplateValues,
274 limits: UriTemplateExpansionLimits,
275 ) -> Result<String, UriTemplateError> {
276 let mut output = String::new();
277 let mut state = ExpansionState::default();
278 for part in &self.parts {
279 match part {
280 UriTemplatePart::Literal(literal) => {
281 append_encoded_literal(&mut output, literal, limits)?;
282 }
283 UriTemplatePart::Expression(expression) => {
284 expand_expression(&mut output, expression, values, limits, &mut state)?;
285 }
286 }
287 }
288 Ok(output)
289 }
290
291 pub fn compile_reversible(&self) -> Result<ReversibleResourceTemplate, UriTemplateError> {
297 ReversibleResourceTemplate::from_template(self)
298 }
299}
300
301impl AsRef<str> for UriTemplate {
302 fn as_ref(&self) -> &str {
303 self.source()
304 }
305}
306
307impl fmt::Display for UriTemplate {
308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
309 formatter.write_str(self.source())
310 }
311}
312
313impl FromStr for UriTemplate {
314 type Err = UriTemplateError;
315
316 fn from_str(source: &str) -> Result<Self, Self::Err> {
317 Self::parse(source)
318 }
319}
320
321#[derive(Clone, Debug, Eq, PartialEq)]
332pub struct ReversibleResourceTemplate {
333 template: UriTemplate,
334 parts: Vec<ReversibleTemplatePart>,
335}
336
337impl ReversibleResourceTemplate {
338 pub fn compile(template: UriTemplate) -> Result<Self, UriTemplateError> {
340 Self::from_template(&template)
341 }
342
343 pub fn from_template(template: &UriTemplate) -> Result<Self, UriTemplateError> {
345 let mut parts = Vec::with_capacity(template.parts.len());
346 let mut variable_names = BTreeSet::new();
347
348 for (index, part) in template.parts.iter().enumerate() {
349 match part {
350 UriTemplatePart::Literal(literal) => {
351 let mut encoded = String::new();
352 append_encoded_literal(
353 &mut encoded,
354 literal,
355 UriTemplateExpansionLimits::default(),
356 )?;
357 parts.push(ReversibleTemplatePart::Literal(encoded));
358 }
359 UriTemplatePart::Expression(expression) => {
360 if expression.variables().is_empty()
361 || (expression.variables().len() != 1
362 && !expression.operator().properties().named)
363 {
364 return Err(UriTemplateError::NonReversibleTemplate {
365 reason: UriTemplateMatchRejection::MultipleVariables,
366 });
367 }
368
369 let mut variables = Vec::with_capacity(expression.variables().len());
370 for variable in expression.variables() {
371 if matches!(variable.modifier(), Some(UriTemplateModifier::Prefix(_))) {
372 return Err(UriTemplateError::NonReversibleTemplate {
373 reason: UriTemplateMatchRejection::LossyPrefix {
374 variable: variable.name().to_owned(),
375 },
376 });
377 }
378 if !variable_names.insert(variable.name().to_owned()) {
379 return Err(UriTemplateError::NonReversibleTemplate {
380 reason: UriTemplateMatchRejection::DuplicateVariable {
381 variable: variable.name().to_owned(),
382 },
383 });
384 }
385 let mut encoded_name = String::new();
386 append_variable_name(
387 &mut encoded_name,
388 variable.name(),
389 UriTemplateExpansionLimits::default(),
390 )?;
391 variables.push(ReversibleTemplateVariable {
392 name: variable.name().to_owned(),
393 encoded_name,
394 });
395 }
396
397 let next_boundary = match template.parts.get(index + 1) {
398 Some(UriTemplatePart::Literal(literal)) => {
399 let mut encoded = String::new();
400 append_encoded_literal(
401 &mut encoded,
402 literal,
403 UriTemplateExpansionLimits::default(),
404 )?;
405 Some(ReversibleBoundary::Literal(encoded))
406 }
407 Some(UriTemplatePart::Expression(next)) => {
408 Some(reversible_adjacent_expression_boundary(expression, next)?)
409 }
410 None => None,
411 };
412
413 validate_reversible_expression(expression, next_boundary.as_ref())?;
414 parts.push(ReversibleTemplatePart::Expression(
415 ReversibleTemplateExpression {
416 operator: expression.operator(),
417 variables,
418 next_boundary,
419 },
420 ));
421 }
422 }
423 }
424
425 Ok(Self {
426 template: template.clone(),
427 parts,
428 })
429 }
430
431 #[must_use]
433 pub fn template(&self) -> &UriTemplate {
434 &self.template
435 }
436
437 pub fn expand(&self, values: &TemplateValues) -> Result<String, UriTemplateError> {
439 for part in &self.parts {
440 let ReversibleTemplatePart::Expression(expression) = part else {
441 continue;
442 };
443 for variable in &expression.variables {
444 let Some(value) = values.get(&variable.name) else {
445 continue;
446 };
447 let TemplateValue::Scalar(value) = value else {
448 return Err(UriTemplateError::NonScalarMatchValue {
449 variable: variable.name.clone(),
450 });
451 };
452 if value.is_empty()
453 && matches!(
454 expression.operator,
455 UriTemplateOperator::Simple | UriTemplateOperator::Reserved
456 )
457 {
458 return Err(UriTemplateError::AmbiguousEmptyScalar {
459 variable: variable.name.clone(),
460 });
461 }
462 if matches!(
463 expression.operator,
464 UriTemplateOperator::Reserved | UriTemplateOperator::Fragment
465 ) && contains_pct_encoded_triplet(value)
466 {
467 return Err(UriTemplateError::PreescapedReservedMatchValue {
468 variable: variable.name.clone(),
469 });
470 }
471 }
472 }
473 self.template.expand(values)
474 }
475
476 pub fn match_uri(&self, uri: &str) -> Result<Option<TemplateValues>, UriTemplateError> {
482 if uri.len() > MAX_URI_TEMPLATE_MATCH_INPUT_BYTES {
483 return Err(UriTemplateError::MatchInputTooLong {
484 actual: uri.len(),
485 maximum: MAX_URI_TEMPLATE_MATCH_INPUT_BYTES,
486 });
487 }
488
489 let mut offset = 0;
490 let mut values = TemplateValues::new();
491 for part in &self.parts {
492 match part {
493 ReversibleTemplatePart::Literal(literal) => {
494 let Some(remainder) = uri.get(offset..) else {
495 return Ok(None);
496 };
497 let Some(remainder) = remainder.strip_prefix(literal) else {
498 return Ok(None);
499 };
500 offset = uri.len() - remainder.len();
501 }
502 ReversibleTemplatePart::Expression(expression) => {
503 let Some(remainder) = uri.get(offset..) else {
504 return Ok(None);
505 };
506 let Some((captures, consumed)) =
507 reverse_match_expression(expression, remainder)
508 else {
509 return Ok(None);
510 };
511 offset = offset.saturating_add(consumed);
512 for (name, capture) in captures {
513 values.insert(name, TemplateValue::Scalar(capture));
514 }
515 }
516 }
517 }
518 if offset != uri.len() {
519 return Ok(None);
520 }
521 if self.expand(&values)? != uri {
522 return Ok(None);
523 }
524 Ok(Some(values))
525 }
526}
527
528impl TryFrom<UriTemplate> for ReversibleResourceTemplate {
529 type Error = UriTemplateError;
530
531 fn try_from(template: UriTemplate) -> Result<Self, Self::Error> {
532 Self::compile(template)
533 }
534}
535
536impl TryFrom<&UriTemplate> for ReversibleResourceTemplate {
537 type Error = UriTemplateError;
538
539 fn try_from(template: &UriTemplate) -> Result<Self, Self::Error> {
540 Self::from_template(template)
541 }
542}
543
544#[derive(Clone, Debug, Eq, PartialEq)]
545enum ReversibleTemplatePart {
546 Literal(String),
547 Expression(ReversibleTemplateExpression),
548}
549
550#[derive(Clone, Debug, Eq, PartialEq)]
551struct ReversibleTemplateExpression {
552 operator: UriTemplateOperator,
553 variables: Vec<ReversibleTemplateVariable>,
554 next_boundary: Option<ReversibleBoundary>,
555}
556
557#[derive(Clone, Debug, Eq, PartialEq)]
558struct ReversibleTemplateVariable {
559 name: String,
560 encoded_name: String,
561}
562
563#[derive(Clone, Debug, Eq, PartialEq)]
565enum ReversibleBoundary {
566 Literal(String),
568 ExpressionPrefix(String),
570}
571
572impl ReversibleBoundary {
573 fn as_str(&self) -> &str {
575 match self {
576 Self::Literal(value) | Self::ExpressionPrefix(value) => value,
577 }
578 }
579
580 const fn permits_absent_following_expression(&self) -> bool {
581 matches!(self, Self::ExpressionPrefix(_))
582 }
583}
584
585#[derive(Clone, Debug, Eq, PartialEq)]
587pub enum UriTemplatePart {
588 Literal(String),
590 Expression(UriTemplateExpression),
592}
593
594#[derive(Clone, Debug, Eq, PartialEq)]
596pub struct UriTemplateExpression {
597 operator: UriTemplateOperator,
598 variables: Vec<UriTemplateVariable>,
599}
600
601impl UriTemplateExpression {
602 #[must_use]
604 pub const fn operator(&self) -> UriTemplateOperator {
605 self.operator
606 }
607
608 #[must_use]
610 pub fn variables(&self) -> &[UriTemplateVariable] {
611 &self.variables
612 }
613}
614
615#[derive(Clone, Copy, Debug, Eq, PartialEq)]
617pub enum UriTemplateOperator {
618 Simple,
620 Reserved,
622 Fragment,
624 Label,
626 Path,
628 PathParameter,
630 Query,
632 QueryContinuation,
634}
635
636impl UriTemplateOperator {
637 #[must_use]
639 pub const fn character(self) -> Option<char> {
640 match self {
641 Self::Simple => None,
642 Self::Reserved => Some('+'),
643 Self::Fragment => Some('#'),
644 Self::Label => Some('.'),
645 Self::Path => Some('/'),
646 Self::PathParameter => Some(';'),
647 Self::Query => Some('?'),
648 Self::QueryContinuation => Some('&'),
649 }
650 }
651
652 const fn properties(self) -> OperatorProperties {
653 match self {
654 Self::Simple => OperatorProperties::new("", ",", false, "", false),
655 Self::Reserved => OperatorProperties::new("", ",", false, "", true),
656 Self::Fragment => OperatorProperties::new("#", ",", false, "", true),
657 Self::Label => OperatorProperties::new(".", ".", false, "", false),
658 Self::Path => OperatorProperties::new("/", "/", false, "", false),
659 Self::PathParameter => OperatorProperties::new(";", ";", true, "", false),
660 Self::Query => OperatorProperties::new("?", "&", true, "=", false),
661 Self::QueryContinuation => OperatorProperties::new("&", "&", true, "=", false),
662 }
663 }
664}
665
666#[derive(Clone, Debug, Eq, PartialEq)]
668pub struct UriTemplateVariable {
669 name: String,
670 modifier: Option<UriTemplateModifier>,
671}
672
673impl UriTemplateVariable {
674 #[must_use]
676 pub fn name(&self) -> &str {
677 &self.name
678 }
679
680 #[must_use]
682 pub const fn modifier(&self) -> Option<UriTemplateModifier> {
683 self.modifier
684 }
685}
686
687#[derive(Clone, Copy, Debug, Eq, PartialEq)]
689pub enum UriTemplateModifier {
690 Prefix(usize),
692 Explode,
694}
695
696#[derive(Clone, Debug, Eq, PartialEq)]
698pub enum UriTemplateMatchRejection {
699 MultipleVariables,
701 AdjacentCaptures,
703 DuplicateVariable {
705 variable: String,
707 },
708 LossyPrefix {
710 variable: String,
712 },
713 ExplodedComposite {
719 variable: String,
721 },
722 AmbiguousBoundary,
724 UnboundedReservedCapture,
726}
727
728#[derive(Clone, Debug, Eq, PartialEq)]
730pub enum UriTemplateError {
731 SourceTooLong {
733 actual: usize,
735 maximum: usize,
737 },
738 TooManyParts {
740 maximum: usize,
742 },
743 TooManyExpressions {
745 maximum: usize,
747 },
748 TooManyVariables {
750 maximum: usize,
752 },
753 VariableNameTooLong {
755 actual: usize,
757 maximum: usize,
759 },
760 InvalidLiteral {
762 offset: usize,
764 },
765 UnclosedExpression {
767 offset: usize,
769 },
770 NestedExpression {
772 offset: usize,
774 },
775 UnexpectedCloseBrace {
777 offset: usize,
779 },
780 EmptyExpression {
782 offset: usize,
784 },
785 UnsupportedOperator {
787 operator: char,
789 offset: usize,
791 },
792 InvalidVariable {
794 offset: usize,
796 },
797 InvalidPrefix {
799 offset: usize,
801 },
802 InvalidExpansionLimit {
804 field: &'static str,
806 actual: usize,
808 maximum: usize,
810 },
811 ValueTooLong {
813 actual: usize,
815 maximum: usize,
817 },
818 TooManyCompositeItems {
820 actual: usize,
822 maximum: usize,
824 },
825 ExpansionTooLarge {
827 actual: usize,
829 maximum: usize,
831 },
832 PrefixAppliedToComposite {
834 variable: String,
836 },
837 DuplicateAssociativeKey {
839 key: String,
841 },
842 NonReversibleTemplate {
844 reason: UriTemplateMatchRejection,
846 },
847 NonScalarMatchValue {
849 variable: String,
851 },
852 AmbiguousEmptyScalar {
854 variable: String,
856 },
857 MatchInputTooLong {
859 actual: usize,
861 maximum: usize,
863 },
864 PreescapedReservedMatchValue {
866 variable: String,
868 },
869}
870
871impl fmt::Display for UriTemplateError {
872 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
873 match self {
874 Self::SourceTooLong { actual, maximum } => {
875 write!(
876 formatter,
877 "URI template is {actual} bytes; maximum is {maximum}"
878 )
879 }
880 Self::TooManyParts { maximum } => {
881 write!(formatter, "URI template exceeds {maximum} parsed parts")
882 }
883 Self::TooManyExpressions { maximum } => {
884 write!(formatter, "URI template exceeds {maximum} expressions")
885 }
886 Self::TooManyVariables { maximum } => {
887 write!(
888 formatter,
889 "URI template expression exceeds {maximum} variables"
890 )
891 }
892 Self::VariableNameTooLong { actual, maximum } => {
893 write!(
894 formatter,
895 "URI template variable is {actual} bytes; maximum is {maximum}"
896 )
897 }
898 Self::InvalidLiteral { offset } => {
899 write!(formatter, "invalid URI template literal at byte {offset}")
900 }
901 Self::UnclosedExpression { offset } => {
902 write!(
903 formatter,
904 "unclosed URI template expression at byte {offset}"
905 )
906 }
907 Self::NestedExpression { offset } => {
908 write!(formatter, "nested URI template expression at byte {offset}")
909 }
910 Self::UnexpectedCloseBrace { offset } => {
911 write!(
912 formatter,
913 "unexpected URI template closing brace at byte {offset}"
914 )
915 }
916 Self::EmptyExpression { offset } => {
917 write!(formatter, "empty URI template expression at byte {offset}")
918 }
919 Self::UnsupportedOperator { operator, offset } => write!(
920 formatter,
921 "unsupported URI template operator {operator:?} at byte {offset}"
922 ),
923 Self::InvalidVariable { offset } => {
924 write!(formatter, "invalid URI template variable at byte {offset}")
925 }
926 Self::InvalidPrefix { offset } => {
927 write!(
928 formatter,
929 "invalid URI template prefix modifier at byte {offset}"
930 )
931 }
932 Self::InvalidExpansionLimit {
933 field,
934 actual,
935 maximum,
936 } => write!(formatter, "{field} is {actual}; maximum is {maximum}"),
937 Self::ValueTooLong { actual, maximum } => {
938 write!(
939 formatter,
940 "URI template value is {actual} bytes; maximum is {maximum}"
941 )
942 }
943 Self::TooManyCompositeItems { actual, maximum } => write!(
944 formatter,
945 "URI template expansion has {actual} composite members; maximum is {maximum}"
946 ),
947 Self::ExpansionTooLarge { actual, maximum } => {
948 write!(
949 formatter,
950 "URI template expansion is {actual} bytes; maximum is {maximum}"
951 )
952 }
953 Self::PrefixAppliedToComposite { variable } => write!(
954 formatter,
955 "URI template prefix modifier cannot be applied to composite variable {variable:?}"
956 ),
957 Self::DuplicateAssociativeKey { key } => {
958 write!(
959 formatter,
960 "URI template associative key {key:?} is duplicated"
961 )
962 }
963 Self::NonReversibleTemplate { reason } => {
964 write!(
965 formatter,
966 "URI template cannot be reverse matched: {reason}"
967 )
968 }
969 Self::NonScalarMatchValue { variable } => write!(
970 formatter,
971 "reversible URI template variable {variable:?} requires a scalar value"
972 ),
973 Self::AmbiguousEmptyScalar { variable } => write!(
974 formatter,
975 "reversible URI template variable {variable:?} cannot use an empty scalar"
976 ),
977 Self::MatchInputTooLong { actual, maximum } => write!(
978 formatter,
979 "URI template match input is {actual} bytes; maximum is {maximum}"
980 ),
981 Self::PreescapedReservedMatchValue { variable } => write!(
982 formatter,
983 "reversible URI template variable {variable:?} cannot contain a pre-escaped percent triplet"
984 ),
985 }
986 }
987}
988
989impl std::error::Error for UriTemplateError {}
990
991impl fmt::Display for UriTemplateMatchRejection {
992 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
993 match self {
994 Self::MultipleVariables => {
995 formatter.write_str("an expression binds multiple variables")
996 }
997 Self::AdjacentCaptures => formatter.write_str("adjacent expressions have no boundary"),
998 Self::DuplicateVariable { variable } => {
999 write!(formatter, "variable {variable:?} is bound more than once")
1000 }
1001 Self::LossyPrefix { variable } => {
1002 write!(
1003 formatter,
1004 "variable {variable:?} uses a lossy prefix modifier"
1005 )
1006 }
1007 Self::ExplodedComposite { variable } => {
1008 write!(
1009 formatter,
1010 "variable {variable:?} uses an exploded composite modifier"
1011 )
1012 }
1013 Self::AmbiguousBoundary => {
1014 formatter.write_str("a capture overlaps its following literal boundary")
1015 }
1016 Self::UnboundedReservedCapture => {
1017 formatter.write_str("a reserved capture must be terminal")
1018 }
1019 }
1020 }
1021}
1022
1023#[derive(Clone, Copy)]
1024struct OperatorProperties {
1025 first: &'static str,
1026 separator: &'static str,
1027 named: bool,
1028 if_empty: &'static str,
1029 allow_reserved: bool,
1030}
1031
1032impl OperatorProperties {
1033 const fn new(
1034 first: &'static str,
1035 separator: &'static str,
1036 named: bool,
1037 if_empty: &'static str,
1038 allow_reserved: bool,
1039 ) -> Self {
1040 Self {
1041 first,
1042 separator,
1043 named,
1044 if_empty,
1045 allow_reserved,
1046 }
1047 }
1048
1049 fn is_form_style(self) -> bool {
1050 matches!(self.first, "?" | "&")
1051 }
1052}
1053
1054#[derive(Default)]
1055struct ExpansionState {
1056 composite_items: usize,
1057}
1058
1059impl ExpansionState {
1060 fn inspect_composite(
1061 &mut self,
1062 count: usize,
1063 limits: UriTemplateExpansionLimits,
1064 ) -> Result<(), UriTemplateError> {
1065 let actual = self.composite_items.saturating_add(count);
1066 if actual > limits.max_composite_items() {
1067 return Err(UriTemplateError::TooManyCompositeItems {
1068 actual,
1069 maximum: limits.max_composite_items(),
1070 });
1071 }
1072 self.composite_items = actual;
1073 Ok(())
1074 }
1075}
1076
1077struct ExpressionWriter<'a> {
1078 output: &'a mut String,
1079 properties: OperatorProperties,
1080 wrote_value: bool,
1081 limits: UriTemplateExpansionLimits,
1082}
1083
1084impl<'a> ExpressionWriter<'a> {
1085 fn new(
1086 output: &'a mut String,
1087 properties: OperatorProperties,
1088 limits: UriTemplateExpansionLimits,
1089 ) -> Self {
1090 Self {
1091 output,
1092 properties,
1093 wrote_value: false,
1094 limits,
1095 }
1096 }
1097
1098 fn begin_value(&mut self) -> Result<(), UriTemplateError> {
1099 let delimiter = if self.wrote_value {
1100 self.properties.separator
1101 } else {
1102 self.properties.first
1103 };
1104 append_output(self.output, delimiter, self.limits)?;
1105 self.wrote_value = true;
1106 Ok(())
1107 }
1108
1109 fn write_value(
1110 &mut self,
1111 name: &str,
1112 value: &str,
1113 logically_empty: bool,
1114 ) -> Result<(), UriTemplateError> {
1115 self.begin_value()?;
1116 if self.properties.named {
1117 append_variable_name(self.output, name, self.limits)?;
1118 append_output(
1119 self.output,
1120 if logically_empty {
1121 self.properties.if_empty
1122 } else {
1123 "="
1124 },
1125 self.limits,
1126 )?;
1127 }
1128 append_encoded_value(
1129 self.output,
1130 value,
1131 self.properties.allow_reserved,
1132 self.limits,
1133 )
1134 }
1135
1136 fn write_associative_pair(&mut self, key: &str, value: &str) -> Result<(), UriTemplateError> {
1137 self.begin_value()?;
1138 if self.properties.named {
1139 append_encoded_value(
1140 self.output,
1141 key,
1142 self.properties.allow_reserved,
1143 self.limits,
1144 )?;
1145 append_output(
1146 self.output,
1147 if value.is_empty() {
1148 self.properties.if_empty
1149 } else {
1150 "="
1151 },
1152 self.limits,
1153 )?;
1154 return append_encoded_value(
1155 self.output,
1156 value,
1157 self.properties.allow_reserved,
1158 self.limits,
1159 );
1160 }
1161
1162 append_encoded_value(
1163 self.output,
1164 key,
1165 self.properties.allow_reserved,
1166 self.limits,
1167 )?;
1168 if !value.is_empty() || self.properties.is_form_style() {
1169 append_output(self.output, "=", self.limits)?;
1170 }
1171 append_encoded_value(
1172 self.output,
1173 value,
1174 self.properties.allow_reserved,
1175 self.limits,
1176 )
1177 }
1178}
1179
1180fn push_part(
1181 parts: &mut Vec<UriTemplatePart>,
1182 part: UriTemplatePart,
1183) -> Result<(), UriTemplateError> {
1184 if parts.len() == MAX_URI_TEMPLATE_PARTS {
1185 return Err(UriTemplateError::TooManyParts {
1186 maximum: MAX_URI_TEMPLATE_PARTS,
1187 });
1188 }
1189 parts.push(part);
1190 Ok(())
1191}
1192
1193fn push_literal(
1194 parts: &mut Vec<UriTemplatePart>,
1195 literal: &str,
1196 offset: usize,
1197) -> Result<(), UriTemplateError> {
1198 if literal.is_empty() {
1199 return Ok(());
1200 }
1201 if let Some(relative_offset) = invalid_literal_offset(literal) {
1202 return Err(UriTemplateError::InvalidLiteral {
1203 offset: offset + relative_offset,
1204 });
1205 }
1206 push_part(parts, UriTemplatePart::Literal(literal.to_owned()))
1207}
1208
1209fn parse_expression(
1210 source: &str,
1211 source_offset: usize,
1212) -> Result<UriTemplateExpression, UriTemplateError> {
1213 let bytes = source.as_bytes();
1214 let Some(&first) = bytes.first() else {
1215 return Err(UriTemplateError::EmptyExpression {
1216 offset: source_offset,
1217 });
1218 };
1219 let (operator, variable_start) = match first {
1220 b'+' => (UriTemplateOperator::Reserved, 1),
1221 b'#' => (UriTemplateOperator::Fragment, 1),
1222 b'.' => (UriTemplateOperator::Label, 1),
1223 b'/' => (UriTemplateOperator::Path, 1),
1224 b';' => (UriTemplateOperator::PathParameter, 1),
1225 b'?' => (UriTemplateOperator::Query, 1),
1226 b'&' => (UriTemplateOperator::QueryContinuation, 1),
1227 b'=' | b',' | b'!' | b'@' | b'|' => {
1228 return Err(UriTemplateError::UnsupportedOperator {
1229 operator: char::from(first),
1230 offset: source_offset,
1231 });
1232 }
1233 _ => (UriTemplateOperator::Simple, 0),
1234 };
1235 let variables_source = &source[variable_start..];
1236 if variables_source.is_empty() {
1237 return Err(UriTemplateError::EmptyExpression {
1238 offset: source_offset + variable_start,
1239 });
1240 }
1241
1242 let mut variables = Vec::new();
1243 let mut variable_offset = source_offset + variable_start;
1244 for variable_source in variables_source.split(',') {
1245 if variables.len() == MAX_URI_TEMPLATE_VARIABLES_PER_EXPRESSION {
1246 return Err(UriTemplateError::TooManyVariables {
1247 maximum: MAX_URI_TEMPLATE_VARIABLES_PER_EXPRESSION,
1248 });
1249 }
1250 variables.push(parse_variable(variable_source, variable_offset)?);
1251 variable_offset += variable_source.len() + 1;
1252 }
1253 Ok(UriTemplateExpression {
1254 operator,
1255 variables,
1256 })
1257}
1258
1259fn parse_variable(
1260 source: &str,
1261 source_offset: usize,
1262) -> Result<UriTemplateVariable, UriTemplateError> {
1263 if source.is_empty() {
1264 return Err(UriTemplateError::InvalidVariable {
1265 offset: source_offset,
1266 });
1267 }
1268
1269 let (name, modifier) = if let Some(name) = source.strip_suffix('*') {
1270 if name.is_empty() || name.contains(':') {
1271 return Err(UriTemplateError::InvalidVariable {
1272 offset: source_offset,
1273 });
1274 }
1275 (name, Some(UriTemplateModifier::Explode))
1276 } else if let Some((name, prefix)) = source.split_once(':') {
1277 let prefix_offset = source_offset + name.len();
1278 if !is_valid_prefix(prefix) {
1279 return Err(UriTemplateError::InvalidPrefix {
1280 offset: prefix_offset,
1281 });
1282 }
1283 let length = prefix
1284 .parse::<usize>()
1285 .map_err(|_| UriTemplateError::InvalidPrefix {
1286 offset: prefix_offset,
1287 })?;
1288 (name, Some(UriTemplateModifier::Prefix(length)))
1289 } else {
1290 (source, None)
1291 };
1292
1293 if name.len() > MAX_URI_TEMPLATE_VARIABLE_NAME_BYTES {
1294 return Err(UriTemplateError::VariableNameTooLong {
1295 actual: name.len(),
1296 maximum: MAX_URI_TEMPLATE_VARIABLE_NAME_BYTES,
1297 });
1298 }
1299 if !is_valid_variable_name(name) {
1300 return Err(UriTemplateError::InvalidVariable {
1301 offset: source_offset,
1302 });
1303 }
1304
1305 Ok(UriTemplateVariable {
1306 name: name.to_owned(),
1307 modifier,
1308 })
1309}
1310
1311fn is_valid_prefix(prefix: &str) -> bool {
1312 let bytes = prefix.as_bytes();
1313 bytes.len() <= 4
1314 && bytes
1315 .first()
1316 .is_some_and(|byte| matches!(*byte, b'1'..=b'9'))
1317 && bytes.iter().all(u8::is_ascii_digit)
1318 && prefix
1319 .parse::<usize>()
1320 .is_ok_and(|length| length <= MAX_URI_TEMPLATE_PREFIX_LENGTH)
1321}
1322
1323fn is_valid_variable_name(name: &str) -> bool {
1324 !name.is_empty() && name.split('.').all(is_valid_variable_name_segment)
1325}
1326
1327fn is_valid_variable_name_segment(segment: &str) -> bool {
1328 if segment.is_empty() {
1329 return false;
1330 }
1331 let bytes = segment.as_bytes();
1332 let mut index = 0;
1333 while index < bytes.len() {
1334 if bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_' {
1335 index += 1;
1336 } else if bytes[index] == b'%'
1337 && index + 2 < bytes.len()
1338 && bytes[index + 1].is_ascii_hexdigit()
1339 && bytes[index + 2].is_ascii_hexdigit()
1340 {
1341 index += 3;
1342 } else {
1343 return false;
1344 }
1345 }
1346 true
1347}
1348
1349fn invalid_literal_offset(literal: &str) -> Option<usize> {
1350 let bytes = literal.as_bytes();
1351 let mut index = 0;
1352 while index < bytes.len() {
1353 let byte = bytes[index];
1354 if byte.is_ascii() {
1355 if byte == b'%' {
1356 if index + 2 >= bytes.len()
1357 || !bytes[index + 1].is_ascii_hexdigit()
1358 || !bytes[index + 2].is_ascii_hexdigit()
1359 {
1360 return Some(index);
1361 }
1362 index += 3;
1363 } else if is_valid_literal_byte(byte) {
1364 index += 1;
1365 } else {
1366 return Some(index);
1367 }
1368 } else {
1369 let Some(character) = literal[index..].chars().next() else {
1370 return Some(index);
1371 };
1372 if !is_valid_literal_character(character) {
1373 return Some(index);
1374 }
1375 index += character.len_utf8();
1376 }
1377 }
1378 None
1379}
1380
1381fn is_valid_literal_character(character: char) -> bool {
1382 matches!(
1383 u32::from(character),
1384 0x00A0..=0xD7FF
1385 | 0xE000..=0xF8FF
1386 | 0xF900..=0xFDCF
1387 | 0xFDF0..=0xFFEF
1388 | 0x10000..=0x1FFFD
1389 | 0x20000..=0x2FFFD
1390 | 0x30000..=0x3FFFD
1391 | 0x40000..=0x4FFFD
1392 | 0x50000..=0x5FFFD
1393 | 0x60000..=0x6FFFD
1394 | 0x70000..=0x7FFFD
1395 | 0x80000..=0x8FFFD
1396 | 0x90000..=0x9FFFD
1397 | 0xA0000..=0xAFFFD
1398 | 0xB0000..=0xBFFFD
1399 | 0xC0000..=0xCFFFD
1400 | 0xD0000..=0xDFFFD
1401 | 0xE1000..=0xEFFFD
1402 | 0xF0000..=0xFFFFD
1403 | 0x100000..=0x10FFFD
1404 )
1405}
1406
1407fn is_valid_literal_byte(byte: u8) -> bool {
1408 matches!(
1409 byte,
1410 b'!'
1411 | b'#'..=b'$'
1412 | b'&'
1413 | b'('..=b';'
1414 | b'='
1415 | b'?'..=b'['
1416 | b']'
1417 | b'_'
1418 | b'a'..=b'z'
1419 | b'~'
1420 )
1421}
1422
1423fn expand_expression(
1424 output: &mut String,
1425 expression: &UriTemplateExpression,
1426 values: &TemplateValues,
1427 limits: UriTemplateExpansionLimits,
1428 state: &mut ExpansionState,
1429) -> Result<(), UriTemplateError> {
1430 let properties = expression.operator.properties();
1431 let mut writer = ExpressionWriter::new(output, properties, limits);
1432 for variable in &expression.variables {
1433 let Some(value) = values.get(variable.name()) else {
1434 continue;
1435 };
1436 expand_variable(&mut writer, variable, value, state)?;
1437 }
1438 Ok(())
1439}
1440
1441fn expand_variable(
1442 writer: &mut ExpressionWriter<'_>,
1443 variable: &UriTemplateVariable,
1444 value: &TemplateValue,
1445 state: &mut ExpansionState,
1446) -> Result<(), UriTemplateError> {
1447 match value {
1448 TemplateValue::Scalar(value) => {
1449 let value = scalar_prefix(value, variable, writer.limits)?;
1450 writer.write_value(variable.name(), value, value.is_empty())
1451 }
1452 TemplateValue::List(values) => {
1453 if values.is_empty() {
1454 return Ok(());
1455 }
1456 state.inspect_composite(values.len(), writer.limits)?;
1457 for value in values.iter().flatten() {
1458 check_value_bound(value, writer.limits)?;
1459 }
1460 if !values.iter().any(Option::is_some) {
1461 return Ok(());
1462 }
1463 reject_prefix_on_composite(variable)?;
1464 if variable.modifier() == Some(UriTemplateModifier::Explode) {
1465 for value in values.iter().flatten() {
1466 writer.write_value(variable.name(), value, value.is_empty())?;
1467 }
1468 return Ok(());
1469 }
1470
1471 writer.begin_value()?;
1472 if writer.properties.named {
1473 append_variable_name(writer.output, variable.name(), writer.limits)?;
1474 append_output(writer.output, "=", writer.limits)?;
1475 }
1476 let mut first = true;
1477 for value in values.iter().flatten() {
1478 if !first {
1479 append_output(writer.output, ",", writer.limits)?;
1480 }
1481 append_encoded_value(
1482 writer.output,
1483 value,
1484 writer.properties.allow_reserved,
1485 writer.limits,
1486 )?;
1487 first = false;
1488 }
1489 Ok(())
1490 }
1491 TemplateValue::Associative(values) => {
1492 if values.is_empty() {
1493 return Ok(());
1494 }
1495 state.inspect_composite(values.len(), writer.limits)?;
1496 let mut unique_keys = BTreeSet::new();
1497 for (key, value) in values {
1498 if !unique_keys.insert(key.as_str()) {
1499 return Err(UriTemplateError::DuplicateAssociativeKey { key: key.clone() });
1500 }
1501 check_value_bound(key, writer.limits)?;
1502 if let Some(value) = value {
1503 check_value_bound(value, writer.limits)?;
1504 }
1505 }
1506 if !values.iter().any(|(_, value)| value.is_some()) {
1507 return Ok(());
1508 }
1509 reject_prefix_on_composite(variable)?;
1510 if variable.modifier() == Some(UriTemplateModifier::Explode) {
1511 for (key, value) in values {
1512 let Some(value) = value else {
1513 continue;
1514 };
1515 writer.write_associative_pair(key, value)?;
1516 }
1517 return Ok(());
1518 }
1519
1520 writer.begin_value()?;
1521 if writer.properties.named {
1522 append_variable_name(writer.output, variable.name(), writer.limits)?;
1523 append_output(writer.output, "=", writer.limits)?;
1524 }
1525 let mut first = true;
1526 for (key, value) in values {
1527 let Some(value) = value else {
1528 continue;
1529 };
1530 if !first {
1531 append_output(writer.output, ",", writer.limits)?;
1532 }
1533 append_encoded_value(
1534 writer.output,
1535 key,
1536 writer.properties.allow_reserved,
1537 writer.limits,
1538 )?;
1539 append_output(writer.output, ",", writer.limits)?;
1540 append_encoded_value(
1541 writer.output,
1542 value,
1543 writer.properties.allow_reserved,
1544 writer.limits,
1545 )?;
1546 first = false;
1547 }
1548 Ok(())
1549 }
1550 }
1551}
1552
1553fn scalar_prefix<'a>(
1554 value: &'a str,
1555 variable: &UriTemplateVariable,
1556 limits: UriTemplateExpansionLimits,
1557) -> Result<&'a str, UriTemplateError> {
1558 check_value_bound(value, limits)?;
1559 let value = match variable.modifier() {
1560 Some(UriTemplateModifier::Prefix(length)) => &value[..prefix_end(value, length)],
1561 Some(UriTemplateModifier::Explode) | None => value,
1562 };
1563 Ok(value)
1564}
1565
1566fn reject_prefix_on_composite(variable: &UriTemplateVariable) -> Result<(), UriTemplateError> {
1567 if matches!(variable.modifier(), Some(UriTemplateModifier::Prefix(_))) {
1568 return Err(UriTemplateError::PrefixAppliedToComposite {
1569 variable: variable.name().to_owned(),
1570 });
1571 }
1572 Ok(())
1573}
1574
1575fn prefix_end(value: &str, maximum_characters: usize) -> usize {
1576 let bytes = value.as_bytes();
1577 let mut index = 0;
1578 let mut characters = 0;
1579 while index < bytes.len() && characters < maximum_characters {
1580 if bytes[index] == b'%'
1581 && index + 2 < bytes.len()
1582 && bytes[index + 1].is_ascii_hexdigit()
1583 && bytes[index + 2].is_ascii_hexdigit()
1584 {
1585 index += pct_encoded_character_width(&bytes[index..]);
1586 } else {
1587 let Some(character) = value[index..].chars().next() else {
1588 break;
1589 };
1590 index += character.len_utf8();
1591 }
1592 characters += 1;
1593 }
1594 index
1595}
1596
1597fn pct_encoded_character_width(source: &[u8]) -> usize {
1598 let first = decode_pct_encoded_octet(source);
1599 let utf8_octets = first.map_or(1, utf8_sequence_width);
1600 if !(2..=4).contains(&utf8_octets) || source.len() < utf8_octets * 3 {
1601 return 3;
1602 }
1603
1604 let mut decoded = [0_u8; 4];
1605 for (index, slot) in decoded.iter_mut().take(utf8_octets).enumerate() {
1606 let offset = index * 3;
1607 let Some(octet) = decode_pct_encoded_octet(&source[offset..]) else {
1608 return 3;
1609 };
1610 *slot = octet;
1611 }
1612 if std::str::from_utf8(&decoded[..utf8_octets])
1613 .is_ok_and(|decoded| decoded.chars().count() == 1)
1614 {
1615 utf8_octets * 3
1616 } else {
1617 3
1618 }
1619}
1620
1621fn decode_pct_encoded_octet(source: &[u8]) -> Option<u8> {
1622 if source.len() < 3 || source[0] != b'%' {
1623 return None;
1624 }
1625 Some((hex_value(source[1])? << 4) | hex_value(source[2])?)
1626}
1627
1628const fn hex_value(byte: u8) -> Option<u8> {
1629 match byte {
1630 b'0'..=b'9' => Some(byte - b'0'),
1631 b'A'..=b'F' => Some(byte - b'A' + 10),
1632 b'a'..=b'f' => Some(byte - b'a' + 10),
1633 _ => None,
1634 }
1635}
1636
1637const fn utf8_sequence_width(first: u8) -> usize {
1638 match first {
1639 0xC2..=0xDF => 2,
1640 0xE0..=0xEF => 3,
1641 0xF0..=0xF4 => 4,
1642 _ => 1,
1643 }
1644}
1645
1646fn check_value_bound(
1647 value: &str,
1648 limits: UriTemplateExpansionLimits,
1649) -> Result<(), UriTemplateError> {
1650 if value.len() > limits.max_value_bytes() {
1651 return Err(UriTemplateError::ValueTooLong {
1652 actual: value.len(),
1653 maximum: limits.max_value_bytes(),
1654 });
1655 }
1656 Ok(())
1657}
1658
1659fn append_encoded_literal(
1660 output: &mut String,
1661 literal: &str,
1662 limits: UriTemplateExpansionLimits,
1663) -> Result<(), UriTemplateError> {
1664 append_percent_encoded(output, literal, true, true, limits)
1665}
1666
1667fn append_variable_name(
1668 output: &mut String,
1669 name: &str,
1670 limits: UriTemplateExpansionLimits,
1671) -> Result<(), UriTemplateError> {
1672 append_percent_encoded(output, name, false, true, limits)
1673}
1674
1675fn append_encoded_value(
1676 output: &mut String,
1677 value: &str,
1678 allow_reserved: bool,
1679 limits: UriTemplateExpansionLimits,
1680) -> Result<(), UriTemplateError> {
1681 append_percent_encoded(output, value, allow_reserved, allow_reserved, limits)
1682}
1683
1684fn append_percent_encoded(
1685 output: &mut String,
1686 source: &str,
1687 allow_reserved: bool,
1688 preserve_pct_encoded: bool,
1689 limits: UriTemplateExpansionLimits,
1690) -> Result<(), UriTemplateError> {
1691 let bytes = source.as_bytes();
1692 let mut index = 0;
1693 while index < bytes.len() {
1694 let byte = bytes[index];
1695 if preserve_pct_encoded
1696 && byte == b'%'
1697 && index + 2 < bytes.len()
1698 && bytes[index + 1].is_ascii_hexdigit()
1699 && bytes[index + 2].is_ascii_hexdigit()
1700 {
1701 append_output(output, &source[index..index + 3], limits)?;
1702 index += 3;
1703 } else if is_unreserved(byte) || (allow_reserved && is_reserved(byte)) {
1704 append_byte(output, byte, limits)?;
1705 index += 1;
1706 } else {
1707 append_percent_triplet(output, byte, limits)?;
1708 index += 1;
1709 }
1710 }
1711 Ok(())
1712}
1713
1714fn append_output(
1715 output: &mut String,
1716 fragment: &str,
1717 limits: UriTemplateExpansionLimits,
1718) -> Result<(), UriTemplateError> {
1719 let actual = output.len().saturating_add(fragment.len());
1720 if actual > limits.max_output_bytes() {
1721 return Err(UriTemplateError::ExpansionTooLarge {
1722 actual,
1723 maximum: limits.max_output_bytes(),
1724 });
1725 }
1726 output.push_str(fragment);
1727 Ok(())
1728}
1729
1730fn append_byte(
1731 output: &mut String,
1732 byte: u8,
1733 limits: UriTemplateExpansionLimits,
1734) -> Result<(), UriTemplateError> {
1735 let actual = output.len().saturating_add(1);
1736 if actual > limits.max_output_bytes() {
1737 return Err(UriTemplateError::ExpansionTooLarge {
1738 actual,
1739 maximum: limits.max_output_bytes(),
1740 });
1741 }
1742 output.push(char::from(byte));
1743 Ok(())
1744}
1745
1746fn append_percent_triplet(
1747 output: &mut String,
1748 byte: u8,
1749 limits: UriTemplateExpansionLimits,
1750) -> Result<(), UriTemplateError> {
1751 const HEX: &[u8; 16] = b"0123456789ABCDEF";
1752 let actual = output.len().saturating_add(3);
1753 if actual > limits.max_output_bytes() {
1754 return Err(UriTemplateError::ExpansionTooLarge {
1755 actual,
1756 maximum: limits.max_output_bytes(),
1757 });
1758 }
1759 output.push('%');
1760 output.push(char::from(HEX[usize::from(byte >> 4)]));
1761 output.push(char::from(HEX[usize::from(byte & 0x0f)]));
1762 Ok(())
1763}
1764
1765const fn is_unreserved(byte: u8) -> bool {
1766 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~')
1767}
1768
1769const fn is_reserved(byte: u8) -> bool {
1770 matches!(
1771 byte,
1772 b':' | b'/'
1773 | b'?'
1774 | b'#'
1775 | b'['
1776 | b']'
1777 | b'@'
1778 | b'!'
1779 | b'$'
1780 | b'&'
1781 | b'\''
1782 | b'('
1783 | b')'
1784 | b'*'
1785 | b'+'
1786 | b','
1787 | b';'
1788 | b'='
1789 )
1790}
1791
1792fn validate_reversible_expression(
1793 expression: &UriTemplateExpression,
1794 next_boundary: Option<&ReversibleBoundary>,
1795) -> Result<(), UriTemplateError> {
1796 match expression.operator() {
1797 UriTemplateOperator::Reserved | UriTemplateOperator::Fragment
1798 if next_boundary.is_some() =>
1799 {
1800 Err(UriTemplateError::NonReversibleTemplate {
1801 reason: UriTemplateMatchRejection::UnboundedReservedCapture,
1802 })
1803 }
1804 UriTemplateOperator::Simple
1805 | UriTemplateOperator::Label
1806 | UriTemplateOperator::Path
1807 | UriTemplateOperator::PathParameter
1808 | UriTemplateOperator::Query
1809 | UriTemplateOperator::QueryContinuation => {
1810 if next_boundary.is_some_and(|boundary| {
1811 !boundary
1812 .as_str()
1813 .as_bytes()
1814 .first()
1815 .is_some_and(|byte| is_reserved(*byte))
1816 }) {
1817 return Err(UriTemplateError::NonReversibleTemplate {
1818 reason: UriTemplateMatchRejection::AmbiguousBoundary,
1819 });
1820 }
1821 Ok(())
1822 }
1823 UriTemplateOperator::Reserved | UriTemplateOperator::Fragment => Ok(()),
1824 }
1825}
1826
1827fn reversible_adjacent_expression_boundary(
1828 expression: &UriTemplateExpression,
1829 next: &UriTemplateExpression,
1830) -> Result<ReversibleBoundary, UriTemplateError> {
1831 let prefix = match next.operator() {
1832 UriTemplateOperator::Simple | UriTemplateOperator::Reserved => {
1835 return Err(UriTemplateError::NonReversibleTemplate {
1836 reason: UriTemplateMatchRejection::AdjacentCaptures,
1837 });
1838 }
1839 UriTemplateOperator::Fragment => "#".to_owned(),
1840 UriTemplateOperator::Label => ".".to_owned(),
1841 UriTemplateOperator::Path => {
1842 if expression.operator() == UriTemplateOperator::Path {
1845 return Err(UriTemplateError::NonReversibleTemplate {
1846 reason: UriTemplateMatchRejection::AmbiguousBoundary,
1847 });
1848 }
1849 "/".to_owned()
1850 }
1851 UriTemplateOperator::PathParameter => ";".to_owned(),
1852 UriTemplateOperator::Query => "?".to_owned(),
1853 UriTemplateOperator::QueryContinuation => "&".to_owned(),
1854 };
1855
1856 Ok(ReversibleBoundary::ExpressionPrefix(prefix))
1857}
1858
1859fn reverse_match_expression(
1860 expression: &ReversibleTemplateExpression,
1861 remainder: &str,
1862) -> Option<(Vec<(String, String)>, usize)> {
1863 if expression.variables.len() > 1 {
1864 return reverse_match_named_expression(expression, remainder);
1865 }
1866 let variable = expression.variables.first()?;
1867 let (capture, consumed) = match expression.operator {
1868 UriTemplateOperator::Simple => {
1869 reverse_match_unprefixed(remainder, expression.next_boundary.as_ref())
1870 }
1871 UriTemplateOperator::Reserved => reverse_match_unprefixed(remainder, None),
1872 UriTemplateOperator::Fragment => {
1873 reverse_match_prefixed(remainder, "#", expression.next_boundary.as_ref())
1874 }
1875 UriTemplateOperator::Label => {
1876 reverse_match_prefixed(remainder, ".", expression.next_boundary.as_ref())
1877 }
1878 UriTemplateOperator::Path => {
1879 reverse_match_prefixed(remainder, "/", expression.next_boundary.as_ref())
1880 }
1881 UriTemplateOperator::PathParameter => reverse_match_named(
1882 remainder,
1883 ";",
1884 variable,
1885 false,
1886 expression.next_boundary.as_ref(),
1887 ),
1888 UriTemplateOperator::Query => reverse_match_named(
1889 remainder,
1890 "?",
1891 variable,
1892 true,
1893 expression.next_boundary.as_ref(),
1894 ),
1895 UriTemplateOperator::QueryContinuation => reverse_match_named(
1896 remainder,
1897 "&",
1898 variable,
1899 true,
1900 expression.next_boundary.as_ref(),
1901 ),
1902 }?;
1903 let captures = capture
1904 .map(|capture| vec![(variable.name.clone(), capture)])
1905 .unwrap_or_default();
1906 Some((captures, consumed))
1907}
1908
1909fn reverse_match_unprefixed(
1910 remainder: &str,
1911 next_boundary: Option<&ReversibleBoundary>,
1912) -> Option<(Option<String>, usize)> {
1913 let (capture, consumed) = reverse_capture_to_boundary(remainder, next_boundary)?;
1914 if capture.is_empty() {
1915 return Some((None, 0));
1916 }
1917 Some((Some(decode_percent_triplets_once(capture)?), consumed))
1918}
1919
1920fn reverse_match_prefixed(
1921 remainder: &str,
1922 prefix: &str,
1923 next_boundary: Option<&ReversibleBoundary>,
1924) -> Option<(Option<String>, usize)> {
1925 let Some(after_prefix) = remainder.strip_prefix(prefix) else {
1926 return Some((None, 0));
1927 };
1928 let Some((capture, consumed)) = reverse_capture_to_boundary(after_prefix, next_boundary) else {
1929 return Some((None, 0));
1933 };
1934 Some((
1935 Some(decode_percent_triplets_once(capture)?),
1936 prefix.len().saturating_add(consumed),
1937 ))
1938}
1939
1940fn reverse_match_named(
1941 remainder: &str,
1942 marker: &str,
1943 variable: &ReversibleTemplateVariable,
1944 requires_equals: bool,
1945 next_boundary: Option<&ReversibleBoundary>,
1946) -> Option<(Option<String>, usize)> {
1947 let mut prefix = String::with_capacity(marker.len() + variable.encoded_name.len() + 1);
1948 prefix.push_str(marker);
1949 prefix.push_str(&variable.encoded_name);
1950 let Some(after_name) = remainder.strip_prefix(&prefix) else {
1951 return Some((None, 0));
1952 };
1953
1954 let (after_equals, equals_len) = if requires_equals {
1955 if let Some(after_equals) = after_name.strip_prefix('=') {
1956 (after_equals, 1)
1957 } else if after_name.is_empty() {
1958 (after_name, 0)
1959 } else {
1960 return Some((None, 0));
1961 }
1962 } else if let Some(after_equals) = after_name.strip_prefix('=') {
1963 (after_equals, 1)
1964 } else if after_name.is_empty() || after_name.starts_with(';') {
1965 (after_name, 0)
1966 } else {
1967 return Some((None, 0));
1968 };
1969 let Some((capture, consumed)) = reverse_capture_to_boundary(after_equals, next_boundary) else {
1970 return Some((None, 0));
1971 };
1972 Some((
1973 Some(decode_percent_triplets_once(capture)?),
1974 prefix
1975 .len()
1976 .saturating_add(equals_len)
1977 .saturating_add(consumed),
1978 ))
1979}
1980
1981fn reverse_match_named_expression(
1982 expression: &ReversibleTemplateExpression,
1983 remainder: &str,
1984) -> Option<(Vec<(String, String)>, usize)> {
1985 let properties = expression.operator.properties();
1986 debug_assert!(properties.named);
1987
1988 let requires_equals = matches!(
1989 expression.operator,
1990 UriTemplateOperator::Query | UriTemplateOperator::QueryContinuation
1991 );
1992 let mut captures = Vec::with_capacity(expression.variables.len());
1993 let mut offset = 0;
1994 let mut wrote_value = false;
1995
1996 for variable in &expression.variables {
1997 let marker = if wrote_value {
1998 properties.separator
1999 } else {
2000 properties.first
2001 };
2002 let remainder = remainder.get(offset..)?;
2003 let Some(after_marker) = remainder.strip_prefix(marker) else {
2004 continue;
2005 };
2006 let Some(after_name) = after_marker.strip_prefix(&variable.encoded_name) else {
2007 continue;
2008 };
2009
2010 let (after_equals, equals_len) = if requires_equals {
2011 let after_equals = after_name.strip_prefix('=')?;
2012 (after_equals, 1)
2013 } else if let Some(after_equals) = after_name.strip_prefix('=') {
2014 (after_equals, 1)
2015 } else if after_name.is_empty() || after_name.starts_with(properties.separator) {
2016 (after_name, 0)
2017 } else {
2018 continue;
2019 };
2020
2021 let capture_len = reversible_named_capture_len(
2022 after_equals,
2023 properties.separator,
2024 expression.next_boundary.as_ref(),
2025 );
2026 let capture = after_equals.get(..capture_len)?;
2027 let value = decode_percent_triplets_once(capture)?;
2028 offset = offset
2029 .saturating_add(marker.len())
2030 .saturating_add(variable.encoded_name.len())
2031 .saturating_add(equals_len)
2032 .saturating_add(capture_len);
2033 captures.push((variable.name.clone(), value));
2034 wrote_value = true;
2035 }
2036
2037 Some((captures, offset))
2038}
2039
2040fn reversible_named_capture_len(
2041 remainder: &str,
2042 separator: &str,
2043 next_boundary: Option<&ReversibleBoundary>,
2044) -> usize {
2045 let mut capture_len = remainder.len();
2046 if let Some(offset) = remainder.find(separator) {
2047 capture_len = capture_len.min(offset);
2048 }
2049 if let Some(offset) = next_boundary.and_then(|boundary| remainder.find(boundary.as_str())) {
2050 capture_len = capture_len.min(offset);
2051 }
2052 capture_len
2053}
2054
2055fn reverse_capture_to_boundary<'a>(
2056 remainder: &'a str,
2057 next_boundary: Option<&ReversibleBoundary>,
2058) -> Option<(&'a str, usize)> {
2059 match next_boundary {
2060 Some(boundary) => {
2061 if let Some(offset) = remainder.find(boundary.as_str()) {
2062 Some((&remainder[..offset], offset))
2063 } else if boundary.permits_absent_following_expression() {
2064 Some((remainder, remainder.len()))
2065 } else {
2066 None
2067 }
2068 }
2069 None => Some((remainder, remainder.len())),
2070 }
2071}
2072
2073fn decode_percent_triplets_once(capture: &str) -> Option<String> {
2074 let bytes = capture.as_bytes();
2075 let mut decoded = Vec::with_capacity(bytes.len());
2076 let mut index = 0;
2077 while index < bytes.len() {
2078 if bytes[index] == b'%' {
2079 let high = *bytes.get(index + 1)?;
2080 let low = *bytes.get(index + 2)?;
2081 decoded.push((hex_value(high)? << 4) | hex_value(low)?);
2082 index += 3;
2083 } else {
2084 decoded.push(bytes[index]);
2085 index += 1;
2086 }
2087 }
2088 String::from_utf8(decoded).ok()
2089}
2090
2091fn contains_pct_encoded_triplet(value: &str) -> bool {
2092 let bytes = value.as_bytes();
2093 bytes.windows(3).any(|window| {
2094 window[0] == b'%' && window[1].is_ascii_hexdigit() && window[2].is_ascii_hexdigit()
2095 })
2096}
2097
2098#[cfg(test)]
2099mod tests {
2100 use super::*;
2101
2102 fn values() -> TemplateValues {
2103 let mut values = TemplateValues::new();
2104 values.insert("var".to_owned(), TemplateValue::scalar("value"));
2105 values.insert("hello".to_owned(), TemplateValue::scalar("Hello World!"));
2106 values.insert("path".to_owned(), TemplateValue::scalar("/foo/bar"));
2107 values.insert(
2108 "list".to_owned(),
2109 TemplateValue::list(vec![
2110 "red".to_owned(),
2111 "green".to_owned(),
2112 "blue".to_owned(),
2113 ]),
2114 );
2115 values.insert(
2116 "keys".to_owned(),
2117 TemplateValue::associative(vec![
2118 ("semi".to_owned(), ";".to_owned()),
2119 ("dot".to_owned(), ".".to_owned()),
2120 ("comma".to_owned(), ",".to_owned()),
2121 ]),
2122 );
2123 values
2124 }
2125
2126 #[test]
2127 fn rfc6570_level_four_expansion_positive() {
2128 let values = values();
2129 let examples = [
2130 ("{var:3}", "val"),
2131 ("{+path}", "/foo/bar"),
2132 ("{#hello}", "#Hello%20World!"),
2133 ("X{.list*}", "X.red.green.blue"),
2134 ("{/list*,path:4}", "/red/green/blue/%2Ffoo"),
2135 ("{;keys*}", ";semi=%3B;dot=.;comma=%2C"),
2136 ("{?keys*}", "?semi=%3B&dot=.&comma=%2C"),
2137 (
2138 "?fixed=yes{&list*}",
2139 "?fixed=yes&list=red&list=green&list=blue",
2140 ),
2141 ("{keys}", "semi,%3B,dot,.,comma,%2C"),
2142 ("{+keys*}", "semi=;,dot=.,comma=,"),
2143 ("{hello}", "Hello%20World%21"),
2144 ];
2145
2146 for (template, expected) in examples {
2147 let parsed = UriTemplate::parse(template).expect("RFC 6570 example parses");
2148 assert_eq!(parsed.source(), template);
2149 assert_eq!(
2150 parsed.expand(&values).expect("RFC example expands"),
2151 expected
2152 );
2153 }
2154 }
2155
2156 #[test]
2157 fn rfc6570_unicode_and_existing_percent_triplets_positive() {
2158 let template = UriTemplate::parse("https://example.test/é%27s/{term}/{+encoded}")
2159 .expect("printable Unicode literal and RFC expressions parse");
2160 let mut values = TemplateValues::new();
2161 values.insert("term".to_owned(), TemplateValue::scalar("café"));
2162 values.insert("encoded".to_owned(), TemplateValue::scalar("50%25"));
2163
2164 assert_eq!(
2165 template
2166 .expand(&values)
2167 .expect("Unicode expands as UTF-8 percent triplets"),
2168 "https://example.test/%C3%A9%27s/caf%C3%A9/50%25"
2169 );
2170 assert_eq!(
2171 UriTemplate::parse("{encoded}")
2172 .expect("simple expansion parses")
2173 .expand(&values)
2174 .expect("simple expansion percent-encodes a supplied triplet once"),
2175 "50%2525"
2176 );
2177 }
2178
2179 #[test]
2180 fn rfc6570_literal_percent_triplets_are_admitted_without_normalizing_bare_percent() {
2181 let template = UriTemplate::parse("mcp://resource/reports%2Fdaily")
2182 .expect("a complete literal percent triplet is valid RFC 6570");
2183 assert_eq!(
2184 template
2185 .expand(&TemplateValues::new())
2186 .expect("a valid literal expands unchanged"),
2187 "mcp://resource/reports%2Fdaily"
2188 );
2189
2190 assert_eq!(
2191 UriTemplate::parse("mcp://resource/reports%Qdaily"),
2192 Err(UriTemplateError::InvalidLiteral {
2193 offset: "mcp://resource/reports".len(),
2194 }),
2195 "changing only the final percent triplet to a bare percent rejects rather than rewriting it"
2196 );
2197 }
2198
2199 #[test]
2200 fn rfc6570_literal_grammar_accepts_final_ucschar_and_rejects_nearby_exclusions() {
2201 let admitted = format!(
2202 "mcp://resource/{}",
2203 char::from_u32(0xE1000).expect("E1000 is a Unicode scalar")
2204 );
2205 UriTemplate::parse(&admitted).expect("the first scalar in RFC 6570's final ucschar range");
2206
2207 let excluded_ucschar = format!(
2208 "mcp://resource/{}",
2209 char::from_u32(0xE0FFF).expect("E0FFF is a Unicode scalar")
2210 );
2211 assert!(matches!(
2212 UriTemplate::parse(&excluded_ucschar),
2213 Err(UriTemplateError::InvalidLiteral { .. })
2214 ));
2215 assert!(matches!(
2216 UriTemplate::parse("mcp://resource/raw'apostrophe"),
2217 Err(UriTemplateError::InvalidLiteral { .. })
2218 ));
2219 UriTemplate::parse("mcp://resource/encoded%27apostrophe")
2220 .expect("pct-encoded apostrophe remains a valid literal");
2221 }
2222
2223 #[test]
2224 fn rfc6570_literal_grammar_admits_supplementary_iprivate_ranges() {
2225 for scalar in [0xF0000, 0xFFFFD, 0x100000, 0x10FFFD] {
2226 let template = format!(
2227 "mcp://resource/{}",
2228 char::from_u32(scalar).expect("the RFC 3987 iprivate scalar is valid Unicode"),
2229 );
2230 UriTemplate::parse(&template)
2231 .expect("RFC 6570 literals admit every supplementary iprivate endpoint");
2232 }
2233
2234 for scalar in [0xEFFFE, 0xEFFFF, 0xFFFFE, 0x10FFFE] {
2235 let template = format!(
2236 "mcp://resource/{}",
2237 char::from_u32(scalar).expect("the adjacent scalar is valid Unicode"),
2238 );
2239 assert!(matches!(
2240 UriTemplate::parse(&template),
2241 Err(UriTemplateError::InvalidLiteral { .. })
2242 ));
2243 }
2244 }
2245
2246 #[test]
2247 fn rfc6570_bounds_apply_before_ownership_and_prefix_projection() {
2248 let oversized_source = "x".repeat(MAX_URI_TEMPLATE_BYTES + 1);
2249 assert_eq!(
2250 UriTemplate::parse(&oversized_source),
2251 Err(UriTemplateError::SourceTooLong {
2252 actual: MAX_URI_TEMPLATE_BYTES + 1,
2253 maximum: MAX_URI_TEMPLATE_BYTES,
2254 })
2255 );
2256
2257 let template = UriTemplate::parse("{value:1}").expect("prefix expression parses");
2258 let mut values = TemplateValues::new();
2259 values.insert("value".to_owned(), TemplateValue::scalar("ab"));
2260 let limits =
2261 UriTemplateExpansionLimits::new(16, 1, 1).expect("one-byte scalar limit is valid");
2262 assert_eq!(
2263 template.expand_with_limits(&values, limits),
2264 Err(UriTemplateError::ValueTooLong {
2265 actual: 2,
2266 maximum: 1,
2267 })
2268 );
2269 }
2270
2271 #[test]
2272 fn rfc6570_prefix_counts_percent_encoded_utf8_as_one_unicode_scalar() {
2273 let template = UriTemplate::parse("{+value:1}").expect("prefix expression parses");
2274 let mut values = TemplateValues::new();
2275 values.insert("value".to_owned(), TemplateValue::scalar("%C3%A9clair"));
2276
2277 assert_eq!(
2278 template
2279 .expand(&values)
2280 .expect("encoded Unicode scalar is not split between octets"),
2281 "%C3%A9"
2282 );
2283 }
2284
2285 #[test]
2286 fn rfc6570_undefined_composite_members_are_ignored_without_losing_empty_values() {
2287 let template = UriTemplate::parse("{?list*,keys*}").expect("composite expression parses");
2288 let mut values = TemplateValues::new();
2289 values.insert(
2290 "list".to_owned(),
2291 TemplateValue::list_with_undefined(vec![
2292 None,
2293 Some("one".to_owned()),
2294 Some(String::new()),
2295 None,
2296 ]),
2297 );
2298 values.insert(
2299 "keys".to_owned(),
2300 TemplateValue::associative_with_undefined(vec![
2301 ("ignored".to_owned(), None),
2302 ("second".to_owned(), Some("2".to_owned())),
2303 ]),
2304 );
2305
2306 assert_eq!(
2307 template
2308 .expand(&values)
2309 .expect("only defined composite members expand"),
2310 "?list=one&list=&second=2"
2311 );
2312
2313 values.insert(
2314 "list".to_owned(),
2315 TemplateValue::list_with_undefined(vec![None]),
2316 );
2317 values.insert(
2318 "keys".to_owned(),
2319 TemplateValue::associative_with_undefined(vec![("ignored".to_owned(), None)]),
2320 );
2321 assert_eq!(
2322 template
2323 .expand(&values)
2324 .expect("all-undefined composites are undefined variables"),
2325 ""
2326 );
2327 assert_eq!(
2328 UriTemplate::parse("{list:1}{keys:1}")
2329 .expect("prefix modifiers parse independently of runtime value types")
2330 .expand(&values)
2331 .expect("an undefined composite is ignored before modifier semantics"),
2332 ""
2333 );
2334 }
2335
2336 #[test]
2337 fn rfc6570_associative_order_is_lossless_and_duplicates_fail_closed() {
2338 let template = UriTemplate::parse("{keys}").expect("associative expression parses");
2339 let mut values = TemplateValues::new();
2340 values.insert(
2341 "keys".to_owned(),
2342 TemplateValue::associative(vec![
2343 ("second".to_owned(), "2".to_owned()),
2344 ("first".to_owned(), "1".to_owned()),
2345 ]),
2346 );
2347 assert_eq!(
2348 template.expand(&values).expect("ordered pairs expand"),
2349 "second,2,first,1"
2350 );
2351
2352 let before = values.clone();
2353 values.insert(
2354 "keys".to_owned(),
2355 TemplateValue::associative(vec![
2356 ("same".to_owned(), "1".to_owned()),
2357 ("same".to_owned(), "2".to_owned()),
2358 ]),
2359 );
2360 let planted_before = values.clone();
2361 assert_eq!(
2362 template.expand(&values),
2363 Err(UriTemplateError::DuplicateAssociativeKey {
2364 key: "same".to_owned(),
2365 })
2366 );
2367 assert_ne!(
2368 planted_before, before,
2369 "negative changes only the associative input"
2370 );
2371 assert_eq!(
2372 values, planted_before,
2373 "rejected expansion leaves caller values unchanged"
2374 );
2375 }
2376
2377 #[test]
2378 fn rfc6570_level_four_near_negative_rejects_invalid_prefix_without_mutation() {
2379 let accepted = "mcp://resources/{term:3}{?cursor}";
2380 let planted = "mcp://resources/{term:0}{?cursor}";
2381 let parsed = UriTemplate::parse(accepted).expect("positive control parses");
2382 let before = parsed.clone();
2383
2384 let error = UriTemplate::parse(planted)
2385 .expect_err("changing only the positive prefix length to zero must reject");
2386 assert!(matches!(error, UriTemplateError::InvalidPrefix { .. }));
2387 assert_eq!(
2388 parsed, before,
2389 "rejected input cannot mutate an admitted AST"
2390 );
2391 assert_eq!(parsed.source(), accepted);
2392 }
2393
2394 #[test]
2395 fn rfc6570_level_four_near_negative_rejects_composite_overflow_without_mutation() {
2396 let template = UriTemplate::parse("{?items*}").expect("positive control parses");
2397 let mut values = TemplateValues::new();
2398 values.insert(
2399 "items".to_owned(),
2400 TemplateValue::list(vec!["one".to_owned(), "two".to_owned()]),
2401 );
2402 let before = values.clone();
2403 let limits = UriTemplateExpansionLimits::new(128, 1, 16)
2404 .expect("the stricter configured limits are valid");
2405
2406 let error = template
2407 .expand_with_limits(&values, limits)
2408 .expect_err("changing only the allowed member count must reject the same input");
2409 assert_eq!(
2410 error,
2411 UriTemplateError::TooManyCompositeItems {
2412 actual: 2,
2413 maximum: 1,
2414 }
2415 );
2416 assert_eq!(
2417 values, before,
2418 "rejected expansion leaves caller values unchanged"
2419 );
2420 }
2421
2422 #[test]
2423 fn reversible_resource_template_decodes_populated_and_absent_path_captures() {
2424 let template = UriTemplate::parse("mcp://resource{/collection}/manifest{?revision}")
2425 .expect("the RFC 6570 template parses");
2426 let matcher = template
2427 .compile_reversible()
2428 .expect("separated scalar captures compile deterministically");
2429 let mut values = TemplateValues::new();
2430 values.insert(
2431 "collection".to_owned(),
2432 TemplateValue::scalar("books/fiction"),
2433 );
2434 values.insert("revision".to_owned(), TemplateValue::scalar("2026-08-09"));
2435
2436 let uri = matcher
2437 .expand(&values)
2438 .expect("declared scalar values expand");
2439 assert_eq!(
2440 uri,
2441 "mcp://resource/books%2Ffiction/manifest?revision=2026-08-09"
2442 );
2443 assert_eq!(
2444 matcher
2445 .match_uri(&uri)
2446 .expect("reverse matching is bounded"),
2447 Some(values),
2448 "a populated path capture decodes its percent triplet exactly once"
2449 );
2450
2451 let omitted = TemplateValues::new();
2452 let omitted_uri = matcher
2453 .expand(&omitted)
2454 .expect("undefined values omit their whole expressions");
2455 assert_eq!(omitted_uri, "mcp://resource/manifest");
2456 assert_eq!(
2457 matcher
2458 .match_uri(&omitted_uri)
2459 .expect("omitted values reverse match"),
2460 Some(omitted),
2461 "an omitted path capture does not consume its following literal"
2462 );
2463 }
2464
2465 #[test]
2466 fn reversible_resource_template_round_trips_raw_scalar_input() {
2467 let matcher = UriTemplate::parse("mcp://resource/{value}")
2468 .expect("the simple template parses")
2469 .compile_reversible()
2470 .expect("a terminal simple capture is deterministic");
2471 let mut values = TemplateValues::new();
2472 values.insert("value".to_owned(), TemplateValue::scalar("books/fiction"));
2473
2474 let uri = matcher
2475 .expand(&values)
2476 .expect("simple expansion percent-encodes a raw slash");
2477 assert_eq!(uri, "mcp://resource/books%2Ffiction");
2478 assert_eq!(
2479 matcher
2480 .match_uri(&uri)
2481 .expect("reverse matching is bounded"),
2482 Some(values),
2483 "reverse matching decodes the URI triplet back to the raw scalar"
2484 );
2485 }
2486
2487 #[test]
2488 fn reversible_resource_template_preescaped_simple_scalar_is_not_preescaped() {
2489 let matcher = UriTemplate::parse("mcp://resource/{value}")
2490 .expect("the simple template parses")
2491 .compile_reversible()
2492 .expect("a terminal simple capture is deterministic");
2493 let mut values = TemplateValues::new();
2494 values.insert("value".to_owned(), TemplateValue::scalar("books%2Ffiction"));
2495
2496 let uri = matcher
2497 .expand(&values)
2498 .expect("simple expansion encodes a percent as data");
2499 assert_eq!(uri, "mcp://resource/books%252Ffiction");
2500 assert_ne!(uri, "mcp://resource/books%2Ffiction");
2501 assert_eq!(
2502 matcher
2503 .match_uri(&uri)
2504 .expect("reverse matching is bounded"),
2505 Some(values),
2506 "one decode restores the preescaped scalar without treating it as wire syntax"
2507 );
2508 }
2509
2510 #[test]
2511 fn reversible_resource_template_reserved_and_fragment_raw_values_round_trip() {
2512 let cases = [
2513 (
2514 "mcp://resource/{+value}",
2515 "docs/guide?draft=true",
2516 "mcp://resource/docs/guide?draft=true",
2517 ),
2518 (
2519 "mcp://resource{#value}",
2520 "docs/guide?draft=true",
2521 "mcp://resource#docs/guide?draft=true",
2522 ),
2523 ];
2524
2525 for (template, raw_value, expected_uri) in cases {
2526 let matcher = UriTemplate::parse(template)
2527 .expect("the reserved or fragment template parses")
2528 .compile_reversible()
2529 .expect("a terminal reserved or fragment capture is deterministic");
2530 let mut values = TemplateValues::new();
2531 values.insert("value".to_owned(), TemplateValue::scalar(raw_value));
2532
2533 let uri = matcher
2534 .expand(&values)
2535 .expect("raw reserved characters expand without pre-escaping");
2536 assert_eq!(uri, expected_uri);
2537 assert_eq!(
2538 matcher
2539 .match_uri(&uri)
2540 .expect("reverse matching is bounded"),
2541 Some(values),
2542 "the raw scalar round-trips through the {template} inverse"
2543 );
2544 }
2545 }
2546
2547 #[test]
2548 fn reversible_resource_template_reserved_and_fragment_preescaped_scalars_reject_without_mutation()
2549 {
2550 for template in ["mcp://resource/{+value}", "mcp://resource{#value}"] {
2551 let matcher = UriTemplate::parse(template)
2552 .expect("the reserved or fragment template parses")
2553 .compile_reversible()
2554 .expect("a terminal reserved or fragment capture is deterministic");
2555 let matcher_before = matcher.clone();
2556 let mut values = TemplateValues::new();
2557 values.insert("value".to_owned(), TemplateValue::scalar("docs%2Fguide"));
2558 let values_before = values.clone();
2559
2560 assert_eq!(
2561 matcher.expand(&values),
2562 Err(UriTemplateError::PreescapedReservedMatchValue {
2563 variable: "value".to_owned(),
2564 }),
2565 "a preescaped scalar is ambiguous for the {template} inverse"
2566 );
2567 assert_eq!(
2568 matcher, matcher_before,
2569 "rejected expansion changes no matcher state"
2570 );
2571 assert_eq!(
2572 values, values_before,
2573 "rejected expansion changes no caller values"
2574 );
2575 }
2576 }
2577
2578 #[test]
2579 fn reversible_resource_template_scalar_explode_operator_matrix_round_trips() {
2580 let cases = [
2581 ("mcp://resource/{value*}", "mcp://resource/one%2Ftwo"),
2582 ("mcp://resource/{+value*}", "mcp://resource/one/two"),
2583 ("mcp://resource{#value*}", "mcp://resource#one/two"),
2584 ("mcp://resource{.value*}", "mcp://resource.one%2Ftwo"),
2585 ("mcp://resource{/value*}", "mcp://resource/one%2Ftwo"),
2586 ("mcp://resource{;value*}", "mcp://resource;value=one%2Ftwo"),
2587 ("mcp://resource{?value*}", "mcp://resource?value=one%2Ftwo"),
2588 (
2589 "mcp://resource?fixed=true{&value*}",
2590 "mcp://resource?fixed=true&value=one%2Ftwo",
2591 ),
2592 ];
2593
2594 for (source, expected_uri) in cases {
2595 let matcher = UriTemplate::parse(source)
2596 .expect("each RFC 6570 operator parses")
2597 .compile_reversible()
2598 .expect("an exploded scalar has the same reversible wire form as a scalar");
2599 let values =
2600 TemplateValues::from([("value".to_owned(), TemplateValue::scalar("one/two"))]);
2601 let uri = matcher
2602 .expand(&values)
2603 .expect("the admitted scalar values expand");
2604 assert_eq!(uri, expected_uri, "{source}");
2605 assert_eq!(
2606 matcher.match_uri(&uri).expect("matching is bounded"),
2607 Some(values),
2608 "{source} extracts the same scalar binding it expanded"
2609 );
2610 }
2611 }
2612
2613 #[test]
2614 fn reversible_resource_template_named_multi_variable_expression_round_trips_omission() {
2615 let matcher = UriTemplate::parse("mcp://resource{?first*,second*}")
2616 .expect("the named Level 4 expression parses")
2617 .compile_reversible()
2618 .expect("wire names make named scalar omissions reversible");
2619 let cases = [
2620 (TemplateValues::new(), "mcp://resource"),
2621 (
2622 TemplateValues::from([("first".to_owned(), TemplateValue::scalar("one/two"))]),
2623 "mcp://resource?first=one%2Ftwo",
2624 ),
2625 (
2626 TemplateValues::from([("second".to_owned(), TemplateValue::scalar("three"))]),
2627 "mcp://resource?second=three",
2628 ),
2629 (
2630 TemplateValues::from([
2631 ("first".to_owned(), TemplateValue::scalar("one/two")),
2632 ("second".to_owned(), TemplateValue::scalar("three")),
2633 ]),
2634 "mcp://resource?first=one%2Ftwo&second=three",
2635 ),
2636 ];
2637
2638 for (values, expected_uri) in cases {
2639 let uri = matcher.expand(&values).expect("named scalar values expand");
2640 assert_eq!(uri, expected_uri);
2641 assert_eq!(
2642 matcher.match_uri(&uri).expect("matching is bounded"),
2643 Some(values),
2644 "the names distinguish omitted variables from present ones"
2645 );
2646 }
2647 }
2648
2649 #[test]
2650 fn reversible_resource_template_named_multi_variable_stops_at_next_expression() {
2651 let matcher = UriTemplate::parse("mcp://resource{?first*,second*}{#fragment*}")
2652 .expect("the adjacent named and fragment expressions parse")
2653 .compile_reversible()
2654 .expect("their distinct wire markers make the boundary reversible");
2655 let values = TemplateValues::from([
2656 ("first".to_owned(), TemplateValue::scalar("one")),
2657 ("second".to_owned(), TemplateValue::scalar("two")),
2658 ("fragment".to_owned(), TemplateValue::scalar("three/four")),
2659 ]);
2660 let uri = matcher
2661 .expand(&values)
2662 .expect("the named values and following fragment expand");
2663 assert_eq!(uri, "mcp://resource?first=one&second=two#three/four");
2664 assert_eq!(
2665 matcher.match_uri(&uri).expect("matching is bounded"),
2666 Some(values),
2667 "the query capture stops before the following fragment marker"
2668 );
2669 }
2670
2671 #[test]
2672 fn reversible_resource_template_near_negative_rejects_lossy_or_composite_without_mutation() {
2673 let accepted = "mcp://resource{/collection}/manifest{?revision}";
2674 let planted_lossy = "mcp://resource{/collection:3}/manifest{?revision}";
2675 let matcher = UriTemplate::parse(accepted)
2676 .expect("positive control parses")
2677 .compile_reversible()
2678 .expect("positive control compiles");
2679 let matcher_before = matcher.clone();
2680 let mut values = TemplateValues::new();
2681 values.insert("collection".to_owned(), TemplateValue::scalar("books"));
2682 values.insert("revision".to_owned(), TemplateValue::scalar("1"));
2683 let values_before = values.clone();
2684
2685 let error = UriTemplate::parse(planted_lossy)
2686 .expect("changing only the scalar modifier remains valid RFC 6570")
2687 .compile_reversible()
2688 .expect_err("a lossy prefix has no deterministic reverse match");
2689 assert_eq!(
2690 error,
2691 UriTemplateError::NonReversibleTemplate {
2692 reason: UriTemplateMatchRejection::LossyPrefix {
2693 variable: "collection".to_owned(),
2694 },
2695 }
2696 );
2697 assert_eq!(
2698 matcher, matcher_before,
2699 "rejected compilation changes no matcher state"
2700 );
2701 assert_eq!(
2702 values, values_before,
2703 "rejected compilation changes no caller values"
2704 );
2705
2706 values.insert(
2707 "collection".to_owned(),
2708 TemplateValue::list(vec!["books".to_owned()]),
2709 );
2710 let planted_values_before = values.clone();
2711 assert_eq!(
2712 matcher.expand(&values),
2713 Err(UriTemplateError::NonScalarMatchValue {
2714 variable: "collection".to_owned(),
2715 }),
2716 "changing only the declared scalar into a composite must reject"
2717 );
2718 assert_eq!(
2719 values, planted_values_before,
2720 "rejected composite expansion leaves caller values unchanged"
2721 );
2722 }
2723
2724 #[test]
2725 fn reversible_resource_template_match_input_bound_rejects_before_capture() {
2726 let matcher = UriTemplate::parse("mcp://resource/{value*}")
2727 .expect("the scalar Level 4 template parses")
2728 .compile_reversible()
2729 .expect("the scalar Level 4 template compiles");
2730 let matcher_before = matcher.clone();
2731 let prefix = "mcp://resource/";
2732 let oversized_uri = format!(
2733 "{prefix}{}",
2734 "x".repeat(MAX_URI_TEMPLATE_MATCH_INPUT_BYTES - prefix.len() + 1)
2735 );
2736
2737 assert_eq!(
2738 matcher.match_uri(&oversized_uri),
2739 Err(UriTemplateError::MatchInputTooLong {
2740 actual: MAX_URI_TEMPLATE_MATCH_INPUT_BYTES + 1,
2741 maximum: MAX_URI_TEMPLATE_MATCH_INPUT_BYTES,
2742 })
2743 );
2744 assert_eq!(
2745 matcher, matcher_before,
2746 "a rejected candidate cannot mutate the compiled matcher"
2747 );
2748 }
2749
2750 #[test]
2751 fn reversible_resource_template_adjacent_path_query_round_trips_exactly() {
2752 let accepted = "mcp://resource{/collection}{?revision}";
2753 let planted_ambiguous = "mcp://resource{/collection}{/revision}";
2754 let matcher = UriTemplate::parse(accepted)
2755 .expect("the positive control parses")
2756 .compile_reversible()
2757 .expect("a path capture is separable from the following query marker");
2758 let matcher_before = matcher.clone();
2759 let mut values = TemplateValues::new();
2760 values.insert(
2761 "collection".to_owned(),
2762 TemplateValue::scalar("books/fiction"),
2763 );
2764 values.insert("revision".to_owned(), TemplateValue::scalar("2026-08-09"));
2765 let values_before = values.clone();
2766
2767 let uri = matcher
2768 .expand(&values)
2769 .expect("the separated scalar values expand");
2770 assert_eq!(uri, "mcp://resource/books%2Ffiction?revision=2026-08-09");
2771 let round_tripped = matcher
2772 .match_uri(&uri)
2773 .expect("reverse matching is bounded")
2774 .expect("the exact expansion belongs to the matcher language");
2775 assert_eq!(round_tripped, values);
2776 assert_eq!(
2777 matcher
2778 .expand(&round_tripped)
2779 .expect("the recovered bindings re-expand"),
2780 uri,
2781 "a reversible match must preserve the exact wire URI"
2782 );
2783
2784 let mut path_only = TemplateValues::new();
2785 path_only.insert("collection".to_owned(), TemplateValue::scalar("books"));
2786 let path_only_uri = matcher
2787 .expand(&path_only)
2788 .expect("the query expression may be absent");
2789 assert_eq!(path_only_uri, "mcp://resource/books");
2790 assert_eq!(
2791 matcher
2792 .match_uri(&path_only_uri)
2793 .expect("matching an absent following expression is bounded"),
2794 Some(path_only),
2795 "the first capture remains exact when the query marker is absent"
2796 );
2797
2798 let error = UriTemplate::parse(planted_ambiguous)
2799 .expect(
2800 "changing only the following query marker to a path marker remains valid RFC 6570",
2801 )
2802 .compile_reversible()
2803 .expect_err("identical path markers cannot distinguish an omitted adjacent scalar");
2804 assert_eq!(
2805 error,
2806 UriTemplateError::NonReversibleTemplate {
2807 reason: UriTemplateMatchRejection::AmbiguousBoundary,
2808 }
2809 );
2810 assert_eq!(
2811 matcher, matcher_before,
2812 "rejected adjacent syntax changes no admitted matcher state"
2813 );
2814 assert_eq!(
2815 values, values_before,
2816 "rejected adjacent syntax changes no caller bindings"
2817 );
2818 }
2819
2820 #[test]
2821 fn reversible_resource_template_rejects_adjacent_captures() {
2822 let template = UriTemplate::parse("mcp://resource/{first}{second}")
2823 .expect("the syntactically valid template parses");
2824 assert_eq!(
2825 template.compile_reversible(),
2826 Err(UriTemplateError::NonReversibleTemplate {
2827 reason: UriTemplateMatchRejection::AdjacentCaptures,
2828 }),
2829 "adjacent captures admit multiple splits and cannot become a handler matcher"
2830 );
2831 }
2832}