Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Routing.Constraints;
Expand Down Expand Up @@ -60,6 +61,13 @@ public static void ApplyValidationAttributes(this OpenApiSchema schema, IEnumera

else if (attribute is StringLengthAttribute stringLengthAttribute)
ApplyStringLengthAttribute(schema, stringLengthAttribute);

else if (attribute is ReadOnlyAttribute readOnlyAttribute)
ApplyReadOnlyAttribute(schema, readOnlyAttribute);

else if (attribute is DescriptionAttribute descriptionAttribute)
ApplyDescriptionAttribute(schema, descriptionAttribute);

}
}

Expand Down Expand Up @@ -230,6 +238,16 @@ private static void ApplyStringLengthAttribute(OpenApiSchema schema, StringLengt
schema.MaxLength = stringLengthAttribute.MaximumLength;
}

private static void ApplyReadOnlyAttribute(OpenApiSchema schema, ReadOnlyAttribute readOnlyAttribute)
{
schema.ReadOnly = readOnlyAttribute.IsReadOnly;
}

private static void ApplyDescriptionAttribute(OpenApiSchema schema, DescriptionAttribute descriptionAttribute)
{
schema.Description ??= descriptionAttribute.Description;
}

private static void ApplyLengthRouteConstraint(OpenApiSchema schema, LengthRouteConstraint lengthRouteConstraint)
{
schema.MinLength = lengthRouteConstraint.MinLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@
"properties": {
"currencyFrom": {
"type": "string",
"description": "Currency From",
"nullable": true
},
"currencyTo": {
Expand Down Expand Up @@ -1017,6 +1018,10 @@
"$ref": "#/components/schemas/CurrenciesRate"
},
"nullable": true
},
"isUpdated": {
"type": "boolean",
"readOnly": true
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@
"properties": {
"currencyFrom": {
"type": "string",
"description": "Currency From",
"nullable": true
},
"currencyTo": {
Expand Down Expand Up @@ -1017,6 +1018,10 @@
"$ref": "#/components/schemas/CurrenciesRate"
},
"nullable": true
},
"isUpdated": {
"type": "boolean",
"readOnly": true
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
using Microsoft.OpenApi.Models;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures;
using Swashbuckle.AspNetCore.TestSupport;
using Xunit;
using Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures;

namespace Swashbuckle.AspNetCore.Newtonsoft.Test
{
Expand Down Expand Up @@ -340,7 +340,9 @@ public void GenerateSchema_SetsValidationProperties_IfComplexTypeHasValidationAt
Assert.False(schema.Properties["StringWithRequired"].Nullable);
Assert.False(schema.Properties["StringWithRequiredAllowEmptyTrue"].Nullable);
Assert.Null(schema.Properties["StringWithRequiredAllowEmptyTrue"].MinLength);
Assert.Equal(new[] { "StringWithRequired", "StringWithRequiredAllowEmptyTrue" }, schema.Required.ToArray());
Assert.Equal(["StringWithRequired", "StringWithRequiredAllowEmptyTrue"], schema.Required);
Assert.Equal("Description", schema.Properties[nameof(TypeWithValidationAttributes.StringWithDescription)].Description);
Assert.True(schema.Properties[nameof(TypeWithValidationAttributes.StringWithReadOnly)].ReadOnly);
}

[Fact]
Expand Down Expand Up @@ -736,7 +738,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonIgnore()
var referenceSchema = Subject().GenerateSchema(typeof(JsonIgnoreAnnotatedType), schemaRepository);

var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(new[] { /* "StringWithJsonIgnore" */ "StringWithNoAnnotation" }, schema.Properties.Keys.ToArray());
Assert.Equal(["StringWithNoAnnotation"], schema.Properties.Keys);
}

[Fact]
Expand All @@ -748,8 +750,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonProperty()

var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(
new[]
{
[
"string-with-json-property-name",
"IntWithRequiredDefault",
"StringWithRequiredDefault",
Expand All @@ -758,17 +759,16 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonProperty()
"StringWithRequiredAllowNull",
"StringWithRequiredAlwaysButConflictingDataMember",
"StringWithRequiredDefaultButConflictingDataMember"
},
schema.Properties.Keys.ToArray()
],
schema.Properties.Keys
);
Assert.Equal(
new[]
{
[
"StringWithRequiredAllowNull",
"StringWithRequiredAlways",
"StringWithRequiredAlwaysButConflictingDataMember"
},
schema.Required.ToArray()
],
schema.Required
);
Assert.True(schema.Properties["string-with-json-property-name"].Nullable);
Assert.False(schema.Properties["IntWithRequiredDefault"].Nullable);
Expand All @@ -788,7 +788,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonRequired()
var referenceSchema = Subject().GenerateSchema(typeof(JsonRequiredAnnotatedType), schemaRepository);

var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(new[] { "StringWithConflictingRequired", "StringWithJsonRequired"}, schema.Required.ToArray());
Assert.Equal(["StringWithConflictingRequired", "StringWithJsonRequired"], schema.Required);
Assert.False(schema.Properties["StringWithJsonRequired"].Nullable);
}

Expand All @@ -801,14 +801,13 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonObject()

var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(
new[]
{
[
"StringWithDataMemberRequiredFalse",
"StringWithNoAnnotation",
"StringWithRequiredAllowNull",
"StringWithRequiredUnspecified"
},
schema.Required.ToArray()
],
schema.Required
);
Assert.False(schema.Properties["StringWithNoAnnotation"].Nullable);
Assert.False(schema.Properties["StringWithRequiredUnspecified"].Nullable);
Expand Down Expand Up @@ -861,24 +860,22 @@ public void GenerateSchema_HonorsDataMemberAttribute()
Assert.True(schema.Properties["NonRequiredWithCustomNameFromDataMember"].Nullable);

Assert.Equal(
new[]
{
[

"StringWithDataMemberRequired",
"StringWithDataMemberNonRequired",
"RequiredWithCustomNameFromDataMember",
"NonRequiredWithCustomNameFromDataMember"
},
schema.Properties.Keys.ToArray()
],
schema.Properties.Keys
);

Assert.Equal(
new[]
{
[
"RequiredWithCustomNameFromDataMember",
"StringWithDataMemberRequired"
},
schema.Required.ToArray()
],
schema.Required
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,9 @@ public void GenerateSchema_SetsValidationProperties_IfComplexTypeHasValidationAt
Assert.False(schema.Properties["StringWithRequired"].Nullable);
Assert.False(schema.Properties["StringWithRequiredAllowEmptyTrue"].Nullable);
Assert.Null(schema.Properties["StringWithRequiredAllowEmptyTrue"].MinLength);
Assert.Equal(new[] { "StringWithRequired", "StringWithRequiredAllowEmptyTrue" }, schema.Required.ToArray());
Assert.Equal(["StringWithRequired", "StringWithRequiredAllowEmptyTrue"], schema.Required);
Assert.Equal("Description", schema.Properties[nameof(TypeWithValidationAttributes.StringWithDescription)].Description);
Assert.True(schema.Properties[nameof(TypeWithValidationAttributes.StringWithReadOnly)].ReadOnly);
}

[Fact]
Expand Down Expand Up @@ -420,7 +422,7 @@ public void GenerateSchema_SetsRequired_IfPropertyHasRequiredKeywordAndValidatio
var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(1, schema.Properties["RequiredProperty"].MinLength);
Assert.True(schema.Properties["RequiredProperty"].Nullable);
Assert.Equal(new[] { "RequiredProperty" }, schema.Required.ToArray());
Assert.Equal(["RequiredProperty"], schema.Required);
}

#nullable enable
Expand Down Expand Up @@ -1153,14 +1155,14 @@ public void GenerateSchema_HonorsSerializerAttributes_JsonIgnore()
var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];

string[] expectedKeys =
{
[
nameof(JsonIgnoreAnnotatedType.StringWithJsonIgnoreConditionNever),
nameof(JsonIgnoreAnnotatedType.StringWithJsonIgnoreConditionWhenWritingDefault),
nameof(JsonIgnoreAnnotatedType.StringWithJsonIgnoreConditionWhenWritingNull),
nameof(JsonIgnoreAnnotatedType.StringWithNoAnnotation)
};
];

Assert.Equal(expectedKeys, schema.Properties.Keys.ToArray());
Assert.Equal(expectedKeys, schema.Properties.Keys);
}

[Fact]
Expand All @@ -1171,7 +1173,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonPropertyName()
var referenceSchema = Subject().GenerateSchema(typeof(JsonPropertyNameAnnotatedType), schemaRepository);

var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(new[] { "string-with-json-property-name" }, schema.Properties.Keys.ToArray());
Assert.Equal(["string-with-json-property-name"], schema.Properties.Keys);
}

#if NET7_0_OR_GREATER
Expand All @@ -1183,7 +1185,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonRequired()
var referenceSchema = Subject().GenerateSchema(typeof(JsonRequiredAnnotatedType), schemaRepository);

var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
Assert.Equal(new[] { "StringWithJsonRequired" }, schema.Required.ToArray());
Assert.Equal(["StringWithJsonRequired"], schema.Required);
Assert.True(schema.Properties["StringWithJsonRequired"].Nullable);
}
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Swashbuckle.AspNetCore.TestSupport
{
Expand Down Expand Up @@ -43,5 +44,11 @@ public class TypeWithValidationAttributes

[Required(AllowEmptyStrings = true)]
public string StringWithRequiredAllowEmptyTrue { get; set; }

[Description("Description")]
public string StringWithDescription { get; set; }

[ReadOnly(true)]
public string StringWithReadOnly { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

namespace Swashbuckle.AspNetCore.TestSupport
Expand Down Expand Up @@ -33,6 +34,10 @@ public class TypeWithValidationAttributesViaMetadataType
public string StringWithRequired { get; set; }

public string StringWithRequiredAllowEmptyTrue { get; set; }

public string StringWithDescription { get; set; }

public string StringWithReadOnly { get; set; }
}

public class MetadataType
Expand Down Expand Up @@ -76,5 +81,11 @@ public class MetadataType

[Required(AllowEmptyStrings = true)]
public string StringWithRequiredAllowEmptyTrue { get; set; }

[Description("Description")]
public string StringWithDescription { get; set; }

[ReadOnly(true)]
public string StringWithReadOnly { get; set; }
}
}
7 changes: 4 additions & 3 deletions test/WebSites/WebApi/EndPoints/OpenApiEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;

namespace WebApi.EndPoints
Expand Down Expand Up @@ -95,8 +96,8 @@ record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
record class Person(string FirstName, string LastName);

record class Address(string Street, string City, string State, string ZipCode);
sealed record OrganizationCustomExchangeRatesDto([property: JsonRequired] CurrenciesRate[] CurrenciesRates);
sealed record CurrenciesRate([property: JsonRequired] string CurrencyFrom, [property: JsonRequired] string CurrencyTo, double Rate);
sealed record OrganizationCustomExchangeRatesDto([property: JsonRequired] CurrenciesRate[] CurrenciesRates, [property: ReadOnly(true)] bool IsUpdated);
sealed record CurrenciesRate([property: JsonRequired, Description("Currency From")] string CurrencyFrom, [property: JsonRequired] string CurrencyTo, double Rate);
record TypeWithTryParse(string Name)
{
public static bool TryParse(string value, out TypeWithTryParse? result)
Expand Down