Skip to content

Commit

Permalink
Support polymorphic types in model generation
Browse files Browse the repository at this point in the history
  • Loading branch information
domaindrivendev committed Feb 11, 2014
1 parent 515c4ba commit a8e84cc
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 8 deletions.
12 changes: 9 additions & 3 deletions Swashbuckle.TestApp/App_Start/WebApiConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,29 @@ public static class WebApiConfig
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Orders_default",
name: "Orders_route",
routeTemplate: "api/orders/{id}",
defaults: new {controller = "Orders", id = RouteParameter.Optional}
);

config.Routes.MapHttpRoute(
name: "OrderItems_default",
name: "OrderItems_route",
routeTemplate: "api/orders/{orderId}/items/{id}",
defaults: new {controller = "OrderItems", id = RouteParameter.Optional}
);

config.Routes.MapHttpRoute(
name: "Customers_default",
name: "Customers_route",
routeTemplate: "api/customers/{id}",
defaults: new {controller = "Customers", id = RouteParameter.Optional}
);

config.Routes.MapHttpRoute(
name: "Products_route",
routeTemplate: "api/products",
defaults: new {controller = "Products"}
);


// Uncomment below to support documentation from Xml Comments
// try
Expand Down
15 changes: 15 additions & 0 deletions Swashbuckle.TestApp/Controllers/ProductsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Web.Http;
using Swashbuckle.TestApp.Models;

namespace Swashbuckle.TestApp.Controllers
{
public class ProductsController : ApiController
{
public IEnumerable<Product> GetAll()
{
throw new NotImplementedException();
}
}
}
32 changes: 32 additions & 0 deletions Swashbuckle.TestApp/Models/Product.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Swashbuckle.TestApp.Models
{
public abstract class Product
{
public int Id { get; set; }

public decimal Price { get; set; }
}

public class Book : Product
{
public string Title { get; set; }

public string Author { get; set; }
}

public class Album : Product
{
public string Name { get; set; }

public string Artist { get; set; }
}

public abstract class Service : Product
{}

public class Shipping : Service
{}

public class Packaging : Service
{}
}
2 changes: 2 additions & 0 deletions Swashbuckle.TestApp/Swashbuckle.TestApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
<ItemGroup>
<Compile Include="App_Start\SwaggerConfig.cs" />
<Compile Include="Controllers\CustomersController.cs" />
<Compile Include="Controllers\ProductsController.cs" />
<Compile Include="Models\Confirmation.cs" />
<Compile Include="Models\Customer.cs" />
<Compile Include="Models\MyGenericType.cs" />
Expand All @@ -122,6 +123,7 @@
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Compile Include="Models\Product.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SwaggerExtensions\AddAuthorizationErrorCodes.cs" />
<Compile Include="SwaggerExtensions\ApplyCustomResponseTypes.cs" />
Expand Down
92 changes: 90 additions & 2 deletions Swashbuckle.Tests/SwaggerGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ public void It_should_generate_a_listing_according_to_provided_strategy()
var resourceListing = _swaggerSpec.Listing;
Assert.AreEqual("1.0", resourceListing.ApiVersion);
Assert.AreEqual("1.2", resourceListing.SwaggerVersion);
Assert.AreEqual(3, resourceListing.Apis.Count());
Assert.AreEqual(4, resourceListing.Apis.Count());

Assert.IsTrue(resourceListing.Apis.Any(a => a.Path == "/Orders"),
"Orders declaration not listed");
Assert.IsTrue(resourceListing.Apis.Any(a => a.Path == "/OrderItems"),
"OrderItems declaration not listed");
Assert.IsTrue(resourceListing.Apis.Any(a => a.Path == "/Customers"),
"Customers declaration not listed");
Assert.IsTrue(resourceListing.Apis.Any(a => a.Path == "/Products"),
"Products declaration not listed");
}

[Test]
Expand Down Expand Up @@ -413,7 +415,7 @@ public void It_should_generate_a_parameter_spec_for_each_parameter_in_a_given_op
}

[Test]
public void It_should_generate_a_model_spec_for_all_complex_types_in_a_declaration()
public void It_should_generate_model_specs_for_all_complex_types_in_a_declaration_including_sub_types()
{
ApiDeclaration("/Orders", dec =>
{
Expand Down Expand Up @@ -546,6 +548,92 @@ public void It_should_generate_a_model_spec_for_all_complex_types_in_a_declarati
});
});

ApiDeclaration("/Products", dec =>
{
Assert.AreEqual(6, dec.Models.Count);
Model(dec, "Product", model =>
{
ModelProperty(model, "Id", property =>
{
Assert.AreEqual("integer", property.Type);
Assert.AreEqual("int32", property.Format);
Assert.IsNull(property.Items);
Assert.IsNull(property.Enum);
});
ModelProperty(model, "Price", property =>
{
Assert.AreEqual("number", property.Type);
Assert.AreEqual("double", property.Format);
Assert.IsNull(property.Items);
Assert.IsNull(property.Enum);
});
CollectionAssert.AreEqual(new[] { "Book", "Album", "Service" }, model.SubTypes);
});
Model(dec, "Book", model =>
{
ModelProperty(model, "Title", property =>
{
Assert.AreEqual("string", property.Type);
Assert.IsNull(property.Format);
Assert.IsNull(property.Items);
Assert.IsNull(property.Enum);
});
ModelProperty(model, "Author", property =>
{
Assert.AreEqual("string", property.Type);
Assert.IsNull(property.Format);
Assert.IsNull(property.Items);
Assert.IsNull(property.Enum);
});
CollectionAssert.IsEmpty(model.SubTypes);
});
Model(dec, "Album", model =>
{
ModelProperty(model, "Name", property =>
{
Assert.AreEqual("string", property.Type);
Assert.IsNull(property.Format);
Assert.IsNull(property.Items);
Assert.IsNull(property.Enum);
});
ModelProperty(model, "Artist", property =>
{
Assert.AreEqual("string", property.Type);
Assert.IsNull(property.Format);
Assert.IsNull(property.Items);
Assert.IsNull(property.Enum);
});
CollectionAssert.IsEmpty(model.SubTypes);
});
Model(dec, "Service", model =>
{
CollectionAssert.IsEmpty(model.Properties);
CollectionAssert.AreEqual(new[] { "Shipping", "Packaging" }, model.SubTypes);
});
Model(dec, "Shipping", model =>
{
CollectionAssert.IsEmpty(model.Properties);
CollectionAssert.IsEmpty(model.SubTypes);
});
Model(dec, "Packaging", model =>
{
CollectionAssert.IsEmpty(model.Properties);
CollectionAssert.IsEmpty(model.SubTypes);
});
});
}

[Test]
Expand Down
10 changes: 8 additions & 2 deletions Swashbuckle/Models/ModelSpecGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private ModelSpec CreateSpecFor(Type type, bool deferIfComplex, IDictionary<Type

private ModelSpec CreateComplexSpecFor(Type type, IDictionary<Type, ModelSpec> complexTypes)
{
var propInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var propInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

var propSpecs = propInfos
.ToDictionary(propInfo => propInfo.Name, propInfo => CreateSpecFor(propInfo.PropertyType, true, complexTypes));
Expand All @@ -107,12 +107,18 @@ private ModelSpec CreateComplexSpecFor(Type type, IDictionary<Type, ModelSpec> c
.Select(propInfo => propInfo.Name)
.ToList();

var subTypes = type.DirectSubTypes()
.Select(subType => CreateSpecFor(subType, true, complexTypes))
.Select(subTypeSpec => subTypeSpec.Ref)
.ToList();

return new ModelSpec
{
Id = UniqueIdFor(type),
Type = "object",
Properties = propSpecs,
Required = required
Required = required,
SubTypes = subTypes
};
}

Expand Down
6 changes: 5 additions & 1 deletion Swashbuckle/Models/SwaggerSpec.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Web.Http.Description;
using Newtonsoft.Json;

Expand Down Expand Up @@ -170,5 +171,8 @@ public class ModelSpec

[JsonProperty("required")]
public IList<string> Required { get; set; }

[JsonProperty("subTypes")]
public IList<string> SubTypes { get; set; }
}
}
5 changes: 5 additions & 0 deletions Swashbuckle/Models/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,10 @@ public static bool IsEnumerable(this Type type, out Type enumerableTypeArgument)

return enumerableType != null;
}

public static IEnumerable<Type> DirectSubTypes(this Type baseType)
{
return baseType.Assembly.GetTypes().Where(type => type.BaseType == baseType);
}
}
}

0 comments on commit a8e84cc

Please sign in to comment.