-
[OCA] Azure Function Extension OpenAPI Issue카테고리 없음 2023. 8. 11. 23:58
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 될 수 있었습니다!
처음 오픈소스 컨트리뷰션을 진행해보면서 왠지 모를 뿌듯함도 느껴졌고, 큰 프로젝트에서 어떻게 디자인패턴이 쓰이는지 볼 수 있어 뜻 깊은 경험이었습니다.