Add Grpc ProtoBuf support (request-response) (#1047)

* ProtoBuf

* .

* x

* ---

* x

* fx

* ...

* sc

* ...

* .

* groen

* x

* fix tests

* ok!?

* fix tests

* fix tests

* !

* x

* 6

* .

* x

* ivaluematcher

* transformer

* .

* sc

* .

* mapping

* x

* tra

* com

* ...

* .

* .

* .

* AddProtoDefinition

* .

* set

* grpahj

* .

* .

* IdOrText

* ...

* async

* async2

* .

* t

* nuget

* <PackageReference Include="ProtoBufJsonConverter" Version="0.2.0-preview-04" />

* http version

* tests

* .WithHttpVersion("2")

* <PackageReference Include="ProtoBufJsonConverter" Version="0.2.0" />

* HttpVersionParser
This commit is contained in:
Stef Heyenrath
2024-02-16 17:16:51 +01:00
committed by GitHub
parent 801546fae7
commit 6ac95cf57d
129 changed files with 4585 additions and 1556 deletions

View File

@@ -91,18 +91,50 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// WithBody : Create a string response based on a object (which will be converted to a JSON string using the <see cref="IJsonConverter"/>).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/>.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null);
IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null);
/// <summary>
/// WithBody : Create a string response based on a object (which will be converted to a JSON string using the <see cref="IJsonConverter"/>).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding, can be <c>null</c>.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/>.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null);
IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null);
/// <summary>
/// WithBody : Create a ProtoBuf byte[] response based on a proto definition, message type and the value.
/// </summary>
/// <param name="protoDefinition">The proto definition as text.</param>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="value">The object to convert to protobuf byte[].</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsProtoBuf(
string protoDefinition,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
);
/// <summary>
/// WithBody : Create a ProtoBuf byte[] response based on a proto definition, message type and the value.
/// </summary>
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
/// <param name="value">The object to convert to protobuf byte[].</param>
/// <param name="jsonConverter">The <see cref="IJsonConverter"/> [optional]. Default value is NewtonsoftJsonConverter.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsProtoBuf(
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
);
}

View File

@@ -9,7 +9,7 @@ namespace WireMock.ResponseBuilders;
public interface IHeadersResponseBuilder : IBodyResponseBuilder
{
/// <summary>
/// The with header.
/// The WithHeader.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
@@ -17,23 +17,52 @@ public interface IHeadersResponseBuilder : IBodyResponseBuilder
IResponseBuilder WithHeader(string name, params string[] values);
/// <summary>
/// The with headers.
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, string> headers);
/// <summary>
/// The with headers.
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, string[]> headers);
/// <summary>
/// The with headers.
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers);
/// <summary>
/// The WithTrailingHeader.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeader(string name, params string[] values);
/// <summary>
/// The WithTrailingHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeaders(IDictionary<string, string> headers);
/// <summary>
/// The WithTrailingHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeaders(IDictionary<string, string[]> headers);
/// <summary>
/// The WithTrailingHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithTrailingHeaders(IDictionary<string, WireMockList<string>> headers);
}

View File

@@ -3,6 +3,7 @@ using System.Text;
using System.Threading.Tasks;
using JsonConverter.Abstractions;
using Stef.Validation;
using WireMock.Exceptions;
using WireMock.Types;
using WireMock.Util;
@@ -185,25 +186,79 @@ public partial class Response
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null)
public IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null)
{
return WithBody(body, null, converter, options);
return WithBody(body, null, jsonConverter, options);
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null)
public IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null)
{
Guard.NotNull(body);
Guard.NotNull(converter);
Guard.NotNull(jsonConverter);
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding,
DetectedBodyType = BodyType.String,
BodyAsString = converter.Serialize(body, options)
BodyAsString = jsonConverter.Serialize(body, options)
};
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
string protoDefinition,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
Guard.NotNullOrWhiteSpace(protoDefinition);
Guard.NotNullOrWhiteSpace(messageType);
Guard.NotNull(value);
#if !PROTOBUF
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
DetectedBodyType = BodyType.ProtoBuf,
BodyAsJson = value,
ProtoDefinition = () => new (null, protoDefinition),
ProtoBufMessageType = messageType
};
#endif
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
Guard.NotNullOrWhiteSpace(messageType);
Guard.NotNull(value);
#if !PROTOBUF
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
DetectedBodyType = BodyType.ProtoBuf,
BodyAsJson = value,
ProtoDefinition = () => Mapping.ProtoDefinition ?? throw new WireMockException("ProtoDefinition cannot be resolved. You probably forgot to call .WithProtoDefinition(...) on the mapping."),
ProtoBufMessageType = messageType
};
#endif
return this;
}
}

View File

@@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Types;
namespace WireMock.ResponseBuilders;
public partial class Response
{
/// <inheritdoc />
public IResponseBuilder WithHeader(string name, params string[] values)
{
Guard.NotNull(name);
ResponseMessage.AddHeader(name, values);
return this;
}
/// <inheritdoc />
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc />
public IResponseBuilder WithHeaders(IDictionary<string, string[]> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc />
public IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeader(string name, params string[] values)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(name);
ResponseMessage.AddTrailingHeader(name, values);
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeaders(IDictionary<string, string> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
ResponseMessage.TrailingHeaders = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeaders(IDictionary<string, string[]> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
ResponseMessage.TrailingHeaders = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithTrailingHeaders(IDictionary<string, WireMockList<string>> headers)
{
#if !TRAILINGHEADERS
throw new System.NotSupportedException("The WithBodyAsProtoBuf method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
Guard.NotNull(headers);
ResponseMessage.TrailingHeaders = headers;
return this;
#endif
}
}

View File

@@ -1,19 +1,20 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Proxy;
using WireMock.RequestBuilders;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.ResponseBuilders;
@@ -26,6 +27,11 @@ public partial class Response : IResponseBuilder
private TimeSpan? _delay;
/// <summary>
/// The link back to the mapping.
/// </summary>
public IMapping Mapping { get; set; } = null!;
/// <summary>
/// The minimum random delay in milliseconds.
/// </summary>
@@ -112,7 +118,7 @@ public partial class Response : IResponseBuilder
{
ResponseMessage = responseMessage;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(int)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)
@@ -156,42 +162,6 @@ public partial class Response : IResponseBuilder
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeader(string, string[])"/>
public IResponseBuilder WithHeader(string name, params string[] values)
{
Guard.NotNull(name);
ResponseMessage.AddHeader(name, values);
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, string[]})"/>
public IResponseBuilder WithHeaders(IDictionary<string, string[]> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
return this;
}
/// <inheritdoc cref="IHeadersResponseBuilder.WithHeaders(IDictionary{string, WireMockList{string}})"/>
public IResponseBuilder WithHeaders(IDictionary<string, WireMockList<string>> headers)
{
Guard.NotNull(headers);
ResponseMessage.Headers = headers;
return this;
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/>
public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{
@@ -304,10 +274,30 @@ public partial class Response : IResponseBuilder
{
responseMessage.Headers = ResponseMessage.Headers;
}
// Copy TrailingHeaders from ResponseMessage (if defined)
if (ResponseMessage.TrailingHeaders?.Count > 0)
{
responseMessage.TrailingHeaders = ResponseMessage.TrailingHeaders;
}
}
if (UseTransformer)
{
// Check if the body matcher is a RequestMessageProtoBufMatcher and try to to decode the byte-array to a BodyAsJson.
if (mapping.RequestMatcher is Request requestMatcher && requestMessage is RequestMessage request)
{
var protoBufMatcher = requestMatcher.GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
if (protoBufMatcher != null)
{
var decoded = await protoBufMatcher.DecodeAsync(request.BodyData?.BodyAsBytes).ConfigureAwait(false);
if (decoded != null)
{
request.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
}
}
}
ITransformer responseMessageTransformer;
switch (TransformerType)
{