Exemplo n.º 1
0
        // 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();
            }
        }
Exemplo n.º 2
0
 private static void WriteString(StringBuilder sb, RoutePatternPathSegment segment)
 {
     for (int i = 0; i < segment.Parts.Count; i++)
     {
         WriteString(sb, segment.Parts[i]);
     }
 }
Exemplo n.º 3
0
        // 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);
            }
        }
Exemplo n.º 4
0
        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());
        }
Exemplo n.º 6
0
        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]));
            }
        }
Exemplo n.º 7
0
        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);
        }
Exemplo n.º 8
0
        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();
            }
        }
Exemplo n.º 9
0
            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();
                }
            }
Exemplo n.º 10
0
            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);
                    }
                }
            }
Exemplo n.º 11
0
        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);
            }
Exemplo n.º 12
0
    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);
    }
Exemplo n.º 13
0
 public TemplateSegment(RoutePatternPathSegment other)
 {
     Parts = new List <TemplatePart>(other.Parts.Select(s => new TemplatePart(s)));
 }