Skip to content

Commit

Permalink
Merge pull request #32 from rbeauchamp/feature/31
Browse files Browse the repository at this point in the history
Provide single quotes around swagger path parameters of type string
  • Loading branch information
Richard Beauchamp committed Dec 24, 2015
2 parents 690c836 + d6000f7 commit 9321d02
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 37 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>Provides support for containment and ODataRoute attributes.</ReleaseNotes>
<ReleaseNotes>Provide single quotes around swagger path parameters of type string</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.0</Version>
<Version>2.7.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Swashbuckle.OData\Swashbuckle.OData.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ static ProductsController()
}).ToList().ForEach(p => Data.TryAdd(p.Id, p));
}

/// <summary>
/// Query products
/// </summary>
[EnableQuery]
public IQueryable<Product> GetProducts()
{
return Data.Values.AsQueryable();
}

/// <summary>
/// Get the most expensive product. This is a function bound to a collection.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions Swashbuckle.OData.Tests/Fixtures/FunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public async Task It_supports_a_function_that_accepts_a_string_parameter()

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/v1/Products({Id})/Default.CalculateGeneralSalesTax(state={state})", out pathItem);
swaggerDocument.paths.TryGetValue("/odata/v1/Products({Id})/Default.CalculateGeneralSalesTax(state='{state}')", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();
}
Expand All @@ -113,7 +113,7 @@ public async Task It_supports_unbound_functions()

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/v1/GetSalesTaxRate(state={state})", out pathItem);
swaggerDocument.paths.TryGetValue("/odata/v1/GetSalesTaxRate(state='{state}')", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();
}
Expand Down
96 changes: 96 additions & 0 deletions Swashbuckle.OData.Tests/Fixtures/StringTypeUrlParamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using System.Web.Http.Dispatcher;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using FluentAssertions;
using Microsoft.OData.Edm;
using Microsoft.Owin.Hosting;
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;
using SwashbuckleODataSample;
using SwashbuckleODataSample.Models;

namespace Swashbuckle.OData.Tests
{
[TestFixture]
public class StringTypeUrlParamTests
{
[Test]
public async Task It_wraps_string_type_url_params_with_single_quotes()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(ProductsV1Controller))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);

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

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/v1/Products({Id})/Default.CalculateGeneralSalesTax(state='{state}')", out pathItem);
pathItem.Should().NotBeNull();
pathItem.get.Should().NotBeNull();
}
}

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

var controllerSelector = new UnitTestODataVersionControllerSelector(config, targetController);
config.Services.Replace(typeof(IHttpControllerSelector), controllerSelector);

// Define a route to a controller class that contains functions
config.MapODataServiceRoute("FunctionsODataRoute", "odata/v1", GetFunctionsEdmModel());
controllerSelector.RouteVersionSuffixMapping.Add("FunctionsODataRoute", "V1");

config.EnsureInitialized();
}

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

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

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

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

// Function bound to a collection
// Returns the top 10 product, a collection
productType.Collection
.Function("Top10")
.ReturnsCollectionFromEntitySet<Product>("Products");

// Function bound to a single entity
// Returns the instance's price rank among all products
productType
.Function("GetPriceRank")
.Returns<int>();

// Function bound to a single entity
// Accept a string as parameter and return a double
// This function calculate the general sales tax base on the
// state
productType
.Function("CalculateGeneralSalesTax")
.Returns<double>()
.Parameter<string>("state");

// Unbound Function
builder.Function("GetSalesTaxRate")
.Returns<double>()
.Parameter<string>("state");

return builder.GetEdmModel();
}
}
}
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 @@ -150,6 +150,7 @@
<Compile Include="Fixtures\PatchTests.cs" />
<Compile Include="Fixtures\PutTests.cs" />
<Compile Include="Fixtures\RestierTests.cs" />
<Compile Include="Fixtures\StringTypeUrlParamTests.cs" />
<Compile Include="HttpClientUtils.cs" />
<Compile Include="Fixtures\GetTests.cs" />
<Compile Include="NorthwindAPI\Category.cs" />
Expand Down
25 changes: 25 additions & 0 deletions Swashbuckle.OData/Descriptions/ApiDescriptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Web.Http.Description;
using Swashbuckle.Swagger;

namespace Swashbuckle.OData.Descriptions
{
public static class ApiDescriptionExtensions
{
public static string GetRelativePathWithQuotedStringParams(this ApiDescription apiDescription)
{
var parameters = apiDescription.ParameterDescriptions;

var newRelativePathSansQueryString = apiDescription.RelativePathSansQueryString();

foreach (var parameter in parameters)
{
if (newRelativePathSansQueryString.Contains("{" + parameter.Name + "}") && parameter.ParameterDescriptor.ParameterType == typeof(string))
{
newRelativePathSansQueryString = newRelativePathSansQueryString.Replace("{" + parameter.Name + "}", "\'{" + parameter.Name + "}\'");
}
}

return apiDescription.RelativePath.Replace(apiDescription.RelativePathSansQueryString(), newRelativePathSansQueryString);
}
}
}
2 changes: 2 additions & 0 deletions Swashbuckle.OData/Descriptions/ODataActionDescriptorMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public IEnumerable<ApiDescription> Map(ODataActionDescriptor oDataActionDescript
// Have to set ResponseDescription because it's internal!??
apiDescription.GetType().GetProperty("ResponseDescription").SetValue(apiDescription, responseDescription);

apiDescription.RelativePath = apiDescription.GetRelativePathWithQuotedStringParams();

apiDescriptions.Add(apiDescription);
}
return apiDescriptions;
Expand Down
35 changes: 3 additions & 32 deletions Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,30 +326,15 @@ public static string GetPathForEntity(string routePrefix, IEdmNavigationSource n
private static string AppendSingleColumnKeyTemplate(IEdmEntitySet entitySet, string singleEntityPath)
{
var key = entitySet.EntityType().Key().Single();
//if (key.Type.Definition.TypeKind == EdmTypeKind.Primitive && ((IEdmPrimitiveType) key.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String)
//{
// singleEntityPath += "'{" + key.Name + "}', ";
//}
//else
//{

singleEntityPath += "{" + key.Name + "}, ";
//}
return singleEntityPath;
}

private static string AppendMultiColumnKeyTemplate(IEdmEntitySet entitySet, string singleEntityPath)
{
foreach (var key in entitySet.EntityType().Key())
{
//if (key.Type.Definition.TypeKind == EdmTypeKind.Primitive && ((IEdmPrimitiveType)key.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String)
//{
// singleEntityPath += key.Name + "='{" + key.Name + "}', ";
//}
//else
//{
singleEntityPath += key.Name + "={" + key.Name + "}, ";
//}
singleEntityPath += key.Name + "={" + key.Name + "}, ";
}
return singleEntityPath;
}
Expand Down Expand Up @@ -403,14 +388,7 @@ public static string GetPathForOperationOfEntitySet(IEdmOperation operation, IEd
{
foreach (var parameter in operation.Parameters.Skip(1))
{
//if (parameter.Type.Definition.TypeKind == EdmTypeKind.Primitive && ((IEdmPrimitiveType) parameter.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String)
//{
// swaggerOperationPath += parameter.Name + "=" + "'{" + parameter.Name + "}',";
//}
//else
//{
swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},";
//}
swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},";
}
}
if (swaggerOperationPath.EndsWith(",", StringComparison.Ordinal))
Expand Down Expand Up @@ -444,14 +422,7 @@ public static string GetPathForOperationOfEntity(string routePrefix, IEdmOperati
{
foreach (var parameter in operation.Parameters.Skip(1))
{
//if (parameter.Type.Definition.TypeKind == EdmTypeKind.Primitive && ((IEdmPrimitiveType) parameter.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String)
//{
// swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},";
//}
//else
//{
swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},";
//}
swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},";
}
}
if (swaggerOperationPath.EndsWith(",", StringComparison.Ordinal))
Expand Down
2 changes: 2 additions & 0 deletions Swashbuckle.OData/Descriptions/SwaggerOperationMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public IEnumerable<ApiDescription> Map(ODataActionDescriptor oDataActionDescript
// Have to set ResponseDescription because it's internal!??
apiDescription.GetType().GetProperty("ResponseDescription").SetValue(apiDescription, responseDescription);

apiDescription.RelativePath = apiDescription.GetRelativePathWithQuotedStringParams();

apiDescriptions.Add(apiDescription);
}

Expand Down
2 changes: 1 addition & 1 deletion Swashbuckle.OData/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("2.7.0")]
[assembly: AssemblyInformationalVersion("2.7.1")]
1 change: 1 addition & 0 deletions Swashbuckle.OData/Swashbuckle.OData.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
<ItemGroup>
<Compile Include="CollectionExtentions.cs" />
<Compile Include="DefaultCompositionRoot.cs" />
<Compile Include="Descriptions\ApiDescriptionExtensions.cs" />
<Compile Include="Descriptions\AttributeRouteStrategy.cs" />
<Compile Include="Descriptions\IODataActionDescriptorMapper.cs" />
<Compile Include="Descriptions\IODataActionDescriptorExplorer.cs" />
Expand Down

0 comments on commit 9321d02

Please sign in to comment.