Skip to content

Validate OpenAPI schema references - #2459

Merged
Vincent Biret (baywet) merged 8 commits into
microsoft:mainfrom
martincostello:gh-2453-validate-schema-references
Aug 19, 2025
Merged

Validate OpenAPI schema references#2459
Vincent Biret (baywet) merged 8 commits into
microsoft:mainfrom
martincostello:gh-2453-validate-schema-references

Conversation

@martincostello

@martincostelloMartin Costello (martincostello) commented Aug 8, 2025

Copy link
Copy Markdown
Contributor

Add validation rule for OpenAPI document schema references.

Resolves#2453.


Initial draft for now based on testing this approach with dotnet/aspnetcore#63095. Needs tests, plus rebasing after #2460 is merged.

Comment threadsrc/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs Outdated
@martincostello
Martin Costello (martincostello) marked this pull request as ready for review August 15, 2025 12:36
CopilotAI review requested due to automatic review settings August 15, 2025 12:36

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds validation for OpenAPI schema references to ensure they point to existing schemas in the document. The validation rule detects both invalid references and circular reference patterns.

  • Adds a new validation rule OpenApiDocumentReferencesAreValid that checks schema reference validity
  • Implements detection of circular references with appropriate error messaging
  • Updates test counts to reflect the addition of the new validation rule

Reviewed Changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.csImplements the main validation logic with schema reference visitor
src/Microsoft.OpenApi/Properties/SRResource.resxAdds error message for invalid schema references
test/Microsoft.OpenApi.Tests/Validations/OpenApiDocumentValidationTests.csComprehensive test coverage for valid, invalid, and circular references
test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.csUpdates expected rule count from 19 to 20
test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txtAdds new public API for the validation rule
Files not reviewed (1)
  • src/Microsoft.OpenApi/Properties/SRResource.Designer.cs: Language not supported

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadsrc/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs Outdated
Comment threadsrc/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs Outdated
Comment threadsrc/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs Outdated

@baywetVincent Biret (baywet) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

Comment threadsrc/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs Outdated
Comment threadsrc/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs Outdated
Add validation rule for OpenAPI document schema references.
Resolvesmicrosoft#2453.
Add two basic unit tests and a fast path for when the components are registered.
- Improve handling of circular references.
- Improve the path.
Add a test for circular schema references.
Copy-pasted into the wrong place during a refactor.
Avoid allocating the segment for the context if the reference is valid.
Parse the document invariantly.
Remove reparsing of the document and just validate the in-memory `OpenApiDocument` instead.

@baywetVincent Biret (baywet) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making the changes!

@baywet
Vincent Biret (baywet) enabled auto-merge (squash) August 19, 2025 13:09
@baywet
Vincent Biret (baywet) merged commit 90b3966 into microsoft:mainAug 19, 2025
8 of 9 checks passed
@martincostello
Martin Costello (martincostello) deleted the gh-2453-validate-schema-references branch August 19, 2025 13:16
@baywet

Copy link
Copy Markdown
Member

Martin Costello (@martincostello) this change resulted in a 15% memory allocation increase for smaller descriptions
https://github.com/microsoft/OpenAPI.NET/actions/runs/17043844187/job/48398121099
As far as I can tell, the only allocation that are being made, are here

Would you please mind looking into it, and see if you can reduce allocations? or follow through with an update to the baseline?

@martincostello

Martin Costello (martincostello) commented Aug 19, 2025

Copy link
Copy Markdown
ContributorAuthor

I'll have a quick look later, but I would imagine it'll be a baseline change.

My guess would be that it's paying for enumerators for walking over the whole document tree, but that's the point of the validator (and it's leveraging the existing infrastructure for that).

Did your baseline/performance test just never touch the walker before?

@baywet

Copy link
Copy Markdown
Member

I haven't checked in details but I think it did since the walker is used by other default validation rules.

@martincostello

Copy link
Copy Markdown
ContributorAuthor

Initial findings:

  1. The OpenApiWalker was already covered by the benchmarks because loading a document invokes this method, which uses the walker:
    document.SetReferenceHostDocument();
  2. The new code is covered by the benchmark because loading an OpenAPI document with the default settings implicitly validates a document against all the built-in rules (which isn't something I knew it did) here:
    varopenApiErrors=document.Validate(settings.RuleSet);
  3. The checked-in benchmarks use a different version of BenchmarkDotNet and are several months old. It's possible that this PR made just enough changes to tip over the failure threshold. I'll need to verify if that's the case by checking out 10b46b9 and running the benchmarks on my system to get a comparable baseline, then running them again with 90b3966 and comparing the results.

@martincostello

Copy link
Copy Markdown
ContributorAuthor

You also might find this interesting: Continuous Benchmarks on a Budget

This is something I set up for my own projects' benchmarks that lets me track trends in the benchmark results over time and visualise them.

Martin Costello (martincostello) added a commit to martincostello/OpenAPI.NET that referenced this pull request Aug 19, 2025
Update benchmarks for microsoft#2459 investigation.
Martin Costello (martincostello) added a commit to martincostello/OpenAPI.NET that referenced this pull request Aug 19, 2025
Update benchmarks for microsoft#2459 investigation after changes.
@martincostello

Copy link
Copy Markdown
ContributorAuthor

Having done item 3, there's definitely a difference before and after #2459.

The TL;DR for each benchmark is:

MethodMean BeforeMean AfterRatioMemory BeforeMemory AfterRatio
PetStoreYaml265.6 μs322.6 μs1.21387.12 KB445.71 KB1.15
PetStoreJson106.6 μs151.7 μs1.42249.26 KB307.85 KB1.23
GHESYaml774,833.9 μs127,873.81 μs1.07400088.73 KB422174.19 KB1.05
GHESJson364,114.2 μs576,195.19 μs1.09261558.87 KB283644.3 KB1.08

As you've noted, there's not much actively being allocated by the rule in and of itself (just OpenApiSchemaReferenceVisitor and OpenApiWalker).

My hunch is either something to do with calls into OpenApiSchemaReference.RecursiveTarget, or there's errors being found by walking the documents, so there's just a tonne of allocations from warnings being created. I'll look at that next.

Before


BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4946/24H2/2024Update/HudsonValley)
13th Gen Intel Core i7-13700H 2.90GHz, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.413
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
ShortRun : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 
MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml265.6 μs53.73 μs2.94 μs31.25007.8125-387.12 KB
PetStoreJson106.6 μs39.80 μs2.18 μs20.01955.3711-249.26 KB
GHESYaml774,833.9 μs155,894.04 μs8,545.08 μs36000.000019000.00004000.0000400088.73 KB
GHESJson364,114.2 μs174,868.35 μs9,585.12 μs22000.000012000.00002000.0000261558.87 KB

After


BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4946/24H2/2024Update/HudsonValley)
13th Gen Intel Core i7-13700H 2.90GHz, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.413
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
ShortRun : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 
MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml322.6 μs251.77 μs13.80 μs35.15637.8125-445.71 KB
PetStoreJson151.7 μs56.10 μs3.07 μs24.41415.8594-307.85 KB
GHESYaml829,977.0 μs127,873.81 μs7,009.19 μs38000.000019000.00004000.0000422174.19 KB
GHESJson397,127.5 μs576,195.19 μs31,583.20 μs23000.000012000.00002000.0000283644.3 KB

@baywet

Copy link
Copy Markdown
Member

Thank you for the additional information.

The article is great! I had not come across it before.

I think so far we've established the memory increase is due to the code change, would you agree with that statement?

@martincostello

Martin Costello (martincostello) commented Aug 19, 2025

Copy link
Copy Markdown
ContributorAuthor

I haven't found any particular smoking gun introduced by the new code other than "the extra validation rule means more work gets done".

I have however found various things in OpenApiWalker that can be refactored to reduce allocations, but I won't get any further with that local refactoring today to get a PR to open, so I'll continue with that tomorrow.

Here's an example of where I'm up to so far (re-run benchmarks from main diffed against my local changes):

image

@martincostello

Copy link
Copy Markdown
ContributorAuthor

With these changes ce6497e (needs further cleanup before a PR) I get these numbers compared to the re-baseline before #2459:

MethodMean BeforeMean AfterRatioMemory BeforeMemory AfterRatio
PetStoreYaml265.6 μs311.7 μs1.17387.12 KB434.34 KB1.12
PetStoreJson106.6 μs142.7 μ1.33249.26 KB296.48 KB1.18
GHESYaml774,833.9 μs796,230.8 μs1.02400088.73 KB404377.45 KB1.01
GHESJson364,114.2 μs359,560.4 μs0.98261558.87 KB265847.63 KB1.01

This reduces the ratios, and I think reinforces my view that the "regression" is just "does more work" rather than any due to any deficiencies in the change made (plus GHESJson is now ~2% faster despite doing more work).


BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4946/24H2/2024Update/HudsonValley)
13th Gen Intel Core i7-13700H 2.90GHz, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.413
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
ShortRun : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 
MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml311.7 μs90.14 μs4.94 μs35.15637.8125-434.34 KB
PetStoreJson142.7 μs28.80 μs1.58 μs23.43756.8359-296.48 KB
GHESYaml796,230.8 μs296,583.76 μs16,256.76 μs37000.000019000.00004000.0000404377.45 KB
GHESJson359,560.4 μs122,198.31 μs6,698.10 μs22000.000012000.00002000.0000265847.63 KB

@baywet

Copy link
Copy Markdown
Member

This is great! Yes that's what I meant, as opposed to "is a random change in how Benchmark.net counts things up"

@martincostello

Copy link
Copy Markdown
ContributorAuthor

Opening a PR shortly, but with the latest round of tweaks I get these results:

MethodMean BeforeMean AfterRatioMemory BeforeMemory AfterRatio
PetStoreYaml265.6 μs292.4 μs1.10387.12 KB421.22 KB1.08
PetStoreJson106.6 μs142.3 μs1.33249.26 KB283.36 KB1.13
GHESYaml774,833.9 μs792,979.6 μs1.02400088.73 KB390824.4 KB0.97
GHESJson364,114.2 μs368,942.1 μs1.01261558.87 KB252294.51 KB0.96

BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4946/24H2/2024Update/HudsonValley)
13th Gen Intel Core i7-13700H 2.90GHz, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.413
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
ShortRun : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 
MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml292.4 μs70.22 μs3.85 μs33.20317.8125-421.22 KB
PetStoreJson142.3 μs23.19 μs1.27 μs22.46094.8828-283.36 KB
GHESYaml792,979.6 μs49,213.49 μs2,697.56 μs35000.000019000.00004000.0000390824.4 KB
GHESJson368,942.1 μs134,561.89 μs7,375.79 μs21000.000012000.00002000.0000252294.51 KB

@martincostello

Copy link
Copy Markdown
ContributorAuthor

#2470

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add default Validation Rule(s) for schema reference validity

3 participants

@martincostello@baywet