Пример #1
0
        public void SubstituteRequiredValues_AllowRequiredValueAnyForParameter()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = RoutePattern.RequiredValueAny, };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.Defaults.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair <string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair <string, object>("controller", "Home"), kvp)); // default is preserved

            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair <string, object>("controller", RoutePattern.RequiredValueAny), kvp));
        }
Пример #2
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 seperator.
            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);
        }
Пример #3
0
        private static bool ParseLiteral(Context context, List <RoutePatternPart> parts)
        {
            context.Mark();

            while (true)
            {
                if (context.Current == Separator)
                {
                    // End of the segment
                    break;
                }
                else if (context.Current == OpenBrace)
                {
                    if (!context.MoveNext())
                    {
                        // This is a dangling open-brace, which is not allowed
                        context.Error = Resources.TemplateRoute_MismatchedParameter;
                        return(false);
                    }

                    if (context.Current == OpenBrace)
                    {
                        // This is an 'escaped' brace in a literal, like "{{foo" - keep going.
                    }
                    else
                    {
                        // We've just seen the start of a parameter, so back up.
                        context.Back();
                        break;
                    }
                }
                else if (context.Current == CloseBrace)
                {
                    if (!context.MoveNext())
                    {
                        // This is a dangling close-brace, which is not allowed
                        context.Error = Resources.TemplateRoute_MismatchedParameter;
                        return(false);
                    }

                    if (context.Current == CloseBrace)
                    {
                        // This is an 'escaped' brace in a literal, like "{{foo" - keep going.
                    }
                    else
                    {
                        // This is an unbalanced close-brace, which is not allowed
                        context.Error = Resources.TemplateRoute_MismatchedParameter;
                        return(false);
                    }
                }

                if (!context.MoveNext())
                {
                    break;
                }
            }

            var encoded = context.Capture();
            var decoded = encoded.Replace("}}", "}").Replace("{{", "{");

            if (IsValidLiteral(context, decoded))
            {
                parts.Add(RoutePatternFactory.LiteralPart(decoded));
                return(true);
            }
            else
            {
                return(false);
            }
        }
Пример #4
0
        private static ConstraintParseResults ParseConstraints(
            string text,
            string parameterName,
            int currentIndex,
            int endIndex)
        {
            var constraints = new List <RoutePatternConstraintReference>();
            var state       = ParseState.Start;
            var startIndex  = currentIndex;

            do
            {
                var currentChar = currentIndex > endIndex ? null : (char?)text[currentIndex];
                switch (state)
                {
                case ParseState.Start:
                    switch (currentChar)
                    {
                    case null:
                        state = ParseState.End;
                        break;

                    case ':':
                        state      = ParseState.ParsingName;
                        startIndex = currentIndex + 1;
                        break;

                    case '(':
                        state = ParseState.InsideParenthesis;
                        break;

                    case '=':
                        state = ParseState.End;
                        currentIndex--;
                        break;
                    }
                    break;

                case ParseState.InsideParenthesis:
                    switch (currentChar)
                    {
                    case null:
                        state = ParseState.End;
                        var constraintText = text.Substring(startIndex, currentIndex - startIndex);
                        constraints.Add(RoutePatternFactory.Constraint(constraintText));
                        break;

                    case ')':
                        // Only consume a ')' token if
                        // (a) it is the last token
                        // (b) the next character is the start of the new constraint ':'
                        // (c) the next character is the start of the default value.

                        var nextChar = currentIndex + 1 > endIndex ? null : (char?)text[currentIndex + 1];
                        switch (nextChar)
                        {
                        case null:
                            state          = ParseState.End;
                            constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));
                            break;

                        case ':':
                            state          = ParseState.Start;
                            constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));
                            startIndex = currentIndex + 1;
                            break;

                        case '=':
                            state          = ParseState.End;
                            constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));
                            break;
                        }
                        break;

                    case ':':
                    case '=':
                        // In the original implementation, the Regex would've backtracked if it encountered an
                        // unbalanced opening bracket followed by (not necessarily immediatiely) a delimiter.
                        // Simply verifying that the parantheses will eventually be closed should suffice to
                        // determine if the terminator needs to be consumed as part of the current constraint
                        // specification.
                        var indexOfClosingParantheses = text.IndexOf(')', currentIndex + 1);
                        if (indexOfClosingParantheses == -1)
                        {
                            constraintText = text.Substring(startIndex, currentIndex - startIndex);
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));

                            if (currentChar == ':')
                            {
                                state      = ParseState.ParsingName;
                                startIndex = currentIndex + 1;
                            }
                            else
                            {
                                state = ParseState.End;
                                currentIndex--;
                            }
                        }
                        else
                        {
                            currentIndex = indexOfClosingParantheses;
                        }

                        break;
                    }
                    break;

                case ParseState.ParsingName:
                    switch (currentChar)
                    {
                    case null:
                        state = ParseState.End;
                        var constraintText = text.Substring(startIndex, currentIndex - startIndex);
                        if (constraintText.Length > 0)
                        {
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));
                        }
                        break;

                    case ':':
                        constraintText = text.Substring(startIndex, currentIndex - startIndex);
                        if (constraintText.Length > 0)
                        {
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));
                        }
                        startIndex = currentIndex + 1;
                        break;

                    case '(':
                        state = ParseState.InsideParenthesis;
                        break;

                    case '=':
                        state          = ParseState.End;
                        constraintText = text.Substring(startIndex, currentIndex - startIndex);
                        if (constraintText.Length > 0)
                        {
                            constraints.Add(RoutePatternFactory.Constraint(constraintText));
                        }
                        currentIndex--;
                        break;
                    }
                    break;
                }

                currentIndex++;
            } while (state != ParseState.End);

            return(new ConstraintParseResults(currentIndex, constraints));
        }