Create WireMock.Net.MimePart project (#1300)

* Create WireMock.Net.MimePart project

* .

* REFACTOR

* ILRepack

* --

* ...

* x

* x

* .

* fix

* public class MimePartMatcher

* shared

* min

* .

* <!--<DelaySign>true</DelaySign>-->

* Update README.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Stef Heyenrath
2025-05-24 12:17:42 +02:00
committed by GitHub
parent c15206ecd8
commit 96eca4262a
306 changed files with 9746 additions and 9285 deletions

View File

@@ -0,0 +1,29 @@
// Copyright © WireMock.Net
namespace WireMock.ResponseBuilders;
/// <summary>
/// Defines the BodyDestinationFormat
/// </summary>
public static class BodyDestinationFormat
{
/// <summary>
/// Same as source (no conversion)
/// </summary>
public const string SameAsSource = "SameAsSource";
/// <summary>
/// Convert to string
/// </summary>
public const string String = "String";
/// <summary>
/// Convert to bytes
/// </summary>
public const string Bytes = "Bytes";
/// <summary>
/// Convert to Json object
/// </summary>
public const string Json = "Json";
}

View File

@@ -0,0 +1,169 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using JsonConverter.Abstractions;
using WireMock.Models;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The BodyResponseBuilder interface.
/// </summary>
public interface IBodyResponseBuilder : IFaultResponseBuilder
{
/// <summary>
/// WithBody : Create a ... response based on a string.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary>
/// WithBody : Create a ... response based on a callback function.
/// </summary>
/// <param name="bodyFactory">The delegate to build the body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary>
/// WithBody : Create a ... response based on an async callback function.
/// </summary>
/// <param name="bodyFactory">The async delegate to build the body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary>
/// WithBody : Create a ... response based on an async callback function.
/// </summary>
/// <param name="bodyFactory">The async delegate to build the body.</param>
/// <param name="timeout">The timeout to wait on new items in the queue. Default value is <c>1</c> hour.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithSseBody(Func<IRequestMessage, IBlockingQueue<string?>, Task> bodyFactory, TimeSpan? timeout = null);
/// <summary>
/// WithBody : Create a ... response based on a bytearray.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary>
/// WithBody : Create a string response based on a object (which will be converted to a JSON string).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <param name="indented">Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null);
/// <summary>
/// WithBody : Create a string response based on a object (which will be converted to a JSON string).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="indented">Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(object body, bool indented);
/// <summary>
/// WithBodyAsJson : Create a ... response based on a callback function.
/// </summary>
/// <param name="bodyFactory">The delegate to build the body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(Func<IRequestMessage, object> bodyFactory, Encoding? encoding = null);
/// <summary>
/// WithBodyAsJson : Create a ... response based on a async callback function.
/// </summary>
/// <param name="bodyFactory">The async delegate to build the body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(Func<IRequestMessage, Task<object>> bodyFactory, Encoding? encoding = null);
/// <summary>
/// WithBodyFromFile : Create a ... response based on a File.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="cache">Defines if this file is cached in memory or retrieved from disk every time the response is created.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyFromFile(string filename, bool cache = true);
/// <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="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 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="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 jsonConverter, JsonConverterOptions? options = null);
/// <summary>
/// WithBodyAsProtoBuf : 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>
/// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on proto definitions, message type and the value.
/// </summary>
/// <param name="protoDefinitions">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(
IReadOnlyList<string> protoDefinitions,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
);
/// <summary>
/// WithBodyAsProtoBuf : 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

@@ -0,0 +1,28 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.ResponseProviders;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The CallbackResponseBuilder interface.
/// </summary>
public interface ICallbackResponseBuilder : IResponseProvider
{
/// <summary>
/// The callback builder
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
IResponseBuilder WithCallback(Func<IRequestMessage, ResponseMessage> callbackHandler);
/// <summary>
/// The async callback builder
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
IResponseBuilder WithCallback(Func<IRequestMessage, Task<ResponseMessage>> callbackHandler);
}

View File

@@ -0,0 +1,33 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The DelayResponseBuilder interface.
/// </summary>
public interface IDelayResponseBuilder : ICallbackResponseBuilder
{
/// <summary>
/// The delay defined as a <see cref="TimeSpan"/>.
/// </summary>
/// <param name="delay">The TimeSpan to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(TimeSpan delay);
/// <summary>
/// The delay defined as milliseconds.
/// </summary>
/// <param name="milliseconds">The milliseconds to delay.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithDelay(int milliseconds);
/// <summary>
/// Introduce random delay
/// </summary>
/// <param name="minimumMilliseconds">Minimum milliseconds to delay</param>
/// <param name="maximumMilliseconds">Maximum milliseconds to delay</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000);
}

View File

@@ -0,0 +1,19 @@
// Copyright © WireMock.Net
using JetBrains.Annotations;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The FaultRequestBuilder interface.
/// </summary>
public interface IFaultResponseBuilder : ITransformResponseBuilder
{
/// <summary>
/// WithBody : Create a fault response.
/// </summary>
/// <param name="faultType">The FaultType.</param>
/// <param name="percentage">The percentage when this fault should occur. When null, it's always a fault.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithFault(FaultType faultType, double? percentage = null);
}

View File

@@ -0,0 +1,70 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using WireMock.Types;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The HeadersResponseBuilder interface.
/// </summary>
public interface IHeadersResponseBuilder : IBodyResponseBuilder
{
/// <summary>
/// The WithHeader.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="values">The values.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeader(string name, params string[] values);
/// <summary>
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, string> headers);
/// <summary>
/// The WithHeaders.
/// </summary>
/// <param name="headers">The headers.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithHeaders(IDictionary<string, string[]> headers);
/// <summary>
/// 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

@@ -0,0 +1,35 @@
// Copyright © WireMock.Net
using System.Security.Cryptography.X509Certificates;
using WireMock.Settings;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The ProxyResponseBuilder interface.
/// </summary>
public interface IProxyResponseBuilder : IStatusCodeResponseBuilder
{
/// <summary>
/// WithProxy URL using Client X509Certificate2.
/// </summary>
/// <param name="proxyUrl">The proxy url.</param>
/// <param name="clientX509Certificate2ThumbprintOrSubjectName">The X509Certificate2 file to use for client authentication.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy(string proxyUrl, string? clientX509Certificate2ThumbprintOrSubjectName = null);
/// <summary>
/// WithProxy using <see cref="ProxyAndRecordSettings"/>.
/// </summary>
/// <param name="settings">The ProxyAndRecordSettings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy(ProxyAndRecordSettings settings);
/// <summary>
/// WithProxy using <see cref="X509Certificate2"/>.
/// </summary>
/// <param name="proxyUrl">The proxy url.</param>
/// <param name="certificate">The X509Certificate2.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate);
}

View File

@@ -0,0 +1,10 @@
// Copyright © WireMock.Net
namespace WireMock.ResponseBuilders;
/// <summary>
/// The ResponseBuilder interface.
/// </summary>
public interface IResponseBuilder : IProxyResponseBuilder
{
}

View File

@@ -0,0 +1,48 @@
// Copyright © WireMock.Net
using System.Net;
using WireMock.Settings;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The StatusCodeResponseBuilder interface.
/// </summary>
public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder
{
/// <summary>
/// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(int code);
/// <summary>
/// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(string code);
/// <summary>
/// The with status code.
/// By default all status codes are allowed, to change this behaviour, see <inheritdoc cref="WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse"/>.
/// </summary>
/// <param name="code">The code.</param>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithStatusCode(HttpStatusCode code);
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithSuccess();
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithNotFound();
}

View File

@@ -0,0 +1,35 @@
// Copyright © WireMock.Net
using WireMock.Types;
namespace WireMock.ResponseBuilders;
/// <summary>
/// The TransformResponseBuilder interface.
/// </summary>
public interface ITransformResponseBuilder : IDelayResponseBuilder
{
/// <summary>
/// Use the Handlebars.Net ResponseMessage transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile);
/// <summary>
/// Use the Handlebars.Net ResponseMessage transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
IResponseBuilder WithTransformer(ReplaceNodeOptions options);
/// <summary>
/// Use a specific ResponseMessage transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.EvaluateAndTryToConvert);
}

View File

@@ -0,0 +1,306 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using JsonConverter.Abstractions;
using Stef.Validation;
using WireMock.Exceptions;
using WireMock.Models;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.ResponseBuilders;
public partial class Response
{
private bool _bodyFromFileSet;
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, req => new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = bodyFactory(req),
Encoding = encoding ?? Encoding.UTF8,
IsFuncUsed = "Func<IRequestMessage, string>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, async req => new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = await bodyFactory(req).ConfigureAwait(false),
Encoding = encoding ?? Encoding.UTF8,
IsFuncUsed = "Func<IRequestMessage, Task<string>>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithSseBody(Func<IRequestMessage, IBlockingQueue<string?>, Task> bodyFactory, TimeSpan? timeout = null)
{
Guard.NotNull(bodyFactory);
var queue = new BlockingQueue<string?>(timeout);
return WithCallbackInternal(true, req => new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.SseString,
SseStringQueue = queue,
BodyAsSseStringTask = bodyFactory(req, queue),
Encoding = Encoding.UTF8,
IsFuncUsed = "Func<IRequestMessage, BlockingQueue<string?>, Task>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(body);
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData();
switch (destination)
{
case BodyDestinationFormat.String:
var enc = encoding ?? Encoding.UTF8;
ResponseMessage.BodyData.DetectedBodyType = BodyType.String;
ResponseMessage.BodyData.BodyAsString = enc.GetString(body);
ResponseMessage.BodyData.Encoding = enc;
break;
default:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = body;
break;
}
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyFromFile(string filename, bool cache = true)
{
Guard.NotNull(filename);
_bodyFromFileSet = true;
ResponseMessage.BodyData = new BodyData
{
BodyAsFileIsCached = cache,
BodyAsFile = filename
};
if (cache && !UseTransformer)
{
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
}
else
{
ResponseMessage.BodyData.DetectedBodyType = BodyType.File;
}
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(body);
encoding ??= Encoding.UTF8;
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding
};
switch (destination)
{
case BodyDestinationFormat.Bytes:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes;
ResponseMessage.BodyData.BodyAsBytes = encoding.GetBytes(body);
break;
case BodyDestinationFormat.Json:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json;
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body);
break;
default:
ResponseMessage.BodyData.DetectedBodyType = BodyType.String;
ResponseMessage.BodyData.BodyAsString = body;
break;
}
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null)
{
Guard.NotNull(body);
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding,
DetectedBodyType = BodyType.Json,
BodyAsJson = body,
BodyAsJsonIndented = indented
};
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(object body, bool indented)
{
return WithBodyAsJson(body, null, indented);
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(Func<IRequestMessage, object> bodyFactory, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, req => new ResponseMessage
{
BodyData = new BodyData
{
Encoding = encoding ?? Encoding.UTF8,
DetectedBodyType = BodyType.Json,
BodyAsJson = bodyFactory(req),
IsFuncUsed = "Func<IRequestMessage, object>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(Func<IRequestMessage, Task<object>> bodyFactory, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, async req => new ResponseMessage
{
BodyData = new BodyData
{
Encoding = encoding ?? Encoding.UTF8,
DetectedBodyType = BodyType.Json,
BodyAsJson = await bodyFactory(req).ConfigureAwait(false),
IsFuncUsed = "Func<IRequestMessage, Task<object>>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null)
{
return WithBody(body, null, jsonConverter, options);
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null)
{
Guard.NotNull(body);
Guard.NotNull(jsonConverter);
ResponseMessage.BodyDestination = null;
ResponseMessage.BodyData = new BodyData
{
Encoding = encoding,
DetectedBodyType = BodyType.String,
BodyAsString = jsonConverter.Serialize(body, options)
};
return this;
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
string protoDefinition,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
return WithBodyAsProtoBuf([protoDefinition], messageType, value, jsonConverter, options);
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
IReadOnlyList<string> protoDefinitions,
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
Guard.NotNullOrEmpty(protoDefinitions);
Guard.NotNullOrWhiteSpace(messageType);
Guard.NotNull(value);
#if !PROTOBUF
throw new 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 IdOrTexts(null, protoDefinitions),
ProtoBufMessageType = messageType
};
return this;
#endif
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsProtoBuf(
string messageType,
object value,
IJsonConverter? jsonConverter = null,
JsonConverterOptions? options = null
)
{
Guard.NotNullOrWhiteSpace(messageType);
Guard.NotNull(value);
#if !PROTOBUF
throw new 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
};
return this;
#endif
}
}

View File

@@ -0,0 +1,64 @@
// Copyright © WireMock.Net
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Stef.Validation;
namespace WireMock.ResponseBuilders;
public partial class Response
{
/// <summary>
/// A delegate to execute to generate the response.
/// </summary>
[MemberNotNullWhen(true, nameof(WithCallbackUsed))]
public Func<IRequestMessage, ResponseMessage>? Callback { get; private set; }
/// <summary>
/// A delegate to execute to generate the response async.
/// </summary>
[MemberNotNullWhen(true, nameof(WithCallbackUsed))]
public Func<IRequestMessage, Task<ResponseMessage>>? CallbackAsync { get; private set; }
/// <summary>
/// Defines if the method WithCallback(...) is used.
/// </summary>
public bool WithCallbackUsed { get; private set; }
/// <inheritdoc />
public IResponseBuilder WithCallback(Func<IRequestMessage, ResponseMessage> callbackHandler)
{
Guard.NotNull(callbackHandler);
return WithCallbackInternal(true, callbackHandler);
}
/// <inheritdoc />
public IResponseBuilder WithCallback(Func<IRequestMessage, Task<ResponseMessage>> callbackHandler)
{
Guard.NotNull(callbackHandler);
return WithCallbackInternal(true, callbackHandler);
}
private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<IRequestMessage, ResponseMessage> callbackHandler)
{
Guard.NotNull(callbackHandler);
WithCallbackUsed = withCallbackUsed;
Callback = callbackHandler;
return this;
}
private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func<IRequestMessage, Task<ResponseMessage>> callbackHandler)
{
Guard.NotNull(callbackHandler);
WithCallbackUsed = withCallbackUsed;
CallbackAsync = callbackHandler;
return this;
}
}

View File

@@ -0,0 +1,15 @@
// Copyright © WireMock.Net
namespace WireMock.ResponseBuilders;
public partial class Response
{
/// <inheritdoc cref="IFaultResponseBuilder.WithFault(FaultType, double?)"/>
public IResponseBuilder WithFault(FaultType faultType, double? percentage = null)
{
ResponseMessage.FaultType = faultType;
ResponseMessage.FaultPercentage = percentage;
return this;
}
}

View File

@@ -0,0 +1,103 @@
// Copyright © WireMock.Net
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 WithTrailingHeader 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 WithTrailingHeaders 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 WithTrailingHeaders 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

@@ -0,0 +1,59 @@
// Copyright © WireMock.Net
using System.Net.Http;
using WireMock.Http;
using WireMock.Settings;
using Stef.Validation;
using System.Security.Cryptography.X509Certificates;
namespace WireMock.ResponseBuilders;
public partial class Response
{
private HttpClient? _httpClientForProxy;
/// <summary>
/// The WebProxy settings.
/// </summary>
public ProxyAndRecordSettings? ProxyAndRecordSettings { get; private set; }
/// <inheritdoc />
public IResponseBuilder WithProxy(string proxyUrl, string? clientX509Certificate2ThumbprintOrSubjectName = null)
{
Guard.NotNullOrEmpty(proxyUrl);
var settings = new ProxyAndRecordSettings
{
Url = proxyUrl,
ClientX509Certificate2ThumbprintOrSubjectName = clientX509Certificate2ThumbprintOrSubjectName
};
return WithProxy(settings);
}
/// <inheritdoc />
public IResponseBuilder WithProxy(ProxyAndRecordSettings settings)
{
Guard.NotNull(settings);
ProxyAndRecordSettings = settings;
_httpClientForProxy = HttpClientBuilder.Build(settings);
return this;
}
/// <inheritdoc />
public IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate)
{
Guard.NotNullOrEmpty(proxyUrl);
Guard.NotNull(certificate);
var settings = new ProxyAndRecordSettings
{
Url = proxyUrl,
Certificate = certificate
};
return WithProxy(settings);
}
}

View File

@@ -0,0 +1,36 @@
// Copyright © WireMock.Net
using System;
using WireMock.Types;
namespace WireMock.ResponseBuilders;
public partial class Response
{
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/>
public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{
return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile);
}
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(ReplaceNodeOptions)"/>
public IResponseBuilder WithTransformer(ReplaceNodeOptions options)
{
return WithTransformer(TransformerType.Handlebars, false, options);
}
/// <inheritdoc />
public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.EvaluateAndTryToConvert)
{
if (_bodyFromFileSet)
{
throw new InvalidOperationException("WithTransformer should be used before WithBodyFromFile.");
}
UseTransformer = true;
TransformerType = transformerType;
UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
TransformerReplaceNodeOptions = options;
return this;
}
}

View File

@@ -0,0 +1,307 @@
// Copyright © WireMock.Net and mock4net by Alexandre Victoor
// 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.Net;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Stef.Validation;
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;
/// <summary>
/// The Response.
/// </summary>
public partial class Response : IResponseBuilder
{
private static readonly ThreadLocal<Random> Random = new(() => new Random(DateTime.UtcNow.Millisecond));
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>
public int? MinimumDelayMilliseconds { get; private set; }
/// <summary>
/// The maximum random delay in milliseconds.
/// </summary>
public int? MaximumDelayMilliseconds { get; private set; }
/// <summary>
/// The delay
/// </summary>
public TimeSpan? Delay
{
get
{
if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null)
{
return TimeSpan.FromMilliseconds(Random.Value!.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value));
}
return _delay;
}
private set => _delay = value;
}
/// <summary>
/// Gets a value indicating whether [use transformer].
/// </summary>
public bool UseTransformer { get; private set; }
/// <summary>
/// Gets the type of the transformer.
/// </summary>
public TransformerType TransformerType { get; private set; }
/// <summary>
/// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary>
public bool UseTransformerForBodyAsFile { get; private set; }
/// <summary>
/// Gets the ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; }
/// <summary>
/// Gets the response message.
/// </summary>
public ResponseMessage ResponseMessage { get; }
/// <summary>
/// Creates this instance.
/// </summary>
/// <param name="responseMessage">ResponseMessage</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create(ResponseMessage? responseMessage = null)
{
var message = responseMessage ?? new ResponseMessage();
return new Response(message);
}
/// <summary>
/// Creates this instance with the specified function.
/// </summary>
/// <param name="func">The callback function.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public static IResponseBuilder Create(Func<ResponseMessage> func)
{
Guard.NotNull(func);
return new Response(func());
}
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class.
/// </summary>
/// <param name="responseMessage">The response.</param>
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(int)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(string)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(string code)
{
ResponseMessage.StatusCode = code;
return this;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(HttpStatusCode)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(HttpStatusCode code)
{
return WithStatusCode((int)code);
}
/// <summary>
/// The with Success status code (200).
/// </summary>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithSuccess()
{
return WithStatusCode((int)HttpStatusCode.OK);
}
/// <summary>
/// The with NotFound status code (404).
/// </summary>
/// <returns>The <see cref="IResponseBuilder"/>.</returns>
[PublicAPI]
public IResponseBuilder WithNotFound()
{
return WithStatusCode((int)HttpStatusCode.NotFound);
}
/// <inheritdoc />
public IResponseBuilder WithDelay(TimeSpan delay)
{
Guard.Condition(delay, d => d == Timeout.InfiniteTimeSpan || d > TimeSpan.Zero);
Delay = delay;
return this;
}
/// <inheritdoc />
public IResponseBuilder WithDelay(int milliseconds)
{
return WithDelay(TimeSpan.FromMilliseconds(milliseconds));
}
/// <inheritdoc />
public IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000)
{
Guard.Condition(minimumMilliseconds, min => min >= 0);
Guard.Condition(maximumMilliseconds, max => max > minimumMilliseconds);
MinimumDelayMilliseconds = minimumMilliseconds;
MaximumDelayMilliseconds = maximumMilliseconds;
return this;
}
/// <inheritdoc />
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)
{
Guard.NotNull(requestMessage);
Guard.NotNull(settings);
if (Delay != null)
{
await Task.Delay(Delay.Value).ConfigureAwait(false);
}
if (ProxyAndRecordSettings != null && _httpClientForProxy != null)
{
string RemoveFirstOccurrence(string source, string find)
{
int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
return place >= 0 ? source.Remove(place, find.Length) : source;
}
var requestUri = new Uri(requestMessage.Url);
// Build the proxy url and skip duplicates
string extra = RemoveFirstOccurrence(requestUri.LocalPath.TrimEnd('/'), new Uri(ProxyAndRecordSettings.Url).LocalPath.TrimEnd('/'));
requestMessage.ProxyUrl = ProxyAndRecordSettings.Url + extra + requestUri.Query;
var proxyHelper = new ProxyHelper(settings);
return await proxyHelper.SendAsync(
mapping,
ProxyAndRecordSettings,
_httpClientForProxy,
requestMessage,
requestMessage.ProxyUrl
).ConfigureAwait(false);
}
ResponseMessage responseMessage;
if (!WithCallbackUsed)
{
responseMessage = ResponseMessage;
}
else
{
if (Callback != null)
{
responseMessage = Callback(requestMessage);
}
else
{
responseMessage = await CallbackAsync!(requestMessage).ConfigureAwait(false);
}
// Copy StatusCode from ResponseMessage (if defined)
if (ResponseMessage.StatusCode != null)
{
responseMessage.StatusCode = ResponseMessage.StatusCode;
}
// Copy Headers from ResponseMessage (if defined)
if (ResponseMessage.Headers?.Count > 0)
{
responseMessage.Headers = ResponseMessage.Headers;
}
// Copy TrailingHeaders from ResponseMessage (if defined)
if (ResponseMessage.TrailingHeaders?.Count > 0)
{
responseMessage.TrailingHeaders = ResponseMessage.TrailingHeaders;
}
}
if (UseTransformer)
{
// If the body matcher is a RequestMessageProtoBufMatcher or BodyMatcher with a ProtoBufMatcher then try to decode the byte-array to a BodyAsJson.
if (mapping.RequestMatcher is Request request && requestMessage is RequestMessage requestMessageImplementation)
{
if (request.TryGetProtoBufMatcher(out var protoBufMatcher))
{
var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false);
if (decoded != null)
{
requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
}
}
}
ITransformer responseMessageTransformer;
switch (TransformerType)
{
case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(settings);
responseMessageTransformer = new Transformer(settings, factoryHandlebars);
break;
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType);
responseMessageTransformer = new Transformer(settings, factoryDotLiquid);
break;
default:
throw new NotSupportedException($"TransformerType '{TransformerType}' is not supported.");
}
return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
}
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true && responseMessage.BodyData?.BodyAsFile is not null)
{
ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
}
return (responseMessage, null);
}
}