diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs
new file mode 100644
index 000000000..757f419df
--- /dev/null
+++ b/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Services;
+using Microsoft.OpenApi.Validations;
+
+namespace Microsoft.OpenApi.Extensions
+{
+ ///
+ /// Extension methods that apply across all OpenAPIElements
+ ///
+ public static class OpenApiElementExtensions
+ {
+ ///
+ /// Validate element and all child elements
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable Validate(this IOpenApiElement element) {
+ var validator = new OpenApiValidator();
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(element);
+ return validator.Errors;
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
index 2b77e680a..4d0dd1fd5 100644
--- a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
+++ b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
@@ -96,6 +96,15 @@ internal static string IndentationLevelInvalid {
}
}
+ ///
+ /// Looks up a localized string similar to The input item should be in type of '{0}'..
+ ///
+ internal static string InputItemShouldBeType {
+ get {
+ return ResourceManager.GetString("InputItemShouldBeType", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The active scope must be an object scope for property name '{0}' to be written..
///
@@ -239,5 +248,68 @@ internal static string SourceExpressionHasInvalidFormat {
return ResourceManager.GetString("SourceExpressionHasInvalidFormat", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Can not find visitor type registered for type '{0}'..
+ ///
+ internal static string UnknownVisitorType {
+ get {
+ return ResourceManager.GetString("UnknownVisitorType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The key '{0}' in '{1}' of components MUST match the regular expression '{2}'..
+ ///
+ internal static string Validation_ComponentsKeyMustMatchRegularExpr {
+ get {
+ return ResourceManager.GetString("Validation_ComponentsKeyMustMatchRegularExpr", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The extension name '{0}' in '{1}' object MUST begin with 'x-'..
+ ///
+ internal static string Validation_ExtensionNameMustBeginWithXDash {
+ get {
+ return ResourceManager.GetString("Validation_ExtensionNameMustBeginWithXDash", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The field '{0}' in '{1}' object is REQUIRED..
+ ///
+ internal static string Validation_FieldIsRequired {
+ get {
+ return ResourceManager.GetString("Validation_FieldIsRequired", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The path item name '{0}' MUST begin with a slash..
+ ///
+ internal static string Validation_PathItemMustBeginWithSlash {
+ get {
+ return ResourceManager.GetString("Validation_PathItemMustBeginWithSlash", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The same rule cannot be in the same rule set twice..
+ ///
+ internal static string Validation_RuleAddTwice {
+ get {
+ return ResourceManager.GetString("Validation_RuleAddTwice", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The string '{0}' MUST be in the format of an email address..
+ ///
+ internal static string Validation_StringMustBeEmailAddress {
+ get {
+ return ResourceManager.GetString("Validation_StringMustBeEmailAddress", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Microsoft.OpenApi/Properties/SRResource.resx b/src/Microsoft.OpenApi/Properties/SRResource.resx
index bfffc638c..e3f232c76 100644
--- a/src/Microsoft.OpenApi/Properties/SRResource.resx
+++ b/src/Microsoft.OpenApi/Properties/SRResource.resx
@@ -129,6 +129,9 @@
Indentation level cannot be lower than 0.
+
+ The input item should be in type of '{0}'.
+
The active scope must be an object scope for property name '{0}' to be written.
@@ -177,4 +180,25 @@
The source expression '{0}' has invalid format.
+
+ Can not find visitor type registered for type '{0}'.
+
+
+ The key '{0}' in '{1}' of components MUST match the regular expression '{2}'.
+
+
+ The extension name '{0}' in '{1}' object MUST begin with 'x-'.
+
+
+ The field '{0}' in '{1}' object is REQUIRED.
+
+
+ The path item name '{0}' MUST begin with a slash.
+
+
+ The same rule cannot be in the same rule set twice.
+
+
+ The string '{0}' MUST be in the format of an email address.
+
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Services/OpenApiValidator.cs b/src/Microsoft.OpenApi/Services/OpenApiValidator.cs
deleted file mode 100644
index 25d9b4d1b..000000000
--- a/src/Microsoft.OpenApi/Services/OpenApiValidator.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT license.
-
-using System.Collections.Generic;
-using Microsoft.OpenApi.Exceptions;
-using Microsoft.OpenApi.Models;
-
-namespace Microsoft.OpenApi.Services
-{
- ///
- /// Class containing logic to validate an Open API document object.
- ///
- public class OpenApiValidator : OpenApiVisitorBase
- {
- ///
- /// Exceptions related to this validation.
- ///
- public List Exceptions { get; } = new List();
-
- ///
- /// Visit Open API Response element.
- ///
- /// Response element.
- public override void Visit(OpenApiResponse response)
- {
- if (string.IsNullOrEmpty(response.Description))
- {
- Exceptions.Add(new OpenApiException("Response must have a description"));
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs
index 1a20c67ca..0a0bc2416 100644
--- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs
+++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs
@@ -13,143 +13,236 @@ namespace Microsoft.OpenApi.Services
public abstract class OpenApiVisitorBase
{
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiDocument doc)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiInfo info)
{
}
///
- /// Validates list of
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiContact contact)
+ {
+ }
+
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiLicense license)
+ {
+ }
+
+ ///
+ /// Visits list of
///
public virtual void Visit(IList servers)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiServer server)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiPaths paths)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiPathItem pathItem)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiServerVariable serverVariable)
{
}
///
- /// Validates the operations.
+ /// Visits the operations.
///
public virtual void Visit(IDictionary operations)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiOperation operation)
{
}
///
- /// Validates list of
+ /// Visits list of
///
public virtual void Visit(IList parameters)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiParameter parameter)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiRequestBody requestBody)
{
}
///
- /// Validates responses.
+ /// Visits responses.
///
public virtual void Visit(IDictionary responses)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiResponse response)
{
}
///
- /// Validates media type content.
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiResponses response)
+ {
+ }
+
+ ///
+ /// Visits media type content.
///
public virtual void Visit(IDictionary content)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiMediaType mediaType)
{
}
///
- /// Validates the examples.
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiEncoding encoding)
+ {
+ }
+
+ ///
+ /// Visits the examples.
///
public virtual void Visit(IDictionary examples)
{
}
///
- /// Validates
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiComponents components)
+ {
+ }
+
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiExternalDocs externalDocs)
+ {
+ }
+
+ ///
+ /// Visits
///
public virtual void Visit(OpenApiSchema schema)
{
}
///
- /// Validates the links.
+ /// Visits the links.
///
public virtual void Visit(IDictionary links)
{
}
///
- /// Validates
+ /// Visits
///
public virtual void Visit(OpenApiLink link)
{
}
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiCallback callback)
+ {
+ }
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiTag tag)
+ {
+ }
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiHeader tag)
+ {
+ }
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiOAuthFlow openApiOAuthFlow)
+ {
+ }
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(OpenApiSecurityRequirement securityRequirement)
+ {
+ }
+
+ ///
+ /// Visits list of
+ ///
+ public virtual void Visit(IList openApiTags)
+ {
+ }
+
+ ///
+ /// Visits
+ ///
+ public virtual void Visit(IOpenApiExtensible openApiExtensible)
+ {
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs
index 66d4e319c..23f898f85 100644
--- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs
+++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
-using Microsoft.OpenApi.Models;
+using System;
using System.Collections.Generic;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Interfaces;
namespace Microsoft.OpenApi.Services
{
@@ -22,81 +24,290 @@ public OpenApiWalker(OpenApiVisitorBase visitor)
}
///
- /// Walks through the and validates each element.
+ /// Visits list of and child objects
///
- ///
+ /// OpenApiDocument to be walked
public void Walk(OpenApiDocument doc)
{
_visitor.Visit(doc);
- _visitor.Visit(doc.Info);
- _visitor.Visit(doc.Servers);
- if (doc.Servers != null)
+ Walk(doc.Info);
+ Walk(doc.Servers);
+ Walk(doc.Paths);
+ Walk(doc.Components);
+ Walk(doc.ExternalDocs);
+ Walk(doc.Tags);
+ Walk(doc as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits list of and child objects
+ ///
+ ///
+ internal void Walk(IList tags)
+ {
+ _visitor.Visit(tags);
+
+ foreach (var tag in tags)
+ {
+ Walk(tag);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiExternalDocs externalDocs)
+ {
+ _visitor.Visit(externalDocs);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiComponents components)
+ {
+ _visitor.Visit(components);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiPaths paths)
+ {
+ _visitor.Visit(paths);
+ foreach (var pathItem in paths.Values)
{
- foreach (var server in doc.Servers)
+ Walk(pathItem);
+ }
+ }
+
+ ///
+ /// Visits list of and child objects
+ ///
+ ///
+ internal void Walk(IList servers)
+ {
+ _visitor.Visit(servers);
+
+ // Visit Servers
+ if (servers != null)
+ {
+ foreach (var server in servers)
{
- _visitor.Visit(server);
- foreach (var variable in server.Variables.Values)
- {
- _visitor.Visit(variable);
- }
+ Walk(server);
}
}
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiInfo info)
+ {
+ _visitor.Visit(info);
+ Walk(info.Contact);
+ Walk(info.License);
+ Walk(info as IOpenApiExtensible);
+ }
- _visitor.Visit(doc.Paths);
- foreach (var pathItem in doc.Paths.Values)
+ ///
+ /// Visits dictionary of extensions
+ ///
+ ///
+ internal void Walk(IOpenApiExtensible openApiExtensible)
+ {
+ _visitor.Visit(openApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiLicense license)
+ {
+ _visitor.Visit(license);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiContact contact)
+ {
+ _visitor.Visit(contact);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiTag tag)
+ {
+ _visitor.Visit(tag);
+ _visitor.Visit(tag.ExternalDocs);
+ _visitor.Visit(tag as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiServer server)
+ {
+ _visitor.Visit(server);
+ foreach (var variable in server.Variables.Values)
+ {
+ Walk(variable);
+ }
+ _visitor.Visit(server as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiServerVariable serverVariable)
+ {
+ _visitor.Visit(serverVariable);
+ _visitor.Visit(serverVariable as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiPathItem pathItem)
+ {
+ _visitor.Visit(pathItem);
+
+ Walk(pathItem.Operations);
+ _visitor.Visit(pathItem as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits dictionary of
+ ///
+ ///
+ internal void Walk(IDictionary operations)
+ {
+ _visitor.Visit(operations);
+ foreach (var operation in operations.Values)
+ {
+ Walk(operation);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiOperation operation)
+ {
+ _visitor.Visit(operation);
+
+ Walk(operation.Parameters);
+ Walk(operation.RequestBody);
+ Walk(operation.Responses);
+ Walk(operation as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits list of
+ ///
+ ///
+ internal void Walk(IList parameters)
+ {
+ if (parameters != null)
{
- _visitor.Visit(pathItem);
- _visitor.Visit(pathItem.Operations);
- foreach (var operation in pathItem.Operations.Values)
+ _visitor.Visit(parameters);
+ foreach (var parameter in parameters)
{
- _visitor.Visit(operation);
- if (operation.Parameters != null)
- {
- _visitor.Visit(operation.Parameters);
- foreach (var parameter in operation.Parameters)
- {
- _visitor.Visit(parameter);
- }
- }
-
- if (operation.RequestBody != null)
- {
- _visitor.Visit(operation.RequestBody);
-
- if (operation.RequestBody.Content != null)
- {
- WalkContent(operation.RequestBody.Content);
- }
- }
-
- if (operation.Responses != null)
- {
- _visitor.Visit(operation.Responses);
-
- foreach (var response in operation.Responses.Values)
- {
- _visitor.Visit(response);
- WalkContent(response.Content);
-
- if (response.Links != null)
- {
- _visitor.Visit(response.Links);
- foreach (var link in response.Links.Values)
- {
- _visitor.Visit(link);
- }
- }
- }
- }
+ Walk(parameter);
}
}
}
///
- /// Walks through each media type in content and validates.
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiParameter parameter)
+ {
+ _visitor.Visit(parameter);
+ Walk(parameter.Schema);
+ Walk(parameter.Content);
+ Walk(parameter as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiResponses responses)
+ {
+ if (responses != null)
+ {
+ _visitor.Visit(responses);
+
+ foreach (var response in responses.Values)
+ {
+ Walk(response);
+ }
+
+ Walk(responses as IOpenApiExtensible);
+ }
+ }
+
+ ///
+ /// Visits and child objects
///
- private void WalkContent(IDictionary content)
+ ///
+ internal void Walk(OpenApiResponse response)
+ {
+ _visitor.Visit(response);
+ Walk(response.Content);
+
+ if (response.Links != null)
+ {
+ _visitor.Visit(response.Links);
+ foreach (var link in response.Links.Values)
+ {
+ _visitor.Visit(link);
+ }
+ }
+
+ _visitor.Visit(response as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiRequestBody requestBody)
+ {
+ if (requestBody != null)
+ {
+ _visitor.Visit(requestBody);
+
+ if (requestBody.Content != null)
+ {
+ Walk(requestBody.Content);
+ }
+
+ Walk(requestBody as IOpenApiExtensible);
+ }
+ }
+
+ ///
+ /// Visits dictionary of
+ ///
+ ///
+ internal void Walk(IDictionary content)
{
if (content == null)
{
@@ -106,10 +317,208 @@ private void WalkContent(IDictionary content)
_visitor.Visit(content);
foreach (var mediaType in content.Values)
{
- _visitor.Visit(mediaType);
- _visitor.Visit(mediaType.Examples);
- _visitor.Visit(mediaType.Schema);
+ Walk(mediaType);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiMediaType mediaType)
+ {
+ _visitor.Visit(mediaType);
+
+ Walk(mediaType.Examples);
+ Walk(mediaType.Schema);
+ Walk(mediaType.Encoding);
+ Walk(mediaType as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits dictionary of
+ ///
+ ///
+ internal void Walk(IDictionary encoding)
+ {
+ foreach (var item in encoding.Values)
+ {
+ _visitor.Visit(item);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiEncoding encoding)
+ {
+ _visitor.Visit(encoding);
+ Walk(encoding as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiSchema schema)
+ {
+ _visitor.Visit(schema);
+ Walk(schema.ExternalDocs);
+ Walk(schema as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits dictionary of
+ ///
+ ///
+ internal void Walk(IDictionary examples)
+ {
+ _visitor.Visit(examples);
+ foreach (var example in examples.Values)
+ {
+ Walk(example);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiExample example)
+ {
+ _visitor.Visit(example);
+ Walk(example as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits the list of and child objects
+ ///
+ ///
+ internal void Walk(IList examples)
+ {
+ foreach (var item in examples)
+ {
+ _visitor.Visit(item);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiOAuthFlows flows)
+ {
+ _visitor.Visit(flows);
+ Walk(flows as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiOAuthFlow oAuthFlow)
+ {
+ _visitor.Visit(oAuthFlow);
+ Walk(oAuthFlow as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits dictionary of and child objects
+ ///
+ ///
+ internal void Walk(IDictionary links)
+ {
+ foreach (var item in links)
+ {
+ _visitor.Visit(item.Value);
+ }
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiLink link)
+ {
+ _visitor.Visit(link);
+ Walk(link.Server);
+ Walk(link as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiHeader header)
+ {
+ _visitor.Visit(header);
+ Walk(header.Content);
+ Walk(header.Example);
+ Walk(header.Examples);
+ Walk(header.Schema);
+ Walk(header as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiSecurityRequirement securityRequirement)
+ {
+ _visitor.Visit(securityRequirement);
+ Walk(securityRequirement as IOpenApiExtensible);
+ }
+
+ ///
+ /// Visits and child objects
+ ///
+ ///
+ internal void Walk(OpenApiSecurityScheme securityScheme)
+ {
+ _visitor.Visit(securityScheme);
+ Walk(securityScheme as IOpenApiExtensible);
+ }
+
+ ///
+ /// Walk IOpenApiElement
+ ///
+ ///
+ internal void Walk(IOpenApiElement element)
+ {
+ switch(element)
+ {
+ case OpenApiDocument e: Walk(e); break;
+ case OpenApiLicense e: Walk(e); break;
+ case OpenApiInfo e: Walk(e); break;
+ case OpenApiComponents e: Walk(e); break;
+ case OpenApiContact e: Walk(e); break;
+ case OpenApiCallback e: Walk(e); break;
+ case OpenApiEncoding e: Walk(e); break;
+ case OpenApiExample e: Walk(e); break;
+ case IDictionary e: Walk(e); break;
+ case OpenApiExternalDocs e: Walk(e); break;
+ case OpenApiHeader e: Walk(e); break;
+ case OpenApiLink e: Walk(e); break;
+ case IDictionary e: Walk(e); break;
+ case OpenApiMediaType e: Walk(e); break;
+ case OpenApiOAuthFlows e: Walk(e); break;
+ case OpenApiOAuthFlow e: Walk(e); break;
+ case OpenApiOperation e: Walk(e); break;
+ case OpenApiParameter e: Walk(e); break;
+ case OpenApiRequestBody e: Walk(e); break;
+ case OpenApiResponse e: Walk(e); break;
+ case OpenApiSchema e: Walk(e); break;
+ case OpenApiSecurityRequirement e: Walk(e); break;
+ case OpenApiSecurityScheme e: Walk(e); break;
+ case OpenApiServer e: Walk(e); break;
+ case OpenApiServerVariable e: Walk(e); break;
+ case OpenApiTag e: Walk(e); break;
+ case IList e: Walk(e); break;
+ case OpenApiXml e: Walk(e); break;
+ case IOpenApiExtensible e: Walk(e); break;
}
}
+
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs
new file mode 100644
index 000000000..468544b94
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Exceptions;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Services;
+using Microsoft.OpenApi.Validations;
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// Class containing dispatchers to execute validation rules on for Open API document.
+ ///
+ public class OpenApiValidator : OpenApiVisitorBase
+ {
+ readonly ValidationRuleSet _ruleSet;
+ readonly ValidationContext _context;
+
+ ///
+ /// Create a vistor that will validate an OpenAPIDocument
+ ///
+ ///
+ public OpenApiValidator(ValidationRuleSet ruleSet = null)
+ {
+ _ruleSet = ruleSet ?? ValidationRuleSet.DefaultRuleSet;
+ _context = new ValidationContext(_ruleSet);
+ }
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiDocument item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiInfo item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiContact item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiComponents item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiResponse item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiResponses item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiExternalDocs item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiLicense item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiOAuthFlow item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiTag item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiSchema item) => Validate(item);
+
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiServer item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiEncoding item) => Validate(item);
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(OpenApiCallback item) => Validate(item);
+
+
+ ///
+ /// Execute validation rules against an
+ ///
+ /// The object to be validated
+ public override void Visit(IOpenApiExtensible item) => Validate(item);
+
+ ///
+ /// Errors accumulated while validating OpenAPI elements
+ ///
+ public IEnumerable Errors => _context.Errors;
+
+ private void Validate(T item)
+ {
+ if (item == null) return; // Required fields should be checked by higher level objects
+ var rules = _ruleSet.Where(r => r.ElementType == typeof(T));
+ foreach (var rule in rules)
+ {
+ rule.Evaluate(_context, item);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs
new file mode 100644
index 000000000..11f0f7ada
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ public static class OpenApiComponentsRules
+ {
+ ///
+ /// The key regex.
+ ///
+ public static Regex KeyRegex = new Regex(@"^[a-zA-Z0-9\.\-_]+$");
+
+ ///
+ /// All the fixed fields declared above are objects
+ /// that MUST use keys that match the regular expression: ^[a-zA-Z0-9\.\-_]+$.
+ ///
+ public static ValidationRule KeyMustBeRegularExpression =>
+ new ValidationRule(
+ (context, components) =>
+ {
+ ValidateKeys(context, components.Schemas?.Keys, "schemas");
+
+ ValidateKeys(context, components.Responses?.Keys, "responses");
+
+ ValidateKeys(context, components.Parameters?.Keys, "parameters");
+
+ ValidateKeys(context, components.Examples?.Keys, "examples");
+
+ ValidateKeys(context, components.RequestBodies?.Keys, "requestBodies");
+
+ ValidateKeys(context, components.Headers?.Keys, "headers");
+
+ ValidateKeys(context, components.SecuritySchemes?.Keys, "securitySchemes");
+
+ ValidateKeys(context, components.Links?.Keys, "links");
+
+ ValidateKeys(context, components.Callbacks?.Keys, "callbacks");
+ });
+
+ private static void ValidateKeys(ValidationContext context, IEnumerable keys, string component)
+ {
+ if (keys == null)
+ {
+ return;
+ }
+
+ foreach (var key in keys)
+ {
+ if (!KeyRegex.IsMatch(key))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Format, context.PathString,
+ string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, component, KeyRegex.ToString()));
+ context.AddError(error);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs
new file mode 100644
index 000000000..e4a239198
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ internal static class OpenApiContactRules
+ {
+ ///
+ /// Email field MUST be email address.
+ ///
+ public static ValidationRule EmailMustBeEmailFormat =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ context.Push("email");
+ if (item != null && item.Email != null)
+ {
+ if (!item.Email.IsEmailAddress())
+ {
+ ValidationError error = new ValidationError(ErrorReason.Format, context.PathString,
+ String.Format(SRResource.Validation_StringMustBeEmailAddress, item.Email));
+ context.AddError(error);
+ }
+ }
+ context.Pop();
+ });
+
+ ///
+ /// Url field MUST be url format.
+ ///
+ public static ValidationRule UrlMustBeUrlFormat =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ context.Push("url");
+ if (item != null && item.Url != null)
+ {
+ // TODO:
+ }
+ context.Pop();
+ });
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs
new file mode 100644
index 000000000..030410123
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ internal static class OpenApiDocumentRules
+ {
+ ///
+ /// The Info field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ // info
+ context.Push("info");
+ if (item.Info == null)
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "info", "document"));
+ context.AddError(error);
+ }
+ context.Pop();
+
+ // paths
+ context.Push("paths");
+ if (item.Paths == null)
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "paths", "document"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs
new file mode 100644
index 000000000..db2a9711a
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ public static class OpenApiExtensibleRules
+ {
+ ///
+ /// Extension name MUST start with "x-".
+ ///
+ public static ValidationRule ExtensionNameMustStartWithXDash =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ context.Push("extensions");
+ foreach (var extensible in item.Extensions)
+ {
+ if (!extensible.Key.StartsWith("x-"))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Format, context.PathString,
+ String.Format(SRResource.Validation_ExtensionNameMustBeginWithXDash, extensible.Key, context.PathString));
+ context.AddError(error);
+ }
+ }
+ context.Pop();
+ });
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs
new file mode 100644
index 000000000..2c42cbb47
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExternalDocsRules.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ internal static class OpenApiExternalDocsRules
+ {
+ ///
+ /// Validate the field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ // url
+ context.Push("url");
+ if (item.Url == null)
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "url", "External Documentation"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rule.
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs
new file mode 100644
index 000000000..d77f28898
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ internal static class OpenApiInfoRules
+ {
+ ///
+ /// Validate the field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ // title
+ context.Push("title");
+ if (String.IsNullOrEmpty(item.Title))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "url", "info"));
+ context.AddError(error);
+ }
+ context.Pop();
+
+ // version
+ context.Push("version");
+ if (String.IsNullOrEmpty(item.Version))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "version", "info"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rule.
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs
new file mode 100644
index 000000000..4f17b4753
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiLicenseRules.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ public static class OpenApiLicenseRules
+ {
+ ///
+ /// REQUIRED.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, license) =>
+ {
+ context.Push("name");
+ if (String.IsNullOrEmpty(license.Name))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "name", "license"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rules
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs
new file mode 100644
index 000000000..dca45f890
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ internal static class OpenApiOAuthFlowRules
+ {
+ ///
+ /// Validate the field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, flow) =>
+ {
+ // authorizationUrl
+ context.Push("authorizationUrl");
+ if (flow.AuthorizationUrl == null)
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "authorizationUrl", "OAuth Flow"));
+ context.AddError(error);
+ }
+ context.Pop();
+
+ // tokenUrl
+ context.Push("tokenUrl");
+ if (flow.TokenUrl == null)
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "tokenUrl", "OAuth Flow"));
+ context.AddError(error);
+ }
+ context.Pop();
+
+ // scopes
+ context.Push("scopes");
+ if (flow.Scopes == null)
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "scopes", "OAuth Flow"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rule.
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs
new file mode 100644
index 000000000..dbc6eb383
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ public static class OpenApiPathsRules
+ {
+ ///
+ /// A relative path to an individual endpoint. The field name MUST begin with a slash.
+ ///
+ public static ValidationRule PathNameMustBeginWithSlash =>
+ new ValidationRule(
+ (context, item) =>
+ {
+ foreach (var pathName in item.Keys)
+ {
+ context.Push(pathName);
+
+ if (string.IsNullOrEmpty(pathName))
+ {
+ // Add the error message
+ // context.Add(...);
+ }
+
+ if (!pathName.StartsWith("/"))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Format, context.PathString,
+ string.Format(SRResource.Validation_PathItemMustBeginWithSlash, pathName));
+ context.AddError(error);
+ }
+
+ context.Pop();
+ }
+ });
+
+ // add more rules
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs
new file mode 100644
index 000000000..1c6f57b16
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponseRules.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ internal static class OpenApiResponseRules
+ {
+ ///
+ /// Validate the field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, response) =>
+ {
+ // description
+ context.Push("description");
+ if (String.IsNullOrEmpty(response.Description))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "description", "response"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rule.
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiRuleAttribute.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiRuleAttribute.cs
new file mode 100644
index 000000000..a9bc65bad
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiRuleAttribute.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The Validator attribute.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ internal class OpenApiRuleAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs
new file mode 100644
index 000000000..e12ebdf87
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiServerRules.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ public static class OpenApiServerRules
+ {
+ ///
+ /// Validate the field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, server) =>
+ {
+ context.Push("url");
+ if (String.IsNullOrEmpty(server.Url))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "url", "server"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rules
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs
new file mode 100644
index 000000000..78c2c972d
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiTagRules.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ [OpenApiRule]
+ public static class OpenApiTagRules
+ {
+ ///
+ /// Validate the field is required.
+ ///
+ public static ValidationRule FieldIsRequired =>
+ new ValidationRule(
+ (context, tag) =>
+ {
+ context.Push("name");
+ if (String.IsNullOrEmpty(tag.Name))
+ {
+ ValidationError error = new ValidationError(ErrorReason.Required, context.PathString,
+ String.Format(SRResource.Validation_FieldIsRequired, "name", "tag"));
+ context.AddError(error);
+ }
+ context.Pop();
+ });
+
+ // add more rules
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs
new file mode 100644
index 000000000..5f369fd9f
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ internal static class RuleHelpers
+ {
+ ///
+ /// Input string must be in the format of an email address
+ ///
+ /// The input string.
+ /// True if it's an email address. Otherwise False.
+ public static bool IsEmailAddress(this string input)
+ {
+ if (String.IsNullOrEmpty(input))
+ {
+ return false;
+ }
+
+ var splits = input.Split('@');
+ if (splits.Length != 2)
+ {
+ return false;
+ }
+
+ if (String.IsNullOrEmpty(splits[0]) || String.IsNullOrEmpty(splits[1]))
+ {
+ return false;
+ }
+
+ // Add more rules.
+
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/ValidationContext.cs b/src/Microsoft.OpenApi/Validations/ValidationContext.cs
new file mode 100644
index 000000000..866e21e6e
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationContext.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.OpenApi.Validations.Rules;
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// The validation context.
+ ///
+ public class ValidationContext
+ {
+ private readonly IList _errors = new List();
+
+ ///
+ /// Initializes the class.
+ ///
+ ///
+ public ValidationContext(ValidationRuleSet ruleSet)
+ {
+ RuleSet = ruleSet ?? throw Error.ArgumentNull(nameof(ruleSet));
+ }
+
+ ///
+ /// Gets the rule set.
+ ///
+ public ValidationRuleSet RuleSet { get; }
+
+ ///
+ /// Gets the validation errors.
+ ///
+ public IEnumerable Errors
+ {
+ get
+ {
+ return _errors;
+ }
+ }
+
+ ///
+ /// Register an error with the validation context.
+ ///
+ /// Error to register.
+ public void AddError(ValidationError error)
+ {
+ if (error == null)
+ {
+ throw Error.ArgumentNull(nameof(error));
+ }
+
+ _errors.Add(error);
+ }
+
+ #region Visit Path
+ private readonly Stack _path = new Stack();
+
+ internal void Push(string segment)
+ {
+ this._path.Push(segment);
+ }
+
+ internal void Pop()
+ {
+ this._path.Pop();
+ }
+
+ internal string PathString
+ {
+ get
+ {
+ return "#/" + String.Join("/", _path);
+ }
+ }
+ #endregion
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/ValidationError.cs b/src/Microsoft.OpenApi/Validations/ValidationError.cs
new file mode 100644
index 000000000..8ea695fa0
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationError.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// Error reason.
+ ///
+ public enum ErrorReason
+ {
+ ///
+ /// Field is required.
+ ///
+ Required,
+
+ ///
+ /// Format error.
+ ///
+ Format,
+ }
+
+ ///
+ /// The validation error class.
+ ///
+ public sealed class ValidationError
+ {
+ ///
+ /// Initializes the class.
+ ///
+ /// The error reason.
+ /// The visit path.
+ /// The error message.
+ public ValidationError(ErrorReason reason, string path, string message)
+ {
+ ErrorCode = reason;
+ ErrorPath = path;
+ ErrorMessage = message;
+ }
+
+ ///
+ /// Gets the path of the error in the Open API in which it occurred.
+ ///
+ public string ErrorPath { get; private set; }
+
+ ///
+ /// Gets an integer code representing the error.
+ ///
+ public ErrorReason ErrorCode { get; private set; }
+
+ ///
+ /// Gets a human readable string describing the error.
+ ///
+ public string ErrorMessage { get; private set; }
+
+ ///
+ /// Returns the whole error message.
+ ///
+ /// The error string.
+ public override string ToString()
+ {
+ return "ErrorCode: " + ErrorCode + ", " + ErrorPath + " | " + ErrorMessage;
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/ValidationRule.cs
new file mode 100644
index 000000000..beb3f519b
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationRule.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// Class containing validation rule logic.
+ ///
+ public abstract class ValidationRule
+ {
+ ///
+ /// Element Type.
+ ///
+ internal abstract Type ElementType { get; }
+
+ ///
+ /// Validate the object.
+ ///
+ /// The context.
+ /// The object item.
+ internal abstract void Evaluate(ValidationContext context, object item);
+ }
+
+ ///
+ /// Class containing validation rule logic for .
+ ///
+ ///
+ public class ValidationRule : ValidationRule where T: IOpenApiElement
+ {
+ private readonly Action _validate;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Action to perform the validation.
+ public ValidationRule(Action validate)
+ {
+ _validate = validate ?? throw Error.ArgumentNull(nameof(validate));
+ }
+
+ internal override Type ElementType
+ {
+ get { return typeof(T); }
+ }
+
+ internal override void Evaluate(ValidationContext context, object item)
+ {
+ if (context == null)
+ {
+ throw Error.ArgumentNull(nameof(context));
+ }
+
+ if (item == null)
+ {
+ return;
+ }
+
+ if (!(item is T))
+ {
+ throw Error.Argument(string.Format(SRResource.InputItemShouldBeType, typeof(T).FullName));
+ }
+
+ T typedItem = (T)item;
+ this._validate(context, typedItem);
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs
new file mode 100644
index 000000000..8cc9a78b0
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs
@@ -0,0 +1,149 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.OpenApi.Exceptions;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Validations.Rules;
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// The rule set of the validation.
+ ///
+ public sealed class ValidationRuleSet : IEnumerable
+ {
+ private IDictionary> _rules = new Dictionary>();
+
+ private static ValidationRuleSet _defaultRuleSet;
+
+ ///
+ /// Gets the default validation rule sets.
+ ///
+ public static ValidationRuleSet DefaultRuleSet
+ {
+ get
+ {
+ if (_defaultRuleSet == null)
+ {
+ _defaultRuleSet = new Lazy(() => BuildDefaultRuleSet(), isThreadSafe: false).Value;
+ }
+
+ return _defaultRuleSet;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ValidationRuleSet()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Rules to be contained in this ruleset.
+ public ValidationRuleSet(IEnumerable rules)
+ {
+ if (rules != null)
+ {
+ foreach (ValidationRule rule in rules)
+ {
+ Add(rule);
+ }
+ }
+ }
+
+ ///
+ /// Gets the rules in this rule set.
+ ///
+ public IEnumerable Rules
+ {
+ get
+ {
+ return _rules.Values.SelectMany(v => v);
+ }
+ }
+
+ ///
+ /// Add the new rule into rule set.
+ ///
+ /// The rule.
+ public void Add(ValidationRule rule)
+ {
+ IList typeRules;
+ if (!_rules.TryGetValue(rule.ElementType, out typeRules))
+ {
+ typeRules = new List();
+ _rules[rule.ElementType] = typeRules;
+ }
+
+ if (typeRules.Contains(rule))
+ {
+ throw new OpenApiException(SRResource.Validation_RuleAddTwice);
+ }
+
+ typeRules.Add(rule);
+ }
+
+ ///
+ /// Get the enumerator.
+ ///
+ /// The enumerator.
+ public IEnumerator GetEnumerator()
+ {
+ foreach (List ruleList in _rules.Values)
+ {
+ foreach (var rule in ruleList)
+ {
+ yield return rule;
+ }
+ }
+ }
+
+ ///
+ /// Get the enumerator.
+ ///
+ /// The enumerator.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ private static ValidationRuleSet BuildDefaultRuleSet()
+ {
+ ValidationRuleSet ruleSet = new ValidationRuleSet();
+
+ IEnumerable allTypes = typeof(ValidationRuleSet).Assembly.GetTypes().Where(t => t.IsClass && t != typeof(object));
+ Type validationRuleType = typeof(ValidationRule);
+ foreach (Type type in allTypes)
+ {
+ if (!type.GetCustomAttributes(typeof(OpenApiRuleAttribute), false).Any())
+ {
+ continue;
+ }
+
+ var properties = type.GetProperties(BindingFlags.Static | BindingFlags.Public);
+ foreach (var property in properties)
+ {
+ if (validationRuleType.IsAssignableFrom(property.PropertyType))
+ {
+ var propertyValue = property.GetValue(null); // static property
+ ValidationRule rule = propertyValue as ValidationRule;
+ if (rule != null)
+ {
+ ruleSet.Add(rule);
+ }
+ }
+ }
+ }
+
+ return ruleSet;
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs
index af5ec8a05..34d5ae1d2 100644
--- a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs
@@ -1,11 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
+using System;
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.OpenApi.Exceptions;
using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
using Microsoft.OpenApi.Services;
+using Microsoft.OpenApi.Validations;
using Xunit;
namespace Microsoft.OpenApi.Tests.Services
@@ -17,7 +20,11 @@ public class OpenApiValidatorTests
public void ResponseMustHaveADescription()
{
var openApiDocument = new OpenApiDocument();
-
+ openApiDocument.Info = new OpenApiInfo()
+ {
+ Title = "foo",
+ Version = "1.2.2"
+ };
openApiDocument.Paths.Add(
"/test",
new OpenApiPathItem
@@ -38,11 +45,12 @@ public void ResponseMustHaveADescription()
var walker = new OpenApiWalker(validator);
walker.Walk(openApiDocument);
- validator.Exceptions.ShouldBeEquivalentTo(
- new List
- {
- new OpenApiException("Response must have a description")
- });
+ validator.Errors.ShouldBeEquivalentTo(
+ new List
+ {
+ new ValidationError(ErrorReason.Required, "#/description",
+ String.Format(SRResource.Validation_FieldIsRequired, "description", "response"))
+ });
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs
new file mode 100644
index 000000000..43aaefe57
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Extensions;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Microsoft.OpenApi.Validations.Rules;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiComponentsValidationTests
+ {
+ [Fact]
+ public void ValidateKeyMustMatchRegularExpressionInComponents()
+ {
+ // Arrange
+ const string key = "%@abc";
+
+ OpenApiComponents components = new OpenApiComponents()
+ {
+ Responses = new Dictionary
+ {
+ { key, new OpenApiResponse { Description = "any" } }
+ }
+ };
+
+ var errors = components.Validate();
+
+ // Act
+ bool result = !errors.Any();
+
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, "responses", OpenApiComponentsRules.KeyRegex.ToString()),
+ error.ErrorMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs
new file mode 100644
index 000000000..80f4743a7
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Extensions;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiContactValidationTests
+ {
+ [Fact]
+ public void ValidateEmailFieldIsEmailAddressInContact()
+ {
+ // Arrange
+ const string testEmail = "support/example.com";
+
+ OpenApiContact contact = new OpenApiContact()
+ {
+ Email = testEmail
+ };
+
+ // Act
+ var errors = contact.Validate();
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_StringMustBeEmailAddress, testEmail), error.ErrorMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs
new file mode 100644
index 000000000..6f350d407
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Extensions;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiExternalDocsValidationTests
+ {
+ [Fact]
+ public void ValidateUrlIsRequiredInExternalDocs()
+ {
+ // Arrange
+ OpenApiExternalDocs externalDocs = new OpenApiExternalDocs();
+
+ // Act
+ var errors = externalDocs.Validate();
+
+ // Assert
+
+ bool result = !errors.Any();
+
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_FieldIsRequired, "url", "External Documentation"), error.ErrorMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs
new file mode 100644
index 000000000..285d64be7
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiInfoValidationTests.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiInfoValidationTests
+ {
+ [Fact]
+ public void ValidateFieldIsRequiredInInfo()
+ {
+ // Arrange
+ string urlError = String.Format(SRResource.Validation_FieldIsRequired, "url", "info");
+ string versionError = String.Format(SRResource.Validation_FieldIsRequired, "version", "info");
+ IEnumerable errors;
+ OpenApiInfo info = new OpenApiInfo();
+
+ // Act
+ var validator = new OpenApiValidator();
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(info);
+
+
+ // Assert
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+
+ Assert.Equal(new[] { urlError, versionError }, errors.Select(e => e.ErrorMessage));
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiLicenseValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiLicenseValidationTests.cs
new file mode 100644
index 000000000..ad542ba5f
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiLicenseValidationTests.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiLicenseValidationTests
+ {
+ [Fact]
+ public void ValidateFieldIsRequiredInLicense()
+ {
+ // Arrange
+ IEnumerable errors;
+ OpenApiLicense license = new OpenApiLicense();
+
+ // Act
+ var validator = new OpenApiValidator();
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(license);
+
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_FieldIsRequired, "name", "license"), error.ErrorMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiOAuthFlowValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiOAuthFlowValidationTests.cs
new file mode 100644
index 000000000..3d38e60e8
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiOAuthFlowValidationTests.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiOAuthFlowValidationTests
+ {
+ [Fact]
+ public void ValidateFixedFieldsIsRequiredInResponse()
+ {
+ // Arrange
+ string authorizationUrlError = String.Format(SRResource.Validation_FieldIsRequired, "authorizationUrl", "OAuth Flow");
+ string tokenUrlError = String.Format(SRResource.Validation_FieldIsRequired, "tokenUrl", "OAuth Flow");
+ IEnumerable errors;
+ OpenApiOAuthFlow oAuthFlow = new OpenApiOAuthFlow();
+
+ // Act
+ var validator = new OpenApiValidator();
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(oAuthFlow);
+
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ Assert.Equal(2, errors.Count());
+ Assert.Equal(new[] { authorizationUrlError, tokenUrlError }, errors.Select(e => e.ErrorMessage));
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiResponseValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiResponseValidationTests.cs
new file mode 100644
index 000000000..494bc0274
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiResponseValidationTests.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiResponseValidationTests
+ {
+ [Fact]
+ public void ValidateDescriptionIsRequiredInResponse()
+ {
+ // Arrange
+ IEnumerable errors;
+ OpenApiResponse response = new OpenApiResponse();
+
+ // Act
+ var validator = new OpenApiValidator();
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(response);
+
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_FieldIsRequired, "description", "response"), error.ErrorMessage);
+ Assert.Equal(ErrorReason.Required, error.ErrorCode);
+ Assert.Equal("#/description", error.ErrorPath);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs
new file mode 100644
index 000000000..f3f5ece46
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiServerValidationTests.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiServerValidationTests
+ {
+ [Fact]
+ public void ValidateFieldIsRequiredInServer()
+ {
+ // Arrange
+ IEnumerable errors;
+ OpenApiServer server = new OpenApiServer();
+
+ // Act
+ var validator = new OpenApiValidator();
+ validator.Visit(server);
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_FieldIsRequired, "url", "server"), error.ErrorMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs
new file mode 100644
index 000000000..56696ffac
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Any;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+using Microsoft.OpenApi.Services;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class OpenApiTagValidationTests
+ {
+ [Fact]
+ public void ValidateNameIsRequiredInTag()
+ {
+ // Arrange
+ IEnumerable errors;
+ OpenApiTag tag = new OpenApiTag();
+
+ // Act
+ var validator = new OpenApiValidator();
+ validator.Visit(tag);
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_FieldIsRequired, "name", "tag"), error.ErrorMessage);
+ }
+
+ [Fact]
+ public void ValidateExtensionNameStartsWithXDashInTag()
+ {
+ // Arrange
+ IEnumerable errors;
+ OpenApiTag tag = new OpenApiTag
+ {
+ Name = "tag"
+ };
+ tag.Extensions.Add("tagExt", new OpenApiString("value"));
+
+ // Act
+ var validator = new OpenApiValidator();
+ validator.Visit(tag as IOpenApiExtensible);
+ errors = validator.Errors;
+ bool result = !errors.Any();
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal(String.Format(SRResource.Validation_ExtensionNameMustBeginWithXDash, "tagExt", "#/extensions"), error.ErrorMessage);
+ }
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
new file mode 100644
index 000000000..9e4bfc80c
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.OpenApi.Validations.Tests
+{
+ public class ValidationRuleSetTests
+ {
+ [Fact]
+ public void DefaultRuleSetReturnsTheCorrectRules()
+ {
+ // Arrange
+ var ruleSet = new ValidationRuleSet();
+
+ // Act
+ var rules = ruleSet.Rules;
+
+ // Assert
+ Assert.NotNull(rules);
+ Assert.Empty(rules);
+ }
+
+ [Fact]
+ public void DefaultRuleSetPropertyReturnsTheCorrectRules()
+ {
+ // Arrange & Act
+ var ruleSet = ValidationRuleSet.DefaultRuleSet;
+ Assert.NotNull(ruleSet); // guard
+
+ var rules = ruleSet.Rules;
+
+ // Assert
+ Assert.NotNull(rules);
+ Assert.NotEmpty(rules);
+ Assert.Equal(13, rules.ToList().Count); // please update the number if you add new rule.
+ }
+ }
+}