diff --git a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
index 2b77e680a..7ad783f28 100644
--- a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
+++ b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
@@ -239,5 +239,23 @@ internal static string SourceExpressionHasInvalidFormat {
return ResourceManager.GetString("SourceExpressionHasInvalidFormat", 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 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..6eab4a17e 100644
--- a/src/Microsoft.OpenApi/Properties/SRResource.resx
+++ b/src/Microsoft.OpenApi/Properties/SRResource.resx
@@ -177,4 +177,10 @@
The source expression '{0}' has invalid format.
+
+ The path item name '{0}' MUST begin with a slash.
+
+
+ The string '{0}' MUST be in the format of an email address.
+
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Validations/OpenApiElementValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiElementValidator.cs
new file mode 100644
index 000000000..e3613f5c5
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/OpenApiElementValidator.cs
@@ -0,0 +1,75 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Validations.Rules;
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// The public APIs to validate the Open API element.
+ ///
+ public static class OpenApiElementValidator
+ {
+ ///
+ /// Validate the Open API element.
+ ///
+ /// The Open API element type.
+ /// The Open API element.
+ /// True means no errors, otherwise with errors.
+ public static bool Validate(this T element) where T : IOpenApiElement
+ {
+ IEnumerable errors;
+ return element.Validate(out errors);
+ }
+
+ ///
+ /// Validate the Open API element.
+ ///
+ /// The Open API element type.
+ /// The Open API element.
+ /// The output errors.
+ /// True means no errors, otherwise with errors.
+ public static bool Validate(this T element, out IEnumerable errors)
+ where T : IOpenApiElement
+ {
+ ValidationRuleSet ruleSet = ValidationRuleSet.DefaultRuleSet;
+ return element.Validate(ruleSet, out errors);
+ }
+
+ ///
+ /// Validate the Open API element.
+ ///
+ /// The Open API element type.
+ /// The Open API element.
+ /// The input rule set.
+ /// The output errors.
+ /// True means no errors, otherwise with errors.
+ public static bool Validate(this T element, ValidationRuleSet ruleSet, out IEnumerable errors)
+ where T : IOpenApiElement
+ {
+ errors = null;
+ if (element == null)
+ {
+ return true;
+ }
+
+ if (ruleSet == null)
+ {
+ ruleSet = ValidationRuleSet.DefaultRuleSet;
+ }
+
+ ValidationContext context = new ValidationContext(ruleSet);
+
+ context.Validate(element);
+
+ errors = context.Errors;
+
+ return !errors.Any();
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs
new file mode 100644
index 000000000..96b7e2735
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs
@@ -0,0 +1,16 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ public static class OpenApiComponentsRules
+ {
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs
new file mode 100644
index 000000000..2ebb37596
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs
@@ -0,0 +1,52 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ internal static class OpenApiContactRules
+ {
+ ///
+ /// Email field MUST be email address.
+ ///
+ public static readonly ValidationRule EmailMustBeEmailFormat =
+ new ValidationRule(
+ (context, item) =>
+ {
+ context.Push("email");
+ if (item != null && item.Email != null)
+ {
+ if (!item.Email.IsEmailAddress())
+ {
+ ValidationError error = new ValidationError(ErrorReason.EmailFormat, context.PathString,
+ String.Format(SRResource.Validation_StringMustBeEmailAddress, item.Email));
+ context.AddError(error);
+ }
+ }
+ context.Pop();
+ });
+
+ ///
+ /// Url field MUST be url format.
+ ///
+ public static readonly 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..511eccbea
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs
@@ -0,0 +1,41 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ internal static class OpenApiDocumentRules
+ {
+ ///
+ /// The Info field is required.
+ ///
+ public static readonly ValidationRule InfoIsRequired =
+ new ValidationRule(
+ (context, item) =>
+ {
+ if (item.Info == null)
+ {
+ //context.AddError();
+ }
+ });
+
+ ///
+ /// The Paths field is required.
+ ///
+ public static readonly ValidationRule PathsIsRequired =
+ new ValidationRule(
+ (context, item) =>
+ {
+ if (item.Paths == null)
+ {
+ //context.AddError();
+ }
+ });
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs
new file mode 100644
index 000000000..5bdf0f5cf
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiExtensionRules.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Interfaces;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ public static class OpenApiExtensibleRules
+ {
+ ///
+ /// Extension name MUST start with "x-".
+ ///
+ private static readonly ValidationRule ExtensionNameMustStartWithXDollar =
+ new ValidationRule(
+ (context, item) =>
+ {
+ foreach (var extensible in item.Extensions)
+ {
+
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs
new file mode 100644
index 000000000..b03f8ad8e
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs
@@ -0,0 +1,31 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System;
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ internal static class OpenApiInfoRules
+ {
+ ///
+ /// The title of the application is required.
+ ///
+ public static readonly ValidationRule TitleIsRequired =
+ new ValidationRule(
+ (context, item) =>
+ {
+ if (String.IsNullOrEmpty(item.Title))
+ {
+ // add error.
+ }
+ });
+
+ // add more rule.
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs
new file mode 100644
index 000000000..437071c13
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs
@@ -0,0 +1,46 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Properties;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// The validation rules for .
+ ///
+ public static class OpenApiPathsRules
+ {
+ ///
+ /// A relative path to an individual endpoint. The field name MUST begin with a slash.
+ ///
+ public static readonly 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/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs
new file mode 100644
index 000000000..67a52f809
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs
@@ -0,0 +1,40 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+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.
+ ///
+ 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/Rules/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/Rules/ValidationRule.cs
new file mode 100644
index 000000000..d9575969e
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/ValidationRule.cs
@@ -0,0 +1,48 @@
+
+using System;
+using System.Diagnostics;
+using Microsoft.OpenApi.Interfaces;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ /// Class containing validation rule logic.
+ ///
+ public abstract class ValidationRule
+ {
+ internal abstract Type ValidatedType { get; }
+
+ 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)
+ {
+ this._validate = validate;
+ }
+
+ internal override Type ValidatedType
+ {
+ get { return typeof(T); }
+ }
+
+ internal override void Evaluate(ValidationContext context, object item)
+ {
+ Debug.Assert(item is T, "item should be " + typeof(T));
+ T typedItem = (T)item;
+ this._validate(context, typedItem);
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Rules/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/Rules/ValidationRuleSet.cs
new file mode 100644
index 000000000..d85c0c201
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Rules/ValidationRuleSet.cs
@@ -0,0 +1,114 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.OpenApi.Exceptions;
+
+namespace Microsoft.OpenApi.Validations.Rules
+{
+ ///
+ ///
+ ///
+ public sealed class ValidationRuleSet : IEnumerable
+ {
+ private IDictionary> _rules = new Dictionary>();
+
+ ///
+ /// The default rule set.
+ ///
+ public static ValidationRuleSet DefaultRuleSet = new ValidationRuleSet
+ {
+ OpenApiDocumentRules.InfoIsRequired,
+ OpenApiDocumentRules.PathsIsRequired,
+
+ OpenApiInfoRules.TitleIsRequired,
+
+ OpenApiContactRules.EmailMustBeEmailFormat,
+
+ // add more default rules.
+ };
+
+ ///
+ /// 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.ValidatedType, out typeRules))
+ {
+ typeRules = new List();
+ _rules[rule.ValidatedType] = typeRules;
+ }
+
+ if (typeRules.Contains(rule))
+ {
+ throw new OpenApiException("The same rule cannot be in the same rule set twice.");
+ }
+
+ 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();
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/ValidationContext.cs b/src/Microsoft.OpenApi/Validations/ValidationContext.cs
new file mode 100644
index 000000000..0adcee72c
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationContext.cs
@@ -0,0 +1,80 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+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/ValidationContextExtensions.cs b/src/Microsoft.OpenApi/Validations/ValidationContextExtensions.cs
new file mode 100644
index 000000000..0cb8325c2
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationContextExtensions.cs
@@ -0,0 +1,60 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Validations.Visitors;
+using System.Collections.Generic;
+
+namespace Microsoft.OpenApi.Validations
+{
+ internal static class ValidationContextExtensions
+ {
+ ///
+ /// Validate the Open API element.
+ ///
+ /// The Open API element.
+ /// The validation cotext.
+ /// The Open API element.
+ public static void Validate(this ValidationContext context, T element) where T : IOpenApiElement
+ {
+ if (context == null)
+ {
+ throw Error.ArgumentNull(nameof(context));
+ }
+
+ if (element != null)
+ {
+ IVisitor visitor = OpenApiVisitorSet.GetVisitor(typeof(T));
+ if (visitor != null)
+ {
+ visitor.Visit(context, element);
+ }
+ }
+ }
+
+ ///
+ /// Validate the collection of Open API element.
+ ///
+ /// The Open API element.
+ /// The validation cotext.
+ /// The collection of Open API element
+ public static void ValidateCollection(this ValidationContext context, IEnumerable collection)
+ where T : IOpenApiElement
+ {
+ if (context == null)
+ {
+ throw Error.ArgumentNull(nameof(context));
+ }
+
+ if (collection != null)
+ {
+ foreach (var element in collection)
+ {
+ context.Validate(element);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/ValidationError.cs b/src/Microsoft.OpenApi/Validations/ValidationError.cs
new file mode 100644
index 000000000..2356e7720
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/ValidationError.cs
@@ -0,0 +1,78 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+namespace Microsoft.OpenApi.Validations
+{
+ ///
+ /// Error reason.
+ ///
+ public enum ErrorReason
+ {
+ ///
+ /// Field is required.
+ ///
+ FieldIsRequired,
+
+ ///
+ /// Format error.
+ ///
+ Format,
+
+ ///
+ /// Must be email format.
+ ///
+ EmailFormat,
+
+ ///
+ /// Must be Url format.
+ ///
+ UrlFormat,
+
+ // Add more.
+ }
+
+ ///
+ /// 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 "ErroCode: " + ErrorCode + ", " + ErrorPath + " | " + ErrorMessage;
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/ContactVisitor.cs b/src/Microsoft.OpenApi/Validations/Visitors/ContactVisitor.cs
new file mode 100644
index 000000000..9b2420f9c
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/ContactVisitor.cs
@@ -0,0 +1,24 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// Visit .
+ ///
+ internal class ContactVisitor : VisitorBase
+ {
+ ///
+ /// Visit the children in .
+ ///
+ /// The validation context.
+ /// The .
+ protected override void Next(ValidationContext context, OpenApiContact contact)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/DocumentVisitor.cs b/src/Microsoft.OpenApi/Validations/Visitors/DocumentVisitor.cs
new file mode 100644
index 000000000..032617ea5
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/DocumentVisitor.cs
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// Vistor for
+ ///
+ internal class DocumentVisitor : VisitorBase
+ {
+ ///
+ /// Visit the children of the .
+ ///
+ /// The validation context.
+ /// The .
+ protected override void Next(ValidationContext context, OpenApiDocument document)
+ {
+ if (document == null)
+ {
+ return;
+ }
+
+ context.Validate(document.Info);
+
+ context.ValidateCollection(document.Tags);
+
+ // add more.
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/IVisitor.cs b/src/Microsoft.OpenApi/Validations/Visitors/IVisitor.cs
new file mode 100644
index 000000000..60374d19c
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/IVisitor.cs
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// The interface for visitor.
+ ///
+ internal interface IVisitor
+ {
+ ///
+ /// Visit the object.
+ ///
+ /// The validation context.
+ /// The validation object.
+ void Visit(ValidationContext context, object item);
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/InfoVisitor.cs b/src/Microsoft.OpenApi/Validations/Visitors/InfoVisitor.cs
new file mode 100644
index 000000000..23e708eec
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/InfoVisitor.cs
@@ -0,0 +1,32 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// Visit .
+ ///
+ internal class InfoVisitor : VisitorBase
+ {
+ ///
+ /// Visit the children in .
+ ///
+ /// The validation context.
+ /// The .
+ protected override void Next(ValidationContext context, OpenApiInfo info)
+ {
+ if (info == null)
+ {
+ return;
+ }
+
+ context.Validate(info.Contact);
+
+ context.Validate(info.License);
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/LicenseVisitor.cs b/src/Microsoft.OpenApi/Validations/Visitors/LicenseVisitor.cs
new file mode 100644
index 000000000..30361834f
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/LicenseVisitor.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// Visit .
+ ///
+ internal class LicenseVisitor : VisitorBase
+ {
+ ///
+ /// Visit the children in .
+ ///
+ /// The validation context.
+ /// The .
+ protected override void Next(ValidationContext context, OpenApiLicense license)
+ {
+ if (license == null)
+ {
+ return;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/OpenApiVisitorSet.cs b/src/Microsoft.OpenApi/Validations/Visitors/OpenApiVisitorSet.cs
new file mode 100644
index 000000000..d48f77120
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/OpenApiVisitorSet.cs
@@ -0,0 +1,45 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ ///
+ ///
+ internal static class OpenApiVisitorSet
+ {
+ private static IDictionary _elementVisitor = new Dictionary
+ {
+ { typeof(OpenApiDocument), new DocumentVisitor() },
+ { typeof(OpenApiInfo), new InfoVisitor() },
+ { typeof(OpenApiTag), new TagVisitor() },
+ { typeof(OpenApiLicense), new LicenseVisitor() },
+ { typeof(OpenApiContact), new ContactVisitor() },
+
+ // add more
+ };
+
+ ///
+ /// Get the element visitor.
+ ///
+ /// The element type.
+ /// The element visitor or null.
+ public static IVisitor GetVisitor(Type elementType)
+ {
+ var visitor = _elementVisitor.FirstOrDefault(c => c.Key == elementType).Value;
+ if (visitor != null)
+ {
+ return visitor;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/TagVisitor.cs b/src/Microsoft.OpenApi/Validations/Visitors/TagVisitor.cs
new file mode 100644
index 000000000..a7b193367
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/TagVisitor.cs
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// Visit .
+ ///
+ internal class TagVisitor : VisitorBase
+ {
+ ///
+ /// Visit the children in .
+ ///
+ /// The validation context.
+ /// The .
+ protected override void Next(ValidationContext context, OpenApiTag tag)
+ {
+ if (tag == null)
+ {
+ return;
+ }
+
+ context.Validate(tag.ExternalDocs);
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi/Validations/Visitors/VisitorBase.cs b/src/Microsoft.OpenApi/Validations/Visitors/VisitorBase.cs
new file mode 100644
index 000000000..8a9075283
--- /dev/null
+++ b/src/Microsoft.OpenApi/Validations/Visitors/VisitorBase.cs
@@ -0,0 +1,43 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.OpenApi.Interfaces;
+
+namespace Microsoft.OpenApi.Validations.Visitors
+{
+ ///
+ /// The base visitor class for Open Api elements.
+ ///
+ internal abstract class VisitorBase : IVisitor where T : IOpenApiElement
+ {
+ ///
+ /// Visit the element itself.
+ ///
+ /// The validation context.
+ /// The element.
+ public void Visit(ValidationContext context, object item)
+ {
+ Debug.Assert(item is T, "item should be " + typeof(T));
+
+ var rules = context.RuleSet.Where(r => r.ValidatedType == typeof(T));
+ foreach (var rule in rules)
+ {
+ rule.Evaluate(context, item);
+ }
+
+ T typedItem = (T)item;
+ this.Next(context, typedItem);
+ }
+
+ ///
+ /// Visit the children.
+ ///
+ /// The validation context.
+ /// The element.
+ protected abstract void Next(ValidationContext context, T element);
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs
new file mode 100644
index 000000000..ad2406d86
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// ------------------------------------------------------------
+
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Validations;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.OpenApi.Tests.Models
+{
+ public class OpenApiContactValidationTests
+ {
+ public static OpenApiContact Contact = new OpenApiContact()
+ {
+ Email = "support/example.com",
+ };
+
+ [Fact]
+ public void SerializeAdvanceContactAsJsonWorks()
+ {
+ // Arrange
+ IEnumerable errors;
+
+ // Act
+ bool result = Contact.Validate(out errors);
+
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(errors);
+ ValidationError error = Assert.Single(errors);
+ Assert.Equal("The string 'support/example.com' MUST be in the format of an email address.", error.ErrorMessage);
+ }
+ }
+}