Skip to content

Commit c882dc2

Browse files
author
Matthias Gessinger
committed
Emit Deprecation header when policy is set
1 parent ec9c98f commit c882dc2

File tree

7 files changed

+157
-13
lines changed

7 files changed

+157
-13
lines changed

src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal DefaultContainer()
1919
container.AddService( typeof( IControllerNameConvention ), static ( sc, t ) => ControllerNameConvention.Default );
2020
container.AddService( typeof( IProblemDetailsFactory ), static ( sc, t ) => new ProblemDetailsFactory() );
2121
container.AddService( typeof( ISunsetPolicyManager ), NewSunsetPolicyManager );
22+
container.AddService( typeof( IDeprecationPolicyManager ), NewDeprecationPolicyManager );
2223
container.AddService( typeof( IReportApiVersions ), NewApiVersionReporter );
2324
}
2425

@@ -69,14 +70,18 @@ private static ApiVersioningOptions GetApiVersioningOptions( IServiceProvider se
6970
private static ISunsetPolicyManager NewSunsetPolicyManager( IServiceProvider serviceProvider, Type type ) =>
7071
new SunsetPolicyManager( GetApiVersioningOptions( serviceProvider ) );
7172

73+
private static IDeprecationPolicyManager NewDeprecationPolicyManager( IServiceProvider serviceProvider, Type type ) =>
74+
new DeprecationPolicyManager( GetApiVersioningOptions( serviceProvider ) );
75+
7276
private static IReportApiVersions NewApiVersionReporter( IServiceProvider serviceProvider, Type type )
7377
{
7478
var options = GetApiVersioningOptions( serviceProvider );
7579

7680
if ( options.ReportApiVersions )
7781
{
7882
var sunsetPolicyManager = (ISunsetPolicyManager) serviceProvider.GetService( typeof( ISunsetPolicyManager ) );
79-
return new DefaultApiVersionReporter( sunsetPolicyManager );
83+
var deprecationPolicyManager = (IDeprecationPolicyManager) serviceProvider.GetService( typeof( IDeprecationPolicyManager ) );
84+
return new DefaultApiVersionReporter( sunsetPolicyManager, deprecationPolicyManager );
8085
}
8186

8287
return new DoNotReportApiVersions();

src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpResponseMessageExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ namespace System.Net.Http;
1313
public static class HttpResponseMessageExtensions
1414
{
1515
private const string Sunset = nameof( Sunset );
16+
private const string Deprecation = nameof( Deprecation );
1617
private const string Link = nameof( Link );
1718

19+
private static readonly DateTime unixEpoch = new DateTime( 1970, 1, 1 );
20+
1821
/// <summary>
1922
/// Writes the sunset policy to the specified HTTP response.
2023
/// </summary>
@@ -35,6 +38,35 @@ public static void WriteSunsetPolicy( this HttpResponseMessage response, SunsetP
3538
AddLinkHeaders( headers, sunsetPolicy.Links );
3639
}
3740

41+
/// <summary>
42+
/// Writes the sunset policy to the specified HTTP response.
43+
/// </summary>
44+
/// <param name="response">The <see cref="HttpResponseMessage">HTTP response</see> to write to.</param>
45+
/// <param name="deprecationPolicy">The <see cref="DeprecationPolicy">deprecation policy</see> to write.</param>
46+
public static void WriteDeprecationPolicy( this HttpResponseMessage response, DeprecationPolicy deprecationPolicy )
47+
{
48+
ArgumentNullException.ThrowIfNull( response );
49+
ArgumentNullException.ThrowIfNull( deprecationPolicy );
50+
51+
var headers = response.Headers;
52+
53+
if ( deprecationPolicy.Date.HasValue )
54+
{
55+
long unixTimestamp;
56+
DateTimeOffset deprecationDate = deprecationPolicy.Date.Value;
57+
58+
#if NETFRAMEWORK
59+
unixTimestamp = (int) deprecationDate.Subtract( unixEpoch ).TotalSeconds;
60+
#else
61+
unixTimestamp = deprecationDate.ToUnixTimeSeconds();
62+
#endif
63+
64+
headers.Add( Deprecation, $"@{unixTimestamp}" );
65+
}
66+
67+
AddLinkHeaders( headers, deprecationPolicy.Links );
68+
}
69+
3870
private static void AddLinkHeaders( HttpResponseHeaders headers, IList<LinkHeaderValue> links )
3971
{
4072
var values = headers.TryGetValues( Link, out var existing )

src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/DefaultApiVersionReporterTest.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public void report_should_add_expected_headers()
1414
{
1515
// arrange
1616
var sunsetDate = DateTimeOffset.Now;
17-
var reporter = new DefaultApiVersionReporter( new TestSunsetPolicyManager( sunsetDate ) );
17+
var deprecationDate = DateTimeOffset.Now;
18+
var reporter = new DefaultApiVersionReporter( new TestSunsetPolicyManager( sunsetDate ), new TestDeprecationPolicyManager( deprecationDate ) );
1819
var configuration = new HttpConfiguration();
1920
var request = new HttpRequestMessage();
2021
var response = new HttpResponseMessage( OK ) { RequestMessage = request };
@@ -50,16 +51,24 @@ public void report_should_add_expected_headers()
5051
// assert
5152
var headers = response.Headers;
5253

54+
long unixTimestamp = (int) deprecationDate.Subtract( new DateTime( 1970, 1, 1 ) ).TotalSeconds;
55+
5356
headers.GetValues( "api-supported-versions" ).Should().Equal( "1.0, 2.0" );
5457
headers.GetValues( "api-deprecated-versions" ).Should().Equal( "0.9" );
5558
headers.GetValues( "Sunset" )
5659
.Single()
5760
.Should()
5861
.Be( sunsetDate.ToString( "r" ) );
59-
headers.GetValues( "Link" )
62+
headers.GetValues( "Deprecation" )
6063
.Single()
6164
.Should()
62-
.Be( "<http://docs.api.com/policy.html>; rel=\"sunset\"" );
65+
.Be( $"@{unixTimestamp}" );
66+
headers.GetValues( "Link" )
67+
.Should()
68+
.BeEquivalentTo( [
69+
"<http://docs.api.com/sunset.html>; rel=\"sunset\"",
70+
"<http://docs.api.com/deprecation.html>; rel=\"deprecation\"",
71+
] );
6372
}
6473

6574
private sealed class TestSunsetPolicyManager : ISunsetPolicyManager
@@ -73,7 +82,7 @@ public bool TryGetPolicy( string name, ApiVersion apiVersion, out SunsetPolicy s
7382
{
7483
if ( name == "Test" )
7584
{
76-
var link = new LinkHeaderValue( new Uri( "http://docs.api.com/policy.html" ), "sunset" );
85+
var link = new LinkHeaderValue( new Uri( "http://docs.api.com/sunset.html" ), "sunset" );
7786
sunsetPolicy = new( sunsetDate, link );
7887
return true;
7988
}
@@ -82,4 +91,25 @@ public bool TryGetPolicy( string name, ApiVersion apiVersion, out SunsetPolicy s
8291
return false;
8392
}
8493
}
94+
95+
private sealed class TestDeprecationPolicyManager : IDeprecationPolicyManager
96+
{
97+
private readonly DateTimeOffset deprecationDate;
98+
99+
public TestDeprecationPolicyManager( DateTimeOffset deprecationDate ) =>
100+
this.deprecationDate = deprecationDate;
101+
102+
public bool TryGetPolicy( string name, ApiVersion apiVersion, out DeprecationPolicy deprecationPolicy )
103+
{
104+
if ( name == "Test" )
105+
{
106+
var link = new LinkHeaderValue( new Uri( "http://docs.api.com/deprecation.html" ), "deprecation" );
107+
deprecationPolicy = new( deprecationDate, link );
108+
return true;
109+
}
110+
111+
deprecationPolicy = default;
112+
return false;
113+
}
114+
}
85115
}

src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Http;
1313
public static class HttpResponseExtensions
1414
{
1515
private const string Sunset = nameof( Sunset );
16+
private const string Deprecation = nameof( Deprecation );
1617
private const string Link = nameof( Link );
1718

1819
/// <summary>
@@ -44,6 +45,44 @@ public static void WriteSunsetPolicy( this HttpResponse response, SunsetPolicy s
4445
AddLinkHeaders( headers, sunsetPolicy.Links );
4546
}
4647

48+
/// <summary>
49+
/// Writes the deprecation policy to the specified HTTP response.
50+
/// </summary>
51+
/// <param name="response">The <see cref="HttpResponseMessage">HTTP response</see> to write to.</param>
52+
/// <param name="deprecationPolicy">The <see cref="DeprecationPolicy">deprecation policy</see> to write.</param>
53+
[CLSCompliant( false )]
54+
public static void WriteDeprecationPolicy( this HttpResponse response, DeprecationPolicy deprecationPolicy )
55+
{
56+
ArgumentNullException.ThrowIfNull( response );
57+
ArgumentNullException.ThrowIfNull( deprecationPolicy );
58+
59+
var headers = response.Headers;
60+
61+
if ( headers.ContainsKey( Deprecation ) )
62+
{
63+
// the 'Deprecation' header is present, assume the headers have been written.
64+
// this can happen when ApiVersioningOptions.ReportApiVersions = true
65+
// and [ReportApiVersions] are both applied
66+
return;
67+
}
68+
69+
if ( deprecationPolicy.Date.HasValue )
70+
{
71+
long unixTimestamp;
72+
DateTimeOffset deprecationDate = deprecationPolicy.Date.Value;
73+
74+
#if NETFRAMEWORK
75+
unixTimestamp = (int) deprecationDate.Subtract( unixEpoch ).TotalSeconds;
76+
#else
77+
unixTimestamp = deprecationDate.ToUnixTimeSeconds();
78+
#endif
79+
80+
headers[Deprecation] = $"@{unixTimestamp}";
81+
}
82+
83+
AddLinkHeaders( headers, deprecationPolicy.Links );
84+
}
85+
4786
private static void AddLinkHeaders( IHeaderDictionary headers, IList<LinkHeaderValue> links )
4887
{
4988
var values = new string[links.Count];

src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DefaultApiVersionReporterTest.cs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public void report_should_add_expected_headers()
1313
{
1414
// arrange
1515
var sunsetDate = DateTimeOffset.Now;
16-
var reporter = new DefaultApiVersionReporter( new TestSunsetPolicyManager( sunsetDate ) );
16+
var deprecationDate = DateTimeOffset.Now;
17+
var reporter = new DefaultApiVersionReporter( new TestSunsetPolicyManager( sunsetDate ), new TestDeprecationPolicyManager( deprecationDate ) );
1718
var httpContext = new Mock<HttpContext>();
1819
var features = new Mock<IFeatureCollection>();
1920
var query = new Mock<IQueryCollection>();
@@ -60,14 +61,21 @@ public void report_should_add_expected_headers()
6061
reporter.Report( response.Object, model );
6162

6263
// assert
64+
long unixTimestamp = (int) deprecationDate.Subtract( new DateTime( 1970, 1, 1 ) ).TotalSeconds;
65+
6366
headers["api-supported-versions"].Should().Equal( "1.0, 2.0" );
6467
headers["api-deprecated-versions"].Should().Equal( "0.9" );
6568
headers["Sunset"].Single()
6669
.Should()
6770
.Be( sunsetDate.ToString( "r" ) );
68-
headers["Link"].Single()
69-
.Should()
70-
.Be( "<http://docs.api.com/policy.html>; rel=\"sunset\"" );
71+
headers["Deprecation"].Single()
72+
.Should()
73+
.Be( $"@{unixTimestamp}" );
74+
headers["Link"].Should()
75+
.BeEquivalentTo( [
76+
"<http://docs.api.com/sunset.html>; rel=\"sunset\"",
77+
"<http://docs.api.com/deprecation.html>; rel=\"deprecation\"",
78+
] );
7179
}
7280

7381
private sealed class TestSunsetPolicyManager : ISunsetPolicyManager
@@ -81,7 +89,7 @@ public bool TryGetPolicy( string name, ApiVersion apiVersion, out SunsetPolicy s
8189
{
8290
if ( name == "Test" )
8391
{
84-
var link = new LinkHeaderValue( new Uri( "http://docs.api.com/policy.html" ), "sunset" );
92+
var link = new LinkHeaderValue( new Uri( "http://docs.api.com/sunset.html" ), "sunset" );
8593
sunsetPolicy = new( sunsetDate, link );
8694
return true;
8795
}
@@ -90,4 +98,25 @@ public bool TryGetPolicy( string name, ApiVersion apiVersion, out SunsetPolicy s
9098
return false;
9199
}
92100
}
101+
102+
private sealed class TestDeprecationPolicyManager : IDeprecationPolicyManager
103+
{
104+
private readonly DateTimeOffset deprecationDate;
105+
106+
public TestDeprecationPolicyManager( DateTimeOffset deprecationDate ) =>
107+
this.deprecationDate = deprecationDate;
108+
109+
public bool TryGetPolicy( string name, ApiVersion apiVersion, out DeprecationPolicy deprecationPolicy )
110+
{
111+
if ( name == "Test" )
112+
{
113+
var link = new LinkHeaderValue( new Uri( "http://docs.api.com/deprecation.html" ), "deprecation" );
114+
deprecationPolicy = new( deprecationDate, link );
115+
return true;
116+
}
117+
118+
deprecationPolicy = default;
119+
return false;
120+
}
121+
}
93122
}

src/AspNetCore/WebApi/test/Asp.Versioning.Mvc.Tests/ReportApiVersionsAttributeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private static ActionExecutingContext CreateContext(
8484
var controller = default( object );
8585
var endpoint = new Endpoint( c => Task.CompletedTask, new( new[] { metadata } ), "Test" );
8686
var options = Options.Create( new ApiVersioningOptions() );
87-
var reporter = new DefaultApiVersionReporter( new SunsetPolicyManager( options ) );
87+
var reporter = new DefaultApiVersionReporter( new SunsetPolicyManager( options ), new DeprecationPolicyManager( options ) );
8888

8989
endpointFeature.SetupProperty( f => f.Endpoint, endpoint );
9090
versioningFeature.SetupProperty( f => f.RequestedApiVersion, new ApiVersion( 1.0 ) );

src/Common/src/Common/DefaultApiVersionReporter.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ public sealed partial class DefaultApiVersionReporter : IReportApiVersions
2323
private const string Sunset = nameof( Sunset );
2424
private const string Link = nameof( Link );
2525
private readonly ISunsetPolicyManager sunsetPolicyManager;
26+
private readonly IDeprecationPolicyManager deprecationPolicyManager;
2627
private readonly string apiSupportedVersionsName;
2728
private readonly string apiDeprecatedVersionsName;
2829

2930
/// <summary>
3031
/// Initializes a new instance of the <see cref="DefaultApiVersionReporter"/> class.
3132
/// </summary>
3233
/// <param name="sunsetPolicyManager">The <see cref="ISunsetPolicyManager">manager</see> used to resolve sunset policies.</param>
34+
/// <param name="deprecationPolicyManager">The <see cref="IDeprecationPolicyManager">manager</see> used to resolve deprecation policies.</param>
3335
/// <param name="supportedHeaderName">The HTTP header name used for supported API versions.
3436
/// The default value is "api-supported-versions".</param>
3537
/// <param name="deprecatedHeaderName">THe HTTP header name used for deprecated API versions.
@@ -38,6 +40,7 @@ public sealed partial class DefaultApiVersionReporter : IReportApiVersions
3840
/// <see cref="ApiVersionMapping.Explicit"/> and <see cref="ApiVersionMapping.Implicit"/>.</param>
3941
public DefaultApiVersionReporter(
4042
ISunsetPolicyManager sunsetPolicyManager,
43+
IDeprecationPolicyManager deprecationPolicyManager,
4144
string supportedHeaderName = ApiSupportedVersions,
4245
string deprecatedHeaderName = ApiDeprecatedVersions,
4346
ApiVersionMapping mapping = Explicit | Implicit )
@@ -47,6 +50,7 @@ public DefaultApiVersionReporter(
4750
ArgumentException.ThrowIfNullOrEmpty( deprecatedHeaderName );
4851

4952
this.sunsetPolicyManager = sunsetPolicyManager;
53+
this.deprecationPolicyManager = deprecationPolicyManager;
5054
apiSupportedVersionsName = supportedHeaderName;
5155
apiDeprecatedVersionsName = deprecatedHeaderName;
5256
Mapping = mapping;
@@ -91,9 +95,14 @@ public void Report( HttpResponse response, ApiVersionModel apiVersionModel )
9195
#endif
9296
var name = metadata.Name;
9397

94-
if ( sunsetPolicyManager.TryResolvePolicy( name, version, out var policy ) )
98+
if ( sunsetPolicyManager.TryResolvePolicy( name, version, out var sunsetPolicy ) )
9599
{
96-
response.WriteSunsetPolicy( policy );
100+
response.WriteSunsetPolicy( sunsetPolicy );
101+
}
102+
103+
if ( deprecationPolicyManager.TryResolvePolicy( name, version, out var deprecationPolicy ) )
104+
{
105+
response.WriteDeprecationPolicy( deprecationPolicy );
97106
}
98107
}
99108
}

0 commit comments

Comments
 (0)