Skip to content

Commit

Permalink
Fix OpenApiPayloadAttributeExtension #107 #112 #113
Browse files Browse the repository at this point in the history
  • Loading branch information
justinyoo committed Aug 31, 2020
1 parent 60145a1 commit f352c16
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public DummyHttpTrigger(IDummyHttpService service)
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Summary = "Dummy name", Description = "Dummy name", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "switch", In = ParameterLocation.Path, Required = true, Type = typeof(StringEnum), Summary = "Dummy switch", Description = "Dummy switch", Visibility = OpenApiVisibilityType.Important)]
[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")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(string), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NotFound, Summary = "Name not found", Description = "Name parameter is not found")]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.BadRequest, Summary = "Invalid switch", Description = "Switch parameter is not valid")]
public async Task<IActionResult> GetDummies(
[HttpTrigger(AuthorizationLevel.Function, "GET", Route = "dummies")] HttpRequest req,
ILogger log)
Expand Down Expand Up @@ -65,6 +65,7 @@ public async Task<IActionResult> AddDummy(
[OpenApiOperation(operationId: "updateDummies", tags: new[] { "dummy" }, Summary = "Updates a list of dummies", Description = "This updates a list of dummies.", Visibility = OpenApiVisibilityType.Advanced)]
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(DummyListModel), Required = true, Description = "Dummy list model")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<DummyStringModel>), Summary = "Dummy response", Description = "This returns the dummy response")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(List<int?>), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
public async Task<IActionResult> UpdateDummies(
[HttpTrigger(AuthorizationLevel.Function, "PUT", Route = "dummies")] HttpRequest req,
ILogger log)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static class DummyHttpTrigger
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Summary = "Dummy name", Description = "Dummy name", Visibility = OpenApiVisibilityType.Important)]
[OpenApiParameter(name: "switch", In = ParameterLocation.Path, Required = true, Type = typeof(StringEnum), Summary = "Dummy switch", Description = "Dummy switch", Visibility = OpenApiVisibilityType.Important)]
[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")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(string), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NotFound, Summary = "Name not found", Description = "Name parameter is not found")]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.BadRequest, Summary = "Invalid switch", Description = "Switch parameter is not valid")]
public static async Task<IActionResult> GetDummies(
[HttpTrigger(AuthorizationLevel.Function, "GET", Route = "dummies")] HttpRequest req,
ILogger log)
Expand Down Expand Up @@ -56,6 +56,7 @@ public static async Task<IActionResult> AddDummy(
[OpenApiOperation(operationId: "updateDummies", tags: new[] { "dummy" }, Summary = "Updates a list of dummies", Description = "This updates a list of dummies.", Visibility = OpenApiVisibilityType.Advanced)]
[OpenApiRequestBody(contentType: "application/json", bodyType: typeof(DummyListModel), Required = true, Description = "Dummy list model")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<DummyStringModel>), Summary = "Dummy response", Description = "This returns the dummy response")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(List<int?>), Summary = "Invalid switch", Description = "Switch parameter is not valid")]
public static async Task<IActionResult> UpdateDummies(
[HttpTrigger(AuthorizationLevel.Function, "PUT", Route = "dummies")] HttpRequest req,
ILogger log)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static OpenApiMediaType ToOpenApiMediaType<T>(this T attribute, NamingStr
var schema = collection.PayloadVisit(type, namingStrategy);

// For array and dictionary object, the reference has already been added by the visitor.
if (!type.IsOpenApiArray() && !type.IsOpenApiDictionary())
if (type.IsReferentialType() && !type.IsOpenApiNullable() && !type.IsOpenApiArray() && !type.IsOpenApiDictionary())
{
var reference = new OpenApiReference()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,45 @@ public static bool IsSimpleType(this Type type)
}
}


/// <summary>
/// Checks whether the type can be referenced or not.
/// </summary>
/// <param name="type">Type to check.</param>
/// <returns>Returns <c>True</c>, if the type can be referenced; otherwise returns <c>False</c>.</returns>
public static bool IsReferentialType(this Type type)
{
var @enum = Type.GetTypeCode(type);
var isReferential = @enum == TypeCode.Object;

if (type == typeof(Guid))
{
isReferential = false;
}
if (type == typeof(DateTime))
{
isReferential = false;
}
if (type == typeof(DateTimeOffset))
{
isReferential = false;
}
if (type.IsOpenApiNullable())
{
isReferential = false;
}
if (type.IsUnflaggedEnumType())
{
isReferential = false;
}
if (type.IsJObjectType())
{
isReferential = false;
}

return isReferential;
}

/// <summary>
/// Checks whether the given type is Json.NET related <see cref="JObject"/>, <see cref="JToken"/> or not.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,20 @@ public override OpenApiSchema PayloadVisit(Type type, NamingStrategy namingStrat
var schema = this.PayloadVisit(dataType: "object", dataFormat: null);

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

// Adds the reference to the schema for the underlying type.
var reference = new OpenApiReference()
if (underlyingType.IsReferentialType())
{
Type = ReferenceType.Schema,
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
};
var reference = new OpenApiReference()
{
Type = ReferenceType.Schema,
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
};

properties.Reference = reference;
properties.Reference = reference;
}

schema.AdditionalProperties = properties;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public override OpenApiSchema ParameterVisit(Type type, NamingStrategy namingStr
{
var schema = this.ParameterVisit(dataType: "array", dataFormat: null);

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

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

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

// Adds the reference to the schema for the underlying type.
var reference = new OpenApiReference()
if (underlyingType.IsReferentialType())
{
Type = ReferenceType.Schema,
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
};
var reference = new OpenApiReference()
{
Type = ReferenceType.Schema,
Id = underlyingType.GetOpenApiReferenceId(isDictionary: false, isList: false, namingStrategy)
};

items.Reference = reference;
items.Reference = reference;
}

schema.Items = items;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public override bool IsParameterVisitable(Type type)
/// <inheritdoc />
public override OpenApiSchema ParameterVisit(Type type, NamingStrategy namingStrategy)
{
type.IsOpenApiNullable(out var underlyingType);
var underlyingType = type.GetUnderlyingType();
var schema = this.VisitorCollection.ParameterVisit(underlyingType, namingStrategy);

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

schema.Nullable = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,33 +105,7 @@ protected bool IsVisitable(Type type, TypeCode code)
/// <returns>Returns <c>True</c>, if the type can be referenced; otherwise returns <c>False</c>.</returns>
protected bool IsReferential(Type type)
{
var @enum = Type.GetTypeCode(type);
var isReferential = @enum == TypeCode.Object;

if (type == typeof(Guid))
{
isReferential = false;
}
if (type == typeof(DateTime))
{
isReferential = false;
}
if (type == typeof(DateTimeOffset))
{
isReferential = false;
}
if (type.IsOpenApiNullable())
{
isReferential = false;
}
if (type.IsUnflaggedEnumType())
{
isReferential = false;
}
if (type.IsJObjectType())
{
isReferential = false;
}
var isReferential = type.IsReferentialType();

return isReferential;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Net;

using Aliencube.AzureFunctions.Extensions.OpenApi.Core.Attributes;
using Aliencube.AzureFunctions.Extensions.OpenApi.Core.Extensions;
using Aliencube.AzureFunctions.Extensions.OpenApi.Core.Tests.Fakes;

using FluentAssertions;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Newtonsoft.Json.Serialization;

namespace Aliencube.AzureFunctions.Extensions.OpenApi.Core.Tests.Extensions
{
[TestClass]
public class OpenApiPayloadAttributeExtensionsTests
{
[TestMethod]
public void Given_Null_When_ToOpenApiMediaType_Invoked_Then_It_Should_Throw_Exception()
{
Action action = () => OpenApiPayloadAttributeExtensions.ToOpenApiMediaType((OpenApiPayloadAttribute)null);

action.Should().Throw<ArgumentNullException>();
}

[DataTestMethod]
[DataRow(typeof(string), "string")]
[DataRow(typeof(FakeModel), "object")]
public void Given_OpenApiRequestBodyAttribute_When_ToOpenApiMediaType_Invoked_Then_It_Should_Return_Result(Type bodyType, string expected)
{
var contentType = "application/json";
var attribute = new OpenApiRequestBodyAttribute(contentType, bodyType)
{
Required = true,
Description = "Dummy request model"
};
var namingStrategy = new CamelCaseNamingStrategy();

var result = OpenApiPayloadAttributeExtensions.ToOpenApiMediaType(attribute, namingStrategy);

result.Schema.Type.Should().Be(expected);
}

[DataTestMethod]
[DataRow(typeof(string), "string", false, false, null)]
[DataRow(typeof(FakeModel), "object", false, false, null)]
[DataRow(typeof(List<string>), "array", true, false, "string")]
[DataRow(typeof(Dictionary<string, int?>), "object", false, true, "integer")]
public void Given_OpenApiResponseWithBodyAttribute_When_ToOpenApiMediaType_Invoked_Then_It_Should_Return_Result(Type bodyType, string expected, bool items, bool additionalProperties, string underlyingType)
{
var statusCode = HttpStatusCode.OK;
var contentType = "application/json";
var attribute = new OpenApiResponseWithBodyAttribute(statusCode, contentType, bodyType);
var namingStrategy = new CamelCaseNamingStrategy();

var result = OpenApiPayloadAttributeExtensions.ToOpenApiMediaType(attribute, namingStrategy);

result.Schema.Type.Should().Be(expected);
if (items)
{
result.Schema.Items.Should().NotBeNull();
result.Schema.Items.Type.Should().Be(underlyingType);
}
else
{
result.Schema.Items.Should().BeNull();
}

if (additionalProperties)
{
result.Schema.AdditionalProperties.Should().NotBeNull();
result.Schema.AdditionalProperties.Type.Should().Be(underlyingType);
}
else
{
result.Schema.AdditionalProperties.Should().BeNull();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,18 @@ public void Given_NullableDictionaryType_When_GetUnderlyingType_Invoked_Then_It_

result.Should().Be(expected);
}

[DataTestMethod]
[DataRow(typeof(int), false)]
[DataRow(typeof(int?), false)]
[DataRow(typeof(List<int>), true)]
[DataRow(typeof(FakeModel), true)]
[DataRow(typeof(JObject), false)]
public void Given_Type_When_IsReferentialType_Invoked_Then_It_Should_Return_Result(Type type, bool expected)
{
var result = TypeExtensions.IsReferentialType(type);

result.Should().Be(expected);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ public void Given_Type_When_ParameterVisit_Invoked_Then_It_Should_Return_Result(
}

[DataTestMethod]
[DataRow(typeof(Dictionary<string, string>), "object", null, "string", "string")]
[DataRow(typeof(IDictionary<string, string>), "object", null, "string", "string")]
[DataRow(typeof(IReadOnlyDictionary<string, string>), "object", null, "string", "string")]
[DataRow(typeof(KeyValuePair<string, string>), "object", null, "string", "string")]
[DataRow(typeof(Dictionary<string, FakeModel>), "object", null, "object", "fakeModel")]
[DataRow(typeof(IDictionary<string, FakeModel>), "object", null, "object", "fakeModel")]
[DataRow(typeof(IReadOnlyDictionary<string, FakeModel>), "object", null, "object", "fakeModel")]
[DataRow(typeof(KeyValuePair<string, FakeModel>), "object", null, "object", "fakeModel")]
public void Given_Type_When_PayloadVisit_Invoked_Then_It_Should_Return_Result(Type dictionaryType, string dataType, string dataFormat, string additionalPropertyType, string referenceId)
[DataRow(typeof(Dictionary<string, string>), "object", null, "string", false, null)]
[DataRow(typeof(IDictionary<string, string>), "object", null, "string", false, null)]
[DataRow(typeof(IReadOnlyDictionary<string, string>), "object", null, "string", false, null)]
[DataRow(typeof(KeyValuePair<string, string>), "object", null, "string", false, null)]
[DataRow(typeof(Dictionary<string, FakeModel>), "object", null, "object", true, "fakeModel")]
[DataRow(typeof(IDictionary<string, FakeModel>), "object", null, "object", true, "fakeModel")]
[DataRow(typeof(IReadOnlyDictionary<string, FakeModel>), "object", null, "object", true, "fakeModel")]
[DataRow(typeof(KeyValuePair<string, FakeModel>), "object", null, "object", true, "fakeModel")]
public void Given_Type_When_PayloadVisit_Invoked_Then_It_Should_Return_Result(Type dictionaryType, string dataType, string dataFormat, string additionalPropertyType, bool reference, string referenceId)
{
var result = this._visitor.PayloadVisit(dictionaryType, this._strategy);

Expand All @@ -164,8 +164,15 @@ public void Given_Type_When_PayloadVisit_Invoked_Then_It_Should_Return_Result(Ty
result.AdditionalProperties.Should().NotBeNull();
result.AdditionalProperties.Type.Should().Be(additionalPropertyType);

result.AdditionalProperties.Reference.Type.Should().Be(ReferenceType.Schema);
result.AdditionalProperties.Reference.Id.Should().Be(referenceId);
if (reference)
{
result.AdditionalProperties.Reference.Type.Should().Be(ReferenceType.Schema);
result.AdditionalProperties.Reference.Id.Should().Be(referenceId);
}
else
{
result.AdditionalProperties.Reference.Should().BeNull();
}
}
}
}
Loading

0 comments on commit f352c16

Please sign in to comment.