// See description on ComputeOutboundPrecedenceDigit(TemplateSegment segment) private static int ComputeOutboundPrecedenceDigit(RoutePatternPathSegment pathSegment) { if (pathSegment.Parts.Count > 1) { return(4); } var part = pathSegment.Parts[0]; if (part.IsLiteral) { return(5); } else if (part is RoutePatternParameterPart parameterPart) { Debug.Assert(parameterPart != null); var digit = parameterPart.IsCatchAll ? 1 : 3; if (parameterPart.ParameterPolicies.Count > 0) { digit++; } return(digit); } else { // Unreachable throw new NotSupportedException(); } }
private static void WriteString(StringBuilder sb, RoutePatternPathSegment segment) { for (int i = 0; i < segment.Parts.Count; i++) { WriteString(sb, segment.Parts[i]); } }
// Segments have the following order: // 1 - Literal segments // 2 - Constrained parameter segments / Multi-part segments // 3 - Unconstrained parameter segments // 4 - Constrained wildcard parameter segments // 5 - Unconstrained wildcard parameter segments private static int ComputeInboundPrecedenceDigit(RoutePatternPathSegment segment) { if (segment.Parts.Count > 1) { // Multi-part segments should appear after literal segments and along with parameter segments return(2); } var part = segment.Parts[0]; // Literal segments always go first if (part.IsLiteral) { return(1); } else { Debug.Assert(part.IsParameter); var parameter = (RoutePatternParameter)part; var digit = parameter.IsCatchAll ? 5 : 3; // If there is a dispatcher value constraint for the parameter, reduce order by 1 // Constrained parameters end up with order 2, Constrained catch alls end up with order 4 if (parameter.Constraints != null && parameter.Constraints.Any()) { digit--; } return(digit); } }
public void Ctor_RoutePatternPathSegment_ShouldThrowArgumentNullExceptionWhenOtherIsNull() { const RoutePatternPathSegment other = null; var actual = Assert.ThrowsAny <ArgumentNullException>(() => new TemplateSegment(other)); Assert.Equal(nameof(other), actual.ParamName); }
private string BuildRouteSegment(RoutePatternPathSegment segment) { var stringBuilder = new StringBuilder(); foreach (var part in segment.Parts) { stringBuilder.Append(BuildRoutePart(part)); } return(stringBuilder.ToString()); }
public TemplateSegment(RoutePatternPathSegment other) { if (other == null) { throw new ArgumentNullException(nameof(other)); } var partCount = other.Parts.Count; Parts = new List <TemplatePart>(partCount); for (var i = 0; i < partCount; i++) { Parts.Add(new TemplatePart(other.Parts[i])); } }
private bool TryMatchLiterals(int index, StringSegment stringSegment, RoutePatternPathSegment pathSegment) { if (pathSegment.IsSimple && !pathSegment.Parts[0].IsParameter) { // This is a literal segment, so we need to match the text, or the route isn't a match. if (pathSegment.Parts[0].IsLiteral) { var part = (RoutePatternLiteral)pathSegment.Parts[0]; if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase)) { return(false); } } else { var part = (RoutePatternSeparator)pathSegment.Parts[0]; if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase)) { return(false); } } } else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter) { // For a parameter, validate that it's a has some length, or we have a default, or it's optional. var part = (RoutePatternParameter)pathSegment.Parts[0]; if (stringSegment.Length == 0 && !_hasDefaultValue[index] && !part.IsOptional) { // There's no value for this parameter, the route can't match. return(false); } } else { Debug.Assert(!pathSegment.IsSimple); // Don't attempt to validate a complex segment at this point other than being non-emtpy, // do it in the second pass. } return(true); }
internal static int ComputeInboundPrecedenceDigit(RoutePattern routePattern, RoutePatternPathSegment pathSegment) { if (pathSegment.Parts.Count > 1) { // Multi-part segments should appear after literal segments and along with parameter segments return(2); } var part = pathSegment.Parts[0]; // Literal segments always go first if (part.IsLiteral) { return(1); } else if (part is RoutePatternParameterPart parameterPart) { // Parameter with a required value is matched as a literal if (routePattern.RequiredValues.TryGetValue(parameterPart.Name, out var requiredValue) && !RouteValueEqualityComparer.Default.Equals(requiredValue, string.Empty)) { return(1); } var digit = parameterPart.IsCatchAll ? 5 : 3; // If there is a route constraint for the parameter, reduce order by 1 // Constrained parameters end up with order 2, Constrained catch alls end up with order 4 if (parameterPart.ParameterPolicies.Count > 0) { digit--; } return(digit); } else { // Unreachable throw new NotSupportedException(); } }
private void AddParentsMatchingComplexSegment(RouteEndpoint endpoint, List <DfaNode> nextParents, RoutePatternPathSegment segment, DfaNode parent, RoutePatternParameterPart parameterPart) { var routeValues = new RouteValueDictionary(); foreach (var literal in parent.Literals.Keys) { if (RoutePatternMatcher.MatchComplexSegment(segment, literal, routeValues)) { // If we got here (rare) it means that the literal matches the complex segment (for example the literal is something A-B) // there is another thing we can try here, which is to evaluate the policies for the parts in case they have one (for example {a:length(4)}-{b:regex(\d+)}) // so that even if it maps closely to a complex parameter we have a chance to discard it and avoid adding the extra branches. var passedAllPolicies = true; for (var i = 0; i < segment.Parts.Count; i++) { var segmentPart = segment.Parts[i]; if (segmentPart is not RoutePatternParameterPart partParameter) { // We skip over the literals and the separator since we already checked against them continue; } if (!routeValues.TryGetValue(partParameter.Name, out var parameterValue)) { // We have a pattern like {a}-{b}.{part?} and a literal "a-b". Since we've matched the complex segment it means that the optional // parameter was not specified, so we skip it. Debug.Assert(i == segment.Parts.Count - 1 && partParameter.IsOptional); continue; } if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(partParameter.Name, out var parameterPolicyReferences)) { for (var j = 0; j < parameterPolicyReferences.Count; j++) { var reference = parameterPolicyReferences[j]; var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference); if (parameterPolicy is IParameterLiteralNodeMatchingPolicy constraint && !constraint.MatchesLiteral(partParameter.Name, (string)parameterValue)) { passedAllPolicies = false; break; } } } } if (passedAllPolicies) { nextParents.Add(parent.Literals[literal]); } } routeValues.Clear(); } }
private void ProcessSegment( RouteEndpoint endpoint, List <DfaNode> parents, List <DfaNode> nextParents, RoutePatternPathSegment segment) { for (var i = 0; i < parents.Count; i++) { var parent = parents[i]; var part = segment.Parts[0]; var parameterPart = part as RoutePatternParameterPart; if (segment.IsSimple && part is RoutePatternLiteralPart literalPart) { AddLiteralNode(_includeLabel, nextParents, parent, literalPart.Content); } else if (segment.IsSimple && parameterPart != null && parameterPart.IsCatchAll) { // A catch all should traverse all literal nodes as well as parameter nodes // we don't need to create the parameter node here because of ordering // all catchalls will be processed after all parameters. if (parent.Literals != null) { nextParents.AddRange(parent.Literals.Values); } if (parent.Parameters != null) { nextParents.Add(parent.Parameters); } // We also create a 'catchall' here. We don't do further traversals // on the catchall node because only catchalls can end up here. The // catchall node allows us to capture an unlimited amount of segments // and also to match a zero-length segment, which a parameter node // doesn't allow. if (parent.CatchAll == null) { parent.CatchAll = new DfaNode() { PathDepth = parent.PathDepth + 1, Label = _includeLabel ? parent.Label + "{*...}/" : null, }; // The catchall node just loops. parent.CatchAll.Parameters = parent.CatchAll; parent.CatchAll.CatchAll = parent.CatchAll; } parent.CatchAll.AddMatch(endpoint); } else if (segment.IsSimple && parameterPart != null && TryGetRequiredValue(endpoint.RoutePattern, parameterPart, out var requiredValue)) { // If the parameter has a matching required value, replace the parameter with the required value // as a literal. This should use the parameter's transformer (if present) // e.g. Template: Home/{action}, Required values: { action = "Index" }, Result: Home/Index AddRequiredLiteralValue(endpoint, nextParents, parent, parameterPart, requiredValue); } else if (segment.IsSimple && parameterPart != null) { if (parent.Parameters == null) { parent.Parameters = new DfaNode() { PathDepth = parent.PathDepth + 1, Label = _includeLabel ? parent.Label + "{...}/" : null, }; } if (parent.Literals != null) { // If the parameter contains constraints, we can be smarter about it and evaluate them while we build the tree. // If the literal doesn't match any of the constraints, we can prune the branch. // For example, for a parameter in a route {lang:length(2)} and a parent literal "ABC", we can check that "ABC" // doesn't meet the parameter constraint (length(2)) when building the tree, and avoid the extra nodes. if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicyReferences)) { // We filter out sibling literals that don't match one of the constraints in the segment to avoid adding nodes to the DFA // that will never match a route and which will result in a much higher memory usage. AddParentsWithMatchingLiteralConstraints(nextParents, parent, parameterPart, parameterPolicyReferences); } else { // This means the current parameter we are evaluating doesn't contain any constraint, so we need to // traverse all literal nodes as well as the parameter node. nextParents.AddRange(parent.Literals.Values); } } nextParents.Add(parent.Parameters); } else { // Complex segment - we treat these are parameters here and do the // expensive processing later. We don't want to spend time processing // complex segments unless they are the best match, and treating them // like parameters in the DFA allows us to do just that. if (parent.Parameters == null) { parent.Parameters = new DfaNode() { PathDepth = parent.PathDepth + 1, Label = _includeLabel ? parent.Label + "{...}/" : null, }; } if (parent.Literals != null) { // For a complex segment like this, we can evaluate the literals and avoid adding extra nodes to // the tree on cases where the literal won't ever be able to match the complex parameter. // For example, if we have a complex parameter {a}-{b}.{c?} and a literal "Hello" we can guarantee // that it will never be a match. // We filter out sibling literals that don't match the complex parameter segment to avoid adding nodes to the DFA // that will never match a route and which will result in a much higher memory usage. AddParentsMatchingComplexSegment(endpoint, nextParents, segment, parent, parameterPart); } nextParents.Add(parent.Parameters); } } }
private bool SavePathSegmentsAsValues(int index, DispatcherValueCollection values, StringSegment requestSegment, RoutePatternPathSegment pathSegment) { if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameter parameter && parameter.IsCatchAll) { // A catch-all captures til the end of the string. var captured = requestSegment.Buffer.Substring(requestSegment.Offset); if (captured.Length > 0) { values[parameter.Name] = captured; } else { // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue. values[parameter.Name] = _defaultValues[index]; } // A catch-all has to be the last part, so we're done. return(true); }
private static bool IsSegmentValid(Context context, List <RoutePatternPart> parts) { // If a segment has multiple parts, then it can't contain a catch all. for (var i = 0; i < parts.Count; i++) { var part = parts[i]; if (part is RoutePatternParameterPart parameter && parameter.IsCatchAll && parts.Count > 1) { context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment; return(false); } } // if a segment has multiple parts, then only the last one parameter can be optional // if it is following a optional separator. for (var i = 0; i < parts.Count; i++) { var part = parts[i]; if (part is RoutePatternParameterPart parameter && parameter.IsOptional && parts.Count > 1) { // This optional parameter is the last part in the segment if (i == parts.Count - 1) { var previousPart = parts[i - 1]; if (!previousPart.IsLiteral && !previousPart.IsSeparator) { // The optional parameter is preceded by something that is not a literal or separator // Example of error message: // "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded // by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter. context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod( RoutePatternPathSegment.DebuggerToString(parts), parameter.Name, parts[i - 1].DebuggerToString()); return(false); } else if (previousPart is RoutePatternLiteralPart literal && literal.Content != PeriodString) { // The optional parameter is preceded by a literal other than period. // Example of error message: // "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded // by an invalid segment '-'. Only a period (.) can precede an optional parameter. context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod( RoutePatternPathSegment.DebuggerToString(parts), parameter.Name, parts[i - 1].DebuggerToString()); return(false); } parts[i - 1] = RoutePatternFactory.SeparatorPart(((RoutePatternLiteralPart)previousPart).Content); } else { // This optional parameter is not the last one in the segment // Example: // An optional parameter must be at the end of the segment. In the segment '{RouteValue?})', // optional parameter 'RouteValue' is followed by ')' context.Error = Resources.FormatTemplateRoute_OptionalParameterHasTobeTheLast( RoutePatternPathSegment.DebuggerToString(parts), parameter.Name, parts[i + 1].DebuggerToString()); return(false); } } } // A segment cannot contain two consecutive parameters var isLastSegmentParameter = false; for (var i = 0; i < parts.Count; i++) { var part = parts[i]; if (part.IsParameter && isLastSegmentParameter) { context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; return(false); } isLastSegmentParameter = part.IsParameter; } return(true); }
public TemplateSegment(RoutePatternPathSegment other) { Parts = new List <TemplatePart>(other.Parts.Select(s => new TemplatePart(s))); }