From 59ff9e687b4f5cc7bcf1f48f701117b694a50da9 Mon Sep 17 00:00:00 2001 From: Chris Martinez Date: Fri, 12 Jan 2024 15:17:24 -0800 Subject: [PATCH] Refactor and unify IApiVersionDescriptionProvider implementations. Explicit group names can only be determined at runtime. Related #1066 --- .../DefaultApiVersionDescriptionProvider.cs | 147 +++---------- .../GroupedApiVersionDescriptionProvider.cs | 200 ++---------------- .../ApiVersionDescriptionCollection.cs | 76 +++++++ .../Internal/ApiVersionDescriptionComparer.cs | 28 +++ .../Internal/DescriptionProvider.cs | 107 ++++++++++ .../Internal/GroupedApiVersion.cs | 5 + .../Internal/IGroupedApiVersionMetadata.cs | 20 ++ .../IGroupedApiVersionMetadataFactory.cs | 9 + 8 files changed, 291 insertions(+), 301 deletions(-) create mode 100644 src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs create mode 100644 src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs create mode 100644 src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs create mode 100644 src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs create mode 100644 src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs create mode 100644 src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs index 63408cad..22151e11 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs @@ -2,9 +2,8 @@ namespace Asp.Versioning.ApiExplorer; +using Asp.Versioning.ApiExplorer.Internal; using Microsoft.Extensions.Options; -using static Asp.Versioning.ApiVersionMapping; -using static System.Globalization.CultureInfo; /// /// Represents the default implementation of an object that discovers and describes the API version information within an application. @@ -12,7 +11,7 @@ namespace Asp.Versioning.ApiExplorer; [CLSCompliant( false )] public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvider { - private readonly ApiVersionDescriptionCollection collection; + private readonly ApiVersionDescriptionCollection collection; private readonly IOptions options; /// @@ -28,7 +27,7 @@ public DefaultApiVersionDescriptionProvider( ISunsetPolicyManager sunsetPolicyManager, IOptions apiExplorerOptions ) { - collection = new( this, providers ?? throw new ArgumentNullException( nameof( providers ) ) ); + collection = new( Describe, providers ?? throw new ArgumentNullException( nameof( providers ) ) ); SunsetPolicyManager = sunsetPolicyManager; options = apiExplorerOptions; } @@ -58,133 +57,53 @@ protected virtual IReadOnlyList Describe( IReadOnlyList( capacity: metadata.Count ); - var supported = new HashSet(); - var deprecated = new HashSet(); - - BucketizeApiVersions( metadata, supported, deprecated ); - AppendDescriptions( descriptions, supported, deprecated: false ); - AppendDescriptions( descriptions, deprecated, deprecated: true ); - - return descriptions.OrderBy( d => d.ApiVersion ).ToArray(); - } - - private void BucketizeApiVersions( IReadOnlyList metadata, HashSet supported, HashSet deprecated ) - { - var declared = new HashSet(); - var advertisedSupported = new HashSet(); - var advertisedDeprecated = new HashSet(); - - for ( var i = 0; i < metadata.Count; i++ ) + // TODO: consider refactoring and removing GroupedApiVersionDescriptionProvider as both implementations are now + // effectively the same. this cast is safe as an internal implementation detail. if this method is + // overridden, then this code doesn't even run + // + // REF: https://github.com/dotnet/aspnet-api-versioning/issues/1066 + if ( metadata is GroupedApiVersionMetadata[] groupedMetadata ) { - var model = metadata[i].Map( Explicit | Implicit ); - var versions = model.DeclaredApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - declared.Add( versions[j] ); - } - - versions = model.SupportedApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - var version = versions[j]; - supported.Add( version ); - advertisedSupported.Add( version ); - } - - versions = model.DeprecatedApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - var version = versions[j]; - deprecated.Add( version ); - advertisedDeprecated.Add( version ); - } + return DescriptionProvider.Describe( groupedMetadata, SunsetPolicyManager, Options ); } - advertisedSupported.ExceptWith( declared ); - advertisedDeprecated.ExceptWith( declared ); - supported.ExceptWith( advertisedSupported ); - deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) ); - - if ( supported.Count == 0 && deprecated.Count == 0 ) - { - supported.Add( Options.DefaultApiVersion ); - } + return Array.Empty(); } - private void AppendDescriptions( List descriptions, IEnumerable versions, bool deprecated ) + private sealed class GroupedApiVersionMetadata : + ApiVersionMetadata, + IEquatable, + IGroupedApiVersionMetadata, + IGroupedApiVersionMetadataFactory { - foreach ( var version in versions ) - { - var groupName = version.ToString( Options.GroupNameFormat, CurrentCulture ); - var sunsetPolicy = SunsetPolicyManager.TryGetPolicy( version, out var policy ) ? policy : default; - descriptions.Add( new( version, groupName, deprecated, sunsetPolicy ) ); - } - } + private GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata ) + : base( metadata ) => GroupName = groupName; - private sealed class ApiVersionDescriptionCollection( - DefaultApiVersionDescriptionProvider provider, - IEnumerable collators ) - { - private readonly object syncRoot = new(); - private readonly DefaultApiVersionDescriptionProvider provider = provider; - private readonly IApiVersionMetadataCollationProvider[] collators = collators.ToArray(); - private IReadOnlyList? items; - private int version; - - public IReadOnlyList Items - { - get - { - if ( items is not null && version == ComputeVersion() ) - { - return items; - } + public string? GroupName { get; } - lock ( syncRoot ) - { - var currentVersion = ComputeVersion(); + static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory.New( + string? groupName, + ApiVersionMetadata metadata ) => new( groupName, metadata ); - if ( items is not null && version == currentVersion ) - { - return items; - } + public bool Equals( GroupedApiVersionMetadata? other ) => + other is not null && other.GetHashCode() == GetHashCode(); - var context = new ApiVersionMetadataCollationContext(); + public override bool Equals( object? obj ) => + obj is not null && + GetType().Equals( obj.GetType() ) && + GetHashCode() == obj.GetHashCode(); - for ( var i = 0; i < collators.Length; i++ ) - { - collators[i].Execute( context ); - } - - items = provider.Describe( context.Results ); - version = currentVersion; - } - - return items; - } - } - - private int ComputeVersion() => - collators.Length switch - { - 0 => 0, - 1 => collators[0].Version, - _ => ComputeVersion( collators ), - }; - - private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers ) + public override int GetHashCode() { var hash = default( HashCode ); - for ( var i = 0; i < providers.Length; i++ ) + if ( !string.IsNullOrEmpty( GroupName ) ) { - hash.Add( providers[i].Version ); + hash.Add( GroupName, StringComparer.Ordinal ); } + hash.Add( base.GetHashCode() ); + return hash.ToHashCode(); } } diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs index ca31264b..294db52c 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/GroupedApiVersionDescriptionProvider.cs @@ -2,10 +2,8 @@ namespace Asp.Versioning.ApiExplorer; +using Asp.Versioning.ApiExplorer.Internal; using Microsoft.Extensions.Options; -using System.Buffers; -using static Asp.Versioning.ApiVersionMapping; -using static System.Globalization.CultureInfo; /// /// Represents the default implementation of an object that discovers and describes the API version information within an application. @@ -13,7 +11,7 @@ namespace Asp.Versioning.ApiExplorer; [CLSCompliant( false )] public class GroupedApiVersionDescriptionProvider : IApiVersionDescriptionProvider { - private readonly ApiVersionDescriptionCollection collection; + private readonly ApiVersionDescriptionCollection collection; private readonly IOptions options; /// @@ -29,7 +27,7 @@ public GroupedApiVersionDescriptionProvider( ISunsetPolicyManager sunsetPolicyManager, IOptions apiExplorerOptions ) { - collection = new( this, providers ?? throw new ArgumentNullException( nameof( providers ) ) ); + collection = new( Describe, providers ?? throw new ArgumentNullException( nameof( providers ) ) ); SunsetPolicyManager = sunsetPolicyManager; options = apiExplorerOptions; } @@ -59,191 +57,17 @@ public GroupedApiVersionDescriptionProvider( protected virtual IReadOnlyList Describe( IReadOnlyList metadata ) { ArgumentNullException.ThrowIfNull( metadata ); - - var descriptions = new SortedSet( new ApiVersionDescriptionComparer() ); - var supported = new HashSet(); - var deprecated = new HashSet(); - - BucketizeApiVersions( metadata, supported, deprecated ); - AppendDescriptions( descriptions, supported, deprecated: false ); - AppendDescriptions( descriptions, deprecated, deprecated: true ); - - return descriptions.ToArray(); - } - - private void BucketizeApiVersions( - IReadOnlyList list, - ISet supported, - ISet deprecated ) - { - var declared = new HashSet(); - var advertisedSupported = new HashSet(); - var advertisedDeprecated = new HashSet(); - - for ( var i = 0; i < list.Count; i++ ) - { - var metadata = list[i]; - var groupName = metadata.GroupName; - var model = metadata.Map( Explicit | Implicit ); - var versions = model.DeclaredApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - declared.Add( new( groupName, versions[j] ) ); - } - - versions = model.SupportedApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - var version = versions[j]; - supported.Add( new( groupName, version ) ); - advertisedSupported.Add( new( groupName, version ) ); - } - - versions = model.DeprecatedApiVersions; - - for ( var j = 0; j < versions.Count; j++ ) - { - var version = versions[j]; - deprecated.Add( new( groupName, version ) ); - advertisedDeprecated.Add( new( groupName, version ) ); - } - } - - advertisedSupported.ExceptWith( declared ); - advertisedDeprecated.ExceptWith( declared ); - supported.ExceptWith( advertisedSupported ); - deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) ); - - if ( supported.Count == 0 && deprecated.Count == 0 ) - { - supported.Add( new( default, Options.DefaultApiVersion ) ); - } - } - - private void AppendDescriptions( - ICollection descriptions, - IEnumerable versions, - bool deprecated ) - { - var format = Options.GroupNameFormat; - var formatGroupName = Options.FormatGroupName; - - foreach ( var (groupName, version) in versions ) - { - var formattedVersion = version.ToString( format, CurrentCulture ); - var formattedGroupName = - string.IsNullOrEmpty( groupName ) || formatGroupName is null - ? formattedVersion - : formatGroupName( groupName, formattedVersion ); - - var sunsetPolicy = SunsetPolicyManager.TryGetPolicy( version, out var policy ) ? policy : default; - descriptions.Add( new( version, formattedGroupName, deprecated, sunsetPolicy ) ); - } - } - - private sealed class ApiVersionDescriptionCollection( - GroupedApiVersionDescriptionProvider provider, - IEnumerable collators ) - { - private readonly object syncRoot = new(); - private readonly GroupedApiVersionDescriptionProvider provider = provider; - private readonly IApiVersionMetadataCollationProvider[] collators = collators.ToArray(); - private IReadOnlyList? items; - private int version; - - public IReadOnlyList Items - { - get - { - if ( items is not null && version == ComputeVersion() ) - { - return items; - } - - lock ( syncRoot ) - { - var currentVersion = ComputeVersion(); - - if ( items is not null && version == currentVersion ) - { - return items; - } - - var context = new ApiVersionMetadataCollationContext(); - - for ( var i = 0; i < collators.Length; i++ ) - { - collators[i].Execute( context ); - } - - var results = context.Results; - var metadata = new GroupedApiVersionMetadata[results.Count]; - - for ( var i = 0; i < metadata.Length; i++ ) - { - metadata[i] = new( context.Results.GroupName( i ), results[i] ); - } - - items = provider.Describe( metadata ); - version = currentVersion; - } - - return items; - } - } - - private int ComputeVersion() => - collators.Length switch - { - 0 => 0, - 1 => collators[0].Version, - _ => ComputeVersion( collators ), - }; - - private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers ) - { - var hash = default( HashCode ); - - for ( var i = 0; i < providers.Length; i++ ) - { - hash.Add( providers[i].Version ); - } - - return hash.ToHashCode(); - } - } - - private sealed class ApiVersionDescriptionComparer : IComparer - { - public int Compare( ApiVersionDescription? x, ApiVersionDescription? y ) - { - if ( x is null ) - { - return y is null ? 0 : -1; - } - - if ( y is null ) - { - return 1; - } - - var result = x.ApiVersion.CompareTo( y.ApiVersion ); - - if ( result == 0 ) - { - result = StringComparer.Ordinal.Compare( x.GroupName, y.GroupName ); - } - - return result; - } + return DescriptionProvider.Describe( metadata, SunsetPolicyManager, Options ); } /// /// Represents the API version metadata applied to an endpoint with an optional group name. /// - protected class GroupedApiVersionMetadata : ApiVersionMetadata, IEquatable + protected class GroupedApiVersionMetadata : + ApiVersionMetadata, + IEquatable, + IGroupedApiVersionMetadata, + IGroupedApiVersionMetadataFactory { /// /// Initializes a new instance of the class. @@ -259,6 +83,10 @@ public GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata /// The associated group name, if any. public string? GroupName { get; } + static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory.New( + string? groupName, + ApiVersionMetadata metadata ) => new( groupName, metadata ); + /// public bool Equals( GroupedApiVersionMetadata? other ) => other is not null && other.GetHashCode() == GetHashCode(); @@ -284,6 +112,4 @@ public override int GetHashCode() return hash.ToHashCode(); } } - - private record struct GroupedApiVersion( string? GroupName, ApiVersion ApiVersion ); } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs new file mode 100644 index 00000000..f5847dd0 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionCollection.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.ApiExplorer.Internal; + +internal sealed class ApiVersionDescriptionCollection( + Func, IReadOnlyList> describe, + IEnumerable collators ) + where T : IGroupedApiVersionMetadata, IGroupedApiVersionMetadataFactory +{ + private readonly object syncRoot = new(); + private readonly Func, IReadOnlyList> describe = describe; + private readonly IApiVersionMetadataCollationProvider[] collators = collators.ToArray(); + private IReadOnlyList? items; + private int version; + + public IReadOnlyList Items + { + get + { + if ( items is not null && version == ComputeVersion() ) + { + return items; + } + + lock ( syncRoot ) + { + var currentVersion = ComputeVersion(); + + if ( items is not null && version == currentVersion ) + { + return items; + } + + var context = new ApiVersionMetadataCollationContext(); + + for ( var i = 0; i < collators.Length; i++ ) + { + collators[i].Execute( context ); + } + + var results = context.Results; + var metadata = new T[results.Count]; + + for ( var i = 0; i < metadata.Length; i++ ) + { + metadata[i] = T.New( context.Results.GroupName( i ), results[i] ); + } + + items = describe( metadata ); + version = currentVersion; + } + + return items; + } + } + + private int ComputeVersion() => + collators.Length switch + { + 0 => 0, + 1 => collators[0].Version, + _ => ComputeVersion( collators ), + }; + + private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers ) + { + var hash = default( HashCode ); + + for ( var i = 0; i < providers.Length; i++ ) + { + hash.Add( providers[i].Version ); + } + + return hash.ToHashCode(); + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs new file mode 100644 index 00000000..3fb73385 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/ApiVersionDescriptionComparer.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.ApiExplorer.Internal; + +internal sealed class ApiVersionDescriptionComparer : IComparer +{ + public int Compare( ApiVersionDescription? x, ApiVersionDescription? y ) + { + if ( x is null ) + { + return y is null ? 0 : -1; + } + + if ( y is null ) + { + return 1; + } + + var result = x.ApiVersion.CompareTo( y.ApiVersion ); + + if ( result == 0 ) + { + result = StringComparer.Ordinal.Compare( x.GroupName, y.GroupName ); + } + + return result; + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs new file mode 100644 index 00000000..ce3a0dbd --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/DescriptionProvider.cs @@ -0,0 +1,107 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.ApiExplorer.Internal; + +using static Asp.Versioning.ApiVersionMapping; +using static System.Globalization.CultureInfo; + +internal static class DescriptionProvider +{ + internal static ApiVersionDescription[] Describe( + IReadOnlyList metadata, + ISunsetPolicyManager sunsetPolicyManager, + ApiExplorerOptions options ) + where T : IGroupedApiVersionMetadata, IEquatable + { + var descriptions = new SortedSet( new ApiVersionDescriptionComparer() ); + var supported = new HashSet(); + var deprecated = new HashSet(); + + BucketizeApiVersions( metadata, supported, deprecated, options ); + AppendDescriptions( descriptions, supported, sunsetPolicyManager, options, deprecated: false ); + AppendDescriptions( descriptions, deprecated, sunsetPolicyManager, options, deprecated: true ); + + return [.. descriptions]; + } + + private static void BucketizeApiVersions( + IReadOnlyList list, + HashSet supported, + HashSet deprecated, + ApiExplorerOptions options ) + where T : IGroupedApiVersionMetadata + { + var declared = new HashSet(); + var advertisedSupported = new HashSet(); + var advertisedDeprecated = new HashSet(); + + for ( var i = 0; i < list.Count; i++ ) + { + var metadata = list[i]; + var groupName = metadata.GroupName; + var model = metadata.Map( Explicit | Implicit ); + var versions = model.DeclaredApiVersions; + + for ( var j = 0; j < versions.Count; j++ ) + { + declared.Add( new( groupName, versions[j] ) ); + } + + versions = model.SupportedApiVersions; + + for ( var j = 0; j < versions.Count; j++ ) + { + var version = versions[j]; + supported.Add( new( groupName, version ) ); + advertisedSupported.Add( new( groupName, version ) ); + } + + versions = model.DeprecatedApiVersions; + + for ( var j = 0; j < versions.Count; j++ ) + { + var version = versions[j]; + deprecated.Add( new( groupName, version ) ); + advertisedDeprecated.Add( new( groupName, version ) ); + } + } + + advertisedSupported.ExceptWith( declared ); + advertisedDeprecated.ExceptWith( declared ); + supported.ExceptWith( advertisedSupported ); + deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) ); + + if ( supported.Count == 0 && deprecated.Count == 0 ) + { + supported.Add( new( default, options.DefaultApiVersion ) ); + } + } + + private static void AppendDescriptions( + SortedSet descriptions, + HashSet versions, + ISunsetPolicyManager sunsetPolicyManager, + ApiExplorerOptions options, + bool deprecated ) + { + var format = options.GroupNameFormat; + var formatGroupName = options.FormatGroupName; + + foreach ( var (groupName, version) in versions ) + { + var formattedGroupName = groupName; + + if ( string.IsNullOrEmpty( formattedGroupName ) ) + { + formattedGroupName = version.ToString( format, CurrentCulture ); + } + else if ( formatGroupName is not null ) + { + formattedGroupName = formatGroupName( formattedGroupName, version.ToString( format, CurrentCulture ) ); + } + + var sunsetPolicy = sunsetPolicyManager.TryGetPolicy( version, out var policy ) ? policy : default; + descriptions.Add( new( version, formattedGroupName, deprecated, sunsetPolicy ) ); + } + } +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs new file mode 100644 index 00000000..8d276e60 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/GroupedApiVersion.cs @@ -0,0 +1,5 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.ApiExplorer.Internal; + +internal record struct GroupedApiVersion( string? GroupName, ApiVersion ApiVersion ); \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs new file mode 100644 index 00000000..ec0c13e3 --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadata.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.ApiExplorer.Internal; + +internal interface IGroupedApiVersionMetadata +{ + string? GroupName { get; } + + string Name { get; } + + bool IsApiVersionNeutral { get; } + + ApiVersionModel Map( ApiVersionMapping mapping ); + + ApiVersionMapping MappingTo( ApiVersion? apiVersion ); + + bool IsMappedTo( ApiVersion? apiVersion ); + + void Deconstruct( out ApiVersionModel apiModel, out ApiVersionModel endpointModel ); +} \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs new file mode 100644 index 00000000..ac9d885f --- /dev/null +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Internal/IGroupedApiVersionMetadataFactory.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. + +namespace Asp.Versioning.ApiExplorer.Internal; + +internal interface IGroupedApiVersionMetadataFactory + where T : IGroupedApiVersionMetadata +{ + static abstract T New( string? groupName, ApiVersionMetadata metadata ); +} \ No newline at end of file