Redesign public API for templates
-Renamed RouteTemplate -> RoutePattern -Made immutable -Added Builder -Lots of fixes to parser to support new design There are a few small issues logged for follow-up but this is mostly in the place I want it design-wise.
This commit is contained in:
parent
a3c1b6d033
commit
08a64048da
|
|
@ -1,32 +0,0 @@
|
||||||
// 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,64 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The parsed representation of a constraint in a <see cref="RoutePattern"/> parameter.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
public sealed class ConstraintReference
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ConstraintReference"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">The constraint identifier.</param>
|
||||||
|
/// <remarks>A new <see cref="ConstraintReference"/>.</remarks>
|
||||||
|
public static ConstraintReference Create(string content)
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConstraintReference(null, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ConstraintReference"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rawText">The raw text of the constraint identifier.</param>
|
||||||
|
/// <param name="content">The constraint identifier.</param>
|
||||||
|
/// <remarks>A new <see cref="ConstraintReference"/>.</remarks>
|
||||||
|
public static ConstraintReference CreateFromText(string rawText, string content)
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConstraintReference(rawText, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConstraintReference(string rawText, string content)
|
||||||
|
{
|
||||||
|
RawText = rawText;
|
||||||
|
Content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the constraint text.
|
||||||
|
/// </summary>
|
||||||
|
public string Content { get; }
|
||||||
|
|
||||||
|
public string RawText { get; }
|
||||||
|
|
||||||
|
private string DebuggerToString()
|
||||||
|
{
|
||||||
|
return RawText ?? Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,43 +3,37 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
{
|
{
|
||||||
public static class InlineRouteParameterParser
|
public static class InlineRouteParameterParser
|
||||||
{
|
{
|
||||||
public static TemplatePart ParseRouteParameter(string routeParameter)
|
public static RoutePatternParameter ParseRouteParameter(string text, string parameter)
|
||||||
{
|
{
|
||||||
if (routeParameter == null)
|
if (parameter == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(routeParameter));
|
throw new ArgumentNullException(nameof(parameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeParameter.Length == 0)
|
if (parameter.Length == 0)
|
||||||
{
|
{
|
||||||
return TemplatePart.CreateParameter(
|
return new RoutePatternParameter(null, string.Empty, null, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||||
name: string.Empty,
|
|
||||||
isCatchAll: false,
|
|
||||||
isOptional: false,
|
|
||||||
defaultValue: null,
|
|
||||||
inlineConstraints: null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
var endIndex = routeParameter.Length - 1;
|
var endIndex = parameter.Length - 1;
|
||||||
|
|
||||||
var isCatchAll = false;
|
var parameterKind = RoutePatternParameterKind.Standard;
|
||||||
var isOptional = false;
|
if (parameter[0] == '*')
|
||||||
|
|
||||||
if (routeParameter[0] == '*')
|
|
||||||
{
|
{
|
||||||
isCatchAll = true;
|
parameterKind = RoutePatternParameterKind.CatchAll;
|
||||||
startIndex++;
|
startIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeParameter[endIndex] == '?')
|
if (parameter[endIndex] == '?')
|
||||||
{
|
{
|
||||||
isOptional = true;
|
parameterKind = RoutePatternParameterKind.Optional;
|
||||||
endIndex--;
|
endIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,14 +44,14 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
while (currentIndex <= endIndex)
|
while (currentIndex <= endIndex)
|
||||||
{
|
{
|
||||||
var currentChar = routeParameter[currentIndex];
|
var currentChar = parameter[currentIndex];
|
||||||
|
|
||||||
if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
|
if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
|
||||||
{
|
{
|
||||||
// Parameter names are allowed to start with delimiters used to denote constraints or default values.
|
// 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
|
// i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
|
||||||
// specifications.
|
// specifications.
|
||||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
parameterName = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||||
|
|
||||||
// Roll the index back and move to the constraint parsing stage.
|
// Roll the index back and move to the constraint parsing stage.
|
||||||
currentIndex--;
|
currentIndex--;
|
||||||
|
|
@ -65,40 +59,36 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
else if (currentIndex == endIndex)
|
else if (currentIndex == endIndex)
|
||||||
{
|
{
|
||||||
parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
parameterName = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parseResults = ParseConstraints(routeParameter, currentIndex, endIndex);
|
var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
|
||||||
currentIndex = parseResults.CurrentIndex;
|
currentIndex = parseResults.CurrentIndex;
|
||||||
|
|
||||||
string defaultValue = null;
|
string defaultValue = null;
|
||||||
if (currentIndex <= endIndex &&
|
if (currentIndex <= endIndex &&
|
||||||
routeParameter[currentIndex] == '=')
|
parameter[currentIndex] == '=')
|
||||||
{
|
{
|
||||||
defaultValue = routeParameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
defaultValue = parameter.Substring(currentIndex + 1, endIndex - currentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TemplatePart.CreateParameter(parameterName,
|
return new RoutePatternParameter(text, parameterName, defaultValue, parameterKind, parseResults.Constraints.ToArray());
|
||||||
isCatchAll,
|
|
||||||
isOptional,
|
|
||||||
defaultValue,
|
|
||||||
parseResults.Constraints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConstraintParseResults ParseConstraints(
|
private static ConstraintParseResults ParseConstraints(
|
||||||
string routeParameter,
|
string parameter,
|
||||||
int currentIndex,
|
int currentIndex,
|
||||||
int endIndex)
|
int endIndex)
|
||||||
{
|
{
|
||||||
var inlineConstraints = new List<InlineConstraint>();
|
var constraints = new List<ConstraintReference>();
|
||||||
var state = ParseState.Start;
|
var state = ParseState.Start;
|
||||||
var startIndex = currentIndex;
|
var startIndex = currentIndex;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var currentChar = currentIndex > endIndex ? null : (char?)routeParameter[currentIndex];
|
var currentChar = currentIndex > endIndex ? null : (char?)parameter[currentIndex];
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ParseState.Start:
|
case ParseState.Start:
|
||||||
|
|
@ -125,8 +115,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
case null:
|
case null:
|
||||||
state = ParseState.End;
|
state = ParseState.End;
|
||||||
var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
var constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
break;
|
break;
|
||||||
case ')':
|
case ')':
|
||||||
// Only consume a ')' token if
|
// Only consume a ')' token if
|
||||||
|
|
@ -134,24 +124,24 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// (b) the next character is the start of the new constraint ':'
|
// (b) the next character is the start of the new constraint ':'
|
||||||
// (c) the next character is the start of the default value.
|
// (c) the next character is the start of the default value.
|
||||||
|
|
||||||
var nextChar = currentIndex + 1 > endIndex ? null : (char?)routeParameter[currentIndex + 1];
|
var nextChar = currentIndex + 1 > endIndex ? null : (char?)parameter[currentIndex + 1];
|
||||||
switch (nextChar)
|
switch (nextChar)
|
||||||
{
|
{
|
||||||
case null:
|
case null:
|
||||||
state = ParseState.End;
|
state = ParseState.End;
|
||||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
break;
|
break;
|
||||||
case ':':
|
case ':':
|
||||||
state = ParseState.Start;
|
state = ParseState.Start;
|
||||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
startIndex = currentIndex + 1;
|
startIndex = currentIndex + 1;
|
||||||
break;
|
break;
|
||||||
case '=':
|
case '=':
|
||||||
state = ParseState.End;
|
state = ParseState.End;
|
||||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
|
constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -162,11 +152,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Simply verifying that the parantheses will eventually be closed should suffice to
|
// 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
|
// determine if the terminator needs to be consumed as part of the current constraint
|
||||||
// specification.
|
// specification.
|
||||||
var indexOfClosingParantheses = routeParameter.IndexOf(')', currentIndex + 1);
|
var indexOfClosingParantheses = parameter.IndexOf(')', currentIndex + 1);
|
||||||
if (indexOfClosingParantheses == -1)
|
if (indexOfClosingParantheses == -1)
|
||||||
{
|
{
|
||||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
|
|
||||||
if (currentChar == ':')
|
if (currentChar == ':')
|
||||||
{
|
{
|
||||||
|
|
@ -192,12 +182,12 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
case null:
|
case null:
|
||||||
state = ParseState.End;
|
state = ParseState.End;
|
||||||
var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
var constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
break;
|
break;
|
||||||
case ':':
|
case ':':
|
||||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
startIndex = currentIndex + 1;
|
startIndex = currentIndex + 1;
|
||||||
break;
|
break;
|
||||||
case '(':
|
case '(':
|
||||||
|
|
@ -205,8 +195,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
break;
|
break;
|
||||||
case '=':
|
case '=':
|
||||||
state = ParseState.End;
|
state = ParseState.End;
|
||||||
constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
|
constraintText = parameter.Substring(startIndex, currentIndex - startIndex);
|
||||||
inlineConstraints.Add(new InlineConstraint(constraintText));
|
constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText));
|
||||||
currentIndex--;
|
currentIndex--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +210,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return new ConstraintParseResults
|
return new ConstraintParseResults
|
||||||
{
|
{
|
||||||
CurrentIndex = currentIndex,
|
CurrentIndex = currentIndex,
|
||||||
Constraints = inlineConstraints
|
Constraints = constraints
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +226,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
public int CurrentIndex;
|
public int CurrentIndex;
|
||||||
|
|
||||||
public IEnumerable<InlineConstraint> Constraints;
|
public IEnumerable<ConstraintReference> Constraints;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
// 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.Patterns
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
public sealed class RoutePattern
|
||||||
|
{
|
||||||
|
private const string SeparatorString = "/";
|
||||||
|
|
||||||
|
internal RoutePattern(
|
||||||
|
string rawText,
|
||||||
|
RoutePatternParameter[] parameters,
|
||||||
|
RoutePatternPathSegment[] pathSegments)
|
||||||
|
{
|
||||||
|
Debug.Assert(parameters != null);
|
||||||
|
Debug.Assert(pathSegments != null);
|
||||||
|
|
||||||
|
RawText = rawText;
|
||||||
|
Parameters = parameters;
|
||||||
|
PathSegments = pathSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RawText { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<RoutePatternParameter> Parameters { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<RoutePatternPathSegment> PathSegments { get; }
|
||||||
|
|
||||||
|
public static RoutePattern Parse(string pattern)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return RoutePatternParser.Parse(pattern);
|
||||||
|
}
|
||||||
|
catch (RoutePatternException ex)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(ex.Message, nameof(pattern), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 RoutePatternParameter 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DebuggerToString()
|
||||||
|
{
|
||||||
|
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
// 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 System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
public sealed class RoutePatternBuilder
|
||||||
|
{
|
||||||
|
private RoutePatternBuilder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<RoutePatternPathSegment> PathSegments { get; } = new List<RoutePatternPathSegment>();
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public RoutePatternBuilder AddPathSegment(RoutePatternPart part)
|
||||||
|
{
|
||||||
|
return AddPathSegment(null, part, Array.Empty<RoutePatternPart>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoutePatternBuilder AddPathSegment(RoutePatternPart part, params RoutePatternPart[] parts)
|
||||||
|
{
|
||||||
|
return AddPathSegment(null, part, Array.Empty<RoutePatternPart>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoutePatternBuilder AddPathSegment(string text, RoutePatternPart part)
|
||||||
|
{
|
||||||
|
return AddPathSegment(text, part, Array.Empty<RoutePatternPart>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoutePatternBuilder AddPathSegment(string text, RoutePatternPart part, params RoutePatternPart[] parts)
|
||||||
|
{
|
||||||
|
var allParts = new RoutePatternPart[1 + parts.Length];
|
||||||
|
allParts[0] = part;
|
||||||
|
parts.CopyTo(allParts, 1);
|
||||||
|
|
||||||
|
var segment = new RoutePatternPathSegment(text, allParts);
|
||||||
|
PathSegments.Add(segment);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoutePattern Build()
|
||||||
|
{
|
||||||
|
var parameters = new List<RoutePatternParameter>();
|
||||||
|
for (var i = 0; i < PathSegments.Count; i++)
|
||||||
|
{
|
||||||
|
var segment = PathSegments[i];
|
||||||
|
for (var j = 0; j < segment.Parts.Count; j++)
|
||||||
|
{
|
||||||
|
var parameter = segment.Parts[j] as RoutePatternParameter;
|
||||||
|
if (parameter != null)
|
||||||
|
{
|
||||||
|
parameters.Add(parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePattern(Text, parameters.ToArray(), PathSegments.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternBuilder Create(string text)
|
||||||
|
{
|
||||||
|
return new RoutePatternBuilder() { Text = text, };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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.Patterns
|
||||||
|
{
|
||||||
|
public class RoutePatternException : Exception
|
||||||
|
{
|
||||||
|
public RoutePatternException(string pattern, string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
if (pattern == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Pattern { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
public sealed class RoutePatternLiteral : RoutePatternPart
|
||||||
|
{
|
||||||
|
internal RoutePatternLiteral(string rawText, string content)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(content));
|
||||||
|
|
||||||
|
RawText = rawText;
|
||||||
|
Content = content;
|
||||||
|
|
||||||
|
PartKind = RoutePatternPartKind.Literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Content { get; }
|
||||||
|
|
||||||
|
public override RoutePatternPartKind PartKind { get; }
|
||||||
|
|
||||||
|
public override string RawText { get; }
|
||||||
|
|
||||||
|
internal override string DebuggerToString()
|
||||||
|
{
|
||||||
|
return RawText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
public class RoutePatternParameter : RoutePatternPart
|
||||||
|
{
|
||||||
|
internal RoutePatternParameter(
|
||||||
|
string rawText,
|
||||||
|
string name,
|
||||||
|
object defaultValue,
|
||||||
|
RoutePatternParameterKind parameterKind,
|
||||||
|
ConstraintReference[] constraints)
|
||||||
|
{
|
||||||
|
// See #475 - this code should have some asserts, but it can't because of the design of InlineRouteParameterParser.
|
||||||
|
|
||||||
|
RawText = rawText;
|
||||||
|
Name = name;
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
ParameterKind = parameterKind;
|
||||||
|
Constraints = constraints;
|
||||||
|
|
||||||
|
PartKind = RoutePatternPartKind.Parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<ConstraintReference> Constraints { get; }
|
||||||
|
|
||||||
|
public object DefaultValue { get; }
|
||||||
|
|
||||||
|
public bool IsCatchAll => ParameterKind == RoutePatternParameterKind.CatchAll;
|
||||||
|
|
||||||
|
public bool IsOptional => ParameterKind == RoutePatternParameterKind.Optional;
|
||||||
|
|
||||||
|
public RoutePatternParameterKind ParameterKind { get; }
|
||||||
|
|
||||||
|
public override RoutePatternPartKind PartKind { get; }
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public override string RawText { get; }
|
||||||
|
|
||||||
|
internal override string DebuggerToString()
|
||||||
|
{
|
||||||
|
return RawText ?? "{" + (IsCatchAll ? "*" : string.Empty) + Name + (IsOptional ? "?" : string.Empty) + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
public enum RoutePatternParameterKind
|
||||||
|
{
|
||||||
|
Standard,
|
||||||
|
Optional,
|
||||||
|
CatchAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
{
|
{
|
||||||
public static class TemplateParser
|
internal static class RoutePatternParser
|
||||||
{
|
{
|
||||||
private const char Separator = '/';
|
private const char Separator = '/';
|
||||||
private const char OpenBrace = '{';
|
private const char OpenBrace = '{';
|
||||||
|
|
@ -18,61 +17,85 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
private const char Asterisk = '*';
|
private const char Asterisk = '*';
|
||||||
private const string PeriodString = ".";
|
private const string PeriodString = ".";
|
||||||
|
|
||||||
public static RouteTemplate Parse(string routeTemplate)
|
internal static readonly char[] InvalidParameterNameChars = new char[]
|
||||||
{
|
{
|
||||||
if (routeTemplate == null)
|
Separator,
|
||||||
|
OpenBrace,
|
||||||
|
CloseBrace,
|
||||||
|
QuestionMark,
|
||||||
|
Asterisk
|
||||||
|
};
|
||||||
|
|
||||||
|
public static RoutePattern Parse(string pattern)
|
||||||
|
{
|
||||||
|
if (pattern == null)
|
||||||
{
|
{
|
||||||
routeTemplate = String.Empty;
|
throw new ArgumentNullException(nameof(pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsInvalidRouteTemplate(routeTemplate))
|
if (IsInvalidPattern(pattern))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate));
|
throw new RoutePatternException(pattern, Resources.TemplateRoute_InvalidRouteTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = new TemplateParserContext(routeTemplate);
|
var context = new TemplateParserContext(pattern);
|
||||||
var segments = new List<TemplateSegment>();
|
var builder = RoutePatternBuilder.Create(pattern);
|
||||||
|
var segments = new List<RoutePatternPathSegment>();
|
||||||
|
|
||||||
while (context.Next())
|
while (context.MoveNext())
|
||||||
{
|
{
|
||||||
|
var i = context.Index;
|
||||||
|
|
||||||
if (context.Current == Separator)
|
if (context.Current == Separator)
|
||||||
{
|
{
|
||||||
// If we get here is means that there's a consecutive '/' character.
|
// 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.
|
// Templates don't start with a '/' and parsing a segment consumes the separator.
|
||||||
throw new ArgumentException(Resources.TemplateRoute_CannotHaveConsecutiveSeparators,
|
throw new RoutePatternException(pattern, Resources.TemplateRoute_CannotHaveConsecutiveSeparators);
|
||||||
nameof(routeTemplate));
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!ParseSegment(context, segments))
|
||||||
{
|
{
|
||||||
if (!ParseSegment(context, segments))
|
throw new RoutePatternException(pattern, context.Error);
|
||||||
{
|
}
|
||||||
throw new ArgumentException(context.Error, nameof(routeTemplate));
|
|
||||||
}
|
// A successful parse should always result in us being at the end or at a separator.
|
||||||
|
Debug.Assert(context.AtEnd() || context.Current == Separator);
|
||||||
|
|
||||||
|
if (context.Index <= i)
|
||||||
|
{
|
||||||
|
throw new InvalidProgramException("Infinite loop in the parser. This is a bug.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsAllValid(context, segments))
|
if (IsAllValid(context, segments))
|
||||||
{
|
{
|
||||||
return new RouteTemplate(routeTemplate, segments);
|
for (var i = 0; i < segments.Count; i++)
|
||||||
|
{
|
||||||
|
builder.PathSegments.Add(segments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentException(context.Error, nameof(routeTemplate));
|
throw new RoutePatternException(pattern, context.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ParseSegment(TemplateParserContext context, List<TemplateSegment> segments)
|
private static bool ParseSegment(TemplateParserContext context, List<RoutePatternPathSegment> segments)
|
||||||
{
|
{
|
||||||
Debug.Assert(context != null);
|
Debug.Assert(context != null);
|
||||||
Debug.Assert(segments != null);
|
Debug.Assert(segments != null);
|
||||||
|
|
||||||
var segment = new TemplateSegment();
|
var parts = new List<RoutePatternPart>();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
var i = context.Index;
|
||||||
|
|
||||||
if (context.Current == OpenBrace)
|
if (context.Current == OpenBrace)
|
||||||
{
|
{
|
||||||
if (!context.Next())
|
if (!context.MoveNext())
|
||||||
{
|
{
|
||||||
// This is a dangling open-brace, which is not allowed
|
// This is a dangling open-brace, which is not allowed
|
||||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||||
|
|
@ -83,43 +106,44 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
// This is an 'escaped' brace in a literal, like "{{foo"
|
// This is an 'escaped' brace in a literal, like "{{foo"
|
||||||
context.Back();
|
context.Back();
|
||||||
if (!ParseLiteral(context, segment))
|
if (!ParseLiteral(context, parts))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This is the inside of a parameter
|
// This is a parameter
|
||||||
if (!ParseParameter(context, segment))
|
context.Back();
|
||||||
|
if (!ParseParameter(context, parts))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (context.Current == Separator)
|
|
||||||
{
|
|
||||||
// We've reached the end of the segment
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!ParseLiteral(context, segment))
|
if (!ParseLiteral(context, parts))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.Next())
|
if (context.Current == Separator || context.AtEnd())
|
||||||
{
|
{
|
||||||
// We've reached the end of the string
|
// We've reached the end of the segment
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Index <= i)
|
||||||
|
{
|
||||||
|
throw new InvalidProgramException("Infinite loop in the parser. This is a bug.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsSegmentValid(context, segment))
|
if (IsSegmentValid(context, parts))
|
||||||
{
|
{
|
||||||
segments.Add(segment);
|
segments.Add(new RoutePatternPathSegment(null, parts.ToArray()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -128,16 +152,19 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment)
|
private static bool ParseParameter(TemplateParserContext context, List<RoutePatternPart> parts)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(context.Current == OpenBrace);
|
||||||
context.Mark();
|
context.Mark();
|
||||||
|
|
||||||
|
context.MoveNext();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (context.Current == OpenBrace)
|
if (context.Current == OpenBrace)
|
||||||
{
|
{
|
||||||
// This is an open brace inside of a parameter, it has to be escaped
|
// This is an open brace inside of a parameter, it has to be escaped
|
||||||
if (context.Next())
|
if (context.MoveNext())
|
||||||
{
|
{
|
||||||
if (context.Current != OpenBrace)
|
if (context.Current != OpenBrace)
|
||||||
{
|
{
|
||||||
|
|
@ -159,10 +186,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// When we encounter Closed brace here, it either means end of the parameter or it is a closed
|
// 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.
|
// 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
|
// Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
|
||||||
if (!context.Next())
|
if (!context.MoveNext())
|
||||||
{
|
{
|
||||||
// This is the end of the string -and we have a valid parameter
|
// This is the end of the string -and we have a valid parameter
|
||||||
context.Back();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,12 +199,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This is the end of the parameter
|
// This is the end of the parameter
|
||||||
context.Back();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.Next())
|
if (!context.MoveNext())
|
||||||
{
|
{
|
||||||
// This is a dangling open-brace, which is not allowed
|
// This is a dangling open-brace, which is not allowed
|
||||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||||
|
|
@ -186,14 +211,22 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawParameter = context.Capture();
|
var text = context.Capture();
|
||||||
var decoded = rawParameter.Replace("}}", "}").Replace("{{", "{");
|
if (text == "{}")
|
||||||
|
{
|
||||||
|
context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inside = text.Substring(1, text.Length - 2);
|
||||||
|
var decoded = inside.Replace("}}", "}").Replace("{{", "{");
|
||||||
|
|
||||||
// At this point, we need to parse the raw name for inline constraint,
|
// At this point, we need to parse the raw name for inline constraint,
|
||||||
// default values and optional parameters.
|
// default values and optional parameters.
|
||||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded);
|
var templatePart = InlineRouteParameterParser.ParseRouteParameter(text, decoded);
|
||||||
|
|
||||||
if (templatePart.IsCatchAll && templatePart.IsOptional)
|
// See #475 - this is here because InlineRouteParameterParser can't return errors
|
||||||
|
if (decoded.StartsWith("*") && decoded.EndsWith("?"))
|
||||||
{
|
{
|
||||||
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
|
context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -212,7 +245,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
var parameterName = templatePart.Name;
|
var parameterName = templatePart.Name;
|
||||||
if (IsValidParameterName(context, parameterName))
|
if (IsValidParameterName(context, parameterName))
|
||||||
{
|
{
|
||||||
segment.Parts.Add(templatePart);
|
parts.Add(templatePart);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -221,22 +254,20 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ParseLiteral(TemplateParserContext context, TemplateSegment segment)
|
private static bool ParseLiteral(TemplateParserContext context, List<RoutePatternPart> parts)
|
||||||
{
|
{
|
||||||
context.Mark();
|
context.Mark();
|
||||||
|
|
||||||
string encoded;
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (context.Current == Separator)
|
if (context.Current == Separator)
|
||||||
{
|
{
|
||||||
encoded = context.Capture();
|
// End of the segment
|
||||||
context.Back();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (context.Current == OpenBrace)
|
else if (context.Current == OpenBrace)
|
||||||
{
|
{
|
||||||
if (!context.Next())
|
if (!context.MoveNext())
|
||||||
{
|
{
|
||||||
// This is a dangling open-brace, which is not allowed
|
// This is a dangling open-brace, which is not allowed
|
||||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||||
|
|
@ -249,16 +280,14 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We've just seen the start of a parameter, so back up and return
|
// We've just seen the start of a parameter, so back up.
|
||||||
context.Back();
|
|
||||||
encoded = context.Capture();
|
|
||||||
context.Back();
|
context.Back();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (context.Current == CloseBrace)
|
else if (context.Current == CloseBrace)
|
||||||
{
|
{
|
||||||
if (!context.Next())
|
if (!context.MoveNext())
|
||||||
{
|
{
|
||||||
// This is a dangling close-brace, which is not allowed
|
// This is a dangling close-brace, which is not allowed
|
||||||
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
context.Error = Resources.TemplateRoute_MismatchedParameter;
|
||||||
|
|
@ -277,17 +306,17 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.Next())
|
if (!context.MoveNext())
|
||||||
{
|
{
|
||||||
encoded = context.Capture();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var encoded = context.Capture();
|
||||||
var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
|
var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
|
||||||
if (IsValidLiteral(context, decoded))
|
if (IsValidLiteral(context, decoded))
|
||||||
{
|
{
|
||||||
segment.Parts.Add(TemplatePart.CreateLiteral(decoded));
|
parts.Add(RoutePatternPart.CreateLiteralFromText(encoded, decoded));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -296,7 +325,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAllValid(TemplateParserContext context, List<TemplateSegment> segments)
|
private static bool IsAllValid(TemplateParserContext context, List<RoutePatternPathSegment> segments)
|
||||||
{
|
{
|
||||||
// A catch-all parameter must be the last part of the last segment
|
// A catch-all parameter must be the last part of the last segment
|
||||||
for (var i = 0; i < segments.Count; i++)
|
for (var i = 0; i < segments.Count; i++)
|
||||||
|
|
@ -306,7 +335,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
{
|
{
|
||||||
var part = segment.Parts[j];
|
var part = segment.Parts[j];
|
||||||
if (part.IsParameter &&
|
if (part.IsParameter &&
|
||||||
part.IsCatchAll &&
|
((RoutePatternParameter)part).IsCatchAll &&
|
||||||
(i != segments.Count - 1 || j != segment.Parts.Count - 1))
|
(i != segments.Count - 1 || j != segment.Parts.Count - 1))
|
||||||
{
|
{
|
||||||
context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
|
context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
|
||||||
|
|
@ -318,13 +347,13 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment)
|
private static bool IsSegmentValid(TemplateParserContext context, List<RoutePatternPart> parts)
|
||||||
{
|
{
|
||||||
// If a segment has multiple parts, then it can't contain a catch all.
|
// If a segment has multiple parts, then it can't contain a catch all.
|
||||||
for (var i = 0; i < segment.Parts.Count; i++)
|
for (var i = 0; i < parts.Count; i++)
|
||||||
{
|
{
|
||||||
var part = segment.Parts[i];
|
var part = parts[i];
|
||||||
if (part.IsParameter && part.IsCatchAll && segment.Parts.Count > 1)
|
if (part.IsParameter && ((RoutePatternParameter)part).IsCatchAll && parts.Count > 1)
|
||||||
{
|
{
|
||||||
context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment;
|
context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -333,30 +362,32 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
// if a segment has multiple parts, then only the last one parameter can be optional
|
// if a segment has multiple parts, then only the last one parameter can be optional
|
||||||
// if it is following a optional seperator.
|
// if it is following a optional seperator.
|
||||||
for (var i = 0; i < segment.Parts.Count; i++)
|
for (var i = 0; i < parts.Count; i++)
|
||||||
{
|
{
|
||||||
var part = segment.Parts[i];
|
var part = parts[i];
|
||||||
|
|
||||||
if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
|
if (part.IsParameter && ((RoutePatternParameter)part).IsOptional && parts.Count > 1)
|
||||||
{
|
{
|
||||||
// This optional parameter is the last part in the segment
|
// This optional parameter is the last part in the segment
|
||||||
if (i == segment.Parts.Count - 1)
|
if (i == parts.Count - 1)
|
||||||
{
|
{
|
||||||
if (!segment.Parts[i - 1].IsLiteral)
|
var previousPart = parts[i - 1];
|
||||||
|
|
||||||
|
if (!previousPart.IsLiteral && !previousPart.IsSeparator)
|
||||||
{
|
{
|
||||||
// The optional parameter is preceded by something that is not a literal.
|
// The optional parameter is preceded by something that is not a literal or separator
|
||||||
// Example of error message:
|
// Example of error message:
|
||||||
// "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded
|
// "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded
|
||||||
// by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter.
|
// by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter.
|
||||||
context.Error = string.Format(
|
context.Error = string.Format(
|
||||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||||
segment.DebuggerToString(),
|
RoutePatternPathSegment.DebuggerToString(parts),
|
||||||
part.Name,
|
((RoutePatternParameter)part).Name,
|
||||||
segment.Parts[i - 1].DebuggerToString());
|
parts[i - 1].DebuggerToString());
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (segment.Parts[i - 1].Text != PeriodString)
|
else if (previousPart is RoutePatternLiteral literal && literal.Content != PeriodString)
|
||||||
{
|
{
|
||||||
// The optional parameter is preceded by a literal other than period.
|
// The optional parameter is preceded by a literal other than period.
|
||||||
// Example of error message:
|
// Example of error message:
|
||||||
|
|
@ -364,29 +395,26 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// by an invalid segment '-'. Only a period (.) can precede an optional parameter.
|
// by an invalid segment '-'. Only a period (.) can precede an optional parameter.
|
||||||
context.Error = string.Format(
|
context.Error = string.Format(
|
||||||
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
|
||||||
segment.DebuggerToString(),
|
RoutePatternPathSegment.DebuggerToString(parts),
|
||||||
part.Name,
|
((RoutePatternParameter)part).Name,
|
||||||
segment.Parts[i - 1].Text);
|
parts[i - 1].DebuggerToString());
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
segment.Parts[i - 1].IsOptionalSeperator = true;
|
parts[i - 1] = RoutePatternPart.CreateSeparatorFromText(previousPart.RawText, ((RoutePatternLiteral)previousPart).Content);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This optional parameter is not the last one in the segment
|
// This optional parameter is not the last one in the segment
|
||||||
// Example:
|
// Example:
|
||||||
// An optional parameter must be at the end of the segment.In the segment '{RouteValue?})',
|
// An optional parameter must be at the end of the segment. In the segment '{RouteValue?})',
|
||||||
// optional parameter 'RouteValue' is followed by ')'
|
// optional parameter 'RouteValue' is followed by ')'
|
||||||
var nextPart = segment.Parts[i + 1];
|
|
||||||
var invalidPartText = nextPart.IsParameter ? nextPart.Name : nextPart.Text;
|
|
||||||
|
|
||||||
context.Error = string.Format(
|
context.Error = string.Format(
|
||||||
Resources.TemplateRoute_OptionalParameterHasTobeTheLast,
|
Resources.TemplateRoute_OptionalParameterHasTobeTheLast,
|
||||||
segment.DebuggerToString(),
|
RoutePatternPathSegment.DebuggerToString(parts),
|
||||||
segment.Parts[i].Name,
|
((RoutePatternParameter)part).Name,
|
||||||
invalidPartText);
|
parts[i + 1].DebuggerToString());
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -395,9 +423,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
// A segment cannot contain two consecutive parameters
|
// A segment cannot contain two consecutive parameters
|
||||||
var isLastSegmentParameter = false;
|
var isLastSegmentParameter = false;
|
||||||
for (var i = 0; i < segment.Parts.Count; i++)
|
for (var i = 0; i < parts.Count; i++)
|
||||||
{
|
{
|
||||||
var part = segment.Parts[i];
|
var part = parts[i];
|
||||||
if (part.IsParameter && isLastSegmentParameter)
|
if (part.IsParameter && isLastSegmentParameter)
|
||||||
{
|
{
|
||||||
context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
|
context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
|
||||||
|
|
@ -412,28 +440,15 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
private static bool IsValidParameterName(TemplateParserContext context, string parameterName)
|
private static bool IsValidParameterName(TemplateParserContext context, string parameterName)
|
||||||
{
|
{
|
||||||
if (parameterName.Length == 0)
|
if (parameterName.Length == 0 || parameterName.IndexOfAny(InvalidParameterNameChars) >= 0)
|
||||||
{
|
{
|
||||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
context.Error = Resources.FormatTemplateRoute_InvalidParameterName(parameterName);
|
||||||
Resources.TemplateRoute_InvalidParameterName, parameterName);
|
|
||||||
return false;
|
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))
|
if (!context.ParameterNames.Add(parameterName))
|
||||||
{
|
{
|
||||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
context.Error = Resources.FormatTemplateRoute_RepeatedParameter(parameterName);
|
||||||
Resources.TemplateRoute_RepeatedParameter, parameterName);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -447,20 +462,20 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
if (literal.IndexOf(QuestionMark) != -1)
|
if (literal.IndexOf(QuestionMark) != -1)
|
||||||
{
|
{
|
||||||
context.Error = String.Format(CultureInfo.CurrentCulture,
|
context.Error = Resources.FormatTemplateRoute_InvalidLiteral(literal);
|
||||||
Resources.TemplateRoute_InvalidLiteral, literal);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsInvalidRouteTemplate(string routeTemplate)
|
private static bool IsInvalidPattern(string routeTemplate)
|
||||||
{
|
{
|
||||||
return routeTemplate.StartsWith("~", StringComparison.Ordinal) ||
|
return routeTemplate.StartsWith("~", StringComparison.Ordinal) ||
|
||||||
routeTemplate.StartsWith("/", StringComparison.Ordinal);
|
routeTemplate.StartsWith("/", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
private class TemplateParserContext
|
private class TemplateParserContext
|
||||||
{
|
{
|
||||||
private readonly string _template;
|
private readonly string _template;
|
||||||
|
|
@ -482,6 +497,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
|
get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int Index => _index;
|
||||||
|
|
||||||
public string Error
|
public string Error
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|
@ -498,13 +515,21 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return --_index >= 0;
|
return --_index >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Next()
|
public bool AtEnd()
|
||||||
|
{
|
||||||
|
return _index >= _template.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
return ++_index < _template.Length;
|
return ++_index < _template.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Mark()
|
public void Mark()
|
||||||
{
|
{
|
||||||
|
Debug.Assert(_index >= 0);
|
||||||
|
|
||||||
|
// Index is always the index of the character *past* Current - we want to 'mark' Current.
|
||||||
_mark = _index;
|
_mark = _index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -521,6 +546,26 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string DebuggerToString()
|
||||||
|
{
|
||||||
|
if (_index == -1)
|
||||||
|
{
|
||||||
|
return _template;
|
||||||
|
}
|
||||||
|
else if (_mark.HasValue)
|
||||||
|
{
|
||||||
|
return _template.Substring(0, _mark.Value) +
|
||||||
|
"|" +
|
||||||
|
_template.Substring(_mark.Value, _index - _mark.Value) +
|
||||||
|
"|" +
|
||||||
|
_template.Substring(_index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _template.Substring(0, _index) + "|" + _template.Substring(_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,235 @@
|
||||||
|
// 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.Patterns
|
||||||
|
{
|
||||||
|
public abstract class RoutePatternPart
|
||||||
|
{
|
||||||
|
// This class is not an extensibility point. It is abstract so we can extend it
|
||||||
|
// or add semantics later inside the library.
|
||||||
|
internal RoutePatternPart()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternLiteral CreateLiteral(string content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.IndexOf('?') >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidLiteral(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePatternLiteral(null, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternLiteral CreateLiteralFromText(string rawText, string content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.IndexOf('?') >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidLiteral(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePatternLiteral(rawText, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternSeparator CreateSeparator(string content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePatternSeparator(null, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternSeparator CreateSeparatorFromText(string rawText, string content)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePatternSeparator(rawText, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameter(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateParameterFromText(null, name, null, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameterFromText(string rawText, string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateParameterFromText(rawText, name, null, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameter(string name, object defaultValue)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateParameterFromText(null, name, defaultValue, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameterFromText(string rawText, string name, object defaultValue)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateParameterFromText(rawText, name, defaultValue, RoutePatternParameterKind.Standard, Array.Empty<ConstraintReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameter(
|
||||||
|
string name,
|
||||||
|
object defaultValue,
|
||||||
|
RoutePatternParameterKind parameterKind)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateParameterFromText(null, name, defaultValue, parameterKind, Array.Empty<ConstraintReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameterFromText(
|
||||||
|
string rawText,
|
||||||
|
string name,
|
||||||
|
object defaultValue,
|
||||||
|
RoutePatternParameterKind parameterKind)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateParameterFromText(rawText, name, defaultValue, parameterKind, Array.Empty<ConstraintReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameter(
|
||||||
|
string name,
|
||||||
|
object defaultValue,
|
||||||
|
RoutePatternParameterKind parameterKind,
|
||||||
|
params ConstraintReference[] constraints)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePatternParameter(null, name, defaultValue, parameterKind, constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoutePatternParameter CreateParameterFromText(
|
||||||
|
string rawText,
|
||||||
|
string name,
|
||||||
|
object defaultValue,
|
||||||
|
RoutePatternParameterKind parameterKind,
|
||||||
|
params ConstraintReference[] constraints)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue != null && parameterKind == RoutePatternParameterKind.Optional)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(Resources.TemplateRoute_OptionalCannotHaveDefaultValue, nameof(parameterKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RoutePatternParameter(rawText, name, defaultValue, parameterKind, constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract RoutePatternPartKind PartKind { get; }
|
||||||
|
|
||||||
|
public abstract string RawText { get; }
|
||||||
|
|
||||||
|
public bool IsLiteral => PartKind == RoutePatternPartKind.Literal;
|
||||||
|
|
||||||
|
public bool IsParameter => PartKind == RoutePatternPartKind.Parameter;
|
||||||
|
|
||||||
|
public bool IsSeparator => PartKind == RoutePatternPartKind.Separator;
|
||||||
|
|
||||||
|
internal abstract string DebuggerToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
public enum RoutePatternPartKind
|
||||||
|
{
|
||||||
|
Literal,
|
||||||
|
Parameter,
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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.Patterns
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
public sealed class RoutePatternPathSegment
|
||||||
|
{
|
||||||
|
internal RoutePatternPathSegment(string rawText, RoutePatternPart[] parts)
|
||||||
|
{
|
||||||
|
RawText = rawText;
|
||||||
|
Parts = parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSimple => Parts.Count == 1;
|
||||||
|
|
||||||
|
public IReadOnlyList<RoutePatternPart> Parts { get; }
|
||||||
|
|
||||||
|
public string RawText { get; set; }
|
||||||
|
|
||||||
|
internal string DebuggerToString()
|
||||||
|
{
|
||||||
|
return RawText ?? DebuggerToString(Parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string DebuggerToString(IReadOnlyList<RoutePatternPart> parts)
|
||||||
|
{
|
||||||
|
return string.Join(string.Empty, parts.Select(p => p.DebuggerToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{DebuggerToString()}")]
|
||||||
|
public sealed class RoutePatternSeparator : RoutePatternPart
|
||||||
|
{
|
||||||
|
internal RoutePatternSeparator(string rawText, string content)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(content));
|
||||||
|
|
||||||
|
RawText = rawText;
|
||||||
|
Content = content;
|
||||||
|
|
||||||
|
PartKind = RoutePatternPartKind.Separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Content { get; }
|
||||||
|
|
||||||
|
public override RoutePatternPartKind PartKind { get; }
|
||||||
|
|
||||||
|
public override string RawText { get; }
|
||||||
|
|
||||||
|
internal override string DebuggerToString()
|
||||||
|
{
|
||||||
|
return RawText ?? Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
// 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.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Dispatcher.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
@ -24,6 +24,20 @@ 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>
|
||||||
|
/// Value cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Argument_NullOrEmpty
|
||||||
|
{
|
||||||
|
get => GetString("Argument_NullOrEmpty");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatArgument_NullOrEmpty()
|
||||||
|
=> GetString("Argument_NullOrEmpty");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,9 @@
|
||||||
<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="Argument_NullOrEmpty" xml:space="preserve">
|
||||||
|
<value>Value cannot be null or empty.</value>
|
||||||
|
</data>
|
||||||
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
|
<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>
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
// 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,9 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
public static class InlineRouteParameterParser
|
public static class InlineRouteParameterParser
|
||||||
{
|
{
|
||||||
|
[Obsolete(
|
||||||
|
"This API is obsolete and will be removed in a future release. It does not report errors correctly. " +
|
||||||
|
"Use 'TemplateParser.Parse()' and filter for the desired parameter as an alternative.")]
|
||||||
public static TemplatePart ParseRouteParameter(string routeParameter)
|
public static TemplatePart ParseRouteParameter(string routeParameter)
|
||||||
{
|
{
|
||||||
if (routeParameter == null)
|
if (routeParameter == null)
|
||||||
|
|
@ -15,7 +18,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
throw new ArgumentNullException(nameof(routeParameter));
|
throw new ArgumentNullException(nameof(routeParameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
var inner = AspNetCore.Dispatcher.InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
// See: #475 - this API has no way to pass the 'raw' text
|
||||||
|
var inner = AspNetCore.Dispatcher.Patterns.InlineRouteParameterParser.ParseRouteParameter(string.Empty, routeParameter);
|
||||||
return new TemplatePart(inner);
|
return new TemplatePart(inner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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 Other = Microsoft.AspNetCore.Dispatcher.Patterns.ConstraintReference;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -24,14 +25,14 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
Constraint = constraint;
|
Constraint = constraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InlineConstraint(AspNetCore.Dispatcher.InlineConstraint constraint)
|
public InlineConstraint(Other other)
|
||||||
{
|
{
|
||||||
if (constraint == null)
|
if (other == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(constraint));
|
throw new ArgumentNullException(nameof(other));
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint = constraint.Constraint;
|
Constraint = other.Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Other = Microsoft.AspNetCore.Dispatcher.Patterns.RoutePattern;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -13,10 +14,10 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
private const string SeparatorString = "/";
|
private const string SeparatorString = "/";
|
||||||
|
|
||||||
public RouteTemplate(AspNetCore.Dispatcher.RouteTemplate routeTemplate)
|
public RouteTemplate(Other other)
|
||||||
{
|
{
|
||||||
TemplateText = routeTemplate.TemplateText;
|
TemplateText = other.RawText;
|
||||||
Segments = new List<TemplateSegment>(routeTemplate.Segments.Select(p => new TemplateSegment(p)));
|
Segments = new List<TemplateSegment>(other.PathSegments.Select(p => new TemplateSegment(p)));
|
||||||
Parameters = new List<TemplatePart>();
|
Parameters = new List<TemplatePart>();
|
||||||
for (var i = 0; i < Segments.Count; i++)
|
for (var i = 0; i < Segments.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Assert(part.IsLiteral);
|
Debug.Assert(part.IsLiteral || part.IsOptionalSeperator);
|
||||||
lastLiteral = part;
|
lastLiteral = part;
|
||||||
|
|
||||||
var startIndex = lastIndex - 1;
|
var startIndex = lastIndex - 1;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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 Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -14,8 +15,15 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
throw new ArgumentNullException(routeTemplate);
|
throw new ArgumentNullException(routeTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
var inner = AspNetCore.Dispatcher.TemplateParser.Parse(routeTemplate);
|
try
|
||||||
return new RouteTemplate(inner);
|
{
|
||||||
|
var inner = Microsoft.AspNetCore.Dispatcher.Patterns.RoutePattern.Parse(routeTemplate);
|
||||||
|
return new RouteTemplate(inner);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex) when (ex.InnerException is RoutePatternException)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(ex.InnerException.Message, nameof(routeTemplate), ex.InnerException);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Other = Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternPart;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -15,17 +16,34 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TemplatePart(AspNetCore.Dispatcher.TemplatePart templatePart)
|
public TemplatePart(Other other)
|
||||||
{
|
{
|
||||||
IsCatchAll = templatePart.IsCatchAll;
|
IsLiteral = other.IsLiteral || other.IsSeparator;
|
||||||
IsLiteral = templatePart.IsLiteral;
|
IsParameter = other.IsParameter;
|
||||||
IsOptional = templatePart.IsOptional;
|
|
||||||
IsOptionalSeperator = templatePart.IsOptionalSeperator;
|
if (other.IsLiteral && other is Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternLiteral literal)
|
||||||
IsParameter = templatePart.IsParameter;
|
{
|
||||||
Name = templatePart.Name;
|
Text = literal.Content;
|
||||||
Text = templatePart.Text;
|
}
|
||||||
DefaultValue = templatePart.DefaultValue;
|
else if (other.IsParameter && other is Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternParameter parameter)
|
||||||
InlineConstraints = templatePart.InlineConstraints?.Select(p => new InlineConstraint(p));
|
{
|
||||||
|
// Text is unused by TemplatePart and assumed to be null when the part is a parameter.
|
||||||
|
Name = parameter.Name;
|
||||||
|
IsCatchAll = parameter.IsCatchAll;
|
||||||
|
IsOptional = parameter.IsOptional;
|
||||||
|
DefaultValue = parameter.DefaultValue;
|
||||||
|
InlineConstraints = parameter.Constraints?.Select(p => new InlineConstraint(p));
|
||||||
|
}
|
||||||
|
else if (other.IsSeparator && other is Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternSeparator separator)
|
||||||
|
{
|
||||||
|
Text = separator.Content;
|
||||||
|
IsOptionalSeperator = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Unreachable
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TemplatePart CreateLiteral(string text)
|
public static TemplatePart CreateLiteral(string text)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Other = Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternPathSegment;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Template
|
namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
|
|
@ -14,9 +15,9 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TemplateSegment(AspNetCore.Dispatcher.TemplateSegment templateSegment)
|
public TemplateSegment(Other other)
|
||||||
{
|
{
|
||||||
Parts = new List<TemplatePart>(templateSegment.Parts.Select(s => new TemplatePart(s)));
|
Parts = new List<TemplatePart>(other.Parts.Select(s => new TemplatePart(s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSimple => Parts.Count == 1;
|
public bool IsSimple => Parts.Count == 1;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
{
|
{
|
||||||
public class InlineRouteParameterParserTests
|
public class InlineRouteParameterParserTest
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("=")]
|
[InlineData("=")]
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(parameterName, templatePart.Name);
|
Assert.Equal(parameterName, templatePart.Name);
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.Empty(templatePart.InlineConstraints);
|
Assert.Empty(templatePart.Constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("", templatePart.DefaultValue);
|
Assert.Equal("", templatePart.DefaultValue);
|
||||||
Assert.Empty(templatePart.InlineConstraints);
|
Assert.Empty(templatePart.Constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -43,8 +43,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Empty(constraint.Constraint);
|
Assert.Empty(constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -56,8 +56,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("", templatePart.DefaultValue);
|
Assert.Equal("", templatePart.DefaultValue);
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Empty(constraint.Constraint);
|
Assert.Empty(constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal(":", templatePart.DefaultValue);
|
Assert.Equal(":", templatePart.DefaultValue);
|
||||||
Assert.Empty(templatePart.InlineConstraints);
|
Assert.Empty(templatePart.Constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -82,8 +82,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("111111", templatePart.DefaultValue);
|
Assert.Equal("111111", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("int", constraint.Constraint);
|
Assert.Equal("int", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -96,8 +96,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("111111", templatePart.DefaultValue);
|
Assert.Equal("111111", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\d+)", constraint.Constraint);
|
Assert.Equal(@"test(\d+)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -110,8 +110,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("int", constraint.Constraint);
|
Assert.Equal("int", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -125,8 +125,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("12", templatePart.DefaultValue);
|
Assert.Equal("12", templatePart.DefaultValue);
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("int", constraint.Constraint);
|
Assert.Equal("int", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -140,8 +140,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("12?", templatePart.DefaultValue);
|
Assert.Equal("12?", templatePart.DefaultValue);
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("int", constraint.Constraint);
|
Assert.Equal("int", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -154,8 +154,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\d+)", constraint.Constraint);
|
Assert.Equal(@"test(\d+)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -170,8 +170,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("abc", templatePart.DefaultValue);
|
Assert.Equal("abc", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\d+)", constraint.Constraint);
|
Assert.Equal(@"test(\d+)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -183,9 +183,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(d+)", constraint.Content),
|
||||||
constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
|
constraint => Assert.Equal(@"test(w+)", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -197,11 +197,11 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Empty(constraint.Constraint),
|
constraint => Assert.Empty(constraint.Content),
|
||||||
constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(d+)", constraint.Content),
|
||||||
constraint => Assert.Empty(constraint.Constraint),
|
constraint => Assert.Empty(constraint.Content),
|
||||||
constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
|
constraint => Assert.Equal(@"test(w+)", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -213,9 +213,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(\d+)", constraint.Content),
|
||||||
constraint => Assert.Equal(@"test(\w:+)", constraint.Constraint));
|
constraint => Assert.Equal(@"test(\w:+)", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -229,9 +229,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("qwer", templatePart.DefaultValue);
|
Assert.Equal("qwer", templatePart.DefaultValue);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(\d+)", constraint.Content),
|
||||||
constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
|
constraint => Assert.Equal(@"test(\w+)", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -245,10 +245,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("=qwer", templatePart.DefaultValue);
|
Assert.Equal("=qwer", templatePart.DefaultValue);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(\d+)", constraint.Content),
|
||||||
constraint => Assert.Empty(constraint.Constraint),
|
constraint => Assert.Empty(constraint.Content),
|
||||||
constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
|
constraint => Assert.Equal(@"test(\w+)", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -264,27 +264,27 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("comparison-operator", templatePart.Name);
|
Assert.Equal("comparison-operator", templatePart.Name);
|
||||||
Assert.Equal(defaultValue, templatePart.DefaultValue);
|
Assert.Equal(defaultValue, templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("length(6)", constraint.Constraint);
|
Assert.Equal("length(6)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
|
public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
|
||||||
{
|
{
|
||||||
// Arrange & Act
|
// Arrange & Act
|
||||||
var template = ParseRouteTemplate(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
|
var routePattern = RoutePattern.Parse(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var parameters = template.Parameters.ToArray();
|
var parameters = routePattern.Parameters.ToArray();
|
||||||
|
|
||||||
var param1 = parameters[0];
|
var param1 = parameters[0];
|
||||||
Assert.Equal("p1", param1.Name);
|
Assert.Equal("p1", param1.Name);
|
||||||
Assert.Equal("hello", param1.DefaultValue);
|
Assert.Equal("hello", param1.DefaultValue);
|
||||||
Assert.False(param1.IsOptional);
|
Assert.False(param1.IsOptional);
|
||||||
|
|
||||||
Assert.Collection(param1.InlineConstraints,
|
Assert.Collection(param1.Constraints,
|
||||||
constraint => Assert.Equal("int", constraint.Constraint),
|
constraint => Assert.Equal("int", constraint.Content),
|
||||||
constraint => Assert.Equal("test(3)", constraint.Constraint)
|
constraint => Assert.Equal("test(3)", constraint.Content)
|
||||||
);
|
);
|
||||||
|
|
||||||
var param2 = parameters[1];
|
var param2 = parameters[1];
|
||||||
|
|
@ -327,8 +327,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\})", constraint.Constraint);
|
Assert.Equal(@"test(\})", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -342,8 +342,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("wer", templatePart.DefaultValue);
|
Assert.Equal("wer", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\})", constraint.Constraint);
|
Assert.Equal(@"test(\})", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -355,8 +355,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\))", constraint.Constraint);
|
Assert.Equal(@"test(\))", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -370,8 +370,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("fsd", templatePart.DefaultValue);
|
Assert.Equal("fsd", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\))", constraint.Constraint);
|
Assert.Equal(@"test(\))", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -383,8 +383,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(:)", constraint.Constraint);
|
Assert.Equal(@"test(:)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -398,8 +398,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("mnf", templatePart.DefaultValue);
|
Assert.Equal("mnf", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(:)", constraint.Constraint);
|
Assert.Equal(@"test(:)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -411,8 +411,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(a:b:c)", constraint.Constraint);
|
Assert.Equal(@"test(a:b:c)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -426,8 +426,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("12", templatePart.DefaultValue);
|
Assert.Equal("12", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("test", constraint.Constraint);
|
Assert.Equal("test", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -441,9 +441,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("12", templatePart.DefaultValue);
|
Assert.Equal("12", templatePart.DefaultValue);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Empty(constraint.Constraint),
|
constraint => Assert.Empty(constraint.Content),
|
||||||
constraint => Assert.Equal("test", constraint.Constraint));
|
constraint => Assert.Equal("test", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -455,9 +455,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(":param", templatePart.Name);
|
Assert.Equal(":param", templatePart.Name);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal("test", constraint.Constraint),
|
constraint => Assert.Equal("test", constraint.Content),
|
||||||
constraint => Assert.Empty(constraint.Constraint));
|
constraint => Assert.Empty(constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -469,8 +469,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\w,\w)", constraint.Constraint);
|
Assert.Equal(@"test(\w,\w)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -482,8 +482,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("par,am", templatePart.Name);
|
Assert.Equal("par,am", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\w)", constraint.Constraint);
|
Assert.Equal(@"test(\w)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -497,8 +497,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("jsd", templatePart.DefaultValue);
|
Assert.Equal("jsd", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\w,\w)", constraint.Constraint);
|
Assert.Equal(@"test(\w,\w)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -513,8 +513,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("int", constraint.Constraint);
|
Assert.Equal("int", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -527,8 +527,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("test(=)", constraint.Constraint);
|
Assert.Equal("test(=)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -552,8 +552,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("test(a==b)", constraint.Constraint);
|
Assert.Equal("test(a==b)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -566,8 +566,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("dvds", templatePart.DefaultValue);
|
Assert.Equal("dvds", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("test(a==b)", constraint.Constraint);
|
Assert.Equal("test(a==b)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -624,8 +624,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("sds", templatePart.DefaultValue);
|
Assert.Equal("sds", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("test(=)", constraint.Constraint);
|
Assert.Equal("test(=)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -637,8 +637,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\{)", constraint.Constraint);
|
Assert.Equal(@"test(\{)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -650,8 +650,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("par{am", templatePart.Name);
|
Assert.Equal("par{am", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\sd)", constraint.Constraint);
|
Assert.Equal(@"test(\sd)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -665,8 +665,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("xvc", templatePart.DefaultValue);
|
Assert.Equal("xvc", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\{)", constraint.Constraint);
|
Assert.Equal(@"test(\{)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -678,8 +678,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("par(am", templatePart.Name);
|
Assert.Equal("par(am", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\()", constraint.Constraint);
|
Assert.Equal(@"test(\()", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -691,8 +691,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\()", constraint.Constraint);
|
Assert.Equal(@"test(\()", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -704,8 +704,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal("test(#$%", constraint.Constraint);
|
Assert.Equal("test(#$%", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -717,9 +717,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(#", constraint.Constraint),
|
constraint => Assert.Equal(@"test(#", constraint.Content),
|
||||||
constraint => Assert.Equal(@"test1", constraint.Constraint));
|
constraint => Assert.Equal(@"test1", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -732,10 +732,10 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("default-value", templatePart.DefaultValue);
|
Assert.Equal("default-value", templatePart.DefaultValue);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Content),
|
||||||
constraint => Assert.Equal(@"name(test1", constraint.Constraint),
|
constraint => Assert.Equal(@"name(test1", constraint.Content),
|
||||||
constraint => Assert.Equal(@"differentname", constraint.Constraint));
|
constraint => Assert.Equal(@"differentname", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -748,8 +748,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("param", templatePart.Name);
|
Assert.Equal("param", templatePart.Name);
|
||||||
Assert.Equal("test1", templatePart.DefaultValue);
|
Assert.Equal("test1", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(constraintvalue", constraint.Constraint);
|
Assert.Equal(@"test(constraintvalue", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -763,8 +763,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
Assert.Equal("djk", templatePart.DefaultValue);
|
Assert.Equal("djk", templatePart.DefaultValue);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\()", constraint.Constraint);
|
Assert.Equal(@"test(\()", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -778,8 +778,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
Assert.Equal(@"test(\?)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -793,8 +793,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
Assert.Equal(@"test(\?)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -808,8 +808,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("sdf", templatePart.DefaultValue);
|
Assert.Equal("sdf", templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
Assert.Equal(@"test(\?)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -823,8 +823,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("sdf", templatePart.DefaultValue);
|
Assert.Equal("sdf", templatePart.DefaultValue);
|
||||||
Assert.True(templatePart.IsOptional);
|
Assert.True(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
Assert.Equal(@"test(\?)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -838,8 +838,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(\?)", constraint.Constraint);
|
Assert.Equal(@"test(\?)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -853,9 +853,9 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
Assert.Collection(templatePart.InlineConstraints,
|
Assert.Collection(templatePart.Constraints,
|
||||||
constraint => Assert.Equal(@"test(#)", constraint.Constraint),
|
constraint => Assert.Equal(@"test(#)", constraint.Content),
|
||||||
constraint => Assert.Equal(@"$)", constraint.Constraint));
|
constraint => Assert.Equal(@"$)", constraint.Content));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -869,8 +869,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"test(#:)$)", constraint.Constraint);
|
Assert.Equal(@"test(#:)$)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -884,8 +884,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Constraint);
|
Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -899,8 +899,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
|
Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -914,8 +914,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Assert.Equal("123-456-7890", templatePart.DefaultValue);
|
Assert.Equal("123-456-7890", templatePart.DefaultValue);
|
||||||
Assert.False(templatePart.IsOptional);
|
Assert.False(templatePart.IsOptional);
|
||||||
|
|
||||||
var constraint = Assert.Single(templatePart.InlineConstraints);
|
var constraint = Assert.Single(templatePart.Constraints);
|
||||||
Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
|
Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -935,20 +935,16 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedParameterName, templatePart.Name);
|
Assert.Equal(expectedParameterName, templatePart.Name);
|
||||||
Assert.Empty(templatePart.InlineConstraints);
|
Assert.Empty(templatePart.Constraints);
|
||||||
Assert.Null(templatePart.DefaultValue);
|
Assert.Null(templatePart.DefaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TemplatePart ParseParameter(string routeParameter)
|
private RoutePatternParameter ParseParameter( string routeParameter)
|
||||||
{
|
{
|
||||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
// See: #475 - these tests don't pass the 'whole' text.
|
||||||
|
var templatePart = InlineRouteParameterParser.ParseRouteParameter(string.Empty, routeParameter);
|
||||||
return templatePart;
|
return templatePart;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RouteTemplate ParseRouteTemplate(string template)
|
|
||||||
{
|
|
||||||
return TemplateParser.Parse(template);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,781 @@
|
||||||
|
// 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;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Dispatcher.Patterns
|
||||||
|
{
|
||||||
|
public class RoutePatternParameterParserTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Parse_SingleLiteral()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "cool";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("cool", RoutePatternPart.CreateLiteralFromText("cool", "cool"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_SingleParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("{p}", RoutePatternPart.CreateParameterFromText("{p}", "p"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_OptionalParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p?}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("{p?}", RoutePatternPart.CreateParameterFromText("{p?}", "p", null, RoutePatternParameterKind.Optional));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_MultipleLiterals()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "cool/awesome/super";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("cool", RoutePatternPart.CreateLiteralFromText("cool", "cool"));
|
||||||
|
builder.AddPathSegment("awesome", RoutePatternPart.CreateLiteralFromText("awesome", "awesome"));
|
||||||
|
builder.AddPathSegment("super", RoutePatternPart.CreateLiteralFromText("super", "super"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_MultipleParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}/{p2}/{*p3}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("{p1}", RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
|
||||||
|
builder.AddPathSegment("{p2}", RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
|
||||||
|
builder.AddPathSegment("{*p3}", RoutePatternPart.CreateParameterFromText("{*p3}", "p3", null, RoutePatternParameterKind.CatchAll));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_LP()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "cool-{p1}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"cool-{p1}",
|
||||||
|
RoutePatternPart.CreateLiteralFromText("cool-", "cool-"),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_PL()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}-cool";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}-cool",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText("-cool", "-cool"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_PLP()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}-cool-{p2}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}-cool",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText("-cool-", "-cool-"),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_LPL()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "cool-{p1}-awesome";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
template,
|
||||||
|
RoutePatternPart.CreateLiteralFromText("cool-", "cool-"),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText("-awesome", "-awesome"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}.{p2?}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}.{p2?}",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateSeparatorFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2?}", "p2", null, RoutePatternParameterKind.Optional));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_ParametersFollowingPeriod()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}.{p2}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}.{p2}",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}.{p2}.{p3?}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}.{p2}.{p3?}",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2}", "p2"),
|
||||||
|
RoutePatternPart.CreateSeparatorFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p3?}", "p3", null, RoutePatternParameterKind.Optional));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_ThreeParametersSeperatedByPeriod()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}.{p2}.{p3}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}.{p2}.{p3}",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2}", "p2"),
|
||||||
|
RoutePatternPart.CreateLiteralFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p3}", "p3"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}.{p2?}/{p3}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
"{p1}.{p2?}",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
|
||||||
|
RoutePatternPart.CreateSeparatorFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2?}", "p2", null, RoutePatternParameterKind.Optional));
|
||||||
|
builder.AddPathSegment("{p3}", RoutePatternPart.CreateParameterFromText("{p3}", "p3"));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p1}/{p2}.{p3?}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("{p1}", RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
|
||||||
|
builder.AddPathSegment("{p2}.{p3?}",
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p2}", "p2"),
|
||||||
|
RoutePatternPart.CreateSeparatorFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p3?}", "p3", null, RoutePatternParameterKind.Optional));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = "{p2}/.{p3?}";
|
||||||
|
|
||||||
|
var builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment("{p2}", RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
|
||||||
|
builder.AddPathSegment(".{p3?}",
|
||||||
|
RoutePatternPart.CreateSeparatorFromText(".", "."),
|
||||||
|
RoutePatternPart.CreateParameterFromText("{p3?}", "p3", null, RoutePatternParameterKind.Optional));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 builder = RoutePatternBuilder.Create(template);
|
||||||
|
builder.AddPathSegment(
|
||||||
|
template,
|
||||||
|
RoutePatternPart.CreateParameterFromText(
|
||||||
|
template,
|
||||||
|
"p1",
|
||||||
|
null,
|
||||||
|
RoutePatternParameterKind.Standard,
|
||||||
|
ConstraintReference.CreateFromText(constraint, constraint)));
|
||||||
|
|
||||||
|
var expected = builder.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = RoutePatternParser.Parse(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse(template),
|
||||||
|
"There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
|
||||||
|
"'}' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse(template),
|
||||||
|
"In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse(template),
|
||||||
|
"An optional parameter must be at the end of the segment. In the segment '" + template +
|
||||||
|
"', optional parameter '" + parameter + "' is followed by '" + invalid + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_WithRepeatedParameter()
|
||||||
|
{
|
||||||
|
var ex = ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{Controller}.mvc/{id}/{controller}"),
|
||||||
|
"The route parameter name 'controller' appears more than one time in the route template.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse(template),
|
||||||
|
@"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||||
|
"matching '}' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{*p1}/{*p2}"),
|
||||||
|
"A catch-all parameter can only appear as the last segment of the route template.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotHaveCatchAllWithNoName()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<RoutePatternException>(() => RoutePatternParser.Parse(template), expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("foo/{{p1}"),
|
||||||
|
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||||
|
"matching '}' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("foo/{p1}}"),
|
||||||
|
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||||
|
"matching '}' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_SameParameterTwiceThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{aaa}/{AAA}"),
|
||||||
|
"The route parameter name 'AAA' appears more than one time in the route template.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{aaa}/{*AAA}"),
|
||||||
|
"The route parameter name 'AAA' appears more than one time in the route template.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{a}/{aa}a}/{z}"),
|
||||||
|
"There is an incomplete parameter in the route template. Check that each '{' character has a " +
|
||||||
|
"matching '}' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{a}/{a{aa}/{z}"),
|
||||||
|
"In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{a}//{z}"),
|
||||||
|
"The route template separator character '/' cannot appear consecutively. It must be separated by " +
|
||||||
|
"either a parameter or a literal value.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("foo/{p1}/{*p2}/{p3}"),
|
||||||
|
"A catch-all parameter can only appear as the last segment of the route template.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_RepeatedParametersThrows()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("foo/aa{p1}{p2}"),
|
||||||
|
"A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
|
||||||
|
"a literal string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotStartWithSlash()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("/foo"),
|
||||||
|
"The route template cannot start with a '/' or '~' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotStartWithTilde()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("~foo"),
|
||||||
|
"The route template cannot start with a '/' or '~' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CannotContainQuestionMark()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("foor?bar"),
|
||||||
|
"The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidTemplate_CatchAllMarkedOptional()
|
||||||
|
{
|
||||||
|
ExceptionAssert.Throws<RoutePatternException>(
|
||||||
|
() => RoutePatternParser.Parse("{a}/{*b?}"),
|
||||||
|
"A catch-all parameter cannot be marked optional.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RoutePatternEqualityComparer : IEqualityComparer<RoutePattern>
|
||||||
|
{
|
||||||
|
public bool Equals(RoutePattern x, RoutePattern y)
|
||||||
|
{
|
||||||
|
if (x == null && y == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (x == null || y == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.Equals(x.RawText, y.RawText, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.PathSegments.Count != y.PathSegments.Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < x.PathSegments.Count; i++)
|
||||||
|
{
|
||||||
|
if (x.PathSegments[i].RawText == y.PathSegments[i].RawText)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.PathSegments[i].Parts.Count != y.PathSegments[i].Parts.Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < x.PathSegments[i].Parts.Count; j++)
|
||||||
|
{
|
||||||
|
if (!Equals(x.PathSegments[i].Parts[j], y.PathSegments[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(RoutePatternPart x, RoutePatternPart y)
|
||||||
|
{
|
||||||
|
if (x.GetType() != y.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.IsLiteral && y.IsLiteral)
|
||||||
|
{
|
||||||
|
return Equals((RoutePatternLiteral)x, (RoutePatternLiteral)y);
|
||||||
|
}
|
||||||
|
else if (x.IsParameter && y.IsParameter)
|
||||||
|
{
|
||||||
|
return Equals((RoutePatternParameter)x, (RoutePatternParameter)y);
|
||||||
|
}
|
||||||
|
else if (x.IsSeparator && y.IsSeparator)
|
||||||
|
{
|
||||||
|
return Equals((RoutePatternSeparator)x, (RoutePatternSeparator)y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Fail("This should not be reachable. Do you need to update the comparison logic?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(RoutePatternLiteral x, RoutePatternLiteral y)
|
||||||
|
{
|
||||||
|
return x.RawText == y.RawText && x.Content == y.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(RoutePatternParameter x, RoutePatternParameter y)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
x.RawText == y.RawText &&
|
||||||
|
x.Name == y.Name &&
|
||||||
|
x.DefaultValue == y.DefaultValue &&
|
||||||
|
x.ParameterKind == y.ParameterKind &&
|
||||||
|
Enumerable.SequenceEqual(x.Constraints.Select(c => (c.Content, c.RawText)), y.Constraints.Select(c => (c.Content, c.RawText)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(RoutePatternSeparator x, RoutePatternSeparator y)
|
||||||
|
{
|
||||||
|
return x.RawText == y.RawText && x.Content == y.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(RoutePattern obj)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,916 +0,0 @@
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -948,7 +948,9 @@ namespace Microsoft.AspNetCore.Routing.Tests
|
||||||
private TemplatePart ParseParameter(string routeParameter)
|
private TemplatePart ParseParameter(string routeParameter)
|
||||||
{
|
{
|
||||||
var _constraintResolver = GetConstraintResolver();
|
var _constraintResolver = GetConstraintResolver();
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
return templatePart;
|
return templatePart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -525,8 +525,8 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
|
[InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
|
||||||
[InlineData("{p1?}{p2}", "p1", "p2")]
|
[InlineData("{p1?}{p2}", "p1", "{p2}")]
|
||||||
[InlineData("{p1?}{p2?}", "p1", "p2")]
|
[InlineData("{p1?}{p2?}", "p1", "{p2?}")]
|
||||||
[InlineData("{p1}.{p2?})", "p2", ")")]
|
[InlineData("{p1}.{p2?})", "p2", ")")]
|
||||||
[InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
|
[InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
|
||||||
public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
|
public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue