Skip to content

Commit accbceb

Browse files
authored
Support multiple policies for a key with RoutePatternFactory.Pattern (#6593)
1 parent 7d50675 commit accbceb

File tree

2 files changed

+133
-7
lines changed

2 files changed

+133
-7
lines changed

src/Http/Routing/src/Patterns/RoutePatternFactory.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections;
56
using System.Collections.Generic;
67
using System.Collections.ObjectModel;
78
using System.Linq;
@@ -51,6 +52,7 @@ public static RoutePattern Parse(string pattern)
5152
/// Additional parameter policies to associated with the route pattern. May be null.
5253
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
5354
/// and then merged into the parsed route pattern.
55+
/// Multiple policies can be specified for a key by providing a collection as the value.
5456
/// </param>
5557
/// <returns>The <see cref="RoutePattern"/>.</returns>
5658
public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies)
@@ -78,6 +80,7 @@ public static RoutePattern Parse(string pattern, object defaults, object paramet
7880
/// Additional parameter policies to associated with the route pattern. May be null.
7981
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
8082
/// and then merged into the parsed route pattern.
83+
/// Multiple policies can be specified for a key by providing a collection as the value.
8184
/// </param>
8285
/// <param name="requiredValues">
8386
/// Route values that can be substituted for parameters in the route pattern. See remarks on <see cref="RoutePattern.RequiredValues"/>.
@@ -138,6 +141,7 @@ public static RoutePattern Pattern(string rawText, IEnumerable<RoutePatternPathS
138141
/// Additional parameter policies to associated with the route pattern. May be null.
139142
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
140143
/// and then merged into the route pattern.
144+
/// Multiple policies can be specified for a key by providing a collection as the value.
141145
/// </param>
142146
/// <param name="segments">The collection of segments.</param>
143147
/// <returns>The <see cref="RoutePattern"/>.</returns>
@@ -168,6 +172,7 @@ public static RoutePattern Pattern(
168172
/// Additional parameter policies to associated with the route pattern. May be null.
169173
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
170174
/// and then merged into the route pattern.
175+
/// Multiple policies can be specified for a key by providing a collection as the value.
171176
/// </param>
172177
/// <param name="segments">The collection of segments.</param>
173178
/// <returns>The <see cref="RoutePattern"/>.</returns>
@@ -229,6 +234,7 @@ public static RoutePattern Pattern(string rawText, params RoutePatternPathSegmen
229234
/// Additional parameter policies to associated with the route pattern. May be null.
230235
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
231236
/// and then merged into the route pattern.
237+
/// Multiple policies can be specified for a key by providing a collection as the value.
232238
/// </param>
233239
/// <param name="segments">The collection of segments.</param>
234240
/// <returns>The <see cref="RoutePattern"/>.</returns>
@@ -259,6 +265,7 @@ public static RoutePattern Pattern(
259265
/// Additional parameter policies to associated with the route pattern. May be null.
260266
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
261267
/// and then merged into the route pattern.
268+
/// Multiple policies can be specified for a key by providing a collection as the value.
262269
/// </param>
263270
/// <param name="segments">The collection of segments.</param>
264271
/// <returns>The <see cref="RoutePattern"/>.</returns>
@@ -312,12 +319,33 @@ private static RoutePattern PatternCore(
312319

313320
foreach (var kvp in parameterPolicies)
314321
{
315-
updatedParameterPolicies.Add(kvp.Key, new List<RoutePatternParameterPolicyReference>()
322+
var policyReferences = new List<RoutePatternParameterPolicyReference>();
323+
324+
if (kvp.Value is IParameterPolicy parameterPolicy)
325+
{
326+
policyReferences.Add(ParameterPolicy(parameterPolicy));
327+
}
328+
else if (kvp.Value is string)
316329
{
317-
kvp.Value is IParameterPolicy parameterPolicy
318-
? ParameterPolicy(parameterPolicy)
319-
: Constraint(kvp.Value), // Constraint will convert string values into regex constraints
320-
});
330+
// Constraint will convert string values into regex constraints
331+
policyReferences.Add(Constraint(kvp.Value));
332+
}
333+
else if (kvp.Value is IEnumerable multiplePolicies)
334+
{
335+
foreach (var item in multiplePolicies)
336+
{
337+
// Constraint will convert string values into regex constraints
338+
policyReferences.Add(item is IParameterPolicy p ? ParameterPolicy(p) : Constraint(item));
339+
}
340+
}
341+
else
342+
{
343+
throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
344+
kvp.Value ?? "null",
345+
typeof(IRouteConstraint)));
346+
}
347+
348+
updatedParameterPolicies.Add(kvp.Key, policyReferences);
321349
}
322350
}
323351

src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -218,6 +218,104 @@ public void Pattern_ExtraConstraints()
218218
});
219219
}
220220

221+
[Fact]
222+
public void Pattern_ExtraConstraints_MultipleConstraintsForKey()
223+
{
224+
// Arrange
225+
var template = "{a}/{b}/{c}";
226+
var defaults = new { };
227+
var constraints = new { d = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
228+
229+
var original = RoutePatternFactory.Parse(template);
230+
231+
// Act
232+
var actual = RoutePatternFactory.Pattern(
233+
original.RawText,
234+
defaults,
235+
constraints,
236+
original.PathSegments);
237+
238+
// Assert
239+
Assert.Collection(
240+
actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
241+
kvp =>
242+
{
243+
Assert.Equal("d", kvp.Key);
244+
Assert.Collection(
245+
kvp.Value,
246+
c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
247+
c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
248+
c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
249+
});
250+
}
251+
252+
[Fact]
253+
public void Pattern_ExtraConstraints_MergeMultipleConstraintsForKey()
254+
{
255+
// Arrange
256+
var template = "{a:int}/{b}/{c:int}";
257+
var defaults = new { };
258+
var constraints = new { b = "fizz", c = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
259+
260+
var original = RoutePatternFactory.Parse(template);
261+
262+
// Act
263+
var actual = RoutePatternFactory.Pattern(
264+
original.RawText,
265+
defaults,
266+
constraints,
267+
original.PathSegments);
268+
269+
// Assert
270+
Assert.Collection(
271+
actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
272+
kvp =>
273+
{
274+
Assert.Equal("a", kvp.Key);
275+
Assert.Collection(
276+
kvp.Value,
277+
c => Assert.Equal("int", c.Content));
278+
},
279+
kvp =>
280+
{
281+
Assert.Equal("b", kvp.Key);
282+
Assert.Collection(
283+
kvp.Value,
284+
c => Assert.Equal("^(fizz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
285+
},
286+
kvp =>
287+
{
288+
Assert.Equal("c", kvp.Key);
289+
Assert.Collection(
290+
kvp.Value,
291+
c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
292+
c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
293+
c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
294+
c => Assert.Equal("int", c.Content));
295+
});
296+
}
297+
298+
[Fact]
299+
public void Pattern_ExtraConstraints_NestedArray_Throws()
300+
{
301+
// Arrange
302+
var template = "{a}/{b}/{c:int}";
303+
var defaults = new { };
304+
var constraints = new { c = new object[] { new object[0] } };
305+
306+
var original = RoutePatternFactory.Parse(template);
307+
308+
// Act & Assert
309+
Assert.Throws<InvalidOperationException>(() =>
310+
{
311+
RoutePatternFactory.Pattern(
312+
original.RawText,
313+
defaults,
314+
constraints,
315+
original.PathSegments);
316+
});
317+
}
318+
221319
[Fact]
222320
public void Pattern_ExtraConstraints_RouteConstraint()
223321
{

0 commit comments

Comments
 (0)