|
7 | 7 | using Elastic.ApiExplorer.Landing; |
8 | 8 | using Elastic.ApiExplorer.Operations; |
9 | 9 | using Elastic.ApiExplorer.Schemas; |
| 10 | +using Elastic.ApiExplorer.Templates; |
10 | 11 | using Elastic.Documentation; |
11 | 12 | using Elastic.Documentation.Configuration; |
| 13 | +using Elastic.Documentation.Configuration.Toc; |
12 | 14 | using Elastic.Documentation.Navigation; |
13 | 15 | using Elastic.Documentation.Site.FileProviders; |
14 | 16 | using Elastic.Documentation.Site.Navigation; |
@@ -44,6 +46,7 @@ public class OpenApiGenerator(ILoggerFactory logFactory, BuildContext context, I |
44 | 46 | private readonly ILogger _logger = logFactory.CreateLogger<OpenApiGenerator>(); |
45 | 47 | private readonly IFileSystem _writeFileSystem = context.WriteFileSystem; |
46 | 48 | private readonly StaticFileContentHashProvider _contentHashProvider = new(new EmbeddedOrPhysicalFileProvider(context)); |
| 49 | + private readonly TemplateProcessor _templateProcessor = TemplateProcessorFactory.Create(markdownStringRenderer, context.ReadFileSystem); |
47 | 50 |
|
48 | 51 | public LandingNavigationItem CreateNavigation(string apiUrlSuffix, OpenApiDocument openApiDocument) |
49 | 52 | { |
@@ -208,41 +211,79 @@ List<IEndpointOrOperationNavigationItem> endpointNavigationItems |
208 | 211 |
|
209 | 212 | public async Task Generate(Cancel ctx = default) |
210 | 213 | { |
211 | | - if (context.Configuration.OpenApiSpecifications is null) |
212 | | - return; |
| 214 | + // Use the new API configurations if available, otherwise fall back to legacy OpenApiSpecifications |
| 215 | + if (context.Configuration.ApiConfigurations is not null) |
| 216 | + { |
| 217 | + foreach (var (prefix, apiConfig) in context.Configuration.ApiConfigurations) |
| 218 | + { |
| 219 | + // Validate assumption of single spec per product |
| 220 | + if (apiConfig.SpecFiles.Count > 1) |
| 221 | + throw new InvalidOperationException($"API product '{prefix}' has {apiConfig.SpecFiles.Count} spec files, but only one spec file per product is currently supported."); |
213 | 222 |
|
214 | | - foreach (var (prefix, path) in context.Configuration.OpenApiSpecifications) |
| 223 | + var openApiDocument = await OpenApiReader.Create(apiConfig.PrimarySpecFile); |
| 224 | + if (openApiDocument is null) |
| 225 | + continue; |
| 226 | + |
| 227 | + await GenerateApiProduct(prefix, openApiDocument, apiConfig, ctx); |
| 228 | + } |
| 229 | + } |
| 230 | + else if (context.Configuration.OpenApiSpecifications is not null) |
215 | 231 | { |
216 | | - var openApiDocument = await OpenApiReader.Create(path); |
217 | | - if (openApiDocument is null) |
218 | | - return; |
| 232 | + // Legacy fallback |
| 233 | + foreach (var (prefix, path) in context.Configuration.OpenApiSpecifications) |
| 234 | + { |
| 235 | + var openApiDocument = await OpenApiReader.Create(path); |
| 236 | + if (openApiDocument is null) |
| 237 | + continue; |
219 | 238 |
|
220 | | - var navigation = CreateNavigation(prefix, openApiDocument); |
221 | | - _logger.LogInformation("Generating OpenApiDocument {Title}", openApiDocument.Info.Title); |
| 239 | + await GenerateApiProduct(prefix, openApiDocument, null, ctx); |
| 240 | + } |
| 241 | + } |
| 242 | + } |
222 | 243 |
|
223 | | - var navigationRenderer = new IsolatedBuildNavigationHtmlWriter(context, navigation); |
| 244 | + private async Task GenerateApiProduct(string prefix, OpenApiDocument openApiDocument, ResolvedApiConfiguration? apiConfig, Cancel ctx) |
| 245 | + { |
| 246 | + var navigation = CreateNavigation(prefix, openApiDocument); |
| 247 | + _logger.LogInformation("Generating OpenApiDocument {Title}", openApiDocument.Info?.Title ?? "<no title>"); |
224 | 248 |
|
225 | | - var renderContext = new ApiRenderContext(context, openApiDocument, _contentHashProvider) |
226 | | - { |
227 | | - NavigationHtml = string.Empty, |
228 | | - CurrentNavigation = navigation, |
229 | | - MarkdownRenderer = markdownStringRenderer |
230 | | - }; |
231 | | - _ = await Render(prefix, navigation, navigation.Index.Model, renderContext, navigationRenderer, ctx); |
232 | | - await RenderNavigationItems(prefix, renderContext, navigationRenderer, navigation, ctx); |
| 249 | + var navigationRenderer = new IsolatedBuildNavigationHtmlWriter(context, navigation); |
233 | 250 |
|
| 251 | + TemplateLanding? templateLanding = null; |
| 252 | + if (apiConfig?.HasCustomTemplate == true) |
| 253 | + { |
| 254 | + var templateContent = await _templateProcessor.ProcessTemplateAsync(apiConfig, ctx); |
| 255 | + if (!string.IsNullOrWhiteSpace(templateContent)) |
| 256 | + templateLanding = new TemplateLanding(templateContent); |
234 | 257 | } |
| 258 | + |
| 259 | + var renderContext = new ApiRenderContext(context, openApiDocument, _contentHashProvider) |
| 260 | + { |
| 261 | + NavigationHtml = string.Empty, |
| 262 | + CurrentNavigation = navigation, |
| 263 | + MarkdownRenderer = markdownStringRenderer, |
| 264 | + TemplateLandingPage = templateLanding |
| 265 | + }; |
| 266 | + |
| 267 | + await RenderNavigationItems(prefix, renderContext, navigationRenderer, navigation, navigation, ctx); |
235 | 268 | } |
236 | 269 |
|
237 | | - private async Task RenderNavigationItems(string prefix, ApiRenderContext renderContext, IsolatedBuildNavigationHtmlWriter navigationRenderer, INavigationItem currentNavigation, Cancel ctx) |
| 270 | + private async Task RenderNavigationItems( |
| 271 | + string prefix, |
| 272 | + ApiRenderContext renderContext, |
| 273 | + IsolatedBuildNavigationHtmlWriter navigationRenderer, |
| 274 | + INavigationItem currentNavigation, |
| 275 | + INavigationItem rootNavigation, |
| 276 | + Cancel ctx) |
238 | 277 | { |
239 | 278 | if (currentNavigation is INodeNavigationItem<IApiModel, INavigationItem> node) |
240 | 279 | { |
241 | | - _ = await Render(prefix, node, node.Index.Model, renderContext, navigationRenderer, ctx); |
| 280 | + _ = renderContext.TemplateLandingPage is { } templateLanding && ReferenceEquals(currentNavigation, rootNavigation) |
| 281 | + ? await Render(prefix, node, templateLanding, renderContext, navigationRenderer, ctx) |
| 282 | + : await Render(prefix, node, node.Index.Model, renderContext, navigationRenderer, ctx); |
| 283 | + |
242 | 284 | foreach (var child in node.NavigationItems) |
243 | | - await RenderNavigationItems(prefix, renderContext, navigationRenderer, child, ctx); |
| 285 | + await RenderNavigationItems(prefix, renderContext, navigationRenderer, child, rootNavigation, ctx); |
244 | 286 | } |
245 | | - |
246 | 287 | else |
247 | 288 | { |
248 | 289 | _ = currentNavigation is ILeafNavigationItem<IApiModel> leaf |
|
0 commit comments