diff --git a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs index 03e493776..add3f1c14 100644 --- a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs +++ b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs @@ -303,6 +303,24 @@ internal class SRResource { } } + /// + /// Looks up a localized string similar to Composite Schema {0} must contain property specified in the discriminator {1}.. + /// + internal static string Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator { + get { + return ResourceManager.GetString("Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Composite schema {0} must contain property specified in the discriminator {1} in the required field list.. + /// + internal static string Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator { + get { + return ResourceManager.GetString("Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator", resourceCulture); + } + } + /// /// Looks up a localized string similar to The extension name '{0}' in '{1}' object MUST begin with 'x-'.. /// diff --git a/src/Microsoft.OpenApi/Properties/SRResource.resx b/src/Microsoft.OpenApi/Properties/SRResource.resx index 6d9ffc112..ffff42905 100644 --- a/src/Microsoft.OpenApi/Properties/SRResource.resx +++ b/src/Microsoft.OpenApi/Properties/SRResource.resx @@ -198,6 +198,12 @@ The key '{0}' in '{1}' of components MUST match the regular expression '{2}'. + + Composite Schema {0} must contain property specified in the discriminator {1}. + + + Composite schema {0} must contain property specified in the discriminator {1} in the required field list. + The extension name '{0}' in '{1}' object MUST begin with 'x-'. diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index 86336bbc5..6fafa53b1 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -3,6 +3,8 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Properties; +using System.Collections.Generic; namespace Microsoft.OpenApi.Validations.Rules { @@ -55,6 +57,68 @@ public static class OpenApiSchemaRules context.Exit(); }); + /// + /// Validates OneOf Discriminator + /// + public static ValidationRule ValidateOneOfDiscriminator => + new ValidationRule( + (context, schema) => + { + // oneOf + context.Enter("oneOf"); + + if (schema.OneOf != null && schema.Discriminator != null) + { + ValidateSchemaListDiscriminator(context, nameof(ValidateOneOfDiscriminator), + schema.OneOf, schema.Discriminator); + } + + context.Exit(); + }); + + // + /// Validates AnyOf Discriminator + /// + public static ValidationRule ValidateAnyOfDiscriminator => + new ValidationRule( + (context, schema) => + { + // oneOf + context.Enter("anyOf"); + + if (schema.AnyOf != null && schema.Discriminator != null) + { + ValidateSchemaListDiscriminator(context, nameof(ValidateAnyOfDiscriminator), + schema.AnyOf, schema.Discriminator); + } + + context.Exit(); + }); + // add more rule. + + + /// + /// Checks if the schemas in the list contain a property with the property name specified by the discriminator. + /// + private static void ValidateSchemaListDiscriminator(IValidationContext context, string ruleName, + IList schemas, OpenApiDiscriminator discriminator) + { + foreach (var schema in schemas) + { + if (schema.Reference != null && !schema.Properties.ContainsKey(discriminator.PropertyName)) + { + context.CreateError(ruleName, + string.Format(SRResource.Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator, + schema.Reference.Id, discriminator.PropertyName)); + } + if (schema.Reference != null && !schema.Required.Contains(discriminator.PropertyName)) + { + context.CreateError(ruleName, + string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, + schema.Reference.Id, discriminator.PropertyName)); + } + } + } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 0fd0685d4..8168f2e86 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -7,6 +7,7 @@ using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Properties; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations.Rules; using Xunit; @@ -231,5 +232,125 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() "#/default/property4", }); } + + [Fact] + public void ValidateOneOfCompositeSchemaMustContainPropertySpecifiedInTheDiscriminator() + { + IEnumerable errors; + var schema = new OpenApiSchema + { + Type = "object", + OneOf = new List + { + new OpenApiSchema + { + Type = "object", + Properties = { + ["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" }, + ["property2"] = new OpenApiSchema() { Type = "string" } + }, + Reference = new OpenApiReference{ Id = "schema1" } + }, + new OpenApiSchema + { + Type = "object", + Properties = { + ["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" }, + }, + Reference = new OpenApiReference{ Id = "schema2" } + } + }, + Discriminator = new OpenApiDiscriminator + { + PropertyName = "property2" + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var walker = new OpenApiWalker(validator); + walker.Walk(schema); + + errors = validator.Errors; + bool result = !errors.Any(); + + // Assert + result.Should().BeFalse(); + errors.ShouldAllBeEquivalentTo(new List + { + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateOneOfDiscriminator),"#/oneOf", + string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, + "schema1", "property2")), + + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateOneOfDiscriminator),"#/oneOf", + string.Format(SRResource.Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator, + "schema2", "property2")), + + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateOneOfDiscriminator),"#/oneOf", + string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, + "schema2", "property2")), + + }); + } + + [Fact] + public void ValidateAnyOfCompositeSchemaMustContainPropertySpecifiedInTheDiscriminator() + { + IEnumerable errors; + var schema = new OpenApiSchema + { + Type = "object", + AnyOf = new List + { + new OpenApiSchema + { + Type = "object", + Properties = { + ["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" }, + ["property2"] = new OpenApiSchema() { Type = "string" } + }, + Reference = new OpenApiReference{ Id = "schema1" } + }, + new OpenApiSchema + { + Type = "object", + Properties = { + ["property1"] = new OpenApiSchema() { Type = "integer", Format ="int64" }, + }, + Reference = new OpenApiReference{ Id = "schema2" } + } + }, + Discriminator = new OpenApiDiscriminator + { + PropertyName = "property2" + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var walker = new OpenApiWalker(validator); + walker.Walk(schema); + + errors = validator.Errors; + bool result = !errors.Any(); + + // Assert + result.Should().BeFalse(); + errors.ShouldAllBeEquivalentTo(new List + { + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateAnyOfDiscriminator),"#/anyOf", + string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, + "schema1", "property2")), + + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateAnyOfDiscriminator),"#/anyOf", + string.Format(SRResource.Validation_CompositeSchemaMustContainPropertySpecifiedInTheDiscriminator, + "schema2", "property2")), + + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateAnyOfDiscriminator),"#/anyOf", + string.Format(SRResource.Validation_CompositeSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, + "schema2", "property2")), + + }); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs index f300761b4..8153e6054 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs @@ -43,7 +43,7 @@ public void DefaultRuleSetPropertyReturnsTheCorrectRules() Assert.NotEmpty(rules); // Update the number if you add new default rule(s). - Assert.Equal(20, rules.Count); + Assert.Equal(22, rules.Count); } } }