Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ProtoDefinitionHelper.FromDirectory #1263

Open
wants to merge 10 commits into
base: 1.7.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions WireMock.Net Solution.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Levenstein/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=openapi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=opentelemetry/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pacticipant/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=protobuf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
Expand Down
31 changes: 31 additions & 0 deletions src/WireMock.Net/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Globalization;

namespace WireMock.Extensions;

internal static class StringExtensions
{
// See https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/
public static string GetDeterministicHashCodeAsString(this string str)
{
unchecked
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;

for (int i = 0; i < str.Length; i += 2)
{

Check warning on line 16 in src/WireMock.Net/Extensions/StringExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Extensions/StringExtensions.cs#L7-L16

Added lines #L7 - L16 were not covered by tests
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1)
{
break;

Check warning on line 20 in src/WireMock.Net/Extensions/StringExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Extensions/StringExtensions.cs#L20

Added line #L20 was not covered by tests
}

hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}

int result = hash1 + hash2 * 1566083941;

Check warning on line 27 in src/WireMock.Net/Extensions/StringExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Extensions/StringExtensions.cs#L24-L27

Added lines #L24 - L27 were not covered by tests
return result.ToString(CultureInfo.InvariantCulture).Replace('-', '_');
}
}
}

Check warning on line 31 in src/WireMock.Net/Extensions/StringExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Extensions/StringExtensions.cs#L31

Added line #L31 was not covered by tests
39 changes: 39 additions & 0 deletions src/WireMock.Net/Models/ProtoDefinitionData.cs
Original file line number Diff line number Diff line change
@@ -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.

Check warning on line 10 in src/WireMock.Net/Models/ProtoDefinitionData.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Models/ProtoDefinitionData.cs#L9-L10

Added lines #L9 - L10 were not covered by tests
/// </summary>
public class ProtoDefinitionData
{
private readonly IDictionary<string, string> _filenameMappedToProtoDefinition;

Check warning on line 14 in src/WireMock.Net/Models/ProtoDefinitionData.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Models/ProtoDefinitionData.cs#L14

Added line #L14 was not covered by tests

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;
}
}
20 changes: 17 additions & 3 deletions src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright © WireMock.Net

using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
Expand All @@ -12,7 +13,7 @@
/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string protoDefinition, string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithBodyAsProtoBuf([ protoDefinition ], messageType, matchBehaviour);
return WithBodyAsProtoBuf([protoDefinition], messageType, matchBehaviour);
}

/// <inheritdoc />
Expand All @@ -36,12 +37,25 @@
/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string messageType, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => Mapping.ProtoDefinition!.Value, messageType));
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, ProtoDefinitionFunc(), messageType));
}

/// <inheritdoc />
public IRequestBuilder WithBodyAsProtoBuf(string messageType, IObjectMatcher matcher, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, () => Mapping.ProtoDefinition!.Value, messageType, matcher));
return Add(new RequestMessageProtoBufMatcher(matchBehaviour, ProtoDefinitionFunc(), messageType, matcher));
}

private Func<IdOrTexts> ProtoDefinitionFunc()

Check warning on line 49 in src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs#L48-L49

Added lines #L48 - L49 were not covered by tests
{
return () =>
{
if (Mapping.ProtoDefinition == null)
{

Check warning on line 54 in src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs#L54

Added line #L54 was not covered by tests
throw new InvalidOperationException($"No ProtoDefinition defined on mapping '{Mapping.Guid}'. Please use the WireMockServerSettings to define ProtoDefinitions.");
}

return Mapping.ProtoDefinition.Value;
};
}
}
5 changes: 4 additions & 1 deletion src/WireMock.Net/Server/RespondWithAProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,12 @@
{
Guard.NotNull(protoDefinitionOrId);

#if PROTOBUF
ProtoDefinition = ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitionOrId);

return this;
#else

Check warning on line 362 in src/WireMock.Net/Server/RespondWithAProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Server/RespondWithAProvider.cs#L362

Added line #L362 was not covered by tests
throw new NotSupportedException("The WithProtoDefinition method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#endif
}

/// <inheritdoc />
Expand Down
47 changes: 46 additions & 1 deletion src/WireMock.Net/Util/PathUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal static class PathUtils
/// <param name="path">The path to remove the loading DirectorySeparatorChars</param>
public static string? RemoveLeadingDirectorySeparators(string? path)
{
return path?.TrimStart(new[] { Path.DirectorySeparatorChar });
return path?.TrimStart(Path.DirectorySeparatorChar);
}

/// <summary>
Expand All @@ -38,4 +38,49 @@ public static string Combine(string root, string? path)
var result = RemoveLeadingDirectorySeparators(path);
return result == null ? root : Path.Combine(root, result);
}

/// <summary>
/// Returns a relative path from one path to another.
/// </summary>
/// <param name="relativeTo">The source path the result should be relative to. This path is always considered to be a directory..</param>
/// <param name="path">The destination path.</param>
/// <returns>The relative path, or path if the paths don't share the same root.</returns>
public static string GetRelativePath(string relativeTo, string path)
{
#if NETCOREAPP3_1 || NET5_0_OR_GREATER || NETSTANDARD2_1
return Path.GetRelativePath(relativeTo, path);
#else
Guard.NotNull(relativeTo);
Guard.NotNull(path);

static string AppendDirectorySeparatorChar(string path)
{
// Append a slash only if the path is a directory and does not have a slash.
if (!Path.HasExtension(path) && !path.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
return path + Path.DirectorySeparatorChar;
}

return path;
}

var fromUri = new System.Uri(AppendDirectorySeparatorChar(relativeTo));
var toUri = new System.Uri(AppendDirectorySeparatorChar(path));

if (fromUri.Scheme != toUri.Scheme)
{
return path;
}

var relativeUri = fromUri.MakeRelativeUri(toUri);
var relativePath = System.Uri.UnescapeDataString(relativeUri.ToString());

if (string.Equals(toUri.Scheme, "FILE", System.StringComparison.OrdinalIgnoreCase))
{
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}

return relativePath;
#endif
}
}
80 changes: 77 additions & 3 deletions src/WireMock.Net/Util/ProtoDefinitionHelper.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,85 @@
// Copyright © WireMock.Net

#if PROTOBUF
using System.Collections.Generic;
using System.IO;
using System.Threading;

Check warning on line 6 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L6

Added line #L6 was not covered by tests
using System.Threading.Tasks;
using ProtoBufJsonConverter;
using ProtoBufJsonConverter.Models;
using Stef.Validation;

Check warning on line 10 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L8-L10

Added lines #L8 - L10 were not covered by tests
using WireMock.Models;
using WireMock.Settings;

namespace WireMock.Util;

internal static class ProtoDefinitionHelper
/// <summary>
/// Some helper methods for Proto Definitions.
/// </summary>
public static class ProtoDefinitionHelper

Check warning on line 19 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L16-L19

Added lines #L16 - L19 were not covered by tests
{
/// <summary>
/// Builds a dictionary of ProtoDefinitions from a directory.
/// - The key will be the filename without extension.

Check warning on line 23 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L21-L23

Added lines #L21 - L23 were not covered by tests
/// - 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>

Check warning on line 25 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L25

Added line #L25 was not covered by tests
/// <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);

Check warning on line 31 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L31

Added line #L31 was not covered by tests
var fileNameMappedToProtoDefinition = new Dictionary<string, string>();
var filePaths = Directory.EnumerateFiles(directory, "*.proto", SearchOption.AllDirectories);

Check warning on line 34 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L34

Added line #L34 was not covered by tests
foreach (var filePath in filePaths)
{
// Get the relative path to the directory (note that this will be OS specific).
var relativePath = PathUtils.GetRelativePath(directory, filePath);

// Make it a valid proto import path
var protoRelativePath = relativePath.Replace(Path.DirectorySeparatorChar, '/');

// Build comment and get content from file.

Check warning on line 43 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L42-L43

Added lines #L42 - L43 were not covered by tests
var comment = $"// {protoRelativePath}";
#if NETSTANDARD2_0

Check warning on line 45 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L45

Added line #L45 was not covered by tests
var content = File.ReadAllText(filePath);
#else

Check warning on line 47 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L47

Added line #L47 was not covered by tests
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);

Check warning on line 53 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L53

Added line #L53 was not covered by tests
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
{

Check warning on line 75 in src/WireMock.Net/Util/ProtoDefinitionHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/ProtoDefinitionHelper.cs#L74-L75

Added lines #L74 - L75 were not covered by tests
// Ignore
}
}

return new ProtoDefinitionData(fileNameMappedToProtoDefinition);
}

internal static IdOrTexts GetIdOrTexts(WireMockServerSettings settings, params string[] protoDefinitionOrId)
{
switch (protoDefinitionOrId.Length)
Expand All @@ -19,9 +92,10 @@
}

return new(null, protoDefinitionOrId);

default:
return new(null, protoDefinitionOrId);
}
}
}
}
#endif
28 changes: 18 additions & 10 deletions src/WireMock.Net/Util/WireMockProtoFileResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
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.
///

Check warning on line 16 in src/WireMock.Net/Util/WireMockProtoFileResolver.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/WireMockProtoFileResolver.cs#L16

Added line #L16 was not covered by tests
/// It assumes that:
/// - the first ProtoDefinition file is the main ProtoDefinition file.
/// - the first commented line of each extra ProtoDefinition file is the filename which is used in the import of the other ProtoDefinition file(s).
/// - The first commented line of each ProtoDefinition file is the filepath which is used in the import of the other ProtoDefinition file(s).

Check warning on line 18 in src/WireMock.Net/Util/WireMockProtoFileResolver.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/WireMockProtoFileResolver.cs#L18

Added line #L18 was not covered by tests
/// </summary>
internal class WireMockProtoFileResolver : IProtoFileResolver
{
private readonly Dictionary<string, string> _files = new();
private readonly Dictionary<string, string> _files = [];

public WireMockProtoFileResolver(IReadOnlyCollection<string> protoDefinitions)
{
Expand All @@ -27,12 +28,19 @@
return;
}

foreach (var extraProtoDefinition in protoDefinitions.Skip(1))
foreach (var extraProtoDefinition in protoDefinitions)
{
var firstNonEmptyLine = extraProtoDefinition.Split(['\r', '\n']).FirstOrDefault(l => !string.IsNullOrEmpty(l));
if (firstNonEmptyLine != null && TryGetValidFileName(firstNonEmptyLine.TrimStart(['/', ' ']), out var validFileName))
if (firstNonEmptyLine != null)
{
_files.Add(validFileName, extraProtoDefinition);
if (TryGetValidPath(firstNonEmptyLine.TrimStart(['/', ' ']), out var validPath))
{
_files.Add(validPath, extraProtoDefinition);
}
else
{
_files.Add(extraProtoDefinition.GetDeterministicHashCodeAsString(), extraProtoDefinition);

Check warning on line 42 in src/WireMock.Net/Util/WireMockProtoFileResolver.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/WireMockProtoFileResolver.cs#L40-L42

Added lines #L40 - L42 were not covered by tests
}
}
}
}
Expand All @@ -52,15 +60,15 @@
throw new FileNotFoundException($"The ProtoDefinition '{path}' was not found.");
}

private static bool TryGetValidFileName(string fileName, [NotNullWhen(true)] out string? validFileName)
private static bool TryGetValidPath(string path, [NotNullWhen(true)] out string? validPath)
{
if (!fileName.Any(c => Path.GetInvalidFileNameChars().Contains(c)))
if (!path.Any(c => Path.GetInvalidPathChars().Contains(c)))
{
validFileName = fileName;
validPath = path;
return true;
}

validFileName = null;
validPath = null;

Check warning on line 71 in src/WireMock.Net/Util/WireMockProtoFileResolver.cs

View check run for this annotation

Codecov / codecov/patch

src/WireMock.Net/Util/WireMockProtoFileResolver.cs#L71

Added line #L71 was not covered by tests
return false;
}
}
Expand Down
Loading
Loading