Skip to content

Commit f7ff6fe

Browse files
Collapse the DefaultApiVersionGroupDescriptionProvider into the GroupedApiVersionDescriptionProvider and remove it
1 parent 9e5f4d3 commit f7ff6fe

6 files changed

Lines changed: 204 additions & 150 deletions

File tree

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>
@@ -72,11 +74,7 @@ protected virtual IReadOnlyList<ApiVersionDescription> Describe( IReadOnlyList<G
7274
/// <summary>
7375
/// Represents the API version metadata applied to an endpoint with an optional group name.
7476
/// </summary>
75-
protected class GroupedApiVersionMetadata :
76-
ApiVersionMetadata,
77-
IEquatable<GroupedApiVersionMetadata>,
78-
IGroupedApiVersionMetadata,
79-
IGroupedApiVersionMetadataFactory<GroupedApiVersionMetadata>
77+
protected class GroupedApiVersionMetadata : ApiVersionMetadata, IEquatable<GroupedApiVersionMetadata>
8078
{
8179
/// <summary>
8280
/// Initializes a new instance of the <see cref="GroupedApiVersionMetadata"/> class.
@@ -92,10 +90,6 @@ public GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata
9290
/// <value>The associated group name, if any.</value>
9391
public string? GroupName { get; }
9492

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

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs

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

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs

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

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs

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

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs

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

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs

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

0 commit comments

Comments
 (0)