From f7d96d8977cd006c308c7e80e72787eda5664841 Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 25 Oct 2024 09:28:07 +0200 Subject: [PATCH] chore: add AvroValidationRule for names and symbols (#197) --- src/LEGO.AsyncAPI/Resource.Designer.cs | 18 ++++++ src/LEGO.AsyncAPI/Resource.resx | 6 ++ .../Services/AsyncApiVisitorBase.cs | 8 +++ src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs | 11 +++- .../Validation/AsyncApiValidator.cs | 4 ++ .../Validation/Rules/AsyncApiAvroRules.cs | 60 +++++++++++++++++++ .../Validation/ValidationRulesetTests.cs | 2 +- 7 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs diff --git a/src/LEGO.AsyncAPI/Resource.Designer.cs b/src/LEGO.AsyncAPI/Resource.Designer.cs index 983e6ad0..755eda9c 100644 --- a/src/LEGO.AsyncAPI/Resource.Designer.cs +++ b/src/LEGO.AsyncAPI/Resource.Designer.cs @@ -104,5 +104,23 @@ internal static string Validation_MustBeAbsoluteUrl { return ResourceManager.GetString("Validation_MustBeAbsoluteUrl", resourceCulture); } } + + /// + /// Looks up a localized string similar to '{0}' MUST match the regular expression '{1}'.. + /// + internal static string Validation_NameMustMatchRegularExpr { + get { + return ResourceManager.GetString("Validation_NameMustMatchRegularExpr", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Symbols MUST match the regular expression '{1}'.. + /// + internal static string Validation_SymbolsMustMatchRegularExpression { + get { + return ResourceManager.GetString("Validation_SymbolsMustMatchRegularExpression", resourceCulture); + } + } } } diff --git a/src/LEGO.AsyncAPI/Resource.resx b/src/LEGO.AsyncAPI/Resource.resx index fa6a1ecc..9d882464 100644 --- a/src/LEGO.AsyncAPI/Resource.resx +++ b/src/LEGO.AsyncAPI/Resource.resx @@ -132,4 +132,10 @@ The field '{0}' in '{1}' object MUST be an absolute uri. + + '{0}' MUST match the regular expression '{1}'. + + + Symbols MUST match the regular expression '{1}'. + \ No newline at end of file diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs index 899731d0..c048e0f5 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiVisitorBase.cs @@ -55,6 +55,14 @@ public virtual void Visit(AsyncApiDocument doc) { } + public virtual void Visit(AsyncApiJsonSchemaPayload jsonPayload) + { + } + + public virtual void Visit(AsyncApiAvroSchemaPayload avroPayload) + { + } + public virtual void Visit(IDictionary anys) { } diff --git a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs index a228496a..9f9c40ee 100644 --- a/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs +++ b/src/LEGO.AsyncAPI/Services/AsyncApiWalker.cs @@ -326,6 +326,11 @@ internal void Walk(AsyncApiParameter parameter, bool isComponent = false) this.Walk(parameter as IAsyncApiExtensible); } + internal void Walk(AsyncApiAvroSchemaPayload payload) + { + this.visitor.Visit(payload); + } + internal void Walk(AsyncApiSchema schema, bool isComponent = false) { if (schema == null || this.ProcessAsReference(schema, isComponent)) @@ -539,7 +544,11 @@ internal void Walk(AsyncApiMessage message, bool isComponent = false) this.Walk(AsyncApiConstants.Payload, () => this.Walk((AsyncApiSchema)payload)); } - // #ToFix Add walking for avro. + if (message.Payload is AsyncApiAvroSchemaPayload avroPayload) + { + this.Walk(AsyncApiConstants.Payload, () => this.Walk(avroPayload)); + } + this.Walk(AsyncApiConstants.CorrelationId, () => this.Walk(message.CorrelationId)); this.Walk(AsyncApiConstants.Tags, () => this.Walk(message.Tags)); this.Walk(AsyncApiConstants.Examples, () => this.Walk(message.Examples)); diff --git a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs index 3f8bf392..32b36ca6 100644 --- a/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs +++ b/src/LEGO.AsyncAPI/Validation/AsyncApiValidator.cs @@ -144,6 +144,10 @@ public void AddWarning(AsyncApiValidatorWarning warning) public override void Visit(IMessageBinding item) => this.Validate(item); + public override void Visit(AsyncApiAvroSchemaPayload item) => this.Validate(item); + + public override void Visit(AsyncApiJsonSchemaPayload item) => this.Validate(item); + /// /// Execute validation rules against an . /// diff --git a/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs new file mode 100644 index 00000000..b12c88b2 --- /dev/null +++ b/src/LEGO.AsyncAPI/Validation/Rules/AsyncApiAvroRules.cs @@ -0,0 +1,60 @@ +// Copyright (c) The LEGO Group. All rights reserved. + +namespace LEGO.AsyncAPI.Validation.Rules +{ + using System.Linq; + using System.Text.RegularExpressions; + using LEGO.AsyncAPI.Models; + using LEGO.AsyncAPI.Validations; + + [AsyncApiRule] + public static class AsyncApiAvroRules + { + /// + /// The key regex. + /// + public static Regex NameRegex = new Regex(@"^[A-Za-z_][A-Za-z0-9_]*$"); + + public static ValidationRule NameRegularExpression => + new ValidationRule( + (context, avroPayload) => + { + string name = null; + context.Enter("name"); + if (avroPayload.TryGetAs(out var record)) + { + name = record.Name; + } + + if (avroPayload.TryGetAs(out var @enum)) + { + name = @enum.Name; + if (@enum.Symbols.Any(symbol => !NameRegex.IsMatch(symbol))) + { + context.CreateError( + "SymbolsRegularExpression", + string.Format(Resource.Validation_SymbolsMustMatchRegularExpression, NameRegex.ToString())); + } + } + + if (avroPayload.TryGetAs(out var @fixed)) + { + name = @fixed.Name; + } + + if (name == null) + { + return; + } + + if (!NameRegex.IsMatch(record.Name)) + { + context.CreateError( + nameof(NameRegex), + string.Format(Resource.Validation_NameMustMatchRegularExpr, name, NameRegex.ToString())); + } + + context.Exit(); + }); + } +} diff --git a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs index 48446738..6dda6f63 100644 --- a/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs +++ b/test/LEGO.AsyncAPI.Tests/Validation/ValidationRulesetTests.cs @@ -35,7 +35,7 @@ public void DefaultRuleSet_PropertyReturnsTheCorrectRules() Assert.IsNotEmpty(rules); // Update the number if you add new default rule(s). - Assert.AreEqual(17, rules.Count); + Assert.AreEqual(18, rules.Count); } } }