From 55c945d4266754b1dcb6613ae14062b5dfe89db0 Mon Sep 17 00:00:00 2001 From: Richard Beauchamp Date: Sat, 19 Dec 2015 07:55:34 -0800 Subject: [PATCH] Define pre and post conditions --- .../ODataControllers/OrdersController.cs | 3 +- .../Descriptions/EdmLibHelpers.cs | 36 +++++++++++++------ .../HttpActionDescriptorExtensions.cs | 7 +++- .../Descriptions/IODataRouteGenerator.cs | 2 +- .../Descriptions/IParameterMapper.cs | 2 +- .../Descriptions/ODataApiExplorer.cs | 21 +++++++++++ .../Descriptions/ODataRouteExtensions.cs | 7 +++- .../Descriptions/ODataSwaggerUtilities.cs | 32 +++++++++++++++++ .../Descriptions/OperationBuilder.cs | 8 +++++ .../Descriptions/OperationExtensions.cs | 1 + .../Descriptions/ParameterExtensions.cs | 8 +++++ .../RestierParameterDescriptor.cs | 3 ++ .../Descriptions/SwaggerPathGenerator.cs | 9 +++++ .../Descriptions/SwaggerRouteBuilder.cs | 10 ++++++ .../Descriptions/TypeExtensions.cs | 3 ++ Swashbuckle.OData/Descriptions/TypeHelper.cs | 21 +++++++++-- Swashbuckle.OData/EqualityComparer.cs | 8 +++++ .../HttpConfigurationExtensions.cs | 11 ++++++ Swashbuckle.OData/ODataSwaggerProvider.cs | 15 ++++++++ .../ODataSwaggerProviderOptions.cs | 7 ++++ Swashbuckle.OData/ReflectionExtensions.cs | 8 ++++- Swashbuckle.OData/SchemaRegistryExtensions.cs | 3 ++ Swashbuckle.OData/Swashbuckle.OData.csproj | 6 ++-- 23 files changed, 210 insertions(+), 21 deletions(-) diff --git a/Swashbuckle.OData.Sample/ODataControllers/OrdersController.cs b/Swashbuckle.OData.Sample/ODataControllers/OrdersController.cs index e89f3e4..c79e48b 100644 --- a/Swashbuckle.OData.Sample/ODataControllers/OrdersController.cs +++ b/Swashbuckle.OData.Sample/ODataControllers/OrdersController.cs @@ -26,7 +26,7 @@ public IQueryable GetOrders() } /// - /// Create a new order for the customer with the given + /// Create a new order for the customer with the given id /// /// The customer id /// Order details @@ -34,6 +34,7 @@ public IQueryable GetOrders() public async Task Post([FromODataUri] int customerId, Order order) { order.OrderId = SequentialGuidGenerator.Generate(SequentialGuidType.SequentialAtEnd); + order.CustomerId = customerId; if (!ModelState.IsValid) { diff --git a/Swashbuckle.OData/Descriptions/EdmLibHelpers.cs b/Swashbuckle.OData/Descriptions/EdmLibHelpers.cs index 3e030ab..ca5c7ad 100644 --- a/Swashbuckle.OData/Descriptions/EdmLibHelpers.cs +++ b/Swashbuckle.OData/Descriptions/EdmLibHelpers.cs @@ -103,8 +103,8 @@ public static IEdmType GetEdmType(this IEdmModel edmModel, Type clrType) private static IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollections) { - Contract.Assert(edmModel != null); - Contract.Assert(clrType != null); + Contract.Requires(edmModel != null); + Contract.Requires(clrType != null); var primitiveType = GetEdmPrimitiveTypeOrNull(clrType); if (primitiveType != null) @@ -160,6 +160,8 @@ private static IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCo public static IEdmTypeReference GetEdmTypeReference(this IEdmModel edmModel, Type clrType) { + Contract.Requires(clrType != null); + var edmType = edmModel.GetEdmType(clrType); if (edmType != null) { @@ -172,7 +174,7 @@ public static IEdmTypeReference GetEdmTypeReference(this IEdmModel edmModel, Typ public static IEdmTypeReference ToEdmTypeReference(this IEdmType edmType, bool isNullable) { - Contract.Assert(edmType != null); + Contract.Requires(edmType != null); switch (edmType.TypeKind) { @@ -229,9 +231,9 @@ public static Type GetClrType(IEdmType edmType, IEdmModel edmModel) public static Type GetClrType(IEdmType edmType, IEdmModel edmModel, IAssembliesResolver assembliesResolver) { - var edmSchemaType = edmType as IEdmSchemaType; + Contract.Requires(edmType is IEdmSchemaType); - Contract.Assert(edmSchemaType != null); + var edmSchemaType = (IEdmSchemaType) edmType; var annotation = edmModel.GetAnnotationValue(edmSchemaType); if (annotation != null) @@ -291,8 +293,8 @@ public static bool IsAutoExpand(IEdmProperty edmProperty, IEdmModel edmModel) private static QueryableRestrictionsAnnotation GetPropertyRestrictions(IEdmProperty edmProperty, IEdmModel edmModel) { - Contract.Assert(edmProperty != null); - Contract.Assert(edmModel != null); + Contract.Requires(edmProperty != null); + Contract.Requires(edmModel != null); return edmModel.GetAnnotationValue(edmProperty); } @@ -332,12 +334,16 @@ public static PropertyInfo GetDynamicPropertyDictionary(IEdmStructuredType edmTy public static IEdmPrimitiveType GetEdmPrimitiveTypeOrNull(Type clrType) { + Contract.Requires(clrType != null); + IEdmPrimitiveType primitiveType; return BuiltInTypesMapping.TryGetValue(clrType, out primitiveType) ? primitiveType : null; } public static IEdmPrimitiveTypeReference GetEdmPrimitiveTypeReferenceOrNull(Type clrType) { + Contract.Requires(clrType != null); + var primitiveType = GetEdmPrimitiveTypeOrNull(clrType); return primitiveType != null ? CoreModel.GetPrimitive(primitiveType.PrimitiveKind, IsNullable(clrType)) : null; } @@ -346,6 +352,8 @@ public static IEdmPrimitiveTypeReference GetEdmPrimitiveTypeReferenceOrNull(Type // and returns the corresponding clr type to which we map like uint => long. public static Type IsNonstandardEdmPrimitive(Type type, out bool isNonstandardEdmPrimitive) { + Contract.Requires(type != null); + var edmType = GetEdmPrimitiveTypeReferenceOrNull(type); if (edmType == null) { @@ -363,19 +371,23 @@ public static Type IsNonstandardEdmPrimitive(Type type, out bool isNonstandardEd // to a valid EDM literal (the C# type name IEnumerable). public static string EdmName(this Type clrType) { + Contract.Requires(clrType != null); + // We cannot use just Type.Name here as it doesn't work for generic types. return MangleClrTypeName(clrType); } public static string EdmFullName(this Type clrType) { + Contract.Requires(clrType != null); + return string.Format(CultureInfo.InvariantCulture, "{0}.{1}", clrType.Namespace, clrType.EdmName()); } public static IEnumerable GetConcurrencyProperties(this IEdmModel model, IEdmEntitySet entitySet) { - Contract.Assert(model != null); - Contract.Assert(entitySet != null); + Contract.Requires(model != null); + Contract.Requires(entitySet != null); IEnumerable cachedProperties; if (_concurrencyProperties != null && _concurrencyProperties.TryGetValue(entitySet, out cachedProperties)) @@ -427,6 +439,8 @@ private static IEdmPrimitiveType GetPrimitiveType(EdmPrimitiveTypeKind primitive public static bool IsNullable(Type type) { + Contract.Requires(type != null); + return !type.IsValueType || Nullable.GetUnderlyingType(type) != null; } @@ -438,13 +452,15 @@ private static Type ExtractGenericInterface(Type queryType, Type interfaceType) private static IEnumerable GetMatchingTypes(string edmFullName, IAssembliesResolver assembliesResolver) { + Contract.Requires(assembliesResolver != null); + return TypeHelper.GetLoadedTypes(assembliesResolver).Where(t => t.IsPublic && t.EdmFullName() == edmFullName); } // TODO (workitem 336): Support nested types and anonymous types. private static string MangleClrTypeName(Type type) { - Contract.Assert(type != null); + Contract.Requires(type != null); return !type.IsGenericType ? type.Name diff --git a/Swashbuckle.OData/Descriptions/HttpActionDescriptorExtensions.cs b/Swashbuckle.OData/Descriptions/HttpActionDescriptorExtensions.cs index cc8ab78..6d0e42c 100644 --- a/Swashbuckle.OData/Descriptions/HttpActionDescriptorExtensions.cs +++ b/Swashbuckle.OData/Descriptions/HttpActionDescriptorExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics.Contracts; +using System.Linq; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Description; @@ -9,6 +10,8 @@ internal static class HttpActionDescriptorExtensions { public static ResponseDescription CreateResponseDescription(this HttpActionDescriptor actionDescriptor) { + Contract.Requires(actionDescriptor != null); + var responseTypeAttribute = actionDescriptor.GetCustomAttributes(); var responseType = responseTypeAttribute.Select(attribute => attribute.ResponseType).FirstOrDefault(); @@ -22,6 +25,8 @@ public static ResponseDescription CreateResponseDescription(this HttpActionDescr private static string GetApiResponseDocumentation(this HttpActionDescriptor actionDescriptor) { + Contract.Requires(actionDescriptor != null); + var documentationProvider = actionDescriptor.Configuration.Services.GetDocumentationProvider(); return documentationProvider?.GetResponseDocumentation(actionDescriptor); } diff --git a/Swashbuckle.OData/Descriptions/IODataRouteGenerator.cs b/Swashbuckle.OData/Descriptions/IODataRouteGenerator.cs index 89e4f01..f9483af 100644 --- a/Swashbuckle.OData/Descriptions/IODataRouteGenerator.cs +++ b/Swashbuckle.OData/Descriptions/IODataRouteGenerator.cs @@ -16,7 +16,7 @@ public interface IODataRouteGenerator } [ContractClassFor(typeof(IODataRouteGenerator))] - public sealed class ODataRouteGeneratorContract : IODataRouteGenerator + public abstract class ODataRouteGeneratorContract : IODataRouteGenerator { public List Generate(string routePrefix, IEdmModel model) { diff --git a/Swashbuckle.OData/Descriptions/IParameterMapper.cs b/Swashbuckle.OData/Descriptions/IParameterMapper.cs index 6acdd06..1656820 100644 --- a/Swashbuckle.OData/Descriptions/IParameterMapper.cs +++ b/Swashbuckle.OData/Descriptions/IParameterMapper.cs @@ -18,7 +18,7 @@ public interface IParameterMapper } [ContractClassFor(typeof(IParameterMapper))] - public sealed class ParameterMapperContract : IParameterMapper + public abstract class ParameterMapperContract : IParameterMapper { public HttpParameterDescriptor Map(Parameter swaggerParameter, int parameterIndex, HttpActionDescriptor actionDescriptor) { diff --git a/Swashbuckle.OData/Descriptions/ODataApiExplorer.cs b/Swashbuckle.OData/Descriptions/ODataApiExplorer.cs index 6c3e321..92a0ed5 100644 --- a/Swashbuckle.OData/Descriptions/ODataApiExplorer.cs +++ b/Swashbuckle.OData/Descriptions/ODataApiExplorer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; @@ -63,6 +64,8 @@ private Collection GetApiDescriptions() /// private List GetApiDescriptions(ODataRoute oDataRoute) { + Contract.Requires(oDataRoute != null); + var apiDescriptions = new List(); var standardRoutes = _routeGenerator.Generate(oDataRoute.RoutePrefix, oDataRoute.GetEdmModel()); @@ -79,6 +82,8 @@ private List GetApiDescriptions(ODataRoute oDataRoute) private List GetApiDescriptions(ODataRoute oDataRoute, SwaggerRoute potentialSwaggerRoute) { + Contract.Requires(potentialSwaggerRoute != null); + var apiDescriptions = new List(); apiDescriptions.AddIfNotNull(GetApiDescription(new HttpMethod("DELETE"), potentialSwaggerRoute.PathItem.delete, potentialSwaggerRoute.Template, oDataRoute)); @@ -134,6 +139,8 @@ private HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, Opera private ApiDescription GetApiDescription(HttpActionDescriptor actionDescriptor, HttpMethod httpMethod, Operation operation, string potentialPathTemplate, ODataRoute oDataRoute) { + Contract.Requires(actionDescriptor == null || operation != null); + if (actionDescriptor == null) { return null; @@ -212,11 +219,15 @@ private static HttpActionDescriptor MapForRestierIfNecessary(HttpActionDescripto private static IEnumerable GetInnerFormatters(IEnumerable mediaTypeFormatters) { + Contract.Requires(mediaTypeFormatters != null); + return mediaTypeFormatters.Select(Decorator.GetInner); } private List CreateParameterDescriptions(Operation operation, HttpActionDescriptor actionDescriptor) { + Contract.Requires(operation != null); + return operation.parameters.Select((parameter, index) => GetParameterDescription(parameter, index, actionDescriptor)).ToList(); } @@ -242,6 +253,8 @@ private HttpParameterDescriptor GetHttpParameterDescriptor(Parameter parameter, private static string GetApiParameterDocumentation(Parameter parameter, HttpParameterDescriptor parameterDescriptor) { + Contract.Requires(parameterDescriptor != null); + var documentationProvider = parameterDescriptor.Configuration.Services.GetDocumentationProvider(); return documentationProvider != null @@ -251,6 +264,8 @@ private static string GetApiParameterDocumentation(Parameter parameter, HttpPara private static string GetApiDocumentation(HttpActionDescriptor actionDescriptor, Operation operation) { + Contract.Requires(actionDescriptor != null); + var documentationProvider = actionDescriptor.Configuration.Services.GetDocumentationProvider(); return documentationProvider != null ? documentationProvider.GetDocumentation(actionDescriptor) @@ -284,5 +299,11 @@ private static IEnumerable FlattenRoutes(IEnumerable rou } } } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_apiDescriptions != null); + } } } \ No newline at end of file diff --git a/Swashbuckle.OData/Descriptions/ODataRouteExtensions.cs b/Swashbuckle.OData/Descriptions/ODataRouteExtensions.cs index f58763f..243d94f 100644 --- a/Swashbuckle.OData/Descriptions/ODataRouteExtensions.cs +++ b/Swashbuckle.OData/Descriptions/ODataRouteExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics.Contracts; +using System.Linq; using System.Web.OData.Routing; using Microsoft.OData.Edm; @@ -8,11 +9,15 @@ public static class ODataRouteExtensions { public static IEdmModel GetEdmModel(this ODataRoute oDataRoute) { + Contract.Requires(oDataRoute != null); + return oDataRoute.GetODataPathRouteConstraint().EdmModel; } public static ODataPathRouteConstraint GetODataPathRouteConstraint(this ODataRoute oDataRoute) { + Contract.Requires(oDataRoute != null); + return oDataRoute.Constraints.Values.SingleOrDefault(value => value is ODataPathRouteConstraint) as ODataPathRouteConstraint; } } diff --git a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs index b8600a0..c5c4e47 100644 --- a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs +++ b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs @@ -294,6 +294,8 @@ public static PathItem CreateSwaggerPathForOperationOfEntity(IEdmOperation opera /// public static Url GetPathForEntitySet(string routePrefix, IEdmEntitySet entitySet) { + Contract.Requires(entitySet != null); + return routePrefix.AppendPathSegment(entitySet.Name); } @@ -617,12 +619,17 @@ private static string GetPrimitiveTypeAndFormat(IEdmPrimitiveType primitiveType, private static Operation Responses(this Operation obj, IDictionary responses) { + Contract.Requires(obj != null); + obj.responses = responses; return obj; } private static IDictionary ResponseRef(this IDictionary responses, string name, string description, string refType) { + Contract.Requires(responses != null); + Contract.Requires(name != null); + responses.Add(name, new Response { description = description, @@ -637,6 +644,9 @@ private static IDictionary ResponseRef(this IDictionary Response(this IDictionary responses, string name, string description, IEdmType type) { + Contract.Requires(responses != null); + Contract.Requires(name != null); + var schema = new Schema(); SetSwaggerType(schema, type); @@ -651,11 +661,16 @@ private static IDictionary Response(this IDictionary DefaultErrorResponse(this IDictionary responses) { + Contract.Requires(responses != null); + return responses.ResponseRef("default", "Unexpected error", "#/definitions/_Error"); } private static IDictionary Response(this IDictionary responses, string name, string description) { + Contract.Requires(responses != null); + Contract.Requires(name != null); + responses.Add(name, new Response { description = description @@ -666,12 +681,16 @@ private static IDictionary Response(this IDictionary parameters) { + Contract.Requires(obj != null); + obj.parameters = parameters; return obj; } private static IList Parameter(this IList parameters, string name, string kind, string description, string type, bool required, string format = null) { + Contract.Requires(parameters != null); + parameters.Add(new Parameter { name = name, @@ -687,6 +706,9 @@ private static IList Parameter(this IList parameters, stri internal static IList Parameter(this IList parameters, string name, string kind, string description, IEdmType type) { + Contract.Requires(parameters != null); + Contract.Requires(type != null); + var parameter = new Parameter { name = name, @@ -711,30 +733,40 @@ internal static IList Parameter(this IList parameters, str private static Operation Tags(this Operation obj, params string[] tags) { + Contract.Requires(obj != null); + obj.tags = tags; return obj; } private static Operation Summary(this Operation obj, string summary) { + Contract.Requires(obj != null); + obj.summary = summary; return obj; } private static Operation Description(this Operation obj, string description) { + Contract.Requires(obj != null); + obj.description = description; return obj; } private static Schema Description(this Schema obj, string description) { + Contract.Requires(obj != null); + obj.description = description; return obj; } private static Operation OperationId(this Operation obj, string operationId) { + Contract.Requires(obj != null); + obj.operationId = operationId; return obj; } diff --git a/Swashbuckle.OData/Descriptions/OperationBuilder.cs b/Swashbuckle.OData/Descriptions/OperationBuilder.cs index 21dcdfc..49fbee5 100644 --- a/Swashbuckle.OData/Descriptions/OperationBuilder.cs +++ b/Swashbuckle.OData/Descriptions/OperationBuilder.cs @@ -25,6 +25,7 @@ public OperationBuilder(Operation operation, SwaggerRouteBuilder swaggerRouteBui public OperationBuilder PathParameter(string parameterName) { Contract.Requires(!string.IsNullOrWhiteSpace(parameterName)); + Contract.Ensures(Contract.Result() != null); _operation.Parameters().Parameter(parameterName, ParameterSource.Path.ToString().ToLower(), null, GetEdmModel().GetEdmType(typeof (T))); @@ -39,6 +40,7 @@ public OperationBuilder PathParameter(string parameterName) public OperationBuilder BodyParameter(string parameterName) { Contract.Requires(!string.IsNullOrWhiteSpace(parameterName)); + Contract.Ensures(Contract.Result() != null); _operation.Parameters().Parameter(parameterName, ParameterSource.Body.ToString().ToLower(), null, GetEdmModel().GetEdmType(typeof (T))); @@ -49,5 +51,11 @@ private IEdmModel GetEdmModel() { return _swaggerRouteBuilder.ODataRoute.GetEdmModel(); } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_swaggerRouteBuilder != null); + } } } \ No newline at end of file diff --git a/Swashbuckle.OData/Descriptions/OperationExtensions.cs b/Swashbuckle.OData/Descriptions/OperationExtensions.cs index ae116c1..39f61f2 100644 --- a/Swashbuckle.OData/Descriptions/OperationExtensions.cs +++ b/Swashbuckle.OData/Descriptions/OperationExtensions.cs @@ -19,6 +19,7 @@ public static IDictionary GenerateSampleQueryParameterValues(thi public static string GenerateSampleODataAbsoluteUri(this Operation operation, string serviceRoot, string pathTemplate) { Contract.Requires(operation != null); + Contract.Requires(serviceRoot != null); var uriTemplate = new UriTemplate(pathTemplate); diff --git a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs index 53e8e55..3480092 100644 --- a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs +++ b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs @@ -9,6 +9,8 @@ internal static class ParameterExtensions { public static ParameterSource MapSource(this Parameter parameter) { + Contract.Requires(parameter != null); + switch (parameter.@in) { case "query": @@ -28,6 +30,8 @@ public static ParameterSource MapSource(this Parameter parameter) public static Type GetClrType(this Parameter parameter) { + Contract.Requires(parameter != null); + var type = parameter.type; var format = parameter.format; @@ -70,6 +74,7 @@ public static Type GetClrType(this Parameter parameter) public static string GenerateSamplePathParameterValue(this Parameter parameter) { + Contract.Requires(parameter != null); Contract.Requires(parameter.@in == "path"); var type = parameter.type; @@ -111,6 +116,7 @@ public static string GenerateSamplePathParameterValue(this Parameter parameter) private static Type GetEntityTypeForBodyParameter(Parameter parameter) { + Contract.Requires(parameter != null); Contract.Requires(parameter.@in == "body"); var fullTypeName = parameter.schema.@ref.Replace("#/definitions/", string.Empty); @@ -120,6 +126,7 @@ private static Type GetEntityTypeForBodyParameter(Parameter parameter) public static Type GetEntityType(this Schema schema) { + Contract.Requires(schema != null); Contract.Requires(!string.IsNullOrWhiteSpace(schema.@ref)); var fullTypeName = schema.@ref.Replace("#/definitions/", string.Empty); @@ -129,6 +136,7 @@ public static Type GetEntityType(this Schema schema) public static Type GetEntitySetType(this Schema schema) { + Contract.Requires(schema != null); Contract.Requires(schema.type == "array"); var queryableType = typeof(IQueryable<>); diff --git a/Swashbuckle.OData/Descriptions/RestierParameterDescriptor.cs b/Swashbuckle.OData/Descriptions/RestierParameterDescriptor.cs index 5ac2c7b..37a9533 100644 --- a/Swashbuckle.OData/Descriptions/RestierParameterDescriptor.cs +++ b/Swashbuckle.OData/Descriptions/RestierParameterDescriptor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; using System.Web.Http; using System.Web.Http.Controllers; using Swashbuckle.Swagger; @@ -10,6 +11,8 @@ internal class RestierParameterDescriptor : HttpParameterDescriptor { public RestierParameterDescriptor(Parameter parameter) { + Contract.Requires(parameter != null); + Parameter = parameter; DefaultValue = null; Prefix = null; diff --git a/Swashbuckle.OData/Descriptions/SwaggerPathGenerator.cs b/Swashbuckle.OData/Descriptions/SwaggerPathGenerator.cs index e7ac3ca..37b9fd2 100644 --- a/Swashbuckle.OData/Descriptions/SwaggerPathGenerator.cs +++ b/Swashbuckle.OData/Descriptions/SwaggerPathGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using Microsoft.OData.Edm; @@ -23,6 +24,8 @@ public List Generate(string routePrefix, IEdmModel model) private static IEnumerable GenerateEntitySetRoutes(string routePrefix, IEdmModel model) { + Contract.Requires(model != null); + return model.EntityContainer .EntitySets() .Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForEntitySet(routePrefix, entitySet), ODataSwaggerUtilities.CreateSwaggerPathForEntitySet(entitySet))); @@ -30,6 +33,8 @@ private static IEnumerable GenerateEntitySetRoutes(string routePre private static IEnumerable GenerateEntityRoutes(string routePrefix, IEdmModel model) { + Contract.Requires(model != null); + return model.EntityContainer .EntitySets() .Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForEntity(routePrefix, entitySet), ODataSwaggerUtilities.CreateSwaggerPathForEntity(entitySet))); @@ -37,6 +42,8 @@ private static IEnumerable GenerateEntityRoutes(string routePrefix private static IEnumerable GenerateOperationImportRoutes(string routePrefix, IEdmModel model) { + Contract.Requires(model != null); + return model.EntityContainer .OperationImports() .Select(operationImport => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationImport(routePrefix, operationImport), ODataSwaggerUtilities.CreateSwaggerPathForOperationImport(operationImport))); @@ -50,6 +57,8 @@ private static IEnumerable GenerateOperationImportRoutes(string ro /// private static IEnumerable GenerateOperationRoutes(string routePrefix, IEdmModel model) { + Contract.Requires(model != null); + var routes = new List(); foreach (var operation in model.SchemaElements.OfType()) diff --git a/Swashbuckle.OData/Descriptions/SwaggerRouteBuilder.cs b/Swashbuckle.OData/Descriptions/SwaggerRouteBuilder.cs index df20ef1..0987c30 100644 --- a/Swashbuckle.OData/Descriptions/SwaggerRouteBuilder.cs +++ b/Swashbuckle.OData/Descriptions/SwaggerRouteBuilder.cs @@ -22,6 +22,7 @@ public SwaggerRouteBuilder(SwaggerRoute swaggerRoute, ODataRoute oDataRoute) public OperationBuilder Operation(HttpMethod httpMethod) { Contract.Requires(GetOperation(httpMethod) == null); + Contract.Ensures(Contract.Result() != null); var operation = new Operation(); @@ -49,8 +50,11 @@ public OperationBuilder Operation(HttpMethod httpMethod) return new OperationBuilder(operation, this); } + [Pure] public Operation GetOperation(HttpMethod httpMethod) { + Contract.Requires(httpMethod != null); + switch (httpMethod.Method.ToUpper()) { case "GET": @@ -67,5 +71,11 @@ public Operation GetOperation(HttpMethod httpMethod) throw new ArgumentOutOfRangeException(nameof(httpMethod)); } } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_swaggerRoute != null); + } } } \ No newline at end of file diff --git a/Swashbuckle.OData/Descriptions/TypeExtensions.cs b/Swashbuckle.OData/Descriptions/TypeExtensions.cs index 12abb18..30ec76b 100644 --- a/Swashbuckle.OData/Descriptions/TypeExtensions.cs +++ b/Swashbuckle.OData/Descriptions/TypeExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.ComponentModel; +using System.Diagnostics.Contracts; namespace System { @@ -13,6 +14,8 @@ internal static class TypeExtensions { public static bool IsNullable(this Type type) { + Contract.Requires(type != null); + if (type.IsValueType) { // value types are only nullable if they are Nullable diff --git a/Swashbuckle.OData/Descriptions/TypeHelper.cs b/Swashbuckle.OData/Descriptions/TypeHelper.cs index 24c78c1..2078092 100644 --- a/Swashbuckle.OData/Descriptions/TypeHelper.cs +++ b/Swashbuckle.OData/Descriptions/TypeHelper.cs @@ -16,6 +16,8 @@ internal static class TypeHelper { public static Type ToNullable(this Type t) { + Contract.Requires(t != null); + if (t.IsNullable()) { return t; @@ -26,6 +28,8 @@ public static Type ToNullable(this Type t) // Gets the collection element type. public static Type GetInnerElementType(this Type type) { + Contract.Requires(type != null); + Type elementType; type.IsCollection(out elementType); Contract.Assert(elementType != null); @@ -35,6 +39,8 @@ public static Type GetInnerElementType(this Type type) public static bool IsCollection(this Type type) { + Contract.Requires(type != null); + Type elementType; return type.IsCollection(out elementType); } @@ -42,6 +48,7 @@ public static bool IsCollection(this Type type) public static bool IsCollection(this Type type, out Type elementType) { Contract.Requires(type != null); + Contract.Ensures(Contract.ValueAtReturn(out elementType) != null); elementType = type; @@ -67,11 +74,15 @@ public static bool IsCollection(this Type type, out Type elementType) public static Type GetUnderlyingTypeOrSelf(Type type) { + Contract.Requires(type != null); + return Nullable.GetUnderlyingType(type) ?? type; } public static bool IsEnum(Type type) { + Contract.Requires(type != null); + var underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(type); return underlyingTypeOrSelf.IsEnum; } @@ -85,7 +96,7 @@ public static bool IsEnum(Type type) /// true if the type is a primitive type. internal static bool IsQueryPrimitiveType(Type type) { - Contract.Assert(type != null); + Contract.Requires(type != null); type = GetInnerMostElementType(type); @@ -100,7 +111,7 @@ internal static bool IsQueryPrimitiveType(Type type) /// The innermost element type internal static Type GetInnerMostElementType(Type type) { - Contract.Assert(type != null); + Contract.Requires(type != null); while (true) { @@ -127,6 +138,8 @@ internal static Type GetInnerMostElementType(Type type) /// internal static Type GetImplementedIEnumerableType(Type type) { + Contract.Requires(type != null); + // get inner type from Task if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Task<>)) { @@ -149,6 +162,8 @@ internal static Type GetImplementedIEnumerableType(Type type) // This code is copied from DefaultHttpControllerTypeResolver.GetControllerTypes. internal static IEnumerable GetLoadedTypes(IAssembliesResolver assembliesResolver) { + Contract.Requires(assembliesResolver != null); + var result = new List(); // Go through all assemblies referenced by the application and search for types matching a predicate @@ -186,6 +201,8 @@ internal static IEnumerable GetLoadedTypes(IAssembliesResolver assembliesR private static Type GetInnerGenericType(Type interfaceType) { + Contract.Requires(interfaceType != null); + // Getting the type T definition if the returning type implements IEnumerable var parameterTypes = interfaceType.GetGenericArguments(); diff --git a/Swashbuckle.OData/EqualityComparer.cs b/Swashbuckle.OData/EqualityComparer.cs index 0bd9909..0b76269 100644 --- a/Swashbuckle.OData/EqualityComparer.cs +++ b/Swashbuckle.OData/EqualityComparer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; namespace Swashbuckle.OData { @@ -147,5 +148,12 @@ public int GetHashCode(TSource obj) } return _comparer.GetHashCode(_projection(obj)); } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_projection != null); + Contract.Invariant(_comparer != null); + } } } \ No newline at end of file diff --git a/Swashbuckle.OData/HttpConfigurationExtensions.cs b/Swashbuckle.OData/HttpConfigurationExtensions.cs index ccb1ed0..f43befb 100644 --- a/Swashbuckle.OData/HttpConfigurationExtensions.cs +++ b/Swashbuckle.OData/HttpConfigurationExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Web; using System.Web.Http; using System.Web.OData.Routing; @@ -12,6 +13,8 @@ public static class HttpConfigurationExtensions { internal static JsonSerializerSettings SerializerSettingsOrDefault(this HttpConfiguration httpConfig) { + Contract.Requires(httpConfig != null); + var formatter = httpConfig.Formatters.JsonFormatter; return formatter != null ? formatter.SerializerSettings @@ -20,6 +23,10 @@ internal static JsonSerializerSettings SerializerSettingsOrDefault(this HttpConf public static SwaggerRouteBuilder AddCustomSwaggerRoute(this HttpConfiguration httpConfig, ODataRoute oDataRoute, string routeTemplate) { + Contract.Requires(httpConfig != null); + Contract.Requires(oDataRoute != null); + Contract.Ensures(Contract.Result() != null); + var fullRouteTemplate = HttpUtility.UrlDecode(oDataRoute.RoutePrefix.AppendPathSegment(routeTemplate)); var swaggerRoute = new SwaggerRoute(fullRouteTemplate); @@ -40,6 +47,10 @@ public static SwaggerRouteBuilder AddCustomSwaggerRoute(this HttpConfiguration h public static List GetCustomSwaggerRoutes(this HttpConfiguration httpConfig, ODataRoute oDataRoute) { + Contract.Requires(httpConfig != null); + Contract.Requires(oDataRoute != null); + Contract.Ensures(Contract.Result>() != null); + object swaggerRoutes; httpConfig.Properties.TryGetValue(oDataRoute, out swaggerRoutes); diff --git a/Swashbuckle.OData/ODataSwaggerProvider.cs b/Swashbuckle.OData/ODataSwaggerProvider.cs index 3320a98..a6cc12d 100644 --- a/Swashbuckle.OData/ODataSwaggerProvider.cs +++ b/Swashbuckle.OData/ODataSwaggerProvider.cs @@ -115,6 +115,8 @@ public SwaggerDocument GetSwagger(string rootUrl, string apiVersion) private SwaggerDocument MergeODataAndWebApiSwaggerDocs(string rootUrl, string apiVersion, SwaggerDocument odataSwaggerDoc) { + Contract.Requires(odataSwaggerDoc != null); + var webApiSwaggerDoc = _defaultProvider.GetSwagger(rootUrl, apiVersion); webApiSwaggerDoc.paths = webApiSwaggerDoc.paths.UnionEvenIfNull(odataSwaggerDoc.paths).ToLookup(pair => pair.Key, pair => pair.Value) @@ -140,6 +142,8 @@ private SwaggerDocument MergeODataAndWebApiSwaggerDocs(string rootUrl, string ap private PathItem CreatePathItem(IEnumerable apiDescriptions, SchemaRegistry schemaRegistry) { + Contract.Requires(apiDescriptions != null); + var pathItem = new PathItem(); // Group further by http method @@ -225,6 +229,8 @@ private Operation CreateOperation(ApiDescription apiDescription, SchemaRegistry private static Parameter CreateParameter(SwaggerApiParameterDescription paramDesc, bool inPath, SchemaRegistry schemaRegistry) { + Contract.Requires(paramDesc != null); + var @in = inPath ? "path" : MapToSwaggerParameterLocation(paramDesc.SwaggerSource); @@ -280,5 +286,14 @@ private IEnumerable GetApiDescriptionsFor(string apiVersion) ? _odataApiExplorer.ApiDescriptions : _odataApiExplorer.ApiDescriptions.Where(apiDesc => _options.VersionSupportResolver(apiDesc, apiVersion)); } + + [ContractInvariantMethod] + private void ObjectInvariant() + { + Contract.Invariant(_options != null); + Contract.Invariant(_apiVersions != null); + Contract.Invariant(_httpConfig != null); + Contract.Invariant(_odataApiExplorer != null); + } } } \ No newline at end of file diff --git a/Swashbuckle.OData/ODataSwaggerProviderOptions.cs b/Swashbuckle.OData/ODataSwaggerProviderOptions.cs index eafd753..ed426f6 100644 --- a/Swashbuckle.OData/ODataSwaggerProviderOptions.cs +++ b/Swashbuckle.OData/ODataSwaggerProviderOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Web.Http.Description; using Swashbuckle.OData.Descriptions; @@ -11,6 +12,8 @@ internal class ODataSwaggerProviderOptions { public ODataSwaggerProviderOptions(SwaggerProviderOptions swaggerProviderOptions) { + Contract.Requires(swaggerProviderOptions != null); + VersionSupportResolver = swaggerProviderOptions.VersionSupportResolver; Schemes = swaggerProviderOptions.Schemes; SecurityDefinitions = swaggerProviderOptions.SecurityDefinitions; @@ -63,6 +66,8 @@ public ODataSwaggerProviderOptions(SwaggerProviderOptions swaggerProviderOptions private static string DefaultGroupingKeySelector(ApiDescription apiDescription) { + Contract.Requires(apiDescription != null); + return apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName == "Restier" ? ((RestierHttpActionDescriptor)apiDescription.ActionDescriptor).EntitySetName : apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName; @@ -75,6 +80,8 @@ private static string DefaultSchemaIdSelector(Type type) private static ApiDescription DefaultConflictingActionsResolver(IEnumerable apiDescriptions) { + Contract.Requires(apiDescriptions != null); + var first = apiDescriptions.First(); throw new NotSupportedException($"Not supported by Swagger 2.0: Multiple operations with path '{first.RelativePathSansQueryString()}' and method '{first.HttpMethod}'. " + "See the config setting - \"ResolveConflictingActions\" for a potential workaround"); } diff --git a/Swashbuckle.OData/ReflectionExtensions.cs b/Swashbuckle.OData/ReflectionExtensions.cs index 9643629..1247f92 100644 --- a/Swashbuckle.OData/ReflectionExtensions.cs +++ b/Swashbuckle.OData/ReflectionExtensions.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Diagnostics.Contracts; +using System.Reflection; namespace Swashbuckle.OData { @@ -12,6 +13,8 @@ internal static class ReflectionExtensions /// The field value from the object. internal static T GetInstanceField(this object instance, string fieldName) { + Contract.Requires(instance != null); + const BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; var field = instance.GetType().GetField(fieldName, bindFlags); return (T)field.GetValue(instance); @@ -26,6 +29,9 @@ internal static T GetInstanceField(this object instance, string fieldName) /// internal static T InvokeFunction(this object instance, string methodName) { + Contract.Requires(instance != null); + Contract.Requires(methodName != null); + var methodInfo = instance.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); return (T)methodInfo.Invoke(instance, null); } diff --git a/Swashbuckle.OData/SchemaRegistryExtensions.cs b/Swashbuckle.OData/SchemaRegistryExtensions.cs index a3bc9ea..51e4aa8 100644 --- a/Swashbuckle.OData/SchemaRegistryExtensions.cs +++ b/Swashbuckle.OData/SchemaRegistryExtensions.cs @@ -10,6 +10,7 @@ internal static class SchemaRegistryExtensions { public static Schema GetOrRegisterODataType(this SchemaRegistry registry, Type type) { + Contract.Requires(registry != null); Contract.Requires(type != null); var isAGenericODataType = IsAGenericODataType(type); @@ -20,6 +21,8 @@ public static Schema GetOrRegisterODataType(this SchemaRegistry registry, Type t private static bool IsAGenericODataType(Type type) { + Contract.Requires(type != null); + var isDelta = type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Delta<>); var isSingleResult = type.IsGenericType && type.GetGenericTypeDefinition() == typeof (SingleResult<>); diff --git a/Swashbuckle.OData/Swashbuckle.OData.csproj b/Swashbuckle.OData/Swashbuckle.OData.csproj index b75a38f..1b58951 100644 --- a/Swashbuckle.OData/Swashbuckle.OData.csproj +++ b/Swashbuckle.OData/Swashbuckle.OData.csproj @@ -38,16 +38,16 @@ True True True - False + True True False True False False False - False + True True - False + True True True True