diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs
index 5384c160..f3b2bf12 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs
@@ -50,25 +50,84 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is { } policy )
+ if ( description.DeprecationPolicy is { } deprecationPolicy )
{
- if ( policy.Date is { } when )
+ if ( deprecationPolicy.Date is { } when )
{
- text.Append( " The API will be sunset on " )
- .Append( when.Date.ToShortDateString() )
- .Append( '.' );
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
}
- if ( policy.HasLinks )
+ if ( deprecationPolicy.HasLinks )
{
text.AppendLine();
var rendered = false;
- for ( var i = 0; i < policy.Links.Count; i++ )
+ foreach ( var link in deprecationPolicy.Links )
{
- var link = policy.Links[i];
+ if ( link.Type == "text/html" )
+ {
+ if ( !rendered )
+ {
+ text.Append( "
" );
+ }
+ }
+ }
+
+ if ( description.SunsetPolicy is { } sunsetPolicy )
+ {
+ if ( sunsetPolicy.Date is { } when )
+ {
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ }
+
+ if ( sunsetPolicy.HasLinks )
+ {
+ text.AppendLine();
+
+ var rendered = false;
+
+ foreach ( var link in sunsetPolicy.Links )
+ {
if ( link.Type == "text/html" )
{
if ( !rendered )
diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs
index b725ab72..38a250af 100644
--- a/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs
@@ -50,25 +50,84 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is { } policy )
+ if ( description.DeprecationPolicy is { } deprecationPolicy )
{
- if ( policy.Date is { } when )
+ if ( deprecationPolicy.Date is { } when )
{
- text.Append( " The API will be sunset on " )
- .Append( when.Date.ToShortDateString() )
- .Append( '.' );
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
}
- if ( policy.HasLinks )
+ if ( deprecationPolicy.HasLinks )
{
text.AppendLine();
var rendered = false;
- for ( var i = 0; i < policy.Links.Count; i++ )
+ foreach ( var link in deprecationPolicy.Links )
{
- var link = policy.Links[i];
+ if ( link.Type == "text/html" )
+ {
+ if ( !rendered )
+ {
+ text.Append( "
" );
+ }
+ }
+ }
+
+ if ( description.SunsetPolicy is { } sunsetPolicy )
+ {
+ if ( sunsetPolicy.Date is { } when )
+ {
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ }
+
+ if ( sunsetPolicy.HasLinks )
+ {
+ text.AppendLine();
+
+ var rendered = false;
+
+ foreach ( var link in sunsetPolicy.Links )
+ {
if ( link.Type == "text/html" )
{
if ( !rendered )
diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs
index d531cea4..5a8cbf51 100644
--- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs
@@ -50,25 +50,84 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is { } policy )
+ if ( description.DeprecationPolicy is { } deprecationPolicy )
{
- if ( policy.Date is { } when )
+ if ( deprecationPolicy.Date is { } when )
{
- text.Append( " The API will be sunset on " )
- .Append( when.Date.ToShortDateString() )
- .Append( '.' );
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
}
- if ( policy.HasLinks )
+ if ( deprecationPolicy.HasLinks )
{
text.AppendLine();
var rendered = false;
- for ( var i = 0; i < policy.Links.Count; i++ )
+ foreach ( var link in deprecationPolicy.Links )
{
- var link = policy.Links[i];
+ if ( link.Type == "text/html" )
+ {
+ if ( !rendered )
+ {
+ text.Append( "
" );
+ }
+ }
+ }
+
+ if ( description.SunsetPolicy is { } sunsetPolicy )
+ {
+ if ( sunsetPolicy.Date is { } when )
+ {
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ }
+
+ if ( sunsetPolicy.HasLinks )
+ {
+ text.AppendLine();
+
+ var rendered = false;
+
+ foreach ( var link in sunsetPolicy.Links )
+ {
if ( link.Type == "text/html" )
{
if ( !rendered )
diff --git a/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs
index d531cea4..5a8cbf51 100644
--- a/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs
@@ -50,25 +50,84 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is { } policy )
+ if ( description.DeprecationPolicy is { } deprecationPolicy )
{
- if ( policy.Date is { } when )
+ if ( deprecationPolicy.Date is { } when )
{
- text.Append( " The API will be sunset on " )
- .Append( when.Date.ToShortDateString() )
- .Append( '.' );
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be deprecated on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
}
- if ( policy.HasLinks )
+ if ( deprecationPolicy.HasLinks )
{
text.AppendLine();
var rendered = false;
- for ( var i = 0; i < policy.Links.Count; i++ )
+ foreach ( var link in deprecationPolicy.Links )
{
- var link = policy.Links[i];
+ if ( link.Type == "text/html" )
+ {
+ if ( !rendered )
+ {
+ text.Append( "
" );
+ }
+ }
+ }
+
+ if ( description.SunsetPolicy is { } sunsetPolicy )
+ {
+ if ( sunsetPolicy.Date is { } when )
+ {
+ if ( when < DateTime.Now )
+ {
+ text.Append( " The API has been sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ else
+ {
+ text.Append( " The API will be sunset on " )
+ .Append( when.Date.ToShortDateString() )
+ .Append( '.' );
+ }
+ }
+
+ if ( sunsetPolicy.HasLinks )
+ {
+ text.AppendLine();
+
+ var rendered = false;
+
+ foreach ( var link in sunsetPolicy.Links )
+ {
if ( link.Type == "text/html" )
{
if ( !rendered )
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs
new file mode 100644
index 00000000..8c454289
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/DeprecationPolicy.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Represents an API version deprecation policy.
+///
+public class DeprecationPolicy
+{
+ private LinkList? links;
+
+ ///
+ /// Gets a read-only list of links that provide information about the deprecation policy.
+ ///
+ /// A read-only list of HTTP links.
+ /// If a link is provided, generally only one link is necessary; however, additional
+ /// links might be provided for different languages or different formats such as a HTML page
+ /// or a JSON file.
+ public IList Links => links ??= new( "deprecation" );
+
+ ///
+ /// Gets a value indicating whether the deprecation policy has any associated links.
+ ///
+ /// True if the deprecation policy has associated links; otherwise, false.
+ public bool HasLinks => links is not null && links.Count > 0;
+
+ ///
+ /// Gets the date and time when the API version will be deprecated.
+ ///
+ /// The date and time when the API version will be deprecated, if any.
+ public DateTimeOffset? Date { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DeprecationPolicy() { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The date and time when the API version will be deprecated.
+ /// The optional link which provides information about the deprecation policy.
+ public DeprecationPolicy( DateTimeOffset date, LinkHeaderValue? link = default )
+ {
+ Date = date;
+
+ if ( link is not null )
+ {
+ Links.Add( link );
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The link which provides information about the deprecation policy.
+ public DeprecationPolicy( LinkHeaderValue link ) => Links.Add( link );
+
+ ///
+ /// Returns a boolean to indicate if this policy is effective at the given .
+ ///
+ /// The point in time to serve as a reference.
+ /// A boolean which indicates if this policy is effective.
+ public bool IsEffective( DateTimeOffset? dateTimeOffset )
+ {
+ if ( dateTimeOffset is not { } when )
+ {
+ return true;
+ }
+
+ if ( Date is not { } date )
+ {
+ return true;
+ }
+
+ return date <= when;
+ }
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs
index dbea4469..2838dbd8 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs
@@ -11,8 +11,10 @@ internal static class Format
#if NETSTANDARD
internal static readonly string ApiVersionBadStatus = SR.ApiVersionBadStatus;
internal static readonly string ApiVersionBadGroupVersion = SR.ApiVersionBadGroupVersion;
+ internal static readonly string InvalidRelationType = SR.InvalidRelationType;
#else
internal static readonly CompositeFormat ApiVersionBadStatus = CompositeFormat.Parse( SR.ApiVersionBadStatus );
internal static readonly CompositeFormat ApiVersionBadGroupVersion = CompositeFormat.Parse( SR.ApiVersionBadGroupVersion );
+ internal static readonly CompositeFormat InvalidRelationType = CompositeFormat.Parse( SR.InvalidRelationType );
#endif
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilder.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilder.cs
index 7a692d84..f400ec49 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilder.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilder.cs
@@ -23,4 +23,14 @@ public interface IApiVersioningPolicyBuilder
/// The and
/// parameters are both null.
ISunsetPolicyBuilder Sunset( string? name, ApiVersion? apiVersion );
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The optional name of the API the policy is for.
+ /// The optional API version the policy is for.
+ /// A new deprecation policy builder.
+ /// The and
+ /// parameters are both null.
+ public IDeprecationPolicyBuilder Deprecate( string? name, ApiVersion? apiVersion );
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs
index 048d6d49..afdfd5b9 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs
@@ -157,4 +157,151 @@ public static ISunsetPolicyBuilder Sunset(
ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( default, new ApiVersion( groupVersion, status ) );
}
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The name of the API the policy is for.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, string name )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( name, default );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The name of the API the policy is for.
+ /// The major version number.
+ /// The optional minor version number.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate(
+ this IApiVersioningPolicyBuilder builder,
+ string name,
+ int majorVersion,
+ int? minorVersion = default,
+ string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( name, new ApiVersion( majorVersion, minorVersion, status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The name of the API the policy is for.
+ /// The version number.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, string name, double version, string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( name, new ApiVersion( version, status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The name of the API the policy is for.
+ /// The version year.
+ /// The version month.
+ /// The version day.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, string name, int year, int month, int day, string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( name, new ApiVersion( new DateOnly( year, month, day ), status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The name of the API the policy is for.
+ /// The group version.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, string name, DateOnly groupVersion, string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( name, new ApiVersion( groupVersion, status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The API version the policy is for.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, ApiVersion apiVersion )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( default, apiVersion );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The major version number.
+ /// The optional minor version number.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate(
+ this IApiVersioningPolicyBuilder builder,
+ int majorVersion,
+ int? minorVersion = default,
+ string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( default, new ApiVersion( majorVersion, minorVersion, status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The version number.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, double version, string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( default, new ApiVersion( version, status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The version year.
+ /// The version month.
+ /// The version day.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, int year, int month, int day, string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( default, new ApiVersion( new DateOnly( year, month, day ), status ) );
+ }
+
+ ///
+ /// Creates and returns a new deprecation policy builder.
+ ///
+ /// The extended API versioning policy builder.
+ /// The group version.
+ /// The optional version status.
+ /// A new deprecation policy builder.
+ public static IDeprecationPolicyBuilder Deprecate( this IApiVersioningPolicyBuilder builder, DateOnly groupVersion, string? status = default )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Deprecate( default, new ApiVersion( groupVersion, status ) );
+ }
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs
new file mode 100644
index 00000000..8f95e51a
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IDeprecationPolicyBuilder.cs
@@ -0,0 +1,8 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Defines the behavior of a deprecation policy builder.
+///
+public interface IDeprecationPolicyBuilder : IPolicyBuilder, IPolicyWithLink, IPolicyWithEffectiveDate { }
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilder.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilder.cs
new file mode 100644
index 00000000..fde4520b
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilder.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Defines the behavior of a policy builder which applies to a single API version.
+///
+/// The type of policy which is built by this builder.
+public interface IPolicyBuilder
+{
+ ///
+ /// Gets the policy name.
+ ///
+ /// The policy name, if any.
+ /// The name is typically of an API.
+ string? Name { get; }
+
+ ///
+ /// Gets the API version the policy is for.
+ ///
+ /// The specific policy API version, if any.
+ ApiVersion? ApiVersion { get; }
+
+ ///
+ /// Configures the builder per the specified .
+ ///
+ /// The applied policy.
+ void Per( TPolicy policy );
+
+ ///
+ /// Builds and returns a policy.
+ ///
+ /// A new policy.
+ TPolicy Build();
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilderExtensions.cs
new file mode 100644
index 00000000..08142139
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyBuilderExtensions.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Provides extension methods for the interface.
+///
+public static class IPolicyBuilderExtensions
+{
+ ///
+ /// Creates and returns a new link builder.
+ ///
+ /// The extended policy builder.
+ /// The link target URL.
+ /// A new link builder.
+ public static ILinkBuilder Link( this IPolicyWithLink builder, string linkTarget )
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) );
+ }
+
+ ///
+ /// Indicates when a policy is applied.
+ ///
+ /// The type of policy builder.
+ /// The extended policy builder.
+ /// The time when the policy is applied.
+ /// The current policy builder.
+ public static TBuilder Effective( this TBuilder builder, DateTimeOffset effectiveDate )
+ where TBuilder : notnull, IPolicyWithEffectiveDate
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ builder.SetEffectiveDate( effectiveDate );
+ return builder;
+ }
+
+ ///
+ /// Indicates when a policy is applied.
+ ///
+ /// The type of policy builder.
+ /// The extended policy builder.
+ /// The year when the policy is applied.
+ /// The month when the policy is applied.
+ /// The day when the policy is applied.
+ /// The current policy builder.
+ public static TBuilder Effective( this TBuilder builder, int year, int month, int day )
+ where TBuilder : notnull, IPolicyWithEffectiveDate
+ {
+ ArgumentNullException.ThrowIfNull( builder );
+ return builder.Effective( new DateTimeOffset( new DateTime( year, month, day ) ) );
+ }
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManager.cs
similarity index 51%
rename from src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs
rename to src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManager.cs
index de2dd77e..43a5fd8d 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManager.cs
@@ -3,20 +3,21 @@
namespace Asp.Versioning;
///
-/// Defines the behavior of an API version sunset policy manager.
+/// Defines the behavior of an API version policy manager.
///
-public interface ISunsetPolicyManager
+/// The type of the policy.
+public interface IPolicyManager
{
///
- /// Returns the sunset policy for the specified API and version.
+ /// Returns the policy for the specified API and version.
///
/// The name of the API.
/// The API version to get the policy for.
- /// The applicable sunset policy, if any.
- /// True if the sunset policy was retrieved; otherwise, false.
- /// If is null, it is assumed the caller intends to match any sunset
+ /// The applicable policy, if any.
+ /// True if the policy was retrieved; otherwise, false.
+ /// If is null, it is assumed the caller intends to match any
/// policy for the specified API version. If
/// API version is null, it is assumed the caller intends to match
- /// any sunset policy for the specified .
- bool TryGetPolicy( string? name, ApiVersion? apiVersion, [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy );
+ /// any policy for the specified .
+ bool TryGetPolicy( string? name, ApiVersion? apiVersion, [MaybeNullWhen( false )] out TPolicy policy );
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs
new file mode 100644
index 00000000..0c468f71
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyManagerExtensions.cs
@@ -0,0 +1,118 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Provides extension methods for the interface.
+///
+public static class IPolicyManagerExtensions
+{
+ ///
+ /// Returns the policy for the specified API and version.
+ ///
+ /// The extended policy manager.
+ /// The API version to get the policy for.
+ /// The applicable policy, if any.
+ /// The type of policy.
+ /// True if the policy was retrieved; otherwise, false.
+ public static bool TryGetPolicy(
+ this IPolicyManager policyManager,
+ ApiVersion apiVersion,
+ [MaybeNullWhen( false )] out TPolicy policy )
+ {
+ ArgumentNullException.ThrowIfNull( policyManager );
+ return policyManager.TryGetPolicy( default, apiVersion, out policy );
+ }
+
+ ///
+ /// Returns the policy for the specified API and version.
+ ///
+ /// The extended policy manager.
+ /// The name of the API.
+ /// The applicable policy, if any.
+ /// The type of policy.
+ /// True if the policy was retrieved; otherwise, false.
+ public static bool TryGetPolicy(
+ this IPolicyManager policyManager,
+ string name,
+ [MaybeNullWhen( false )] out TPolicy policy )
+ {
+ ArgumentNullException.ThrowIfNull( policyManager );
+ return policyManager.TryGetPolicy( name, default, out policy );
+ }
+
+ ///
+ /// Attempts to resolve a policy for the specified name and API version combination.
+ ///
+ /// The extended policy manager.
+ /// The name of the API.
+ /// The API version to get the policy for.
+ /// The type of policy.
+ /// The applicable policy, if any.
+ /// The resolution order is as follows:
+ ///
+ /// and
+ /// only
+ /// only
+ ///
+ ///
+ public static TPolicy? ResolvePolicyOrDefault(
+ this IPolicyManager policyManager,
+ string? name,
+ ApiVersion? apiVersion )
+ {
+ ArgumentNullException.ThrowIfNull( policyManager );
+
+ if ( policyManager.TryResolvePolicy( name, apiVersion, out var policy ) )
+ {
+ return policy;
+ }
+
+ return default;
+ }
+
+ ///
+ /// Attempts to resolve a policy for the specified name and API version combination.
+ ///
+ /// The extended policy manager.
+ /// The name of the API.
+ /// The API version to get the policy for.
+ /// The applicable policy, if any.
+ /// The type of policy.
+ /// True if the policy was retrieved; otherwise, false.
+ /// The resolution order is as follows:
+ ///
+ /// and
+ /// only
+ /// only
+ ///
+ ///
+ public static bool TryResolvePolicy(
+ this IPolicyManager policyManager,
+ string? name,
+ ApiVersion? apiVersion,
+ [MaybeNullWhen( false )] out TPolicy policy )
+ {
+ ArgumentNullException.ThrowIfNull( policyManager );
+
+ if ( !string.IsNullOrEmpty( name ) )
+ {
+ if ( apiVersion != null && policyManager.TryGetPolicy( name, apiVersion, out policy ) )
+ {
+ return true;
+ }
+ else if ( policyManager.TryGetPolicy( name!, out policy ) )
+ {
+ return true;
+ }
+ }
+
+ if ( apiVersion != null && policyManager.TryGetPolicy( apiVersion, out policy ) )
+ {
+ return true;
+ }
+
+ policy = default!;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs
new file mode 100644
index 00000000..f148a0c3
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithEffectiveDate.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// A policy which can be configured to only be effective after a particular date.
+///
+public interface IPolicyWithEffectiveDate
+{
+ ///
+ /// Indicates when a policy is applied.
+ ///
+ ///
+ /// The date and time when a policy is applied.
+ ///
+ void SetEffectiveDate( DateTimeOffset effectiveDate );
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithLink.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithLink.cs
new file mode 100644
index 00000000..2e4bd315
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IPolicyWithLink.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Defines a policy which can (optionally) expose a link to more information.
+///
+public interface IPolicyWithLink
+{
+ ///
+ /// Creates and returns a new link builder.
+ ///
+ /// The link target URL.
+ /// A new link builder.
+ ILinkBuilder Link( Uri linkTarget );
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs
index deca62dc..58a45e70 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilder.cs
@@ -5,45 +5,4 @@ namespace Asp.Versioning;
///
/// Defines the behavior of a sunset policy builder.
///
-public interface ISunsetPolicyBuilder
-{
- ///
- /// Gets the policy name.
- ///
- /// The policy name, if any.
- /// The name is typically of an API.
- string? Name { get; }
-
- ///
- /// Gets the API version the policy is for.
- ///
- /// The specific policy API version, if any.
- ApiVersion? ApiVersion { get; }
-
- ///
- /// Applies a sunset policy per the specified policy.
- ///
- /// The applied sunset policy.
- void Per( SunsetPolicy policy );
-
- ///
- /// Indicates when a sunset policy is applied.
- ///
- /// The date and time when a
- /// sunset policy is applied.
- /// The current sunset policy builder.
- ISunsetPolicyBuilder Effective( DateTimeOffset sunsetDate );
-
- ///
- /// Creates and returns a new link builder.
- ///
- /// The link target URL.
- /// A new link builder.
- ILinkBuilder Link( Uri linkTarget );
-
- ///
- /// Builds and returns a sunset policy.
- ///
- /// A new sunset policy.
- SunsetPolicy Build();
-}
\ No newline at end of file
+public interface ISunsetPolicyBuilder : IPolicyBuilder, IPolicyWithLink, IPolicyWithEffectiveDate { }
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs
deleted file mode 100644
index 4a81ebd4..00000000
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-
-namespace Asp.Versioning;
-
-///
-/// Provides extension methods for the interface.
-///
-public static class ISunsetPolicyBuilderExtensions
-{
- ///
- /// Creates and returns a new link builder.
- ///
- /// The extended sunset policy builder.
- /// The link target URL.
- /// A new link builder.
- public static ILinkBuilder Link( this ISunsetPolicyBuilder builder, string linkTarget )
- {
- ArgumentNullException.ThrowIfNull( builder );
- return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) );
- }
-
- ///
- /// Indicates when a sunset policy is applied.
- ///
- /// The type of sunset policy builder.
- /// The extended sunset policy builder.
- /// The year when the sunset policy is applied.
- /// The month when the sunset policy is applied.
- /// The day when the sunset policy is applied.
- /// The current sunset policy builder.
- public static TBuilder Effective( this TBuilder builder, int year, int month, int day )
- where TBuilder : notnull, ISunsetPolicyBuilder
- {
- ArgumentNullException.ThrowIfNull( builder );
- builder.Effective( new DateTimeOffset( new DateTime( year, month, day ) ) );
- return builder;
- }
-}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs
deleted file mode 100644
index f28f9cca..00000000
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-
-namespace Asp.Versioning;
-
-///
-/// Provides extension methods for the interface.
-///
-public static class ISunsetPolicyManagerExtensions
-{
- ///
- /// Returns the sunset policy for the specified API and version.
- ///
- /// The extended sunset policy manager.
- /// The API version to get the policy for.
- /// The applicable sunset policy, if any.
- /// True if the sunset policy was retrieved; otherwise, false.
- public static bool TryGetPolicy(
- this ISunsetPolicyManager policyManager,
- ApiVersion apiVersion,
- [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
- {
- ArgumentNullException.ThrowIfNull( policyManager );
- return policyManager.TryGetPolicy( default, apiVersion, out sunsetPolicy );
- }
-
- ///
- /// Returns the sunset policy for the specified API and version.
- ///
- /// The extended sunset policy manager.
- /// The name of the API.
- /// The applicable sunset policy, if any.
- /// True if the sunset policy was retrieved; otherwise, false.
- public static bool TryGetPolicy(
- this ISunsetPolicyManager policyManager,
- string name,
- [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
- {
- ArgumentNullException.ThrowIfNull( policyManager );
- return policyManager.TryGetPolicy( name, default, out sunsetPolicy );
- }
-
- ///
- /// Attempts to resolve a sunset policy for the specified name and API version combination.
- ///
- /// The extended sunset policy manager.
- /// The name of the API.
- /// The API version to get the policy for.
- /// The applicable sunset policy, if any.
- /// The resolution order is as follows:
- ///
- /// and
- /// only
- /// only
- ///
- ///
- public static SunsetPolicy? ResolvePolicyOrDefault(
- this ISunsetPolicyManager policyManager,
- string? name,
- ApiVersion? apiVersion )
- {
- ArgumentNullException.ThrowIfNull( policyManager );
-
- if ( policyManager.TryResolvePolicy( name, apiVersion, out var policy ) )
- {
- return policy;
- }
-
- return default;
- }
-
- ///
- /// Attempts to resolve a sunset policy for the specified name and API version combination.
- ///
- /// The extended sunset policy manager.
- /// The name of the API.
- /// The API version to get the policy for.
- /// /// The applicable sunset policy, if any.
- /// True if the sunset policy was retrieved; otherwise, false.
- /// The resolution order is as follows:
- ///
- /// and
- /// only
- /// only
- ///
- ///
- public static bool TryResolvePolicy(
- this ISunsetPolicyManager policyManager,
- string? name,
- ApiVersion? apiVersion,
- [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
- {
- ArgumentNullException.ThrowIfNull( policyManager );
-
- if ( !string.IsNullOrEmpty( name ) )
- {
- if ( apiVersion != null && policyManager.TryGetPolicy( name, apiVersion, out sunsetPolicy ) )
- {
- return true;
- }
- else if ( policyManager.TryGetPolicy( name!, out sunsetPolicy ) )
- {
- return true;
- }
- }
-
- if ( apiVersion != null && policyManager.TryGetPolicy( apiVersion, out sunsetPolicy ) )
- {
- return true;
- }
-
- sunsetPolicy = default!;
- return false;
- }
-}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/LinkList.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/LinkList.cs
new file mode 100644
index 00000000..bc68ddca
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/LinkList.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using System.Collections.ObjectModel;
+using System.Globalization;
+
+internal sealed class LinkList( string relationType ) : Collection
+{
+ private readonly string relationType = relationType;
+
+ protected override void InsertItem( int index, LinkHeaderValue item )
+ {
+ EnsureRelationType( item );
+ base.InsertItem( index, item );
+ }
+
+ protected override void SetItem( int index, LinkHeaderValue item )
+ {
+ EnsureRelationType( item );
+ base.SetItem( index, item );
+ }
+
+ private void EnsureRelationType( LinkHeaderValue item )
+ {
+ if ( !item.RelationType.Equals( relationType, StringComparison.OrdinalIgnoreCase ) )
+ {
+ var message = string.Format( CultureInfo.CurrentCulture, Format.InvalidRelationType, relationType );
+ throw new ArgumentException( message, nameof( item ) );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/SR.Designer.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/SR.Designer.cs
index 9f45b67f..c08c889f 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/SR.Designer.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/SR.Designer.cs
@@ -134,11 +134,11 @@ internal static string InvalidOrMalformedHeader {
}
///
- /// Looks up a localized string similar to The relation type for a sunset policy link must be "sunset"..
+ /// Looks up a localized string similar to The relation type for a {0} policy link must be "{0}"..
///
- internal static string InvalidSunsetRelationType {
+ internal static string InvalidRelationType {
get {
- return ResourceManager.GetString("InvalidSunsetRelationType", resourceCulture);
+ return ResourceManager.GetString("InvalidRelationType", resourceCulture);
}
}
}
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/SR.resx b/src/Abstractions/src/Asp.Versioning.Abstractions/SR.resx
index ea5522a8..0b4f2abf 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/SR.resx
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/SR.resx
@@ -141,7 +141,7 @@
The header contains invalid or missing values.
-
- The relation type for a sunset policy link must be "sunset".
+
+ The relation type for a {0} policy link must be "{0}".
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs
index e5e0f4be..28a41062 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs
@@ -2,14 +2,33 @@
namespace Asp.Versioning;
-using System.Collections.ObjectModel;
-
///
/// Represents an API version sunset policy.
///
public class SunsetPolicy
{
- private SunsetLinkList? links;
+ private LinkList? links;
+
+ ///
+ /// Gets a read-only list of links that provide information about the sunset policy.
+ ///
+ /// A read-only list of HTTP links.
+ /// If a link is provided, generally only one link is necessary; however, additional
+ /// links might be provided for different languages or different formats such as a HTML page
+ /// or a JSON file.
+ public IList Links => links ??= new( "sunset" );
+
+ ///
+ /// Gets a value indicating whether the sunset policy has any associated links.
+ ///
+ /// True if the sunset policy has associated links; otherwise, false.
+ public bool HasLinks => links is not null && links.Count > 0;
+
+ ///
+ /// Gets the date and time when the API version will be sunset.
+ ///
+ /// The date and time when the API version will be sunset, if any.
+ public DateTimeOffset? Date { get; }
///
/// Initializes a new instance of the class.
@@ -22,12 +41,13 @@ public SunsetPolicy() { }
/// The date and time when the API version will be sunset.
/// The optional link which provides information about the sunset policy.
public SunsetPolicy( DateTimeOffset date, LinkHeaderValue? link = default )
+ : this()
{
Date = date;
if ( link is not null )
{
- links = new() { link };
+ Links.Add( link );
}
}
@@ -35,49 +55,5 @@ public SunsetPolicy() { }
/// Initializes a new instance of the class.
///
/// The link which provides information about the sunset policy.
- public SunsetPolicy( LinkHeaderValue link ) => links = new() { link };
-
- ///
- /// Gets the date and time when the API version will be sunset.
- ///
- /// The date and time when the API version will be sunset, if any.
- public DateTimeOffset? Date { get; }
-
- ///
- /// Gets a value indicating whether the sunset policy has any associated links.
- ///
- /// True if the sunset policy has associated links; otherwise, false.
- public bool HasLinks => links is not null && links.Count > 0;
-
- ///
- /// Gets a read-only list of links that provide information about the sunset policy.
- ///
- /// A read-only list of HTTP links.
- /// If a link is provided, generally only one link is necessary; however, additional
- /// links might be provided for different languages or different formats such as a HTML page
- /// or a JSON file.
- public IList Links => links ??= new();
-
- private sealed class SunsetLinkList : Collection
- {
- protected override void InsertItem( int index, LinkHeaderValue item )
- {
- base.InsertItem( index, item );
- EnsureRelationType( item );
- }
-
- protected override void SetItem( int index, LinkHeaderValue item )
- {
- base.SetItem( index, item );
- EnsureRelationType( item );
- }
-
- private static void EnsureRelationType( LinkHeaderValue item )
- {
- if ( !item.RelationType.Equals( "sunset", StringComparison.OrdinalIgnoreCase ) )
- {
- throw new ArgumentException( SR.InvalidSunsetRelationType, nameof( item ) );
- }
- }
- }
+ public SunsetPolicy( LinkHeaderValue link ) => Links.Add( link );
}
\ No newline at end of file
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyBuilderExtensionsTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IPolicyBuilderExtensionsTest.cs
similarity index 58%
rename from src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyBuilderExtensionsTest.cs
rename to src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IPolicyBuilderExtensionsTest.cs
index 052771f0..0b41013d 100644
--- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyBuilderExtensionsTest.cs
+++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IPolicyBuilderExtensionsTest.cs
@@ -2,7 +2,7 @@
namespace Asp.Versioning;
-public class ISunsetPolicyBuilderExtensionsTest
+public class IPolicyBuilderExtensionsTest
{
[Fact]
public void link_should_build_url_from_string()
@@ -11,10 +11,10 @@ public void link_should_build_url_from_string()
var builder = Mock.Of();
// act
- builder.Link( "http://tempuri.org" );
+ builder.Link("http://tempuri.org");
// assert
- Mock.Get( builder ).Verify( b => b.Link( new Uri( "http://tempuri.org" ) ) );
+ Mock.Get(builder).Verify(b => b.Link(new Uri("http://tempuri.org")));
}
[Fact]
@@ -22,12 +22,12 @@ public void effective_should_build_date_from_parts()
{
// arrange
var builder = Mock.Of();
- var date = new DateTime( 2022, 2, 1 );
+ var date = new DateTime(2022, 2, 1);
// act
- builder.Effective( 2022, 2, 1 );
+ builder.Effective(2022, 2, 1);
// assert
- Mock.Get( builder ).Verify( b => b.Effective( new( date ) ) );
+ Mock.Get(builder).Verify(b => b.SetEffectiveDate(new(date)));
}
}
\ No newline at end of file
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IPolicyManagerExtensionsTest.cs
similarity index 87%
rename from src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs
rename to src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IPolicyManagerExtensionsTest.cs
index 9cc9b693..41733df1 100644
--- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs
+++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IPolicyManagerExtensionsTest.cs
@@ -2,13 +2,13 @@
namespace Asp.Versioning;
-public class ISunsetPolicyManagerExtensionsTest
+public class IPolicyManagerExtensionsTest
{
[Fact]
public void try_get_policy_should_get_global_policy_by_version()
{
// arrange
- var manager = new Mock();
+ var manager = new Mock>();
var version = ApiVersion.Default;
var expected = new SunsetPolicy();
@@ -26,7 +26,7 @@ public void try_get_policy_should_get_global_policy_by_version()
public void try_get_policy_should_get_global_policy_by_name()
{
// arrange
- var manager = new Mock();
+ var manager = new Mock>();
var expected = new SunsetPolicy();
manager.Setup( m => m.TryGetPolicy( "Test", default, out expected ) )
@@ -43,7 +43,7 @@ public void try_get_policy_should_get_global_policy_by_name()
public void resolve_policy_should_return_most_specific_result()
{
// arrange
- var manager = new Mock();
+ var manager = new Mock>();
var expected = new SunsetPolicy();
var other = new SunsetPolicy();
@@ -61,7 +61,7 @@ public void resolve_policy_should_return_most_specific_result()
public void resolve_policy_should_fall_back_to_global_result()
{
// arrange
- var manager = new Mock();
+ var manager = new Mock>();
var expected = new SunsetPolicy();
var other = new SunsetPolicy();
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
index bb3eed90..24399a61 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
@@ -566,6 +566,7 @@ private void PopulateActionDescriptions(
ApiVersion = apiVersion,
IsDeprecated = deprecated,
SunsetPolicy = SunsetPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ),
+ DeprecationPolicy = DeprecationPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ),
Properties = { [typeof( IEdmModel )] = routeBuilderContext.EdmModel },
};
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
index c39e10b4..0f4db376 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
@@ -31,8 +31,11 @@ public async Task options_should_return_expected_headers()
var resolver = new SimpleDependencyResolver( configuration );
resolver.AddService(
- typeof( ISunsetPolicyManager ),
+ typeof( IPolicyManager ),
( sp, t ) => new SunsetPolicyManager( sp.GetRequiredService().GetApiVersioningOptions() ) );
+ resolver.AddService(
+ typeof( IPolicyManager ),
+ ( sp, t ) => new DeprecationPolicyManager( sp.GetRequiredService().GetApiVersioningOptions() ) );
configuration.DependencyResolver = resolver;
configuration.AddApiVersioning(
options =>
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs
index c7585844..e2403079 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs
@@ -32,7 +32,8 @@ public class VersionedApiExplorer : IApiExplorer
private readonly ApiExplorerOptions options;
private readonly Lazy apiDescriptionsHolder;
private IDocumentationProvider? documentationProvider;
- private ISunsetPolicyManager? sunsetPolicyManager;
+ private IPolicyManager? sunsetPolicyManager;
+ private IPolicyManager? deprecationPolicyManager;
///
/// Initializes a new instance of the class.
@@ -98,13 +99,23 @@ public IDocumentationProvider DocumentationProvider
///
/// Gets or sets the manager used to resolve sunset policies for API descriptions.
///
- /// The configured sunset policy manager.
- protected ISunsetPolicyManager SunsetPolicyManager
+ /// The configured sunset policy manager.
+ protected IPolicyManager SunsetPolicyManager
{
get => sunsetPolicyManager ??= Configuration.GetSunsetPolicyManager();
set => sunsetPolicyManager = value;
}
+ ///
+ /// Gets or sets the manager used to resolve deprecation policies for API descriptions.
+ ///
+ /// The configured deprecation policy manager.
+ protected IPolicyManager DeprecationPolicyManager
+ {
+ get => deprecationPolicyManager ??= Configuration.GetDeprecationPolicyManager();
+ set => deprecationPolicyManager = value;
+ }
+
///
/// Gets a collection of HTTP methods supported by the action.
///
@@ -227,11 +238,11 @@ protected virtual ApiDescriptionGroupCollection InitializeApiDescriptions()
}
var routes = FlattenRoutes( Configuration.Routes ).ToArray();
- var policyManager = Configuration.GetSunsetPolicyManager();
foreach ( var apiVersion in FlattenApiVersions( controllerMappings ) )
{
- var sunsetPolicy = policyManager.TryGetPolicy( apiVersion, out var policy ) ? policy : default;
+ SunsetPolicyManager.TryGetPolicy( apiVersion, out var sunsetPolicy );
+ DeprecationPolicyManager.TryGetPolicy( apiVersion, out var deprecationPolicy );
for ( var i = 0; i < routes.Length; i++ )
{
@@ -244,6 +255,7 @@ protected virtual ApiDescriptionGroupCollection InitializeApiDescriptions()
ExploreRouteControllers( controllerMappings, route, apiVersion );
apiDescriptionGroup.SunsetPolicy = sunsetPolicy;
+ apiDescriptionGroup.DeprecationPolicy = deprecationPolicy;
// Remove ApiDescription that will lead to ambiguous action matching.
// E.g. a controller with Post() and PostComment(). When the route template is {controller}, it produces POST /controller and POST /controller.
@@ -878,6 +890,7 @@ private void PopulateActionDescriptions(
ApiVersion = apiVersion,
IsDeprecated = deprecated,
SunsetPolicy = SunsetPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ),
+ DeprecationPolicy = DeprecationPolicyManager.ResolvePolicyOrDefault( metadata.Name, apiVersion ),
};
foreach ( var supportedResponseFormatter in supportedResponseFormatters )
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs
index 469c44f5..a8ce4ccc 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs
@@ -51,6 +51,12 @@ public ApiDescriptionGroup( ApiVersion apiVersion, string name )
/// The defined sunset policy defined for the API, if any.
public SunsetPolicy? SunsetPolicy { get; set; }
+ ///
+ /// Gets or sets described API deprecation policy.
+ ///
+ /// The defined deprecation policy defined for the API, if any.
+ public DeprecationPolicy? DeprecationPolicy { get; set; }
+
///
/// Gets a collection of API descriptions for the current version.
///
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs
index da07e7d1..5087f286 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs
@@ -49,6 +49,12 @@ public ApiVersion ApiVersion
/// The defined sunset policy defined for the API, if any.
public SunsetPolicy? SunsetPolicy { get; set; }
+ ///
+ /// Gets or sets the described API deprecation policy.
+ ///
+ /// The defined deprecation policy defined for the API, if any.
+ public DeprecationPolicy? DeprecationPolicy { get; set; }
+
///
/// Gets or sets the response description.
///
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj
index 761b386b..e226b019 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs
index 7725393c..dc142649 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs
@@ -18,7 +18,8 @@ internal DefaultContainer()
container.AddService( typeof( IApiVersionParser ), static ( sc, t ) => ApiVersionParser.Default );
container.AddService( typeof( IControllerNameConvention ), static ( sc, t ) => ControllerNameConvention.Default );
container.AddService( typeof( IProblemDetailsFactory ), static ( sc, t ) => new ProblemDetailsFactory() );
- container.AddService( typeof( ISunsetPolicyManager ), NewSunsetPolicyManager );
+ container.AddService( typeof( IPolicyManager ), NewSunsetPolicyManager );
+ container.AddService( typeof( IPolicyManager ), NewDeprecationPolicyManager );
container.AddService( typeof( IReportApiVersions ), NewApiVersionReporter );
}
@@ -66,17 +67,21 @@ public IEnumerable
diff --git a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs
index b42c2d28..974a5db0 100644
--- a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs
+++ b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs
@@ -94,8 +94,9 @@ public static async Task GetApiInformationAsync(
var deprecated = versions.ToArray();
var sunsetPolicy = response.ReadSunsetPolicy();
+ var deprecationPolicy = response.ReadDeprecationPolicy();
var urls = response.GetOpenApiDocumentUrls( parser );
- return new( supported, deprecated, sunsetPolicy, urls );
+ return new( supported, deprecated, sunsetPolicy, deprecationPolicy, urls );
}
}
\ No newline at end of file
diff --git a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs
index 0a430a32..a072788c 100644
--- a/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs
+++ b/src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpResponseMessageExtensions.cs
@@ -15,8 +15,13 @@ namespace System.Net.Http;
public static class HttpResponseMessageExtensions
{
private const string Sunset = nameof( Sunset );
+ private const string Deprecation = nameof( Deprecation );
private const string Link = nameof( Link );
+#if NETSTANDARD1_1
+ private static readonly DateTime UnixEpoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );
+#endif
+
///
/// Gets an API sunset policy from the HTTP response.
///
@@ -69,6 +74,87 @@ public static SunsetPolicy ReadSunsetPolicy( this HttpResponseMessage response )
return policy;
}
+ ///
+ /// Formats the as required for a Deprecation header.
+ ///
+ /// The date when the api is deprecated.
+ /// A formatted string as required for a Deprecation header.
+ public static string ToDeprecationHeaderValue( this DateTimeOffset deprecationDate )
+ {
+ var unixTimestamp = deprecationDate.ToUnixTimeSeconds();
+ return unixTimestamp.ToString( "'@'0", CultureInfo.InvariantCulture );
+ }
+
+ ///
+ /// Gets an API deprecation policy from the HTTP response.
+ ///
+ /// The HTTP response to read from.
+ /// A new deprecation policy.
+ public static DeprecationPolicy ReadDeprecationPolicy( this HttpResponseMessage response )
+ {
+ ArgumentNullException.ThrowIfNull( response );
+
+ var headers = response.Headers;
+ var date = default( DateTimeOffset );
+ DeprecationPolicy policy;
+
+ if ( headers.TryGetValues( Deprecation, out var values ) )
+ {
+ var culture = CultureInfo.InvariantCulture;
+ var style = NumberStyles.Integer;
+
+ foreach ( var value in values )
+ {
+ if ( value.Length < 2 || value[0] != '@' )
+ {
+ continue;
+ }
+
+#if NETSTANDARD
+ if ( long.TryParse( value.Substring( 1 ), style, culture, out var unixTimestamp ) )
+#else
+ if ( long.TryParse( value.AsSpan()[1..], style, culture, out var unixTimestamp ) )
+#endif
+ {
+ DateTimeOffset parsed;
+#if NETSTANDARD1_1
+ parsed = UnixEpoch + TimeSpan.FromSeconds( unixTimestamp );
+#else
+ parsed = DateTimeOffset.FromUnixTimeSeconds( unixTimestamp );
+#endif
+
+ if ( date == default || date > parsed )
+ {
+ date = parsed;
+ }
+ }
+ }
+
+ policy = date == default ? new() : new( date );
+ }
+ else
+ {
+ policy = new();
+ }
+
+ if ( headers.TryGetValues( Link, out values ) )
+ {
+ var baseUrl = response.RequestMessage?.RequestUri;
+ Func resolver = baseUrl is null ? url => url : url => new( baseUrl, url );
+
+ foreach ( var value in values )
+ {
+ if ( LinkHeaderValue.TryParse( value, resolver, out var link ) &&
+ link.RelationType.Equals( "deprecation", OrdinalIgnoreCase ) )
+ {
+ policy.Links.Add( link );
+ }
+ }
+ }
+
+ return policy;
+ }
+
///
/// Gets the OpenAPI document URLs from the HTTP response.
///
diff --git a/src/Client/src/Asp.Versioning.Http.Client/net#.0/ApiVersionHandlerLogger{T}.cs b/src/Client/src/Asp.Versioning.Http.Client/net#.0/ApiVersionHandlerLogger{T}.cs
index 4f8a8b86..818f360e 100644
--- a/src/Client/src/Asp.Versioning.Http.Client/net#.0/ApiVersionHandlerLogger{T}.cs
+++ b/src/Client/src/Asp.Versioning.Http.Client/net#.0/ApiVersionHandlerLogger{T}.cs
@@ -36,8 +36,9 @@ protected override void OnApiDeprecated( ApiNotificationContext context )
var requestUrl = context.Response.RequestMessage!.RequestUri!;
var apiVersion = context.ApiVersion;
var sunsetPolicy = context.SunsetPolicy;
+ var deprecationPolicy = context.DeprecationPolicy;
- logger.ApiVersionDeprecated( requestUrl, apiVersion, sunsetPolicy );
+ logger.ApiVersionDeprecated( requestUrl, apiVersion, sunsetPolicy, deprecationPolicy );
}
///
diff --git a/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs b/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs
index a7535807..841e086a 100644
--- a/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs
+++ b/src/Client/src/Asp.Versioning.Http.Client/net#.0/ILoggerExtensions.cs
@@ -16,25 +16,33 @@ internal static void ApiVersionDeprecated(
this ILogger logger,
Uri requestUrl,
ApiVersion apiVersion,
- SunsetPolicy sunsetPolicy )
+ SunsetPolicy sunsetPolicy,
+ DeprecationPolicy deprecationPolicy )
{
var sunsetDate = FormatDate( sunsetPolicy.Date );
- var additionalInfo = FormatLinks( sunsetPolicy );
+ var deprecationDate = FormatDate( deprecationPolicy.Date );
+
+ var additionalInfoSunset = FormatLinks( sunsetPolicy );
+ var additionalInfoDeprecation = FormatLinks( deprecationPolicy );
+
+ var additionalInfo = additionalInfoDeprecation.Concat( additionalInfoSunset ).ToArray();
ApiVersionDeprecated(
logger,
apiVersion.ToString(),
requestUrl.OriginalString,
sunsetDate,
+ deprecationDate,
additionalInfo );
}
- [LoggerMessage( EventId = 1, Level = Warning, Message = "API version {apiVersion} for {requestUrl} has been deprecated and will sunset on {sunsetDate}. Additional information: {links}" )]
+ [LoggerMessage( EventId = 1, Level = Warning, Message = "API version {apiVersion} for {requestUrl} has been deprecated since {deprecationDate} and will sunset on {sunsetDate}. Additional information: {links}" )]
static partial void ApiVersionDeprecated(
ILogger logger,
string apiVersion,
string requestUrl,
string sunsetDate,
+ string deprecationDate,
string[] links );
[MethodImpl( MethodImplOptions.AggressiveInlining )]
@@ -77,9 +85,23 @@ private static string[] FormatLinks( SunsetPolicy sunsetPolicy )
return [];
}
+ return FormatLinks( sunsetPolicy.Links );
+ }
+
+ private static string[] FormatLinks( DeprecationPolicy deprecationPolicy )
+ {
+ if ( !deprecationPolicy.HasLinks )
+ {
+ return [];
+ }
+
+ return FormatLinks( deprecationPolicy.Links );
+ }
+
+ private static string[] FormatLinks( IList links )
+ {
// ([,]):
var text = new StringBuilder();
- var links = sunsetPolicy.Links;
var additionalInfo = new string[links.Count];
for ( var i = 0; i < links.Count; i++ )
diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs
index 348b4886..fb87a691 100644
--- a/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs
+++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/ApiVersionHandlerTest.cs
@@ -25,7 +25,31 @@ public async Task send_async_should_write_api_version_to_request()
}
[Fact]
- public async Task send_async_should_signal_deprecated_api_version()
+ public async Task send_async_should_not_notify_when_no_headers_are_set()
+ {
+ // arrange
+ var writer = Mock.Of();
+ var notification = Mock.Of();
+ var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
+ var version = new ApiVersion( 1.0 );
+ using var handler = new ApiVersionHandler( writer, version, notification )
+ {
+ InnerHandler = new TestServer(),
+ };
+ using var invoker = new HttpMessageInvoker( handler );
+
+ // act
+ await invoker.SendAsync( request, default );
+
+ // assert
+ Mock.Get( notification )
+ .Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), It.IsAny() ), Times.Never );
+ Mock.Get( notification )
+ .Verify( n => n.OnNewApiAvailableAsync( It.IsAny(), It.IsAny() ), Times.Never );
+ }
+
+ [Fact]
+ public async Task send_async_should_signal_deprecated_api_versions_from_header()
{
// arrange
var writer = Mock.Of();
@@ -50,6 +74,32 @@ public async Task send_async_should_signal_deprecated_api_version()
.Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), default ) );
}
+ [Fact]
+ public async Task send_async_should_signal_deprecated_api_versions_from_deprecation_policy()
+ {
+ // arrange
+ var writer = Mock.Of();
+ var notification = Mock.Of();
+ var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
+ var response = new HttpResponseMessage();
+ var version = new ApiVersion( 1.0 );
+ using var handler = new ApiVersionHandler( writer, version, notification )
+ {
+ InnerHandler = new TestServer( response ),
+ };
+ using var invoker = new HttpMessageInvoker( handler );
+
+ response.Headers.Add( "api-supported-versions", "2.0" );
+ response.Headers.Add( "Deprecation", DateTimeOffset.UtcNow.ToDeprecationHeaderValue() );
+
+ // act
+ await invoker.SendAsync( request, default );
+
+ // assert
+ Mock.Get( notification )
+ .Verify( n => n.OnApiDeprecatedAsync( It.IsAny(), default ) );
+ }
+
[Fact]
public async Task send_async_should_signal_new_api_version()
{
diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs
index 6ac1890a..b64a396e 100644
--- a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs
+++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpClientExtensionsTest.cs
@@ -41,6 +41,7 @@ public async Task get_api_information_async_should_return_expected_result()
{
Type = "text/html",
} ),
+ new(),
new Dictionary() { [new( 1.0 )] = new( "http://tempuri.org/swagger/v1/swagger.json" ) } ) );
}
}
\ No newline at end of file
diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs
index bb4ffb45..ef10b244 100644
--- a/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs
+++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/System.Net.Http/HttpResponseMessageExtensionsTest.cs
@@ -10,7 +10,7 @@ public class HttpResponseMessageExtensionsTest
public void read_sunset_policy_should_parse_response()
{
// arrange
- var date = DateTimeOffset.Now;
+ var date = DateTimeOffset.UtcNow;
var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
var response = new HttpResponseMessage() { RequestMessage = request };
@@ -35,7 +35,7 @@ public void read_sunset_policy_should_parse_response()
public void read_sunset_policy_should_use_greatest_date()
{
// arrange
- var date = DateTimeOffset.Now;
+ var date = DateTimeOffset.UtcNow;
var expected = date.AddDays( 14 );
var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
var response = new HttpResponseMessage() { RequestMessage = request };
@@ -87,6 +87,87 @@ public void read_sunset_policy_should_ignore_unrelated_links()
} );
}
+ [Fact]
+ public void read_deprecation_policy_should_parse_response()
+ {
+ // arrange
+ var date = DateTimeOffset.UtcNow;
+ var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
+ var response = new HttpResponseMessage() { RequestMessage = request };
+
+ response.Headers.Add( "deprecation", date.ToDeprecationHeaderValue() );
+ response.Headers.Add( "link", "; rel=\"deprecation\"; type=\"text/html\"" );
+
+ // act
+ var policy = response.ReadDeprecationPolicy();
+
+ // assert
+ policy.Date.Value.ToLocalTime().Should().BeCloseTo( date, TimeSpan.FromSeconds( 2d ) );
+ policy.Links.Single().Should().BeEquivalentTo(
+ new LinkHeaderValue(
+ new Uri( "http://tempuri.org/policy" ),
+ "deprecation" )
+ {
+ Type = "text/html",
+ } );
+ }
+
+ [Fact]
+ public void read_deprecation_policy_should_use_smallest_date()
+ {
+ // arrange
+ var date = DateTimeOffset.UtcNow;
+ var expected = date.Subtract( TimeSpan.FromDays( 14 ) );
+ var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
+ var response = new HttpResponseMessage() { RequestMessage = request };
+
+ response.Headers.Add(
+ "deprecation",
+ new string[]
+ {
+ date.ToDeprecationHeaderValue(),
+ expected.ToDeprecationHeaderValue(),
+ expected.AddDays( 3 ).ToDeprecationHeaderValue(),
+ } );
+
+ // act
+ var policy = response.ReadDeprecationPolicy();
+
+ // assert
+ policy.Date.Value.ToLocalTime().Should().BeCloseTo( expected, TimeSpan.FromSeconds( 2d ) );
+ policy.HasLinks.Should().BeFalse();
+ }
+
+ [Fact]
+ public void read_deprecation_policy_should_ignore_unrelated_links()
+ {
+ // arrange
+ var request = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" );
+ var response = new HttpResponseMessage() { RequestMessage = request };
+
+ response.Headers.Add(
+ "link",
+ new[]
+ {
+ "; rel=\"openapi\"; type=\"application/json\" title=\"OpenAPI\"",
+ "; rel=\"deprecation\"; type=\"text/html\"",
+ "; rel=\"info\"; type=\"text/html\" title=\"Documentation\"",
+ } );
+
+ // act
+ var policy = response.ReadDeprecationPolicy();
+
+ // assert
+ policy.Date.Should().BeNull();
+ policy.Links.Single().Should().BeEquivalentTo(
+ new LinkHeaderValue(
+ new Uri( "http://tempuri.org/policy" ),
+ "deprecation" )
+ {
+ Type = "text/html",
+ } );
+ }
+
[Fact]
public void get_open_api_document_urls_should_return_expected_values()
{
diff --git a/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs b/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs
index 7662f2ce..510dec39 100644
--- a/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs
+++ b/src/Client/test/Asp.Versioning.Http.Client.Tests/net#.0/ApiVersionHandlerLoggerTTest.cs
@@ -21,10 +21,10 @@ public async Task on_api_deprecated_should_log_message()
};
var context = new ApiNotificationContext( response, new ApiVersion( 1.0 ) );
var date = DateTimeOffset.Now;
- var expected = "API version 1.0 for http://tempuri.org has been deprecated and will " +
+ var expected = "API version 1.0 for http://tempuri.org has been deprecated since and will " +
$"sunset on {date.ToUniversalTime()}. Additional information: " +
- "API Policy (en): http://tempuri.org/policy/en, " +
- "API Política (es): http://tempuri.org/policy/es";
+ "[API Policy (en): http://tempuri.org/policy/en, " +
+ "API Política (es): http://tempuri.org/policy/es]";
response.Headers.Add( "sunset", date.ToString( "r" ) );
response.Headers.Add( "link", "; rel=\"sunset\"; type=\"text/html\"; title=\"API Policy\"; hreflang=\"en\"" );
diff --git a/src/Common/src/Common.Backport/DateTimeOffsetExtensions.cs b/src/Common/src/Common.Backport/DateTimeOffsetExtensions.cs
new file mode 100644
index 00000000..724e6525
--- /dev/null
+++ b/src/Common/src/Common.Backport/DateTimeOffsetExtensions.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace System;
+
+internal static class DateTimeOffsetExtensions
+{
+ private const long UnixEpochSeconds = 62_135_596_800L;
+
+ // REF: https://github.com/dotnet/dotnet/blob/main/src/runtime/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs#L745
+ public static long ToUnixTimeSeconds( this DateTimeOffset dateTimeOffset )
+ {
+ var seconds = (long) ( (ulong) dateTimeOffset.UtcTicks / TimeSpan.TicksPerSecond );
+ return seconds - UnixEpochSeconds;
+ }
+}
\ No newline at end of file
diff --git a/src/Common/src/Common/ApiVersioningPolicyBuilder.cs b/src/Common/src/Common/ApiVersioningPolicyBuilder.cs
index 2eb50a29..55687f82 100644
--- a/src/Common/src/Common/ApiVersioningPolicyBuilder.cs
+++ b/src/Common/src/Common/ApiVersioningPolicyBuilder.cs
@@ -10,6 +10,7 @@ namespace Asp.Versioning;
public class ApiVersioningPolicyBuilder : IApiVersioningPolicyBuilder
{
private Dictionary? sunsetPolicies;
+ private Dictionary? deprecationPolicies;
///
public virtual IReadOnlyList OfType() where T : notnull
@@ -18,6 +19,10 @@ public virtual IReadOnlyList OfType() where T : notnull
{
return ( sunsetPolicies.Values.ToArray() as IReadOnlyList )!;
}
+ else if ( typeof( T ) == typeof( IDeprecationPolicyBuilder ) && deprecationPolicies != null )
+ {
+ return ( deprecationPolicies.Values.ToArray() as IReadOnlyList )!;
+ }
return Array.Empty();
}
@@ -42,4 +47,25 @@ public virtual ISunsetPolicyBuilder Sunset( string? name, ApiVersion? apiVersion
return builder;
}
+
+ ///
+ public virtual IDeprecationPolicyBuilder Deprecate( string? name, ApiVersion? apiVersion )
+ {
+ if ( string.IsNullOrEmpty( name ) && apiVersion == null )
+ {
+ var message = string.Format( CultureInfo.CurrentCulture, Format.InvalidPolicyKey, nameof( name ), nameof( apiVersion ) );
+ throw new System.ArgumentException( message );
+ }
+
+ var key = new PolicyKey( name, apiVersion );
+
+ deprecationPolicies ??= [];
+
+ if ( !deprecationPolicies.TryGetValue( key, out var builder ) )
+ {
+ deprecationPolicies.Add( key, builder = new DeprecationPolicyBuilder( name, apiVersion ) );
+ }
+
+ return builder;
+ }
}
\ No newline at end of file
diff --git a/src/Common/src/Common/DefaultApiVersionReporter.cs b/src/Common/src/Common/DefaultApiVersionReporter.cs
index 72ed1408..84c71984 100644
--- a/src/Common/src/Common/DefaultApiVersionReporter.cs
+++ b/src/Common/src/Common/DefaultApiVersionReporter.cs
@@ -22,14 +22,16 @@ public sealed partial class DefaultApiVersionReporter : IReportApiVersions
private const string ApiDeprecatedVersions = "api-deprecated-versions";
private const string Sunset = nameof( Sunset );
private const string Link = nameof( Link );
- private readonly ISunsetPolicyManager sunsetPolicyManager;
+ private readonly IPolicyManager sunsetPolicyManager;
+ private readonly IPolicyManager deprecationPolicyManager;
private readonly string apiSupportedVersionsName;
private readonly string apiDeprecatedVersionsName;
///
/// Initializes a new instance of the class.
///
- /// The manager used to resolve sunset policies.
+ /// The manager used to resolve sunset policies.
+ /// The manager used to resolve deprecation policies.
/// The HTTP header name used for supported API versions.
/// The default value is "api-supported-versions".
/// THe HTTP header name used for deprecated API versions.
@@ -37,7 +39,8 @@ public sealed partial class DefaultApiVersionReporter : IReportApiVersions
/// One or more of API versioning mappings. The default value is
/// and .
public DefaultApiVersionReporter(
- ISunsetPolicyManager sunsetPolicyManager,
+ IPolicyManager sunsetPolicyManager,
+ IPolicyManager deprecationPolicyManager,
string supportedHeaderName = ApiSupportedVersions,
string deprecatedHeaderName = ApiDeprecatedVersions,
ApiVersionMapping mapping = Explicit | Implicit )
@@ -47,6 +50,7 @@ public DefaultApiVersionReporter(
ArgumentException.ThrowIfNullOrEmpty( deprecatedHeaderName );
this.sunsetPolicyManager = sunsetPolicyManager;
+ this.deprecationPolicyManager = deprecationPolicyManager;
apiSupportedVersionsName = supportedHeaderName;
apiDeprecatedVersionsName = deprecatedHeaderName;
Mapping = mapping;
@@ -90,10 +94,21 @@ public void Report( HttpResponse response, ApiVersionModel apiVersionModel )
var version = context.GetRequestedApiVersion();
#endif
var name = metadata.Name;
+ DateTimeOffset? sunsetDate = null;
- if ( sunsetPolicyManager.TryResolvePolicy( name, version, out var policy ) )
+ if ( sunsetPolicyManager.TryResolvePolicy( name, version, out var sunsetPolicy ) )
{
- response.WriteSunsetPolicy( policy );
+ sunsetDate = sunsetPolicy.Date;
+ response.WriteSunsetPolicy( sunsetPolicy );
+ }
+
+ if ( deprecationPolicyManager.TryResolvePolicy( name, version, out var deprecationPolicy ) )
+ {
+ // Only emit a deprecation header if the deprecation policy becomes effective before the sunset date.
+ if ( deprecationPolicy.IsEffective( sunsetDate ) )
+ {
+ response.WriteDeprecationPolicy( deprecationPolicy );
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Common/src/Common/DeprecationPolicyBuilder.cs b/src/Common/src/Common/DeprecationPolicyBuilder.cs
new file mode 100644
index 00000000..ca80bb2e
--- /dev/null
+++ b/src/Common/src/Common/DeprecationPolicyBuilder.cs
@@ -0,0 +1,96 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Represents the default deprecation policy builder.
+///
+public class DeprecationPolicyBuilder : PolicyBuilder, IDeprecationPolicyBuilder
+{
+ private DateTimeOffset? date;
+ private DeprecationLinkBuilder? linkBuilder;
+ private Dictionary? linkBuilders;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the API the policy is for.
+ /// The API version the policy is for.
+ public DeprecationPolicyBuilder( string? name, ApiVersion? apiVersion )
+ : base( name, apiVersion ) { }
+
+ ///
+ public virtual void SetEffectiveDate( DateTimeOffset effectiveDate )
+ {
+ date = effectiveDate;
+ }
+
+ ///
+ public virtual ILinkBuilder Link( Uri linkTarget )
+ {
+ DeprecationLinkBuilder newLinkBuilder;
+
+ if ( linkBuilder == null )
+ {
+ linkBuilder = newLinkBuilder = new( this, linkTarget );
+ }
+ else if ( linkBuilder.LinkTarget.Equals( linkTarget ) )
+ {
+ return linkBuilder;
+ }
+ else if ( linkBuilders == null )
+ {
+ linkBuilders = new()
+ {
+ [linkBuilder.LinkTarget] = linkBuilder,
+ [linkTarget] = newLinkBuilder = new( this, linkTarget ),
+ };
+ }
+ else if ( !linkBuilders.TryGetValue( linkTarget, out newLinkBuilder! ) )
+ {
+ linkBuilders.Add( linkTarget, newLinkBuilder = new( this, linkTarget ) );
+ }
+
+ return newLinkBuilder;
+ }
+
+ ///
+ public override DeprecationPolicy Build()
+ {
+ if ( Policy is not null )
+ {
+ return Policy;
+ }
+
+ DeprecationPolicy policy = date is null ? new() : new( date.Value );
+
+ if ( linkBuilders == null )
+ {
+ if ( linkBuilder != null )
+ {
+ policy.Links.Add( linkBuilder.Build() );
+ }
+ }
+ else
+ {
+ foreach ( var builder in linkBuilders.Values )
+ {
+ policy.Links.Add( builder.Build() );
+ }
+ }
+
+ return policy;
+ }
+
+ private sealed class DeprecationLinkBuilder : LinkBuilder, ILinkBuilder
+ {
+ protected override string RelationType => "deprecation";
+
+ private readonly DeprecationPolicyBuilder policyBuilder;
+
+ public DeprecationLinkBuilder( DeprecationPolicyBuilder policy, Uri linkTarget )
+ : base( linkTarget ) => policyBuilder = policy;
+
+ public override ILinkBuilder Link( Uri linkTarget ) => policyBuilder.Link( linkTarget );
+ }
+}
\ No newline at end of file
diff --git a/src/Common/src/Common/DeprecationPolicyManager.cs b/src/Common/src/Common/DeprecationPolicyManager.cs
new file mode 100644
index 00000000..3c8765da
--- /dev/null
+++ b/src/Common/src/Common/DeprecationPolicyManager.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Represents the default API version deprecation policy manager.
+///
+///
+/// This class serves as a type alias to hide the generic arguments of .
+///
+public partial class DeprecationPolicyManager : PolicyManager
+{ }
\ No newline at end of file
diff --git a/src/Common/src/Common/SunsetLinkBuilder.cs b/src/Common/src/Common/LinkBuilder.cs
similarity index 80%
rename from src/Common/src/Common/SunsetLinkBuilder.cs
rename to src/Common/src/Common/LinkBuilder.cs
index 03f72175..16075de3 100644
--- a/src/Common/src/Common/SunsetLinkBuilder.cs
+++ b/src/Common/src/Common/LinkBuilder.cs
@@ -2,17 +2,16 @@
namespace Asp.Versioning;
-internal sealed class SunsetLinkBuilder : ILinkBuilder
+internal abstract class LinkBuilder : ILinkBuilder
{
- private readonly SunsetPolicyBuilder policy;
+ protected abstract string RelationType { get; }
private string? language;
private List? languages;
private string? title;
private string? type;
- public SunsetLinkBuilder( SunsetPolicyBuilder policy, Uri linkTarget )
+ public LinkBuilder( Uri linkTarget )
{
- this.policy = policy;
LinkTarget = linkTarget;
}
@@ -36,8 +35,6 @@ public ILinkBuilder Language( string value )
return this;
}
- public ILinkBuilder Link( Uri linkTarget ) => policy.Link( linkTarget );
-
public ILinkBuilder Title( string value )
{
title = value;
@@ -50,9 +47,11 @@ public ILinkBuilder Type( string value )
return this;
}
+ public abstract ILinkBuilder Link( Uri linkTarget );
+
public LinkHeaderValue Build()
{
- var link = new LinkHeaderValue( LinkTarget, "sunset" );
+ var link = new LinkHeaderValue( LinkTarget, RelationType );
if ( title != null )
{
diff --git a/src/Common/src/Common/PolicyBuilder.cs b/src/Common/src/Common/PolicyBuilder.cs
new file mode 100644
index 00000000..a493d906
--- /dev/null
+++ b/src/Common/src/Common/PolicyBuilder.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using System.Globalization;
+
+///
+/// Represents the default policy builder.
+///
+/// The type of policy.
+public abstract class PolicyBuilder : IPolicyBuilder
+{
+ ///
+ /// Gets a pre-built policy.
+ ///
+ /// The pre-built policy, if it exists.
+ protected TPolicy? Policy { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the API the policy is for.
+ /// The API version the policy is for.
+ protected PolicyBuilder( string? name, ApiVersion? apiVersion )
+ {
+ if ( string.IsNullOrEmpty( name ) && apiVersion == null )
+ {
+ var message = string.Format( CultureInfo.CurrentCulture, Format.InvalidPolicyKey, nameof( name ), nameof( apiVersion ) );
+ throw new System.ArgumentException( message );
+ }
+
+ Name = name;
+ ApiVersion = apiVersion;
+ }
+
+ ///
+ public string? Name { get; }
+
+ ///
+ public ApiVersion? ApiVersion { get; }
+
+ ///
+ public virtual void Per( TPolicy policy ) =>
+ Policy = policy ?? throw new System.ArgumentNullException( nameof( policy ) );
+
+ ///
+ public abstract TPolicy Build();
+}
\ No newline at end of file
diff --git a/src/Common/src/Common/PolicyManager.cs b/src/Common/src/Common/PolicyManager.cs
new file mode 100644
index 00000000..ee06951f
--- /dev/null
+++ b/src/Common/src/Common/PolicyManager.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+public abstract class PolicyManager : IPolicyManager
+ where TPolicyBuilder : IPolicyBuilder
+{
+ private Dictionary? policies;
+
+ ///
+ /// Gets the current api versioning options.
+ ///
+ /// The api versioning options.
+ protected abstract ApiVersioningOptions Options { get; }
+
+ ///
+ public virtual bool TryGetPolicy(
+ string? name,
+ ApiVersion? apiVersion,
+ [MaybeNullWhen( false )] out TPolicy policy )
+ {
+ if ( string.IsNullOrEmpty( name ) && apiVersion == null )
+ {
+ policy = default!;
+ return false;
+ }
+
+ policies ??= BuildPolicies( Options );
+
+ var key = new PolicyKey( name, apiVersion );
+
+ return policies.TryGetValue( key, out policy );
+ }
+
+ private static Dictionary BuildPolicies( ApiVersioningOptions options )
+ {
+ var builders = options.Policies.OfType();
+ var mapping = new Dictionary( capacity: builders.Count );
+
+ for ( var i = 0; i < builders.Count; i++ )
+ {
+ var builder = builders[i];
+ var policy = builder.Build();
+ var key = new PolicyKey( builder.Name, builder.ApiVersion );
+
+ mapping[key] = policy;
+ }
+
+ return mapping;
+ }
+}
\ No newline at end of file
diff --git a/src/Common/src/Common/SunsetPolicyBuilder.cs b/src/Common/src/Common/SunsetPolicyBuilder.cs
index a6450b49..dc189696 100644
--- a/src/Common/src/Common/SunsetPolicyBuilder.cs
+++ b/src/Common/src/Common/SunsetPolicyBuilder.cs
@@ -2,14 +2,11 @@
namespace Asp.Versioning;
-using System.Globalization;
-
///
/// Represents the default sunset policy builder.
///
-public class SunsetPolicyBuilder : ISunsetPolicyBuilder
+public class SunsetPolicyBuilder : PolicyBuilder, ISunsetPolicyBuilder
{
- private SunsetPolicy? sunsetPolicy;
private DateTimeOffset? date;
private SunsetLinkBuilder? linkBuilder;
private Dictionary? linkBuilders;
@@ -20,32 +17,12 @@ public class SunsetPolicyBuilder : ISunsetPolicyBuilder
/// The name of the API the policy is for.
/// The API version the policy is for.
public SunsetPolicyBuilder( string? name, ApiVersion? apiVersion )
- {
- if ( string.IsNullOrEmpty( name ) && apiVersion == null )
- {
- var message = string.Format( CultureInfo.CurrentCulture, Format.InvalidPolicyKey, nameof( name ), nameof( apiVersion ) );
- throw new System.ArgumentException( message );
- }
-
- Name = name;
- ApiVersion = apiVersion;
- }
-
- ///
- public string? Name { get; }
-
- ///
- public ApiVersion? ApiVersion { get; }
-
- ///
- public virtual void Per( SunsetPolicy policy ) =>
- sunsetPolicy = policy ?? throw new System.ArgumentNullException( nameof( policy ) );
+ : base( name, apiVersion ) { }
///
- public virtual ISunsetPolicyBuilder Effective( DateTimeOffset sunsetDate )
+ public virtual void SetEffectiveDate( DateTimeOffset effectiveDate )
{
- date = sunsetDate;
- return this;
+ date = effectiveDate;
}
///
@@ -78,11 +55,11 @@ public virtual ILinkBuilder Link( Uri linkTarget )
}
///
- public virtual SunsetPolicy Build()
+ public override SunsetPolicy Build()
{
- if ( sunsetPolicy is not null )
+ if ( Policy is not null )
{
- return sunsetPolicy;
+ return Policy;
}
SunsetPolicy policy = date is null ? new() : new( date.Value );
@@ -104,4 +81,16 @@ public virtual SunsetPolicy Build()
return policy;
}
+
+ private sealed class SunsetLinkBuilder : LinkBuilder, ILinkBuilder
+ {
+ protected override string RelationType => "sunset";
+
+ private readonly SunsetPolicyBuilder policyBuilder;
+
+ public SunsetLinkBuilder( SunsetPolicyBuilder policy, Uri linkTarget )
+ : base( linkTarget ) => policyBuilder = policy;
+
+ public override ILinkBuilder Link( Uri linkTarget ) => policyBuilder.Link( linkTarget );
+ }
}
\ No newline at end of file
diff --git a/src/Common/src/Common/SunsetPolicyManager.cs b/src/Common/src/Common/SunsetPolicyManager.cs
index 50fdf115..9cfe0c73 100644
--- a/src/Common/src/Common/SunsetPolicyManager.cs
+++ b/src/Common/src/Common/SunsetPolicyManager.cs
@@ -5,46 +5,8 @@ namespace Asp.Versioning;
///
/// Represents the default API version sunset policy manager.
///
-public partial class SunsetPolicyManager : ISunsetPolicyManager
-{
- private Dictionary? policies;
-
- ///
- public virtual bool TryGetPolicy(
- string? name,
- ApiVersion? apiVersion,
- [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
- {
- if ( string.IsNullOrEmpty( name ) && apiVersion == null )
- {
- sunsetPolicy = default!;
- return false;
- }
-
-#if NETFRAMEWORK
- policies ??= BuildPolicies( options );
-#else
- policies ??= BuildPolicies( options.Value );
-#endif
- var key = new PolicyKey( name, apiVersion );
-
- return policies.TryGetValue( key, out sunsetPolicy );
- }
-
- private static Dictionary BuildPolicies( ApiVersioningOptions options )
- {
- var builders = options.Policies.OfType();
- var mapping = new Dictionary( capacity: builders.Count );
-
- for ( var i = 0; i < builders.Count; i++ )
- {
- var builder = builders[i];
- var policy = builder.Build();
- var key = new PolicyKey( builder.Name, builder.ApiVersion );
-
- mapping[key] = policy;
- }
-
- return mapping;
- }
-}
\ No newline at end of file
+///
+/// This class serves as a type alias to hide the generic arguments of .
+///
+public partial class SunsetPolicyManager : PolicyManager
+{ }
\ No newline at end of file
diff --git a/src/Common/test/Common.Tests/ApiVersioningPolicyBuilderTest.cs b/src/Common/test/Common.Tests/ApiVersioningPolicyBuilderTest.cs
index 727c9060..82f0839e 100644
--- a/src/Common/test/Common.Tests/ApiVersioningPolicyBuilderTest.cs
+++ b/src/Common/test/Common.Tests/ApiVersioningPolicyBuilderTest.cs
@@ -36,6 +36,38 @@ public void sunset_should_return_same_policy_builder( string name, double? versi
result.Should().BeSameAs( expected );
}
+ [Fact]
+ public void deprecate_should_not_allow_empty_name_and_version()
+ {
+ // arrange
+ var builder = new ApiVersioningPolicyBuilder();
+
+ // act
+ Func deprecation = () => builder.Deprecate( default, default );
+
+ // assert
+ deprecation.Should().Throw().And
+ .Message.Should().Be( "'name' and 'apiVersion' cannot both be null." );
+ }
+
+ [Theory]
+ [InlineData( "Test", null )]
+ [InlineData( null, 1.1 )]
+ [InlineData( "Test", 1.1 )]
+ public void deprecate_should_return_same_policy_builder( string name, double? version )
+ {
+ // arrange
+ var apiVersion = version is null ? default : new ApiVersion( version.Value );
+ var builder = new ApiVersioningPolicyBuilder();
+ var expected = builder.Deprecate( name, apiVersion );
+
+ // act
+ var result = builder.Deprecate( name, apiVersion );
+
+ // assert
+ result.Should().BeSameAs( expected );
+ }
+
[Fact]
public void of_type_should_return_empty_list_for_unknown_type()
{
@@ -50,16 +82,34 @@ public void of_type_should_return_empty_list_for_unknown_type()
}
[Fact]
- public void of_type_should_return_filtered_builders()
+ public void of_type_sunset_should_return_filtered_builders()
{
// arrange
var builder = new ApiVersioningPolicyBuilder();
var expected = builder.Sunset( default, ApiVersion.Default );
+ var deprecation = builder.Deprecate( default, ApiVersion.Default );
// act
var list = builder.OfType();
// assert
list.Single().Should().BeSameAs( expected );
+ list.Single().Should().NotBeSameAs( deprecation );
+ }
+
+ [Fact]
+ public void of_type_deprecation_should_return_filtered_builders()
+ {
+ // arrange
+ var builder = new ApiVersioningPolicyBuilder();
+ var sunset = builder.Sunset( default, ApiVersion.Default );
+ var expected = builder.Deprecate( default, ApiVersion.Default );
+
+ // act
+ var list = builder.OfType();
+
+ // assert
+ list.Single().Should().BeSameAs( expected );
+ list.Single().Should().NotBeSameAs( sunset );
}
}
\ No newline at end of file
diff --git a/src/Common/test/Common.Tests/DeprecationPolicyBuilderTest.cs b/src/Common/test/Common.Tests/DeprecationPolicyBuilderTest.cs
new file mode 100644
index 00000000..778fa5f8
--- /dev/null
+++ b/src/Common/test/Common.Tests/DeprecationPolicyBuilderTest.cs
@@ -0,0 +1,71 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+public class DeprecationPolicyBuilderTest
+{
+ [Fact]
+ public void constructor_should_not_allow_empty_name_and_version()
+ {
+ // arrange
+
+
+ // act
+ Func @new = () => new DeprecationPolicyBuilder( default, default );
+
+ // assert
+ @new.Should().Throw().And
+ .Message.Should().Be( "'name' and 'apiVersion' cannot both be null." );
+ }
+
+ [Fact]
+ public void per_should_set_existing_immutable_policy()
+ {
+ // arrange
+ var builder = new DeprecationPolicyBuilder( default, ApiVersion.Default );
+ var policy = new DeprecationPolicy();
+
+ // act
+ builder.Per( policy );
+ builder.Link( "http://tempuri.org" );
+
+ var result = builder.Build();
+
+ // assert
+ result.Should().BeSameAs( policy );
+ policy.HasLinks.Should().BeFalse();
+ }
+
+ [Fact]
+ public void link_should_should_return_existing_builder()
+ {
+ // arrange
+ var builder = new DeprecationPolicyBuilder( default, ApiVersion.Default );
+ var expected = builder.Link( "http://tempuri.org" );
+
+ // act
+ var result = builder.Link( "http://tempuri.org" );
+
+ // assert
+ result.Should().BeSameAs( expected );
+ }
+
+ [Fact]
+ public void build_should_construct_deprecation_policy()
+ {
+ // arrange
+ var builder = new DeprecationPolicyBuilder( default, ApiVersion.Default );
+
+ builder.Effective( 2022, 2, 1 )
+ .Link( "http://tempuri.org" );
+
+ // act
+ var policy = builder.Build();
+
+ // assert
+ policy.Should().BeEquivalentTo(
+ new DeprecationPolicy(
+ new DateTimeOffset( new DateTime( 2022, 2, 1 ) ),
+ new LinkHeaderValue( new Uri( "http://tempuri.org" ), "deprecation" ) ) );
+ }
+}
\ No newline at end of file
diff --git a/src/Common/test/Common.Tests/DeprecationPolicyManagerTest.cs b/src/Common/test/Common.Tests/DeprecationPolicyManagerTest.cs
new file mode 100644
index 00000000..90994d5f
--- /dev/null
+++ b/src/Common/test/Common.Tests/DeprecationPolicyManagerTest.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+#if !NETFRAMEWORK
+using Microsoft.Extensions.Options;
+#endif
+
+public class DeprecationPolicyManagerTest
+{
+ [Fact]
+ public void try_get_policy_should_return_false_for_no_name_and_version()
+ {
+ // arrange
+ var options = new ApiVersioningOptions();
+ var manager = NewDeprecationPolicyManager( options );
+
+ // act
+ var result = manager.TryGetPolicy( default, default, out _ );
+
+ // assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void try_get_policy_should_return_false_without_any_policies()
+ {
+ // arrange
+ var options = new ApiVersioningOptions();
+ var manager = NewDeprecationPolicyManager( options );
+
+ // act
+ var result = manager.TryGetPolicy( ApiVersion.Default, out _ );
+
+ // assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void try_get_policy_should_return_true_for_matching_policy()
+ {
+ // arrange
+ var options = new ApiVersioningOptions();
+ var manager = NewDeprecationPolicyManager( options );
+
+ options.Policies.Deprecate( ApiVersion.Default ).Effective( 2022, 2, 1 );
+
+ // act
+ var result = manager.TryGetPolicy( ApiVersion.Default, out var policy );
+
+ // assert
+ result.Should().BeTrue();
+ policy.Should().NotBeNull();
+ }
+
+ private static DeprecationPolicyManager NewDeprecationPolicyManager( ApiVersioningOptions options )
+ {
+#if NETFRAMEWORK
+ return new( options );
+#else
+ return new(Options.Create(options));
+#endif
+ }
+}
\ No newline at end of file