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

Manifest placeholder escape sequence support #282

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions src/Aspirate.Processors/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static IServiceCollection AddPlaceholderTransformation(this IServiceColle
services
.AddSingleton<IResourceExpressionProcessor, ResourceExpressionProcessor>()
.AddSingleton<IJsonExpressionProcessor, JsonExpressionProcessor>()
.AddSingleton<IJsonInterpolationUnescapeProcessor, JsonInterpolationUnescapeProcessor>()
.AddSingleton<IBindingProcessor, BindingProcessor>();


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ public static IBindingProcessor CreateDefaultExpressionProcessor() =>
public void ResetServicePort() =>
_servicePort = DefaultServicePort;

public string HandleBindingReplacement(JsonNode rootNode, IReadOnlyList<string> pathParts, string input, string jsonPath)
public string? ParseBinding(IReadOnlyList<string> pathParts, JsonNode? rootNode)
{
var resourceName = pathParts[0];
var bindingName = pathParts[2];
var bindingProperty = pathParts[3];

var replacement = ParseBinding(resourceName, bindingName, bindingProperty, rootNode);

return input.Replace($"{{{jsonPath}}}", replacement, StringComparison.OrdinalIgnoreCase);
return ParseBinding(resourceName, bindingName, bindingProperty, rootNode);
}

private static string? ParseBinding(string resourceName, string bindingName, string bindingProperty, JsonNode? rootNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ namespace Aspirate.Processors.Transformation.Bindings;
public interface IBindingProcessor
{
void ResetServicePort();
string HandleBindingReplacement(JsonNode rootNode, IReadOnlyList<string> pathParts, string input, string jsonPath);
string? ParseBinding(IReadOnlyList<string> pathParts, JsonNode? rootNode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Aspirate.Processors.Transformation.Json;

public interface IJsonInterpolationUnescapeProcessor
{
void UnescapeJsonExpression(JsonNode node);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text;
using Json.More;

namespace Aspirate.Processors.Transformation.Json;
Expand Down Expand Up @@ -26,9 +27,6 @@ public void ResolveJsonExpressions(JsonNode? jsonNode, JsonNode rootNode)
} while (_unresolvedExpressionPointers.Count > 0);
}

[GeneratedRegex(@"\{([\w\.-]+)\}")]
private static partial Regex PlaceholderPatternRegex();

public static IJsonExpressionProcessor CreateDefaultExpressionProcessor() =>
new JsonExpressionProcessor(BindingProcessor.CreateDefaultExpressionProcessor());

Expand Down Expand Up @@ -99,50 +97,81 @@ private void ReplaceWithResolvedExpression(JsonNode rootNode, JsonNode jsonValue
return;
}

var matches = PlaceholderPatternRegex().Matches(input);
for (var i = 0; i < matches.Count; i++)
{
var match = matches[i];
var jsonPath = match.Groups[1].Value;
var pathParts = jsonPath.Split('.');
if (pathParts.Length == 1)
{
input = input.Replace($"{{{jsonPath}}}", rootNode[pathParts[0]].ToString(), StringComparison.OrdinalIgnoreCase);
continue;
}

if (pathParts is [_, Literals.Bindings, ..])
{
input = bindingProcessor.HandleBindingReplacement(rootNode, pathParts, input, jsonPath);
continue;
}

var selectionPath = pathParts.AsJsonPath();
var path = JsonPath.Parse(selectionPath);
var result = path.Evaluate(rootNode);
var tokens = JsonInterpolation.Tokenize(input);
var transformedInput = new StringBuilder();

if (result.Matches.Count == 0)
foreach (var token in tokens)
{
if (token.IsText())
{
continue;
transformedInput.Append(token.Lexeme);
}

var value = result.Matches.FirstOrDefault()?.Value;

if (value is null)
else
{
continue;
var jsonPath = token.Lexeme;
var pathParts = jsonPath.Split('.');

void AppendPlaceholderTokenAsText()
{
transformedInput.Append('{');
transformedInput.Append(jsonPath);
transformedInput.Append('}');
}

if (pathParts.Length == 1)
{
var resolvedNode = rootNode[pathParts[0]];

if (resolvedNode != null)
{
transformedInput.Append(resolvedNode.ToString());
}
else
{
AppendPlaceholderTokenAsText();
}

continue;
}
else if (pathParts is [_, Literals.Bindings, ..])
{
transformedInput.Append(bindingProcessor.ParseBinding(pathParts, rootNode));
continue;
}

var selectionPath = pathParts.AsJsonPath();
var path = JsonPath.Parse(selectionPath);
var result = path.Evaluate(rootNode);

if (result.Matches.Count == 0)
{
AppendPlaceholderTokenAsText();
continue;
}

var value = result.Matches.FirstOrDefault()?.Value;

if (value is null)
{
AppendPlaceholderTokenAsText();
continue;
}

transformedInput.Append(value.ToString());
}

input = input.Replace($"{{{jsonPath}}}", value.ToString(), StringComparison.OrdinalIgnoreCase);
}

input = transformedInput.ToString();

var pointer = jsonValue.GetPointerFromRoot();
jsonValue.ReplaceWith(input);

// If input is different, retokenize and see if any placeholders are present.
if (!string.Equals(inputBefore, input, StringComparison.OrdinalIgnoreCase) &&
input.Contains('{', StringComparison.OrdinalIgnoreCase) && input.Contains('}', StringComparison.OrdinalIgnoreCase))
JsonInterpolation.Tokenize(input).Any(x => x.IsPlaceholder()))
{
_unresolvedExpressionPointers.Add(pointer);
}

jsonValue.ReplaceWith(input);
}
}
Loading