Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- fixes a bug where multiple allOf entries type would not get merged if they were part of a discriminator #4381

Merged
merged 17 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Put opening brace after property definition on new line, if property has getter and setter [#4625](https://github.com/microsoft/kiota/issues/4625)
- Put spaces correctly around dictionary entries [#4625](https://github.com/microsoft/kiota/issues/4625)
- Remove trailing space after class definition [#4625](https://github.com/microsoft/kiota/issues/4625)
- Fixed a bug where multiple allOf entries type would not get merged if they were part of a discriminator. [#4325](https://github.com/microsoft/kiota/issues/4325)
- Fixed a bug where allOf structure with one entry and properties would not be considered as inheritance case. [#4346](https://github.com/microsoft/kiota/issues/4346)
- Fixed a bug where some allOf scenarios would be missing properties if type object wasn't set on the schema. [#4074](https://github.com/microsoft/kiota/issues/4074)
baywet marked this conversation as resolved.
Show resolved Hide resolved
- Fixed a bug where schema with multiple allOf entries would incorrectly get merged to inherit from the first entry [#4428] (https://github.com/microsoft/kiota/issues/4428)
- Fixes constructor generation for nullable properties that are initialized as null in C#,Java and PHP

## [1.14.0] - 2024-05-02
Expand Down
68 changes: 38 additions & 30 deletions src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,37 @@ public static class OpenApiSchemaExtensions
{
private static readonly Func<OpenApiSchema, IList<OpenApiSchema>> classNamesFlattener = x =>
(x.AnyOf ?? Enumerable.Empty<OpenApiSchema>()).Union(x.AllOf).Union(x.OneOf).ToList();
public static IEnumerable<string> GetSchemaNames(this OpenApiSchema schema)
public static IEnumerable<string> GetSchemaNames(this OpenApiSchema schema, bool directOnly = false)
{
if (schema == null)
return Enumerable.Empty<string>();
if (schema.Items != null)
return [];
if (!directOnly && schema.Items != null)
return schema.Items.GetSchemaNames();
if (!string.IsNullOrEmpty(schema.Reference?.Id))
return new[] { schema.Reference.Id.Split('/')[^1].Split('.')[^1] };
if (schema.AnyOf.Any())
return [schema.Reference.Id.Split('/')[^1].Split('.')[^1]];
if (!directOnly && schema.AnyOf.Any())
return schema.AnyOf.FlattenIfRequired(classNamesFlattener);
if (schema.AllOf.Any())
if (!directOnly && schema.AllOf.Any())
return schema.AllOf.FlattenIfRequired(classNamesFlattener);
if (schema.OneOf.Any())
if (!directOnly && schema.OneOf.Any())
return schema.OneOf.FlattenIfRequired(classNamesFlattener);
if (!string.IsNullOrEmpty(schema.Title))
return new[] { schema.Title };
if (!string.IsNullOrEmpty(schema.Xml?.Name))
return new[] { schema.Xml.Name };
return Enumerable.Empty<string>();
return [];
}
internal static IEnumerable<OpenApiSchema> FlattenSchemaIfRequired(this IList<OpenApiSchema> schemas, Func<OpenApiSchema, IList<OpenApiSchema>> subsequentGetter)
{
if (schemas is null) return Enumerable.Empty<OpenApiSchema>();
return schemas.Count == 1 ?
if (schemas is null) return [];
return schemas.Count == 1 && !schemas[0].HasAnyProperty() ?
schemas.FlattenEmptyEntries(subsequentGetter, 1) :
schemas;
}
private static IEnumerable<string> FlattenIfRequired(this IList<OpenApiSchema> schemas, Func<OpenApiSchema, IList<OpenApiSchema>> subsequentGetter)
{
return schemas.FlattenSchemaIfRequired(subsequentGetter).Where(static x => !string.IsNullOrEmpty(x.Title)).Select(static x => x.Title);
return schemas.FlattenSchemaIfRequired(subsequentGetter).SelectMany(static x => x.GetSchemaNames());
}

public static string GetSchemaName(this OpenApiSchema schema)
public static string GetSchemaName(this OpenApiSchema schema, bool directOnly = false)
{
return schema.GetSchemaNames().LastOrDefault()?.TrimStart('$') ?? string.Empty;// OData $ref
return schema.GetSchemaNames(directOnly).LastOrDefault()?.TrimStart('$') ?? string.Empty;// OData $ref
}

public static bool IsReferencedSchema(this OpenApiSchema schema)
Expand All @@ -61,13 +57,17 @@ public static bool IsArray(this OpenApiSchema? schema)
(schema.Items.IsComposedEnum() ||
schema.Items.IsEnum() ||
schema.Items.IsSemanticallyMeaningful() ||
FlattenEmptyEntries(new OpenApiSchema[] { schema.Items }, static x => x.AnyOf.Union(x.AllOf).Union(x.OneOf).ToList(), 1).FirstOrDefault() is OpenApiSchema flat && flat.IsSemanticallyMeaningful());
FlattenEmptyEntries([schema.Items], static x => x.AnyOf.Union(x.AllOf).Union(x.OneOf).ToList(), 1).FirstOrDefault() is OpenApiSchema flat && flat.IsSemanticallyMeaningful());
}

public static bool IsObject(this OpenApiSchema? schema)
public static bool IsObjectType(this OpenApiSchema? schema)
{
return "object".Equals(schema?.Type, StringComparison.OrdinalIgnoreCase);
}
public static bool HasAnyProperty(this OpenApiSchema? schema)
{
return schema?.Properties is { Count: > 0 };
}
public static bool IsInclusiveUnion(this OpenApiSchema? schema)
{
return schema?.AnyOf?.Count(static x => IsSemanticallyMeaningful(x, true)) > 1;
Expand All @@ -77,25 +77,33 @@ public static bool IsInclusiveUnion(this OpenApiSchema? schema)
public static bool IsInherited(this OpenApiSchema? schema)
{
if (schema is null) return false;
var meaningfulSchemas = schema.AllOf.FlattenSchemaIfRequired(static x => x.AllOf).Where(static x => x.IsSemanticallyMeaningful()).ToArray();
return meaningfulSchemas.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) == 1 && meaningfulSchemas.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) == 1;
var meaningfulMemberSchemas = schema.AllOf.FlattenSchemaIfRequired(static x => x.AllOf).Where(static x => x.IsSemanticallyMeaningful()).ToArray();
var isRootSchemaMeaningful = schema.IsSemanticallyMeaningful();
return meaningfulMemberSchemas.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) == 1 &&
(meaningfulMemberSchemas.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) == 1 ||
isRootSchemaMeaningful);
}

internal static OpenApiSchema? MergeIntersectionSchemaEntries(this OpenApiSchema? schema)
internal static OpenApiSchema? MergeIntersectionSchemaEntries(this OpenApiSchema? schema, HashSet<OpenApiSchema>? schemasToExclude = default)
{
if (schema is null) return null;
if (!schema.IsIntersection()) return schema;
var result = new OpenApiSchema(schema);
result.AllOf.Clear();
var meaningfulSchemas = schema.AllOf.Where(static x => x.IsSemanticallyMeaningful()).Select(MergeIntersectionSchemaEntries).Where(x => x is not null).OfType<OpenApiSchema>();
meaningfulSchemas.SelectMany(static x => x.Properties).ToList().ForEach(x => result.Properties.TryAdd(x.Key, x.Value));
var meaningfulSchemas = schema.AllOf
.Where(static x => x.IsSemanticallyMeaningful() || x.AllOf.Any())
.Select(x => MergeIntersectionSchemaEntries(x, schemasToExclude))
.Where(x => x is not null && (schemasToExclude is null || !schemasToExclude.Contains(x)))
.OfType<OpenApiSchema>()
.ToArray();
meaningfulSchemas.FlattenEmptyEntries(static x => x.AllOf).Union(meaningfulSchemas).SelectMany(static x => x.Properties).ToList().ForEach(x => result.Properties.TryAdd(x.Key, x.Value));
return result;
}

public static bool IsIntersection(this OpenApiSchema? schema)
{
var meaningfulSchemas = schema?.AllOf?.Where(static x => x.IsSemanticallyMeaningful());
return meaningfulSchemas?.Count() > 3 || meaningfulSchemas?.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) > 1 || meaningfulSchemas?.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) > 1;
var meaningfulSchemas = schema?.AllOf?.Where(static x => x.IsSemanticallyMeaningful()).ToArray();
return meaningfulSchemas?.Count(static x => !string.IsNullOrEmpty(x.Reference?.Id)) > 1 || meaningfulSchemas?.Count(static x => string.IsNullOrEmpty(x.Reference?.Id)) > 1;
}

public static bool IsExclusiveUnion(this OpenApiSchema? schema)
Expand Down Expand Up @@ -136,7 +144,7 @@ public static bool IsComposedEnum(this OpenApiSchema schema)
public static bool IsSemanticallyMeaningful(this OpenApiSchema schema, bool ignoreNullableObjects = false)
{
if (schema is null) return false;
return schema.Properties.Any() ||
return schema.HasAnyProperty() ||
schema.Enum is { Count: > 0 } ||
schema.Items != null ||
(!string.IsNullOrEmpty(schema.Type) &&
Expand Down Expand Up @@ -171,11 +179,11 @@ public static IEnumerable<string> GetSchemaReferenceIds(this OpenApiSchema schem
return result.Distinct();
}

return Enumerable.Empty<string>();
return [];
}
private static IEnumerable<OpenApiSchema> FlattenEmptyEntries(this IEnumerable<OpenApiSchema> schemas, Func<OpenApiSchema, IList<OpenApiSchema>> subsequentGetter, int? maxDepth = default)
{
if (schemas == null) return Enumerable.Empty<OpenApiSchema>();
if (schemas == null) return [];
ArgumentNullException.ThrowIfNull(subsequentGetter);

if ((maxDepth ?? 1) <= 0)
Expand All @@ -186,7 +194,7 @@ private static IEnumerable<OpenApiSchema> FlattenEmptyEntries(this IEnumerable<O
foreach (var item in result)
{
var subsequentItems = subsequentGetter(item);
if (string.IsNullOrEmpty(item.Title) && subsequentItems.Any())
if (subsequentItems.Any())
permutations.Add(item, subsequentItems.FlattenEmptyEntries(subsequentGetter, maxDepth.HasValue ? --maxDepth : default));
}
if (permutations.Count > 0)
Expand Down
Loading
Loading