ApiExplorer: fail the build on invalid supplemental files - #3936
Conversation
Unmatched op/tag files and unknown parameter or request-body keys must error at generate time so authors catch typos before publish. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Requesting changes: base supplemental operation overrides are not validated for non-main renders, so invalid keys can pass for versioned builds.
What is this? | From workflow: PR Review
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
…y @github-actions) Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Requesting changes: unmatched base supplemental files are not enforced when no main moniker is rendered, so invalid op-*.md/tag-*.md files can still pass generation in that configuration.
What is this? | From workflow: PR Review
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
…thub-actions) Numeric-only products never hit the main unmatched check, so invalid op-/tag- files could slip through. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Requesting changes: unmatched base supplemental validation can still be skipped when main is absent and the first rendered moniker is non-numeric.
What is this? | From workflow: PR Review
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
…eview by @github-actions) The unmatched flag was ignored when the first rendered version was not main or numeric. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Requesting changes: matched base supplemental override-key validation is skipped for non-main, non-numeric monikers, so invalid keys can still pass in that render path.
What is this? | From workflow: PR Review
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
Remove the moniker early return so unmatched and override checks are independent. Version-suffixed files stay numeric-only. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Adds build-time validation for API Explorer supplemental Markdown files against versioned OpenAPI specifications.
Changes:
- Reports unmatched operation/tag supplemental files.
- Validates parameter and request-body override keys.
- Adds version-aware validation tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
ApiSupplementalValidationTests.cs |
Covers supplemental validation scenarios. |
ApiSupplementalValidator.cs |
Implements supplemental-file validation. |
ApiSupplementalDiscovery.cs |
Exposes operations for validation. |
OpenApiGenerator.cs |
Runs validation during generation. |
Suppressed comments (2)
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:56
- This helper has six parameters, exceeding the project's four-parameter method limit and making the version-validation state difficult to pass consistently. Bundle the collected spec entities and validation services into a context/options record rather than extending this parameter list.
private static void ValidateVersionSuffixed(
IReadOnlyList<ApiSupplementalVersionedFile> versionSuffixed,
int major,
IReadOnlyDictionary<string, OpenApiOperation> operationsById,
IReadOnlySet<string> tagSlugs,
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:117
- This helper introduces five parameters, over the project's four-parameter limit. Passing a shared validation context containing the analyzer and collector alongside the file and operation would keep this validation path easier to extend.
private static void ValidateOverrideKeys(
IFileInfo file,
OpenApiOperation operation,
SchemaAnalyzer analyzer,
IDiagnosticsCollector collector,
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| var schema = operation.RequestBody?.Content?.FirstOrDefault().Value?.Schema; | ||
| var properties = analyzer.GetSchemaProperties(schema); | ||
| return new HashSet<string>(properties?.Keys ?? [], StringComparer.OrdinalIgnoreCase); |
There was a problem hiding this comment.
Fixed in 772ca70 — request-body override keys now collect nested, array-item, additionalProperties, and union field names, matching the renderer leaf-name lookup. Traversal stays in the validator; sharing the property-tree builder would be a larger change than this PR needs.
| public static void Validate( | ||
| ApiSupplementalDiscoveryResult discovery, | ||
| OpenApiDocument document, | ||
| IDiagnosticsCollector collector, | ||
| string moniker, |
There was a problem hiding this comment.
Fixed in 4125d46 — Validate now takes discovery plus an ApiSupplementalValidationRequest. The private helpers at the other lines already exceeded four parameters before this PR and are left as-is.
| @@ -227,9 +236,13 @@ private async Task GenerateApiProduct( | |||
| OpenApiDocument openApiDocument, | |||
| ResolvedApiConfiguration? apiConfig, | |||
| IReadOnlyList<ApiVersionSwitcherItem> versionSwitcherItems, | |||
There was a problem hiding this comment.
Fixed in 6000881 — GenerateApiProduct now takes an ApiProductGeneration record plus Cancel. Suffix, document, switcher items, moniker, and unmatched-file policy live on that record.
| private static CapturingDiagnosticsCollector Validate( | ||
| IDirectoryInfo folder, | ||
| OpenApiDocument document, | ||
| string moniker, | ||
| bool? emitUnmatchedBaseFiles = null) |
There was a problem hiding this comment.
Fixed in 99eadcb — default-latest helper is three parameters; the explicit unmatched-file flag is a four-parameter overload.
…-pull-request-reviewer) The renderer matches overrides by leaf name at any depth, so top-level-only validation was a false error. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ew by @copilot-pull-request-reviewer) Validate was over the four-parameter limit. Private helpers that already exceeded it are unchanged. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ull-request-reviewer) Keep the default-latest path at three parameters and the explicit unmatched-file flag at four. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…pull-request-reviewer) GenerateApiProduct was over the four-parameter limit after this PR added moniker and unmatched-file policy. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:131
- Base supplemental files are validated against every generated major, but this error does not identify which version rejected the parameter. For a key present on
mainbut absent from v8, the diagnostic only namesop-*.mdand the operation, leaving the failing spec ambiguous. Include the moniker/spec label in the message.
This issue also appears on line 141 of the same file.
collector.EmitError(file, $"API supplemental: Parameter '{key}' not found in operation '{operationId}'");
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:141
- This diagnostic also omits the version being validated. Since a base request-body override is checked against multiple specs, a field can be valid on latest and fail only on an older major without the message saying which one. Include the moniker/spec label in the error.
collector.EmitError(file, $"API supplemental: Request body field '{key}' not found in operation '{operationId}'");
| EmitUnmatchedBaseFiles: versioned.Version.Moniker == "main" | ||
| || (!hasMain && versioned.Version.Moniker == monikers[0])), |
There was a problem hiding this comment.
Fixed in 1f41a6f — unmatched base files now run only on the declared latest version, and only if that document actually resolved. A failed main fetch no longer treats an older spec as latest.
…review by @copilot-pull-request-reviewer) If main fails to fetch, do not treat an older resolved version as latest. That would fail base files for operations that exist only on main. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:31
- Version-suffixed tag validation rebuilds this set from every tag name, so it re-admits slugs that discovery deliberately classified as collisions. For tags such as
foo barandfoo-bar,tag-foo-bar.v8.mdpasses validation even though it has no unique target; the equivalent base file is unmatched byApiSupplementalDiscovery.IndexTags(lines 144–168). Exclude collision slugs before validating versioned files.
var tagSlugs = new HashSet<string>(tagNames.Select(ApiUrlBuilder.TagSlug), StringComparer.Ordinal);
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:131
- This error can be emitted while validating any generated major, but it does not identify that version. A base file may be valid for
mainand fail only for version 8, leaving identical file/operation diagnostics with no indication of which spec rejected the key. Include the current moniker/spec label in the message so the author can resolve version-specific failures.
This issue also appears on line 141 of the same file.
collector.EmitError(file, $"API supplemental: Parameter '{key}' not found in operation '{operationId}'");
src/Elastic.ApiExplorer/Supplemental/ApiSupplementalValidator.cs:141
- Like the parameter diagnostic, this request-body error omits the spec version being validated. Since a base supplemental file is checked against every matched major, the same field can be valid on
mainbut fail on an older version; include the current moniker/spec label so the error identifies the incompatible spec.
collector.EmitError(file, $"API supplemental: Request body field '{key}' not found in operation '{operationId}'");
…opilot-pull-request-reviewer) Version-suffixed tag checks now use the same unique-slug index as discovery, so tag-foo-bar.v8.md cannot match both foo bar and foo-bar. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ilot-pull-request-reviewer) A base file can be valid on main and fail on an older major. The diagnostic now says which spec rejected the key. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the suppressed items from #3936 (review): |
Why
op-*.mdortag-*.mdfile can name a missing operation, tag, parameter, or request-body field, and the build still succeeds.What
op-*.mdandtag-*.mdfiles against the latest spec.Notes
Made with Cursor