Skip to content

Commit 26a2ef1

Browse files
authored
Merge pull request #346 from senthilkumarmohan/bugfix#344-Report-error-when-OneOf-schema-doesnt-have-the-discriminator-property
Fix #344 Validate `anyOf` and `oneOf` contains the property specified in the `discriminator`.
2 parents 8edd198 + 07580e1 commit 26a2ef1

File tree

5 files changed

+210
-1
lines changed

5 files changed

+210
-1
lines changed

src/Microsoft.OpenApi/Properties/SRResource.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.OpenApi/Properties/SRResource.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@
198198
<data name="Validation_ComponentsKeyMustMatchRegularExpr" xml:space="preserve">
199199
<value>The key '{0}' in '{1}' of components MUST match the regular expression '{2}'.</value>
200200
</data>
201+
<data name="Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator" xml:space="preserve">
202+
<value>Composite Schema {0} must contain property specified in the discriminator {1}.</value>
203+
</data>
204+
<data name="Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator" xml:space="preserve">
205+
<value>Composite schema {0} must contain property specified in the discriminator {1} in the required field list.</value>
206+
</data>
201207
<data name="Validation_ExtensionNameMustBeginWithXDash" xml:space="preserve">
202208
<value>The extension name '{0}' in '{1}' object MUST begin with 'x-'.</value>
203209
</data>

src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using Microsoft.OpenApi.Any;
55
using Microsoft.OpenApi.Models;
6+
using Microsoft.OpenApi.Properties;
7+
using System.Collections.Generic;
68

79
namespace Microsoft.OpenApi.Validations.Rules
810
{
@@ -55,6 +57,68 @@ public static class OpenApiSchemaRules
5557
context.Exit();
5658
});
5759

60+
/// <summary>
61+
/// Validates OneOf Discriminator
62+
/// </summary>
63+
public static ValidationRule<OpenApiSchema> ValidateOneOfDiscriminator =>
64+
new ValidationRule<OpenApiSchema>(
65+
(context, schema) =>
66+
{
67+
// oneOf
68+
context.Enter("oneOf");
69+
70+
if (schema.OneOf != null && schema.Discriminator != null)
71+
{
72+
ValidateSchemaListDiscriminator(context, nameof(ValidateOneOfDiscriminator),
73+
schema.OneOf, schema.Discriminator);
74+
}
75+
76+
context.Exit();
77+
});
78+
79+
// <summary>
80+
/// Validates AnyOf Discriminator
81+
/// </summary>
82+
public static ValidationRule<OpenApiSchema> ValidateAnyOfDiscriminator =>
83+
new ValidationRule<OpenApiSchema>(
84+
(context, schema) =>
85+
{
86+
// oneOf
87+
context.Enter("anyOf");
88+
89+
if (schema.AnyOf != null && schema.Discriminator != null)
90+
{
91+
ValidateSchemaListDiscriminator(context, nameof(ValidateAnyOfDiscriminator),
92+
schema.AnyOf, schema.Discriminator);
93+
}
94+
95+
context.Exit();
96+
});
97+
5898
// add more rule.
99+
100+
101+
/// <summary>
102+
/// Checks if the schemas in the list contain a property with the property name specified by the discriminator.
103+
/// </summary>
104+
private static void ValidateSchemaListDiscriminator(IValidationContext context, string ruleName,
105+
IList<OpenApiSchema> schemas, OpenApiDiscriminator discriminator)
106+
{
107+
foreach (var schema in schemas)
108+
{
109+
if (schema.Reference != null && !schema.Properties.ContainsKey(discriminator.PropertyName))
110+
{
111+
context.CreateError(ruleName,
112+
string.Format(SRResource.Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator,
113+
schema.Reference.Id, discriminator.PropertyName));
114+
}
115+
if (schema.Reference != null && !schema.Required.Contains(discriminator.PropertyName))
116+
{
117+
context.CreateError(ruleName,
118+
string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator,
119+
schema.Reference.Id, discriminator.PropertyName));
120+
}
121+
}
122+
}
59123
}
60124
}

test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using FluentAssertions;
88
using Microsoft.OpenApi.Any;
99
using Microsoft.OpenApi.Models;
10+
using Microsoft.OpenApi.Properties;
1011
using Microsoft.OpenApi.Services;
1112
using Microsoft.OpenApi.Validations.Rules;
1213
using Xunit;
@@ -231,5 +232,125 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema()
231232
"#/default/property4",
232233
});
233234
}
235+
236+
[Fact]
237+
public void ValidateOneOfCompositeSchemaMustContainPropertySpecifiedInTheDiscriminator()
238+
{
239+
IEnumerable<OpenApiError> errors;
240+
var schema = new OpenApiSchema
241+
{
242+
Type = "object",
243+
OneOf = new List<OpenApiSchema>
244+
{
245+
new OpenApiSchema
246+
{
247+
Type = "object",
248+
Properties = {
249+
["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" },
250+
["property2"] = new OpenApiSchema() { Type = "string" }
251+
},
252+
Reference = new OpenApiReference{ Id = "schema1" }
253+
},
254+
new OpenApiSchema
255+
{
256+
Type = "object",
257+
Properties = {
258+
["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" },
259+
},
260+
Reference = new OpenApiReference{ Id = "schema2" }
261+
}
262+
},
263+
Discriminator = new OpenApiDiscriminator
264+
{
265+
PropertyName = "property2"
266+
}
267+
};
268+
269+
// Act
270+
var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
271+
var walker = new OpenApiWalker(validator);
272+
walker.Walk(schema);
273+
274+
errors = validator.Errors;
275+
bool result = !errors.Any();
276+
277+
// Assert
278+
result.Should().BeFalse();
279+
errors.ShouldAllBeEquivalentTo(new List<OpenApiValidatorError>
280+
{
281+
new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateOneOfDiscriminator),"#/oneOf",
282+
string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator,
283+
"schema1", "property2")),
284+
285+
new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateOneOfDiscriminator),"#/oneOf",
286+
string.Format(SRResource.Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator,
287+
"schema2", "property2")),
288+
289+
new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateOneOfDiscriminator),"#/oneOf",
290+
string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator,
291+
"schema2", "property2")),
292+
293+
});
294+
}
295+
296+
[Fact]
297+
public void ValidateAnyOfCompositeSchemaMustContainPropertySpecifiedInTheDiscriminator()
298+
{
299+
IEnumerable<OpenApiError> errors;
300+
var schema = new OpenApiSchema
301+
{
302+
Type = "object",
303+
AnyOf = new List<OpenApiSchema>
304+
{
305+
new OpenApiSchema
306+
{
307+
Type = "object",
308+
Properties = {
309+
["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" },
310+
["property2"] = new OpenApiSchema() { Type = "string" }
311+
},
312+
Reference = new OpenApiReference{ Id = "schema1" }
313+
},
314+
new OpenApiSchema
315+
{
316+
Type = "object",
317+
Properties = {
318+
["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" },
319+
},
320+
Reference = new OpenApiReference{ Id = "schema2" }
321+
}
322+
},
323+
Discriminator = new OpenApiDiscriminator
324+
{
325+
PropertyName = "property2"
326+
}
327+
};
328+
329+
// Act
330+
var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
331+
var walker = new OpenApiWalker(validator);
332+
walker.Walk(schema);
333+
334+
errors = validator.Errors;
335+
bool result = !errors.Any();
336+
337+
// Assert
338+
result.Should().BeFalse();
339+
errors.ShouldAllBeEquivalentTo(new List<OpenApiValidatorError>
340+
{
341+
new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateAnyOfDiscriminator),"#/anyOf",
342+
string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator,
343+
"schema1", "property2")),
344+
345+
new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateAnyOfDiscriminator),"#/anyOf",
346+
string.Format(SRResource.Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator,
347+
"schema2", "property2")),
348+
349+
new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateAnyOfDiscriminator),"#/anyOf",
350+
string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator,
351+
"schema2", "property2")),
352+
353+
});
354+
}
234355
}
235356
}

test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void DefaultRuleSetPropertyReturnsTheCorrectRules()
4343
Assert.NotEmpty(rules);
4444

4545
// Update the number if you add new default rule(s).
46-
Assert.Equal(20, rules.Count);
46+
Assert.Equal(22, rules.Count);
4747
}
4848
}
4949
}

0 commit comments

Comments
 (0)