Skip to content

Commit

Permalink
Support multiple policies for a key with RoutePatternFactory.Pattern (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Jan 12, 2019
1 parent 7d50675 commit accbceb
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 7 deletions.
40 changes: 34 additions & 6 deletions src/Http/Routing/src/Patterns/RoutePatternFactory.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
Expand Down Expand Up @@ -51,6 +52,7 @@ public static RoutePattern Parse(string pattern)
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the parsed route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies)
Expand Down Expand Up @@ -78,6 +80,7 @@ public static RoutePattern Parse(string pattern, object defaults, object paramet
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the parsed route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="requiredValues">
/// Route values that can be substituted for parameters in the route pattern. See remarks on <see cref="RoutePattern.RequiredValues"/>.
Expand Down Expand Up @@ -138,6 +141,7 @@ public static RoutePattern Pattern(string rawText, IEnumerable<RoutePatternPathS
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
Expand Down Expand Up @@ -168,6 +172,7 @@ public static RoutePattern Pattern(
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
Expand Down Expand Up @@ -229,6 +234,7 @@ public static RoutePattern Pattern(string rawText, params RoutePatternPathSegmen
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
Expand Down Expand Up @@ -259,6 +265,7 @@ public static RoutePattern Pattern(
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
Expand Down Expand Up @@ -312,12 +319,33 @@ private static RoutePattern PatternCore(

foreach (var kvp in parameterPolicies)
{
updatedParameterPolicies.Add(kvp.Key, new List<RoutePatternParameterPolicyReference>()
var policyReferences = new List<RoutePatternParameterPolicyReference>();

if (kvp.Value is IParameterPolicy parameterPolicy)
{
policyReferences.Add(ParameterPolicy(parameterPolicy));
}
else if (kvp.Value is string)
{
kvp.Value is IParameterPolicy parameterPolicy
? ParameterPolicy(parameterPolicy)
: Constraint(kvp.Value), // Constraint will convert string values into regex constraints
});
// Constraint will convert string values into regex constraints
policyReferences.Add(Constraint(kvp.Value));
}
else if (kvp.Value is IEnumerable multiplePolicies)
{
foreach (var item in multiplePolicies)
{
// Constraint will convert string values into regex constraints
policyReferences.Add(item is IParameterPolicy p ? ParameterPolicy(p) : Constraint(item));
}
}
else
{
throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
kvp.Value ?? "null",
typeof(IRouteConstraint)));
}

updatedParameterPolicies.Add(kvp.Key, policyReferences);
}
}

Expand Down
100 changes: 99 additions & 1 deletion src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
Expand Down Expand Up @@ -218,6 +218,104 @@ public void Pattern_ExtraConstraints()
});
}

[Fact]
public void Pattern_ExtraConstraints_MultipleConstraintsForKey()
{
// Arrange
var template = "{a}/{b}/{c}";
var defaults = new { };
var constraints = new { d = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };

var original = RoutePatternFactory.Parse(template);

// Act
var actual = RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);

// Assert
Assert.Collection(
actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal("d", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
});
}

[Fact]
public void Pattern_ExtraConstraints_MergeMultipleConstraintsForKey()
{
// Arrange
var template = "{a:int}/{b}/{c:int}";
var defaults = new { };
var constraints = new { b = "fizz", c = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };

var original = RoutePatternFactory.Parse(template);

// Act
var actual = RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);

// Assert
Assert.Collection(
actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal("a", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("int", c.Content));
},
kvp =>
{
Assert.Equal("b", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("^(fizz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
},
kvp =>
{
Assert.Equal("c", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("int", c.Content));
});
}

[Fact]
public void Pattern_ExtraConstraints_NestedArray_Throws()
{
// Arrange
var template = "{a}/{b}/{c:int}";
var defaults = new { };
var constraints = new { c = new object[] { new object[0] } };

var original = RoutePatternFactory.Parse(template);

// Act & Assert
Assert.Throws<InvalidOperationException>(() =>
{
RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);
});
}

[Fact]
public void Pattern_ExtraConstraints_RouteConstraint()
{
Expand Down

0 comments on commit accbceb

Please sign in to comment.