parent
927f8ed3d2
commit
3a5cd6dd25
|
|
@ -9,7 +9,6 @@ using BenchmarkDotNet.Attributes;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Internal;
|
using Microsoft.AspNetCore.Routing.Internal;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
|
||||||
using Microsoft.AspNetCore.Routing.Tree;
|
using Microsoft.AspNetCore.Routing.Tree;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
|
@ -35,11 +34,11 @@ namespace Microsoft.AspNetCore.Dispatcher.Performance
|
||||||
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default)),
|
new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy(UrlEncoder.Default)),
|
||||||
new DefaultInlineConstraintResolver(new OptionsManager<RouteOptions>(new OptionsFactory<RouteOptions>(Enumerable.Empty<IConfigureOptions<RouteOptions>>(), Enumerable.Empty<IPostConfigureOptions<RouteOptions>>()))));
|
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, Routing.Template.TemplateParser.Parse("api/Widgets"), "default", 0);
|
||||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets/{id}"), "default", 0);
|
||||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("api/Widgets/search/{term}"), "default", 0);
|
treeBuilder.MapInbound(handler, Routing.Template.TemplateParser.Parse("api/Widgets/search/{term}"), "default", 0);
|
||||||
treeBuilder.MapInbound(handler, TemplateParser.Parse("admin/users/{id}"), "default", 0);
|
treeBuilder.MapInbound(handler, Routing.Template.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("admin/users/{id}/manage"), "default", 0);
|
||||||
|
|
||||||
_treeRouter = treeBuilder.Build();
|
_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)
|
internal static string FormatAmbiguousEndpoints(object p0, object p1)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousEndpoints"), p0, 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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -121,4 +121,46 @@
|
||||||
<value>Multiple endpoints matched. The following endpoints matched the request:{0}{0}{1}</value>
|
<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>
|
<comment>0 is the newline - 1 is a newline separate list of action display names</comment>
|
||||||
</data>
|
</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>
|
</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");
|
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 feature = httpContext.Features.Get<IDispatcherFeature>();
|
||||||
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
|
var result = binder.GetValues(feature.Values.AsRouteValueDictionary(), new RouteValueDictionary(values));
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||||
var entries = new List<InboundRouteEntry>();
|
var entries = new List<InboundRouteEntry>();
|
||||||
foreach (var group in groups)
|
foreach (var group in groups)
|
||||||
{
|
{
|
||||||
var template = TemplateParser.Parse(group.Key.RouteTemplate);
|
var template = Template.TemplateParser.Parse(group.Key.RouteTemplate);
|
||||||
|
|
||||||
var defaults = new RouteValueDictionary();
|
var defaults = new RouteValueDictionary();
|
||||||
for (var i = 0; i < template.Parameters.Count; i++)
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
@ -16,228 +15,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
throw new ArgumentNullException(nameof(routeParameter));
|
throw new ArgumentNullException(nameof(routeParameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeParameter.Length == 0)
|
var inner = AspNetCore.Dispatcher.InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
||||||
{
|
return new TemplatePart(inner);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,174 +122,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
internal static string FormatDefaultInlineConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
|
internal static string FormatDefaultInlineConstraintResolver_TypeNotConstraint(object p0, object p1, object p2)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DefaultInlineConstraintResolver_TypeNotConstraint"), p0, p1, 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>
|
/// <summary>
|
||||||
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
/// The constraint entry '{0}' - '{1}' on the route '{2}' must have a string value or be of a type which implements '{3}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -318,48 +150,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
internal static string FormatRouteConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
internal static string FormatRouteConstraintBuilder_CouldNotResolveConstraint(object p0, object p1, object p2, object p3)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("RouteConstraintBuilder_CouldNotResolveConstraint"), p0, p1, p2, 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>
|
/// <summary>
|
||||||
/// Two or more routes named '{0}' have different templates.
|
/// Two or more routes named '{0}' have different templates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -388,20 +178,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
internal static string FormatUnableToFindServices(object p0, object p1, object p2)
|
internal static string FormatUnableToFindServices(object p0, object p1, object p2)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, 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>
|
/// <summary>
|
||||||
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
|
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -416,6 +192,34 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
internal static string FormatDispatcherFeatureIsRequired(object p0, object p1, object p2)
|
internal static string FormatDispatcherFeatureIsRequired(object p0, object p1, object p2)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherFeatureIsRequired"), p0, p1, 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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -141,67 +141,25 @@
|
||||||
<data name="DefaultInlineConstraintResolver_TypeNotConstraint" xml:space="preserve">
|
<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>
|
<value>The constraint type '{0}' which is mapped to constraint key '{1}' must implement the '{2}' interface.</value>
|
||||||
</data>
|
</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">
|
<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>
|
<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>
|
||||||
<data name="RouteConstraintBuilder_CouldNotResolveConstraint" xml:space="preserve">
|
<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>
|
<value>The constraint entry '{0}' - '{1}' on the route '{2}' could not be resolved by the constraint resolver of type '{3}'.</value>
|
||||||
</data>
|
</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">
|
<data name="AttributeRoute_DifferentLinkGenerationEntries_SameName" xml:space="preserve">
|
||||||
<value>Two or more routes named '{0}' have different templates.</value>
|
<value>Two or more routes named '{0}' have different templates.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnableToFindServices" xml:space="preserve">
|
<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>
|
<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>
|
||||||
<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">
|
<data name="DispatcherFeatureIsRequired" xml:space="preserve">
|
||||||
<value>The '{0}' has no '{1}'. '{2}' requires a dispatcher.</value>
|
<value>The '{0}' has no '{1}'. '{2}' requires a dispatcher.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
|
|
@ -24,6 +24,16 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
Constraint = constraint;
|
Constraint = constraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InlineConstraint(AspNetCore.Dispatcher.InlineConstraint constraint)
|
||||||
|
{
|
||||||
|
if (constraint == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(constraint));
|
||||||
|
}
|
||||||
|
|
||||||
|
Constraint = constraint.Constraint;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the constraint text.
|
/// Gets the constraint text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,25 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
private const string SeparatorString = "/";
|
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)
|
public RouteTemplate(string template, List<TemplateSegment> segments)
|
||||||
{
|
{
|
||||||
if (segments == null)
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
public static class TemplateParser
|
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)
|
public static RouteTemplate Parse(string routeTemplate)
|
||||||
{
|
{
|
||||||
if (routeTemplate == null)
|
if (routeTemplate == null)
|
||||||
{
|
{
|
||||||
routeTemplate = String.Empty;
|
throw new ArgumentNullException(routeTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsInvalidRouteTemplate(routeTemplate))
|
var inner = AspNetCore.Dispatcher.TemplateParser.Parse(routeTemplate);
|
||||||
{
|
return new RouteTemplate(inner);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,23 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
[DebuggerDisplay("{DebuggerToString()}")]
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
public class TemplatePart
|
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)
|
public static TemplatePart CreateLiteral(string text)
|
||||||
{
|
{
|
||||||
return new TemplatePart()
|
return new TemplatePart()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,15 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
[DebuggerDisplay("{DebuggerToString()}")]
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
public class TemplateSegment
|
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 bool IsSimple => Parts.Count == 1;
|
||||||
|
|
||||||
public List<TemplatePart> Parts { get; } = new List<TemplatePart>();
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template.Tests
|
namespace Microsoft.AspNetCore.Routing.Template.Tests
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue