Skip to content

Commit

Permalink
Merge pull request #4381 from microsoft/feature/intersection-types
Browse files Browse the repository at this point in the history
- fixes a bug where multiple allOf entries type would not get merged if they were part of a discriminator
  • Loading branch information
andrueastman authored May 14, 2024
2 parents 4fc6539 + 2d58387 commit f54bbda
Show file tree
Hide file tree
Showing 7 changed files with 711 additions and 128 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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)
- 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

0 comments on commit f54bbda

Please sign in to comment.