Skip to content

Commit

Permalink
Merge pull request #35 from rbeauchamp/feature/23
Browse files Browse the repository at this point in the history
Support OData paths that contain URL params of type array
  • Loading branch information
rbeauchamp committed Dec 26, 2015
2 parents 9321d02 + 263ba4a commit a734025
Show file tree
Hide file tree
Showing 24 changed files with 212 additions and 70 deletions.
4 changes: 2 additions & 2 deletions Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
<Owners>Richard Beauchamp</Owners>
<Summary>Extends Swashbuckle with OData v4 support!</Summary>
<Description>Extends Swashbuckle with OData v4 support!</Description>
<ReleaseNotes>Provide single quotes around swagger path parameters of type string</ReleaseNotes>
<ReleaseNotes>Supports OData paths that contain parameters of type array</ReleaseNotes>
<ProjectUrl>https://github.com/rbeauchamp/Swashbuckle.OData</ProjectUrl>
<LicenseUrl>https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/License.txt</LicenseUrl>
<Copyright>Copyright 2015</Copyright>
<Tags>Swashbuckle Swagger SwaggerUi OData Documentation Discovery Help WebApi AspNet AspNetWebApi Docs WebHost IIS</Tags>
<Version>2.7.1</Version>
<Version>2.7.2</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Swashbuckle.OData\Swashbuckle.OData.csproj" />
Expand Down
16 changes: 8 additions & 8 deletions Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ private static IEdmModel GetFunctionsEdmModel()

var productType = builder.EntityType<Product>();

// Function bound to a collection
// Function bound to an entity set
// Returns the most expensive product, a single entity
productType.Collection
.Function("MostExpensive")
.Returns<double>();

// Function bound to a collection
// Function bound to an entity set
// Returns the top 10 product, a collection
productType.Collection
.Function("Top10")
Expand All @@ -125,12 +125,12 @@ private static IEdmModel GetFunctionsEdmModel()
.Returns<double>()
.Parameter<string>("state");

// Commented out because unbound functions don't
// play well in a config with mulitple OData routes
// Unbound Function
//builder.Function("GetSalesTaxRate")
// .Returns<double>()
// .Parameter<string>("state");
// Function bound to an entity set
// Accepts an array as a parameter
productType.Collection
.Function("ProductsWithIds")
.ReturnsCollectionFromEntitySet<Product>("Products")
.CollectionParameter<int>("Ids");

return builder.GetEdmModel();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.OData;
using System.Web.OData.Routing;
using SwashbuckleODataSample.Models;
using SwashbuckleODataSample.Repositories;

Expand Down
23 changes: 11 additions & 12 deletions Swashbuckle.OData.Sample/ODataControllers/ProductsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public IHttpActionResult GetPriceRank(int key)
}

/// <summary>
/// Get the sales tax for a product in a given state. This is a function which accepts a parameter.
/// Get the sales tax for a product in a given state. This function accepts a parameter.
/// </summary>
/// <param name="key">The product id</param>
/// <param name="state">The state</param>
Expand All @@ -99,17 +99,16 @@ public IHttpActionResult CalculateGeneralSalesTax(int key, string state)
return NotFound();
}

///// <summary>
///// Get the sales tax rate for a state. This is an unbound function.
///// </summary>
///// <param name="state">The state</param>
//[HttpGet]
//[ResponseType(typeof(double))]
//[ODataRoute("GetSalesTaxRate(state={state})")]
//public IHttpActionResult GetSalesTaxRate([FromODataUri] string state)
//{
// return Ok(GetRate(state));
//}
/// <summary>
/// Get products with the given Ids. This function accepts a parameter of type 'array'.
/// </summary>
/// <param name="Ids">The ids.</param>
/// <returns></returns>
[HttpGet]
public IQueryable<Product> ProductsWithIds([FromODataUri]int[] Ids)
{
return Data.Values.Where(p => Ids.Contains(p.Id)).AsQueryable();
}

private static double GetRate(string state)
{
Expand Down
5 changes: 2 additions & 3 deletions Swashbuckle.OData.Tests/Containment/ContainmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;
using SwashbuckleODataSample;

namespace Swashbuckle.OData.Tests.Containment
{
Expand All @@ -19,7 +18,7 @@ public async Task It_supports_models_that_use_containment()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(AccountsController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -39,7 +38,7 @@ public async Task It_supports_odata_attribute_routing()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(AccountsController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand Down
2 changes: 1 addition & 1 deletion Swashbuckle.OData.Tests/Fixtures/CustomRouteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task It_allows_definition_of_custom_routes()
using (WebApp.Start(HttpClientUtils.BaseAddress, builder => Configuration(builder, typeof(OrdersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand Down
106 changes: 106 additions & 0 deletions Swashbuckle.OData.Tests/Fixtures/FromUriArrayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
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 FromUriArrayTests
{
[Test]
public async Task It_supports_uris_that_contain_arrays()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(Products2Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
// Verify that the OData route is valid
var products = await httpClient.GetJsonAsync<ODataResponse<Product2>>("/odata/ProductsWithIds(Ids=[0,1])");
products.Should().NotBeNull();
products.Value.Count.Should().Be(2);

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

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/ProductsWithIds(Ids=[{Ids}])", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

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

// Define a route to a controller class that contains functions
config.MapODataServiceRoute("FromUriArrayRoute", "odata", GetEdmModel());

config.EnsureInitialized();
}

private static IEdmModel GetEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();

builder.EntitySet<Product2>("Products");

builder.Function("ProductsWithIds")
.ReturnsCollectionFromEntitySet<Product2>("Products")
.CollectionParameter<int>("Ids");

return builder.GetEdmModel();
}
}

public class Product2
{
[Key]
public int Id { get; set; }

public string Name { get; set; }

public double Price { get; set; }
}

public class Products2Controller : ODataController
{
private static readonly ConcurrentDictionary<int, Product2> Data;

static Products2Controller()
{
Data = new ConcurrentDictionary<int, Product2>();
var rand = new Random();

Enumerable.Range(0, 100).Select(i => new Product2
{
Id = i,
Name = "Product " + i,
Price = rand.NextDouble() * 1000
}).ToList().ForEach(p => Data.TryAdd(p.Id, p));
}

[HttpGet]
[ODataRoute("ProductsWithIds(Ids={Ids})")]
public IQueryable<Product2> ProductsWithIds([FromODataUri]int[] Ids)
{
return Data.Values.Where(p => Ids.Contains(p.Id)).AsQueryable();
}
}
}
21 changes: 15 additions & 6 deletions Swashbuckle.OData.Tests/Fixtures/FunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;
using SwashbuckleODataSample;
using SwashbuckleODataSample.Models;

namespace Swashbuckle.OData.Tests
Expand All @@ -30,7 +29,7 @@ public async Task It_supports_a_parameterless_function_bound_to_a_collection()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductsV1Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -40,6 +39,8 @@ public async Task It_supports_a_parameterless_function_bound_to_a_collection()
swaggerDocument.paths.TryGetValue("/odata/v1/Products/Default.MostExpensive()", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

Expand All @@ -49,7 +50,7 @@ public async Task It_supports_a_function_bound_to_an_entity_set_that_returns_a_c
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductsV1Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -59,6 +60,8 @@ public async Task It_supports_a_function_bound_to_an_entity_set_that_returns_a_c
swaggerDocument.paths.TryGetValue("/odata/v1/Products/Default.Top10()", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

Expand All @@ -68,7 +71,7 @@ public async Task It_supports_a_function_bound_to_an_entity()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductsV1Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -78,6 +81,8 @@ public async Task It_supports_a_function_bound_to_an_entity()
swaggerDocument.paths.TryGetValue("/odata/v1/Products({Id})/Default.GetPriceRank()", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

Expand All @@ -87,7 +92,7 @@ public async Task It_supports_a_function_that_accepts_a_string_parameter()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductsV1Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -97,6 +102,8 @@ public async Task It_supports_a_function_that_accepts_a_string_parameter()
swaggerDocument.paths.TryGetValue("/odata/v1/Products({Id})/Default.CalculateGeneralSalesTax(state='{state}')", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

Expand All @@ -106,7 +113,7 @@ public async Task It_supports_unbound_functions()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductsV1Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -116,6 +123,8 @@ public async Task It_supports_unbound_functions()
swaggerDocument.paths.TryGetValue("/odata/v1/GetSalesTaxRate(state='{state}')", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

Expand Down
9 changes: 4 additions & 5 deletions Swashbuckle.OData.Tests/Fixtures/GetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;
using SwashbuckleODataSample;
using SwashbuckleODataSample.Models;
using SwashbuckleODataSample.ODataControllers;

Expand All @@ -24,7 +23,7 @@ public async Task It_includes_the_filter_parameter()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(CustomersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -49,7 +48,7 @@ public async Task It_has_all_optional_odata_query_parameters()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(CustomersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -69,7 +68,7 @@ public async Task It_has_a_parameter_with_a_name_equal_to_the_path_name()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(CustomersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");
Expand All @@ -91,7 +90,7 @@ public async Task It_supports_types_with_a_guid_primary_key()
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(OrdersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);

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

0 comments on commit a734025

Please sign in to comment.