Skip to content

Commit 4093dc0

Browse files
Parse versions single header CSVs. Fixes #1070
1 parent 59ff9e6 commit 4093dc0

File tree

2 files changed

+173
-16
lines changed

2 files changed

+173
-16
lines changed

Diff for: src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs

+57-16
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@
55

66
namespace Asp.Versioning.Http;
77

8+
#if NET
9+
using System.Buffers;
10+
#endif
811
using System.Collections;
12+
#if NET
13+
using static System.StringSplitOptions;
14+
#endif
915

1016
/// <summary>
1117
/// Represents an enumerator of API versions from a HTTP header.
1218
/// </summary>
1319
public readonly struct ApiVersionEnumerator : IEnumerable<ApiVersion>
1420
{
15-
private readonly IEnumerable<string> values;
21+
private readonly string[] values;
1622
private readonly IApiVersionParser parser;
1723

1824
/// <summary>
@@ -29,37 +35,72 @@ public ApiVersionEnumerator(
2935
ArgumentNullException.ThrowIfNull( response );
3036
ArgumentException.ThrowIfNullOrEmpty( headerName );
3137

32-
this.values =
33-
response.Headers.TryGetValues( headerName, out var values )
34-
? values
35-
: Enumerable.Empty<string>();
36-
38+
this.values = response.Headers.TryGetValues( headerName, out var values ) ? values.ToArray() : [];
3739
this.parser = parser ?? ApiVersionParser.Default;
3840
}
3941

4042
/// <inheritdoc />
4143
public IEnumerator<ApiVersion> GetEnumerator()
4244
{
43-
using var iterator = values.GetEnumerator();
45+
#if NETSTANDARD
46+
for ( var i = 0; i < values.Length; i++ )
47+
{
48+
var items = values[i].Split( ',' );
4449

45-
if ( !iterator.MoveNext() )
50+
for ( var j = 0; j < items.Length; j++ )
51+
{
52+
var item = items[j].Trim();
53+
54+
if ( item.Length > 0 && parser.TryParse( item, out var result ) )
55+
{
56+
yield return result!;
57+
}
58+
}
59+
}
60+
#else
61+
for ( var i = 0; i < values.Length; i++ )
4662
{
47-
yield break;
63+
var (count, versions) = ParseVersions( values[i] );
64+
65+
for ( var j = 0; j < count; j++ )
66+
{
67+
yield return versions[j];
68+
}
4869
}
70+
#endif
71+
}
4972

50-
if ( parser.TryParse( iterator.Current, out var value ) )
73+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
74+
#if NET
75+
private (int Count, ApiVersion[] Results) ParseVersions( ReadOnlySpan<char> value )
76+
{
77+
var pool = ArrayPool<Range>.Shared;
78+
var ranges = pool.Rent( 5 );
79+
var length = value.Split( ranges, ',', RemoveEmptyEntries | TrimEntries );
80+
81+
while ( length >= ranges.Length )
5182
{
52-
yield return value!;
83+
pool.Return( ranges );
84+
length <<= 1;
85+
ranges = pool.Rent( length );
86+
length = value.Split( ranges, ',', RemoveEmptyEntries | TrimEntries );
5387
}
5488

55-
while ( iterator.MoveNext() )
89+
var results = new ApiVersion[length];
90+
var count = 0;
91+
92+
for ( var i = 0; i < length; i++ )
5693
{
57-
if ( parser.TryParse( iterator.Current, out value ) )
94+
var text = value[ranges[i]];
95+
96+
if ( text.Length > 0 && parser.TryParse( text, out var result ) )
5897
{
59-
yield return value!;
98+
results[count++] = result;
6099
}
61100
}
62-
}
63101

64-
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
102+
pool.Return( ranges );
103+
return (count, results);
104+
}
105+
#endif
65106
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.Http;
4+
5+
public class ApiVersionEnumeratorTest
6+
{
7+
[Fact]
8+
public void enumerator_should_process_single_header_value()
9+
{
10+
// arrange
11+
var response = new HttpResponseMessage();
12+
13+
response.Headers.Add( "api-supported-versions", "1.0" );
14+
15+
var enumerator = new ApiVersionEnumerator( response, "api-supported-versions" );
16+
17+
// act
18+
var results = enumerator.ToArray();
19+
20+
// assert
21+
results.Should().BeEquivalentTo( [new ApiVersion( 1.0 )] );
22+
}
23+
24+
[Fact]
25+
public void enumerator_should_process_multiple_header_values()
26+
{
27+
// arrange
28+
var response = new HttpResponseMessage();
29+
30+
response.Headers.Add( "api-supported-versions", ["1.0", "2.0"] );
31+
32+
var enumerator = new ApiVersionEnumerator( response, "api-supported-versions" );
33+
34+
// act
35+
var results = enumerator.ToArray();
36+
37+
// assert
38+
results.Should().BeEquivalentTo( new ApiVersion[] { new( 1.0 ), new( 2.0 ) } );
39+
}
40+
41+
[Theory]
42+
[InlineData( "1.0,2.0" )]
43+
[InlineData( "1.0, 2.0" )]
44+
[InlineData( "1.0,,2.0" )]
45+
[InlineData( "1.0, abc, 2.0" )]
46+
public void enumerator_should_process_single_header_comma_separated_values( string value )
47+
{
48+
// arrange
49+
var response = new HttpResponseMessage();
50+
51+
response.Headers.Add( "api-supported-versions", [value] );
52+
53+
var enumerator = new ApiVersionEnumerator( response, "api-supported-versions" );
54+
55+
// act
56+
var results = enumerator.ToArray();
57+
58+
// assert
59+
results.Should().BeEquivalentTo( new ApiVersion[] { new( 1.0 ), new( 2.0 ) } );
60+
}
61+
62+
[Fact]
63+
public void enumerator_should_process_many_header_comma_separated_values()
64+
{
65+
// arrange
66+
const string Value = "1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0";
67+
var response = new HttpResponseMessage();
68+
69+
response.Headers.Add( "api-supported-versions", [Value] );
70+
71+
var enumerator = new ApiVersionEnumerator( response, "api-supported-versions" );
72+
73+
// act
74+
var results = enumerator.ToArray();
75+
76+
// assert
77+
results.Should().BeEquivalentTo(
78+
new ApiVersion[]
79+
{
80+
new( 1.0 ),
81+
new( 2.0 ),
82+
new( 3.0 ),
83+
new( 4.0 ),
84+
new( 5.0 ),
85+
new( 6.0 ),
86+
new( 7.0 ),
87+
new( 8.0 ),
88+
new( 9.0 ),
89+
new( 10.0 ),
90+
} );
91+
}
92+
93+
[Fact]
94+
public void enumerator_should_process_multiple_header_comma_separated_values()
95+
{
96+
// arrange
97+
var response = new HttpResponseMessage();
98+
99+
response.Headers.Add( "api-supported-versions", ["1.0, 2.0", "3.0, 4.0"] );
100+
101+
var enumerator = new ApiVersionEnumerator( response, "api-supported-versions" );
102+
103+
// act
104+
var results = enumerator.ToArray();
105+
106+
// assert
107+
results.Should().BeEquivalentTo(
108+
new ApiVersion[]
109+
{
110+
new( 1.0 ),
111+
new( 2.0 ),
112+
new( 3.0 ),
113+
new( 4.0 ),
114+
} );
115+
}
116+
}

0 commit comments

Comments
 (0)