public Enumerator(PathTokenizer tokenizer) { _path = tokenizer._path; _index = -1; _length = -1; }
public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act & Assert Assert.Equal <StringSegment>(expectedSegments, tokenizer); }
public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act & Assert for (var i = 0; i < expectedSegments.Length; i++) { Assert.Equal(expectedSegments[i], tokenizer[i]); } }
public void PathTokenizer_Count(string path, StringSegment[] expectedSegments) { // Arrange var tokenizer = new PathTokenizer(new PathString(path)); // Act var count = tokenizer.Count; // Assert Assert.Equal(expectedSegments.Length, count); }
public bool TryMatch(PathString path, RouteValueDictionary values) { if (values == null) { throw new ArgumentNullException(nameof(values)); } var i = 0; var pathTokenizer = new PathTokenizer(path); // Perf: We do a traversal of the request-segments + route-segments twice. // // For most segment-types, we only really need to any work on one of the two passes. // // On the first pass, we're just looking to see if there's anything that would disqualify us from matching. // The most common case would be a literal segment that doesn't match. // // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values' // and start capturing strings. foreach (var stringSegment in pathTokenizer) { if (stringSegment.Length == 0) { return(false); } var pathSegment = i >= RoutePattern.PathSegments.Count ? null : RoutePattern.PathSegments[i]; if (pathSegment == null && stringSegment.Length > 0) { // If pathSegment is null, then we're out of route segments. All we can match is the empty // string. return(false); } else if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameterPart parameter && parameter.IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } if (!TryMatchLiterals(i++, stringSegment, pathSegment)) { return(false); } } for (; i < RoutePattern.PathSegments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. These need // to be all single-part parameter segments with default values or else they won't match. var pathSegment = RoutePattern.PathSegments[i]; Debug.Assert(pathSegment != null); if (!pathSegment.IsSimple) { // If the segment is a complex segment, it MUST contain literals, and we've parsed the full // path so far, so it can't match. return(false); } var part = pathSegment.Parts[0]; if (part.IsLiteral || part.IsSeparator) { // If the segment is a simple literal - which need the URL to provide a value, so we don't match. return(false); } var parameter = (RoutePatternParameterPart)part; if (parameter.IsCatchAll) { // Nothing to validate for a catch-all - it can match any string, including the empty string. // // Also, a catch-all has to be the last part, so we're done. break; } // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the // defaults to have a value. if (!_hasDefaultValue[i] && !parameter.IsOptional) { // There's no default for this (non-optional) parameter so it can't match. return(false); } } // At this point we've very likely got a match, so start capturing values for real. i = 0; foreach (var requestSegment in pathTokenizer) { var pathSegment = RoutePattern.PathSegments[i++]; if (SavePathSegmentsAsValues(i, values, requestSegment, pathSegment)) { break; } if (!pathSegment.IsSimple) { if (!MatchComplexSegment(pathSegment, requestSegment.AsSpan(), values)) { return(false); } } } for (; i < RoutePattern.PathSegments.Count; i++) { // We've matched the request path so far, but still have remaining route segments. We already know these // are simple parameters that either have a default, or don't need to produce a value. var pathSegment = RoutePattern.PathSegments[i]; Debug.Assert(pathSegment != null); Debug.Assert(pathSegment.IsSimple); var part = pathSegment.Parts[0]; Debug.Assert(part.IsParameter); // It's ok for a catch-all to produce a null value if (part is RoutePatternParameterPart parameter && (parameter.IsCatchAll || _hasDefaultValue[i])) { // Don't replace an existing value with a null. var defaultValue = _defaultValues[i]; if (defaultValue != null || !values.ContainsKey(parameter.Name)) { values[parameter.Name] = defaultValue; } } } // Copy all remaining default values to the route data foreach (var kvp in Defaults) { #if RVD_TryAdd values.TryAdd(kvp.Key, kvp.Value); #else if (!values.ContainsKey(kvp.Key)) { values.Add(kvp.Key, kvp.Value); } #endif } return(true); }