Skip to content

Commit

Permalink
Displays query string parameters not described by the EDM model or in…
Browse files Browse the repository at this point in the history
… the ODataRoute. Fixes #62.
  • Loading branch information
Richard Beauchamp committed Jan 30, 2016
1 parent b798e0f commit 8366dc8
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 90 deletions.
181 changes: 181 additions & 0 deletions Swashbuckle.OData.Tests/Fixtures/QueryStringParameterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web.OData;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;
using FluentAssertions;
using Microsoft.OData.Edm;
using Microsoft.Owin.Hosting;
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;

namespace Swashbuckle.OData.Tests
{
[TestFixture]
public class QueryStringParameterTests
{
[Test]
public async Task It_displays_parameters_not_described_in_the_edm_model()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => FoobarsSetup.Configuration(appBuilder, typeof(FoobarsSetup.FoobarsController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
// Verify that the OData route in the test controller is valid
var results = await httpClient.GetJsonAsync<ODataResponse<List<FoobarsSetup.Foobar>>>("odata/Foobars?bar=true");
results.Should().NotBeNull();
results.Value.Count.Should().Be(2);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Foobars", out pathItem);
pathItem.Should().NotBeNull();
var barParameter = pathItem.get.parameters.SingleOrDefault(parameter => parameter.name == "bar");
barParameter.Should().NotBeNull();
barParameter.required.Should().BeFalse();
barParameter.type.ShouldBeEquivalentTo("boolean");
barParameter.@in.ShouldBeEquivalentTo("query");
var filterParameter = pathItem.get.parameters.SingleOrDefault(parameter => parameter.name == "$filter");
filterParameter.Should().NotBeNull();
filterParameter.description.Should().NotBeNullOrWhiteSpace();
filterParameter.type.ShouldBeEquivalentTo("string");
filterParameter.@in.ShouldBeEquivalentTo("query");

await ValidationUtils.ValidateSwaggerJson();
}
}

[Test]
public async Task It_displays_parameters_not_defined_in_the_odata_route()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => WombatsSetup.Configuration(appBuilder, typeof(WombatsSetup.WombatsController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
// Verify that the OData route in the test controller is valid
var results = await httpClient.GetJsonAsync<ODataResponse<List<WombatsSetup.Wombat>>>("odata/Wombats?bat=true");
results.Should().NotBeNull();
results.Value.Count.Should().Be(2);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Wombats", out pathItem);
pathItem.Should().NotBeNull();
var barParameter = pathItem.get.parameters.SingleOrDefault(parameter => parameter.name == "bat");
barParameter.Should().NotBeNull();
barParameter.required.Should().BeFalse();
barParameter.type.ShouldBeEquivalentTo("boolean");
barParameter.@in.ShouldBeEquivalentTo("query");
var filterParameter = pathItem.get.parameters.SingleOrDefault(parameter => parameter.name == "$filter");
filterParameter.Should().NotBeNull();
filterParameter.description.Should().NotBeNullOrWhiteSpace();
filterParameter.type.ShouldBeEquivalentTo("string");
filterParameter.@in.ShouldBeEquivalentTo("query");

await ValidationUtils.ValidateSwaggerJson();
}
}
}

public class FoobarsSetup
{
public static void Configuration(IAppBuilder appBuilder, Type targetController)
{
var config = appBuilder.GetStandardHttpConfig(targetController);

config.MapODataServiceRoute("odata", "odata", GetEdmModel());

config.EnsureInitialized();
}

public static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();

builder.EntitySet<Foobar>("Foobars");

return builder.GetEdmModel();
}

public class Foobar
{
[Key]
public long Id { get; set; }
public string Variation { get; set; }
}

public class FoobarsController : ODataController
{
[EnableQuery]
public IQueryable<Foobar> GetFoobars([FromODataUri] bool? bar = null)
{
IEnumerable<Foobar> foobars = new[]
{
new Foobar { Id=1, Variation = "a"},
new Foobar { Id=2, Variation = "b"},
new Foobar { Id=3, Variation = "c"},
new Foobar { Id=4, Variation = "d"}
};
if (bar != null && bar.Value) foobars = foobars.Where(fb => fb.Id >= 3);
return foobars.AsQueryable();
}
}
}

public class WombatsSetup
{
public static void Configuration(IAppBuilder appBuilder, Type targetController)
{
var config = appBuilder.GetStandardHttpConfig(targetController);

config.MapODataServiceRoute("odata", "odata", GetEdmModel());

config.EnsureInitialized();
}

public static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();

builder.EntitySet<Wombat>("Wombats");

return builder.GetEdmModel();
}

public class Wombat
{
[Key]
public long Id { get; set; }
public string Variation { get; set; }
}

public class WombatsController : ODataController
{
[EnableQuery]
[ODataRoute("Wombats")]
public IQueryable<Wombat> GetWombats([FromODataUri] bool? bat = null)
{
IEnumerable<Wombat> wombats = new[]
{
new Wombat { Id=1, Variation = "a"},
new Wombat { Id=2, Variation = "b"},
new Wombat { Id=3, Variation = "c"},
new Wombat { Id=4, Variation = "d"}
};
if (bat != null && bat.Value) wombats = wombats.Where(wb => wb.Id >= 3);
return wombats.AsQueryable();
}
}
}
}
1 change: 1 addition & 0 deletions Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
<Compile Include="Containment\ODataModels.cs" />
<Compile Include="Containment\Program.cs" />
<Compile Include="ContentType.cs" />
<Compile Include="Fixtures\QueryStringParameterTests.cs" />
<Compile Include="Fixtures\RoutePrefixTests.cs" />
<Compile Include="Fixtures\ModelSchemaTests.cs" />
<Compile Include="Fixtures\CustomSwaggerRouteTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Web.Http.Controllers;
using System.Web.Http.Description;

namespace Swashbuckle.OData.Descriptions
{
internal class ApiParameterDescriptionEqualityComparer : IEqualityComparer<ApiParameterDescription>
{
public bool Equals(ApiParameterDescription x, ApiParameterDescription y)
{
return ReferenceEquals(x, y)
|| RootParameterDescriptorsAreEqual(x, y)
|| string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
}

private static bool RootParameterDescriptorsAreEqual(ApiParameterDescription x, ApiParameterDescription y)
{
var xParameterDescriptor = GetRootParameterDescriptor(x);
var yParameterDescriptor = GetRootParameterDescriptor(y);

return xParameterDescriptor != null && yParameterDescriptor != null && ReferenceEquals(xParameterDescriptor, yParameterDescriptor);
}

private static HttpParameterDescriptor GetRootParameterDescriptor(ApiParameterDescription apiParameterDescription)
{
var oDataParameterDescriptor = apiParameterDescription.ParameterDescriptor as ODataParameterDescriptor;

return oDataParameterDescriptor != null
? oDataParameterDescriptor.ReflectedHttpParameterDescriptor
: apiParameterDescription.ParameterDescriptor;
}

public int GetHashCode(ApiParameterDescription obj)
{
// Make all HashCodes the same to force
// an Equals(ApiParameterDescription x, ApiParameterDescription y) check
return 1;
}
}
}
2 changes: 1 addition & 1 deletion Swashbuckle.OData/Descriptions/MapByDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public HttpParameterDescriptor Map(Parameter swaggerParameter, int parameterInde
{
var httpControllerDescriptor = actionDescriptor.ControllerDescriptor;
Contract.Assume(httpControllerDescriptor != null);
return new ODataParameterDescriptor(swaggerParameter.name, parameterDescriptor.ParameterType, parameterDescriptor.IsOptional)
return new ODataParameterDescriptor(swaggerParameter.name, parameterDescriptor.ParameterType, parameterDescriptor.IsOptional, parameterDescriptor)
{
Configuration = httpControllerDescriptor.Configuration,
ActionDescriptor = actionDescriptor,
Expand Down
2 changes: 1 addition & 1 deletion Swashbuckle.OData/Descriptions/MapByIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public HttpParameterDescriptor Map(Parameter swaggerParameter, int parameterInde
{
var httpControllerDescriptor = actionDescriptor.ControllerDescriptor;
Contract.Assume(httpControllerDescriptor != null);
return new ODataParameterDescriptor(swaggerParameter.name, parameterDescriptor.ParameterType, parameterDescriptor.IsOptional)
return new ODataParameterDescriptor(swaggerParameter.name, parameterDescriptor.ParameterType, parameterDescriptor.IsOptional, parameterDescriptor)
{
Configuration = httpControllerDescriptor.Configuration,
ActionDescriptor = actionDescriptor,
Expand Down
2 changes: 1 addition & 1 deletion Swashbuckle.OData/Descriptions/MapToDefault.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public HttpParameterDescriptor Map(Parameter swaggerParameter, int parameterInde
var required = swaggerParameter.required;
Contract.Assume(required != null);

return new ODataParameterDescriptor(swaggerParameter.name, swaggerParameter.GetClrType(), !required.Value)
return new ODataParameterDescriptor(swaggerParameter.name, swaggerParameter.GetClrType(), !required.Value, null)
{
Configuration = actionDescriptor.ControllerDescriptor.Configuration,
ActionDescriptor = actionDescriptor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public HttpParameterDescriptor Map(Parameter swaggerParameter, int parameterInde
{
var odataActionParametersDescriptor = actionDescriptor.GetParameters().SingleOrDefault(descriptor => descriptor.ParameterType == typeof (ODataActionParameters));
Contract.Assume(odataActionParametersDescriptor != null);
return new ODataActionParameterDescriptor(odataActionParametersDescriptor.ParameterName, typeof(ODataActionParameters), !required.Value, swaggerParameter.schema)
return new ODataActionParameterDescriptor(odataActionParametersDescriptor.ParameterName, typeof(ODataActionParameters), !required.Value, swaggerParameter.schema, odataActionParametersDescriptor)
{
Configuration = actionDescriptor.ControllerDescriptor.Configuration,
ActionDescriptor = actionDescriptor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Swashbuckle.OData.Descriptions
{
internal class ODataActionDescriptorEqualityComparer : IEqualityComparer<ODataActionDescriptor>
{
public bool Equals(ODataActionDescriptor x, ODataActionDescriptor y)
{
if (ReferenceEquals(x, y))
{
return true;
}
return x.Request.Method.Equals(y.Request.Method)
&& string.Equals(NormalizeRelativePath(x.RelativePathTemplate), NormalizeRelativePath(y.RelativePathTemplate), StringComparison.OrdinalIgnoreCase)
&& x.ActionDescriptor.Equals(y.ActionDescriptor);
}

public int GetHashCode(ODataActionDescriptor obj)
{
var hashCode = obj.Request.Method.GetHashCode();
hashCode = (hashCode * 397) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(NormalizeRelativePath(obj.RelativePathTemplate));
hashCode = (hashCode * 397) ^ obj.ActionDescriptor.GetHashCode();
return hashCode;
}

private static string NormalizeRelativePath(string path)
{
Contract.Requires(path != null);

return path.Replace("()", string.Empty);
}
}
}
82 changes: 0 additions & 82 deletions Swashbuckle.OData/Descriptions/ODataActionDescriptorMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,88 +21,6 @@ public IEnumerable<ApiDescription> Map(ODataActionDescriptor oDataActionDescript
return apiDescriptions;
}

private static List<ApiParameterDescription> CreateParameterDescriptions(HttpActionDescriptor actionDescriptor)
{
Contract.Requires(actionDescriptor != null);

Contract.Assume(actionDescriptor.ControllerDescriptor == null || actionDescriptor.ControllerDescriptor.Configuration != null);

var parameterDescriptions = new List<ApiParameterDescription>();
var actionBinding = GetActionBinding(actionDescriptor);

var parameterBindings = actionBinding.ParameterBindings;
if (parameterBindings != null)
{
foreach (var parameterBinding in parameterBindings)
{
Contract.Assume(parameterBinding != null);
parameterDescriptions.Add(CreateParameterDescriptionFromBinding(parameterBinding));
}
}

return parameterDescriptions;
}

private static HttpActionBinding GetActionBinding(HttpActionDescriptor actionDescriptor)
{
Contract.Requires(actionDescriptor != null);
Contract.Ensures(Contract.Result<HttpActionBinding>() != null);

Contract.Assume(actionDescriptor.ControllerDescriptor?.Configuration != null);

var controllerDescriptor = actionDescriptor.ControllerDescriptor;
var controllerServices = controllerDescriptor.Configuration.Services;
var actionValueBinder = controllerServices.GetActionValueBinder();
Contract.Assume(actionValueBinder != null);
var actionBinding = actionValueBinder.GetBinding(actionDescriptor);
Contract.Assume(actionBinding != null);
return actionBinding;
}

private static ApiParameterDescription CreateParameterDescriptionFromBinding(HttpParameterBinding parameterBinding)
{
Contract.Requires(parameterBinding != null);

Contract.Assume(parameterBinding.Descriptor?.Configuration != null);

var parameterDescription = CreateParameterDescriptionFromDescriptor(parameterBinding.Descriptor);
if (parameterBinding.WillReadBody)
{
parameterDescription.Source = ApiParameterSource.FromBody;
}
else if (parameterBinding.WillReadUri())
{
parameterDescription.Source = ApiParameterSource.FromUri;
}

return parameterDescription;
}

private static ApiParameterDescription CreateParameterDescriptionFromDescriptor(HttpParameterDescriptor parameter)
{
Contract.Requires(parameter != null);

Contract.Assume(parameter.Configuration != null);

return new ApiParameterDescription
{
ParameterDescriptor = parameter,
Name = parameter.Prefix ?? parameter.ParameterName,
Documentation = GetApiParameterDocumentation(parameter),
Source = ApiParameterSource.Unknown
};
}

private static string GetApiParameterDocumentation(HttpParameterDescriptor parameterDescriptor)
{
Contract.Requires(parameterDescriptor != null);
Contract.Requires(parameterDescriptor.Configuration != null);

var documentationProvider = parameterDescriptor.Configuration.Services.GetDocumentationProvider();

return documentationProvider?.GetDocumentation(parameterDescriptor);
}

private static string GetApiDocumentation(HttpActionDescriptor actionDescriptor)
{
Contract.Requires(actionDescriptor != null);
Expand Down
Loading

0 comments on commit 8366dc8

Please sign in to comment.