Skip to content

Commit 2988c4b

Browse files
committed
Performance fixes for validation and reference resolution
1 parent d1a05fa commit 2988c4b

19 files changed

+130
-26
lines changed

src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class OpenApiReaderSettings
4747
/// <summary>
4848
/// Rules to use for validating OpenAPI specification. If none are provided a default set of rules are applied.
4949
/// </summary>
50-
public ValidationRuleSet RuleSet { get; set; }
50+
public ValidationRuleSet RuleSet { get; set; } = ValidationRuleSet.GetDefaultRuleSet();
5151

5252
}
5353
}

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,14 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic)
9090
}
9191

9292
// Validate the document
93-
var errors = document.Validate(_settings.RuleSet);
94-
foreach (var item in errors)
93+
if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0)
9594
{
96-
diagnostic.Errors.Add(item);
97-
}
95+
var errors = document.Validate(_settings.RuleSet);
96+
foreach (var item in errors)
97+
{
98+
diagnostic.Errors.Add(item);
99+
}
100+
}
98101

99102
return document;
100103
}

src/Microsoft.OpenApi.Workbench/MainModel.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.OpenApi.Extensions;
1010
using Microsoft.OpenApi.Models;
1111
using Microsoft.OpenApi.Readers;
12+
using Microsoft.OpenApi.Validations;
1213

1314
namespace Microsoft.OpenApi.Workbench
1415
{
@@ -19,6 +20,8 @@ public class MainModel : INotifyPropertyChanged
1920
{
2021
private string _input;
2122

23+
private string _inputFile;
24+
2225
private string _output;
2326

2427
private string _errors;
@@ -47,6 +50,16 @@ public string Input
4750
}
4851
}
4952

53+
public string InputFile
54+
{
55+
get => _inputFile;
56+
set
57+
{
58+
_inputFile = value;
59+
OnPropertyChanged(nameof(InputFile));
60+
}
61+
}
62+
5063
public string Output
5164
{
5265
get => _output;
@@ -153,12 +166,26 @@ internal void Validate()
153166
{
154167
try
155168
{
156-
var stream = CreateStream(_input);
169+
Stream stream;
170+
if (!String.IsNullOrWhiteSpace(_inputFile))
171+
{
172+
stream = new FileStream(_inputFile, FileMode.Open);
173+
}
174+
else
175+
{
176+
stream = CreateStream(_input);
177+
}
178+
157179

158180
var stopwatch = new Stopwatch();
159181
stopwatch.Start();
160182

161-
var document = new OpenApiStreamReader().Read(stream, out var context);
183+
var document = new OpenApiStreamReader(new OpenApiReaderSettings
184+
{
185+
ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences,
186+
RuleSet = ValidationRuleSet.GetDefaultRuleSet()
187+
}
188+
).Read(stream, out var context);
162189
stopwatch.Stop();
163190
ParseTime = $"{stopwatch.ElapsedMilliseconds} ms";
164191

src/Microsoft.OpenApi.Workbench/MainWindow.xaml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,24 @@
1111
<ColumnDefinition/>
1212
<ColumnDefinition />
1313
</Grid.ColumnDefinitions>
14-
<TextBox x:Name="txtInput" Text="{Binding Input}" VerticalScrollBarVisibility="Auto" Margin="10" TextWrapping="Wrap" AcceptsReturn="True" FontFamily="Consolas" />
15-
<DockPanel Grid.Column="1" Margin="0,10" >
14+
<DockPanel Grid.Column="0" >
15+
<StackPanel DockPanel.Dock="Top">
16+
<Label>Input File:</Label>
17+
<TextBox x:Name="txtInputFile"
18+
Text="{Binding InputFile}"
19+
Margin="10"
20+
TextWrapping="Wrap" AcceptsReturn="false" FontFamily="Consolas" />
21+
<Label>Input Content:</Label>
22+
</StackPanel>
23+
<TextBox x:Name="txtInput" Text="{Binding Input}"
24+
VerticalScrollBarVisibility="Auto"
25+
Margin="10"
26+
TextWrapping="Wrap"
27+
AcceptsReturn="True"
28+
FontFamily="Consolas"
29+
VerticalContentAlignment="Stretch" />
30+
</DockPanel>
31+
<DockPanel Grid.Column="1" Margin="0,10" >
1632
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
1733
<Button Content="Convert" VerticalAlignment="Center" HorizontalAlignment="Left" Height="20" Margin="10" Width="131" Click="Button_Click" Grid.Column="1"/>
1834
<StackPanel Orientation="Vertical" DockPanel.Dock="Top">

src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static class OpenApiElementExtensions
2020
/// <param name="element">Element to validate</param>
2121
/// <param name="ruleSet">Optional set of rules to use for validation</param>
2222
/// <returns>An IEnumerable of errors. This function will never return null.</returns>
23-
public static IEnumerable<OpenApiError> Validate(this IOpenApiElement element, ValidationRuleSet ruleSet = null)
23+
public static IEnumerable<OpenApiError> Validate(this IOpenApiElement element, ValidationRuleSet ruleSet)
2424
{
2525
var validator = new OpenApiValidator(ruleSet);
2626
var walker = new OpenApiWalker(validator);

src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Microsoft.OpenApi.Services
1515
public abstract class OpenApiVisitorBase
1616
{
1717
private readonly Stack<string> _path = new Stack<string>();
18+
private bool _inComponents = false;
1819

1920
/// <summary>
2021
/// Allow Rule to indicate validation error occured at a deeper context level.
@@ -33,6 +34,16 @@ public void Exit()
3334
this._path.Pop();
3435
}
3536

37+
public void EnterComponents()
38+
{
39+
_inComponents = true;
40+
}
41+
42+
public void ExitComponents()
43+
{
44+
_inComponents = false;
45+
}
46+
3647
/// <summary>
3748
/// Pointer to source of validation error in document
3849
/// </summary>
@@ -44,7 +55,14 @@ public string PathString
4455
}
4556
}
4657

47-
58+
public bool InComponents
59+
{
60+
get
61+
{
62+
return _inComponents;
63+
}
64+
}
65+
4866
/// <summary>
4967
/// Visits <see cref="OpenApiDocument"/>
5068
/// </summary>

src/Microsoft.OpenApi/Services/OpenApiWalker.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ internal void Walk(OpenApiComponents components)
9696
return;
9797
}
9898

99+
_visitor.EnterComponents();
100+
99101
_visitor.Visit(components);
100102

101103
if (components == null)
@@ -192,6 +194,7 @@ internal void Walk(OpenApiComponents components)
192194
});
193195

194196
Walk(components as IOpenApiExtensible);
197+
_visitor.ExitComponents();
195198
}
196199

197200
/// <summary>

src/Microsoft.OpenApi/Validations/OpenApiValidator.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ public class OpenApiValidator : OpenApiVisitorBase, IValidationContext
2222
/// Create a vistor that will validate an OpenAPIDocument
2323
/// </summary>
2424
/// <param name="ruleSet"></param>
25-
public OpenApiValidator(ValidationRuleSet ruleSet = null)
25+
public OpenApiValidator(ValidationRuleSet ruleSet)
2626
{
27-
_ruleSet = ruleSet ?? ValidationRuleSet.GetDefaultRuleSet();
27+
_ruleSet = ruleSet;
2828
}
2929

3030
/// <summary>
@@ -170,7 +170,24 @@ private void Validate<T>(T item)
170170
private void Validate(object item, Type type)
171171
{
172172
if (item == null) return; // Required fields should be checked by higher level objects
173-
var rules = _ruleSet.Where(r => r.ElementType == type);
173+
var potentialReference = item as IOpenApiReferenceable;
174+
175+
if (potentialReference != null && potentialReference.Reference != null)
176+
{
177+
if (potentialReference.UnresolvedReference)
178+
{
179+
return; // Don't attempt to validate unresolved references
180+
}
181+
else
182+
{
183+
if (potentialReference.Reference != null && !InComponents)
184+
{
185+
// Don't validate references if they are in not in Components to avoid validating on every reference
186+
return;
187+
}
188+
}
189+
}
190+
var rules = _ruleSet.FindRules(type);
174191
foreach (var rule in rules)
175192
{
176193
rule.Evaluate(this as IValidationContext, item);

src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ public sealed class ValidationRuleSet : IEnumerable<ValidationRule>
2121

2222
private static ValidationRuleSet _defaultRuleSet;
2323

24+
private IList<ValidationRule> _emptyRules = new List<ValidationRule>();
25+
26+
/// <summary>
27+
/// Retrieve the rules that are related to a specific type
28+
/// </summary>
29+
/// <param name="type">The type that is to be validated</param>
30+
/// <returns>Either the rules related to the type, or an empty list.</returns>
31+
public IList<ValidationRule> FindRules(Type type)
32+
{
33+
IList<ValidationRule> results = null;
34+
_rules.TryGetValue(type, out results);
35+
return results ?? _emptyRules;
36+
}
37+
2438
/// <summary>
2539
/// Gets the default validation rule sets.
2640
/// </summary>
@@ -42,6 +56,12 @@ public static ValidationRuleSet GetDefaultRuleSet()
4256
return new ValidationRuleSet(_defaultRuleSet);
4357
}
4458

59+
public static ValidationRuleSet GetEmptyRuleSet()
60+
{
61+
// We create a new instance of ValidationRuleSet per call as a safeguard
62+
// against unintentional modification of the private _defaultRuleSet.
63+
return new ValidationRuleSet();
64+
}
4565
/// <summary>
4666
/// Initializes a new instance of the <see cref="ValidationRuleSet"/> class.
4767
/// </summary>

test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void ResponseMustHaveADescription()
5151
}
5252
});
5353

54-
var validator = new OpenApiValidator();
54+
var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
5555
var walker = new OpenApiWalker(validator);
5656
walker.Walk(openApiDocument);
5757

@@ -83,7 +83,7 @@ public void ServersShouldBeReferencedByIndex()
8383
}
8484
};
8585

86-
var validator = new OpenApiValidator();
86+
var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
8787
var walker = new OpenApiWalker(validator);
8888
walker.Walk(openApiDocument);
8989

0 commit comments

Comments
 (0)