parent
927f8ed3d2
commit
3a5cd6dd25
|
|
@ -9,7 +9,6 @@ using BenchmarkDotNet.Attributes;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
|
@ -35,11 +34,11 @@ namespace Microsoft.AspNetCore.Dispatcher.Performance
|
|||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default)),
|
||||
new DefaultInlineConstraintResolver(new OptionsManager<RouteOptions>(new OptionsFactory<RouteOptions>(Enumerable.Empty<IConfigureOptions<RouteOptions>>(), Enumerable.Empty<IPostConfigureOptions<RouteOptions>>()))));
|
||||
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/search/{term}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("admin/users/{id}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("admin/users/{id}/manage"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets/search/{term}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("admin/users/{id}"), "default", 0);
|
||||
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("admin/users/{id}/manage"), "default", 0);
|
||||
|
||||
_treeRouter = treeBuilder.Build();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The parsed representation of an inline constraint in a route parameter.
|
||||
/// </summary>
|
||||
public class InlineConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="InlineConstraint"/>.
|
||||
/// </summary>
|
||||
/// <param name="constraint">The constraint text.</param>
|
||||
public InlineConstraint(string constraint)
|
||||
{
|
||||
if (constraint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraint));
|
||||
}
|
||||
|
||||
Constraint = constraint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the constraint text.
|
||||
/// </summary>
|
||||
public string Constraint { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public static class InlineRouteParameterParser
|
||||
{
|
||||
public static TemplatePart ParseRouteParameter(string routeParameter)
|
||||
{
|
||||
if (routeParameter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeParameter));
|
||||
}
|
||||
|
||||
if (routeParameter.Length == 0)
|
||||
{
|
||||
return TemplatePart.CreateParameter(
|
||||
name: string.Empty,
|
||||
isCatchAll: false,
|
||||
isOptional: false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null);
|
||||
}
|
||||
|
||||
var startIndex = 0;
|
||||
var endIndex = routeParameter.Length - 1;
|
||||
|
||||
var isCatchAll = false;
|
||||
var isOptional = false;
|
||||
|
||||
if (routeParameter[0] == '*')
|
||||
{
|
||||
isCatchAll = true;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (routeParameter[endIndex] == '?')
|
||||
{
|
||||
isOptional = true;
|
||||
endIndex--;
|
||||
}
|
||||
|
||||
var currentIndex = startIndex;
|
||||
|
||||
// Parse parameter name
|
||||
var parameterName = string.Empty;
|
||||
|
||||
while (currentIndex <= endIndex)
|
||||
{
|
||||
var currentChar = routeParameter[currentIndex];
|
||||
|
||||
if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
|
||||
{
|
||||
// Parameter names are allowed to start with delimiters used to denote constraints or default values.
|
||||
// i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
|
||||
// specifications.
|
||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
|
||||
// Roll the index back and move to the constraint parsing stage.
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
else if (currentIndex == endIndex)
|
||||
{
|
||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
var parseResults = ParseConstraints(routeParameter, currentIndex, endIndex);
|
||||
currentIndex = parseResults.CurrentIndex;
|
||||
|
||||
string defaultValue = null;
|
||||
if (currentIndex <= endIndex &&
|
||||
routeParameter[currentIndex] == '=')
|
||||
{
|
||||
defaultValue = routeParameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
||||
}
|
||||
|
||||
return TemplatePart.CreateParameter(parameterName,
|
||||
isCatchAll,
|
||||
isOptional,
|
||||
defaultValue,
|
||||
parseResults.Constraints);
|
||||
}
|
||||
|
||||
private static ConstraintParseResults ParseConstraints(
|
||||
string routeParameter,
|
||||
int currentIndex,
|
||||
int endIndex)
|
||||
{
|
||||
var inlineConstraints = new List<InlineConstraint>();
|
||||
var state = ParseState.Start;
|
||||
var startIndex = currentIndex;
|
||||
do
|
||||
{
|
||||
var currentChar = currentIndex > endIndex ? null : (char?)routeParameter[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 = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(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?)routeParameter[currentIndex + 1];
|
||||
switch (nextChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ':':
|
||||
state = ParseState.Start;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(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 = routeParameter.IndexOf(')', currentIndex + 1);
|
||||
if (indexOfClosingParantheses == -1)
|
||||
{
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(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 = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ':':
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '(':
|
||||
state = ParseState.InsideParenthesis;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
} while (state != ParseState.End);
|
||||
|
||||
return new ConstraintParseResults
|
||||
{
|
||||
CurrentIndex = currentIndex,
|
||||
Constraints = inlineConstraints
|
||||
};
|
||||
}
|
||||
|
||||
private enum ParseState
|
||||
{
|
||||
Start,
|
||||
ParsingName,
|
||||
InsideParenthesis,
|
||||
End
|
||||
}
|
||||
|
||||
private struct ConstraintParseResults
|
||||
{
|
||||
public int CurrentIndex;
|
||||
|
||||
public IEnumerable<InlineConstraint> Constraints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,202 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
internal static string FormatAmbiguousEndpoints(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousEndpoints"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveCatchAllInMultiSegment
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveCatchAllInMultiSegment()
|
||||
=> GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveParameters
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveParameters()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveSeparators
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveSeparators()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllCannotBeOptional
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllCannotBeOptional()
|
||||
=> GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllMustBeLast
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllMustBeLast()
|
||||
=> GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidLiteral
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidLiteral");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidLiteral(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidLiteral"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidParameterName
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidParameterName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidParameterName(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '/' or '~' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidRouteTemplate
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '/' or '~' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidRouteTemplate()
|
||||
=> GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_MismatchedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_MismatchedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_MismatchedParameter()
|
||||
=> GetString("TemplateRoute_MismatchedParameter");
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalCannotHaveDefaultValue
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalCannotHaveDefaultValue()
|
||||
=> GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterCanbBePrecededByPeriod
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterHasTobeTheLast
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterHasTobeTheLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterHasTobeTheLast(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterHasTobeTheLast"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_RepeatedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_RepeatedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_RepeatedParameter(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_RepeatedParameter"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_UnescapedBrace
|
||||
{
|
||||
get => GetString("TemplateRoute_UnescapedBrace");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_UnescapedBrace()
|
||||
=> GetString("TemplateRoute_UnescapedBrace");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -121,4 +121,46 @@
|
|||
<value>Multiple endpoints matched. The following endpoints matched the request:{0}{0}{1}</value>
|
||||
<comment>0 is the newline - 1 is a newline separate list of action display names</comment>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
|
||||
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveParameters" xml:space="preserve">
|
||||
<value>A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveSeparators" xml:space="preserve">
|
||||
<value>The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||
<value>A catch-all parameter cannot be marked optional.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllMustBeLast" xml:space="preserve">
|
||||
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidLiteral" xml:space="preserve">
|
||||
<value>The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidParameterName" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve">
|
||||
<value>The route template cannot start with a '/' or '~' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_MismatchedParameter" xml:space="preserve">
|
||||
<value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalCannotHaveDefaultValue" xml:space="preserve">
|
||||
<value>An optional parameter cannot have default value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterCanbBePrecededByPeriod" xml:space="preserve">
|
||||
<value>In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterHasTobeTheLast" xml:space="preserve">
|
||||
<value>An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_RepeatedParameter" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' appears more than one time in the route template.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_UnescapedBrace" xml:space="preserve">
|
||||
<value>In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class RouteTemplate
|
||||
{
|
||||
private const string SeparatorString = "/";
|
||||
|
||||
public RouteTemplate(string template, List<TemplateSegment> segments)
|
||||
{
|
||||
if (segments == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(segments));
|
||||
}
|
||||
|
||||
TemplateText = template;
|
||||
|
||||
Segments = segments;
|
||||
|
||||
Parameters = new List<TemplatePart>();
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = Segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsParameter)
|
||||
{
|
||||
Parameters.Add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string TemplateText { get; }
|
||||
|
||||
public IList<TemplatePart> Parameters { get; }
|
||||
|
||||
public IList<TemplateSegment> Segments { get; }
|
||||
|
||||
public TemplateSegment GetSegment(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
return index >= Segments.Count ? null : Segments[index];
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Join(SeparatorString, Segments.Select(s => s.DebuggerToString()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter matching the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter to match.</param>
|
||||
/// <returns>The matching parameter or <c>null</c> if no parameter matches the given name.</returns>
|
||||
public TemplatePart GetParameter(string name)
|
||||
{
|
||||
for (var i = 0; i < Parameters.Count; i++)
|
||||
{
|
||||
var parameter = Parameters[i];
|
||||
if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,526 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public static class TemplateParser
|
||||
{
|
||||
private const char Separator = '/';
|
||||
private const char OpenBrace = '{';
|
||||
private const char CloseBrace = '}';
|
||||
private const char EqualsSign = '=';
|
||||
private const char QuestionMark = '?';
|
||||
private const char Asterisk = '*';
|
||||
private const string PeriodString = ".";
|
||||
|
||||
public static RouteTemplate Parse(string routeTemplate)
|
||||
{
|
||||
if (routeTemplate == null)
|
||||
{
|
||||
routeTemplate = String.Empty;
|
||||
}
|
||||
|
||||
if (IsInvalidRouteTemplate(routeTemplate))
|
||||
{
|
||||
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate));
|
||||
}
|
||||
|
||||
var context = new TemplateParserContext(routeTemplate);
|
||||
var segments = new List<TemplateSegment>();
|
||||
|
||||
while (context.Next())
|
||||
{
|
||||
if (context.Current == Separator)
|
||||
{
|
||||
// If we get here is means that there's a consecutive '/' character.
|
||||
// Templates don't start with a '/' and parsing a segment consumes the separator.
|
||||
throw new ArgumentException(Resources.TemplateRoute_CannotHaveConsecutiveSeparators,
|
||||
nameof(routeTemplate));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ParseSegment(context, segments))
|
||||
{
|
||||
throw new ArgumentException(context.Error, nameof(routeTemplate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsAllValid(context, segments))
|
||||
{
|
||||
return new RouteTemplate(routeTemplate, segments);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(context.Error, nameof(routeTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseSegment(TemplateParserContext context, List<TemplateSegment> segments)
|
||||
{
|
||||
Debug.Assert(context != null);
|
||||
Debug.Assert(segments != null);
|
||||
|
||||
var segment = new TemplateSegment();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// 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"
|
||||
context.Back();
|
||||
if (!ParseLiteral(context, segment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the inside of a parameter
|
||||
if (!ParseParameter(context, segment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (context.Current == Separator)
|
||||
{
|
||||
// We've reached the end of the segment
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ParseLiteral(context, segment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.Next())
|
||||
{
|
||||
// We've reached the end of the string
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSegmentValid(context, segment))
|
||||
{
|
||||
segments.Add(segment);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment)
|
||||
{
|
||||
context.Mark();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is an open brace inside of a parameter, it has to be escaped
|
||||
if (context.Next())
|
||||
{
|
||||
if (context.Current != OpenBrace)
|
||||
{
|
||||
// If we see something like "{p1:regex(^\d{3", we will come here.
|
||||
context.Error = Resources.TemplateRoute_UnescapedBrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
// Example: "{p1:regex(^\d{"
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
// When we encounter Closed brace here, it either means end of the parameter or it is a closed
|
||||
// brace in the parameter, in that case it needs to be escaped.
|
||||
// Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
|
||||
if (!context.Next())
|
||||
{
|
||||
// This is the end of the string -and we have a valid parameter
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.Current == CloseBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a parameter name
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the end of the parameter
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.Next())
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var rawParameter = context.Capture();
|
||||
var decoded = rawParameter.Replace("}}", "}").Replace("{{", "{");
|
||||
|
||||
// At this point, we need to parse the raw name for inline constraint,
|
||||
// default values and optional parameters.
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded);
|
||||
|
||||
if (templatePart.IsCatchAll && templatePart.IsOptional)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (templatePart.IsOptional && templatePart.DefaultValue != null)
|
||||
{
|
||||
// Cannot be optional and have a default value.
|
||||
// The only way to declare an optional parameter is to have a ? at the end,
|
||||
// hence we cannot have both default value and optional parameter within the template.
|
||||
// A workaround is to add it as a separate entry in the defaults argument.
|
||||
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameterName = templatePart.Name;
|
||||
if (IsValidParameterName(context, parameterName))
|
||||
{
|
||||
segment.Parts.Add(templatePart);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseLiteral(TemplateParserContext context, TemplateSegment segment)
|
||||
{
|
||||
context.Mark();
|
||||
|
||||
string encoded;
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == Separator)
|
||||
{
|
||||
encoded = context.Capture();
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
else if (context.Current == OpenBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// 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 and return
|
||||
context.Back();
|
||||
encoded = context.Capture();
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// 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.Next())
|
||||
{
|
||||
encoded = context.Capture();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
|
||||
if (IsValidLiteral(context, decoded))
|
||||
{
|
||||
segment.Parts.Add(TemplatePart.CreateLiteral(decoded));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllValid(TemplateParserContext context, List<TemplateSegment> segments)
|
||||
{
|
||||
// A catch-all parameter must be the last part of the last segment
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsParameter &&
|
||||
part.IsCatchAll &&
|
||||
(i != segments.Count - 1 || j != segment.Parts.Count - 1))
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment)
|
||||
{
|
||||
// If a segment has multiple parts, then it can't contain a catch all.
|
||||
for (var i = 0; i < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
if (part.IsParameter && part.IsCatchAll && segment.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 < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
|
||||
if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
|
||||
{
|
||||
// This optional parameter is the last part in the segment
|
||||
if (i == segment.Parts.Count - 1)
|
||||
{
|
||||
if (!segment.Parts[i - 1].IsLiteral)
|
||||
{
|
||||
// The optional parameter is preceded by something that is not a literal.
|
||||
// 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 = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||
segment.DebuggerToString(),
|
||||
part.Name,
|
||||
segment.Parts[i - 1].DebuggerToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (segment.Parts[i - 1].Text != 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 = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||
segment.DebuggerToString(),
|
||||
part.Name,
|
||||
segment.Parts[i - 1].Text);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
segment.Parts[i - 1].IsOptionalSeperator = true;
|
||||
}
|
||||
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 ')'
|
||||
var nextPart = segment.Parts[i + 1];
|
||||
var invalidPartText = nextPart.IsParameter ? nextPart.Name : nextPart.Text;
|
||||
|
||||
context.Error = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterHasTobeTheLast,
|
||||
segment.DebuggerToString(),
|
||||
segment.Parts[i].Name,
|
||||
invalidPartText);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A segment cannot contain two consecutive parameters
|
||||
var isLastSegmentParameter = false;
|
||||
for (var i = 0; i < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
if (part.IsParameter && isLastSegmentParameter)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
|
||||
return false;
|
||||
}
|
||||
|
||||
isLastSegmentParameter = part.IsParameter;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidParameterName(TemplateParserContext context, string parameterName)
|
||||
{
|
||||
if (parameterName.Length == 0)
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_InvalidParameterName, parameterName);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameterName.Length; i++)
|
||||
{
|
||||
var c = parameterName[i];
|
||||
if (c == Separator || c == OpenBrace || c == CloseBrace || c == QuestionMark || c == Asterisk)
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_InvalidParameterName, parameterName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.ParameterNames.Add(parameterName))
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_RepeatedParameter, parameterName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidLiteral(TemplateParserContext context, string literal)
|
||||
{
|
||||
Debug.Assert(context != null);
|
||||
Debug.Assert(literal != null);
|
||||
|
||||
if (literal.IndexOf(QuestionMark) != -1)
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_InvalidLiteral, literal);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsInvalidRouteTemplate(string routeTemplate)
|
||||
{
|
||||
return routeTemplate.StartsWith("~", StringComparison.Ordinal) ||
|
||||
routeTemplate.StartsWith("/", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private class TemplateParserContext
|
||||
{
|
||||
private readonly string _template;
|
||||
private int _index;
|
||||
private int? _mark;
|
||||
|
||||
private HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public TemplateParserContext(string template)
|
||||
{
|
||||
Debug.Assert(template != null);
|
||||
_template = template;
|
||||
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public char Current
|
||||
{
|
||||
get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public HashSet<string> ParameterNames
|
||||
{
|
||||
get { return _parameterNames; }
|
||||
}
|
||||
|
||||
public bool Back()
|
||||
{
|
||||
return --_index >= 0;
|
||||
}
|
||||
|
||||
public bool Next()
|
||||
{
|
||||
return ++_index < _template.Length;
|
||||
}
|
||||
|
||||
public void Mark()
|
||||
{
|
||||
_mark = _index;
|
||||
}
|
||||
|
||||
public string Capture()
|
||||
{
|
||||
if (_mark.HasValue)
|
||||
{
|
||||
var value = _template.Substring(_mark.Value, _index - _mark.Value);
|
||||
_mark = null;
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class TemplatePart
|
||||
{
|
||||
public static TemplatePart CreateLiteral(string text)
|
||||
{
|
||||
return new TemplatePart()
|
||||
{
|
||||
IsLiteral = true,
|
||||
Text = text,
|
||||
};
|
||||
}
|
||||
|
||||
public static TemplatePart CreateParameter(string name,
|
||||
bool isCatchAll,
|
||||
bool isOptional,
|
||||
object defaultValue,
|
||||
IEnumerable<InlineConstraint> inlineConstraints)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return new TemplatePart()
|
||||
{
|
||||
IsParameter = true,
|
||||
Name = name,
|
||||
IsCatchAll = isCatchAll,
|
||||
IsOptional = isOptional,
|
||||
DefaultValue = defaultValue,
|
||||
InlineConstraints = inlineConstraints ?? Enumerable.Empty<InlineConstraint>(),
|
||||
};
|
||||
}
|
||||
|
||||
public bool IsCatchAll { get; private set; }
|
||||
public bool IsLiteral { get; private set; }
|
||||
public bool IsParameter { get; private set; }
|
||||
public bool IsOptional { get; private set; }
|
||||
public bool IsOptionalSeperator { get; set; }
|
||||
public string Name { get; private set; }
|
||||
public string Text { get; private set; }
|
||||
public object DefaultValue { get; private set; }
|
||||
public IEnumerable<InlineConstraint> InlineConstraints { get; private set; }
|
||||
|
||||
internal string DebuggerToString()
|
||||
{
|
||||
if (IsParameter)
|
||||
{
|
||||
return "{" + (IsCatchAll ? "*" : string.Empty) + Name + (IsOptional ? "?" : string.Empty) + "}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class TemplateSegment
|
||||
{
|
||||
public bool IsSimple => Parts.Count == 1;
|
||||
|
||||
public List<TemplatePart> Parts { get; } = new List<TemplatePart>();
|
||||
|
||||
internal string DebuggerToString()
|
||||
{
|
||||
return string.Join(string.Empty, Parts.Select(p => p.DebuggerToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
|||
throw new InvalidOperationException("Can't find address");
|
||||
}
|
||||
|
||||
var binder = new TemplateBinder(_urlEncoder, _pool, TemplateParser.Parse(address.Template), new RouteValueDictionary());
|
||||
var binder = new TemplateBinder(_urlEncoder, _pool, Template.TemplateParser.Parse(address.Template), new RouteValueDictionary());
|
||||
|
||||
var feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
|||
var entries = new List<InboundRouteEntry>();
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var template = TemplateParser.Parse(group.Key.RouteTemplate);
|
||||
var template = Template.TemplateParser.Parse(group.Key.RouteTemplate);
|
||||
|
||||
var defaults = new RouteValueDictionary();
|
||||
for (var i = 0; i < template.Parameters.Count; i++)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
|
|
@ -16,228 +15,8 @@ namespace Microsoft.AspNetCore.Routing
|
|||
throw new ArgumentNullException(nameof(routeParameter));
|
||||
}
|
||||
|
||||
if (routeParameter.Length == 0)
|
||||
{
|
||||
return TemplatePart.CreateParameter(
|
||||
name: string.Empty,
|
||||
isCatchAll: false,
|
||||
isOptional: false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null);
|
||||
}
|
||||
|
||||
var startIndex = 0;
|
||||
var endIndex = routeParameter.Length - 1;
|
||||
|
||||
var isCatchAll = false;
|
||||
var isOptional = false;
|
||||
|
||||
if (routeParameter[0] == '*')
|
||||
{
|
||||
isCatchAll = true;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (routeParameter[endIndex] == '?')
|
||||
{
|
||||
isOptional = true;
|
||||
endIndex--;
|
||||
}
|
||||
|
||||
var currentIndex = startIndex;
|
||||
|
||||
// Parse parameter name
|
||||
var parameterName = string.Empty;
|
||||
|
||||
while (currentIndex <= endIndex)
|
||||
{
|
||||
var currentChar = routeParameter[currentIndex];
|
||||
|
||||
if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
|
||||
{
|
||||
// Parameter names are allowed to start with delimiters used to denote constraints or default values.
|
||||
// i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
|
||||
// specifications.
|
||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
|
||||
// Roll the index back and move to the constraint parsing stage.
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
else if (currentIndex == endIndex)
|
||||
{
|
||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
var parseResults = ParseConstraints(routeParameter, currentIndex, endIndex);
|
||||
currentIndex = parseResults.CurrentIndex;
|
||||
|
||||
string defaultValue = null;
|
||||
if (currentIndex <= endIndex &&
|
||||
routeParameter[currentIndex] == '=')
|
||||
{
|
||||
defaultValue = routeParameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
||||
}
|
||||
|
||||
return TemplatePart.CreateParameter(parameterName,
|
||||
isCatchAll,
|
||||
isOptional,
|
||||
defaultValue,
|
||||
parseResults.Constraints);
|
||||
}
|
||||
|
||||
private static ConstraintParseResults ParseConstraints(
|
||||
string routeParameter,
|
||||
int currentIndex,
|
||||
int endIndex)
|
||||
{
|
||||
var inlineConstraints = new List<InlineConstraint>();
|
||||
var state = ParseState.Start;
|
||||
var startIndex = currentIndex;
|
||||
do
|
||||
{
|
||||
var currentChar = currentIndex > endIndex ? null : (char?)routeParameter[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 = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(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?)routeParameter[currentIndex + 1];
|
||||
switch (nextChar)
|
||||
{
|
||||
case null:
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ':':
|
||||
state = ParseState.Start;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||
inlineConstraints.Add(new InlineConstraint(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 = routeParameter.IndexOf(')', currentIndex + 1);
|
||||
if (indexOfClosingParantheses == -1)
|
||||
{
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(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 = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
break;
|
||||
case ':':
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
startIndex = currentIndex + 1;
|
||||
break;
|
||||
case '(':
|
||||
state = ParseState.InsideParenthesis;
|
||||
break;
|
||||
case '=':
|
||||
state = ParseState.End;
|
||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
||||
currentIndex--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
|
||||
} while (state != ParseState.End);
|
||||
|
||||
return new ConstraintParseResults
|
||||
{
|
||||
CurrentIndex = currentIndex,
|
||||
Constraints = inlineConstraints
|
||||
};
|
||||
}
|
||||
|
||||
private enum ParseState
|
||||
{
|
||||
Start,
|
||||
ParsingName,
|
||||
InsideParenthesis,
|
||||
End
|
||||
}
|
||||
|
||||
private struct ConstraintParseResults
|
||||
{
|
||||
public int CurrentIndex;
|
||||
|
||||
public IEnumerable<InlineConstraint> Constraints;
|
||||
var inner = AspNetCore.Dispatcher.InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
||||
return new TemplatePart(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,174 +122,6 @@ namespace Microsoft.AspNetCore.Routing
|
|||
internal static string FormatDefaultInlineConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_TypeNotConstraint"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveCatchAllInMultiSegment
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveCatchAllInMultiSegment()
|
||||
=> GetString("TemplateRoute_CannotHaveCatchAllInMultiSegment");
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveParameters
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveParameters()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveParameters");
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveConsecutiveSeparators
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveConsecutiveSeparators()
|
||||
=> GetString("TemplateRoute_CannotHaveConsecutiveSeparators");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllCannotBeOptional
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter cannot be marked optional.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllCannotBeOptional()
|
||||
=> GetString("TemplateRoute_CatchAllCannotBeOptional");
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalCannotHaveDefaultValue
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter cannot have default value.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalCannotHaveDefaultValue()
|
||||
=> GetString("TemplateRoute_OptionalCannotHaveDefaultValue");
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CatchAllMustBeLast
|
||||
{
|
||||
get => GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A catch-all parameter can only appear as the last segment of the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CatchAllMustBeLast()
|
||||
=> GetString("TemplateRoute_CatchAllMustBeLast");
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidLiteral
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidLiteral");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidLiteral(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidLiteral"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidParameterName
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidParameterName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidParameterName(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '/' or '~' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_InvalidRouteTemplate
|
||||
{
|
||||
get => GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route template cannot start with a '/' or '~' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_InvalidRouteTemplate()
|
||||
=> GetString("TemplateRoute_InvalidRouteTemplate");
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_MismatchedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_MismatchedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_MismatchedParameter()
|
||||
=> GetString("TemplateRoute_MismatchedParameter");
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_RepeatedParameter
|
||||
{
|
||||
get => GetString("TemplateRoute_RepeatedParameter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter name '{0}' appears more than one time in the route template.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_RepeatedParameter(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_RepeatedParameter"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||
/// </summary>
|
||||
|
|
@ -318,48 +150,6 @@ namespace Microsoft.AspNetCore.Routing
|
|||
internal static string FormatRouteConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_CouldNotResolveConstraint"), p0, p1, p2, p3);
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_UnescapedBrace
|
||||
{
|
||||
get => GetString("TemplateRoute_UnescapedBrace");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_UnescapedBrace()
|
||||
=> GetString("TemplateRoute_UnescapedBrace");
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterCanbBePrecededByPeriod
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterCanbBePrecededByPeriod"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_OptionalParameterHasTobeTheLast
|
||||
{
|
||||
get => GetString("TemplateRoute_OptionalParameterHasTobeTheLast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_OptionalParameterHasTobeTheLast(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_OptionalParameterHasTobeTheLast"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// Two or more routes named '{0}' have different templates.
|
||||
/// </summary>
|
||||
|
|
@ -388,20 +178,6 @@ namespace Microsoft.AspNetCore.Routing
|
|||
internal static string FormatUnableToFindServices(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred while creating the route with name '{0}' and template '{1}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_Exception
|
||||
{
|
||||
get => GetString("TemplateRoute_Exception");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred while creating the route with name '{0}' and template '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_Exception(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_Exception"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
|
||||
/// </summary>
|
||||
|
|
@ -416,6 +192,34 @@ namespace Microsoft.AspNetCore.Routing
|
|||
internal static string FormatDispatcherFeatureIsRequired(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherFeatureIsRequired"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly
|
||||
{
|
||||
get => GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred while creating the route with name '{0}' and template '{1}'.
|
||||
/// </summary>
|
||||
internal static string TemplateRoute_Exception
|
||||
{
|
||||
get => GetString("TemplateRoute_Exception");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred while creating the route with name '{0}' and template '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateRoute_Exception(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_Exception"), p0, p1);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -141,67 +141,25 @@
|
|||
<data name="DefaultInlineConstraintResolver_TypeNotConstraint" xml:space="preserve">
|
||||
<value>The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
|
||||
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly" xml:space="preserve">
|
||||
<value>The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveParameters" xml:space="preserve">
|
||||
<value>A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveConsecutiveSeparators" xml:space="preserve">
|
||||
<value>The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllCannotBeOptional" xml:space="preserve">
|
||||
<value>A catch-all parameter cannot be marked optional.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalCannotHaveDefaultValue" xml:space="preserve">
|
||||
<value>An optional parameter cannot have default value.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CatchAllMustBeLast" xml:space="preserve">
|
||||
<value>A catch-all parameter can only appear as the last segment of the route template.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidLiteral" xml:space="preserve">
|
||||
<value>The literal section '{0}' is invalid. Literal sections cannot contain the '?' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidParameterName" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_InvalidRouteTemplate" xml:space="preserve">
|
||||
<value>The route template cannot start with a '/' or '~' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_MismatchedParameter" xml:space="preserve">
|
||||
<value>There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_RepeatedParameter" xml:space="preserve">
|
||||
<value>The route parameter name '{0}' appears more than one time in the route template.</value>
|
||||
</data>
|
||||
<data name="RouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.</value>
|
||||
</data>
|
||||
<data name="RouteConstraintBuilder_CouldNotResolveConstraint" xml:space="preserve">
|
||||
<value>The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_UnescapedBrace" xml:space="preserve">
|
||||
<value>In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterCanbBePrecededByPeriod" xml:space="preserve">
|
||||
<value>In the segment '{0}', the optional parameter '{1}' is preceded by an invalid segment '{2}'. Only a period (.) can precede an optional parameter.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_OptionalParameterHasTobeTheLast" xml:space="preserve">
|
||||
<value>An optional parameter must be at the end of the segment. In the segment '{0}', optional parameter '{1}' is followed by '{2}'.</value>
|
||||
</data>
|
||||
<data name="AttributeRoute_DifferentLinkGenerationEntries_SameName" xml:space="preserve">
|
||||
<value>Two or more routes named '{0}' have different templates.</value>
|
||||
</data>
|
||||
<data name="UnableToFindServices" xml:space="preserve">
|
||||
<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_Exception" xml:space="preserve">
|
||||
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
|
||||
</data>
|
||||
<data name="DispatcherFeatureIsRequired" xml:space="preserve">
|
||||
<value>The '{0}' has no '{1}'. '{2}' requires a dispatcher.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly" xml:space="preserve">
|
||||
<value>The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.</value>
|
||||
</data>
|
||||
<data name="TemplateRoute_Exception" xml:space="preserve">
|
||||
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -24,6 +24,16 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
Constraint = constraint;
|
||||
}
|
||||
|
||||
public InlineConstraint(AspNetCore.Dispatcher.InlineConstraint constraint)
|
||||
{
|
||||
if (constraint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(constraint));
|
||||
}
|
||||
|
||||
Constraint = constraint.Constraint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the constraint text.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,25 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
{
|
||||
private const string SeparatorString = "/";
|
||||
|
||||
public RouteTemplate(AspNetCore.Dispatcher.RouteTemplate routeTemplate)
|
||||
{
|
||||
TemplateText = routeTemplate.TemplateText;
|
||||
Segments = new List<TemplateSegment>(routeTemplate.Segments.Select(p => new TemplateSegment(p)));
|
||||
Parameters = new List<TemplatePart>();
|
||||
for (var i = 0; i < Segments.Count; i++)
|
||||
{
|
||||
var segment = Segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsParameter)
|
||||
{
|
||||
Parameters.Add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RouteTemplate(string template, List<TemplateSegment> segments)
|
||||
{
|
||||
if (segments == null)
|
||||
|
|
|
|||
|
|
@ -1,526 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Template
|
||||
{
|
||||
public static class TemplateParser
|
||||
{
|
||||
private const char Separator = '/';
|
||||
private const char OpenBrace = '{';
|
||||
private const char CloseBrace = '}';
|
||||
private const char EqualsSign = '=';
|
||||
private const char QuestionMark = '?';
|
||||
private const char Asterisk = '*';
|
||||
private const string PeriodString = ".";
|
||||
|
||||
public static RouteTemplate Parse(string routeTemplate)
|
||||
{
|
||||
if (routeTemplate == null)
|
||||
{
|
||||
routeTemplate = String.Empty;
|
||||
throw new ArgumentNullException(routeTemplate);
|
||||
}
|
||||
|
||||
if (IsInvalidRouteTemplate(routeTemplate))
|
||||
{
|
||||
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate));
|
||||
}
|
||||
|
||||
var context = new TemplateParserContext(routeTemplate);
|
||||
var segments = new List<TemplateSegment>();
|
||||
|
||||
while (context.Next())
|
||||
{
|
||||
if (context.Current == Separator)
|
||||
{
|
||||
// If we get here is means that there's a consecutive '/' character.
|
||||
// Templates don't start with a '/' and parsing a segment consumes the separator.
|
||||
throw new ArgumentException(Resources.TemplateRoute_CannotHaveConsecutiveSeparators,
|
||||
nameof(routeTemplate));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ParseSegment(context, segments))
|
||||
{
|
||||
throw new ArgumentException(context.Error, nameof(routeTemplate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsAllValid(context, segments))
|
||||
{
|
||||
return new RouteTemplate(routeTemplate, segments);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(context.Error, nameof(routeTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseSegment(TemplateParserContext context, List<TemplateSegment> segments)
|
||||
{
|
||||
Debug.Assert(context != null);
|
||||
Debug.Assert(segments != null);
|
||||
|
||||
var segment = new TemplateSegment();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// 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"
|
||||
context.Back();
|
||||
if (!ParseLiteral(context, segment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the inside of a parameter
|
||||
if (!ParseParameter(context, segment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (context.Current == Separator)
|
||||
{
|
||||
// We've reached the end of the segment
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ParseLiteral(context, segment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.Next())
|
||||
{
|
||||
// We've reached the end of the string
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSegmentValid(context, segment))
|
||||
{
|
||||
segments.Add(segment);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment)
|
||||
{
|
||||
context.Mark();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is an open brace inside of a parameter, it has to be escaped
|
||||
if (context.Next())
|
||||
{
|
||||
if (context.Current != OpenBrace)
|
||||
{
|
||||
// If we see something like "{p1:regex(^\d{3", we will come here.
|
||||
context.Error = Resources.TemplateRoute_UnescapedBrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
// Example: "{p1:regex(^\d{"
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
// When we encounter Closed brace here, it either means end of the parameter or it is a closed
|
||||
// brace in the parameter, in that case it needs to be escaped.
|
||||
// Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
|
||||
if (!context.Next())
|
||||
{
|
||||
// This is the end of the string -and we have a valid parameter
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.Current == CloseBrace)
|
||||
{
|
||||
// This is an 'escaped' brace in a parameter name
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the end of the parameter
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.Next())
|
||||
{
|
||||
// This is a dangling open-brace, which is not allowed
|
||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var rawParameter = context.Capture();
|
||||
var decoded = rawParameter.Replace("}}", "}").Replace("{{", "{");
|
||||
|
||||
// At this point, we need to parse the raw name for inline constraint,
|
||||
// default values and optional parameters.
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded);
|
||||
|
||||
if (templatePart.IsCatchAll && templatePart.IsOptional)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (templatePart.IsOptional && templatePart.DefaultValue != null)
|
||||
{
|
||||
// Cannot be optional and have a default value.
|
||||
// The only way to declare an optional parameter is to have a ? at the end,
|
||||
// hence we cannot have both default value and optional parameter within the template.
|
||||
// A workaround is to add it as a separate entry in the defaults argument.
|
||||
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameterName = templatePart.Name;
|
||||
if (IsValidParameterName(context, parameterName))
|
||||
{
|
||||
segment.Parts.Add(templatePart);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseLiteral(TemplateParserContext context, TemplateSegment segment)
|
||||
{
|
||||
context.Mark();
|
||||
|
||||
string encoded;
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == Separator)
|
||||
{
|
||||
encoded = context.Capture();
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
else if (context.Current == OpenBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// 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 and return
|
||||
context.Back();
|
||||
encoded = context.Capture();
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// 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.Next())
|
||||
{
|
||||
encoded = context.Capture();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
|
||||
if (IsValidLiteral(context, decoded))
|
||||
{
|
||||
segment.Parts.Add(TemplatePart.CreateLiteral(decoded));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllValid(TemplateParserContext context, List<TemplateSegment> segments)
|
||||
{
|
||||
// A catch-all parameter must be the last part of the last segment
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsParameter &&
|
||||
part.IsCatchAll &&
|
||||
(i != segments.Count - 1 || j != segment.Parts.Count - 1))
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment)
|
||||
{
|
||||
// If a segment has multiple parts, then it can't contain a catch all.
|
||||
for (var i = 0; i < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
if (part.IsParameter && part.IsCatchAll && segment.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 < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
|
||||
if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
|
||||
{
|
||||
// This optional parameter is the last part in the segment
|
||||
if (i == segment.Parts.Count - 1)
|
||||
{
|
||||
if (!segment.Parts[i - 1].IsLiteral)
|
||||
{
|
||||
// The optional parameter is preceded by something that is not a literal.
|
||||
// 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 = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||
segment.DebuggerToString(),
|
||||
part.Name,
|
||||
segment.Parts[i - 1].DebuggerToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (segment.Parts[i - 1].Text != 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 = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||
segment.DebuggerToString(),
|
||||
part.Name,
|
||||
segment.Parts[i - 1].Text);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
segment.Parts[i - 1].IsOptionalSeperator = true;
|
||||
}
|
||||
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 ')'
|
||||
var nextPart = segment.Parts[i + 1];
|
||||
var invalidPartText = nextPart.IsParameter ? nextPart.Name : nextPart.Text;
|
||||
|
||||
context.Error = string.Format(
|
||||
Resources.TemplateRoute_OptionalParameterHasTobeTheLast,
|
||||
segment.DebuggerToString(),
|
||||
segment.Parts[i].Name,
|
||||
invalidPartText);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A segment cannot contain two consecutive parameters
|
||||
var isLastSegmentParameter = false;
|
||||
for (var i = 0; i < segment.Parts.Count; i++)
|
||||
{
|
||||
var part = segment.Parts[i];
|
||||
if (part.IsParameter && isLastSegmentParameter)
|
||||
{
|
||||
context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
|
||||
return false;
|
||||
}
|
||||
|
||||
isLastSegmentParameter = part.IsParameter;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidParameterName(TemplateParserContext context, string parameterName)
|
||||
{
|
||||
if (parameterName.Length == 0)
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_InvalidParameterName, parameterName);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameterName.Length; i++)
|
||||
{
|
||||
var c = parameterName[i];
|
||||
if (c == Separator || c == OpenBrace || c == CloseBrace || c == QuestionMark || c == Asterisk)
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_InvalidParameterName, parameterName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.ParameterNames.Add(parameterName))
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_RepeatedParameter, parameterName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidLiteral(TemplateParserContext context, string literal)
|
||||
{
|
||||
Debug.Assert(context != null);
|
||||
Debug.Assert(literal != null);
|
||||
|
||||
if (literal.IndexOf(QuestionMark) != -1)
|
||||
{
|
||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
||||
Resources.TemplateRoute_InvalidLiteral, literal);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsInvalidRouteTemplate(string routeTemplate)
|
||||
{
|
||||
return routeTemplate.StartsWith("~", StringComparison.Ordinal) ||
|
||||
routeTemplate.StartsWith("/", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private class TemplateParserContext
|
||||
{
|
||||
private readonly string _template;
|
||||
private int _index;
|
||||
private int? _mark;
|
||||
|
||||
private HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public TemplateParserContext(string template)
|
||||
{
|
||||
Debug.Assert(template != null);
|
||||
_template = template;
|
||||
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public char Current
|
||||
{
|
||||
get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public HashSet<string> ParameterNames
|
||||
{
|
||||
get { return _parameterNames; }
|
||||
}
|
||||
|
||||
public bool Back()
|
||||
{
|
||||
return --_index >= 0;
|
||||
}
|
||||
|
||||
public bool Next()
|
||||
{
|
||||
return ++_index < _template.Length;
|
||||
}
|
||||
|
||||
public void Mark()
|
||||
{
|
||||
_mark = _index;
|
||||
}
|
||||
|
||||
public string Capture()
|
||||
{
|
||||
if (_mark.HasValue)
|
||||
{
|
||||
var value = _template.Substring(_mark.Value, _index - _mark.Value);
|
||||
_mark = null;
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var inner = AspNetCore.Dispatcher.TemplateParser.Parse(routeTemplate);
|
||||
return new RouteTemplate(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,23 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class TemplatePart
|
||||
{
|
||||
public TemplatePart()
|
||||
{
|
||||
}
|
||||
|
||||
public TemplatePart(AspNetCore.Dispatcher.TemplatePart templatePart)
|
||||
{
|
||||
IsCatchAll = templatePart.IsCatchAll;
|
||||
IsLiteral = templatePart.IsLiteral;
|
||||
IsOptional = templatePart.IsOptional;
|
||||
IsOptionalSeperator = templatePart.IsOptionalSeperator;
|
||||
IsParameter = templatePart.IsParameter;
|
||||
Name = templatePart.Name;
|
||||
Text = templatePart.Text;
|
||||
DefaultValue = templatePart.DefaultValue;
|
||||
InlineConstraints = templatePart.InlineConstraints?.Select(p => new InlineConstraint(p));
|
||||
}
|
||||
|
||||
public static TemplatePart CreateLiteral(string text)
|
||||
{
|
||||
return new TemplatePart()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@ namespace Microsoft.AspNetCore.Routing.Template
|
|||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class TemplateSegment
|
||||
{
|
||||
public TemplateSegment()
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateSegment(AspNetCore.Dispatcher.TemplateSegment templateSegment)
|
||||
{
|
||||
Parts = new List<TemplatePart>(templateSegment.Parts.Select(s => new TemplatePart(s)));
|
||||
}
|
||||
|
||||
public bool IsSimple => Parts.Count == 1;
|
||||
|
||||
public List<TemplatePart> Parts { get; } = new List<TemplatePart>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,954 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class InlineRouteParameterParserTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("=")]
|
||||
[InlineData(":")]
|
||||
public void ParseRouteParameter_WithoutADefaultValue(string parameterName)
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(parameterName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(parameterName, templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.Empty(templatePart.InlineConstraints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithEmptyDefaultValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("param=");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("", templatePart.DefaultValue);
|
||||
Assert.Empty(templatePart.InlineConstraints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithoutAConstraintName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("param:");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Empty(constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithoutAConstraintNameOrParameterName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("param:=");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("", templatePart.DefaultValue);
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Empty(constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_WithADefaultValueContainingConstraintSeparator()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("param=:");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal(":", templatePart.DefaultValue);
|
||||
Assert.Empty(templatePart.InlineConstraints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintAndDefault_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("param:int=111111");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("111111", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("int", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithArgumentsAndDefault_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\d+)=111111");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("111111", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\d+)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintAndOptional_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:int?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("int", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:int=12?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("12", templatePart.DefaultValue);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("int", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValueWithQuestionMark_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:int=12??");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("12?", templatePart.DefaultValue);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("int", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\d+)?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\d+)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\d+)=abc?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
Assert.Equal("abc", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\d+)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ChainedConstraints_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(d+):test(w+)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ChainedConstraints_DoubleDelimiters_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param::test(d+)::test(w+)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Empty(constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
|
||||
constraint => Assert.Empty(constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ChainedConstraints_ColonInPattern_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\d+):test(\w:+)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test(\w:+)", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\d+):test(\w+)=qwer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("qwer", templatePart.DefaultValue);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_DoubleDelimiters_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\d+)::test(\w+)==qwer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("=qwer", templatePart.DefaultValue);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
|
||||
constraint => Assert.Empty(constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("=")]
|
||||
[InlineData("+=")]
|
||||
[InlineData(">= || <= || ==")]
|
||||
public void ParseRouteParameter_WithDefaultValue_ContainingDelimiter(string defaultValue)
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter($"comparison-operator:length(6)={defaultValue}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("comparison-operator", templatePart.Name);
|
||||
Assert.Equal(defaultValue, templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("length(6)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var template = ParseRouteTemplate(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
|
||||
|
||||
// Assert
|
||||
var parameters = template.Parameters.ToArray();
|
||||
|
||||
var param1 = parameters[0];
|
||||
Assert.Equal("p1", param1.Name);
|
||||
Assert.Equal("hello", param1.DefaultValue);
|
||||
Assert.False(param1.IsOptional);
|
||||
|
||||
Assert.Collection(param1.InlineConstraints,
|
||||
constraint => Assert.Equal("int", constraint.Constraint),
|
||||
constraint => Assert.Equal("test(3)", constraint.Constraint)
|
||||
);
|
||||
|
||||
var param2 = parameters[1];
|
||||
Assert.Equal("p2", param2.Name);
|
||||
Assert.Equal("abc", param2.DefaultValue);
|
||||
Assert.False(param2.IsOptional);
|
||||
|
||||
var param3 = parameters[2];
|
||||
Assert.Equal("p3", param3.Name);
|
||||
Assert.True(param3.IsOptional);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_NoTokens_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("world");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("world", templatePart.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ParamDefault_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter("param=world");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("world", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_ClosingBraceIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\})");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\})", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\})=wer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("wer", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\})", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithClosingParenInPattern_ClosingParenIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\))");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\))", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithClosingParenInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\))=fsd");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("fsd", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\))", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithColonInPattern_ColonIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(:)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(:)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithColonInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(:)=mnf");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("mnf", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(:)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithColonsInPattern_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(a:b:c)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(a:b:c)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithColonInParamName_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@":param:test=12");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(":param", templatePart.Name);
|
||||
|
||||
Assert.Equal("12", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("test", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithTwoColonInParamName_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@":param::test=12");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(":param", templatePart.Name);
|
||||
|
||||
Assert.Equal("12", templatePart.DefaultValue);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Empty(constraint.Constraint),
|
||||
constraint => Assert.Equal("test", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_EmptyConstraint_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@":param:test:");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(":param", templatePart.Name);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal("test", constraint.Constraint),
|
||||
constraint => Assert.Empty(constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithCommaInPattern_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\w,\w)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\w,\w)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithCommaInName_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par,am:test(\w)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par,am", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\w)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithCommaInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\w,\w)=jsd");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("jsd", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\w,\w)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithEqualsFollowedByQuestionMark_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:int=?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("", templatePart.DefaultValue);
|
||||
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("int", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(=)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("test(=)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_EqualsSignInDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param=test=bar");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("test=bar", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(a==b)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("test(a==b)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(a==b)=dvds");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("dvds", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("test(a==b)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_EqualEqualSignInName_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par==am:test=dvds");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par", templatePart.Name);
|
||||
Assert.Equal("=am:test=dvds", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_EqualEqualSignInDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test==dvds");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("=dvds", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_DefaultValueWithColonAndParens_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par=am:test(asd)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par", templatePart.Name);
|
||||
Assert.Equal("am:test(asd)", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_DefaultValueWithEqualsSignIn_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par=test(am):est=asd");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par", templatePart.Name);
|
||||
Assert.Equal("test(am):est=asd", templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(=)=sds");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("sds", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("test(=)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\{)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\{)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenBraceInName_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par{am:test(\sd)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par{am", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\sd)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\{)=xvc");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("xvc", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\{)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenInName_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par(am:test(\()");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par(am", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\()", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenInPattern_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\()");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\()", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenNoCloseParen_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(#$%");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal("test(#$%", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenAndColon_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(#:test1");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(#", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"test1", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenAndColonWithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(abc:somevalue):name(test1:differentname=default-value");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("default-value", templatePart.DefaultValue);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"name(test1", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"differentname", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenAndDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(constraintvalue=test1");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("test1", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(constraintvalue", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithOpenParenInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\()=djk");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
|
||||
Assert.Equal("djk", templatePart.DefaultValue);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\()", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\?)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\?)?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\?)=sdf");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("sdf", templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_WithDefaultValue_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(\?)=sdf?");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Equal("sdf", templatePart.DefaultValue);
|
||||
Assert.True(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithQuestionMarkInName_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"par?am:test(\?)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("par?am", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithClosedParenAndColonInPattern_ParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(#):$)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
Assert.Collection(templatePart.InlineConstraints,
|
||||
constraint => Assert.Equal(@"test(#)", constraint.Constraint),
|
||||
constraint => Assert.Equal(@"$)", constraint.Constraint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithColonAndClosedParenInPattern_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"param:test(#:)$)");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("param", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"test(#:)$)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ContainingMultipleUnclosedParenthesisInConstraint()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"foo:regex(\\(\\(\\(\\()");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("foo", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithBraces_PatternIsParsedCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)"); // ssn
|
||||
|
||||
// Assert
|
||||
Assert.Equal("p1", templatePart.Name);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRouteParameter_ConstraintWithBraces_WithDefaultValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)=123-456-7890"); // ssn
|
||||
|
||||
// Assert
|
||||
Assert.Equal("p1", templatePart.Name);
|
||||
Assert.Equal("123-456-7890", templatePart.DefaultValue);
|
||||
Assert.False(templatePart.IsOptional);
|
||||
|
||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
||||
Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "")]
|
||||
[InlineData("?", "")]
|
||||
[InlineData("*", "")]
|
||||
[InlineData(" ", " ")]
|
||||
[InlineData("\t", "\t")]
|
||||
[InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
|
||||
[InlineData(",,,", ",,,")]
|
||||
public void ParseRouteParameter_ParameterWithoutInlineConstraint_ReturnsTemplatePartWithEmptyInlineValues(
|
||||
string parameter,
|
||||
string expectedParameterName)
|
||||
{
|
||||
// Arrange & Act
|
||||
var templatePart = ParseParameter(parameter);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedParameterName, templatePart.Name);
|
||||
Assert.Empty(templatePart.InlineConstraints);
|
||||
Assert.Null(templatePart.DefaultValue);
|
||||
}
|
||||
|
||||
|
||||
private TemplatePart ParseParameter(string routeParameter)
|
||||
{
|
||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
||||
return templatePart;
|
||||
}
|
||||
|
||||
private static RouteTemplate ParseRouteTemplate(string template)
|
||||
{
|
||||
return TemplateParser.Parse(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,916 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class TemplateRouteParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void Parse_SingleLiteral()
|
||||
{
|
||||
// Arrange
|
||||
var template = "cool";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_SingleParameter()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(
|
||||
TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_OptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p?}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(
|
||||
TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_MultipleLiterals()
|
||||
{
|
||||
// Arrange
|
||||
var template = "cool/awesome/super";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("awesome"));
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[2].Parts.Add(TemplatePart.CreateLiteral("super"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_MultipleParameters()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}/{p2}/{*p3}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
true,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[2].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_LP()
|
||||
{
|
||||
// Arrange
|
||||
var template = "cool-{p1}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_PL()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}-cool";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_PLP()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}-cool-{p2}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_LPL()
|
||||
{
|
||||
// Arrange
|
||||
var template = "cool-{p1}-awesome";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2?}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_ParametersFollowingPeriod()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2}.{p3?}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[4]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_ThreeParametersSeperatedByPeriod()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2}.{p3}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[4]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}.{p2?}/{p3}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
true,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p1}/{p2}.{p3?}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{p2}/.{p3?}";
|
||||
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: null));
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
null));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[1]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", @"regex(^\d{3}-\d{3}-\d{4}$)")] // ssn
|
||||
[InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)}", @"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")] // date
|
||||
[InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", @"regex(^\w+\@\w+\.\w+)")] // email
|
||||
[InlineData(@"{p1:regex(([}}])\w+)}", @"regex(([}])\w+)")] // Not balanced }
|
||||
[InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced {
|
||||
public void Parse_RegularExpressions(string template, string constraint)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new RouteTemplate(template, new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
var c = new InlineConstraint(constraint);
|
||||
expected.Segments[0].Parts.Add(
|
||||
TemplatePart.CreateParameter("p1",
|
||||
false,
|
||||
false,
|
||||
defaultValue: null,
|
||||
inlineConstraints: new List<InlineConstraint> { c }));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}}$)}")] // extra }
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}}")] // extra } at the end
|
||||
[InlineData(@"{{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}")] // extra { at the begining
|
||||
[InlineData(@"{p1:regex(([}])\w+}")] // Not escaped }
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}$)}")] // Not escaped }
|
||||
[InlineData(@"{p1:regex(abc)")]
|
||||
public void Parse_RegularExpressions_Invalid(string template)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
|
||||
"'}' character." + Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{{4}}$)}")] // extra {
|
||||
[InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{4}}$)}")] // Not escaped {
|
||||
public void Parse_RegularExpressions_Unescaped(string template)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"In a route parameter, '{' and '}' must be escaped with '{{' and '}}'." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
|
||||
[InlineData("{p1?}{p2}", "p1", "p2")]
|
||||
[InlineData("{p1?}{p2?}", "p1", "p2")]
|
||||
[InlineData("{p1}.{p2?})", "p2", ")")]
|
||||
[InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
|
||||
public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
|
||||
string template,
|
||||
string parameter,
|
||||
string invalid)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"An optional parameter must be at the end of the segment. In the segment '" + template +
|
||||
"', optional parameter '" + parameter + "' is followed by '" + invalid + "'."
|
||||
+ Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{p1}-{p2?}", "-")]
|
||||
[InlineData("{p1}..{p2?}", "..")]
|
||||
[InlineData("..{p2?}", "..")]
|
||||
[InlineData("{p1}.abc.{p2?}", ".abc.")]
|
||||
[InlineData("{p1}{p2?}", "{p1}")]
|
||||
public void Parse_ComplexSegment_OptionalParametersSeperatedByPeriod_Invalid(string template, string parameter)
|
||||
{
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
"In the segment '"+ template +"', the optional parameter 'p2' is preceded by an invalid " +
|
||||
"segment '" + parameter +"'. Only a period (.) can precede an optional parameter." +
|
||||
Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_WithRepeatedParameter()
|
||||
{
|
||||
var ex = ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
||||
"The route parameter name 'controller' appears more than one time in the route template." +
|
||||
Environment.NewLine + "Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("123{a}abc{")]
|
||||
[InlineData("123{a}abc}")]
|
||||
[InlineData("xyz}123{a}abc}")]
|
||||
[InlineData("{{p1}")]
|
||||
[InlineData("{p1}}")]
|
||||
[InlineData("p1}}p2{")]
|
||||
public void InvalidTemplate_WithMismatchedBraces(string template)
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template),
|
||||
@"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("123{a}abc{*moo}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}/{*p2}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{*p1}abc{*p2}"),
|
||||
"A path segment that contains more than one section, such as a literal section or a parameter, " +
|
||||
"cannot contain a catch-all parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotHaveCatchAllWithNoName()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{*}"),
|
||||
"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
|
||||
" contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional," +
|
||||
" and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
" and can occur only at the start of the parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{**}", "*")]
|
||||
[InlineData("{a*}", "a*")]
|
||||
[InlineData("{*a*}", "a*")]
|
||||
[InlineData("{*a*:int}", "a*")]
|
||||
[InlineData("{*a*=5}", "a*")]
|
||||
[InlineData("{*a*b=5}", "a*b")]
|
||||
[InlineData("{p1?}.{p2/}/{p3}", "p2/")]
|
||||
[InlineData("{p{{}", "p{")]
|
||||
[InlineData("{p}}}", "p}")]
|
||||
[InlineData("{p/}", "p/")]
|
||||
public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(
|
||||
string template,
|
||||
string parameterName)
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The route parameter name '" + parameterName + "' is invalid. Route parameter " +
|
||||
"names must be non-empty and cannot contain these characters: '{', '}', '/'. The '?' character " +
|
||||
"marks a parameter as optional, and can occur only at the end of the parameter. The '*' character " +
|
||||
"marks a parameter as catch-all, and can occur only at the start of the parameter.";
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse(template), expectedMessage + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{{p1}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_SameParameterTwiceThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{aaa}/{*AAA}"),
|
||||
"The route parameter name 'AAA' appears more than one time in the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{aa}a}/{z}"),
|
||||
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||
"matching '}' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{a{aa}/{z}"),
|
||||
"In a route parameter, '{' and '}' must be escaped with '{{' and '}}'." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{}/{z}"),
|
||||
"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
|
||||
" contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
|
||||
" can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
" and can occur only at the start of the parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{Controller}.mvc/{?}"),
|
||||
"The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
|
||||
" contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
|
||||
" can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
" and can occur only at the start of the parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}//{z}"),
|
||||
"The route template separator character '/' cannot appear consecutively. It must be separated by " +
|
||||
"either a parameter or a literal value." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
|
||||
"A catch-all parameter can only appear as the last segment of the route template." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_RepeatedParametersThrows()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foo/aa{p1}{p2}"),
|
||||
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
|
||||
"a literal string." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotStartWithSlash()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("/foo"),
|
||||
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotStartWithTilde()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("~foo"),
|
||||
"The route template cannot start with a '/' or '~' character." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CannotContainQuestionMark()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("foor?bar"),
|
||||
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character." +
|
||||
Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{foor?b}"),
|
||||
"The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot" +
|
||||
" contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
|
||||
" can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
|
||||
" and can occur only at the start of the parameter." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidTemplate_CatchAllMarkedOptional()
|
||||
{
|
||||
ExceptionAssert.Throws<ArgumentException>(
|
||||
() => TemplateParser.Parse("{a}/{*b?}"),
|
||||
"A catch-all parameter cannot be marked optional." + Environment.NewLine +
|
||||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
private class TemplateEqualityComparer : IEqualityComparer<RouteTemplate>
|
||||
{
|
||||
public bool Equals(RouteTemplate x, RouteTemplate y)
|
||||
{
|
||||
if (x == null && y == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (x == null || y == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.Equals(x.TemplateText, y.TemplateText, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x.Segments.Count != y.Segments.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < x.Segments.Count; i++)
|
||||
{
|
||||
if (x.Segments[i].Parts.Count != y.Segments[i].Parts.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int j = 0; j < x.Segments[i].Parts.Count; j++)
|
||||
{
|
||||
if (!Equals(x.Segments[i].Parts[j], y.Segments[i].Parts[j]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x.Parameters.Count != y.Parameters.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < x.Parameters.Count; i++)
|
||||
{
|
||||
if (!Equals(x.Parameters[i], y.Parameters[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Equals(TemplatePart x, TemplatePart y)
|
||||
{
|
||||
if (x.IsLiteral != y.IsLiteral ||
|
||||
x.IsParameter != y.IsParameter ||
|
||||
x.IsCatchAll != y.IsCatchAll ||
|
||||
x.IsOptional != y.IsOptional ||
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
|
||||
(x.InlineConstraints == null && y.InlineConstraints != null) ||
|
||||
(x.InlineConstraints != null && y.InlineConstraints == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x.InlineConstraints == null && y.InlineConstraints == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.InlineConstraints.Count() != y.InlineConstraints.Count())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var xconstraint in x.InlineConstraints)
|
||||
{
|
||||
if (!y.InlineConstraints.Any<InlineConstraint>(
|
||||
c => string.Equals(c.Constraint, xconstraint.Constraint)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetHashCode(RouteTemplate obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Template.Tests
|
||||
|
|
|
|||
Loading…
Reference in New Issue