Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ root = true
# don't use tabs for indentation
[*]
indent_style = space
guidelines = 120 1px solid yellow
vsspell_section_id = 41b65011239a40959ccaae2a4ec7044a
vsspell_ignored_words_41b65011239a40959ccaae2a4ec7044a = Accessor|app|clr|Edm|inline|middleware|Mvc|odata|Validator|Deconstruct

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ protected override void OnConfigureEndpoints( IEndpointRouteBuilder endpoints )

protected override void OnAddApiVersioning( ApiVersioningOptions options )
{
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader(),
new UrlSegmentApiVersionReader(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace given_a_versioned_minimal_API;

using Asp.Versioning;
using Asp.Versioning.Http;
using static System.Net.HttpStatusCode;

[Collection( nameof( MinimalApiTestCollection ) )]
public class when_using_a_query_string : AcceptanceTest
Expand Down Expand Up @@ -34,9 +35,58 @@ public async Task then_get_should_report_api_versions()
var response = await GetAsync( "api/values?api-version=1.0" );

// assert
response.StatusCode.Should().Be( OK );
response.Headers.GetValues( "api-supported-versions" ).Should().Equal( "1.0, 2.0" );
}

[Fact]
public async Task then_get_should_return_400_for_an_unsupported_version()
{
// arrange


// act
var response = await GetAsync( "api/values?api-version=3.0" );
var problem = await response.Content.ReadAsProblemDetailsAsync();

// assert
response.StatusCode.Should().Be( BadRequest );
response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" );
problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );
}

[Fact]
public async Task then_get_should_return_400_for_an_unspecified_version()
{
// arrange


// act
var response = await GetAsync( "api/values" );
var problem = await response.Content.ReadAsProblemDetailsAsync();

// assert
response.StatusCode.Should().Be( BadRequest );
response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" );
problem.Type.Should().Be( ProblemDetailsDefaults.Unspecified.Type );
}

[Fact]
public async Task then_get_should_return_400_for_a_malformed_version()
{
// arrange


// act
var response = await GetAsync( "api/values?api-version=abc" );
var problem = await response.Content.ReadAsProblemDetailsAsync();

// assert
response.StatusCode.Should().Be( BadRequest );
response.Headers.GetValues( "api-supported-versions" ).Should().Equal( "1.0, 2.0" );
problem.Type.Should().Be( ProblemDetailsDefaults.Invalid.Type );
}

public when_using_a_query_string( MinimalApiFixture fixture, ITestOutputHelper console )
: base( fixture ) => console.WriteLine( fixture.DirectedGraphVisualizationUrl );
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,12 @@ public async Task then_get_should_return_400_for_an_unsupported_version()

// act
var response = await GetAsync( "api/values?api-version=3.0" );
var problem = await response.Content.ReadAsProblemDetailsAsync();

// assert
response.StatusCode.Should().Be( BadRequest );
response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" );
problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );
}

[Fact]
Expand All @@ -111,6 +114,7 @@ public async Task then_get_should_return_400_for_an_unspecified_version()

// assert
response.StatusCode.Should().Be( BadRequest );
response.Headers.GetValues( "api-supported-versions" ).Single().Should().Be( "1.0, 2.0" );
problem.Type.Should().Be( ProblemDetailsDefaults.Unspecified.Type );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,14 @@ private static void Collate(
{
// this is a best guess effort at collating all supported and deprecated
// versions for an api when unmatched and it needs to be reported. it's
// impossible to sure as there is no way to correlate an arbitrary
// impossible to be sure as there is no way to correlate an arbitrary
// request url by endpoint or name. the routing system will build a tree
// based on the route template before the jump table policy is created,
// which provides a natural method of grouping. manual, contrived tests
// demonstrated that were the results were correctly collated together.
// it is possible there is an edge case that isn't covered, but it's
// unclear what that would look like. one or more test cases should be
// added to document that if discovered
// added to document that is discovered
ApiVersionModel model;

if ( supported == null )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public override int GetDestination( HttpContext httpContext )
return rejection.AssumeDefault;
}

httpContext.Features.Set( policyFeature );

// 3. unspecified
return versionsByUrlOnly
/* 404 */ ? rejection.Exit
Expand All @@ -86,6 +88,8 @@ public override int GetDestination( HttpContext httpContext )

if ( !parser.TryParse( rawApiVersion, out var apiVersion ) )
{
httpContext.Features.Set( policyFeature );

if ( versionsByUrl )
{
feature.RawRequestedApiVersion = rawApiVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public Endpoint Build()
{
if ( feature.RawRequestedApiVersions.Count == 0 )
{
return new UnspecifiedApiVersionEndpoint( logger, GetDisplayNames() );
return new UnspecifiedApiVersionEndpoint( logger, options, GetDisplayNames() );
}

return new UnsupportedApiVersionEndpoint( options );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public EdgeBuilder(
keys = new( capacity + 1 );
edges = new( capacity + RejectionEndpointCapacity )
{
[EdgeKey.Malformed] = new( capacity: 1 ) { new MalformedApiVersionEndpoint( logger ) },
[EdgeKey.Ambiguous] = new( capacity: 1 ) { new AmbiguousApiVersionEndpoint( logger ) },
[EdgeKey.Unspecified] = new( capacity: 1 ) { new UnspecifiedApiVersionEndpoint( logger ) },
[EdgeKey.Unsupported] = new( capacity: 1 ) { new UnsupportedApiVersionEndpoint( options ) },
[EdgeKey.UnsupportedMediaType] = new( capacity: 1 ) { new UnsupportedMediaTypeEndpoint( options ) },
[EdgeKey.NotAcceptable] = new( capacity: 1 ) { new NotAcceptableEndpoint( options ) },
[EdgeKey.Malformed] = [new MalformedApiVersionEndpoint( logger, options )],
[EdgeKey.Ambiguous] = [new AmbiguousApiVersionEndpoint( logger )],
[EdgeKey.Unspecified] = [new UnspecifiedApiVersionEndpoint( logger, options )],
[EdgeKey.Unsupported] = [new UnsupportedApiVersionEndpoint( options )],
[EdgeKey.UnsupportedMediaType] = [new UnsupportedMediaTypeEndpoint( options )],
[EdgeKey.NotAcceptable] = [new NotAcceptableEndpoint( options )],
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,30 @@ internal static ProblemDetailsContext New( HttpContext context, ProblemDetailsIn
return newContext;
}

internal static Task UnsupportedApiVersion(
HttpContext context,
ApiVersioningOptions options,
int statusCode )
internal static bool TryReportApiVersions( HttpContext context, ApiVersioningOptions options )
{
context.Response.StatusCode = statusCode;

if ( options.ReportApiVersions &&
context.Features.Get<ApiVersionPolicyFeature>() is ApiVersionPolicyFeature feature )
{
var reporter = context.RequestServices.GetRequiredService<IReportApiVersions>();
var model = feature.Metadata.Map( reporter.Mapping );
context.Response.OnStarting( ReportApiVersions, (reporter, context.Response, model) );
return true;
}
else
{
return false;
}
}

internal static Task UnsupportedApiVersion(
HttpContext context,
ApiVersioningOptions options,
int statusCode )
{
context.Response.StatusCode = statusCode;

TryReportApiVersions( context, options );

if ( context.TryGetProblemDetailsService( out var problemDetails ) )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ internal sealed class MalformedApiVersionEndpoint : Endpoint
{
private const string Name = "400 Invalid API Version";

internal MalformedApiVersionEndpoint( ILogger logger )
: base( c => OnExecute( c, logger ), Empty, Name ) { }
internal MalformedApiVersionEndpoint( ILogger logger, ApiVersioningOptions options )
: base( context => OnExecute( context, options, logger ), Empty, Name ) { }

private static Task OnExecute( HttpContext context, ILogger logger )
private static Task OnExecute( HttpContext context, ApiVersioningOptions options, ILogger logger )
{
var requestedVersion = context.ApiVersioningFeature().RawRequestedApiVersion;

logger.ApiVersionInvalid( requestedVersion );
context.Response.StatusCode = StatusCodes.Status400BadRequest;

EndpointProblem.TryReportApiVersions( context, options );

if ( !context.TryGetProblemDetailsService( out var problemDetails ) )
{
return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ internal sealed class UnspecifiedApiVersionEndpoint : Endpoint
{
private const string Name = "400 Unspecified API Version";

internal UnspecifiedApiVersionEndpoint( ILogger logger, string[]? displayNames = default )
: base( c => OnExecute( c, displayNames, logger ), Empty, Name ) { }

private static Task OnExecute( HttpContext context, string[]? candidateEndpoints, ILogger logger )
internal UnspecifiedApiVersionEndpoint(
ILogger logger,
ApiVersioningOptions options,
string[]? displayNames = default )
: base( context => OnExecute( context, options, displayNames, logger ), Empty, Name ) { }

private static Task OnExecute(
HttpContext context,
ApiVersioningOptions options,
string[]? candidateEndpoints,
ILogger logger )
{
if ( candidateEndpoints == null || candidateEndpoints.Length == 0 )
{
Expand All @@ -26,6 +33,8 @@ private static Task OnExecute( HttpContext context, string[]? candidateEndpoints

context.Response.StatusCode = StatusCodes.Status400BadRequest;

EndpointProblem.TryReportApiVersions( context, options );

if ( context.TryGetProblemDetailsService( out var problemDetails ) )
{
return problemDetails.TryWriteAsync(
Expand Down
Loading