Skip to content

Commit f352c16

Browse files
committed
Fix OpenApiPayloadAttributeExtension #107 #112 #113
1 parent 60145a1 commit f352c16

File tree

12 files changed

+201
-71
lines changed

12 files changed

+201
-71
lines changed

samples/Aliencube.AzureFunctions.FunctionAppV3IoC/DummyHttpTrigger.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public DummyHttpTrigger(IDummyHttpService service)
3131
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Summary = "Dummy name", Description = "Dummy name", Visibility = OpenApiVisibilityType.Important)]
3232
[OpenApiParameter(name: "switch", In = ParameterLocation.Path, Required = true, Type = typeof(StringEnum), Summary = "Dummy switch", Description = "Dummy switch", Visibility = OpenApiVisibilityType.Important)]
3333
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<DummyResponseModel>), Summary = "List of the dummy responses", Description = "This returns the list of dummy responses")]
34+
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(string), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
3435
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NotFound, Summary = "Name not found", Description = "Name parameter is not found")]
35-
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.BadRequest, Summary = "Invalid switch", Description = "Switch parameter is not valid")]
3636
public async Task<IActionResult> GetDummies(
3737
[HttpTrigger(AuthorizationLevel.Function, "GET", Route = "dummies")] HttpRequest req,
3838
ILogger log)
@@ -65,6 +65,7 @@ public async Task<IActionResult> AddDummy(
6565
[OpenApiOperation(operationId: "updateDummies", tags: new[] { "dummy" }, Summary = "Updates a list of dummies", Description = "This updates a list of dummies.", Visibility = OpenApiVisibilityType.Advanced)]
6666
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(DummyListModel), Required = true, Description = "Dummy list model")]
6767
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<DummyStringModel>), Summary = "Dummy response", Description = "This returns the dummy response")]
68+
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(List<int?>), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
6869
public async Task<IActionResult> UpdateDummies(
6970
[HttpTrigger(AuthorizationLevel.Function, "PUT", Route = "dummies")] HttpRequest req,
7071
ILogger log)

samples/Aliencube.AzureFunctions.FunctionAppV3Static/DummyHttpTrigger.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public static class DummyHttpTrigger
2222
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Summary = "Dummy name", Description = "Dummy name", Visibility = OpenApiVisibilityType.Important)]
2323
[OpenApiParameter(name: "switch", In = ParameterLocation.Path, Required = true, Type = typeof(StringEnum), Summary = "Dummy switch", Description = "Dummy switch", Visibility = OpenApiVisibilityType.Important)]
2424
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<DummyResponseModel>), Summary = "List of the dummy responses", Description = "This returns the list of dummy responses")]
25+
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(string), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
2526
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NotFound, Summary = "Name not found", Description = "Name parameter is not found")]
26-
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.BadRequest, Summary = "Invalid switch", Description = "Switch parameter is not valid")]
2727
public static async Task<IActionResult> GetDummies(
2828
[HttpTrigger(AuthorizationLevel.Function, "GET", Route = "dummies")] HttpRequest req,
2929
ILogger log)
@@ -56,6 +56,7 @@ public static async Task<IActionResult> AddDummy(
5656
[OpenApiOperation(operationId: "updateDummies", tags: new[] { "dummy" }, Summary = "Updates a list of dummies", Description = "This updates a list of dummies.", Visibility = OpenApiVisibilityType.Advanced)]
5757
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(DummyListModel), Required = true, Description = "Dummy list model")]
5858
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<DummyStringModel>), Summary = "Dummy response", Description = "This returns the dummy response")]
59+
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(List<int?>), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
5960
public static async Task<IActionResult> UpdateDummies(
6061
[HttpTrigger(AuthorizationLevel.Function, "PUT", Route = "dummies")] HttpRequest req,
6162
ILogger log)

src/Aliencube.AzureFunctions.Extensions.OpenApi.Core/Extensions/OpenApiPayloadAttributeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static OpenApiMediaType ToOpenApiMediaType<T>(this T attribute, NamingStr
4040
var schema = collection.PayloadVisit(type, namingStrategy);
4141

4242
// For array and dictionary object, the reference has already been added by the visitor.
43-
if (!type.IsOpenApiArray() && !type.IsOpenApiDictionary())
43+
if (type.IsReferentialType() && !type.IsOpenApiNullable() && !type.IsOpenApiArray() && !type.IsOpenApiDictionary())
4444
{
4545
var reference = new OpenApiReference()
4646
{

src/Aliencube.AzureFunctions.Extensions.OpenApi.Core/Extensions/TypeExtensions.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,45 @@ public static bool IsSimpleType(this Type type)
6868
}
6969
}
7070

71+
72+
/// <summary>
73+
/// Checks whether the type can be referenced or not.
74+
/// </summary>
75+
/// <param name="type">Type to check.</param>
76+
/// <returns>Returns <c>True</c>, if the type can be referenced; otherwise returns <c>False</c>.</returns>
77+
public static bool IsReferentialType(this Type type)
78+
{
79+
var @enum = Type.GetTypeCode(type);
80+
var isReferential = @enum == TypeCode.Object;
81+
82+
if (type == typeof(Guid))
83+
{
84+
isReferential = false;
85+
}
86+
if (type == typeof(DateTime))
87+
{
88+
isReferential = false;
89+
}
90+
if (type == typeof(DateTimeOffset))
91+
{
92+
isReferential = false;
93+
}
94+
if (type.IsOpenApiNullable())
95+
{
96+
isReferential = false;
97+
}
98+
if (type.IsUnflaggedEnumType())
99+
{
100+
isReferential = false;
101+
}
102+
if (type.IsJObjectType())
103+
{
104+
isReferential = false;
105+
}
106+
107+
return isReferential;
108+
}
109+
71110
/// <summary>
72111
/// Checks whether the given type is Json.NET related <see cref="JObject"/>, <see cref="JToken"/> or not.
73112
/// </summary>

src/Aliencube.AzureFunctions.Extensions.OpenApi.Core/Visitors/DictionaryObjectTypeVisitor.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,20 @@ public override OpenApiSchema PayloadVisit(Type type, NamingStrategy namingStrat
124124
var schema = this.PayloadVisit(dataType: "object", dataFormat: null);
125125

126126
// Gets the schema for the underlying type.
127-
var underlyingType = type.GetGenericArguments()[1];
127+
var underlyingType = type.GetUnderlyingType();
128128
var properties = this.VisitorCollection.PayloadVisit(underlyingType, namingStrategy);
129129

130130
// Adds the reference to the schema for the underlying type.
131-
var reference = new OpenApiReference()
131+
if (underlyingType.IsReferentialType())
132132
{
133-
Type = ReferenceType.Schema,
134-
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
135-
};
133+
var reference = new OpenApiReference()
134+
{
135+
Type = ReferenceType.Schema,
136+
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
137+
};
136138

137-
properties.Reference = reference;
139+
properties.Reference = reference;
140+
}
138141

139142
schema.AdditionalProperties = properties;
140143

src/Aliencube.AzureFunctions.Extensions.OpenApi.Core/Visitors/ListObjectTypeVisitor.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public override OpenApiSchema ParameterVisit(Type type, NamingStrategy namingStr
117117
{
118118
var schema = this.ParameterVisit(dataType: "array", dataFormat: null);
119119

120-
var underlyingType = type.GetElementType() ?? type.GetGenericArguments()[0];
120+
var underlyingType = type.GetUnderlyingType();
121121
var items = this.VisitorCollection.ParameterVisit(underlyingType, namingStrategy);
122122

123123
schema.Items = items;
@@ -139,17 +139,20 @@ public override OpenApiSchema PayloadVisit(Type type, NamingStrategy namingStrat
139139
var schema = this.PayloadVisit(dataType: "array", dataFormat: null);
140140

141141
// Gets the schema for the underlying type.
142-
var underlyingType = type.GetElementType() ?? type.GetGenericArguments()[0];
142+
var underlyingType = type.GetUnderlyingType();
143143
var items = this.VisitorCollection.PayloadVisit(underlyingType, namingStrategy);
144144

145145
// Adds the reference to the schema for the underlying type.
146-
var reference = new OpenApiReference()
146+
if (underlyingType.IsReferentialType())
147147
{
148-
Type = ReferenceType.Schema,
149-
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
150-
};
148+
var reference = new OpenApiReference()
149+
{
150+
Type = ReferenceType.Schema,
151+
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
152+
};
151153

152-
items.Reference = reference;
154+
items.Reference = reference;
155+
}
153156

154157
schema.Items = items;
155158

src/Aliencube.AzureFunctions.Extensions.OpenApi.Core/Visitors/NullableObjectTypeVisitor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public override bool IsParameterVisitable(Type type)
8888
/// <inheritdoc />
8989
public override OpenApiSchema ParameterVisit(Type type, NamingStrategy namingStrategy)
9090
{
91-
type.IsOpenApiNullable(out var underlyingType);
91+
var underlyingType = type.GetUnderlyingType();
9292
var schema = this.VisitorCollection.ParameterVisit(underlyingType, namingStrategy);
9393

9494
schema.Nullable = true;
@@ -107,7 +107,7 @@ public override bool IsPayloadVisitable(Type type)
107107
/// <inheritdoc />
108108
public override OpenApiSchema PayloadVisit(Type type, NamingStrategy namingStrategy)
109109
{
110-
type.IsOpenApiNullable(out var underlyingType);
110+
var underlyingType = type.GetUnderlyingType();
111111
var schema = this.VisitorCollection.PayloadVisit(underlyingType, namingStrategy);
112112

113113
schema.Nullable = true;

src/Aliencube.AzureFunctions.Extensions.OpenApi.Core/Visitors/TypeVisitor.cs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -105,33 +105,7 @@ protected bool IsVisitable(Type type, TypeCode code)
105105
/// <returns>Returns <c>True</c>, if the type can be referenced; otherwise returns <c>False</c>.</returns>
106106
protected bool IsReferential(Type type)
107107
{
108-
var @enum = Type.GetTypeCode(type);
109-
var isReferential = @enum == TypeCode.Object;
110-
111-
if (type == typeof(Guid))
112-
{
113-
isReferential = false;
114-
}
115-
if (type == typeof(DateTime))
116-
{
117-
isReferential = false;
118-
}
119-
if (type == typeof(DateTimeOffset))
120-
{
121-
isReferential = false;
122-
}
123-
if (type.IsOpenApiNullable())
124-
{
125-
isReferential = false;
126-
}
127-
if (type.IsUnflaggedEnumType())
128-
{
129-
isReferential = false;
130-
}
131-
if (type.IsJObjectType())
132-
{
133-
isReferential = false;
134-
}
108+
var isReferential = type.IsReferentialType();
135109

136110
return isReferential;
137111
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
5+
using Aliencube.AzureFunctions.Extensions.OpenApi.Core.Attributes;
6+
using Aliencube.AzureFunctions.Extensions.OpenApi.Core.Extensions;
7+
using Aliencube.AzureFunctions.Extensions.OpenApi.Core.Tests.Fakes;
8+
9+
using FluentAssertions;
10+
11+
using Microsoft.VisualStudio.TestTools.UnitTesting;
12+
13+
using Newtonsoft.Json.Serialization;
14+
15+
namespace Aliencube.AzureFunctions.Extensions.OpenApi.Core.Tests.Extensions
16+
{
17+
[TestClass]
18+
public class OpenApiPayloadAttributeExtensionsTests
19+
{
20+
[TestMethod]
21+
public void Given_Null_When_ToOpenApiMediaType_Invoked_Then_It_Should_Throw_Exception()
22+
{
23+
Action action = () => OpenApiPayloadAttributeExtensions.ToOpenApiMediaType((OpenApiPayloadAttribute)null);
24+
25+
action.Should().Throw<ArgumentNullException>();
26+
}
27+
28+
[DataTestMethod]
29+
[DataRow(typeof(string), "string")]
30+
[DataRow(typeof(FakeModel), "object")]
31+
public void Given_OpenApiRequestBodyAttribute_When_ToOpenApiMediaType_Invoked_Then_It_Should_Return_Result(Type bodyType, string expected)
32+
{
33+
var contentType = "application/json";
34+
var attribute = new OpenApiRequestBodyAttribute(contentType, bodyType)
35+
{
36+
Required = true,
37+
Description = "Dummy request model"
38+
};
39+
var namingStrategy = new CamelCaseNamingStrategy();
40+
41+
var result = OpenApiPayloadAttributeExtensions.ToOpenApiMediaType(attribute, namingStrategy);
42+
43+
result.Schema.Type.Should().Be(expected);
44+
}
45+
46+
[DataTestMethod]
47+
[DataRow(typeof(string), "string", false, false, null)]
48+
[DataRow(typeof(FakeModel), "object", false, false, null)]
49+
[DataRow(typeof(List<string>), "array", true, false, "string")]
50+
[DataRow(typeof(Dictionary<string, int?>), "object", false, true, "integer")]
51+
public void Given_OpenApiResponseWithBodyAttribute_When_ToOpenApiMediaType_Invoked_Then_It_Should_Return_Result(Type bodyType, string expected, bool items, bool additionalProperties, string underlyingType)
52+
{
53+
var statusCode = HttpStatusCode.OK;
54+
var contentType = "application/json";
55+
var attribute = new OpenApiResponseWithBodyAttribute(statusCode, contentType, bodyType);
56+
var namingStrategy = new CamelCaseNamingStrategy();
57+
58+
var result = OpenApiPayloadAttributeExtensions.ToOpenApiMediaType(attribute, namingStrategy);
59+
60+
result.Schema.Type.Should().Be(expected);
61+
if (items)
62+
{
63+
result.Schema.Items.Should().NotBeNull();
64+
result.Schema.Items.Type.Should().Be(underlyingType);
65+
}
66+
else
67+
{
68+
result.Schema.Items.Should().BeNull();
69+
}
70+
71+
if (additionalProperties)
72+
{
73+
result.Schema.AdditionalProperties.Should().NotBeNull();
74+
result.Schema.AdditionalProperties.Type.Should().Be(underlyingType);
75+
}
76+
else
77+
{
78+
result.Schema.AdditionalProperties.Should().BeNull();
79+
}
80+
}
81+
}
82+
}

test/Aliencube.AzureFunctions.Extensions.OpenApi.Core.Tests/Extensions/TypeExtensionsTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,18 @@ public void Given_NullableDictionaryType_When_GetUnderlyingType_Invoked_Then_It_
125125

126126
result.Should().Be(expected);
127127
}
128+
129+
[DataTestMethod]
130+
[DataRow(typeof(int), false)]
131+
[DataRow(typeof(int?), false)]
132+
[DataRow(typeof(List<int>), true)]
133+
[DataRow(typeof(FakeModel), true)]
134+
[DataRow(typeof(JObject), false)]
135+
public void Given_Type_When_IsReferentialType_Invoked_Then_It_Should_Return_Result(Type type, bool expected)
136+
{
137+
var result = TypeExtensions.IsReferentialType(type);
138+
139+
result.Should().Be(expected);
140+
}
128141
}
129142
}

0 commit comments

Comments
 (0)