Skip to content
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
12 changes: 9 additions & 3 deletions src/Elastic.ApiExplorer/Components/PropertyTree/ApiProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ public record ApiProperty
public RequiredAlternatives? Requires { get; init; }

/// <summary>
/// <c>X</c> of an <c>X | X[]</c> row whose type does not say so and whose fields list <c>X</c>'s: the row reads "A
/// single X or an array of them", since nothing else tells the reader an array is accepted too.
/// The two shapes of an <c>X | X[]</c> row whose type does not say so and whose fields list <c>X</c>'s: the row reads
/// "X or X[]", since nothing else tells the reader an array is accepted too.
/// </summary>
public string? SingleOrArrayOf { get; init; }
public SingleOrArray? SingleOrArray { get; init; }

public required bool IsCollapsible { get; init; }
public required bool DefaultExpanded { get; init; }
Expand All @@ -197,6 +197,12 @@ public record ApiProperty
/// <summary>An ordered list of property rows; the model for <c>_PropertyList</c>.</summary>
public record ApiPropertyList(IReadOnlyList<ApiProperty> Items);

/// <summary>
/// How an <c>X | X[]</c> row names its two shapes: <c>Rescore</c> and <c>Rescore[]</c>. A map's array takes the
/// <c>[]</c> in front, <c>[] map string to X</c>, since <c>map string to X[]</c> would read as a map of arrays.
/// </summary>
public record SingleOrArray(string One, string Many);

/// <summary>A "Requires at least one of:" row; each option is a field, or fields joined by <c>+</c> that go together.</summary>
public record RequiredAlternatives(string Label, IReadOnlyList<string> Options);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ private static void WriteProperty(StringBuilder markdown, ApiProperty property,
if (property.Requires is { } requires)
WriteNestedLine(markdown, depth, Format(requires));

if (property.SingleOrArrayOf is { } single)
WriteNestedLine(markdown, depth, $"A single `{single}` or an array of them");
if (property.SingleOrArray is { } shapes)
WriteNestedLine(markdown, depth, $"`{shapes.One}` or `{shapes.Many}`");

WriteChildren(markdown, property, apiBaseUrl, depth);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ private ApiProperty BuildProperty(PropertyRow row, PropertyTreeScope scope)
TypeLink = typeLink,
AlsoIncludes = BuildAlsoIncludes(typeInfo),
Requires = DescribeRequiredAlternatives(propSchema),
SingleOrArrayOf = SingleOrArrayOf(typeInfo, expansion),
SingleOrArray = DescribeSingleOrArray(typeInfo, expansion),
// A repeat lists nothing itself, so it gets no show/hide toggle.
IsCollapsible = repeats is null && expansion.IsCollapsible,
DefaultExpanded = expansion.DefaultExpanded,
Expand Down Expand Up @@ -310,7 +310,7 @@ private IReadOnlyList<TypePageLink> BuildAlsoIncludes(TypeInfo typeInfo) =>
/// <c>X</c> when an <c>X | X[]</c> row lists <c>X</c>'s fields and its type does not read <c>X | X[]</c>. Such a row
/// hides its options line; a row that lists <c>X</c>'s variants names both shapes in the list's label instead.
/// </summary>
private string? SingleOrArrayOf(TypeInfo typeInfo, Expansion expansion)
private SingleOrArray? DescribeSingleOrArray(TypeInfo typeInfo, Expansion expansion)
{
if (
expansion is not { ArrayUnion: { Expands: true } arrayUnion, Plan: ChildPlan.Properties }
Expand All @@ -322,7 +322,7 @@ private IReadOnlyList<TypePageLink> BuildAlsoIncludes(TypeInfo typeInfo) =>
var single = typeInfo.UnionOptions?.FirstOrDefault(o => o is { IsArray: false } && o.BaseName == arrayUnion.BaseName);
var isMap = single?.Schema is { } schema && _analyzer.GetTypeInfo(schema).IsDictionary;
var name = SchemaHelpers.ReadableSchemaName(arrayUnion.BaseName);
return isMap ? $"map {name}" : name;
return isMap ? new SingleOrArray($"map {name}", $"[] map {name}") : new SingleOrArray(name, $"{name}[]");
}

private UnionDisplay? BuildUnionDisplay(IOpenApiSchema propSchema, TypeInfo typeInfo, Expansion expansion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private static void Append(StringBuilder text, ApiPropertyList? properties, Page
.Append('|')
.AppendJoin(',', property.AlsoIncludes.Select(t => t.TypeName))
.Append('|')
.Append(property.SingleOrArrayOf)
.Append(property.SingleOrArray?.One)
.Append(property.Requires?.Label)
.AppendJoin(',', property.Requires?.Options ?? [])
.Append('|')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@
}
</dd>
}
@if (Model.SingleOrArrayOf is { } single)
@if (Model.SingleOrArray is { } shapes)
{
<dd class="union-options">
<span class="values-label">A single</span>
<code>@single</code>
<span class="values-label">or an array of them</span>
<code>@shapes.One</code>
<span class="union-inline-separator">or</span>
<code>@shapes.Many</code>
</dd>
}
@if (Model.Requires is { } requires)
Expand Down
16 changes: 11 additions & 5 deletions tests/Elastic.ApiExplorer.Tests/UnionVariantTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -534,13 +534,19 @@ public async Task BuildPropertyList_SingleOrArrayRowListingFields_SaysAnArrayIsA

ApiProperty Row(string name) => list.Items.Single(p => p.Name == name);

Row("rescore").SingleOrArrayOf.Should().Be("Rescore", "the row lists Rescore's fields and its type reads only union oneOf");
Row("rescore")
.SingleOrArray
.Should()
.Be(new SingleOrArray("Rescore", "Rescore[]"), "the row lists Rescore's fields and its type reads only union oneOf");
Row("rescore").Children.Properties!.Items.Select(p => p.Name).Should().Equal("window_size", "query");
Row("fields").SingleOrArrayOf.Should().Be("map string to Field");
Row("nodes").SingleOrArrayOf.Should().BeNull("a primitive X lists no fields, so the options row still names both shapes");
Row("fields")
.SingleOrArray
.Should()
.Be(new SingleOrArray("map string to Field", "[] map string to Field"), "map string to Field[] would read as a map of arrays");
Row("nodes").SingleOrArray.Should().BeNull("a primitive X lists no fields, so the options row still names both shapes");
var markdown = new System.Text.StringBuilder();
ApiPropertyMarkdown.WriteList(markdown, new ApiPropertyList([Row("rescore")]), "/api/doc/fixture");
markdown.ToString().Should().Contain("A single `Rescore` or an array of them");
markdown.ToString().Should().Contain("`Rescore` or `Rescore[]`");
}

[Test]
Expand Down Expand Up @@ -578,7 +584,7 @@ public async Task BuildPropertyList_ArrayUnionOfAUnion_LabelsTheVariantsWithTheI
)!.Items.Single();

like.Union.Should().BeNull("the type already reads Like | Like[]");
like.SingleOrArrayOf.Should().BeNull("the variant list's label already names both shapes");
like.SingleOrArray.Should().BeNull("the variant list's label already names both shapes");
like.Children.Variants!.Label.Should().Be("Like or Like[]; each Like is any of:");
like.Children.Variants.Variants.Select(v => v.DiscriminatorLabel).Should().Equal("kind: doc", "kind: LikeText");
var markdown = new System.Text.StringBuilder();
Expand Down
Loading