Skip to content

Commit

Permalink
Merge pull request #2259 from captainsafia/safia/route-constraint-int…
Browse files Browse the repository at this point in the history
…egration

Add support for generating schema from route constraints
  • Loading branch information
domaindrivendev authored Jan 28, 2022
2 parents 5381a9f + eaf3bc8 commit 2c5306e
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.OpenApi.Models;
using AnnotationsDataType = System.ComponentModel.DataAnnotations.DataType;

Expand Down Expand Up @@ -31,6 +33,45 @@ public static void ApplyValidationAttributes(this OpenApiSchema schema, IEnumera
}
}

public static void ApplyRouteConstraints(this OpenApiSchema schema, ApiParameterRouteInfo routeInfo)
{
foreach (var constraint in routeInfo.Constraints)
{
if (constraint is MinRouteConstraint minRouteConstraint)
ApplyMinRouteConstraint(schema, minRouteConstraint);

else if (constraint is MaxRouteConstraint maxRouteConstraint)
ApplyMaxRouteConstraint(schema, maxRouteConstraint);

else if (constraint is MinLengthRouteConstraint minLengthRouteConstraint)
ApplyMinLengthRouteConstraint(schema, minLengthRouteConstraint);

else if (constraint is MaxLengthRouteConstraint maxLengthRouteConstraint)
ApplyMaxLengthRouteConstraint(schema, maxLengthRouteConstraint);

else if (constraint is RangeRouteConstraint rangeRouteConstraint)
ApplyRangeRouteConstraint(schema, rangeRouteConstraint);

else if (constraint is RegexRouteConstraint regexRouteConstraint)
ApplyRegexRouteConstraint(schema, regexRouteConstraint);

else if (constraint is LengthRouteConstraint lengthRouteConstraint)
ApplyLengthRouteConstraint(schema, lengthRouteConstraint);

else if (constraint is LongRouteConstraint || constraint is FloatRouteConstraint || constraint is DecimalRouteConstraint)
schema.Type = "number";

else if (constraint is IntRouteConstraint)
schema.Type = "integer";

else if (constraint is GuidRouteConstraint || constraint is StringRouteConstraint)
schema.Type = "string";

else if (constraint is BoolRouteConstraint)
schema.Type = "boolean";
}
}

public static string ResolveType(this OpenApiSchema schema, SchemaRepository schemaRepository)
{
if (schema.Reference != null && schemaRepository.Schemas.TryGetValue(schema.Reference.Id, out OpenApiSchema definitionSchema))
Expand Down Expand Up @@ -80,6 +121,14 @@ private static void ApplyMinLengthAttribute(OpenApiSchema schema, MinLengthAttri
schema.MinLength = minLengthAttribute.Length;
}

private static void ApplyMinLengthRouteConstraint(OpenApiSchema schema, MinLengthRouteConstraint minLengthRouteConstraint)
{
if (schema.Type == "array")
schema.MinItems = minLengthRouteConstraint.MinLength;
else
schema.MinLength = minLengthRouteConstraint.MinLength;
}

private static void ApplyMaxLengthAttribute(OpenApiSchema schema, MaxLengthAttribute maxLengthAttribute)
{
if (schema.Type == "array")
Expand All @@ -88,6 +137,14 @@ private static void ApplyMaxLengthAttribute(OpenApiSchema schema, MaxLengthAttri
schema.MaxLength = maxLengthAttribute.Length;
}

private static void ApplyMaxLengthRouteConstraint(OpenApiSchema schema, MaxLengthRouteConstraint maxLengthRouteConstraint)
{
if (schema.Type == "array")
schema.MaxItems = maxLengthRouteConstraint.MaxLength;
else
schema.MaxLength = maxLengthRouteConstraint.MaxLength;
}

private static void ApplyRangeAttribute(OpenApiSchema schema, RangeAttribute rangeAttribute)
{
schema.Maximum = decimal.TryParse(rangeAttribute.Maximum.ToString(), out decimal maximum)
Expand All @@ -99,15 +156,36 @@ private static void ApplyRangeAttribute(OpenApiSchema schema, RangeAttribute ran
: schema.Minimum;
}

private static void ApplyRangeRouteConstraint(OpenApiSchema schema, RangeRouteConstraint rangeRouteConstraint)
{
schema.Maximum = rangeRouteConstraint.Max;
schema.Minimum = rangeRouteConstraint.Min;
}

private static void ApplyMinRouteConstraint(OpenApiSchema schema, MinRouteConstraint minRouteConstraint)
=> schema.Minimum = minRouteConstraint.Min;

private static void ApplyMaxRouteConstraint(OpenApiSchema schema, MaxRouteConstraint maxRouteConstraint)
=> schema.Maximum = maxRouteConstraint.Max;

private static void ApplyRegularExpressionAttribute(OpenApiSchema schema, RegularExpressionAttribute regularExpressionAttribute)
{
schema.Pattern = regularExpressionAttribute.Pattern;
}

private static void ApplyRegexRouteConstraint(OpenApiSchema schema, RegexRouteConstraint regexRouteConstraint)
=> schema.Pattern = regexRouteConstraint.Constraint.ToString();

private static void ApplyStringLengthAttribute(OpenApiSchema schema, StringLengthAttribute stringLengthAttribute)
{
schema.MinLength = stringLengthAttribute.MinimumLength;
schema.MaxLength = stringLengthAttribute.MaximumLength;
}

private static void ApplyLengthRouteConstraint(OpenApiSchema schema, LengthRouteConstraint lengthRouteConstraint)
{
schema.MinLength = lengthRouteConstraint.MinLength;
schema.MaxLength = lengthRouteConstraint.MaxLength;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;

namespace Swashbuckle.AspNetCore.SwaggerGen
Expand All @@ -28,13 +29,14 @@ public OpenApiSchema GenerateSchema(
Type modelType,
SchemaRepository schemaRepository,
MemberInfo memberInfo = null,
ParameterInfo parameterInfo = null)
ParameterInfo parameterInfo = null,
ApiParameterRouteInfo routeInfo = null)
{
if (memberInfo != null)
return GenerateSchemaForMember(modelType, schemaRepository, memberInfo);

if (parameterInfo != null)
return GenerateSchemaForParameter(modelType, schemaRepository, parameterInfo);
return GenerateSchemaForParameter(modelType, schemaRepository, parameterInfo, routeInfo);

return GenerateSchemaForType(modelType, schemaRepository);
}
Expand All @@ -61,7 +63,7 @@ private OpenApiSchema GenerateSchemaForMember(
{
var customAttributes = memberInfo.GetInlineAndMetadataAttributes();

// Nullable, ReadOnly & WriteOnly are only relevant for Schema "properties" (i.e. where dataProperty is non-null)
// Nullable, ReadOnly & WriteOnly are only relevant for Schema "properties" (i.e. where dataProperty is non-null)
if (dataProperty != null)
{
schema.Nullable = _generatorOptions.SupportNonNullableReferenceTypes
Expand Down Expand Up @@ -96,7 +98,8 @@ private OpenApiSchema GenerateSchemaForMember(
private OpenApiSchema GenerateSchemaForParameter(
Type modelType,
SchemaRepository schemaRepository,
ParameterInfo parameterInfo)
ParameterInfo parameterInfo,
ApiParameterRouteInfo routeInfo)
{
var dataContract = GetDataContractFor(modelType);

Expand Down Expand Up @@ -125,6 +128,10 @@ private OpenApiSchema GenerateSchemaForParameter(
}

schema.ApplyValidationAttributes(customAttributes);
if (routeInfo != null)
{
schema.ApplyRouteConstraints(routeInfo);
}

ApplyFilters(schema, modelType, schemaRepository, parameterInfo: parameterInfo);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Reflection;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Mvc.ApiExplorer;

namespace Swashbuckle.AspNetCore.SwaggerGen
{
Expand All @@ -10,6 +11,7 @@ OpenApiSchema GenerateSchema(
Type modelType,
SchemaRepository schemaRepository,
MemberInfo memberInfo = null,
ParameterInfo parameterInfo = null);
ParameterInfo parameterInfo = null,
ApiParameterRouteInfo routeInfo = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ private OpenApiParameter GenerateParameter(
apiParameter.ModelMetadata.ModelType,
schemaRepository,
apiParameter.PropertyInfo(),
apiParameter.ParameterInfo())
apiParameter.ParameterInfo(),
apiParameter.RouteInfo)
: new OpenApiSchema { Type = "string" };

var parameter = new OpenApiParameter
Expand Down Expand Up @@ -232,11 +233,12 @@ private OpenApiSchema GenerateSchema(
Type type,
SchemaRepository schemaRepository,
PropertyInfo propertyInfo = null,
ParameterInfo parameterInfo = null)
ParameterInfo parameterInfo = null,
ApiParameterRouteInfo routeInfo = null)
{
try
{
return _schemaGenerator.GenerateSchema(type, schemaRepository, propertyInfo, parameterInfo);
return _schemaGenerator.GenerateSchema(type, schemaRepository, propertyInfo, parameterInfo, routeInfo);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.OpenApi.Models;
using Xunit;
using Swashbuckle.AspNetCore.TestSupport;
Expand Down Expand Up @@ -765,6 +768,68 @@ public void GenerateSchema_GeneratesOpenSchema_IfDynamicJsonType(Type type)
Assert.Null(schema.Type);
}

[Fact]
public void GenerateSchema_GeneratesSchema_IfParameterHasMaxLengthRouteConstraint()
{
var maxLength = 3;
var constraints = new List<IRouteConstraint>()
{
new MaxLengthRouteConstraint(maxLength)
};
var routeInfo = new ApiParameterRouteInfo
{
Constraints = constraints
};
var parameterInfo = typeof(FakeController)
.GetMethod(nameof(FakeController.ActionWithParameter))
.GetParameters()
.First();
var schema = Subject().GenerateSchema(typeof(string), new SchemaRepository(), parameterInfo: parameterInfo, routeInfo: routeInfo);
Assert.Equal(maxLength, schema.MaxLength);
}

[Fact]
public void GenerateSchema_GeneratesSchema_IfParameterHasMultipleConstraints()
{
var maxLength = 3;
var minLength = 1;
var constraints = new List<IRouteConstraint>()
{
new MaxLengthRouteConstraint(3),
new MinLengthRouteConstraint(minLength)
};
var routeInfo = new ApiParameterRouteInfo
{
Constraints = constraints
};
var parameterInfo = typeof(FakeController)
.GetMethod(nameof(FakeController.ActionWithParameter))
.GetParameters()
.First();
var schema = Subject().GenerateSchema(typeof(string), new SchemaRepository(), parameterInfo: parameterInfo, routeInfo: routeInfo);
Assert.Equal(maxLength, schema.MaxLength);
Assert.Equal(minLength, schema.MinLength);
}

[Fact]
public void GenerateSchema_GeneratesSchema_IfParameterHasTypeConstraints()
{
var constraints = new List<IRouteConstraint>()
{
new IntRouteConstraint(),
};
var routeInfo = new ApiParameterRouteInfo
{
Constraints = constraints
};
var parameterInfo = typeof(FakeController)
.GetMethod(nameof(FakeController.ActionWithParameter))
.GetParameters()
.First();
var schema = Subject().GenerateSchema(typeof(string), new SchemaRepository(), parameterInfo: parameterInfo, routeInfo: routeInfo);
Assert.Equal("integer", schema.Type);
}

private SchemaGenerator Subject(
Action<SchemaGeneratorOptions> configureGenerator = null,
Action<JsonSerializerOptions> configureSerializer = null)
Expand Down

0 comments on commit 2c5306e

Please sign in to comment.