mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-21 08:21:53 +02:00
Create WireMock.Net.ProtoBuf project (#1350)
* Create WireMock.Net.ProtoBuf project * ok * Update Directory.Build.props 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:
118
src/WireMock.Net.ProtoBuf/Matchers/ProtoBufMatcher.cs
Normal file
118
src/WireMock.Net.ProtoBuf/Matchers/ProtoBufMatcher.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Grpc ProtoBuf Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IProtoBufMatcher"/>
|
||||
public class ProtoBufMatcher : IProtoBufMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(ProtoBufMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<IdOrTexts> ProtoDefinition { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string MessageType { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IObjectMatcher? Matcher { get; }
|
||||
|
||||
private static readonly Converter ProtoBufToJsonConverter = SingletonFactory<Converter>.GetInstance();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProtoBufMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="protoDefinition">The proto definition as id or text.</param>
|
||||
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="matcher">The optional jsonMatcher to use to match the ProtoBuf as (json) object.</param>
|
||||
public ProtoBufMatcher(
|
||||
Func<IdOrTexts> protoDefinition,
|
||||
string messageType,
|
||||
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
|
||||
IObjectMatcher? matcher = null
|
||||
)
|
||||
{
|
||||
ProtoDefinition = Guard.NotNull(protoDefinition);
|
||||
MessageType = Guard.NotNullOrWhiteSpace(messageType);
|
||||
Matcher = matcher;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<MatchResult> 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<object?> DecodeAsync(byte[]? input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return DecodeAsync(input, false, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return "NotImplemented";
|
||||
}
|
||||
|
||||
private async Task<object?> DecodeAsync(byte[]? input, bool throwException, CancellationToken cancellationToken)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var protoDefinitions = ProtoDefinition().Texts;
|
||||
|
||||
var resolver = new WireMockProtoFileResolver(protoDefinitions);
|
||||
var request = new ConvertToObjectRequest(protoDefinitions[0], MessageType, input)
|
||||
.WithProtoFileResolver(resolver);
|
||||
|
||||
try
|
||||
{
|
||||
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (throwException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/WireMock.Net.ProtoBuf/Models/ProtoDefinitionData.cs
Normal file
39
src/WireMock.Net.ProtoBuf/Models/ProtoDefinitionData.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A placeholder class for Proto Definitions.
|
||||
/// </summary>
|
||||
public class ProtoDefinitionData
|
||||
{
|
||||
private readonly IDictionary<string, string> _filenameMappedToProtoDefinition;
|
||||
|
||||
internal ProtoDefinitionData(IDictionary<string, string> filenameMappedToProtoDefinition)
|
||||
{
|
||||
_filenameMappedToProtoDefinition = filenameMappedToProtoDefinition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all the ProtoDefinitions.
|
||||
/// Note: the main ProtoDefinition will be the first one in the list.
|
||||
/// </summary>
|
||||
/// <param name="mainProtoFilename">The main ProtoDefinition filename.</param>
|
||||
public IReadOnlyList<string> ToList(string mainProtoFilename)
|
||||
{
|
||||
Guard.NotNullOrEmpty(mainProtoFilename);
|
||||
|
||||
if (!_filenameMappedToProtoDefinition.TryGetValue(mainProtoFilename, out var mainProtoDefinition))
|
||||
{
|
||||
throw new KeyNotFoundException($"The ProtoDefinition with filename '{mainProtoFilename}' was not found.");
|
||||
}
|
||||
|
||||
var list = new List<string> { mainProtoDefinition };
|
||||
list.AddRange(_filenameMappedToProtoDefinition.Where(kvp => kvp.Key != mainProtoFilename).Select(kvp => kvp.Value));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
8
src/WireMock.Net.ProtoBuf/Properties/AssemblyInfo.cs
Normal file
8
src/WireMock.Net.ProtoBuf/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
|
||||
// Needed for Moq in the UnitTest project
|
||||
// [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.RequestBuilders;
|
||||
|
||||
/// <summary>
|
||||
/// IRequestBuilderExtensions extensions for ProtoBuf.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IRequestBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <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="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithBodyAsProtoBuf(this IRequestBuilder requestBuilder, string protoDefinition, string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return requestBuilder.WithBodyAsProtoBuf([protoDefinition], messageType, matchBehaviour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <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="matcher">The matcher to use to match the ProtoBuf as (json) object.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithBodyAsProtoBuf(this IRequestBuilder requestBuilder, string protoDefinition, string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return requestBuilder.WithBodyAsProtoBuf([protoDefinition], messageType, matcher, matchBehaviour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="protoDefinitions">The proto definitions 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="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithBodyAsProtoBuf(this IRequestBuilder requestBuilder, IReadOnlyList<string> protoDefinitions, string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return requestBuilder.Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => new IdOrTexts(null, protoDefinitions), messageType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="protoDefinitions">The proto definitions 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="matcher">The matcher to use to match the ProtoBuf as (json) object.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithBodyAsProtoBuf(this IRequestBuilder requestBuilder, IReadOnlyList<string> protoDefinitions, string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return requestBuilder.Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => new IdOrTexts(null, protoDefinitions), messageType, matcher));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithBodyAsProtoBuf(this IRequestBuilder requestBuilder, string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return requestBuilder.Add(new RequestMessageProtoBufMatcher(matchBehaviour, ProtoDefinitionFunc(requestBuilder), messageType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf
|
||||
/// </summary>
|
||||
/// <param name="requestBuilder">The <see cref="IRequestBuilder"/>.</param>
|
||||
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
|
||||
/// <param name="matcher">The matcher to use to match the ProtoBuf as (json) object.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
|
||||
public static IRequestBuilder WithBodyAsProtoBuf(this IRequestBuilder requestBuilder, string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
|
||||
{
|
||||
return requestBuilder.Add(new RequestMessageProtoBufMatcher(matchBehaviour, ProtoDefinitionFunc(requestBuilder), messageType, matcher));
|
||||
}
|
||||
|
||||
private static Func<IdOrTexts> ProtoDefinitionFunc(IRequestBuilder requestBuilder)
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
if (requestBuilder.Mapping.ProtoDefinition == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No ProtoDefinition defined on mapping '{requestBuilder.Mapping.Guid}'. Please use the WireMockServerSettings to define ProtoDefinitions.");
|
||||
}
|
||||
|
||||
return requestBuilder.Mapping.ProtoDefinition.Value;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JsonConverter.Abstractions;
|
||||
using Stef.Validation;
|
||||
using WireMock.Exceptions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.ResponseBuilders;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for <see cref="IResponseBuilder"/> to implement WithBodyAsProtoBuf.
|
||||
/// </summary>
|
||||
public static class IResponseBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on a proto definition, message type and the value.
|
||||
/// </summary>
|
||||
/// <param name="responseBuilder">The response builder.</param>
|
||||
/// <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>
|
||||
public static IResponseBuilder WithBodyAsProtoBuf(
|
||||
this IResponseBuilder responseBuilder,
|
||||
string protoDefinition,
|
||||
string messageType,
|
||||
object value,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? options = null
|
||||
)
|
||||
{
|
||||
return responseBuilder.WithBodyAsProtoBuf([protoDefinition], messageType, value, jsonConverter, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on proto definitions, message type and the value.
|
||||
/// </summary>
|
||||
/// <param name="responseBuilder">The response builder.</param>
|
||||
/// <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>
|
||||
public static IResponseBuilder WithBodyAsProtoBuf(
|
||||
this IResponseBuilder responseBuilder,
|
||||
IReadOnlyList<string> protoDefinitions,
|
||||
string messageType,
|
||||
object value,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? options = null
|
||||
)
|
||||
{
|
||||
Guard.NotNullOrEmpty(protoDefinitions);
|
||||
Guard.NotNullOrWhiteSpace(messageType);
|
||||
Guard.NotNull(value);
|
||||
|
||||
responseBuilder.ResponseMessage.BodyDestination = null;
|
||||
responseBuilder.ResponseMessage.BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.ProtoBuf,
|
||||
BodyAsJson = value,
|
||||
ProtoDefinition = () => new IdOrTexts(null, protoDefinitions),
|
||||
ProtoBufMessageType = messageType
|
||||
};
|
||||
|
||||
return responseBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on a proto definition, message type and the value.
|
||||
/// </summary>
|
||||
/// <param name="responseBuilder">The response builder.</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>
|
||||
public static IResponseBuilder WithBodyAsProtoBuf(
|
||||
this IResponseBuilder responseBuilder,
|
||||
string messageType,
|
||||
object value,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
JsonConverterOptions? options = null
|
||||
)
|
||||
{
|
||||
Guard.NotNullOrWhiteSpace(messageType);
|
||||
Guard.NotNull(value);
|
||||
|
||||
responseBuilder.ResponseMessage.BodyDestination = null;
|
||||
responseBuilder.ResponseMessage.BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.ProtoBuf,
|
||||
BodyAsJson = value,
|
||||
ProtoDefinition = () => responseBuilder.Mapping.ProtoDefinition ?? throw new WireMockException("ProtoDefinition cannot be resolved. You probably forgot to call .WithProtoDefinition(...) on the mapping."),
|
||||
ProtoBufMessageType = messageType
|
||||
};
|
||||
|
||||
return responseBuilder;
|
||||
}
|
||||
}
|
||||
47
src/WireMock.Net.ProtoBuf/Util/ProtoBufUtils.cs
Normal file
47
src/WireMock.Net.ProtoBuf/Util/ProtoBufUtils.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JsonConverter.Abstractions;
|
||||
using ProtoBufJsonConverter;
|
||||
using ProtoBufJsonConverter.Models;
|
||||
using WireMock.ResponseBuilders;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
internal class ProtoBufUtils : IProtoBufUtils
|
||||
{
|
||||
public async Task<byte[]> GetProtoBufMessageWithHeaderAsync(
|
||||
IReadOnlyList<string>? protoDefinitions,
|
||||
string? messageType,
|
||||
object? value,
|
||||
IJsonConverter? jsonConverter = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (protoDefinitions == null || string.IsNullOrWhiteSpace(messageType) || value is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var resolver = new WireMockProtoFileResolver(protoDefinitions);
|
||||
var request = new ConvertToProtoBufRequest(protoDefinitions[0], messageType!, value, true)
|
||||
.WithProtoFileResolver(resolver);
|
||||
|
||||
return await SingletonFactory<Converter>
|
||||
.GetInstance()
|
||||
.ConvertAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public IResponseBuilder UpdateResponseBuilder(IResponseBuilder responseBuilder, string protoBufMessageType, object bodyAsJson, params string[] protoDefinitions)
|
||||
{
|
||||
if (protoDefinitions.Length > 0)
|
||||
{
|
||||
return responseBuilder.WithBodyAsProtoBuf(protoDefinitions, protoBufMessageType, bodyAsJson);
|
||||
}
|
||||
|
||||
// ProtoDefinition(s) is/are defined at Mapping/Server level
|
||||
return responseBuilder.WithBodyAsProtoBuf(protoBufMessageType, bodyAsJson);
|
||||
}
|
||||
}
|
||||
80
src/WireMock.Net.ProtoBuf/Util/ProtoDefinitionDataHelper.cs
Normal file
80
src/WireMock.Net.ProtoBuf/Util/ProtoDefinitionDataHelper.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ProtoBufJsonConverter;
|
||||
using ProtoBufJsonConverter.Models;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Some helper methods for Proto Definitions.
|
||||
/// </summary>
|
||||
internal static class ProtoDefinitionDataHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a dictionary of ProtoDefinitions from a directory.
|
||||
/// - The key will be the filename without extension.
|
||||
/// - The value will be the ProtoDefinition with an extra comment with the relative path to each <c>.proto</c> file so it can be used by the WireMockProtoFileResolver.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to start from.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <c>System.Threading.CancellationToken.None</c>.</param>
|
||||
public static async Task<ProtoDefinitionData> FromDirectory(string directory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Guard.NotNullOrEmpty(directory);
|
||||
|
||||
var fileNameMappedToProtoDefinition = new Dictionary<string, string>();
|
||||
var filePaths = Directory.EnumerateFiles(directory, "*.proto", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var filePath in filePaths)
|
||||
{
|
||||
// Get the relative path to the directory (note that this will be OS specific).
|
||||
var relativePath = FilePathUtils.GetRelativePath(directory, filePath);
|
||||
|
||||
// Make it a valid proto import path
|
||||
var protoRelativePath = relativePath.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
// Build comment and get content from file.
|
||||
var comment = $"// {protoRelativePath}";
|
||||
#if NETSTANDARD2_0 || NET462
|
||||
var content = File.ReadAllText(filePath);
|
||||
#else
|
||||
var content = await File.ReadAllTextAsync(filePath, cancellationToken);
|
||||
#endif
|
||||
// Only add the comment if it's not already defined.
|
||||
var modifiedContent = !content.StartsWith(comment) ? $"{comment}\n{content}" : content;
|
||||
var key = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
fileNameMappedToProtoDefinition.Add(key, modifiedContent);
|
||||
}
|
||||
|
||||
var converter = SingletonFactory<Converter>.GetInstance();
|
||||
var resolver = new WireMockProtoFileResolver(fileNameMappedToProtoDefinition.Values);
|
||||
|
||||
var messageTypeMappedToWithProtoDefinition = new Dictionary<string, string>();
|
||||
|
||||
foreach (var protoDefinition in fileNameMappedToProtoDefinition.Values)
|
||||
{
|
||||
var infoRequest = new GetInformationRequest(protoDefinition, resolver);
|
||||
|
||||
try
|
||||
{
|
||||
var info = await converter.GetInformationAsync(infoRequest, cancellationToken);
|
||||
foreach (var messageType in info.MessageTypes)
|
||||
{
|
||||
messageTypeMappedToWithProtoDefinition[messageType.Key] = protoDefinition;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return new ProtoDefinitionData(fileNameMappedToProtoDefinition);
|
||||
}
|
||||
}
|
||||
73
src/WireMock.Net.ProtoBuf/Util/WireMockProtoFileResolver.cs
Normal file
73
src/WireMock.Net.ProtoBuf/Util/WireMockProtoFileResolver.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ProtoBufJsonConverter;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
/// <summary>
|
||||
/// This resolver is used to resolve the extra ProtoDefinition files.
|
||||
///
|
||||
/// It assumes that:
|
||||
/// - The first commented line of each ProtoDefinition file is the filepath which is used in the import of the other ProtoDefinition file(s).
|
||||
/// </summary>
|
||||
internal class WireMockProtoFileResolver : IProtoFileResolver
|
||||
{
|
||||
private readonly Dictionary<string, string> _files = [];
|
||||
|
||||
public WireMockProtoFileResolver(IReadOnlyCollection<string> protoDefinitions)
|
||||
{
|
||||
if (Guard.NotNullOrEmpty(protoDefinitions).Count() <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var extraProtoDefinition in protoDefinitions)
|
||||
{
|
||||
var firstNonEmptyLine = extraProtoDefinition.Split('\r', '\n').FirstOrDefault(l => !string.IsNullOrEmpty(l));
|
||||
if (firstNonEmptyLine != null)
|
||||
{
|
||||
if (TryGetValidPath(firstNonEmptyLine.TrimStart('/', ' '), out var validPath))
|
||||
{
|
||||
_files.Add(validPath, extraProtoDefinition);
|
||||
}
|
||||
else
|
||||
{
|
||||
_files.Add(extraProtoDefinition.GetDeterministicHashCodeAsString(), extraProtoDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Exists(string path)
|
||||
{
|
||||
return _files.ContainsKey(path);
|
||||
}
|
||||
|
||||
public TextReader OpenText(string path)
|
||||
{
|
||||
if (_files.TryGetValue(path, out var extraProtoDefinition))
|
||||
{
|
||||
return new StringReader(extraProtoDefinition);
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"The ProtoDefinition '{path}' was not found.");
|
||||
}
|
||||
|
||||
private static bool TryGetValidPath(string path, [NotNullWhen(true)] out string? validPath)
|
||||
{
|
||||
if (!path.Any(c => Path.GetInvalidPathChars().Contains(c)))
|
||||
{
|
||||
validPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
validPath = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
37
src/WireMock.Net.ProtoBuf/WireMock.Net.ProtoBuf.csproj
Normal file
37
src/WireMock.Net.ProtoBuf/WireMock.Net.ProtoBuf.csproj
Normal file
@@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.10.1-preview-00</Version>
|
||||
<Description>ProtoBuf and gRPC support for WireMock.Net</Description>
|
||||
<AssemblyTitle>WireMock.Net.ProtoBuf</AssemblyTitle>
|
||||
<Authors>Stef Heyenrath</Authors>
|
||||
<TargetFrameworks>netstandard2.1;net462;net6.0;net8.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>wiremock;matchers;matcher;protobuf;grpc</PackageTags>
|
||||
<RootNamespace>WireMock</RootNamespace>
|
||||
<ProjectGuid>{B47413AA-55D3-49A7-896A-17ADBFF72407}</ProjectGuid>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<!--<DelaySign>true</DelaySign>-->
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ProtoBufJsonConverter" Version="0.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user