Skip to content

Commit 8f7c6d9

Browse files
committed
Allow multiple API source to be specified in a single docset
1 parent a48896f commit 8f7c6d9

File tree

6 files changed

+70
-54
lines changed

6 files changed

+70
-54
lines changed

docs/_docset.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ subs:
2020
features:
2121
primary-nav: false
2222

23-
#api: kibana-openapi.json
24-
api: elasticsearch-openapi.json
23+
api:
24+
elasticsearch: elasticsearch-openapi.json
25+
kibana: kibana-openapi.json
2526

2627
toc:
2728
- file: index.md

src/Elastic.ApiExplorer/OpenApiGenerator.cs

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ public class OpenApiGenerator(BuildContext context, IMarkdownStringRenderer mark
4444
private readonly IFileSystem _writeFileSystem = context.WriteFileSystem;
4545
private readonly StaticFileContentHashProvider _contentHashProvider = new(new EmbeddedOrPhysicalFileProvider(context));
4646

47-
public LandingNavigationItem CreateNavigation(OpenApiDocument openApiDocument)
47+
public LandingNavigationItem CreateNavigation(string apiUrlSuffix, OpenApiDocument openApiDocument)
4848
{
49-
var url = $"{context.UrlPathPrefix}/api";
49+
var url = $"{context.UrlPathPrefix}/api/" + apiUrlSuffix;
5050
var rootNavigation = new LandingNavigationItem(url);
5151

5252
var ops = openApiDocument.Paths
@@ -163,20 +163,21 @@ group tagGroup by classificationGroup.Key
163163
var classificationNavigationItem = new ClassificationNavigationItem(classification, rootNavigation, rootNavigation);
164164
var tagNavigationItems = new List<IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem>>();
165165

166-
CreateTagNavigationItems(classification, classificationNavigationItem, classificationNavigationItem, tagNavigationItems);
166+
CreateTagNavigationItems(apiUrlSuffix, classification, classificationNavigationItem, classificationNavigationItem, tagNavigationItems);
167167
topLevelNavigationItems.Add(classificationNavigationItem);
168168
// if there is only a single tag item will be added directly to the classificationNavigationItem, otherwise they will be added to the tagNavigationItems
169169
if (classificationNavigationItem.NavigationItems.Count == 0)
170170
classificationNavigationItem.NavigationItems = tagNavigationItems;
171171
}
172172
else
173-
CreateTagNavigationItems(classification, rootNavigation, rootNavigation, topLevelNavigationItems);
173+
CreateTagNavigationItems(apiUrlSuffix, classification, rootNavigation, rootNavigation, topLevelNavigationItems);
174174
}
175175
rootNavigation.NavigationItems = topLevelNavigationItems;
176176
return rootNavigation;
177177
}
178178

179179
private void CreateTagNavigationItems(
180+
string apiUrlSuffix,
180181
ApiClassification classification,
181182
IRootNavigationItem<IApiGroupingModel, INavigationItem> rootNavigation,
182183
IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parent,
@@ -190,13 +191,13 @@ List<IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem>> parentNavig
190191
if (hasTags)
191192
{
192193
var tagNavigationItem = new TagNavigationItem(tag, rootNavigation, parent);
193-
CreateEndpointNavigationItems(rootNavigation, tag, tagNavigationItem, endpointNavigationItems);
194+
CreateEndpointNavigationItems(apiUrlSuffix, rootNavigation, tag, tagNavigationItem, endpointNavigationItems);
194195
parentNavigationItems.Add(tagNavigationItem);
195196
tagNavigationItem.NavigationItems = endpointNavigationItems;
196197
}
197198
else
198199
{
199-
CreateEndpointNavigationItems(rootNavigation, tag, parent, endpointNavigationItems);
200+
CreateEndpointNavigationItems(apiUrlSuffix, rootNavigation, tag, parent, endpointNavigationItems);
200201
if (parent is ClassificationNavigationItem classificationNavigationItem)
201202
classificationNavigationItem.NavigationItems = endpointNavigationItems;
202203
else if (parent is LandingNavigationItem landingNavigationItem)
@@ -206,6 +207,7 @@ List<IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem>> parentNavig
206207
}
207208

208209
private void CreateEndpointNavigationItems(
210+
string apiUrlSuffix,
209211
IRootNavigationItem<IApiGroupingModel, INavigationItem> rootNavigation,
210212
ApiTag tag,
211213
IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parentNavigationItem,
@@ -220,7 +222,7 @@ List<IEndpointOrOperationNavigationItem> endpointNavigationItems
220222
var operationNavigationItems = new List<OperationNavigationItem>();
221223
foreach (var operation in endpoint.Operations)
222224
{
223-
var operationNavigationItem = new OperationNavigationItem(context.UrlPathPrefix, operation, rootNavigation, endpointNavigationItem)
225+
var operationNavigationItem = new OperationNavigationItem(context.UrlPathPrefix, apiUrlSuffix, operation, rootNavigation, endpointNavigationItem)
224226
{
225227
Hidden = true
226228
};
@@ -232,7 +234,7 @@ List<IEndpointOrOperationNavigationItem> endpointNavigationItems
232234
else
233235
{
234236
var operation = endpoint.Operations.First();
235-
var operationNavigationItem = new OperationNavigationItem(context.UrlPathPrefix, operation, rootNavigation, parentNavigationItem);
237+
var operationNavigationItem = new OperationNavigationItem(context.UrlPathPrefix, apiUrlSuffix, operation, rootNavigation, parentNavigationItem);
236238
endpointNavigationItems.Add(operationNavigationItem);
237239

238240
}
@@ -241,47 +243,51 @@ List<IEndpointOrOperationNavigationItem> endpointNavigationItems
241243

242244
public async Task Generate(Cancel ctx = default)
243245
{
244-
if (context.Configuration.OpenApiSpecification is null)
246+
if (context.Configuration.OpenApiSpecifications is null)
245247
return;
246248

247-
var openApiDocument = await OpenApiReader.Create(context.Configuration.OpenApiSpecification);
248-
if (openApiDocument is null)
249-
return;
250-
251-
var navigation = CreateNavigation(openApiDocument);
252-
_logger.LogInformation("Generating OpenApiDocument {Title}", openApiDocument.Info.Title);
253-
254-
var navigationRenderer = new IsolatedBuildNavigationHtmlWriter(context, navigation);
249+
foreach (var (prefix, path) in context.Configuration.OpenApiSpecifications)
250+
{
251+
var openApiDocument = await OpenApiReader.Create(path);
252+
if (openApiDocument is null)
253+
return;
255254

255+
var navigation = CreateNavigation(prefix, openApiDocument);
256+
_logger.LogInformation("Generating OpenApiDocument {Title}", openApiDocument.Info.Title);
256257

257-
var renderContext = new ApiRenderContext(context, openApiDocument, _contentHashProvider)
258-
{
259-
NavigationHtml = string.Empty,
260-
CurrentNavigation = navigation,
261-
MarkdownRenderer = markdownStringRenderer
262-
};
263-
_ = await Render(navigation, navigation.Index, renderContext, navigationRenderer, ctx);
264-
await RenderNavigationItems(navigation);
258+
var navigationRenderer = new IsolatedBuildNavigationHtmlWriter(context, navigation);
265259

266-
async Task RenderNavigationItems(INavigationItem currentNavigation)
267-
{
268-
if (currentNavigation is INodeNavigationItem<IApiModel, INavigationItem> node)
260+
var renderContext = new ApiRenderContext(context, openApiDocument, _contentHashProvider)
269261
{
270-
_ = await Render(node, node.Index, renderContext, navigationRenderer, ctx);
271-
foreach (var child in node.NavigationItems)
272-
await RenderNavigationItems(child);
273-
}
262+
NavigationHtml = string.Empty,
263+
CurrentNavigation = navigation,
264+
MarkdownRenderer = markdownStringRenderer
265+
};
266+
_ = await Render(prefix, navigation, navigation.Index, renderContext, navigationRenderer, ctx);
267+
await RenderNavigationItems(navigation);
268+
async Task RenderNavigationItems(INavigationItem currentNavigation)
269+
{
270+
if (currentNavigation is INodeNavigationItem<IApiModel, INavigationItem> node)
271+
{
272+
_ = await Render(prefix, node, node.Index, renderContext, navigationRenderer, ctx);
273+
foreach (var child in node.NavigationItems)
274+
await RenderNavigationItems(child);
275+
}
274276

275-
#pragma warning disable IDE0045
276-
else if (currentNavigation is ILeafNavigationItem<IApiModel> leaf)
277-
#pragma warning restore IDE0045
278-
_ = await Render(leaf, leaf.Model, renderContext, navigationRenderer, ctx);
279-
else
280-
throw new Exception($"Unknown navigation item type {currentNavigation.GetType()}");
277+
else
278+
{
279+
_ = currentNavigation is ILeafNavigationItem<IApiModel> leaf
280+
? await Render(prefix, leaf, leaf.Model, renderContext, navigationRenderer, ctx)
281+
: throw new Exception($"Unknown navigation item type {currentNavigation.GetType()}");
282+
}
283+
}
281284
}
282285
}
283286

284-
private async Task<IFileInfo> Render<T>(INavigationItem current, T page, ApiRenderContext renderContext, IsolatedBuildNavigationHtmlWriter navigationRenderer, Cancel ctx)
287+
#pragma warning disable IDE0060
288+
private async Task<IFileInfo> Render<T>(string prefix, INavigationItem current, T page, ApiRenderContext renderContext,
289+
#pragma warning restore IDE0060
290+
IsolatedBuildNavigationHtmlWriter navigationRenderer, Cancel ctx)
285291
where T : INavigationModel, IPageRenderer<ApiRenderContext>
286292
{
287293
var outputFile = OutputFile(current);

src/Elastic.ApiExplorer/Operations/OperationNavigationItem.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class OperationNavigationItem : ILeafNavigationItem<ApiOperation>, IEndpo
2929
{
3030
public OperationNavigationItem(
3131
string? urlPathPrefix,
32+
string apiUrlSuffix,
3233
ApiOperation apiOperation,
3334
IRootNavigationItem<IApiGroupingModel, INavigationItem> root,
3435
IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parent
@@ -39,7 +40,7 @@ IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parent
3940
NavigationTitle = apiOperation.ApiName;
4041
Parent = parent;
4142
var moniker = apiOperation.Operation.OperationId ?? apiOperation.Route.Replace("}", "").Replace("{", "").Replace('/', '-');
42-
Url = $"{urlPathPrefix}/api/endpoints/{moniker}";
43+
Url = $"{urlPathPrefix?.TrimEnd('/')}/api/{apiUrlSuffix}/{moniker}";
4344
Id = ShortId.Create(Url);
4445
}
4546

src/Elastic.ApiExplorer/Operations/OperationView.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
var method = overload.Model.OperationType.ToString().ToLowerInvariant();
3535
var current = overload.Model.Route == Model.Operation.Route && overload.Model.OperationType == Model.Operation.OperationType ? "current" : "";
3636
<li class="api-url-list-item">
37-
<a href="@overload.Url" class="@current">
37+
<a href="@overload.Url" class="@current" hx-disable="true">
3838
<span class="api-method api-method-@method">@method.ToUpperInvariant()</span>
3939
<span class="api-url">@overload.Model.Route</span>
4040
</a>

src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public record ConfigurationFile : ITableOfContentsScope
5050

5151
public IDirectoryInfo ScopeDirectory { get; }
5252

53-
public IFileInfo? OpenApiSpecification { get; }
53+
public IReadOnlyDictionary<string, IFileInfo>? OpenApiSpecifications { get; }
5454

5555
/// This is a documentation set that is not linked to by assembler.
5656
/// Setting this to true relaxes a few restrictions such as mixing toc references with file and folder reference
@@ -111,11 +111,18 @@ public ConfigurationFile(IDocumentationContext context)
111111
// read this later
112112
break;
113113
case "api":
114-
var specification = reader.ReadString(entry.Entry);
115-
if (specification is null)
114+
var configuredApis = reader.ReadDictionary(entry.Entry);
115+
if (configuredApis.Count == 0)
116116
break;
117-
var path = Path.Combine(context.DocumentationSourceDirectory.FullName, specification);
118-
OpenApiSpecification = context.ReadFileSystem.FileInfo.New(path);
117+
118+
var specs = new Dictionary<string, IFileInfo>(StringComparer.OrdinalIgnoreCase);
119+
foreach (var (k, v) in configuredApis)
120+
{
121+
var path = Path.Combine(context.DocumentationSourceDirectory.FullName, v);
122+
var fi = context.ReadFileSystem.FileInfo.New(path);
123+
specs[k] = fi;
124+
}
125+
OpenApiSpecifications = specs;
119126
break;
120127
case "products":
121128
if (entry.Entry.Value is not YamlSequenceNode sequence)
@@ -129,7 +136,7 @@ public ConfigurationFile(IDocumentationContext context)
129136
YamlScalarNode? productId = null;
130137
foreach (var child in node.Children)
131138
{
132-
if (child.Key is YamlScalarNode { Value: "id" } && child.Value is YamlScalarNode scalarNode)
139+
if (child is { Key: YamlScalarNode { Value: "id" }, Value: YamlScalarNode scalarNode })
133140
{
134141
productId = scalarNode;
135142
break;

tests/Elastic.ApiExplorer.Tests/ReaderTests.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ public async Task Reads()
3535
};
3636
var context = new BuildContext(collector, new FileSystem(), versionsConfig);
3737

38-
context.Configuration.OpenApiSpecification.Should().NotBeNull();
38+
context.Configuration.OpenApiSpecifications.Should().NotBeNull().And.NotBeEmpty();
3939

40-
var x = await OpenApiReader.Create(context.Configuration.OpenApiSpecification);
40+
var x = await OpenApiReader.Create(context.Configuration.OpenApiSpecifications.First().Value);
4141

4242
x.Should().NotBeNull();
4343
x.BaseUri.Should().NotBeNull();
@@ -63,11 +63,12 @@ public async Task Navigation()
6363
var collector = new DiagnosticsCollector([]);
6464
var context = new BuildContext(collector, new FileSystem(), versionsConfig);
6565
var generator = new OpenApiGenerator(context, NoopMarkdownStringRenderer.Instance, NullLoggerFactory.Instance);
66-
context.Configuration.OpenApiSpecification.Should().NotBeNull();
66+
context.Configuration.OpenApiSpecifications.Should().NotBeNull().And.NotBeEmpty();
6767

68-
var openApiDocument = await OpenApiReader.Create(context.Configuration.OpenApiSpecification);
68+
var (urlPathPrefix, fi) = context.Configuration.OpenApiSpecifications.First();
69+
var openApiDocument = await OpenApiReader.Create(fi);
6970
openApiDocument.Should().NotBeNull();
70-
var navigation = generator.CreateNavigation(openApiDocument);
71+
var navigation = generator.CreateNavigation(urlPathPrefix, openApiDocument);
7172

7273
navigation.Should().NotBeNull();
7374
}

0 commit comments

Comments
 (0)