From 2d869323f3a5cfdadbc8a6c1ae5772c47747ad52 Mon Sep 17 00:00:00 2001 From: christian <6939810+chkr1011@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:08:10 +0200 Subject: [PATCH] Address warnings from analyzers --- Samples/MQTTnet.Samples.csproj | 2 +- .../MQTTnet.AspTestApp.csproj | 2 +- .../MQTTnet.AspNetCore.csproj | 2 +- .../MQTTnet.Benchmarks.csproj | 4 +- .../MQTTnet.Extensions.Rpc.csproj | 2 +- .../MQTTnet.Extensions.TopicTemplate.csproj | 2 +- .../MqttTopicTemplate.cs | 575 +++++++++--------- Source/MQTTnet.Server/MQTTnet.Server.csproj | 2 +- Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj | 2 +- Source/MQTTnet.Tests/MQTTnet.Tests.csproj | 2 +- Source/MQTTnet/MQTTnet.csproj | 1 + 11 files changed, 297 insertions(+), 299 deletions(-) diff --git a/Samples/MQTTnet.Samples.csproj b/Samples/MQTTnet.Samples.csproj index 8606285c8..88e39be5e 100644 --- a/Samples/MQTTnet.Samples.csproj +++ b/Samples/MQTTnet.Samples.csproj @@ -10,10 +10,10 @@ false false 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj b/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj index 765e90351..b277d7349 100644 --- a/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj +++ b/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj @@ -9,10 +9,10 @@ false false 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index 4e46db45f..7bfd85cba 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -32,10 +32,10 @@ true true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 0d2e1857c..f1c414ea5 100644 --- a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -10,10 +10,10 @@ false true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true - true + false all true + latest-Recommended diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index a50a19a6f..b5b8eef92 100644 --- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -31,10 +31,10 @@ true true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj b/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj index e74a41187..5ba711528 100644 --- a/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj +++ b/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj @@ -32,10 +32,10 @@ For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/). true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs b/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs index b193e22c8..3fb7215d7 100644 --- a/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs +++ b/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs @@ -9,336 +9,333 @@ using System.Threading; using MQTTnet.Protocol; -namespace MQTTnet.Extensions.TopicTemplate +namespace MQTTnet.Extensions.TopicTemplate; + +/// +/// A topic template is an MQTT topic filter string that may contain +/// segments in curly braces called parameters. This well-known +/// 'moustache' syntax also matches AsyncAPI Channel Address Expressions. +/// The topic template is designed to support dynamic subscription/publication, +/// message-topic matching and routing. It is intended to be more convenient +/// than String.Format() for aforementioned purposes. +/// +/// +/// topic/subtopic/{parameter}/{otherParameter} +/// +public sealed class MqttTopicTemplate : IEquatable { + static readonly Regex MoustacheRegex = new("{([^/]+?)}", RegexOptions.Compiled); + + readonly string[] _parameterSegments; + + string _topicFilter; + /// - /// A topic template is an MQTT topic filter string that may contain - /// segments in curly braces called parameters. This well-known - /// 'moustache' syntax also matches AsyncAPI Channel Address Expressions. - /// The topic template is designed to support dynamic subscription/publication, - /// message-topic matching and routing. It is intended to be more convenient - /// than String.Format() for aforementioned purposes. + /// Create a topic template from an mqtt topic filter with moustache placeholders. /// - /// - /// topic/subtopic/{parameter}/{otherParameter} - /// - public sealed class MqttTopicTemplate : IEquatable + /// + /// + /// + /// + public MqttTopicTemplate(string topicTemplate) { - static readonly Regex MoustacheRegex = new Regex("{([^/]+?)}", RegexOptions.Compiled); - - readonly string[] _parameterSegments; - - string _topicFilter; - - /// - /// Create a topic template from an mqtt topic filter with moustache placeholders. - /// - /// - /// - /// - /// - public MqttTopicTemplate(string topicTemplate) + ArgumentNullException.ThrowIfNull(topicTemplate); + + MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate); + + Template = topicTemplate; + _parameterSegments = topicTemplate.Split(MqttTopicFilterComparer.LevelSeparator) + .Select(segment => MoustacheRegex.Match(segment).Groups[1].Value) + .Select(s => s.Length > 0 ? s : null) + .ToArray(); + } + + /// + /// Yield the template parameter names. + /// + public IEnumerable Parameters => _parameterSegments.Where(s => s != null); + + /// + /// The topic template string representation, e.g. A/B/{foo}/D. + /// + public string Template { get; } + + /// + /// The topic template as an MQTT topic filter (+ substituted for all parameters). If the template + /// ends with a multi-level wildcard (hash), this will be reflected here. + /// + public string TopicFilter + { + get { - if (topicTemplate == null) - { - throw new ArgumentNullException(nameof(topicTemplate)); - } - - MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate); - - Template = topicTemplate; - _parameterSegments = topicTemplate.Split(MqttTopicFilterComparer.LevelSeparator) - .Select(segment => MoustacheRegex.Match(segment).Groups[1].Value) - .Select(s => s.Length > 0 ? s : null) - .ToArray(); + LazyInitializer.EnsureInitialized(ref _topicFilter, () => MoustacheRegex.Replace(Template, MqttTopicFilterComparer.SingleLevelWildcard.ToString())); + return _topicFilter; } - - /// - /// Yield the template parameter names. - /// - public IEnumerable Parameters => _parameterSegments.Where(s => s != null); - - /// - /// The topic template string representation, e.g. A/B/{foo}/D. - /// - public string Template { get; } - - /// - /// The topic template as an MQTT topic filter (+ substituted for all parameters). If the template - /// ends with a multi-level wildcard (hash), this will be reflected here. - /// - public string TopicFilter + } + + /// + /// Return the topic filter of this template, ending with a multi-level wildcard (hash). + /// + public string TopicTreeRootFilter + { + get { - get + var filter = TopicFilter; + // append slash if neccessary + if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator) && !filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard)) { - LazyInitializer.EnsureInitialized(ref _topicFilter, () => MoustacheRegex.Replace(Template, MqttTopicFilterComparer.SingleLevelWildcard.ToString())); - return _topicFilter; + filter += MqttTopicFilterComparer.LevelSeparator; } - } - - /// - /// Return the topic filter of this template, ending with a multi-level wildcard (hash). - /// - public string TopicTreeRootFilter - { - get + + // append hash if neccessary + if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard)) { - var filter = TopicFilter; - // append slash if neccessary - if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) && - !filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString())) - { - filter += MqttTopicFilterComparer.LevelSeparator; - } - - // append hash if neccessary - if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString())) - { - filter += MqttTopicFilterComparer.MultiLevelWildcard; - } - - return filter; + filter += MqttTopicFilterComparer.MultiLevelWildcard; } + + return filter; } - - public bool Equals(MqttTopicTemplate other) + } + + public bool Equals(MqttTopicTemplate other) + { + return other != null && Template == other.Template; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - return other != null && Template == other.Template; + return false; } - - public override bool Equals(object obj) + + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((MqttTopicTemplate)obj); + return true; } - - /// - /// Determine the shortest common prefix of the given templates. Partial segments - /// are not returned. - /// - /// - /// topic templates - /// - /// - public static MqttTopicTemplate FindCanonicalPrefix(IEnumerable templates) + + if (obj.GetType() != GetType()) { - string root = null; - - string CommonPrefix(string a, string b) + return false; + } + + return Equals((MqttTopicTemplate)obj); + } + + /// + /// Determine the shortest common prefix of the given templates. Partial segments + /// are not returned. + /// + /// + /// topic templates + /// + /// + public static MqttTopicTemplate FindCanonicalPrefix(IEnumerable templates) + { + string root = null; + + string CommonPrefix(string a, string b) + { + var maxIndex = Math.Min(a.Length, b.Length) - 1; + for (var i = 0; i <= maxIndex; i++) { - var maxIndex = Math.Min(a.Length, b.Length) - 1; - for (var i = 0; i <= maxIndex; i++) + if (a[i] != b[i]) { - if (a[i] != b[i]) - { - return a.Substring(0, i); - } + return a.Substring(0, i); } - - return a.Substring(0, maxIndex+1); } - - foreach (string topic in from template in templates select template.Template) - { - root = root == null ? topic : CommonPrefix(root, topic); - } - - if (string.IsNullOrEmpty(root)) - return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString()); - - if (root.Contains(MqttTopicFilterComparer.LevelSeparator) && - !root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) && - !root.EndsWith("}")) - { - root = root.Substring(0, root.LastIndexOf(MqttTopicFilterComparer.LevelSeparator)+1); - } - - if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString())) - root += MqttTopicFilterComparer.SingleLevelWildcard; - return new MqttTopicTemplate(root); + return a.Substring(0, maxIndex + 1); } - - public override int GetHashCode() + + foreach (var topic in from template in templates select template.Template) { - return Template.GetHashCode(); + root = root == null ? topic : CommonPrefix(root, topic); } - - /// - /// Test if this topic template matches a given topic. - /// - /// - /// a fully specified topic - /// - /// - /// true to match including the subtree (multi-level wildcard) - /// - /// true iff the topic matches the template's filter - /// - /// - /// - /// if the topic is invalid - /// - public bool MatchesTopic(string topic, bool subtree = false) + + if (string.IsNullOrEmpty(root)) { - var comparison = MqttTopicFilterComparer.Compare(topic, subtree ? TopicTreeRootFilter : TopicFilter); - if (comparison == MqttTopicFilterCompareResult.FilterInvalid) - { - throw new InvalidOperationException("Invalid filter"); - } - - if (comparison == MqttTopicFilterCompareResult.TopicInvalid) - { - throw new ArgumentException("Invalid topic", nameof(topic)); - } - - return comparison == MqttTopicFilterCompareResult.IsMatch; + return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString()); } - - /// - /// Extract the parameter values from a topic corresponding to the template - /// parameters. The topic has to match this template. - /// - /// - /// the topic - /// - /// an enumeration of (parameter, index, value) - public IEnumerable<(string parameter, int index, string value)> ParseParameterValues(string topic) + + if (root.Contains(MqttTopicFilterComparer.LevelSeparator) && !root.EndsWith(MqttTopicFilterComparer.LevelSeparator) && !root.EndsWith('}')) { - if (!MatchesTopic(topic)) - { - throw new ArgumentException("the topic has to match this template", nameof(topic)); - } - - return parseParameterValuesInternal(topic); + root = root.Substring(0, root.LastIndexOf(MqttTopicFilterComparer.LevelSeparator) + 1); } - - /// - /// Extract the parameter values from the message topic corresponding to the template - /// parameters. The message topic has to match this topic template. - /// - /// - /// the message - /// - /// an enumeration of (parameter, index, value) - public IEnumerable<(string parameter, int index, string value)> ParseParameterValues(MqttApplicationMessage message) + + if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator)) { - return ParseParameterValues(message.Topic); + root += MqttTopicFilterComparer.SingleLevelWildcard; } - - /// - /// Try to set a parameter to a given value. If the parameter is not present, - /// this is returned. The value must not contain slashes. - /// - /// - /// a template parameter - /// - /// - /// a string - /// - /// - public MqttTopicTemplate TrySetParameter(string parameter, string value) + + return new MqttTopicTemplate(root); + } + + public override int GetHashCode() + { + return Template.GetHashCode(); + } + + /// + /// Test if this topic template matches a given topic. + /// + /// + /// a fully specified topic + /// + /// + /// true to match including the subtree (multi-level wildcard) + /// + /// true iff the topic matches the template's filter + /// + /// + /// + /// if the topic is invalid + /// + public bool MatchesTopic(string topic, bool subtree = false) + { + var comparison = MqttTopicFilterComparer.Compare(topic, subtree ? TopicTreeRootFilter : TopicFilter); + if (comparison == MqttTopicFilterCompareResult.FilterInvalid) { - if (parameter != null && _parameterSegments.Contains(parameter)) - { - return WithParameter(parameter, value); - } - - return this; + throw new InvalidOperationException("Invalid filter"); } - - /// - /// Replace the given parameter with a single-level wildcard (plus sign). - /// - /// - /// parameter name - /// - /// the topic template (without the parameter) - public MqttTopicTemplate WithoutParameter(string parameter) + + if (comparison == MqttTopicFilterCompareResult.TopicInvalid) { - return WithParameter(parameter, MqttTopicFilterComparer.SingleLevelWildcard.ToString()); + throw new ArgumentException("Invalid topic", nameof(topic)); } - - /// - /// Substitute a parameter with a given value, thus removing the parameter. If the parameter is not present, - /// the method trows. The value must not contain slashes. - /// - /// - /// a template parameter - /// - /// - /// a string - /// - /// - /// when the parameter is not present - /// - /// the topic template (without the parameter) - public MqttTopicTemplate WithParameter(string parameter, string value) + + return comparison == MqttTopicFilterCompareResult.IsMatch; + } + + /// + /// Extract the parameter values from a topic corresponding to the template + /// parameters. The topic has to match this template. + /// + /// + /// the topic + /// + /// an enumeration of (parameter, index, value) + public IEnumerable<(string parameter, int index, string value)> ParseParameterValues(string topic) + { + if (!MatchesTopic(topic)) { - if (value == null || string.IsNullOrEmpty(parameter) || !_parameterSegments.Contains(parameter) || value.Contains(MqttTopicFilterComparer.LevelSeparator) || - value.Contains(MqttTopicFilterComparer.MultiLevelWildcard)) - { - throw new ArgumentException("parameter must exist and value must not contain slashes."); - } - - var moustache = "{" + parameter + "}"; - return new MqttTopicTemplate(Template.Replace(moustache, value)); + throw new ArgumentException("the topic has to match this template", nameof(topic)); + } + + return parseParameterValuesInternal(topic); + } + + /// + /// Extract the parameter values from the message topic corresponding to the template + /// parameters. The message topic has to match this topic template. + /// + /// + /// the message + /// + /// an enumeration of (parameter, index, value) + public IEnumerable<(string parameter, int index, string value)> ParseParameterValues(MqttApplicationMessage message) + { + return ParseParameterValues(message.Topic); + } + + /// + /// Try to set a parameter to a given value. If the parameter is not present, + /// this is returned. The value must not contain slashes. + /// + /// + /// a template parameter + /// + /// + /// a string + /// + /// + public MqttTopicTemplate TrySetParameter(string parameter, string value) + { + if (parameter != null && _parameterSegments.Contains(parameter)) + { + return WithParameter(parameter, value); } - - /// - /// Reuse parameters as they are extracted using another topic template on this template - /// when the parameter name matches. Useful - /// for compatibility routing. - /// - /// - /// - /// - public MqttTopicTemplate WithParameterValuesFrom(IEnumerable<(string parameter, int index, string value)> parameters) + + return this; + } + + /// + /// Replace the given parameter with a single-level wildcard (plus sign). + /// + /// + /// parameter name + /// + /// the topic template (without the parameter) + public MqttTopicTemplate WithoutParameter(string parameter) + { + return WithParameter(parameter, MqttTopicFilterComparer.SingleLevelWildcard.ToString()); + } + + /// + /// Substitute a parameter with a given value, thus removing the parameter. If the parameter is not present, + /// the method trows. The value must not contain slashes. + /// + /// + /// a template parameter + /// + /// + /// a string + /// + /// + /// when the parameter is not present + /// + /// the topic template (without the parameter) + public MqttTopicTemplate WithParameter(string parameter, string value) + { + if (value == null || string.IsNullOrEmpty(parameter) || !_parameterSegments.Contains(parameter) || value.Contains(MqttTopicFilterComparer.LevelSeparator) || + value.Contains(MqttTopicFilterComparer.MultiLevelWildcard)) { - return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value)); + throw new ArgumentException("parameter must exist and value must not contain slashes."); } - - IEnumerable<(string parameter, int index, string value)> parseParameterValuesInternal(string topic) + + var moustache = "{" + parameter + "}"; + return new MqttTopicTemplate(Template.Replace(moustache, value)); + } + + /// + /// Reuse parameters as they are extracted using another topic template on this template + /// when the parameter name matches. Useful + /// for compatibility routing. + /// + /// + /// + /// + public MqttTopicTemplate WithParameterValuesFrom(IEnumerable<(string parameter, int index, string value)> parameters) + { + return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value)); + } + + IEnumerable<(string parameter, int index, string value)> parseParameterValuesInternal(string topic) + { + // because we have a match, we know the segment array is at least the template's length + var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator); + for (var i = 0; i < _parameterSegments.Length; i++) { - // because we have a match, we know the segment array is at least the template's length - var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator); - for (var i = 0; i < _parameterSegments.Length; i++) + var name = _parameterSegments[i]; + if (name != null) { - var name = _parameterSegments[i]; - if (name != null) - { - yield return (name, i, segments[i]); - } + yield return (name, i, segments[i]); } } } diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj index faecf048a..514ff8acc 100644 --- a/Source/MQTTnet.Server/MQTTnet.Server.csproj +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -30,12 +30,12 @@ MIT true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true enable disable + latest-Recommended diff --git a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj index 07bfc7444..f5b32a5a9 100644 --- a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj +++ b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj @@ -9,10 +9,10 @@ false true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj index 0caa6c33c..90a2e1d89 100644 --- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj +++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj @@ -7,10 +7,10 @@ false true 1591;NETSDK1138;NU1803;NU1901;NU1902 - true true all true + latest-Recommended diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index f37be30c1..1d3adc6de 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -43,6 +43,7 @@ true all true + latest-Recommended