Skip to content

Commit 234055b

Browse files
committed
Fix remaining covariance issues
1 parent 65a445e commit 234055b

File tree

16 files changed

+182
-135
lines changed

16 files changed

+182
-135
lines changed

src/Elastic.Documentation.Navigation/Assembler/AssembledNavigation.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public SiteNavigation(
7070
items.Add(navItem);
7171
}
7272

73-
var indexNavigation = items.QueryIndex(this, new NotFoundModel("/index.md"), out var navigationItems);
73+
var indexNavigation = items.QueryIndex<IDocumentationFile>(this, "/index.md", out var navigationItems);
7474
Index = indexNavigation;
7575
NavigationItems = navigationItems;
7676
_ = this.UpdateNavigationIndex(context);
@@ -236,6 +236,10 @@ void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavig
236236
if (nodeChild is INavigationHomeAccessor childAccessor)
237237
childAccessor.HomeProvider = homeAccessor.HomeProvider;
238238

239+
// roots are only added if configured by navigation.yml (tocRef)
240+
if (nodeChild is IRootNavigationItem<INavigationModel, INavigationItem>)
241+
continue;
242+
239243
children.Add(nodeChild);
240244
}
241245

src/Elastic.Documentation.Navigation/Isolated/DocumentationSetNavigation.cs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ public static VirtualFileNavigation<TModel> CreateVirtualFileNavigation<TModel>(
2626
new(model, fileInfo, args) { NavigationIndex = args.NavigationIndex };
2727
}
2828

29-
public interface IDocumentationSetNavigation : IRootNavigationItem<IDocumentationFile, INavigationItem>
29+
public interface IDocumentationSetNavigation
3030
{
3131
IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> TableOfContentNodes { get; }
3232
}
3333

3434
[DebuggerDisplay("{Url}")]
3535
public class DocumentationSetNavigation<TModel>
36-
: IDocumentationSetNavigation, INavigationHomeAccessor, INavigationHomeProvider
36+
: IDocumentationSetNavigation, IRootNavigationItem<TModel, INavigationItem>, INavigationHomeAccessor, INavigationHomeProvider
3737

38-
where TModel : IDocumentationFile
38+
where TModel : class, IDocumentationFile
3939
{
4040
private readonly IDocumentationFileFactory<TModel> _factory;
4141

@@ -82,10 +82,29 @@ public DocumentationSetNavigation(
8282
items.Add(navItem);
8383
}
8484

85-
var indexNavigation = items.QueryIndex(this, new NotFoundModel($"{PathPrefix}/index.md"), out var navigationItems);
86-
Index = indexNavigation;
87-
NavigationItems = navigationItems;
88-
_ = this.UpdateNavigationIndex(context);
85+
// Handle empty TOC - emit errors and create a minimal structure
86+
if (items.Count == 0)
87+
{
88+
var setName = documentationSet.Project ?? "unnamed";
89+
var setPath = context.ConfigurationPath.FullName;
90+
91+
// Emit error if TOC was defined but no items could be created
92+
if (documentationSet.TableOfContents.Count > 0)
93+
context.EmitError(context.ConfigurationPath, $"Documentation set '{setName}' ({setPath}) table of contents has items defined but none could be created");
94+
// Emit error if TOC was never defined
95+
else
96+
context.EmitError(context.ConfigurationPath, $"Documentation set '{setName}' ({setPath}) has no table of contents defined");
97+
98+
Index = null!;
99+
NavigationItems = [];
100+
}
101+
else
102+
{
103+
var indexNavigation = items.QueryIndex<TModel>(this, $"{PathPrefix}/index.md", out var navigationItems);
104+
Index = indexNavigation;
105+
NavigationItems = navigationItems;
106+
_ = this.UpdateNavigationIndex(context);
107+
}
89108

90109
}
91110

@@ -102,9 +121,10 @@ public DocumentationSetNavigation(
102121

103122
public GitCheckoutInformation Git { get; }
104123

105-
private readonly Dictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> _tableOfContentNodes = [];
124+
private readonly Dictionary<Uri, IRootNavigationItem<TModel, INavigationItem>> _tableOfContentNodes = [];
106125
private readonly IDocumentationSetContext _context;
107-
public IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> TableOfContentNodes => _tableOfContentNodes;
126+
public IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> TableOfContentNodes =>
127+
_tableOfContentNodes.ToDictionary(kvp => kvp.Key, IRootNavigationItem<IDocumentationFile, INavigationItem> (kvp) => kvp.Value);
108128

109129
public Uri Identifier { get; }
110130

@@ -137,7 +157,7 @@ public DocumentationSetNavigation(
137157
public string Id { get; }
138158

139159
/// <inheritdoc />
140-
public ILeafNavigationItem<IDocumentationFile> Index { get; private set; }
160+
public ILeafNavigationItem<TModel> Index { get; private set; }
141161

142162
/// <inheritdoc />
143163
public bool IsUsingNavigationDropdown { get; }
@@ -148,7 +168,7 @@ public DocumentationSetNavigation(
148168
void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems) => SetNavigationItems(navigationItems);
149169
internal void SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems)
150170
{
151-
var indexNavigation = navigationItems.QueryIndex(this, new NotFoundModel($"{PathPrefix}/index.md"), out navigationItems);
171+
var indexNavigation = navigationItems.QueryIndex<TModel>(this, $"{PathPrefix}/index.md", out navigationItems);
152172
Index = indexNavigation;
153173
NavigationItems = navigationItems;
154174
_ = this.UpdateNavigationIndex(_context);
@@ -268,7 +288,7 @@ INavigationHomeAccessor homeAccessor
268288
{
269289
var childNav = ConvertToNavigationItem(
270290
child, childIndex++, context,
271-
(INodeNavigationItem<INavigationModel, INavigationItem>)fileNavigation,
291+
fileNavigation,
272292
homeAccessor, // Files don't change the URL root
273293
0 // Depth will be set by child
274294
);
@@ -325,7 +345,7 @@ int depth
325345
var folderPath = folderRef.PathRelativeToDocumentationSet;
326346

327347
// Create folder navigation with null parent initially - we'll pass it to children but set it properly after
328-
var folderNavigation = new FolderNavigation(depth + 1, folderPath, parent, homeAccessor, [])
348+
var folderNavigation = new FolderNavigation<TModel>(depth + 1, folderPath, parent, homeAccessor)
329349
{
330350
NavigationIndex = index
331351
};
@@ -385,13 +405,12 @@ int depth
385405
// Create the TOC navigation with empty children initially
386406
// We use null parent temporarily - we'll set it properly at the end using the public setter
387407
// Pass tocHomeProvider so the TOC uses parent's NavigationRoot (enables dynamic URL updates)
388-
var tocNavigation = new TableOfContentsNavigation(
408+
var tocNavigation = new TableOfContentsNavigation<TModel>(
389409
tocDirectory,
390410
depth + 1,
391411
fullTocPath,
392412
parent, // Temporary null parent
393413
isolatedHomeProvider.PathPrefix,
394-
[],
395414
Git,
396415
_tableOfContentNodes,
397416
isolatedHomeProvider

src/Elastic.Documentation.Navigation/Isolated/FolderNavigation.cs

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,17 @@
88
namespace Elastic.Documentation.Navigation.Isolated;
99

1010
[DebuggerDisplay("{Url}")]
11-
public class FolderNavigation : INodeNavigationItem<IDocumentationFile, INavigationItem>, IAssignableChildrenNavigation
11+
public class FolderNavigation<TModel>(
12+
int depth,
13+
string parentPath,
14+
INodeNavigationItem<INavigationModel, INavigationItem>? parent,
15+
INavigationHomeAccessor homeAccessor)
16+
: INodeNavigationItem<TModel, INavigationItem>, IAssignableChildrenNavigation
17+
where TModel : class, IDocumentationFile
1218
{
13-
private readonly INavigationHomeAccessor _homeAccessor;
14-
15-
public FolderNavigation(
16-
int depth,
17-
string parentPath,
18-
INodeNavigationItem<INavigationModel, INavigationItem>? parent,
19-
INavigationHomeAccessor homeAccessor,
20-
IReadOnlyCollection<INavigationItem> navigationItems
21-
)
22-
{
23-
_homeAccessor = homeAccessor;
24-
FolderPath = parentPath;
25-
NavigationItems = navigationItems;
26-
Parent = parent;
27-
Depth = depth;
28-
Hidden = false;
29-
IsCrossLink = false;
30-
Id = ShortId.Create(parentPath);
31-
var indexNavigation = navigationItems.QueryIndex(this, new NotFoundModel($"{FolderPath}/index.md"), out navigationItems);
32-
Index = indexNavigation;
33-
NavigationItems = navigationItems;
34-
}
19+
// Will be set by SetNavigationItems
3520

36-
public string FolderPath { get; }
21+
public string FolderPath { get; } = parentPath;
3722

3823
/// <inheritdoc />
3924
public string Url => Index.Url;
@@ -42,10 +27,10 @@ IReadOnlyCollection<INavigationItem> navigationItems
4227
public string NavigationTitle => Index.NavigationTitle;
4328

4429
/// <inheritdoc />
45-
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot => _homeAccessor.HomeProvider.NavigationRoot;
30+
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot => homeAccessor.HomeProvider.NavigationRoot;
4631

4732
/// <inheritdoc />
48-
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
33+
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; } = parent;
4934

5035
/// <inheritdoc />
5136
public bool Hidden { get; }
@@ -57,20 +42,20 @@ IReadOnlyCollection<INavigationItem> navigationItems
5742
public bool IsCrossLink { get; }
5843

5944
/// <inheritdoc />
60-
public int Depth { get; }
45+
public int Depth { get; } = depth;
6146

6247
/// <inheritdoc />
63-
public string Id { get; }
48+
public string Id { get; } = ShortId.Create(parentPath);
6449

6550
/// <inheritdoc />
66-
public ILeafNavigationItem<IDocumentationFile> Index { get; private set; }
51+
public ILeafNavigationItem<TModel> Index { get; private set; } = null!;
6752

68-
public IReadOnlyCollection<INavigationItem> NavigationItems { get; private set; }
53+
public IReadOnlyCollection<INavigationItem> NavigationItems { get; private set; } = [];
6954

7055
void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems) => SetNavigationItems(navigationItems);
7156
internal void SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems)
7257
{
73-
var indexNavigation = navigationItems.QueryIndex(this, new NotFoundModel($"{FolderPath}/index.md"), out navigationItems);
58+
var indexNavigation = navigationItems.QueryIndex<TModel>(this, $"{FolderPath}/index.md", out navigationItems);
7459
Index = indexNavigation;
7560
NavigationItems = navigationItems;
7661
}

src/Elastic.Documentation.Navigation/Isolated/TableOfContentsNavigation.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,23 @@ public interface IDocumentationFile : INavigationModel
1414
}
1515

1616
[DebuggerDisplay("{Url}")]
17-
public class TableOfContentsNavigation : IRootNavigationItem<IDocumentationFile, INavigationItem>
17+
public class TableOfContentsNavigation<TModel> : IRootNavigationItem<TModel, INavigationItem>
1818
, INavigationHomeAccessor
1919
, INavigationHomeProvider
20+
where TModel : class, IDocumentationFile
2021
{
2122
public TableOfContentsNavigation(
2223
IDirectoryInfo tableOfContentsDirectory,
2324
int depth,
2425
string parentPath,
2526
INodeNavigationItem<INavigationModel, INavigationItem>? parent,
2627
string pathPrefix,
27-
IReadOnlyCollection<INavigationItem> navigationItems,
2828
GitCheckoutInformation git,
29-
Dictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> tocNodes,
29+
Dictionary<Uri, IRootNavigationItem<TModel, INavigationItem>> tocNodes,
3030
INavigationHomeProvider homeProvider
3131
)
3232
{
3333
TableOfContentsDirectory = tableOfContentsDirectory;
34-
NavigationItems = navigationItems;
3534
Parent = parent;
3635
Hidden = false;
3736
IsUsingNavigationDropdown = false;
@@ -49,10 +48,9 @@ INavigationHomeProvider homeProvider
4948
Identifier = new Uri($"{git.RepositoryName}://{parentPath.TrimEnd('/')}");
5049
_ = tocNodes.TryAdd(Identifier, this);
5150

52-
// FindIndex must be called after _homeProvider is set
53-
var indexNavigation = navigationItems.QueryIndex(this, new NotFoundModel($"{parentPath}/index.md"), out navigationItems);
54-
Index = indexNavigation;
55-
NavigationItems = navigationItems;
51+
// Will be set by SetNavigationItems
52+
Index = null!;
53+
NavigationItems = [];
5654
}
5755

5856
/// <summary>
@@ -103,7 +101,7 @@ INavigationHomeProvider homeProvider
103101
public string Id { get; }
104102

105103
/// <inheritdoc />
106-
public ILeafNavigationItem<IDocumentationFile> Index { get; private set; }
104+
public ILeafNavigationItem<TModel> Index { get; private set; }
107105

108106
/// <inheritdoc />
109107
public bool IsUsingNavigationDropdown { get; }
@@ -117,7 +115,7 @@ INavigationHomeProvider homeProvider
117115
void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems) => SetNavigationItems(navigationItems);
118116
internal void SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems)
119117
{
120-
var indexNavigation = navigationItems.QueryIndex(this, new NotFoundModel($"{ParentPath}/index.md"), out navigationItems);
118+
var indexNavigation = navigationItems.QueryIndex<TModel>(this, $"{ParentPath}/index.md", out navigationItems);
121119
Index = indexNavigation;
122120
NavigationItems = navigationItems;
123121
}

src/Elastic.Documentation.Navigation/NavigationItemExtensions.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,32 @@ public class NotFoundLeafNavigationItem<TModel>(TModel model, INodeNavigationIte
4141

4242
public static class NavigationItemExtensions
4343
{
44-
public static ILeafNavigationItem<IDocumentationFile> QueryIndex<TModel>(
45-
this IReadOnlyCollection<INavigationItem> items, INodeNavigationItem<INavigationModel, INavigationItem> node, TModel fallback, out IReadOnlyCollection<INavigationItem> children
44+
public static ILeafNavigationItem<TModel> QueryIndex<TModel>(
45+
this IReadOnlyCollection<INavigationItem> items, INodeNavigationItem<INavigationModel, INavigationItem> node, string fallbackPath, out IReadOnlyCollection<INavigationItem> children
4646
)
47-
where TModel : IDocumentationFile
47+
where TModel : class, IDocumentationFile
4848
{
4949
var index = LookupIndex();
5050

5151
children = items.Except([index]).ToArray();
5252

5353
return index;
5454

55-
ILeafNavigationItem<IDocumentationFile> LookupIndex()
55+
ILeafNavigationItem<TModel> LookupIndex()
5656
{
57-
var leaf = items.OfType<ILeafNavigationItem<IDocumentationFile>>().FirstOrDefault();
58-
if (leaf is not null)
59-
return leaf;
60-
61-
var nodes = items.OfType<INodeNavigationItem<IDocumentationFile, INavigationItem>>().ToList();
62-
if (nodes.Count == 0)
63-
return new NotFoundLeafNavigationItem<IDocumentationFile>(fallback, node);
64-
65-
return nodes.First().Index;
57+
foreach (var item in items)
58+
{
59+
// Check for exact type match
60+
if (item is ILeafNavigationItem<TModel> leaf)
61+
return leaf;
62+
63+
// Check if this is a node navigation item and return its index
64+
if (item is INodeNavigationItem<TModel, INavigationItem> nodeItem)
65+
return nodeItem.Index;
66+
}
67+
68+
// If no index is found, throw an exception
69+
throw new InvalidOperationException($"No index found for navigation node '{node.GetType().Name}' at path '{fallbackPath}'");
6670
}
6771
}
6872

src/Elastic.Markdown/IO/DocumentationSet.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Elastic.Markdown.IO.NewNavigation;
2121
using Elastic.Markdown.Myst;
2222
using Microsoft.Extensions.Logging;
23+
using YamlDotNet.Serialization.TypeInspectors;
2324

2425
namespace Elastic.Markdown.IO;
2526

@@ -121,28 +122,23 @@ ICrossLinkResolver linkResolver
121122

122123
private IReadOnlyCollection<INavigationItem> CreateNavigationLookup(INavigationItem item)
123124
{
124-
// warning emit an error again
125125
switch (item)
126126
{
127127
case ILeafNavigationItem<MarkdownFile> markdownLeaf:
128128
var added = MarkdownNavigationLookup.TryAdd(markdownLeaf.Model, markdownLeaf);
129129
if (!added)
130-
Context.EmitWarning(Configuration.SourceFile, $"1. Duplicate navigation item {markdownLeaf.Model.CrossLink}");
130+
Context.EmitWarning(Configuration.SourceFile, $"Duplicate navigation item {markdownLeaf.Model.CrossLink}");
131131
return [markdownLeaf];
132+
case ILeafNavigationItem<CrossLinkModel> crossLink:
133+
return [crossLink];
132134
case ILeafNavigationItem<INavigationModel> leaf:
133-
return [leaf];
135+
throw new Exception($"Should not be possible to have a leaf navigation item that is not a markdown file: {leaf.Model.GetType().FullName}");
134136
case INodeNavigationItem<MarkdownFile, INavigationItem> node:
135-
var addedNode = MarkdownNavigationLookup.TryAdd(node.Index.Model, node);
136-
if (!addedNode)
137-
Context.EmitWarning(Configuration.SourceFile, $"2. Duplicate navigation item {node.Index.Model.CrossLink}");
137+
_ = MarkdownNavigationLookup.TryAdd(node.Index.Model, node);
138138
var nodeItems = node.NavigationItems.SelectMany(CreateNavigationLookup);
139139
return nodeItems.Concat([node, node.Index]).ToArray();
140140
case INodeNavigationItem<INavigationModel, INavigationItem> node:
141-
var items = node.NavigationItems.SelectMany(CreateNavigationLookup);
142-
if (node.Index.Model is MarkdownFile md)
143-
_ = MarkdownNavigationLookup.TryAdd(md, node.Index);
144-
145-
return items.Concat([node, node.Index]).ToArray();
141+
throw new Exception($"Should not be possible to have a leaf navigation item that is not a markdown file: {node.GetType().FullName}");
146142
default:
147143
return [];
148144
}

tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public void InjectsNestedTocsIntoDocumentationSet()
3333
parent.Parent.Parent.Should().BeNull();
3434

3535
// its parent should point to an index
36-
var index = (parent as TableOfContentsNavigation)?.Index;
36+
var index = (parent as TableOfContentsNavigation<MarkdownFile>)?.Index;
3737
index.Should().NotBeNull();
3838
var fileNav = index as FileNavigationLeaf<MarkdownFile>;
3939
fileNav.Should().NotBeNull();

0 commit comments

Comments
 (0)