Skip to content

Commit fba9885

Browse files
Collapse the DefaultApiVersionGroupDescriptionProvider into the GroupedApiVersionDescriptionProvider and remove it
1 parent c691d85 commit fba9885

10 files changed

+205
-427
lines changed

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ public IApiVersionDescriptionProvider Create( EndpointDataSource endpointDataSou
3636

3737
collators.AddRange( providers );
3838

39-
return new DefaultApiVersionDescriptionProvider( collators, sunsetPolicyManager, options );
39+
return new GroupedApiVersionDescriptionProvider( collators, sunsetPolicyManager, options );
4040
}
4141
}

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs

Lines changed: 0 additions & 110 deletions
This file was deleted.

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs

Lines changed: 204 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
namespace Asp.Versioning.ApiExplorer;
44

5-
using Asp.Versioning.ApiExplorer.Internal;
65
using Microsoft.Extensions.Options;
6+
using static Asp.Versioning.ApiVersionMapping;
7+
using static System.Globalization.CultureInfo;
78

89
/// <summary>
9-
/// Represents the default implementation of an object that discovers and describes the API version information within an application.
10+
/// Represents the default implementation of an object that discovers and describes the API version information within
11+
/// an application.
1012
/// </summary>
1113
[CLSCompliant( false )]
1214
public class GroupedApiVersionDescriptionProvider : IApiVersionDescriptionProvider
1315
{
14-
private readonly ApiVersionDescriptionCollection<GroupedApiVersionMetadata> collection;
16+
private readonly ApiVersionDescriptionCollection collection;
1517
private readonly IOptions<ApiExplorerOptions> options;
1618

1719
/// <summary>
@@ -63,11 +65,7 @@ protected virtual IReadOnlyList<ApiVersionDescription> Describe( IReadOnlyList<G
6365
/// <summary>
6466
/// Represents the API version metadata applied to an endpoint with an optional group name.
6567
/// </summary>
66-
protected class GroupedApiVersionMetadata :
67-
ApiVersionMetadata,
68-
IEquatable<GroupedApiVersionMetadata>,
69-
IGroupedApiVersionMetadata,
70-
IGroupedApiVersionMetadataFactory<GroupedApiVersionMetadata>
68+
protected class GroupedApiVersionMetadata : ApiVersionMetadata, IEquatable<GroupedApiVersionMetadata>
7169
{
7270
/// <summary>
7371
/// Initializes a new instance of the <see cref="GroupedApiVersionMetadata"/> class.
@@ -83,10 +81,6 @@ public GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata
8381
/// <value>The associated group name, if any.</value>
8482
public string? GroupName { get; }
8583

86-
static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory<GroupedApiVersionMetadata>.New(
87-
string? groupName,
88-
ApiVersionMetadata metadata ) => new( groupName, metadata );
89-
9084
/// <inheritdoc />
9185
public bool Equals( GroupedApiVersionMetadata? other ) =>
9286
other is not null && other.GetHashCode() == GetHashCode();
@@ -112,4 +106,202 @@ public override int GetHashCode()
112106
return hash.ToHashCode();
113107
}
114108
}
109+
110+
private record struct GroupedApiVersion( string? GroupName, ApiVersion ApiVersion );
111+
112+
private sealed class ApiVersionDescriptionCollection(
113+
Func<IReadOnlyList<GroupedApiVersionMetadata>, IReadOnlyList<ApiVersionDescription>> describe,
114+
IEnumerable<IApiVersionMetadataCollationProvider> collators )
115+
{
116+
private readonly Lock syncRoot = new();
117+
private readonly Func<IReadOnlyList<GroupedApiVersionMetadata>, IReadOnlyList<ApiVersionDescription>> describe = describe;
118+
private readonly IApiVersionMetadataCollationProvider[] collators = [.. collators];
119+
private IReadOnlyList<ApiVersionDescription>? items;
120+
private int version;
121+
122+
public IReadOnlyList<ApiVersionDescription> Items
123+
{
124+
get
125+
{
126+
if ( items is not null && version == ComputeVersion() )
127+
{
128+
return items;
129+
}
130+
131+
using ( syncRoot.EnterScope() )
132+
{
133+
var currentVersion = ComputeVersion();
134+
135+
if ( items is not null && version == currentVersion )
136+
{
137+
return items;
138+
}
139+
140+
var context = new ApiVersionMetadataCollationContext();
141+
142+
for ( var i = 0; i < collators.Length; i++ )
143+
{
144+
collators[i].Execute( context );
145+
}
146+
147+
var results = context.Results;
148+
var metadata = new GroupedApiVersionMetadata[results.Count];
149+
150+
for ( var i = 0; i < metadata.Length; i++ )
151+
{
152+
metadata[i] = new GroupedApiVersionMetadata( context.Results.GroupName( i ), results[i] );
153+
}
154+
155+
items = describe( metadata );
156+
version = currentVersion;
157+
}
158+
159+
return items;
160+
}
161+
}
162+
163+
private int ComputeVersion() =>
164+
collators.Length switch
165+
{
166+
0 => 0,
167+
1 => collators[0].Version,
168+
_ => ComputeVersion( collators ),
169+
};
170+
171+
private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers )
172+
{
173+
var hash = default( HashCode );
174+
175+
for ( var i = 0; i < providers.Length; i++ )
176+
{
177+
hash.Add( providers[i].Version );
178+
}
179+
180+
return hash.ToHashCode();
181+
}
182+
}
183+
184+
private sealed class ApiVersionDescriptionComparer : IComparer<ApiVersionDescription>
185+
{
186+
public int Compare( ApiVersionDescription? x, ApiVersionDescription? y )
187+
{
188+
if ( x is null )
189+
{
190+
return y is null ? 0 : -1;
191+
}
192+
193+
if ( y is null )
194+
{
195+
return 1;
196+
}
197+
198+
var result = x.ApiVersion.CompareTo( y.ApiVersion );
199+
200+
if ( result == 0 )
201+
{
202+
result = StringComparer.Ordinal.Compare( x.GroupName, y.GroupName );
203+
}
204+
205+
return result;
206+
}
207+
}
208+
209+
private static class DescriptionProvider
210+
{
211+
internal static ApiVersionDescription[] Describe(
212+
IReadOnlyList<GroupedApiVersionMetadata> metadata,
213+
ISunsetPolicyManager sunsetPolicyManager,
214+
ApiExplorerOptions options )
215+
{
216+
var descriptions = new SortedSet<ApiVersionDescription>( new ApiVersionDescriptionComparer() );
217+
var supported = new HashSet<GroupedApiVersion>();
218+
var deprecated = new HashSet<GroupedApiVersion>();
219+
220+
BucketizeApiVersions( metadata, supported, deprecated, options );
221+
AppendDescriptions( descriptions, supported, sunsetPolicyManager, options, deprecated: false );
222+
AppendDescriptions( descriptions, deprecated, sunsetPolicyManager, options, deprecated: true );
223+
224+
return [.. descriptions];
225+
}
226+
227+
private static void BucketizeApiVersions(
228+
IReadOnlyList<GroupedApiVersionMetadata> list,
229+
HashSet<GroupedApiVersion> supported,
230+
HashSet<GroupedApiVersion> deprecated,
231+
ApiExplorerOptions options )
232+
{
233+
var declared = new HashSet<GroupedApiVersion>();
234+
var advertisedSupported = new HashSet<GroupedApiVersion>();
235+
var advertisedDeprecated = new HashSet<GroupedApiVersion>();
236+
237+
for ( var i = 0; i < list.Count; i++ )
238+
{
239+
var metadata = list[i];
240+
var groupName = metadata.GroupName;
241+
var model = metadata.Map( Explicit | Implicit );
242+
var versions = model.DeclaredApiVersions;
243+
244+
for ( var j = 0; j < versions.Count; j++ )
245+
{
246+
declared.Add( new( groupName, versions[j] ) );
247+
}
248+
249+
versions = model.SupportedApiVersions;
250+
251+
for ( var j = 0; j < versions.Count; j++ )
252+
{
253+
var version = versions[j];
254+
supported.Add( new( groupName, version ) );
255+
advertisedSupported.Add( new( groupName, version ) );
256+
}
257+
258+
versions = model.DeprecatedApiVersions;
259+
260+
for ( var j = 0; j < versions.Count; j++ )
261+
{
262+
var version = versions[j];
263+
deprecated.Add( new( groupName, version ) );
264+
advertisedDeprecated.Add( new( groupName, version ) );
265+
}
266+
}
267+
268+
advertisedSupported.ExceptWith( declared );
269+
advertisedDeprecated.ExceptWith( declared );
270+
supported.ExceptWith( advertisedSupported );
271+
deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) );
272+
273+
if ( supported.Count == 0 && deprecated.Count == 0 )
274+
{
275+
supported.Add( new( default, options.DefaultApiVersion ) );
276+
}
277+
}
278+
279+
private static void AppendDescriptions(
280+
SortedSet<ApiVersionDescription> descriptions,
281+
HashSet<GroupedApiVersion> versions,
282+
ISunsetPolicyManager sunsetPolicyManager,
283+
ApiExplorerOptions options,
284+
bool deprecated )
285+
{
286+
var format = options.GroupNameFormat;
287+
var formatGroupName = options.FormatGroupName;
288+
289+
foreach ( var (groupName, version) in versions )
290+
{
291+
var formattedGroupName = groupName;
292+
293+
if ( string.IsNullOrEmpty( formattedGroupName ) )
294+
{
295+
formattedGroupName = version.ToString( format, CurrentCulture );
296+
}
297+
else if ( formatGroupName is not null )
298+
{
299+
formattedGroupName = formatGroupName( formattedGroupName, version.ToString( format, CurrentCulture ) );
300+
}
301+
302+
var sunsetPolicy = sunsetPolicyManager.TryGetPolicy( version, out var policy ) ? policy : default;
303+
descriptions.Add( new( version, formattedGroupName, deprecated, sunsetPolicy ) );
304+
}
305+
}
306+
}
115307
}

0 commit comments

Comments
 (0)