Support conversions from RouteTemplate -> RoutePattern

This commit is contained in:
Ryan Nowak 2017-10-20 15:23:16 -07:00
parent df78db934d
commit bd517f891f
6 changed files with 329 additions and 44 deletions

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.AspNetCore.Dispatcher.Patterns
{
@ -16,30 +15,36 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
public IList<RoutePatternPathSegment> PathSegments { get; } = new List<RoutePatternPathSegment>();
public string Text { get; set; }
public string RawText { get; set; }
public RoutePatternBuilder AddPathSegment(RoutePatternPart part)
public RoutePatternBuilder AddPathSegment(params RoutePatternPart[] parts)
{
return AddPathSegment(null, part, Array.Empty<RoutePatternPart>());
if (parts == null)
{
throw new ArgumentNullException(nameof(parts));
}
if (parts.Length == 0)
{
throw new ArgumentException(Resources.RoutePatternBuilder_CollectionCannotBeEmpty, nameof(parts));
}
return AddPathSegmentFromText(null, parts);
}
public RoutePatternBuilder AddPathSegment(RoutePatternPart part, params RoutePatternPart[] parts)
public RoutePatternBuilder AddPathSegmentFromText(string text, params RoutePatternPart[] parts)
{
return AddPathSegment(null, part, Array.Empty<RoutePatternPart>());
}
if (parts == null)
{
throw new ArgumentNullException(nameof(parts));
}
public RoutePatternBuilder AddPathSegment(string text, RoutePatternPart part)
{
return AddPathSegment(text, part, Array.Empty<RoutePatternPart>());
}
if (parts.Length == 0)
{
throw new ArgumentException(Resources.RoutePatternBuilder_CollectionCannotBeEmpty, nameof(parts));
}
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);
var segment = new RoutePatternPathSegment(text, parts.ToArray());
PathSegments.Add(segment);
return this;
@ -61,12 +66,12 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
}
}
return new RoutePattern(Text, parameters.ToArray(), PathSegments.ToArray());
return new RoutePattern(RawText, parameters.ToArray(), PathSegments.ToArray());
}
public static RoutePatternBuilder Create(string text)
{
return new RoutePatternBuilder() { Text = text, };
return new RoutePatternBuilder() { RawText = text, };
}
}
}

View File

@ -38,6 +38,20 @@ namespace Microsoft.AspNetCore.Dispatcher
internal static string FormatArgument_NullOrEmpty()
=> GetString("Argument_NullOrEmpty");
/// <summary>
/// The collection cannot be empty.
/// </summary>
internal static string RoutePatternBuilder_CollectionCannotBeEmpty
{
get => GetString("RoutePatternBuilder_CollectionCannotBeEmpty");
}
/// <summary>
/// The collection cannot be empty.
/// </summary>
internal static string FormatRoutePatternBuilder_CollectionCannotBeEmpty()
=> GetString("RoutePatternBuilder_CollectionCannotBeEmpty");
/// <summary>
/// A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.
/// </summary>

View File

@ -124,6 +124,9 @@
<data name="Argument_NullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value>
</data>
<data name="RoutePatternBuilder_CollectionCannotBeEmpty" xml:space="preserve">
<value>The collection cannot be empty.</value>
</data>
<data name="TemplateRoute_CannotHaveCatchAllInMultiSegment" xml:space="preserve">
<value>A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter.</value>
</data>

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Dispatcher.Patterns;
using Other = Microsoft.AspNetCore.Dispatcher.Patterns.RoutePattern;
namespace Microsoft.AspNetCore.Routing.Template
@ -98,5 +99,50 @@ namespace Microsoft.AspNetCore.Routing.Template
return null;
}
/// <summary>
/// Converts the <see cref="RouteTemplate"/> to the equivalent
/// <see cref="RoutePattern"/>
/// </summary>
/// <returns>A <see cref="RoutePattern"/>.</returns>
public Other ToRoutePattern()
{
var builder = RoutePatternBuilder.Create(TemplateText);
for (var i = 0; i < Segments.Count; i++)
{
var segment = Segments[i];
var parts = new List<RoutePatternPart>();
for (var j = 0; j < segment.Parts.Count; j++)
{
var part = segment.Parts[j];
if (part.IsLiteral && part.IsOptionalSeperator)
{
parts.Add(RoutePatternPart.CreateSeparator(part.Text));
}
else if (part.IsLiteral)
{
parts.Add(RoutePatternPart.CreateLiteral(part.Text));
}
else
{
var kind = part.IsCatchAll ?
RoutePatternParameterKind.CatchAll :
part.IsOptional ?
RoutePatternParameterKind.Optional :
RoutePatternParameterKind.Standard;
var constraints = part.InlineConstraints.Select(c => ConstraintReference.Create(c.Constraint)).ToArray();
parts.Add(RoutePatternPart.CreateParameter(part.Name, part.DefaultValue, kind, constraints));
}
}
builder.AddPathSegment(parts.ToArray());
}
return builder.Build();
}
}
}

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "cool";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment("cool", RoutePatternPart.CreateLiteralFromText("cool", "cool"));
builder.AddPathSegmentFromText("cool", RoutePatternPart.CreateLiteralFromText("cool", "cool"));
var expected = builder.Build();
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment("{p}", RoutePatternPart.CreateParameterFromText("{p}", "p"));
builder.AddPathSegmentFromText("{p}", RoutePatternPart.CreateParameterFromText("{p}", "p"));
var expected = builder.Build();
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p?}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment("{p?}", RoutePatternPart.CreateParameterFromText("{p?}", "p", null, RoutePatternParameterKind.Optional));
builder.AddPathSegmentFromText("{p?}", RoutePatternPart.CreateParameterFromText("{p?}", "p", null, RoutePatternParameterKind.Optional));
var expected = builder.Build();
@ -73,9 +73,9 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
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"));
builder.AddPathSegmentFromText("cool", RoutePatternPart.CreateLiteralFromText("cool", "cool"));
builder.AddPathSegmentFromText("awesome", RoutePatternPart.CreateLiteralFromText("awesome", "awesome"));
builder.AddPathSegmentFromText("super", RoutePatternPart.CreateLiteralFromText("super", "super"));
var expected = builder.Build();
@ -93,9 +93,9 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
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));
builder.AddPathSegmentFromText("{p1}", RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
builder.AddPathSegmentFromText("{p2}", RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
builder.AddPathSegmentFromText("{*p3}", RoutePatternPart.CreateParameterFromText("{*p3}", "p3", null, RoutePatternParameterKind.CatchAll));
var expected = builder.Build();
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "cool-{p1}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"cool-{p1}",
RoutePatternPart.CreateLiteralFromText("cool-", "cool-"),
RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}-cool";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}-cool",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateLiteralFromText("-cool", "-cool"));
@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}-cool-{p2}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}-cool",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateLiteralFromText("-cool-", "-cool-"),
@ -177,7 +177,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "cool-{p1}-awesome";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
template,
RoutePatternPart.CreateLiteralFromText("cool-", "cool-"),
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}.{p2?}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}.{p2?}",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateSeparatorFromText(".", "."),
@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}.{p2}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}.{p2}",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateLiteralFromText(".", "."),
@ -243,7 +243,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}.{p2}.{p3?}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}.{p2}.{p3?}",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateLiteralFromText(".", "."),
@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}.{p2}.{p3}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}.{p2}.{p3}",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateLiteralFromText(".", "."),
@ -291,12 +291,12 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}.{p2?}/{p3}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
"{p1}.{p2?}",
RoutePatternPart.CreateParameterFromText("{p1}", "p1"),
RoutePatternPart.CreateSeparatorFromText(".", "."),
RoutePatternPart.CreateParameterFromText("{p2?}", "p2", null, RoutePatternParameterKind.Optional));
builder.AddPathSegment("{p3}", RoutePatternPart.CreateParameterFromText("{p3}", "p3"));
builder.AddPathSegmentFromText("{p3}", RoutePatternPart.CreateParameterFromText("{p3}", "p3"));
var expected = builder.Build();
@ -314,8 +314,8 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p1}/{p2}.{p3?}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment("{p1}", RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
builder.AddPathSegment("{p2}.{p3?}",
builder.AddPathSegmentFromText("{p1}", RoutePatternPart.CreateParameterFromText("{p1}", "p1"));
builder.AddPathSegmentFromText("{p2}.{p3?}",
RoutePatternPart.CreateParameterFromText("{p2}", "p2"),
RoutePatternPart.CreateSeparatorFromText(".", "."),
RoutePatternPart.CreateParameterFromText("{p3?}", "p3", null, RoutePatternParameterKind.Optional));
@ -336,8 +336,8 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
var template = "{p2}/.{p3?}";
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment("{p2}", RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
builder.AddPathSegment(".{p3?}",
builder.AddPathSegmentFromText("{p2}", RoutePatternPart.CreateParameterFromText("{p2}", "p2"));
builder.AddPathSegmentFromText(".{p3?}",
RoutePatternPart.CreateSeparatorFromText(".", "."),
RoutePatternPart.CreateParameterFromText("{p3?}", "p3", null, RoutePatternParameterKind.Optional));
@ -360,7 +360,7 @@ namespace Microsoft.AspNetCore.Dispatcher.Patterns
{
// Arrange
var builder = RoutePatternBuilder.Create(template);
builder.AddPathSegment(
builder.AddPathSegmentFromText(
template,
RoutePatternPart.CreateParameterFromText(
template,

View File

@ -0,0 +1,217 @@
// 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 Microsoft.AspNetCore.Dispatcher.Patterns;
using Xunit;
namespace Microsoft.AspNetCore.Routing.Template
{
public class RouteTemplateTest
{
[Fact]
public void ToRoutePattern_ConvertsToRoutePattern_MultipleSegments()
{
// Arrange
var routeTemplate = TemplateParser.Parse("api/{foo}");
// Act
var routePattern = routeTemplate.ToRoutePattern();
// Assert
Assert.Same(routeTemplate.TemplateText, routePattern.RawText);
Assert.Collection(
routePattern.PathSegments,
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var literal = Assert.IsType<RoutePatternLiteral>(p);
Assert.Equal("api", literal.Content);
});
},
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("foo", parameter.Name);
Assert.Equal(RoutePatternParameterKind.Standard, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Empty(parameter.Constraints);
});
});
}
[Fact]
public void ToRoutePattern_ConvertsToRoutePattern_ComplexSegment()
{
// Arrange
var routeTemplate = TemplateParser.Parse("api-{foo}");
// Act
var routePattern = routeTemplate.ToRoutePattern();
// Assert
Assert.Same(routeTemplate.TemplateText, routePattern.RawText);
Assert.Collection(
routePattern.PathSegments,
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var literal = Assert.IsType<RoutePatternLiteral>(p);
Assert.Equal("api-", literal.Content);
},
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("foo", parameter.Name);
Assert.Equal(RoutePatternParameterKind.Standard, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Empty(parameter.Constraints);
});
});
}
[Fact]
public void ToRoutePattern_ConvertsToRoutePattern_CatchAllParameter()
{
// Arrange
var routeTemplate = TemplateParser.Parse("{*foo}");
// Act
var routePattern = routeTemplate.ToRoutePattern();
// Assert
Assert.Same(routeTemplate.TemplateText, routePattern.RawText);
Assert.Collection(
routePattern.PathSegments,
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("foo", parameter.Name);
Assert.Equal(RoutePatternParameterKind.CatchAll, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Empty(parameter.Constraints);
});
});
}
[Fact]
public void ToRoutePattern_ConvertsToRoutePattern_OptionalParameter()
{
// Arrange
var routeTemplate = TemplateParser.Parse("{foo?}");
// Act
var routePattern = routeTemplate.ToRoutePattern();
// Assert
Assert.Same(routeTemplate.TemplateText, routePattern.RawText);
Assert.Collection(
routePattern.PathSegments,
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("foo", parameter.Name);
Assert.Equal(RoutePatternParameterKind.Optional, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Empty(parameter.Constraints);
});
});
}
[Fact]
public void ToRoutePattern_ConvertsToRoutePattern_Constraints()
{
// Arrange
var routeTemplate = TemplateParser.Parse("{foo:bar:baz}");
// Act
var routePattern = routeTemplate.ToRoutePattern();
// Assert
Assert.Same(routeTemplate.TemplateText, routePattern.RawText);
Assert.Collection(
routePattern.PathSegments,
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("foo", parameter.Name);
Assert.Equal(RoutePatternParameterKind.Standard, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Collection(
parameter.Constraints,
c =>
{
Assert.Equal("bar", c.Content);
},
c =>
{
Assert.Equal("baz", c.Content);
});
});
});
}
[Fact]
public void ToRoutePattern_ConvertsToRoutePattern_OptionalSeparator()
{
// Arrange
var routeTemplate = TemplateParser.Parse("{bar}.{foo?}");
// Act
var routePattern = routeTemplate.ToRoutePattern();
// Assert
Assert.Same(routeTemplate.TemplateText, routePattern.RawText);
Assert.Collection(
routePattern.PathSegments,
s =>
{
Assert.Collection(
s.Parts,
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("bar", parameter.Name);
Assert.Equal(RoutePatternParameterKind.Standard, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Empty(parameter.Constraints);
},
p =>
{
var separator = Assert.IsType<RoutePatternSeparator>(p);
Assert.Equal(".", separator.Content);
},
p =>
{
var parameter = Assert.IsType<RoutePatternParameter>(p);
Assert.Equal("foo", parameter.Name);
Assert.Equal(RoutePatternParameterKind.Optional, parameter.ParameterKind);
Assert.Null(parameter.DefaultValue);
Assert.Empty(parameter.Constraints);
});
});
}
}
}