-
Notifications
You must be signed in to change notification settings - Fork 717
Add [IntroducedInApiVersion] attribute (closes #1183) #1184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
xavierjohn
wants to merge
26
commits into
dotnet:main
Choose a base branch
from
xavierjohn:feature/introduced-in-api-version
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
112373d
Add introduced-in API version attribute
xavierjohn 061b136
Fix introduced-in version routing
xavierjohn 991b14c
Fix introduced-in attribute equality and docs
xavierjohn ead80dd
Align introduced-in routing status selection
xavierjohn 469e4f5
Define introduced-in convention edge cases
xavierjohn 94c0814
Demonstrate IntroducedInApiVersion in BasicExample
xavierjohn 5a80710
Use exact type for introduced version equality
xavierjohn 658a0f6
Materialize introduced version mappings
xavierjohn 865f35d
Stabilize introduced-later routing errors
xavierjohn aac5a7d
Support introduced minimal API endpoints
xavierjohn 86b2f1f
Compare edge keys by fields
xavierjohn c1207f8
Demonstrate IntroducedInApiVersion in MinimalApiExample
xavierjohn 0690b02
Demonstrate IntroducedInApiVersion in OpenApiExample
xavierjohn 29e5d72
Stabilize introduced slow-path status selection
xavierjohn 9335920
Clarify mapped version constraint docs
xavierjohn 5ab4956
Write problem details for introduced endpoints
xavierjohn b532bf6
Apply introduced-in precedence across candidates
xavierjohn db53e29
Drop introduced metadata from neutral endpoints
xavierjohn 3db2ee1
Populate URL segment version for introduced endpoints
xavierjohn ff5ed87
Add introduced-in MVC action conventions
xavierjohn 0eb7922
Add summary to introduced-in convention extension block
xavierjohn 6a455a3
Intersect introduced action supported versions
xavierjohn 1baa005
Restore action convention interface shape
xavierjohn bbdb874
Expand minimal API introduced versions
xavierjohn 513fa4c
Recognize introduced type in ErrorObjectWriter
xavierjohn 0402e70
Share introduced metadata instances in MVC convention apply
xavierjohn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
src/Abstractions/src/Asp.Versioning.Abstractions/IIntroducedInApiVersionProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
|
|
||
| namespace Asp.Versioning; | ||
|
|
||
| /// <summary> | ||
| /// Defines the behavior of an <see cref="ApiVersion">API version</see> provider that describes when an API was introduced. | ||
| /// </summary> | ||
| public interface IIntroducedInApiVersionProvider : IApiVersionProvider | ||
| { | ||
| /// <summary> | ||
| /// Gets the HTTP status code returned when the requested API version is earlier than the introduced API version. | ||
| /// </summary> | ||
| /// <value>The HTTP status code.</value> | ||
| int StatusCode { get; } | ||
| } |
97 changes: 97 additions & 0 deletions
97
src/Abstractions/src/Asp.Versioning.Abstractions/IntroducedInApiVersionAttribute.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
|
|
||
| #pragma warning disable IDE0079 | ||
| #pragma warning disable CA1019 | ||
| #pragma warning disable CA1033 | ||
| #pragma warning disable CA1813 | ||
|
|
||
| namespace Asp.Versioning; | ||
|
|
||
| using static System.AttributeTargets; | ||
| #if NETSTANDARD | ||
| using DateOnly = System.DateTime; | ||
| #endif | ||
|
|
||
| /// <summary> | ||
| /// Represents metadata that describes the <see cref="ApiVersion">API version</see> in which an API action was introduced. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The action is mapped to every API version declared by the containing controller that is greater than or equal to | ||
| /// the introduced API version. Requests for a controller-declared API version earlier than the introduced API version | ||
| /// are rejected using <see cref="StatusCode"/>. Version-neutral controllers and actions ignore introduced API version | ||
| /// metadata because version-neutral endpoints are not constrained to declared API versions. | ||
| /// </remarks> | ||
| /// <example> | ||
| /// A controller that declares API versions 1.0, 2.0, and 3.0 can mark an action with | ||
| /// <c>[IntroducedInApiVersion( "2.0" )]</c>. The action is mapped to versions 2.0 and 3.0. | ||
| /// A request for version 1.0 is rejected using <see cref="StatusCode"/>. Set | ||
| /// <see cref="StatusCode"/> to <see cref="UseConfiguredStatusCode"/> (<c>0</c>) to use the globally configured | ||
| /// unsupported API version status code instead. | ||
| /// </example> | ||
| /// <seealso cref="MapToApiVersionAttribute"/> | ||
| [AttributeUsage( Method, AllowMultiple = false, Inherited = false )] | ||
| public class IntroducedInApiVersionAttribute : ApiVersionsBaseAttribute, IIntroducedInApiVersionProvider | ||
| { | ||
| /// <summary> | ||
| /// The default HTTP status code used when a requested API version is earlier than the introduced API version. | ||
| /// </summary> | ||
| public const int DefaultStatusCode = 404; | ||
|
|
||
| /// <summary> | ||
| /// Indicates that the configured unsupported API version status code should be used. | ||
| /// </summary> | ||
| public const int UseConfiguredStatusCode = 0; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="IntroducedInApiVersionAttribute"/> class. | ||
| /// </summary> | ||
| /// <param name="version">The <see cref="ApiVersion">API version</see>.</param> | ||
| protected IntroducedInApiVersionAttribute( ApiVersion version ) : base( version ) { } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="IntroducedInApiVersionAttribute"/> class. | ||
| /// </summary> | ||
| /// <param name="parser">The parser used to parse the specified versions.</param> | ||
| /// <param name="version">The API version string.</param> | ||
| protected IntroducedInApiVersionAttribute( IApiVersionParser parser, string version ) : base( parser, version ) { } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="IntroducedInApiVersionAttribute"/> class. | ||
| /// </summary> | ||
| /// <param name="version">A numeric API version.</param> | ||
| /// <param name="status">The status associated with the API version, if any.</param> | ||
| public IntroducedInApiVersionAttribute( double version, string? status = default ) | ||
| : base( new ApiVersion( version, status ) ) { } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="IntroducedInApiVersionAttribute"/> class. | ||
| /// </summary> | ||
| /// <param name="year">The version year.</param> | ||
| /// <param name="month">The version month.</param> | ||
| /// <param name="day">The version day.</param> | ||
| /// <param name="status">The status associated with the API version, if any.</param> | ||
| public IntroducedInApiVersionAttribute( int year, int month, int day, string? status = default ) | ||
| : base( new ApiVersion( new DateOnly( year, month, day ), status ) ) { } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="IntroducedInApiVersionAttribute"/> class. | ||
| /// </summary> | ||
| /// <param name="version">The API version string.</param> | ||
| public IntroducedInApiVersionAttribute( string version ) : base( version ) { } | ||
|
|
||
| ApiVersionProviderOptions IApiVersionProvider.Options => ApiVersionProviderOptions.Introduced; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the HTTP status code returned when the requested API version is earlier than the introduced API version. | ||
| /// </summary> | ||
| /// <value>The HTTP status code. The default value is <c>404</c>.</value> | ||
| /// <remarks>Set the value to <see cref="UseConfiguredStatusCode"/> to use the configured unsupported API version status code.</remarks> | ||
| public int StatusCode { get; set; } = DefaultStatusCode; | ||
|
|
||
| /// <inheritdoc /> | ||
| public override bool Equals( object? obj ) => | ||
| obj is IntroducedInApiVersionAttribute other && base.Equals( obj ) && StatusCode == other.StatusCode; | ||
Check warningCode scanning / CodeQL Equals should not apply "is" Warning
IntroducedInApiVersionAttribute.Equals(object) should not use "is" on its parameter, as it will not work properly for subclasses of IntroducedInApiVersionAttribute.
|
||
|
|
||
| /// <inheritdoc /> | ||
| public override int GetHashCode() => HashCode.Combine( base.GetHashCode(), StatusCode ); | ||
| } | ||
32 changes: 32 additions & 0 deletions
32
src/Abstractions/src/Asp.Versioning.Abstractions/IntroducedInApiVersionMetadata.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
|
|
||
| namespace Asp.Versioning; | ||
|
|
||
| /// <summary> | ||
| /// Represents endpoint metadata that describes when an API action was introduced. | ||
| /// </summary> | ||
| public sealed class IntroducedInApiVersionMetadata | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="IntroducedInApiVersionMetadata"/> class. | ||
| /// </summary> | ||
| /// <param name="introducedIn">The <see cref="ApiVersion">API version</see> in which the API action was introduced.</param> | ||
| /// <param name="statusCode">The HTTP status code returned for earlier controller-declared API versions.</param> | ||
| public IntroducedInApiVersionMetadata( ApiVersion introducedIn, int statusCode ) | ||
| { | ||
| IntroducedIn = introducedIn; | ||
| StatusCode = statusCode; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the API version in which the API action was introduced. | ||
| /// </summary> | ||
| /// <value>The introduced <see cref="ApiVersion">API version</see>.</value> | ||
| public ApiVersion IntroducedIn { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the HTTP status code returned for earlier controller-declared API versions. | ||
| /// </summary> | ||
| /// <value>The HTTP status code.</value> | ||
| public int StatusCode { get; } | ||
| } |
87 changes: 87 additions & 0 deletions
87
...bstractions/test/Asp.Versioning.Abstractions.Tests/IntroducedInApiVersionAttributeTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| // Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
|
|
||
| namespace Asp.Versioning; | ||
|
|
||
| #if NETFRAMEWORK | ||
| using DateOnly = System.DateTime; | ||
| #endif | ||
|
|
||
| public class IntroducedInApiVersionAttributeTest | ||
| { | ||
| [Fact] | ||
| public void introduced_in_api_version_attribute_should_initialize_from_string() | ||
| { | ||
| // arrange | ||
| var expected = new ApiVersion( new DateOnly( 2026, 12, 1 ) ); | ||
|
|
||
| // act | ||
| var attribute = new IntroducedInApiVersionAttribute( "2026-12-01" ); | ||
|
|
||
| // assert | ||
| attribute.Versions[0].Should().Be( expected ); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void introduced_in_api_version_attribute_should_initialize_from_date() | ||
| { | ||
| // arrange | ||
| var expected = new ApiVersion( new DateOnly( 2026, 12, 1 ) ); | ||
|
|
||
| // act | ||
| var attribute = new IntroducedInApiVersionAttribute( 2026, 12, 1 ); | ||
|
|
||
| // assert | ||
| attribute.Versions[0].Should().Be( expected ); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void introduced_in_api_version_attribute_should_use_default_status_code() | ||
| { | ||
| // arrange | ||
| var provider = new IntroducedInApiVersionAttribute( "2026-12-01" ); | ||
|
|
||
| // act | ||
| var statusCode = provider.StatusCode; | ||
|
|
||
| // assert | ||
| statusCode.Should().Be( IntroducedInApiVersionAttribute.DefaultStatusCode ); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void introduced_in_api_version_attribute_should_allow_configured_status_code() | ||
| { | ||
| // arrange | ||
| var provider = new IntroducedInApiVersionAttribute( "2026-12-01" ) | ||
| { | ||
| StatusCode = IntroducedInApiVersionAttribute.UseConfiguredStatusCode, | ||
| }; | ||
|
|
||
| // act | ||
| var statusCode = provider.StatusCode; | ||
|
|
||
| // assert | ||
| statusCode.Should().Be( IntroducedInApiVersionAttribute.UseConfiguredStatusCode ); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void introduced_in_api_version_attribute_should_compare_status_code_for_equality() | ||
| { | ||
| // arrange | ||
| var version = new IntroducedInApiVersionAttribute( "2026-12-01" ) { StatusCode = 404 }; | ||
| var sameVersionAndStatus = new IntroducedInApiVersionAttribute( "2026-12-01" ) { StatusCode = 404 }; | ||
| var sameVersionDifferentStatus = new IntroducedInApiVersionAttribute( "2026-12-01" ) { StatusCode = 410 }; | ||
| var differentVersionSameStatus = new IntroducedInApiVersionAttribute( "2027-06-01" ) { StatusCode = 404 }; | ||
|
|
||
| // act | ||
| var same = version.Equals( sameVersionAndStatus ); | ||
| var differentStatus = version.Equals( sameVersionDifferentStatus ); | ||
| var differentVersion = version.Equals( differentVersionSameStatus ); | ||
|
|
||
| // assert | ||
| same.Should().BeTrue(); | ||
| version.GetHashCode().Should().Be( sameVersionAndStatus.GetHashCode() ); | ||
| differentStatus.Should().BeFalse(); | ||
| version.GetHashCode().Should().NotBe( sameVersionDifferentStatus.GetHashCode() ); | ||
| differentVersion.Should().BeFalse(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.