diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln
index 8fabcff0..7bc052d0 100644
--- a/WireMock.Net Solution.sln
+++ b/WireMock.Net Solution.sln
@@ -112,6 +112,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET8",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{7FC0B409-2682-40EE-B3B9-3930D6769D01}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.GrpcClient", "examples\WireMock.Net.Console.GrpcClient\WireMock.Net.Console.GrpcClient.csproj", "{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -262,6 +264,10 @@ Global
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -305,6 +311,7 @@ Global
{941229D6-191B-4B5E-AC81-0905EBF4F19D} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{7FC0B409-2682-40EE-B3B9-3930D6769D01} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
+ {B1580A38-84E7-44BE-8FE7-3EE5031D74A1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
diff --git a/WireMock.Net Solution.sln.DotSettings b/WireMock.Net Solution.sln.DotSettings
index a0261229..ace99049 100644
--- a/WireMock.Net Solution.sln.DotSettings
+++ b/WireMock.Net Solution.sln.DotSettings
@@ -25,12 +25,14 @@
XUA
True
True
+ True
True
True
True
True
True
True
+ True
True
True
True
diff --git a/examples/WireMock.Net.Console.GrpcClient/Program.cs b/examples/WireMock.Net.Console.GrpcClient/Program.cs
new file mode 100644
index 00000000..37b3df49
--- /dev/null
+++ b/examples/WireMock.Net.Console.GrpcClient/Program.cs
@@ -0,0 +1,21 @@
+using Greet;
+using Grpc.Net.Client;
+
+namespace WireMock.Net.Console.GrpcClient;
+
+internal class Program
+{
+ static async Task Main(string[] args)
+ {
+ var channel = GrpcChannel.ForAddress("http://localhost:9093/grpc3", new GrpcChannelOptions
+ {
+ Credentials = Grpc.Core.ChannelCredentials.Insecure
+ });
+
+ var client = new Greeter.GreeterClient(channel);
+
+ var reply = await client.SayHelloAsync(new HelloRequest { Name = "stef" });
+
+ System.Console.WriteLine("Greeting: " + reply.Message);
+ }
+}
\ No newline at end of file
diff --git a/examples/WireMock.Net.Console.GrpcClient/WireMock.Net.Console.GrpcClient.csproj b/examples/WireMock.Net.Console.GrpcClient/WireMock.Net.Console.GrpcClient.csproj
new file mode 100644
index 00000000..c87f3b10
--- /dev/null
+++ b/examples/WireMock.Net.Console.GrpcClient/WireMock.Net.Console.GrpcClient.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/examples/WireMock.Net.Console.GrpcClient/greet.proto b/examples/WireMock.Net.Console.GrpcClient/greet.proto
new file mode 100644
index 00000000..6f9e10fa
--- /dev/null
+++ b/examples/WireMock.Net.Console.GrpcClient/greet.proto
@@ -0,0 +1,33 @@
+// Copyright 2019 The gRPC Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package greet;
+
+// The greeting service definition.
+service Greeter {
+ // Sends a greeting
+ rpc SayHello (HelloRequest) returns (HelloReply);
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+ string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+ string message = 1;
+}
\ No newline at end of file
diff --git a/examples/WireMock.Net.Console.NET8/WireMock.Net.Console.NET8.csproj b/examples/WireMock.Net.Console.NET8/WireMock.Net.Console.NET8.csproj
index 071afcc2..ff5b2a2f 100644
--- a/examples/WireMock.Net.Console.NET8/WireMock.Net.Console.NET8.csproj
+++ b/examples/WireMock.Net.Console.NET8/WireMock.Net.Console.NET8.csproj
@@ -3,7 +3,7 @@
Exe
net8.0
- $(DefineConstants);GRAPHQL;MIMEKIT
+ $(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF
diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
index f5949bc6..14fa5e1a 100644
--- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
+++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
@@ -42,6 +42,24 @@ namespace WireMock.Net.ConsoleApplication
public static class MainApp
{
+ private const string ProtoDefinition = @"
+syntax = ""proto3"";
+
+package greet;
+
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+}
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloReply {
+ string message = 1;
+}
+";
+
private const string TestSchema = @"
scalar DateTime
scalar MyCustomScalar
@@ -115,17 +133,14 @@ namespace WireMock.Net.ConsoleApplication
.WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())])
);
- var httpClient = server.CreateClient();
- //server.Stop();
-
- var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
+ using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps,
Port = 12399
});
httpAndHttpsWithPort.Stop();
- var httpAndHttpsFree = WireMockServer.Start(new WireMockServerSettings
+ using var httpAndHttpsFree = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps
});
@@ -134,11 +149,14 @@ namespace WireMock.Net.ConsoleApplication
string url1 = "http://localhost:9091/";
string url2 = "http://localhost:9092/";
string url3 = "https://localhost:9443/";
+ string urlGrpc = "grpc://localhost:9093/";
+ string urlGrpcSSL = "grpcs://localhost:9094/";
server = WireMockServer.Start(new WireMockServerSettings
{
+ // CorsPolicyOptions = CorsPolicyOptions.AllowAll,
AllowCSharpCodeMatcher = true,
- Urls = new[] { url1, url2, url3 },
+ Urls = new[] { url1, url2, url3, urlGrpc, urlGrpcSSL },
StartAdminInterface = true,
ReadStaticMappings = true,
SaveUnmatchedRequests = true,
@@ -171,17 +189,91 @@ namespace WireMock.Net.ConsoleApplication
//server.SetAzureADAuthentication("6c2a4722-f3b9-4970-b8fc-fac41e29stef", "8587fde1-7824-42c7-8592-faf92b04stef");
// server.AllowPartialMapping();
+
+#if PROTOBUF
+ var protoBufJsonMatcher = new JsonPartialWildcardMatcher(new { name = "*" });
+ server
+ .Given(Request.Create()
+ .UsingPost()
+ .WithHttpVersion("2")
+ .WithPath("/grpc/greet.Greeter/SayHello")
+ .WithBodyAsProtoBuf(ProtoDefinition, "greet.HelloRequest", protoBufJsonMatcher)
+ )
+ .RespondWith(Response.Create()
+ .WithHeader("Content-Type", "application/grpc")
+ .WithBodyAsProtoBuf(ProtoDefinition, "greet.HelloReply",
+ new
+ {
+ message = "hello {{request.BodyAsJson.name}}"
+ }
+ )
+ .WithTrailingHeader("grpc-status", "0")
+ .WithTransformer()
+ );
+
+ server
+ .Given(Request.Create()
+ .UsingPost()
+ .WithHttpVersion("2")
+ .WithPath("/grpc2/greet.Greeter/SayHello")
+ .WithBodyAsProtoBuf("greet.HelloRequest", protoBufJsonMatcher)
+ )
+ .WithProtoDefinition(ProtoDefinition)
+ .RespondWith(Response.Create()
+ .WithHeader("Content-Type", "application/grpc")
+ .WithBodyAsProtoBuf("greet.HelloReply",
+ new
+ {
+ message = "hello {{request.BodyAsJson.name}}"
+ }
+ )
+ .WithTrailingHeader("grpc-status", "0")
+ .WithTransformer()
+ );
+
+ server
+ .AddProtoDefinition("my-greeter", ProtoDefinition)
+ .Given(Request.Create()
+ .UsingPost()
+ .WithPath("/grpc3/greet.Greeter/SayHello")
+ .WithBodyAsProtoBuf("greet.HelloRequest", protoBufJsonMatcher)
+ )
+ .WithProtoDefinition("my-greeter")
+ .RespondWith(Response.Create()
+ .WithHeader("Content-Type", "application/grpc")
+ .WithBodyAsProtoBuf("greet.HelloReply",
+ new
+ {
+ message = "hello {{request.BodyAsJson.name}}"
+ }
+ )
+ .WithTrailingHeader("grpc-status", "0")
+ .WithTransformer()
+ );
+#endif
+
#if GRAPHQL
var customScalars = new Dictionary { { "MyCustomScalar", typeof(int) } };
server
.Given(Request.Create()
.WithPath("/graphql")
.UsingPost()
- .WithGraphQLSchema(TestSchema, customScalars)
+ .WithBodyAsGraphQL(TestSchema, customScalars)
)
.RespondWith(Response.Create()
.WithBody("GraphQL is ok")
);
+
+ //server
+ // .AddGraphQLSchema("my-graphql", TestSchema, customScalars)
+ // .Given(Request.Create()
+ // .WithPath("/graphql2")
+ // .UsingPost()
+ // )
+ // .WithGraphQLSchema("my-graphql")
+ // .RespondWith(Response.Create()
+ // .WithBody("GraphQL is ok")
+ // );
#endif
#if MIMEKIT
@@ -336,8 +428,8 @@ namespace WireMock.Net.ConsoleApplication
Url = "http://localhost:9999",
ReplaceSettings = new ProxyUrlReplaceSettings
{
- OldValue = "old",
- NewValue = "new"
+ OldValue = "old",
+ NewValue = "new"
}
})
);
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs
index 9f6061a2..e570065c 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs
@@ -98,4 +98,9 @@ public class MappingModel
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
///
public double? Probability { get; set; }
+
+ ///
+ /// The Grpc ProtoDefinition which is used for this mapping (request and response). [Optional]
+ ///
+ public string? ProtoDefinition { get; set; }
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
index 994b8d05..6e133ab4 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs
@@ -70,13 +70,22 @@ public class MatcherModel
/// ContentTransferEncoding Matcher (base64)
///
public MatcherModel? ContentTransferEncodingMatcher { get; set; }
+ #endregion
+ #region MimePartMatcher + ProtoBufMatcher
///
/// Content Matcher
///
public MatcherModel? ContentMatcher { get; set; }
#endregion
+ #region ProtoBufMatcher
+ ///
+ /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
+ ///
+ public string? ProtoBufMessageType { get; set; }
+ #endregion
+
#region XPathMatcher
///
/// Array of namespace prefix and uri. (optional)
@@ -86,7 +95,7 @@ public class MatcherModel
#region GraphQLMatcher
///
- /// Mapping of custom GraphQL Scalar name to ClrType. (optional)
+ /// Mapping of custom GraphQL Scalar name to ClrType. (optional)
///
public IDictionary? CustomScalars { get; set; }
#endregion
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs
index b3b25cd8..8953c732 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs
@@ -28,6 +28,11 @@ public class RequestModel
///
public string[]? Methods { get; set; }
+ ///
+ /// The HTTP Version
+ ///
+ public string? HttpVersion { get; set; }
+
///
/// Reject on match for Methods.
///
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs
index 0dacb64e..1857cea9 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs
@@ -35,7 +35,7 @@ public class ResponseModel
public bool? BodyAsJsonIndented { get; set; }
///
- /// Gets or sets the body (as bytearray).
+ /// Gets or sets the body (as byte array).
///
public byte[]? BodyAsBytes { get; set; }
@@ -84,6 +84,11 @@ public class ResponseModel
///
public string? HeadersRaw { get; set; }
+ ///
+ /// Gets or sets the Trailing Headers.
+ ///
+ public IDictionary? TrailingHeaders { get; set; }
+
///
/// Gets or sets the delay in milliseconds.
///
@@ -123,4 +128,16 @@ public class ResponseModel
/// Gets or sets the WebProxy settings.
///
public WebProxyModel? WebProxy { get; set; }
+
+ #region ProtoBuf
+ ///
+ /// Gets or sets the proto definition.
+ ///
+ public string? ProtoDefinition { get; set; }
+
+ ///
+ /// Gets or sets the full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
+ ///
+ public string? ProtoBufMessageType { get; set; }
+ #endregion
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestModel.cs
index 16d0c415..f1e7f84a 100644
--- a/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestModel.cs
@@ -55,6 +55,11 @@ public class LogRequestModel
///
public string Method { get; set; }
+ ///
+ /// The HTTP Version.
+ ///
+ public string HttpVersion { get; set; } = null!;
+
///
/// The Headers.
///
diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs
index 9f02d969..a82f7ae4 100644
--- a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
using System.Text.RegularExpressions;
+using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Types;
@@ -114,6 +116,11 @@ public class SettingsModel
///
public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
+ ///
+ /// A list of Grpc ProtoDefinitions which can be used.
+ ///
+ public Dictionary? ProtoDefinitions { get; set; }
+
#if NETSTANDARD1_3_OR_GREATER || NET461
///
/// Server client certificate mode
diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs
index c94d95a6..42d9c99f 100644
--- a/src/WireMock.Net.Abstractions/IRequestMessage.cs
+++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs
@@ -63,6 +63,11 @@ public interface IRequestMessage
///
string Method { get; }
+ ///
+ /// Gets the HTTP Version.
+ ///
+ string HttpVersion { get; }
+
///
/// Gets the headers.
///
@@ -94,23 +99,27 @@ public interface IRequestMessage
IBodyData? BodyData { get; }
///
- /// The original body as string. Convenience getter for Handlebars and WireMockAssertions.
+ /// The original body as string.
+ /// Convenience getter for Handlebars and WireMockAssertions.
///
string? Body { get; }
///
- /// The body (as JSON object). Convenience getter for Handlebars and WireMockAssertions.
+ /// The body (as JSON object).
+ /// Convenience getter for Handlebars and WireMockAssertions.
///
object? BodyAsJson { get; }
///
- /// The body (as bytearray). Convenience getter for Handlebars and WireMockAssertions.
+ /// The body (as bytearray).
+ /// Convenience getter for Handlebars and WireMockAssertions.
///
byte[]? BodyAsBytes { get; }
#if MIMEKIT
///
- /// The original body as MimeMessage. Convenience getter for Handlebars and WireMockAssertions.
+ /// The original body as MimeMessage.
+ /// Convenience getter for Handlebars and WireMockAssertions.
///
object? BodyAsMimeMessage { get; }
#endif
diff --git a/src/WireMock.Net.Abstractions/IResponseMessage.cs b/src/WireMock.Net.Abstractions/IResponseMessage.cs
index b1499b1a..1acdc3a8 100644
--- a/src/WireMock.Net.Abstractions/IResponseMessage.cs
+++ b/src/WireMock.Net.Abstractions/IResponseMessage.cs
@@ -40,11 +40,16 @@ public interface IResponseMessage
///
IDictionary>? Headers { get; }
+ ///
+ /// Gets the trailing headers.
+ ///
+ IDictionary>? TrailingHeaders { get; }
+
///
/// Gets or sets the status code.
///
object? StatusCode { get; }
-
+
///
/// Adds the header.
///
@@ -53,9 +58,23 @@ public interface IResponseMessage
void AddHeader(string name, string value);
///
- /// Adds the header.
+ /// Adds the trailing header.
///
/// The name.
/// The values.
void AddHeader(string name, params string[] values);
+
+ ///
+ /// Adds the trailing header.
+ ///
+ /// The name.
+ /// The value.
+ void AddTrailingHeader(string name, string value);
+
+ ///
+ /// Adds the header.
+ ///
+ /// The name.
+ /// The values.
+ void AddTrailingHeader(string name, params string[] values);
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Models/IBodyData.cs b/src/WireMock.Net.Abstractions/Models/IBodyData.cs
index 557be21c..e74e6193 100644
--- a/src/WireMock.Net.Abstractions/Models/IBodyData.cs
+++ b/src/WireMock.Net.Abstractions/Models/IBodyData.cs
@@ -1,7 +1,10 @@
+using System;
using System.Collections.Generic;
using System.Text;
+using WireMock.Models;
using WireMock.Types;
+// ReSharper disable once CheckNamespace
namespace WireMock.Util;
///
@@ -10,7 +13,7 @@ namespace WireMock.Util;
public interface IBodyData
{
///
- /// The body (as bytearray).
+ /// The body (as byte array).
///
byte[]? BodyAsBytes { get; set; }
@@ -26,6 +29,7 @@ public interface IBodyData
///
/// The body (as JSON object).
+ /// Also used for ProtoBuf.
///
object? BodyAsJson { get; set; }
@@ -68,4 +72,16 @@ public interface IBodyData
/// Defines if this BodyData is the result of a dynamically created response-string. (
///
public string? IsFuncUsed { get; set; }
+
+ #region ProtoBuf
+ ///
+ /// Gets or sets the proto definition.
+ ///
+ public Func? ProtoDefinition { get; set; }
+
+ ///
+ /// Gets or sets the full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
+ ///
+ public string? ProtoBufMessageType { get; set; }
+ #endregion
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Models/IdOrText.cs b/src/WireMock.Net.Abstractions/Models/IdOrText.cs
new file mode 100644
index 00000000..5d1a4c58
--- /dev/null
+++ b/src/WireMock.Net.Abstractions/Models/IdOrText.cs
@@ -0,0 +1,33 @@
+namespace WireMock.Models;
+
+///
+/// A structure defining an (optional) Id and a Text.
+///
+public readonly struct IdOrText
+{
+ ///
+ /// The Id [optional].
+ ///
+ public string? Id { get; }
+
+ ///
+ /// The Text.
+ ///
+ public string Text { get; }
+
+ ///
+ /// When Id is defined, return the Id, else the Text.
+ ///
+ public string Value => Id ?? Text;
+
+ ///
+ /// Create a IdOrText
+ ///
+ /// The Id [optional]
+ /// The Text.
+ public IdOrText(string? id, string text)
+ {
+ Id = id;
+ Text = text;
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Types/BodyType.cs b/src/WireMock.Net.Abstractions/Types/BodyType.cs
index a03534d1..47a62ae2 100644
--- a/src/WireMock.Net.Abstractions/Types/BodyType.cs
+++ b/src/WireMock.Net.Abstractions/Types/BodyType.cs
@@ -38,5 +38,10 @@ public enum BodyType
///
/// Body is a String which is x-www-form-urlencoded.
///
- FormUrlEncoded
+ FormUrlEncoded,
+
+ ///
+ /// Body is a ProtoBuf Byte array
+ ///
+ ProtoBuf
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj
index 29aa2805..32fa99b0 100644
--- a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj
+++ b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj
@@ -31,7 +31,7 @@
- $(DefineConstants);GRAPHQL;MIMEKIT
+ $(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF
diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs
index 051d46aa..e1078f74 100644
--- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs
+++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs
@@ -44,11 +44,11 @@ public partial class WireMockAssertions
}
[CustomAssertion]
- public AndConstraint WithBodyAsJson(IValueMatcher matcher, string because = "", params object[] becauseArgs)
+ public AndConstraint WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
- return ExecuteAssertionWithBodyAsJsonValueMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsJson);
+ return ExecuteAssertionWithBodyAsIObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsJson);
}
[CustomAssertion]
@@ -89,8 +89,8 @@ public partial class WireMockAssertions
return new AndConstraint(this);
}
- private AndConstraint ExecuteAssertionWithBodyAsJsonValueMatcher(
- IValueMatcher matcher,
+ private AndConstraint ExecuteAssertionWithBodyAsIObjectMatcher(
+ IObjectMatcher matcher,
string because,
object[] becauseArgs,
Func, bool> condition,
@@ -134,13 +134,13 @@ public partial class WireMockAssertions
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
- matcher.ValueAsObject ?? matcher.ValueAsBytes
+ matcher.Value
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
- _ => matcher.ValueAsObject ?? matcher.ValueAsBytes,
+ _ => matcher.Value,
requests => requests.Select(expression)
);
diff --git a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs
index 0ad92cdc..90dcd3bc 100644
--- a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs
+++ b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs
@@ -33,6 +33,9 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
///
public MatchBehaviour MatchBehaviour { get; }
+ ///
+ public object Value { get; }
+
private readonly AnyOf[] _patterns;
///
@@ -54,6 +57,7 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
+ Value = patterns;
}
public MatchResult IsMatch(string? input)
@@ -160,34 +164,34 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
}
#elif (NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER)
- Assembly assembly;
- try
- {
- assembly = CSScriptLib.CSScript.Evaluator.CompileCode(source);
- }
- catch (Exception ex)
- {
- throw new WireMockException($"CSharpCodeMatcher: Unable to compile code `{source}` for WireMock.CodeHelper", ex);
- }
+ Assembly assembly;
+ try
+ {
+ assembly = CSScriptLib.CSScript.Evaluator.CompileCode(source);
+ }
+ catch (Exception ex)
+ {
+ throw new WireMockException($"CSharpCodeMatcher: Unable to compile code `{source}` for WireMock.CodeHelper", ex);
+ }
- dynamic script;
- try
- {
- script = CSScripting.ReflectionExtensions.CreateObject(assembly, "*");
- }
- catch (Exception ex)
- {
- throw new WireMockException("CSharpCodeMatcher: Unable to create object from assembly", ex);
- }
+ dynamic script;
+ try
+ {
+ script = CSScripting.ReflectionExtensions.CreateObject(assembly, "*");
+ }
+ catch (Exception ex)
+ {
+ throw new WireMockException("CSharpCodeMatcher: Unable to create object from assembly", ex);
+ }
- try
- {
- result = script.IsMatch(inputValue);
- }
- catch (Exception ex)
- {
- throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
- }
+ try
+ {
+ result = script.IsMatch(inputValue);
+ }
+ catch (Exception ex)
+ {
+ throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
+ }
#else
throw new NotSupportedException("The 'CSharpCodeMatcher' cannot be used in netstandard 1.3");
#endif
diff --git a/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj b/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj
index 2ba81efc..97440b25 100644
--- a/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj
+++ b/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj
@@ -46,7 +46,7 @@
-
+
\ No newline at end of file
diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs
index 20b3e947..b052dd4f 100644
--- a/src/WireMock.Net/IMapping.cs
+++ b/src/WireMock.Net/IMapping.cs
@@ -69,12 +69,12 @@ public interface IMapping
int? StateTimes { get; }
///
- /// The Request matcher.
+ /// The RequestMatcher.
///
IRequestMatcher RequestMatcher { get; }
///
- /// The Provider.
+ /// The ResponseProvider.
///
IResponseProvider Provider { get; }
@@ -136,6 +136,11 @@ public interface IMapping
///
double? Probability { get; }
+ ///
+ /// The Grpc ProtoDefinition which is used for this mapping (request and response). [Optional]
+ ///
+ IdOrText? ProtoDefinition { get; }
+
///
/// ProvideResponseAsync
///
@@ -150,4 +155,44 @@ public interface IMapping
/// The Next State.
/// The .
IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState);
-}
\ No newline at end of file
+
+ ///
+ /// Define the scenario.
+ ///
+ /// The scenario.
+ /// The .
+ IMapping WithScenario(string scenario);
+
+ ///
+ /// Define the probability when this request should be matched. [Optional]
+ ///
+ /// The probability.
+ /// The .
+ IMapping WithProbability(double probability);
+
+ ///
+ /// Define a Grpc ProtoDefinition which is used for this mapping (request and response).
+ ///
+ /// The proto definition as text.
+ /// The .
+ IMapping WithProtoDefinition(IdOrText protoDefinition);
+}
+
+/*
+ executionConditionState">State in which the current mapping can occur. [Optional]
+ nextState">The next state which will occur after the current mapping execution. [Optional]
+ stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]
+ webhooks">The Webhooks. [Optional]
+ useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]
+ timeSettings">The TimeSettings. [Optional]
+ data">The data object. [Optional]
+
+
+ string? executionConditionState,
+ string? nextState,
+ int? stateTimes,
+ IWebhook[]? webhooks,
+ bool? useWebhooksFireAndForget,
+ ITimeSettings? timeSettings,
+ object? data,
+*/
\ No newline at end of file
diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs
index 019c208b..dd9c2b4d 100644
--- a/src/WireMock.Net/Mapping.cs
+++ b/src/WireMock.Net/Mapping.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
@@ -31,7 +32,7 @@ public class Mapping : IMapping
public int Priority { get; }
///
- public string? Scenario { get; }
+ public string? Scenario { get; private set; }
///
public string? ExecutionConditionState { get; }
@@ -76,7 +77,10 @@ public class Mapping : IMapping
public object? Data { get; }
///
- public double? Probability { get; }
+ public double? Probability { get; private set; }
+
+ ///
+ public IdOrText? ProtoDefinition { get; private set; }
///
/// Initializes a new instance of the class.
@@ -98,8 +102,8 @@ public class Mapping : IMapping
/// Use Fire and Forget for the defined webhook(s). [Optional]
/// The TimeSettings. [Optional]
/// The data object. [Optional]
- /// Define the probability when this request should be matched. [Optional]
- public Mapping(
+ public Mapping
+ (
Guid guid,
DateTime updatedAt,
string? title,
@@ -116,8 +120,8 @@ public class Mapping : IMapping
IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings,
- object? data,
- double? probability)
+ object? data
+ )
{
Guid = guid;
UpdatedAt = updatedAt;
@@ -136,7 +140,6 @@ public class Mapping : IMapping
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings;
Data = data;
- Probability = probability;
}
///
@@ -168,4 +171,25 @@ public class Mapping : IMapping
return result;
}
+
+ ///
+ public IMapping WithProbability(double probability)
+ {
+ Probability = Guard.NotNull(probability);
+ return this;
+ }
+
+ ///
+ public IMapping WithScenario(string scenario)
+ {
+ Scenario = Guard.NotNullOrWhiteSpace(scenario);
+ return this;
+ }
+
+ ///
+ public IMapping WithProtoDefinition(IdOrText protoDefinition)
+ {
+ ProtoDefinition = protoDefinition;
+ return this;
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net/MappingBuilder.cs b/src/WireMock.Net/MappingBuilder.cs
index da6f479f..8103ba2a 100644
--- a/src/WireMock.Net/MappingBuilder.cs
+++ b/src/WireMock.Net/MappingBuilder.cs
@@ -6,6 +6,8 @@ using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Owin;
+using WireMock.RequestBuilders;
+using WireMock.ResponseBuilders;
using WireMock.Serialization;
using WireMock.Server;
using WireMock.Settings;
@@ -146,6 +148,15 @@ public class MappingBuilder : IMappingBuilder
{
_mappingToFileSaver.SaveMappingToFile(mapping);
}
+
+ // Link this mapping to the Request
+ ((Request)mapping.RequestMatcher).Mapping = mapping;
+
+ // Link this mapping to the Response
+ if (mapping.Provider is Response response)
+ {
+ response.Mapping = mapping;
+ }
}
private static string ToJson(object value)
diff --git a/src/WireMock.Net/Matchers/ExactMatcher.cs b/src/WireMock.Net/Matchers/ExactMatcher.cs
index 4d67eed9..0c6580fe 100644
--- a/src/WireMock.Net/Matchers/ExactMatcher.cs
+++ b/src/WireMock.Net/Matchers/ExactMatcher.cs
@@ -17,6 +17,15 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
///
public MatchBehaviour MatchBehaviour { get; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The match behaviour.
+ /// The string value.
+ public ExactMatcher(MatchBehaviour matchBehaviour, string value) : this(matchBehaviour, true, MatchOperator.Or, new AnyOf(value))
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/WireMock.Net/Matchers/ExactObjectMatcher.cs b/src/WireMock.Net/Matchers/ExactObjectMatcher.cs
index bc01e70e..ea5bbbff 100644
--- a/src/WireMock.Net/Matchers/ExactObjectMatcher.cs
+++ b/src/WireMock.Net/Matchers/ExactObjectMatcher.cs
@@ -9,15 +9,8 @@ namespace WireMock.Matchers;
///
public class ExactObjectMatcher : IObjectMatcher
{
- ///
- /// Gets the value as object.
- ///
- public object? ValueAsObject { get; }
-
- ///
- /// Gets the value as byte[].
- ///
- public byte[]? ValueAsBytes { get; }
+ ///
+ public object Value { get; }
///
public MatchBehaviour MatchBehaviour { get; }
@@ -37,7 +30,7 @@ public class ExactObjectMatcher : IObjectMatcher
/// The value.
public ExactObjectMatcher(MatchBehaviour matchBehaviour, object value)
{
- ValueAsObject = Guard.NotNull(value);
+ Value = Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
}
@@ -56,21 +49,21 @@ public class ExactObjectMatcher : IObjectMatcher
/// The value.
public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value)
{
- ValueAsBytes = Guard.NotNull(value);
+ Value = Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
}
///
public MatchResult IsMatch(object? input)
{
- bool equals = false;
- if (ValueAsObject != null)
+ bool equals;
+ if (Value is byte[] valueAsBytes && input is byte[] inputAsBytes)
{
- equals = Equals(ValueAsObject, input);
+ equals = valueAsBytes.SequenceEqual(inputAsBytes);
}
- else if (input != null)
+ else
{
- equals = ValueAsBytes?.SequenceEqual((byte[])input) == true;
+ equals = Equals(Value, input);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals));
diff --git a/src/WireMock.Net/Matchers/IBytesMatcher.cs b/src/WireMock.Net/Matchers/IBytesMatcher.cs
new file mode 100644
index 00000000..2870a148
--- /dev/null
+++ b/src/WireMock.Net/Matchers/IBytesMatcher.cs
@@ -0,0 +1,18 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace WireMock.Matchers;
+
+///
+/// IBytesMatcher
+///
+public interface IBytesMatcher : IMatcher
+{
+ ///
+ /// Determines whether the specified input is match.
+ ///
+ /// The input byte array.
+ /// The CancellationToken [optional].
+ /// MatchResult
+ Task IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/IDecodeMatcher.cs b/src/WireMock.Net/Matchers/IDecodeMatcher.cs
new file mode 100644
index 00000000..ffc520f8
--- /dev/null
+++ b/src/WireMock.Net/Matchers/IDecodeMatcher.cs
@@ -0,0 +1,18 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace WireMock.Matchers;
+
+///
+/// IDecodeBytesMatcher
+///
+public interface IDecodeBytesMatcher
+{
+ ///
+ /// Decode byte array to an object.
+ ///
+ /// The byte array
+ /// The CancellationToken [optional].
+ /// object
+ Task
public interface IObjectMatcher : IMatcher
{
+ ///
+ /// Gets the value (can be a string or an object).
+ ///
+ /// Value
+ object Value { get; }
+
///
/// Determines whether the specified input is match.
///
diff --git a/src/WireMock.Net/Matchers/IProtoBufMatcher.cs b/src/WireMock.Net/Matchers/IProtoBufMatcher.cs
new file mode 100644
index 00000000..576ee9c1
--- /dev/null
+++ b/src/WireMock.Net/Matchers/IProtoBufMatcher.cs
@@ -0,0 +1,8 @@
+namespace WireMock.Matchers;
+
+///
+/// IProtoBufMatcher
+///
+public interface IProtoBufMatcher : IDecodeBytesMatcher, IBytesMatcher
+{
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/IValueMatcher.cs b/src/WireMock.Net/Matchers/IValueMatcher.cs
deleted file mode 100644
index c2b8e94e..00000000
--- a/src/WireMock.Net/Matchers/IValueMatcher.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace WireMock.Matchers;
-
-///
-/// IValueMatcher
-///
-///
-public interface IValueMatcher : IObjectMatcher
-{
- ///
- /// Gets the value (can be a string or an object).
- ///
- /// Value
- object Value { get; }
-}
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/JSONPathMatcher.cs b/src/WireMock.Net/Matchers/JSONPathMatcher.cs
index 1971616b..f1183fd0 100644
--- a/src/WireMock.Net/Matchers/JSONPathMatcher.cs
+++ b/src/WireMock.Net/Matchers/JSONPathMatcher.cs
@@ -11,7 +11,7 @@ namespace WireMock.Matchers;
///
/// JsonPathMatcher
///
-///
+///
///
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
{
@@ -20,6 +20,9 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
///
public MatchBehaviour MatchBehaviour { get; }
+ ///
+ public object Value { get; }
+
///
/// Initializes a new instance of the class.
///
@@ -52,6 +55,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
+ Value = patterns;
}
///
@@ -119,7 +123,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
// The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price").
// In that case it will return a JValue (some type) which does not implement the IEnumerable interface.
var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray();
-
+
return MatchScores.ToScore(values, MatchOperator);
}
diff --git a/src/WireMock.Net/Matchers/JmesPathMatcher.cs b/src/WireMock.Net/Matchers/JmesPathMatcher.cs
index 0f1488cd..f87c7a1a 100644
--- a/src/WireMock.Net/Matchers/JmesPathMatcher.cs
+++ b/src/WireMock.Net/Matchers/JmesPathMatcher.cs
@@ -16,6 +16,9 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
{
private readonly AnyOf[] _patterns;
+ ///
+ public object Value { get; }
+
///
public MatchBehaviour MatchBehaviour { get; }
@@ -59,6 +62,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
+ Value = patterns;
}
///
diff --git a/src/WireMock.Net/Matchers/JsonMatcher.cs b/src/WireMock.Net/Matchers/JsonMatcher.cs
index 6ff6bfce..a259ab5d 100644
--- a/src/WireMock.Net/Matchers/JsonMatcher.cs
+++ b/src/WireMock.Net/Matchers/JsonMatcher.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Linq;
using Newtonsoft.Json.Linq;
using Stef.Validation;
@@ -10,12 +9,12 @@ namespace WireMock.Matchers;
///
/// JsonMatcher
///
-public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
+public class JsonMatcher : IJsonMatcher
{
///
- public virtual string Name => "JsonMatcher";
+ public virtual string Name => nameof(JsonMatcher);
- ///
+ ///
public object Value { get; }
///
@@ -59,7 +58,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
IgnoreCase = ignoreCase;
Value = value;
- _valueAsJToken = ConvertValueToJToken(value);
+ _valueAsJToken = JsonUtils.ConvertValueToJToken(value);
_jTokenConverter = ignoreCase ? Rename : jToken => jToken;
}
@@ -74,7 +73,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
{
try
{
- var inputAsJToken = ConvertValueToJToken(input);
+ var inputAsJToken = JsonUtils.ConvertValueToJToken(input);
var match = IsMatch(_jTokenConverter(_valueAsJToken), _jTokenConverter(inputAsJToken));
score = MatchScores.ToScore(match);
@@ -99,25 +98,6 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
return JToken.DeepEquals(value, input);
}
- private static JToken ConvertValueToJToken(object value)
- {
- // Check if JToken, string, IEnumerable or object
- switch (value)
- {
- case JToken tokenValue:
- return tokenValue;
-
- case string stringValue:
- return JsonUtils.Parse(stringValue);
-
- case IEnumerable enumerableValue:
- return JArray.FromObject(enumerableValue);
-
- default:
- return JObject.FromObject(value);
- }
- }
-
private static string? ToUpper(string? input)
{
return input?.ToUpperInvariant();
diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs
index 8c6b9228..6fdf88a8 100644
--- a/src/WireMock.Net/Matchers/LinqMatcher.cs
+++ b/src/WireMock.Net/Matchers/LinqMatcher.cs
@@ -22,6 +22,9 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
///
public MatchBehaviour MatchBehaviour { get; }
+ ///
+ public object Value { get; }
+
///
/// Initializes a new instance of the class.
///
@@ -61,6 +64,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
+ Value = patterns;
}
///
diff --git a/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs b/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs
index f90889e4..12e4864b 100644
--- a/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs
+++ b/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs
@@ -23,4 +23,11 @@ internal static class MatchBehaviourHelper
return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch;
}
+
+ internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult result)
+ {
+ return matchBehaviour == MatchBehaviour.AcceptOnMatch ?
+ result :
+ new MatchResult(Convert(matchBehaviour, result.Score), result.Exception);
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs b/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs
index 4f7ee89f..7c746a86 100644
--- a/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs
+++ b/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs
@@ -17,6 +17,9 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
///
public MatchBehaviour MatchBehaviour { get; }
+ ///
+ public object Value { get; }
+
///
/// Initializes a new instance of the class.
///
@@ -24,6 +27,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
MatchBehaviour = matchBehaviour;
+ Value = string.Empty;
}
///
diff --git a/src/WireMock.Net/Matchers/ProtoBufMatcher.cs b/src/WireMock.Net/Matchers/ProtoBufMatcher.cs
new file mode 100644
index 00000000..45a22184
--- /dev/null
+++ b/src/WireMock.Net/Matchers/ProtoBufMatcher.cs
@@ -0,0 +1,114 @@
+#if PROTOBUF
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using ProtoBufJsonConverter;
+using ProtoBufJsonConverter.Models;
+using Stef.Validation;
+using WireMock.Models;
+using WireMock.Util;
+
+namespace WireMock.Matchers;
+
+///
+/// Grpc ProtoBuf Matcher
+///
+///
+public class ProtoBufMatcher : IProtoBufMatcher
+{
+ ///
+ public string Name => nameof(ProtoBufMatcher);
+
+ ///
+ public MatchBehaviour MatchBehaviour { get; }
+
+ ///
+ /// The Func to define The proto definition as text.
+ ///
+ public Func ProtoDefinition { get; }
+
+ ///
+ /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
+ ///
+ public string MessageType { get; }
+
+ ///
+ /// The Matcher to use (optional).
+ ///
+ public IObjectMatcher? Matcher { get; }
+
+ private static readonly Converter ProtoBufToJsonConverter = SingletonFactory.GetInstance();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The proto definition.
+ /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
+ /// The match behaviour. (default = "AcceptOnMatch")
+ /// The optional jsonMatcher to use to match the ProtoBuf as (json) object.
+ public ProtoBufMatcher(
+ Func protoDefinition,
+ string messageType,
+ MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
+ IObjectMatcher? matcher = null
+ )
+ {
+ ProtoDefinition = Guard.NotNull(protoDefinition);
+ MessageType = Guard.NotNullOrWhiteSpace(messageType);
+ Matcher = matcher;
+ MatchBehaviour = matchBehaviour;
+ }
+
+ ///
+ public async Task IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default)
+ {
+ var result = new MatchResult();
+
+ if (input != null)
+ {
+ try
+ {
+ var instance = await DecodeAsync(input, true, cancellationToken).ConfigureAwait(false);
+
+ result = Matcher?.IsMatch(instance) ?? new MatchResult(MatchScores.Perfect);
+ }
+ catch (Exception e)
+ {
+ result = new MatchResult(MatchScores.Mismatch, e);
+ }
+ }
+
+ return MatchBehaviourHelper.Convert(MatchBehaviour, result);
+ }
+
+ ///
+ public Task