[OCA] Azure Function Extension OpenAPI Issue
Issue #396
OCA에서 Challenger 과정이 끝나가면서, Issue 하나를 정해 파악하는 시간을 가지게 되었다.
처음 정한 Issue는 #396으로 다음과 같다.
https://github.com/Azure/azure-functions-openapi-extension/issues/396
Nullable<byte> => "Sequence contains no elements" · Issue #396 · Azure/azure-functions-openapi-extension
Describe the issue I'm getting an exception trying to render the Swagger Document when my API includes a class that has a Nullable in it. To Reproduce Steps to reproduce the behavior: Use Visual St...
github.com
1. 원인 파악
Sequence contains no elements
at System.Linq.ThrowHelper.ThrowNoElementsException()
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors.NullableObjectTypeVisitor.Visit(IAcceptor acceptor, KeyValuePair`2 type, NamingStrategy namingStrategy, Attribute[] attributes)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors.OpenApiSchemaAcceptor.Accept(VisitorCollection collection, NamingStrategy namingStrategy)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors.ObjectTypeVisitor.ProcessProperties(IOpenApiSchemaAcceptor instance, String schemaName, Dictionary`2 properties, NamingStrategy namingStrategy)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors.ObjectTypeVisitor.Visit(IAcceptor acceptor, KeyValuePair`2 type, NamingStrategy namingStrategy, Attribute[] attributes)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors.OpenApiSchemaAcceptor.Accept(VisitorCollection collection, NamingStrategy namingStrategy)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.DocumentHelper.GetOpenApiSchemas(List`1 elements, NamingStrategy namingStrategy, VisitorCollection collection)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.Document.Build(Assembly assembly, OpenApiVersionType version)
at Microsoft.Azure.WebJobs.Extensions.OpenApi.OpenApiTriggerFunctionProvider.RenderSwaggerDocument(HttpRequest req, String extension, ExecutionContext ctx, ILogger log)
해당 Exception을 따라가보면, Schema에 값이 추가가 되지 않아 NullableObjectTypeVisitor에서 OpenApiSchemaAcceptor의 Schema가 비어있어 First() 메서드에서 오류가 발생하는 것을 알 수 있었습니다.
// NullableObjectTypeVisitor에서 오류가 발생하는 코드 (58~63번째 줄)
subAcceptor.Accept(this.VisitorCollection, namingStrategy);
// Adds the schema for the underlying type.
var name = subAcceptor.Schemas.First().Key;
var schema = subAcceptor.Schemas.First().Value;
schema.Nullable = true;
OpenApiSchemaAcceptor의 Accept 메서드를 확인해보면 Visitor Collection을 하나하나 돌면서, IsVisitable이 True일 때, Byte Type이 Schema에 추가가 되지 않는 것으로 보입니다.
// OpenApiSchemaAccepter의 Accept 메소드
// 해당 메소드의 19번째 줄의 foreach문에서 Visitor Collection을 돌면서 확인할 때, Byte Type에 해당하는 Visitor가 없어서 발생하는 것으로 추정
public void Accept(VisitorCollection collection, NamingStrategy namingStrategy)
{
// Checks the properties only.
if (this.Properties.Any())
{
foreach (var property in this.Properties)
{
var attributes = new List<Attribute>
{
property.Value.GetCustomAttribute<OpenApiSchemaVisibilityAttribute>(inherit: false),
property.Value.GetCustomAttribute<OpenApiPropertyAttribute>(inherit: false),
};
attributes.AddRange(property.Value.GetCustomAttributes<ValidationAttribute>(inherit: false));
attributes.AddRange(property.Value.GetCustomAttributes<JsonPropertyAttribute>(inherit: false));
foreach (var visitor in collection.Visitors)
{
if (!visitor.IsVisitable(property.Value.PropertyType))
{
continue;
}
var type = new KeyValuePair<string, Type>(property.Key, property.Value.PropertyType);
visitor.Visit(this, type, namingStrategy, attributes.ToArray());
}
}
return;
}
Nullable<T>를 사용할 수 있는 다른 값 변수들 역시 실험해본 결과, Nullable<byte> 타입과 Nullable<char> 타입, 2가지 타입이 오류가 발생하는 것을 발견할 수 있었습니다.
2. 해결 방안
ByteTypeVisitor가 없어서 발생하는 오류라 생각하고 ByteTypeVisitor를 만들고 실행한 결과, 오류가 없이 실행되는 것을 볼 수 있었습니다.
using System;
using System.Collections.Generic;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors
{
/// <summary>
/// This represents the type visitor for <see cref="byte"/>.
/// </summary>
public class ByteTypeVisitor : TypeVisitor
{
/// <inheritdoc />
public ByteTypeVisitor(VisitorCollection visitorCollection)
: base(visitorCollection)
{
}
/// <inheritdoc />
public override bool IsVisitable(Type type)
{
var isVisitable = this.IsVisitable(type, TypeCode.Byte);
return isVisitable;
}
/// <inheritdoc />
public override void Visit(IAcceptor acceptor, KeyValuePair<string, Type> type, NamingStrategy namingStrategy, params Attribute[] attributes)
{
this.Visit(acceptor, name: type.Key, title: null, dataType: "string", dataFormat: "binary", attributes: attributes);
}
/// <inheritdoc />
public override bool IsParameterVisitable(Type type)
{
var isVisitable = this.IsVisitable(type);
return isVisitable;
}
/// <inheritdoc />
public override OpenApiSchema ParameterVisit(Type type, NamingStrategy namingStrategy)
{
return this.ParameterVisit(dataType: "string", dataFormat: "binary");
}
/// <inheritdoc />
public override bool IsPayloadVisitable(Type type)
{
var isVisitable = this.IsVisitable(type);
return isVisitable;
}
/// <inheritdoc />
public override OpenApiSchema PayloadVisit(Type type, NamingStrategy namingStrategy)
{
return this.PayloadVisit(dataType: "string", dataFormat: "binary");
}
}
}
3. 의문점
실행은 되지만, Nullable<byte>가 아닌 byte의 경우에는 어떤 Visitor를 거쳐서 실행되는지 알 수 없었습니다.
using System;
namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.Models
{
public class Dto
{
public byte Byte {get; set; }
}
}
해당 사진은 Dto 클래스에 아무것도 표현되지 않는 것을 볼 수 있어 byte 타입의 경우 Visitor가 따로 존재하지 않는 것인지 의문이 듭니다.
==> 피드백 후, ByteTypeVisitor와 CharTypeVisitor가 없는 것으로 확인하고 OpenAPI 문서를 참고해 작성하기로 했습니다.
4. PR 그리고 Merge
직접 올린 PR : https://github.com/Azure/azure-functions-openapi-extension/pull/600
피드백을 받고, OpenAPI 문서와 Json Schema ruleset을 참고해 ByteTypeVisitor,CharTypeVisitor를 작성했습니다.
이 후에, Unit Test와 Intergration Test를 작성해야 했는데 단지 2개의 파일 추가였음에도 생각보다 많은 양의 Test Code를 작성해야 했습니다.
그렇게 각 Visitor 별로, Test Code를 작성한 결과! PR이 main 브랜치에 merge 될 수 있었습니다!
처음 오픈소스 컨트리뷰션을 진행해보면서 왠지 모를 뿌듯함도 느껴졌고, 큰 프로젝트에서 어떻게 디자인패턴이 쓰이는지 볼 수 있어 뜻 깊은 경험이었습니다.