From 4919e32264d62dc9384d7fd4b6c18e21eb7e9658 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 8 Feb 2017 19:55:45 +0100 Subject: [PATCH] support multiple patterns --- .../Program.cs | 2 +- .../Admin/Mappings/MatcherModel.cs | 8 ++ src/WireMock.Net/Matchers/ExactMatcher.cs | 21 ++--- src/WireMock.Net/Matchers/IMatcher.cs | 6 +- src/WireMock.Net/Matchers/JSONPathMatcher.cs | 22 ++--- src/WireMock.Net/Matchers/MatchScores.cs | 28 +++++- src/WireMock.Net/Matchers/RegexMatcher.cs | 32 ++++--- .../Matchers/SimMetricsMatcher.cs | 86 +++++++++---------- src/WireMock.Net/Matchers/WildcardMatcher.cs | 22 +++-- src/WireMock.Net/Matchers/XPathMatcher.cs | 22 ++--- .../Server/FluentMockServer.Admin.cs | 33 ++++--- 11 files changed, 171 insertions(+), 111 deletions(-) diff --git a/examples/WireMock.Net.ConsoleApplication/Program.cs b/examples/WireMock.Net.ConsoleApplication/Program.cs index b4f808cc..b4c83630 100644 --- a/examples/WireMock.Net.ConsoleApplication/Program.cs +++ b/examples/WireMock.Net.ConsoleApplication/Program.cs @@ -71,7 +71,7 @@ namespace WireMock.Net.ConsoleApplication .WithStatusCode(200)); server - .Given(Request.Create().WithPath("/partial").UsingPost().WithBody(new SimMetricsMatcher("cat"))) + .Given(Request.Create().WithPath("/partial").UsingPost().WithBody(new SimMetricsMatcher(new [] { "cat", "dog" }))) .RespondWith(Response.Create().WithStatusCode(200).WithBody("partial = 200")); // http://localhost:8080/any/any?start=1000&stop=1&stop=2 diff --git a/src/WireMock.Net/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net/Admin/Mappings/MatcherModel.cs index 3bceaf13..cb71a454 100644 --- a/src/WireMock.Net/Admin/Mappings/MatcherModel.cs +++ b/src/WireMock.Net/Admin/Mappings/MatcherModel.cs @@ -21,6 +21,14 @@ /// public string Pattern { get; set; } + /// + /// Gets or sets the patterns. + /// + /// + /// The patterns. + /// + public string[] Patterns { get; set; } + /// /// Gets or sets the ignore case. /// diff --git a/src/WireMock.Net/Matchers/ExactMatcher.cs b/src/WireMock.Net/Matchers/ExactMatcher.cs index 789f6225..fb5354e4 100644 --- a/src/WireMock.Net/Matchers/ExactMatcher.cs +++ b/src/WireMock.Net/Matchers/ExactMatcher.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Linq; +using JetBrains.Annotations; using WireMock.Validation; namespace WireMock.Matchers @@ -9,17 +10,17 @@ namespace WireMock.Matchers /// public class ExactMatcher : IMatcher { - private readonly string _value; + private readonly string[] _values; /// /// Initializes a new instance of the class. /// - /// The value. - public ExactMatcher([NotNull] string value) + /// The values. + public ExactMatcher([NotNull] params string[] values) { - Check.NotNull(value, nameof(value)); + Check.NotNull(values, nameof(values)); - _value = value; + _values = values; } /// @@ -29,16 +30,16 @@ namespace WireMock.Matchers /// A value between 0.0 - 1.0 of the similarity. public double IsMatch(string input) { - return MatchScores.ToScore(_value.Equals(input)); + return MatchScores.ToScore(_values.Select(value => value.Equals(input))); } /// /// Gets the value. /// - /// Pattern - public string GetPattern() + /// Patterns + public string[] GetPatterns() { - return _value; + return _values; } /// diff --git a/src/WireMock.Net/Matchers/IMatcher.cs b/src/WireMock.Net/Matchers/IMatcher.cs index 14ea4044..4bcd2cff 100644 --- a/src/WireMock.Net/Matchers/IMatcher.cs +++ b/src/WireMock.Net/Matchers/IMatcher.cs @@ -13,10 +13,10 @@ double IsMatch(string input); /// - /// Gets the pattern. + /// Gets the patterns. /// - /// Pattern - string GetPattern(); + /// Patterns + string[] GetPatterns(); /// /// Gets the name. diff --git a/src/WireMock.Net/Matchers/JSONPathMatcher.cs b/src/WireMock.Net/Matchers/JSONPathMatcher.cs index 09292540..c92b0189 100644 --- a/src/WireMock.Net/Matchers/JSONPathMatcher.cs +++ b/src/WireMock.Net/Matchers/JSONPathMatcher.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json.Linq; using WireMock.Validation; @@ -11,17 +12,17 @@ namespace WireMock.Matchers /// public class JsonPathMatcher : IMatcher { - private readonly string _pattern; + private readonly string[] _patterns; /// /// Initializes a new instance of the class. /// - /// The pattern. - public JsonPathMatcher([NotNull] string pattern) + /// The patterns. + public JsonPathMatcher([NotNull] params string[] patterns) { - Check.NotNull(pattern, nameof(pattern)); + Check.NotNull(patterns, nameof(patterns)); - _pattern = pattern; + _patterns = patterns; } /// @@ -37,9 +38,8 @@ namespace WireMock.Matchers try { JObject o = JObject.Parse(input); - JToken token = o.SelectToken(_pattern); - - return MatchScores.ToScore(token != null); + + return MatchScores.ToScore(_patterns.Select(p => o.SelectToken(p) != null)); } catch (Exception) { @@ -48,12 +48,12 @@ namespace WireMock.Matchers } /// - /// Gets the pattern. + /// Gets the patterns. /// /// Pattern - public string GetPattern() + public string[] GetPatterns() { - return _pattern; + return _patterns; } /// diff --git a/src/WireMock.Net/Matchers/MatchScores.cs b/src/WireMock.Net/Matchers/MatchScores.cs index 3410ca2b..8aabdfc5 100644 --- a/src/WireMock.Net/Matchers/MatchScores.cs +++ b/src/WireMock.Net/Matchers/MatchScores.cs @@ -1,4 +1,8 @@ -namespace WireMock.Matchers +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WireMock.Matchers { /// /// MatchScores @@ -29,5 +33,27 @@ { return value ? Perfect : Mismatch; } + + /// + /// Calculates the score from multiple funcs. + /// + /// The values. + /// score + public static double ToScore(IEnumerable values) + { + var list = values.Select(ToScore).ToList(); + return list.Sum() / list.Count; + } + + /// + /// Calculates the score from multiple funcs. + /// + /// The values. + /// score + public static double ToScore(IEnumerable values) + { + var list = values.ToList(); + return list.Sum() / list.Count; + } } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/RegexMatcher.cs b/src/WireMock.Net/Matchers/RegexMatcher.cs index 73b03b6e..918f0e9a 100644 --- a/src/WireMock.Net/Matchers/RegexMatcher.cs +++ b/src/WireMock.Net/Matchers/RegexMatcher.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text.RegularExpressions; using JetBrains.Annotations; using WireMock.Validation; @@ -11,25 +12,34 @@ namespace WireMock.Matchers /// public class RegexMatcher : IMatcher { - private readonly string _pattern; - private readonly Regex _expression; + private readonly string[] _patterns; + private readonly Regex[] _expressions; /// /// Initializes a new instance of the class. /// /// The pattern. /// IgnoreCase - public RegexMatcher([NotNull, RegexPattern] string pattern, bool ignoreCase = false) + public RegexMatcher([NotNull, RegexPattern] string pattern, bool ignoreCase = false) : this(new [] { pattern }, ignoreCase ) { - Check.NotNull(pattern, nameof(pattern)); + } - _pattern = pattern; + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// IgnoreCase + public RegexMatcher([NotNull, RegexPattern] string[] patterns, bool ignoreCase = false) + { + Check.NotNull(patterns, nameof(patterns)); + + _patterns = patterns; RegexOptions options = RegexOptions.Compiled; if (ignoreCase) options |= RegexOptions.IgnoreCase; - - _expression = new Regex(_pattern, options); + + _expressions = patterns.Select(p => new Regex(p, options)).ToArray(); } /// @@ -44,7 +54,7 @@ namespace WireMock.Matchers try { - return MatchScores.ToScore(_expression.IsMatch(input)); + return MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input))); } catch (Exception) { @@ -53,12 +63,12 @@ namespace WireMock.Matchers } /// - /// Gets the pattern. + /// Gets the patterns. /// /// Pattern - public virtual string GetPattern() + public virtual string[] GetPatterns() { - return _pattern; + return _patterns; } /// diff --git a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs b/src/WireMock.Net/Matchers/SimMetricsMatcher.cs index c4c98ee0..db49d248 100644 --- a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs +++ b/src/WireMock.Net/Matchers/SimMetricsMatcher.cs @@ -1,5 +1,7 @@ -using JetBrains.Annotations; +using System.Linq; +using JetBrains.Annotations; using SimMetrics.Net; +using SimMetrics.Net.API; using SimMetrics.Net.Metric; using WireMock.Validation; @@ -11,7 +13,7 @@ namespace WireMock.Matchers /// public class SimMetricsMatcher : IMatcher { - private readonly string _pattern; + private readonly string[] _patterns; private readonly SimMetricType _simMetricType; /// @@ -19,11 +21,20 @@ namespace WireMock.Matchers /// /// The pattern. /// The SimMetric Type - public SimMetricsMatcher([NotNull] string pattern, SimMetricType simMetricType = SimMetricType.Levenstein) + public SimMetricsMatcher([NotNull] string pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new [] { pattern }, simMetricType) { - Check.NotNull(pattern, nameof(pattern)); + } - _pattern = pattern; + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// The SimMetric Type + public SimMetricsMatcher([NotNull] string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) + { + Check.NotEmpty(patterns, nameof(patterns)); + + _patterns = patterns; _simMetricType = simMetricType; } @@ -33,63 +44,52 @@ namespace WireMock.Matchers /// The input string /// A value between 0.0 - 1.0 of the similarity. public double IsMatch(string input) + { + IStringMetric m = GetStringMetricType(); + + return MatchScores.ToScore(_patterns.Select(p => m.GetSimilarity(p, input))); + } + + private IStringMetric GetStringMetricType() { switch (_simMetricType) { case SimMetricType.BlockDistance: - var sim2 = new BlockDistance(); - return sim2.GetSimilarity(_pattern, input); + return new BlockDistance(); case SimMetricType.ChapmanLengthDeviation: - var sim3 = new ChapmanLengthDeviation(); - return sim3.GetSimilarity(_pattern, input); + return new ChapmanLengthDeviation(); case SimMetricType.CosineSimilarity: - var sim4 = new CosineSimilarity(); - return sim4.GetSimilarity(_pattern, input); + return new CosineSimilarity(); case SimMetricType.DiceSimilarity: - var sim5 = new DiceSimilarity(); - return sim5.GetSimilarity(_pattern, input); + return new DiceSimilarity(); case SimMetricType.EuclideanDistance: - var sim6 = new EuclideanDistance(); - return sim6.GetSimilarity(_pattern, input); + return new EuclideanDistance(); case SimMetricType.JaccardSimilarity: - var sim7 = new JaccardSimilarity(); - return sim7.GetSimilarity(_pattern, input); + return new JaccardSimilarity(); case SimMetricType.Jaro: - var sim8 = new Jaro(); - return sim8.GetSimilarity(_pattern, input); + return new Jaro(); case SimMetricType.JaroWinkler: - var sim9 = new JaroWinkler(); - return sim9.GetSimilarity(_pattern, input); + return new JaroWinkler(); case SimMetricType.MatchingCoefficient: - var sim10 = new MatchingCoefficient(); - return sim10.GetSimilarity(_pattern, input); + return new MatchingCoefficient(); case SimMetricType.MongeElkan: - var sim11 = new MongeElkan(); - return sim11.GetSimilarity(_pattern, input); + return new MongeElkan(); case SimMetricType.NeedlemanWunch: - var sim12 = new NeedlemanWunch(); - return sim12.GetSimilarity(_pattern, input); + return new NeedlemanWunch(); case SimMetricType.OverlapCoefficient: - var sim13 = new OverlapCoefficient(); - return sim13.GetSimilarity(_pattern, input); + return new OverlapCoefficient(); case SimMetricType.QGramsDistance: - var sim14 = new QGramsDistance(); - return sim14.GetSimilarity(_pattern, input); + return new QGramsDistance(); case SimMetricType.SmithWaterman: - var sim15 = new SmithWaterman(); - return sim15.GetSimilarity(_pattern, input); + return new SmithWaterman(); case SimMetricType.SmithWatermanGotoh: - var sim16 = new SmithWatermanGotoh(); - return sim16.GetSimilarity(_pattern, input); + return new SmithWatermanGotoh(); case SimMetricType.SmithWatermanGotohWindowedAffine: - var sim17 = new SmithWatermanGotohWindowedAffine(); - return sim17.GetSimilarity(_pattern, input); + return new SmithWatermanGotohWindowedAffine(); case SimMetricType.ChapmanMeanLength: - var sim18 = new ChapmanMeanLength(); - return sim18.GetSimilarity(_pattern, input); + return new ChapmanMeanLength(); default: - var sim1 = new Levenstein(); - return sim1.GetSimilarity(_pattern, input); + return new Levenstein(); } } @@ -97,9 +97,9 @@ namespace WireMock.Matchers /// Gets the pattern. /// /// Pattern - public string GetPattern() + public string[] GetPatterns() { - return _pattern; + return _patterns; } /// diff --git a/src/WireMock.Net/Matchers/WildcardMatcher.cs b/src/WireMock.Net/Matchers/WildcardMatcher.cs index 74690685..1ecbe647 100644 --- a/src/WireMock.Net/Matchers/WildcardMatcher.cs +++ b/src/WireMock.Net/Matchers/WildcardMatcher.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.Linq; +using System.Text.RegularExpressions; using JetBrains.Annotations; namespace WireMock.Matchers @@ -9,25 +10,34 @@ namespace WireMock.Matchers /// public class WildcardMatcher : RegexMatcher { - private readonly string _pattern; + private readonly string[] _patterns; /// /// Initializes a new instance of the class. /// /// The pattern. /// IgnoreCase - public WildcardMatcher([NotNull] string pattern, bool ignoreCase = false) : base("^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$", ignoreCase) + public WildcardMatcher([NotNull] string pattern, bool ignoreCase = false) : this(new [] { pattern }, ignoreCase) { - _pattern = pattern; + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// IgnoreCase + public WildcardMatcher([NotNull] string[] patterns, bool ignoreCase = false) : base(patterns.Select(pattern => "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$").ToArray(), ignoreCase) + { + _patterns = patterns; } /// /// Gets the pattern. /// /// Pattern - public override string GetPattern() + public override string[] GetPatterns() { - return _pattern; + return _patterns; } /// diff --git a/src/WireMock.Net/Matchers/XPathMatcher.cs b/src/WireMock.Net/Matchers/XPathMatcher.cs index 1e5af9ed..68011421 100644 --- a/src/WireMock.Net/Matchers/XPathMatcher.cs +++ b/src/WireMock.Net/Matchers/XPathMatcher.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Xml; using JetBrains.Annotations; using WireMock.Validation; @@ -12,17 +13,17 @@ namespace WireMock.Matchers /// public class XPathMatcher : IMatcher { - private readonly string _pattern; + private readonly string[] _patterns; /// /// Initializes a new instance of the class. /// - /// The pattern. - public XPathMatcher([NotNull] string pattern) + /// The patterns. + public XPathMatcher([NotNull] params string[] patterns) { - Check.NotNull(pattern, nameof(pattern)); + Check.NotNull(patterns, nameof(patterns)); - _pattern = pattern; + _patterns = patterns; } /// @@ -38,9 +39,8 @@ namespace WireMock.Matchers try { var nav = new XmlDocument { InnerXml = input }.CreateNavigator(); - object result = nav.XPath2Evaluate($"boolean({_pattern})"); - return MatchScores.ToScore(true.Equals(result)); + return MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p})")))); } catch (Exception) { @@ -49,12 +49,12 @@ namespace WireMock.Matchers } /// - /// Gets the pattern. + /// Gets the patterns. /// - /// Pattern - public string GetPattern() + /// Patterns + public string[] GetPatterns() { - return _pattern; + return _patterns; } /// diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index 55c2094f..3d0ecd5a 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -334,40 +334,40 @@ namespace WireMock.Server Priority = mapping.Priority, Request = new RequestModel { - Path = pathMatchers != null ? new PathModel + Path = pathMatchers != null && pathMatchers.Any() ? new PathModel { Matchers = Map(pathMatchers.Where(m => m.Matchers != null).SelectMany(m => m.Matchers)), Funcs = Map(pathMatchers.Where(m => m.Funcs != null).SelectMany(m => m.Funcs)) } : null, - Url = urlMatchers != null ? new UrlModel + Url = urlMatchers != null && urlMatchers.Any() ? new UrlModel { Matchers = Map(urlMatchers.Where(m => m.Matchers != null).SelectMany(m => m.Matchers)), Funcs = Map(urlMatchers.Where(m => m.Funcs != null).SelectMany(m => m.Funcs)) } : null, - Methods = methodMatcher != null ? methodMatcher.Methods : new[] { "any" }, + Methods = methodMatcher?.Methods, - Headers = headerMatchers?.Select(hm => new HeaderModel + Headers = headerMatchers != null && headerMatchers.Any() ? headerMatchers?.Select(hm => new HeaderModel { Name = hm.Name, Matchers = Map(hm.Matchers), Funcs = Map(hm.Funcs) - }).ToList(), + }).ToList() : null, - Cookies = cookieMatchers?.Select(cm => new CookieModel + Cookies = cookieMatchers != null && cookieMatchers.Any() ? cookieMatchers?.Select(cm => new CookieModel { Name = cm.Name, Matchers = Map(cm.Matchers), Funcs = Map(cm.Funcs) - }).ToList(), + }).ToList() : null, - Params = paramsMatchers?.Select(pm => new ParamModel + Params = paramsMatchers != null && paramsMatchers.Any() ? paramsMatchers?.Select(pm => new ParamModel { Name = pm.Key, Values = pm.Values?.ToList(), Funcs = Map(pm.Funcs) - }).ToList(), + }).ToList() : null, Body = new BodyModel { @@ -400,10 +400,13 @@ namespace WireMock.Server if (matcher == null) return null; + var patterns = matcher.GetPatterns(); + return new MatcherModel { Name = matcher.GetName(), - Pattern = matcher.GetPattern() + Pattern = patterns.Length == 1 ? patterns.First() : null, + Patterns = patterns.Length > 1 ? patterns : null }; } @@ -429,22 +432,24 @@ namespace WireMock.Server string matcherName = parts[0]; string matcherType = parts.Length > 1 ? parts[1] : null; + string[] patterns = matcher.Patterns ?? new[] { matcher.Pattern }; + switch (matcherName) { case "ExactMatcher": - return new ExactMatcher(matcher.Pattern); + return new ExactMatcher(patterns); case "RegexMatcher": - return new RegexMatcher(matcher.Pattern); + return new RegexMatcher(patterns); case "JsonPathMatcher": - return new JsonPathMatcher(matcher.Pattern); + return new JsonPathMatcher(patterns); case "XPathMatcher": return new XPathMatcher(matcher.Pattern); case "WildcardMatcher": - return new WildcardMatcher(matcher.Pattern, matcher.IgnoreCase == true); + return new WildcardMatcher(patterns, matcher.IgnoreCase == true); case "SimMetricsMatcher": SimMetricType type = SimMetricType.Levenstein;