mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-03-26 11:01:03 +01:00
Swagger support (#749)
* r * fix * sw * x * s * . * . * . * CreateTypeFromJObject * . * . * f * c * . * . * . * . * . * . * ok * , * . * . * . * . * n * pact * fix * schema * null * fluent * r * -p * . * . * refs * .
This commit is contained in:
@@ -1,30 +1,34 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace WireMock.Serialization
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal static class JsonSerializationConstants
|
||||
{
|
||||
internal static class JsonSerializationConstants
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new()
|
||||
{
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Include
|
||||
};
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Include
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new JsonSerializerSettings
|
||||
{
|
||||
DateParseHandling = DateParseHandling.None
|
||||
};
|
||||
public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new()
|
||||
{
|
||||
DateParseHandling = DateParseHandling.None
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsPact = new JsonSerializerSettings
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsPact = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
}
|
||||
NamingStrategy = new CamelCaseNamingStrategy()
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using JetBrains.Annotations;
|
||||
using SimMetrics.Net;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
@@ -22,12 +21,12 @@ namespace WireMock.Serialization
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
public IMatcher[] Map([CanBeNull] IEnumerable<MatcherModel> matchers)
|
||||
public IMatcher[] Map(IEnumerable<MatcherModel>? matchers)
|
||||
{
|
||||
return matchers?.Select(Map).Where(m => m != null).ToArray();
|
||||
}
|
||||
|
||||
public IMatcher Map([CanBeNull] MatcherModel matcher)
|
||||
public IMatcher? Map(MatcherModel? matcher)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
@@ -36,7 +35,7 @@ namespace WireMock.Serialization
|
||||
|
||||
string[] parts = matcher.Name.Split('.');
|
||||
string matcherName = parts[0];
|
||||
string matcherType = parts.Length > 1 ? parts[1] : null;
|
||||
string? matcherType = parts.Length > 1 ? parts[1] : null;
|
||||
var stringPatterns = ParseStringPatterns(matcher);
|
||||
var matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
|
||||
bool ignoreCase = matcher.IgnoreCase == true;
|
||||
@@ -69,16 +68,16 @@ namespace WireMock.Serialization
|
||||
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended);
|
||||
|
||||
case nameof(JsonMatcher):
|
||||
object valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonMatcher(matchBehaviour, valueForJsonMatcher, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
var valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case nameof(JsonPartialMatcher):
|
||||
object valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
var valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case nameof(JsonPartialWildcardMatcher):
|
||||
object valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
var valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case nameof(JsonPathMatcher):
|
||||
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
|
||||
@@ -114,12 +113,12 @@ namespace WireMock.Serialization
|
||||
}
|
||||
}
|
||||
|
||||
public MatcherModel[] Map([CanBeNull] IEnumerable<IMatcher> matchers)
|
||||
public MatcherModel[] Map(IEnumerable<IMatcher>? matchers)
|
||||
{
|
||||
return matchers?.Select(Map).Where(m => m != null).ToArray();
|
||||
}
|
||||
|
||||
public MatcherModel Map([CanBeNull] IMatcher matcher)
|
||||
public MatcherModel? Map(IMatcher? matcher)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
|
||||
150
src/WireMock.Net/Serialization/PactMapper.cs
Normal file
150
src/WireMock.Net/Serialization/PactMapper.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Pact.Models.V2;
|
||||
using WireMock.Server;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal static class PactMapper
|
||||
{
|
||||
private const string DefaultMethod = "GET";
|
||||
private const int DefaultStatusCode = 200;
|
||||
private const string DefaultConsumer = "Default Consumer";
|
||||
private const string DefaultProvider = "Default Provider";
|
||||
|
||||
public static (string FileName, byte[] Bytes) ToPact(WireMockServer server, string? filename = null)
|
||||
{
|
||||
var consumer = server.Consumer ?? DefaultConsumer;
|
||||
var provider = server.Provider ?? DefaultProvider;
|
||||
|
||||
filename ??= $"{consumer} - {provider}.json";
|
||||
|
||||
var pact = new Pact.Models.V2.Pact
|
||||
{
|
||||
Consumer = new Pacticipant { Name = consumer },
|
||||
Provider = new Pacticipant { Name = provider }
|
||||
};
|
||||
|
||||
foreach (var mapping in server.MappingModels.OrderBy(m => m.Guid))
|
||||
{
|
||||
var path = mapping.Request.GetPathAsString();
|
||||
if (path == null)
|
||||
{
|
||||
// Path is null (probably a Func<>), skip this.
|
||||
continue;
|
||||
}
|
||||
|
||||
var interaction = new Interaction
|
||||
{
|
||||
Description = mapping.Description,
|
||||
ProviderState = mapping.Title,
|
||||
Request = MapRequest(mapping.Request, path),
|
||||
Response = MapResponse(mapping.Response)
|
||||
};
|
||||
|
||||
pact.Interactions.Add(interaction);
|
||||
}
|
||||
|
||||
return (filename, JsonUtils.SerializeAsPactFile(pact));
|
||||
}
|
||||
|
||||
private static Request MapRequest(RequestModel request, string path)
|
||||
{
|
||||
return new Request
|
||||
{
|
||||
Method = request.Methods?.FirstOrDefault() ?? DefaultMethod,
|
||||
Path = path,
|
||||
Query = MapQueryParameters(request.Params),
|
||||
Headers = MapRequestHeaders(request.Headers),
|
||||
Body = MapBody(request.Body)
|
||||
};
|
||||
}
|
||||
|
||||
private static Response MapResponse(ResponseModel? response)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
return new Response();
|
||||
}
|
||||
|
||||
return new Response
|
||||
{
|
||||
Status = MapStatusCode(response.StatusCode),
|
||||
Headers = MapResponseHeaders(response.Headers),
|
||||
Body = response.BodyAsJson
|
||||
};
|
||||
}
|
||||
|
||||
private static int MapStatusCode(object? statusCode)
|
||||
{
|
||||
if (statusCode is string statusCodeAsString)
|
||||
{
|
||||
return int.TryParse(statusCodeAsString, out var statusCodeAsInt) ? statusCodeAsInt : DefaultStatusCode;
|
||||
}
|
||||
|
||||
if (statusCode != null)
|
||||
{
|
||||
// Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long.
|
||||
return Convert.ToInt32(statusCode);
|
||||
}
|
||||
|
||||
return DefaultStatusCode;
|
||||
}
|
||||
|
||||
private static string? MapQueryParameters(IList<ParamModel>? queryParameters)
|
||||
{
|
||||
if (queryParameters == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var values = queryParameters
|
||||
.Where(qp => qp.Matchers != null && qp.Matchers.Any() && qp.Matchers[0].Pattern is string)
|
||||
.Select(param => $"{Uri.EscapeDataString(param.Name)}={Uri.EscapeDataString((string)param.Matchers![0].Pattern!)}");
|
||||
|
||||
return string.Join("&", values);
|
||||
}
|
||||
|
||||
private static IDictionary<string, string>? MapRequestHeaders(IList<HeaderModel>? headers)
|
||||
{
|
||||
var validHeaders = headers?.Where(h => h.Matchers != null && h.Matchers.Any() && h.Matchers[0].Pattern is string);
|
||||
return validHeaders?.ToDictionary(x => x.Name, y => (string)y.Matchers![0].Pattern!);
|
||||
}
|
||||
|
||||
private static IDictionary<string, string>? MapResponseHeaders(IDictionary<string, object>? headers)
|
||||
{
|
||||
var validHeaders = headers?.Where(h => h.Value is string);
|
||||
return validHeaders?.ToDictionary(x => x.Key, y => (string)y.Value);
|
||||
}
|
||||
|
||||
private static object? MapBody(BodyModel? body)
|
||||
{
|
||||
if (body?.Matcher == null || body.Matchers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (body.Matcher is { Name: nameof(JsonMatcher) })
|
||||
{
|
||||
return body.Matcher.Pattern;
|
||||
}
|
||||
|
||||
var jsonMatcher = body.Matchers.FirstOrDefault(m => m.Name == nameof(JsonMatcher));
|
||||
return jsonMatcher?.Pattern;
|
||||
}
|
||||
|
||||
private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
|
||||
{
|
||||
if (matchers != null && matchers.Any() && matchers[0].Pattern is string patternAsString)
|
||||
{
|
||||
return patternAsString;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
325
src/WireMock.Net/Serialization/SwaggerMapper.cs
Normal file
325
src/WireMock.Net/Serialization/SwaggerMapper.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Extensions;
|
||||
using NSwag;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Constants;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Server;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal static class SwaggerMapper
|
||||
{
|
||||
private const string DefaultMethod = "GET";
|
||||
private const string Generator = "WireMock.Net";
|
||||
|
||||
private static readonly JsonSchema JsonSchemaString = new() { Type = JsonObjectType.String };
|
||||
|
||||
public static string ToSwagger(WireMockServer server)
|
||||
{
|
||||
var openApiDocument = new OpenApiDocument
|
||||
{
|
||||
Generator = Generator,
|
||||
Info = new OpenApiInfo
|
||||
{
|
||||
Title = $"{Generator} Mappings Swagger specification",
|
||||
Version = SystemUtils.Version
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var url in server.Urls)
|
||||
{
|
||||
openApiDocument.Servers.Add(new OpenApiServer
|
||||
{
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var mapping in server.MappingModels)
|
||||
{
|
||||
var path = mapping.Request.GetPathAsString();
|
||||
if (path == null)
|
||||
{
|
||||
// Path is null (probably a Func<>), skip this.
|
||||
continue;
|
||||
}
|
||||
|
||||
var operation = new OpenApiOperation();
|
||||
foreach (var openApiParameter in MapRequestQueryParameters(mapping.Request.Params))
|
||||
{
|
||||
operation.Parameters.Add(openApiParameter);
|
||||
}
|
||||
foreach (var openApiParameter in MapRequestHeaders(mapping.Request.Headers))
|
||||
{
|
||||
operation.Parameters.Add(openApiParameter);
|
||||
}
|
||||
foreach (var openApiParameter in MapRequestCookies(mapping.Request.Cookies))
|
||||
{
|
||||
operation.Parameters.Add(openApiParameter);
|
||||
}
|
||||
|
||||
operation.RequestBody = MapRequestBody(mapping.Request);
|
||||
|
||||
var response = MapResponse(mapping.Response);
|
||||
if (response != null)
|
||||
{
|
||||
operation.Responses.Add(mapping.Response.GetStatusCodeAsString(), response);
|
||||
}
|
||||
|
||||
var method = mapping.Request.Methods?.FirstOrDefault() ?? DefaultMethod;
|
||||
if (!openApiDocument.Paths.ContainsKey(path))
|
||||
{
|
||||
var openApiPathItem = new OpenApiPathItem
|
||||
{
|
||||
{ method, operation }
|
||||
};
|
||||
|
||||
openApiDocument.Paths.Add(path, openApiPathItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The combination of path+method uniquely identify an operation. Duplicates are not allowed.
|
||||
if (!openApiDocument.Paths[path].ContainsKey(method))
|
||||
{
|
||||
openApiDocument.Paths[path].Add(method, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return openApiDocument.ToJson(SchemaType.OpenApi3, Formatting.Indented);
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenApiParameter> MapRequestQueryParameters(IList<ParamModel>? queryParameters)
|
||||
{
|
||||
if (queryParameters == null)
|
||||
{
|
||||
return new List<OpenApiParameter>();
|
||||
}
|
||||
|
||||
return queryParameters
|
||||
.Where(x => x.Matchers != null && x.Matchers.Any())
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
Details = GetDetailsFromMatcher(x.Matchers![0])
|
||||
})
|
||||
.Select(x => new OpenApiParameter
|
||||
{
|
||||
Name = x.Name,
|
||||
Example = x.Details.Example,
|
||||
Description = x.Details.Description,
|
||||
Kind = OpenApiParameterKind.Query,
|
||||
Schema = x.Details.JsonSchemaRegex,
|
||||
IsRequired = !x.Details.Reject
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenApiParameter> MapRequestHeaders(IList<HeaderModel>? headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
return new List<OpenApiHeader>();
|
||||
}
|
||||
|
||||
return headers
|
||||
.Where(x => x.Matchers != null && x.Matchers.Any())
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
Details = GetDetailsFromMatcher(x.Matchers![0])
|
||||
})
|
||||
.Select(x => new OpenApiHeader
|
||||
{
|
||||
Name = x.Name,
|
||||
Example = x.Details.Example,
|
||||
Description = x.Details.Description,
|
||||
Kind = OpenApiParameterKind.Header,
|
||||
Schema = x.Details.JsonSchemaRegex,
|
||||
IsRequired = !x.Details.Reject
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenApiParameter> MapRequestCookies(IList<CookieModel>? cookies)
|
||||
{
|
||||
if (cookies == null)
|
||||
{
|
||||
return new List<OpenApiParameter>();
|
||||
}
|
||||
|
||||
return cookies
|
||||
.Where(x => x.Matchers != null && x.Matchers.Any())
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
Details = GetDetailsFromMatcher(x.Matchers![0])
|
||||
})
|
||||
.Select(x => new OpenApiParameter
|
||||
{
|
||||
Name = x.Name,
|
||||
Example = x.Details.Example,
|
||||
Description = x.Details.Description,
|
||||
Kind = OpenApiParameterKind.Cookie,
|
||||
Schema = x.Details.JsonSchemaRegex,
|
||||
IsRequired = !x.Details.Reject
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static (JsonSchema JsonSchemaRegex, string? Example, string? Description, bool Reject) GetDetailsFromMatcher(MatcherModel matcher)
|
||||
{
|
||||
var pattern = GetPatternAsStringFromMatcher(matcher);
|
||||
var reject = matcher.RejectOnMatch == true;
|
||||
var description = $"{matcher.Name} with RejectOnMatch = '{reject}' and Pattern = '{pattern}'";
|
||||
|
||||
return matcher.Name is nameof(RegexMatcher) ?
|
||||
(new JsonSchema { Type = JsonObjectType.String, Format = "regex", Pattern = pattern }, pattern, description, reject) :
|
||||
(JsonSchemaString, pattern, description, reject);
|
||||
}
|
||||
|
||||
private static OpenApiRequestBody? MapRequestBody(RequestModel request)
|
||||
{
|
||||
var body = MapRequestBody(request.Body);
|
||||
if (body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var openApiMediaType = new OpenApiMediaType
|
||||
{
|
||||
Schema = GetJsonSchema(body)
|
||||
};
|
||||
|
||||
var requestBodyPost = new OpenApiRequestBody();
|
||||
requestBodyPost.Content.Add(GetContentType(request), openApiMediaType);
|
||||
|
||||
return requestBodyPost;
|
||||
}
|
||||
|
||||
private static OpenApiResponse? MapResponse(ResponseModel response)
|
||||
{
|
||||
if (response.Body != null)
|
||||
{
|
||||
return new OpenApiResponse
|
||||
{
|
||||
Schema = new JsonSchemaProperty
|
||||
{
|
||||
Type = JsonObjectType.String,
|
||||
Example = response.Body
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.BodyAsBytes != null)
|
||||
{
|
||||
// https://stackoverflow.com/questions/62794949/how-to-define-byte-array-in-openapi-3-0
|
||||
return new OpenApiResponse
|
||||
{
|
||||
Schema = new JsonSchemaProperty
|
||||
{
|
||||
Type = JsonObjectType.Array,
|
||||
Items =
|
||||
{
|
||||
new JsonSchema
|
||||
{
|
||||
Type = JsonObjectType.String,
|
||||
Format = JsonFormatStrings.Byte
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.BodyAsJson == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OpenApiResponse
|
||||
{
|
||||
Schema = GetJsonSchema(response.BodyAsJson)
|
||||
};
|
||||
}
|
||||
|
||||
private static JsonSchema GetJsonSchema(object instance)
|
||||
{
|
||||
switch (instance)
|
||||
{
|
||||
case string instanceAsString:
|
||||
try
|
||||
{
|
||||
var value = JsonConvert.DeserializeObject(instanceAsString);
|
||||
return GetJsonSchema(value!);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return JsonSchemaString;
|
||||
}
|
||||
|
||||
default:
|
||||
return instance.ToJsonSchema();
|
||||
}
|
||||
}
|
||||
|
||||
private static object? MapRequestBody(BodyModel? body)
|
||||
{
|
||||
if (body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var matcher = GetMatcher(body.Matcher, body.Matchers);
|
||||
if (matcher is { Name: nameof(JsonMatcher) })
|
||||
{
|
||||
var pattern = GetPatternAsStringFromMatcher(matcher);
|
||||
if (JsonUtils.TryParseAsJObject(pattern, out var jObject))
|
||||
{
|
||||
return jObject;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetContentType(RequestModel request)
|
||||
{
|
||||
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");
|
||||
|
||||
return contentType == null ?
|
||||
WireMockConstants.ContentTypeJson :
|
||||
GetPatternAsStringFromMatchers(contentType.Matchers, WireMockConstants.ContentTypeJson);
|
||||
}
|
||||
|
||||
private static string GetPatternAsStringFromMatchers(IList<MatcherModel>? matchers, string defaultValue)
|
||||
{
|
||||
if (matchers == null || !matchers.Any())
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return GetPatternAsStringFromMatcher(matchers.First()) ?? defaultValue;
|
||||
}
|
||||
|
||||
private static string? GetPatternAsStringFromMatcher(MatcherModel matcher)
|
||||
{
|
||||
if (matcher.Pattern is string patternAsString)
|
||||
{
|
||||
return patternAsString;
|
||||
}
|
||||
|
||||
return matcher.Patterns?.FirstOrDefault() as string;
|
||||
}
|
||||
|
||||
private static MatcherModel? GetMatcher(MatcherModel? matcher, MatcherModel[]? matchers)
|
||||
{
|
||||
return matcher ?? matchers?.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user