Skip to content

Commit

Permalink
Merge pull request #1381 from microsoft/bugfix/schema-title
Browse files Browse the repository at this point in the history
- fixes #1270 a bug where missing schema title would make union types fail
  • Loading branch information
baywet authored Mar 16, 2022
2 parents cceb6c5 + 3889c95 commit 5438548
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug where unnecessary namespaces would be added to models generation #1273
- Fixed a bug where Go byte arrays would not write deserializers properly.
- Fixed a bug where integers would not be recognized when type is not number.
- Fixed a bug where union types with primitive member types would fail to generate #1270
- Fixed a bug where union types with inline schema member types would fail to generate #1270
- Fixed a bug where referenced types with no titles would fail to generate #1271
- Fixed a bug where the generator would introduce unnecessary union types for nullables. #990

## [0.0.18] - 2022-03-14
Expand Down
6 changes: 3 additions & 3 deletions src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ public static IEnumerable<string> GetSchemaTitles(this OpenApiSchema schema) {
else if(schema.Items != null)
return schema.Items.GetSchemaTitles();
else if(!string.IsNullOrEmpty(schema.Title))
return new List<string>{ schema.Title };
return new string[] { schema.Title };
else if(schema.AnyOf.Any())
return schema.AnyOf.FlattenIfRequired(classNamesFlattener);
else if(schema.AllOf.Any())
return schema.AllOf.FlattenIfRequired(classNamesFlattener);
else if(schema.OneOf.Any())
return schema.OneOf.FlattenIfRequired(classNamesFlattener);
else if(!string.IsNullOrEmpty(schema.Reference?.Id))
return new List<string>{schema.Reference.Id.Split('/').Last().Split('.').Last()};
return new string[] {schema.Reference.Id.Split('/').Last().Split('.').Last()};
else if(!string.IsNullOrEmpty(schema.Xml?.Name))
return new List<string>{schema.Xml.Name};
return new string[] {schema.Xml.Name};
else return Enumerable.Empty<string>();
}
public static IEnumerable<OpenApiSchema> GetNonEmptySchemas(this OpenApiSchema schema) {
Expand Down
8 changes: 8 additions & 0 deletions src/Kiota.Builder/KiotaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentN
private static string GetReferenceIdFromOriginalSchema(OpenApiSchema schema, OpenApiSchema parentSchema) {
var title = schema.Title;
if(!string.IsNullOrEmpty(schema.Reference?.Id)) return schema.Reference.Id;
else if (string.IsNullOrEmpty(title)) return string.Empty;
if(parentSchema.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Reference.Id;
if(parentSchema.Items?.Reference?.Id?.EndsWith(title, StringComparison.OrdinalIgnoreCase) ?? false) return parentSchema.Items.Reference.Id;
return (parentSchema.
Expand All @@ -810,12 +811,19 @@ private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode,
var unionType = new CodeUnionType {
Name = currentNode.GetClassName(operation: operation, suffix: suffixForInlineSchema, schema: schema),
};
var membersWithNoName = 0;
foreach(var currentSchema in schemas) {
var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(config.ClientNamespaceName) : GetModelsNamespaceNameFromReferenceId(currentSchema.Reference.Id);
var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName);
if(shortestNamespace == null)
shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName);
var className = currentSchema.GetSchemaTitle();
if (string.IsNullOrEmpty(className))
if(GetPrimitiveType(currentSchema) is CodeType primitiveType && !string.IsNullOrEmpty(primitiveType.Name)) {
unionType.AddType(primitiveType);
continue;
} else
className = $"{unionType.Name}Member{++membersWithNoName}";
var codeDeclaration = AddModelDeclarationIfDoesntExist(currentNode, currentSchema, className, shortestNamespace);
unionType.AddType(new CodeType {
TypeDefinition = codeDeclaration,
Expand Down
238 changes: 238 additions & 0 deletions tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,244 @@ public void AddsDiscriminatorMappings(){
Assert.NotNull(castType.TypeDefinition);
Assert.Equal(directoryObjectClass, castType.TypeDefinition);
}
[Fact]
public void UnionOfPrimitiveTypesWorks() {
var simpleObjet = new OpenApiSchema {
Type = "object",
Properties = new Dictionary<string, OpenApiSchema> {
{
"id", new OpenApiSchema {
Type = "string"
}
}
},
Reference = new OpenApiReference {
Id = "subNS.simpleObject",
Type = ReferenceType.Schema
},
UnresolvedReference = false
};
var document = new OpenApiDocument() {
Paths = new OpenApiPaths() {
["unionType"] = new OpenApiPathItem() {
Operations = {
[OperationType.Get] = new OpenApiOperation() {
Responses = new OpenApiResponses
{
["200"] = new OpenApiResponse {
Content = {
["application/json"] = new OpenApiMediaType {
Schema = new OpenApiSchema {
OneOf = new List<OpenApiSchema> {
simpleObjet,
new OpenApiSchema {
Type = "number"
}
}
}
}
}
},
}
}
}
}
},
Components = new OpenApiComponents() {
Schemas = new Dictionary<string, OpenApiSchema> {
{
"subNS.simpleObject", simpleObjet
}
}
},
};
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" });
var node = builder.CreateUriSpace(document);
var codeModel = builder.CreateSourceModel(node);
var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.unionType");
Assert.NotNull(requestBuilderNS);
var requestBuilderClass = requestBuilderNS.FindChildByName<CodeClass>("unionTypeRequestBuilder", false);
Assert.NotNull(requestBuilderClass);
var requestExecutorMethod = requestBuilderClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor));
Assert.NotNull(requestExecutorMethod);
var executorReturnType = requestExecutorMethod.ReturnType as CodeUnionType;
Assert.NotNull(executorReturnType);
Assert.Equal(2, executorReturnType.Types.Count());
var typeNames = executorReturnType.Types.Select(x => x.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
Assert.Contains("simpleObject", typeNames);
Assert.Contains("number", typeNames);
}
[Fact]
public void UnionOfInlineSchemasWorks() {
var simpleObjet = new OpenApiSchema {
Type = "object",
Properties = new Dictionary<string, OpenApiSchema> {
{
"id", new OpenApiSchema {
Type = "string"
}
}
},
Reference = new OpenApiReference {
Id = "subNS.simpleObject",
Type = ReferenceType.Schema
},
UnresolvedReference = false
};
var document = new OpenApiDocument() {
Paths = new OpenApiPaths() {
["unionType"] = new OpenApiPathItem() {
Operations = {
[OperationType.Get] = new OpenApiOperation() {
Responses = new OpenApiResponses
{
["200"] = new OpenApiResponse {
Content = {
["application/json"] = new OpenApiMediaType {
Schema = new OpenApiSchema {
OneOf = new List<OpenApiSchema> {
simpleObjet,
new OpenApiSchema {
Type = "object",
Properties = new Dictionary<string, OpenApiSchema> {
{
"name", new OpenApiSchema {
Type = "string"
}
}
}
}
}
}
}
}
},
}
}
}
}
},
Components = new OpenApiComponents() {
Schemas = new Dictionary<string, OpenApiSchema> {
{
"subNS.simpleObject", simpleObjet
}
}
},
};
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" });
var node = builder.CreateUriSpace(document);
var codeModel = builder.CreateSourceModel(node);
var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.unionType");
Assert.NotNull(requestBuilderNS);
var requestBuilderClass = requestBuilderNS.FindChildByName<CodeClass>("unionTypeRequestBuilder", false);
Assert.NotNull(requestBuilderClass);
var requestExecutorMethod = requestBuilderClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor));
Assert.NotNull(requestExecutorMethod);
var executorReturnType = requestExecutorMethod.ReturnType as CodeUnionType;
Assert.NotNull(executorReturnType);
Assert.Equal(2, executorReturnType.Types.Count());
var typeNames = executorReturnType.Types.Select(x => x.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
Assert.Contains("simpleObject", typeNames);
Assert.Contains("unionTypeResponseMember1", typeNames);
}
[Fact]
public void InheritedTypeWithInlineSchemaWorks() {
var baseObjet = new OpenApiSchema {
Type = "object",
Properties = new Dictionary<string, OpenApiSchema> {
{
"name", new OpenApiSchema {
Type = "string"
}
},
{
"kind", new OpenApiSchema {
Type = "string"
}
}
},
Discriminator = new OpenApiDiscriminator {
PropertyName = "kind",
Mapping = new Dictionary<string, string> {
{
"derivedObject", "#/components/schemas/subNS.derivedObject"
}
}
},
Reference = new OpenApiReference {
Id = "subNS.baseObject",
Type = ReferenceType.Schema
},
UnresolvedReference = false
};
var derivedObjet = new OpenApiSchema {
Type = "object",
AllOf = new List<OpenApiSchema> {
baseObjet,
new OpenApiSchema {
Type = "object",
Properties = new Dictionary<string, OpenApiSchema> {
{
"special", new OpenApiSchema {
Type = "string"
}
}
}
}
},
Reference = new OpenApiReference {
Id = "subNS.derivedObject",
Type = ReferenceType.Schema
},
UnresolvedReference = false
};
var document = new OpenApiDocument() {
Paths = new OpenApiPaths() {
["derivedType"] = new OpenApiPathItem() {
Operations = {
[OperationType.Get] = new OpenApiOperation() {
Responses = new OpenApiResponses
{
["200"] = new OpenApiResponse {
Content = {
["application/json"] = new OpenApiMediaType {
Schema = derivedObjet
}
}
},
}
}
}
}
},
Components = new OpenApiComponents() {
Schemas = new Dictionary<string, OpenApiSchema> {
{
"subNS.baseObject", baseObjet
},
{
"subNS.derivedObject", derivedObjet
}
}
},
};
var mockLogger = new Mock<ILogger<KiotaBuilder>>();
var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph", ApiRootUrl = "https://localhost" });
var node = builder.CreateUriSpace(document);
var codeModel = builder.CreateSourceModel(node);
var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.derivedType");
Assert.NotNull(requestBuilderNS);
var requestBuilderClass = requestBuilderNS.FindChildByName<CodeClass>("derivedTypeRequestBuilder", false);
Assert.NotNull(requestBuilderClass);
var requestExecutorMethod = requestBuilderClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor));
Assert.NotNull(requestExecutorMethod);
var executorReturnType = requestExecutorMethod.ReturnType as CodeType;
Assert.NotNull(executorReturnType);
Assert.Contains("DerivedObject", requestExecutorMethod.ReturnType.Name);
}
[InlineData("string", "", "string")]// https://spec.openapis.org/registry/format/
[InlineData("string", "commonmark", "string")]
[InlineData("string", "html", "string")]
Expand Down

0 comments on commit 5438548

Please sign in to comment.