Skip to content

Commit

Permalink
Added minimum and maximum constraint for unsigned integers to more ac…
Browse files Browse the repository at this point in the history
…curately declare them in the schema. UInt128 maximum value is left as null because OpenApi maximum field is too small to support it. Changed uint to use int64 format because int32 is too small to represent its upper limit. Fixes domaindrivendev#2582
  • Loading branch information
Havunen committed Feb 24, 2024
1 parent b39f252 commit f354f74
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public static class CommonFormats
[typeof(ushort?)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(int)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(int?)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(uint)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(uint?)] = Tuple.Create(DataType.Integer, "int32"),
[typeof(uint)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(uint?)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(long)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(long?)] = Tuple.Create(DataType.Integer, "int64"),
[typeof(ulong)] = Tuple.Create(DataType.Integer, "int64"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,23 +309,47 @@ private bool TryGetCustomTypeMapping(Type modelType, out Func<OpenApiSchema> sch

private OpenApiSchema CreatePrimitiveSchema(DataContract dataContract, bool isNullable)
{
var underlyingType = dataContract.UnderlyingType;
var schema = new OpenApiSchema
{
Type = dataContract.DataType.ToString().ToLower(CultureInfo.InvariantCulture),
Format = dataContract.DataFormat,
Nullable = isNullable || Nullable.GetUnderlyingType(dataContract.UnderlyingType) != null
Nullable = isNullable || Nullable.GetUnderlyingType(underlyingType) != null
};

if (dataContract.UnderlyingType.IsEnum)
if (underlyingType.IsEnum)
{
schema.Enum = dataContract.UnderlyingType.GetEnumValues()
schema.Enum = underlyingType.GetEnumValues()
.Cast<object>()
.Select(value => dataContract.JsonConverter(value))
.Distinct()
.Select(valueAsJson => OpenApiAnyFactory.CreateFromJson(valueAsJson))
.ToList();
}

if (dataContract.DataType == DataType.Integer)
{
if (underlyingType == typeof(ushort))
{
schema.Minimum = 0;
schema.Maximum = ushort.MaxValue;
} else if (underlyingType == typeof(uint))
{
schema.Minimum = 0;
schema.Maximum = uint.MaxValue;
} else if (underlyingType == typeof(ulong))
{
schema.Minimum = 0;
schema.Maximum = ulong.MaxValue;
} else if (underlyingType == typeof(UInt128))
{
schema.Minimum = 0;
// OpenApiMaximum property is too small to represent UInt128.MaxValue
//schema.Maximum = UInt128.MaxValue;
}

}

return schema;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void GenerateSchema_GeneratesFileSchema_IfFormFileOrFileResultType(Type t
[InlineData(typeof(short), "integer", "int32", false)]
[InlineData(typeof(ushort), "integer", "int32", false)]
[InlineData(typeof(int), "integer", "int32", false)]
[InlineData(typeof(uint), "integer", "int32", false)]
[InlineData(typeof(uint), "integer", "int64", false)]
[InlineData(typeof(long), "integer", "int64", false)]
[InlineData(typeof(ulong), "integer", "int64", false)]
[InlineData(typeof(float), "number", "float", false)]
Expand All @@ -67,6 +67,7 @@ public void GenerateSchema_GeneratesFileSchema_IfFormFileOrFileResultType(Type t
[InlineData(typeof(Version), "string", null, false)]
[InlineData(typeof(bool?), "boolean", null, true)]
[InlineData(typeof(int?), "integer", "int32", true)]
[InlineData(typeof(uint?), "integer", "int64", true)]
[InlineData(typeof(Int128?), "integer", "int128", true)]
[InlineData(typeof(UInt128?), "integer", "int128", true)]
[InlineData(typeof(DateTime?), "string", "date-time", true)]
Expand Down Expand Up @@ -125,6 +126,51 @@ bool expectedRequired
Assert.Equal(expectedRequired, schema.Required.Contains(propertyName));
}

[Theory]
[InlineData(typeof(ushort), "integer", "int32", ushort.MaxValue, false)]
[InlineData(typeof(uint), "integer", "int64", uint.MaxValue, false)]
[InlineData(typeof(ulong), "integer", "int64", ulong.MaxValue, false)]
[InlineData(typeof(ushort?), "integer", "int32", ushort.MaxValue, true)]
[InlineData(typeof(uint?), "integer", "int64", uint.MaxValue, true)]
[InlineData(typeof(ulong?), "integer", "int64", ulong.MaxValue, true)]
public void UnsignedIntegerTypes_HaveMinAndMax_DefinedInSchema(
Type type,
string schemaType,
string schemaFormat,
decimal max,
bool nullable
)
{
var schema = Subject().GenerateSchema(type, new SchemaRepository());

Assert.Equal(schemaType, schema.Type);
Assert.Equal(schemaFormat, schema.Format);
Assert.Equal(0, schema.Minimum);
Assert.Equal(max, schema.Maximum);
Assert.Equal(nullable, schema.Nullable);
}

[Fact]
public void UInt128_HaveMinAndMax_DefinedInSchema()
{
var schema = Subject().GenerateSchema(typeof(UInt128), new SchemaRepository());

Assert.Equal("integer", schema.Type);
Assert.Equal("int128", schema.Format);
Assert.Equal(0, schema.Minimum);
Assert.Equal(null, schema.Maximum);
Assert.Equal(false, schema.Nullable);


schema = Subject().GenerateSchema(typeof(UInt128?), new SchemaRepository());

Assert.Equal("integer", schema.Type);
Assert.Equal("int128", schema.Format);
Assert.Equal(0, schema.Minimum);
Assert.Equal(null, schema.Maximum);
Assert.Equal(true, schema.Nullable);
}

[Theory]
[InlineData(typeof(IntEnum), "integer", "int32", "2", "4", "8")]
[InlineData(typeof(LongEnum), "integer", "int64", "2", "4", "8")]
Expand Down

0 comments on commit f354f74

Please sign in to comment.