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); + } + } +}