From 01a18e9770c62b441658110cef8febea069d2dd3 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Wed, 17 Apr 2024 10:29:27 +0100
Subject: [PATCH 01/28] Remove JmesPath external dependency. Update Idempotency
to use new internal JmesPath implementation. Add internal JmesPath
implementation and tests.
---
libraries/AWS.Lambda.Powertools.sln | 30 +
.../AWS.Lambda.Powertools.Idempotency.csproj | 2 +-
.../Persistence/BasePersistenceStore.cs | 20 +-
.../Serialization/JsonFunction.cs | 42 -
.../AWS.Lambda.Powertools.JMESPath.csproj | 7 +
.../BinaryOperator.cs | 295 +++
.../Expression.cs | 748 +++++++
.../Function.cs | 1492 ++++++++++++++
.../InternalsVisibleTo.cs | 18 +
.../JmesPathParser.cs | 1749 +++++++++++++++++
.../JsonTransformer.cs | 145 ++
.../Operator.cs | 74 +
.../AWS.Lambda.Powertools.JMESPath/README.md | 1 +
.../AWS.Lambda.Powertools.JMESPath/Slice.cs | 53 +
.../AWS.Lambda.Powertools.JMESPath/Token.cs | 245 +++
.../UnaryOperator.cs | 75 +
.../Utilities/JsonDocumentBuilder.cs | 305 +++
.../Utilities/JsonElementComparer.cs | 159 ++
.../Utilities/JsonElementEqualityComparer.cs | 183 ++
.../Utilities/JsonFlattener.cs | 503 +++++
.../Utilities/JsonMergePatch.cs | 213 ++
.../Utilities/JsonPatch.cs | 413 ++++
.../Utilities/JsonPointer.cs | 576 ++++++
.../Utilities/JsonPointerExtensions.cs | 343 ++++
.../AWS.Lambda.Powertools.JMESPath/Value.cs | 816 ++++++++
.../ValueComparer.cs | 154 ++
.../ValueEqualityComparer.cs | 129 ++
libraries/src/Directory.Packages.props | 1 -
.../Persistence/BasePersistenceStoreTests.cs | 12 +-
...WS.Lambda.Powertools.JMESPath.Tests.csproj | 94 +
.../GlobalUsings.cs | 1 +
.../JmesPathTests.cs | 113 ++
.../test_files/apigw_event.json | 76 +
.../test_files/apigw_event_2.json | 54 +
.../test_files/basic.json | 96 +
.../test_files/benchmarks.json | 138 ++
.../test_files/boolean.json | 275 +++
.../test_files/current.json | 25 +
.../test_files/escape.json | 46 +
.../test_files/example.json | 50 +
.../test_files/filters.json | 468 +++++
.../test_files/functions.json | 829 ++++++++
.../test_files/identifiers.json | 1377 +++++++++++++
.../test_files/indices.json | 346 ++++
.../test_files/literal.json | 200 ++
.../test_files/multiselect.json | 398 ++++
.../test_files/pipe.json | 131 ++
.../test_files/slice.json | 187 ++
.../test_files/syntax.json | 678 +++++++
.../test_files/test.json | 19 +
.../test_files/unicode.json | 38 +
.../test_files/wildcard.json | 460 +++++
52 files changed, 14843 insertions(+), 59 deletions(-)
delete mode 100644 libraries/src/AWS.Lambda.Powertools.Idempotency/Serialization/JsonFunction.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event_2.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/basic.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/benchmarks.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/boolean.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/current.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/escape.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/example.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/filters.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/functions.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/identifiers.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/indices.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/literal.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/multiselect.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/pipe.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/slice.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/syntax.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/test.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/unicode.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/wildcard.json
diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln
index 62c1cec1..27ad89f2 100644
--- a/libraries/AWS.Lambda.Powertools.sln
+++ b/libraries/AWS.Lambda.Powertools.sln
@@ -35,6 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Param
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Parameters.Tests", "tests\AWS.Lambda.Powertools.Parameters.Tests\AWS.Lambda.Powertools.Parameters.Tests.csproj", "{386A9769-59BF-4BE3-99D4-A9603E300729}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.JMESPath", "src\AWS.Lambda.Powertools.JMESPath\AWS.Lambda.Powertools.JMESPath.csproj", "{4F5020DB-9856-4A6F-B2CB-2C213FD749BC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.JMESPath.Tests", "tests\AWS.Lambda.Powertools.JMESPath.Tests\AWS.Lambda.Powertools.JMESPath.Tests.csproj", "{B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -216,6 +220,30 @@ Global
{386A9769-59BF-4BE3-99D4-A9603E300729}.Release|x64.Build.0 = Release|Any CPU
{386A9769-59BF-4BE3-99D4-A9603E300729}.Release|x86.ActiveCfg = Release|Any CPU
{386A9769-59BF-4BE3-99D4-A9603E300729}.Release|x86.Build.0 = Release|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Debug|x64.Build.0 = Debug|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Debug|x86.Build.0 = Debug|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Release|x64.ActiveCfg = Release|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Release|x64.Build.0 = Release|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Release|x86.ActiveCfg = Release|Any CPU
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC}.Release|x86.Build.0 = Release|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Debug|x64.Build.0 = Debug|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Debug|x86.Build.0 = Debug|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Release|x64.ActiveCfg = Release|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Release|x64.Build.0 = Release|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Release|x86.ActiveCfg = Release|Any CPU
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
@@ -233,5 +261,7 @@ Global
{F8B4100F-4014-4A1E-8130-D281453B79ED} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5}
{12B940EF-A5D3-459D-BD36-A603834D1F7D} = {1CFF5568-8486-475F-81F6-06105C437528}
{3E1D77BD-70AF-4767-B00A-4A321D5AB2C3} = {1CFF5568-8486-475F-81F6-06105C437528}
+ {4F5020DB-9856-4A6F-B2CB-2C213FD749BC} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5}
+ {B1A91FDB-A843-4CE5-A1AC-2ED48A158AA1} = {1CFF5568-8486-475F-81F6-06105C437528}
EndGlobalSection
EndGlobal
diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/AWS.Lambda.Powertools.Idempotency.csproj b/libraries/src/AWS.Lambda.Powertools.Idempotency/AWS.Lambda.Powertools.Idempotency.csproj
index d12fe902..50a99381 100644
--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/AWS.Lambda.Powertools.Idempotency.csproj
+++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/AWS.Lambda.Powertools.Idempotency.csproj
@@ -14,8 +14,8 @@
-
+
diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs
index 27ae02f6..75191c5a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs
@@ -21,8 +21,7 @@
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Idempotency.Exceptions;
using AWS.Lambda.Powertools.Idempotency.Internal;
-using AWS.Lambda.Powertools.Idempotency.Serialization;
-using DevLab.JmesPath;
+using AWS.Lambda.Powertools.JMESPath;
namespace AWS.Lambda.Powertools.Idempotency.Persistence;
@@ -262,15 +261,11 @@ private string GetHashedPayload(JsonDocument data)
return "";
}
- var jmes = new JmesPath();
- jmes.FunctionRepository.Register();
- var result = jmes.Transform(data.RootElement.ToString(), _idempotencyOptions.PayloadValidationJmesPath);
- var node = JsonDocument.Parse(result);
- return GenerateHash(node.RootElement);
+ var transformer = JsonTransformer.Parse(_idempotencyOptions.PayloadValidationJmesPath);
+ JsonDocument result = transformer.Transform(data.RootElement);
+ return GenerateHash(result.RootElement);
}
-
-
///
/// Calculate unix timestamp of expiry date for idempotency record
///
@@ -293,10 +288,9 @@ private string GetHashedIdempotencyKey(JsonDocument data)
var eventKeyJmesPath = _idempotencyOptions.EventKeyJmesPath;
if (eventKeyJmesPath != null)
{
- var jmes = new JmesPath();
- jmes.FunctionRepository.Register();
- var result = jmes.Transform(node.ToString(), eventKeyJmesPath);
- node = JsonDocument.Parse(result).RootElement;
+ var transformer = JsonTransformer.Parse(eventKeyJmesPath);
+ JsonDocument result = transformer.Transform(node);
+ node = result.RootElement;
}
if (IsMissingIdempotencyKey(node))
diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Serialization/JsonFunction.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Serialization/JsonFunction.cs
deleted file mode 100644
index 94ed3f25..00000000
--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Serialization/JsonFunction.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-using System.Diagnostics;
-using DevLab.JmesPath.Functions;
-using Newtonsoft.Json.Linq;
-
-namespace AWS.Lambda.Powertools.Idempotency.Serialization;
-
-///
-/// Creates JMESPath function powertools_json() to treat the payload as a JSON object rather than a string.
-///
-public class JsonFunction : JmesPathFunction
-{
- ///
- public JsonFunction()
- : base("powertools_json", 1)
- {
- }
-
- ///
- public override JToken Execute(params JmesPathFunctionArgument[] args)
- {
- Debug.Assert(args.Length == 1);
- Debug.Assert(args[0].IsToken);
- var argument = args[0];
- var token = argument.Token;
- return JToken.Parse(token.ToString());
- }
-}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj b/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj
new file mode 100644
index 00000000..891af5d4
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
new file mode 100644
index 00000000..f2c911b0
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
@@ -0,0 +1,295 @@
+using System;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal interface IBinaryOperator
+ {
+ int PrecedenceLevel {get;}
+ bool IsRightAssociative {get;}
+ bool TryEvaluate(IValue lhs, IValue rhs, out IValue result);
+ };
+
+ internal abstract class BinaryOperator : IBinaryOperator
+ {
+ internal BinaryOperator(Operator oper)
+ {
+ PrecedenceLevel = OperatorTable.PrecedenceLevel(oper);
+ }
+
+ public int PrecedenceLevel {get;}
+
+ public bool IsRightAssociative => false;
+
+ public abstract bool TryEvaluate(IValue lhs, IValue rhs, out IValue result);
+ };
+
+ internal sealed class OrOperator : BinaryOperator
+ {
+ internal static OrOperator Instance { get; } = new();
+
+ private OrOperator()
+ : base(Operator.Or)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ if (lhs.Type == JmesPathType.Null && rhs.Type == JmesPathType.Null)
+ {
+ result = lhs;
+ return true;
+ }
+ result = Expression.IsTrue(lhs) ? lhs : rhs;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "OrOperator";
+ }
+ };
+
+ internal sealed class AndOperator : BinaryOperator
+ {
+ internal static AndOperator Instance { get; } = new();
+
+ private AndOperator()
+ : base(Operator.And)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ result = Expression.IsTrue(lhs) ? rhs : lhs;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "AndOperator";
+ }
+ };
+
+ internal sealed class EqOperator : BinaryOperator
+ {
+ internal static EqOperator Instance { get; } = new();
+
+ private EqOperator()
+ : base(Operator.Eq)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ var comparer = ValueEqualityComparer.Instance;
+ result = comparer.Equals(lhs, rhs) ? JsonConstants.True : JsonConstants.False;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "EqOperator";
+ }
+ };
+
+ internal sealed class NeOperator : BinaryOperator
+ {
+ internal static NeOperator Instance { get; } = new();
+
+ private NeOperator()
+ : base(Operator.Ne)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ if (!EqOperator.Instance.TryEvaluate(lhs, rhs, out var value))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ result = Expression.IsFalse(value) ? JsonConstants.True : JsonConstants.False;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "NeOperator";
+ }
+ };
+
+ internal sealed class LtOperator : BinaryOperator
+ {
+ internal static LtOperator Instance { get; } = new();
+
+ private LtOperator()
+ : base(Operator.Lt)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 < dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 < val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ }
+ else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
+ {
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) < 0 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "LtOperator";
+ }
+ };
+
+ internal sealed class LteOperator : BinaryOperator
+ {
+ internal static LteOperator Instance { get; } = new();
+
+ private LteOperator()
+ : base(Operator.Lte)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 <= dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 <= val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ }
+ else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
+ {
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) <= 0 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ return true;
+ }
+
+
+ public override string ToString()
+ {
+ return "LteOperator";
+ }
+ };
+
+ internal sealed class GtOperator : BinaryOperator
+ {
+ internal static GtOperator Instance { get; } = new();
+
+ private GtOperator()
+ : base(Operator.Gt)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 > dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 > val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ }
+ else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
+ {
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) > 0 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "GtOperator";
+ }
+ };
+
+ internal sealed class GteOperator : BinaryOperator
+ {
+ internal static GteOperator Instance { get; } = new();
+
+ private GteOperator()
+ : base(Operator.Gte)
+ {
+ }
+
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ {
+ if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 >= dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 >= val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ }
+ else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
+ {
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) >= 0 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "GteOperator";
+ }
+ };
+}
+
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
new file mode 100644
index 00000000..7d993671
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
@@ -0,0 +1,748 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal static class JsonConstants
+ {
+ static JsonConstants()
+ {
+ True = new TrueValue();
+ False = new FalseValue();
+ Null = new NullValue();
+ }
+
+ internal static IValue True {get;}
+ internal static IValue False {get;}
+ internal static IValue Null {get;}
+ }
+
+ internal interface IExpression
+ {
+ bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value);
+
+ int PrecedenceLevel {get;}
+
+ bool IsProjection {get;}
+
+ bool IsRightAssociative {get;}
+
+ void AddExpression(IExpression expr);
+ }
+
+ // BaseExpression
+ internal abstract class BaseExpression : IExpression
+ {
+ public int PrecedenceLevel {get;}
+
+ public bool IsRightAssociative {get;}
+
+ public bool IsProjection {get;}
+
+ internal BaseExpression(Operator oper, bool isProjection)
+ {
+ PrecedenceLevel = OperatorTable.PrecedenceLevel(oper);
+ IsRightAssociative = OperatorTable.IsRightAssociative(oper);
+ IsProjection = isProjection;
+ }
+
+ public abstract bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value);
+
+ public virtual void AddExpression(IExpression expressions)
+ {
+ }
+
+ public override string ToString()
+ {
+ return "ToString not implemented";
+ }
+ }
+
+ internal sealed class IdentifierSelector : BaseExpression
+ {
+ private readonly string _identifier;
+
+ internal IdentifierSelector(string name)
+ : base(Operator.Default, false)
+ {
+ _identifier = name;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type == JmesPathType.Object && current.TryGetProperty(_identifier, out value))
+ {
+ return true;
+ }
+
+ value = JsonConstants.Null;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return $"IdentifierSelector {_identifier}";
+ }
+ };
+
+ internal sealed class CurrentNode : BaseExpression
+ {
+ internal CurrentNode()
+ : base(Operator.Default, false)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ value = current;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "CurrentNode";
+ }
+ };
+
+ internal sealed class IndexSelector : BaseExpression
+ {
+ private readonly int _index;
+ internal IndexSelector(int index)
+ : base(Operator.Default, false)
+ {
+ _index = index;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type != JmesPathType.Array)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+ var slen = current.GetArrayLength();
+ if (_index >= 0 && _index < slen)
+ {
+ value = current[_index];
+ }
+ else if ((slen + _index) >= 0 && (slen+_index) < slen)
+ {
+ var index = slen + _index;
+ value = current[index];
+ }
+ else
+ {
+ value = JsonConstants.Null;
+ }
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return $"Index Selector {_index}";
+ }
+ };
+
+ internal abstract class Projection : BaseExpression
+ {
+ private readonly List _expressions;
+
+ internal Projection(Operator oper)
+ : base(oper, true)
+ {
+ _expressions = new List();
+ }
+
+ public override void AddExpression(IExpression expr)
+ {
+ if (_expressions.Count != 0 && _expressions[_expressions.Count-1].IsProjection &&
+ (expr.PrecedenceLevel > _expressions[_expressions.Count-1].PrecedenceLevel ||
+ (expr.PrecedenceLevel == _expressions[_expressions.Count-1].PrecedenceLevel && expr.IsRightAssociative)))
+ {
+ _expressions[_expressions.Count-1].AddExpression(expr);
+ }
+ else
+ {
+ _expressions.Add(expr);
+ }
+ }
+ internal bool TryApplyExpressions(DynamicResources resources, IValue current, out IValue value)
+ {
+ value = current;
+ foreach (var expression in _expressions)
+ {
+ if (!expression.TryEvaluate(resources, value, out value))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+
+ internal sealed class ObjectProjection : Projection
+ {
+ internal ObjectProjection()
+ : base(Operator.Projection)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type != JmesPathType.Object)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+
+ var result = new List();
+ value = new ArrayValue(result);
+ foreach (var item in current.EnumerateObject())
+ {
+ if (item.Value.Type == JmesPathType.Null) continue;
+ if (!TryApplyExpressions(resources, item.Value, out var val))
+ {
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "ObjectProjection";
+ }
+ };
+
+ internal sealed class ListProjection : Projection
+ {
+ internal ListProjection()
+ : base(Operator.Projection)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type != JmesPathType.Array)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+
+ var result = new List();
+ foreach (var item in current.EnumerateArray())
+ {
+ if (item.Type != JmesPathType.Null)
+ {
+ if (!TryApplyExpressions(resources, item, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ }
+ value = new ArrayValue(result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "ListProjection";
+ }
+ };
+
+ internal sealed class FlattenProjection : Projection
+ {
+ internal FlattenProjection()
+ : base(Operator.FlattenProjection)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type != JmesPathType.Array)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+
+ var result = new List();
+ foreach (var item in current.EnumerateArray())
+ {
+ if (item.Type == JmesPathType.Array)
+ {
+ foreach (var elem in item.EnumerateArray())
+ {
+ if (elem.Type != JmesPathType.Null)
+ {
+ if (!TryApplyExpressions(resources, elem, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (item.Type != JmesPathType.Null)
+ {
+ if (!TryApplyExpressions(resources, item, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ }
+ }
+
+ value = new ArrayValue(result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "FlattenProjection";
+ }
+ };
+
+ internal sealed class SliceProjection : Projection
+ {
+ private readonly Slice _slice;
+
+ internal SliceProjection(Slice s)
+ : base(Operator.Projection)
+ {
+ _slice = s;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type != JmesPathType.Array)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+
+ var start = _slice.GetStart(current.GetArrayLength());
+ var end = _slice.GetStop(current.GetArrayLength());
+ var step = _slice.Step;
+
+ if (step == 0)
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+
+ var result = new List();
+ if (step > 0)
+ {
+ if (start < 0)
+ {
+ start = 0;
+ }
+ if (end > current.GetArrayLength())
+ {
+ end = current.GetArrayLength();
+ }
+ for (var i = start; i < end; i += step)
+ {
+ if (!TryApplyExpressions(resources, current[i], out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ }
+ else
+ {
+ if (start >= current.GetArrayLength())
+ {
+ start = current.GetArrayLength() - 1;
+ }
+ if (end < -1)
+ {
+ end = -1;
+ }
+ for (var i = start; i > end; i += step)
+ {
+ if (!TryApplyExpressions(resources, current[i], out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ }
+
+ value = new ArrayValue(result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "SliceProjection";
+ }
+ };
+
+ internal sealed class FilterExpression : Projection
+ {
+ private readonly Expression _expr;
+
+ internal FilterExpression(Expression expr)
+ : base(Operator.Projection)
+ {
+ _expr = expr;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type != JmesPathType.Array)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+ var result = new List();
+
+ foreach (var item in current.EnumerateArray())
+ {
+ if (!_expr.TryEvaluate(resources, item, out var test))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (Expression.IsTrue(test))
+ {
+ if (!TryApplyExpressions(resources, item, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
+ }
+ value = new ArrayValue(result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "FilterExpression";
+ }
+ };
+
+ internal sealed class MultiSelectList : BaseExpression
+ {
+ private readonly IList _expressions;
+
+ internal MultiSelectList(IList expressions)
+ : base(Operator.Default, false)
+ {
+ _expressions = expressions;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type == JmesPathType.Null)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+ var result = new List();
+
+ foreach (var expr in _expressions)
+ {
+ if (!expr.TryEvaluate(resources, current, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ result.Add(val);
+ }
+ value = new ArrayValue(result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "MultiSelectList";
+ }
+ };
+
+ internal struct KeyExpressionPair
+ {
+ internal string Key {get;}
+ internal Expression Expression {get;}
+
+ internal KeyExpressionPair(string key, Expression expression)
+ {
+ Key = key;
+ Expression = expression;
+ }
+ };
+
+ internal sealed class MultiSelectHash : BaseExpression
+ {
+ private readonly IList _keyExprPairs;
+
+ internal MultiSelectHash(IList keyExprPairs)
+ : base(Operator.Default, false)
+ {
+ _keyExprPairs = keyExprPairs;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (current.Type == JmesPathType.Null)
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+ var result = new Dictionary();
+ foreach (var item in _keyExprPairs)
+ {
+ if (!item.Expression.TryEvaluate(resources, current, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ result.Add(item.Key, val);
+ }
+
+ value = new ObjectValue(result);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "MultiSelectHash";
+ }
+ }
+
+ internal sealed class FunctionExpression : BaseExpression
+ {
+ private readonly Expression _expr;
+
+ internal FunctionExpression(Expression expr)
+ : base(Operator.Default, false)
+ {
+ _expr = expr;
+ }
+
+ public override bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue value)
+ {
+ if (!_expr.TryEvaluate(resources, current, out var val))
+ {
+ value = JsonConstants.Null;
+ return true;
+ }
+ value = val;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "FunctionExpression";
+ }
+ }
+
+ internal class Expression
+ {
+ private readonly Token[] _tokens;
+
+ internal Expression(Token[] tokens)
+ {
+ _tokens = tokens;
+ }
+
+ public bool TryEvaluate(DynamicResources resources,
+ IValue current,
+ out IValue result)
+ {
+ var stack = new Stack();
+ IList argStack = new List();
+
+ var rootPtr = current;
+
+ for (var i = _tokens.Length-1; i >= 0; --i)
+ {
+ var token = _tokens[i];
+ switch (token.Type)
+ {
+ case TokenType.Literal:
+ {
+ stack.Push(token.GetValue());
+ break;
+ }
+ case TokenType.BeginExpressionType:
+ {
+ Debug.Assert(i>0);
+ token = _tokens[--i];
+ Debug.Assert(token.Type == TokenType.Expression);
+ Debug.Assert(stack.Count != 0);
+ stack.Pop();
+ stack.Push(new ExpressionValue(token.GetExpression()));
+ break;
+ }
+ case TokenType.Pipe:
+ {
+ Debug.Assert(stack.Count != 0);
+ rootPtr = stack.Peek();
+ break;
+ }
+ case TokenType.CurrentNode:
+ stack.Push(rootPtr);
+ break;
+ case TokenType.Expression:
+ {
+ Debug.Assert(stack.Count != 0);
+ var ptr = stack.Pop();
+ if (!token.GetExpression().TryEvaluate(resources, ptr, out var val))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ stack.Push(val);
+ break;
+ }
+ case TokenType.UnaryOperator:
+ {
+ Debug.Assert(stack.Count >= 1);
+ var rhs = stack.Pop();
+ if (!token.GetUnaryOperator().TryEvaluate(rhs, out var val))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ stack.Push(val);
+ break;
+ }
+ case TokenType.BinaryOperator:
+ {
+ Debug.Assert(stack.Count >= 2);
+ var rhs = stack.Pop();
+ var lhs = stack.Pop();
+ if (!token.GetBinaryOperator().TryEvaluate(lhs, rhs, out var val))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ stack.Push(val);
+ break;
+ }
+ case TokenType.Argument:
+ {
+ Debug.Assert(stack.Count != 0);
+ argStack.Add(stack.Pop());
+ break;
+ }
+ case TokenType.Function:
+ {
+ if (token.GetFunction().Arity != null && token.GetFunction().Arity != argStack.Count())
+ {
+ // airty error should never happen here
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (!token.GetFunction().TryEvaluate(resources, argStack, out var val))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ argStack.Clear();
+ stack.Push(val);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ Debug.Assert(stack.Count == 1);
+ result = stack.Peek();
+ return true;
+ }
+
+ internal static bool IsFalse(IValue val)
+ {
+ var comparer = ValueEqualityComparer.Instance;
+ switch (val.Type)
+ {
+ case JmesPathType.False:
+ return true;
+ case JmesPathType.Null:
+ return true;
+ case JmesPathType.Array:
+ return val.GetArrayLength() == 0;
+ case JmesPathType.Object:
+ return val.EnumerateObject().MoveNext() == false;
+ case JmesPathType.String:
+ return val.GetString().Length == 0;
+ case JmesPathType.Number:
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsTrue(IValue val)
+ {
+ return !IsFalse(val);
+ }
+ }
+}
+
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
new file mode 100644
index 00000000..2ee2d28b
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
@@ -0,0 +1,1492 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal sealed class SortByComparer : IComparer, System.Collections.IComparer
+ {
+ private readonly DynamicResources _resources;
+ private readonly IExpression _expr;
+
+ internal bool IsValid { get; set; } = true;
+
+ internal SortByComparer(DynamicResources resources,
+ IExpression expr)
+ {
+ _resources = resources;
+ _expr = expr;
+ }
+
+ public int Compare(IValue lhs, IValue rhs)
+ {
+ var comparer = ValueComparer.Instance;
+
+ if (!IsValid)
+ {
+ return 0;
+ }
+
+ if (!_expr.TryEvaluate(_resources, lhs, out var key1))
+ {
+ IsValid = false;
+ return 0;
+ }
+
+ var isNumber1 = key1.Type == JmesPathType.Number;
+ var isString1 = key1.Type == JmesPathType.String;
+ if (!(isNumber1 || isString1))
+ {
+ IsValid = false;
+ return 0;
+ }
+
+ if (!_expr.TryEvaluate(_resources, rhs, out var key2))
+ {
+ IsValid = false;
+ return 0;
+ }
+
+ var isNumber2 = key2.Type == JmesPathType.Number;
+ var isString2 = key2.Type == JmesPathType.String;
+ if (isNumber2 == isNumber1 && isString2 == isString1) return comparer.Compare(key1, key2);
+ IsValid = false;
+ return 0;
+ }
+
+ int System.Collections.IComparer.Compare(object x, object y)
+ {
+ return Compare((IValue)x, (IValue)y);
+ }
+ }
+
+ internal interface IFunction
+ {
+ int? Arity { get; }
+ bool TryEvaluate(DynamicResources resources, IList args, out IValue element);
+ };
+
+ internal abstract class BaseFunction : IFunction
+ {
+ internal BaseFunction(int? argCount)
+ {
+ Arity = argCount;
+ }
+
+ public int? Arity { get; }
+
+ public abstract bool TryEvaluate(DynamicResources resources, IList args, out IValue element);
+ };
+
+ internal sealed class AbsFunction : BaseFunction
+ {
+ internal AbsFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg = args[0];
+
+ if (arg.TryGetDecimal(out var decVal))
+ {
+ result = new DecimalValue(decVal >= 0 ? decVal : -decVal);
+ return true;
+ }
+
+ if (arg.TryGetDouble(out var dblVal))
+ {
+ result = new DecimalValue(dblVal >= 0 ? decVal : new decimal(-dblVal));
+ return true;
+ }
+
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return "abs";
+ }
+ };
+
+ internal sealed class AvgFunction : BaseFunction
+ {
+ internal AvgFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Array || arg0.GetArrayLength() == 0)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (!SumFunction.Instance.TryEvaluate(resources, args, out var sum))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (sum.TryGetDecimal(out var decVal))
+ {
+ result = new DecimalValue(decVal / arg0.GetArrayLength());
+ return true;
+ }
+
+ if (sum.TryGetDouble(out var dblVal))
+ {
+ result = new DoubleValue(dblVal / arg0.GetArrayLength());
+ return true;
+ }
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return "avg";
+ }
+ };
+
+ internal sealed class CeilFunction : BaseFunction
+ {
+ internal CeilFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var val = args[0];
+ if (val.Type != JmesPathType.Number)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (val.TryGetDecimal(out var decVal))
+ {
+ result = new DecimalValue(decimal.Ceiling(decVal));
+ return true;
+ }
+
+ if (val.TryGetDouble(out var dblVal))
+ {
+ result = new DoubleValue(Math.Ceiling(dblVal));
+ return true;
+ }
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return "ceil";
+ }
+ };
+
+ internal sealed class ContainsFunction : BaseFunction
+ {
+ internal ContainsFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ var arg1 = args[1];
+
+ var comparer = ValueEqualityComparer.Instance;
+
+ switch (arg0.Type)
+ {
+ case JmesPathType.Array:
+ foreach (var item in arg0.EnumerateArray())
+ {
+ if (comparer.Equals(item, arg1))
+ {
+ result = JsonConstants.True;
+ return true;
+ }
+ }
+
+ result = JsonConstants.False;
+ return true;
+ case JmesPathType.String:
+ {
+ if (arg1.Type != JmesPathType.String)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var s0 = arg0.GetString();
+ var s1 = arg1.GetString();
+ if (s0.Contains(s1))
+ {
+ result = JsonConstants.True;
+ return true;
+ }
+
+ result = JsonConstants.False;
+ return true;
+ }
+ default:
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return "contains";
+ }
+ };
+
+ internal sealed class EndsWithFunction : BaseFunction
+ {
+ internal EndsWithFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ var arg1 = args[1];
+ if (arg0.Type != JmesPathType.String
+ || arg1.Type != JmesPathType.String)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var s0 = arg0.GetString();
+ var s1 = arg1.GetString();
+
+ if (s0.EndsWith(s1))
+ {
+ result = JsonConstants.True;
+ }
+ else
+ {
+ result = JsonConstants.False;
+ }
+
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "ends_with";
+ }
+ };
+
+ internal sealed class FloorFunction : BaseFunction
+ {
+ internal FloorFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var val = args[0];
+ if (val.Type != JmesPathType.Number)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (val.TryGetDecimal(out var decVal))
+ {
+ result = new DecimalValue(decimal.Floor(decVal));
+ return true;
+ }
+
+ if (val.TryGetDouble(out var dblVal))
+ {
+ result = new DoubleValue(Math.Floor(dblVal));
+ return true;
+ }
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return "floor";
+ }
+ };
+
+ internal sealed class JoinFunction : BaseFunction
+ {
+ internal JoinFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ var arg1 = args[1];
+
+ if (!(arg0.Type == JmesPathType.String && args[1].Type == JmesPathType.Array))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var sep = arg0.GetString();
+ var buf = new StringBuilder();
+ foreach (var j in arg1.EnumerateArray())
+ {
+ if (j.Type != JmesPathType.String)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (buf.Length != 0)
+ {
+ buf.Append(sep);
+ }
+
+ var sv = j.GetString();
+ buf.Append(sv);
+ }
+
+ result = new StringValue(buf.ToString());
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "join";
+ }
+ }
+
+ internal sealed class KeysFunction : BaseFunction
+ {
+ internal KeysFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Object)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var values = new List();
+
+ foreach (var property in arg0.EnumerateObject())
+ {
+ values.Add(new StringValue(property.Name));
+ }
+
+ result = new ArrayValue(values);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "keys";
+ }
+ }
+
+ internal sealed class LengthFunction : BaseFunction
+ {
+ internal LengthFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+
+ switch (arg0.Type)
+ {
+ case JmesPathType.Object:
+ {
+ var count = 0;
+ foreach (var unused in arg0.EnumerateObject())
+ {
+ ++count;
+ }
+
+ result = new DecimalValue(new decimal(count));
+ return true;
+ }
+ case JmesPathType.Array:
+ result = new DecimalValue(new decimal(arg0.GetArrayLength()));
+ return true;
+ case JmesPathType.String:
+ {
+ var bytes = Encoding.UTF32.GetBytes(arg0.GetString().ToCharArray());
+ result = new DecimalValue(new decimal(bytes.Length / 4));
+ return true;
+ }
+ default:
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return "length";
+ }
+ };
+
+ internal sealed class MaxFunction : BaseFunction
+ {
+ internal MaxFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Array)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (arg0.GetArrayLength() == 0)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var isNumber = arg0[0].Type == JmesPathType.Number;
+ var isString = arg0[0].Type == JmesPathType.String;
+ if (!isNumber && !isString)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var greater = GtOperator.Instance;
+ var index = 0;
+ for (var i = 1; i < arg0.GetArrayLength(); ++i)
+ {
+ if (!(((arg0[i].Type == JmesPathType.Number) == isNumber) &&
+ (arg0[i].Type == JmesPathType.String) == isString))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (!greater.TryEvaluate(arg0[i], arg0[index], out var value))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (Expression.IsTrue(value))
+ {
+ index = i;
+ }
+ }
+
+ result = arg0[index];
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "max";
+ }
+ }
+
+ internal sealed class MaxByFunction : BaseFunction
+ {
+ internal MaxByFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var arg0 = args[0];
+ if (arg0.GetArrayLength() == 0)
+ {
+ result = JsonConstants.Null;
+ return true;
+ }
+
+ var expr = args[1].GetExpression();
+
+ if (!expr.TryEvaluate(resources, arg0[0], out var key1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var isNumber1 = key1.Type == JmesPathType.Number;
+ var isString1 = key1.Type == JmesPathType.String;
+ if (!(isNumber1 || isString1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var greater = GtOperator.Instance;
+ var index = 0;
+ for (var i = 1; i < arg0.GetArrayLength(); ++i)
+ {
+ if (!expr.TryEvaluate(resources, arg0[i], out var key2))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var isNumber2 = key2.Type == JmesPathType.Number;
+ var isString2 = key2.Type == JmesPathType.String;
+ if (!(isNumber2 == isNumber1 && isString2 == isString1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (!greater.TryEvaluate(key2, key1, out var value))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (value.Type == JmesPathType.True)
+ {
+ key1 = key2;
+ index = i;
+ }
+ }
+
+ result = arg0[index];
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "max_by";
+ }
+ }
+
+ internal sealed class MinFunction : BaseFunction
+ {
+ internal MinFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Array)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (arg0.GetArrayLength() == 0)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var isNumber = arg0[0].Type == JmesPathType.Number;
+ var isString = arg0[0].Type == JmesPathType.String;
+ if (!isNumber && !isString)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var less = LtOperator.Instance;
+ var index = 0;
+ for (var i = 1; i < arg0.GetArrayLength(); ++i)
+ {
+ if (!(((arg0[i].Type == JmesPathType.Number) == isNumber) &&
+ (arg0[i].Type == JmesPathType.String) == isString))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (!less.TryEvaluate(arg0[i], arg0[index], out var value))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (value.Type == JmesPathType.True)
+ {
+ index = i;
+ }
+ }
+
+ result = arg0[index];
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "min";
+ }
+ }
+
+ internal sealed class MergeFunction : BaseFunction
+ {
+ internal MergeFunction()
+ : base(null)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ if (!args.Any())
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Object)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (args.Count == 1)
+ {
+ result = arg0;
+ return true;
+ }
+
+ var dict = new Dictionary();
+ for (var i = 0; i < args.Count; ++i)
+ {
+ var argi = args[i];
+ if (argi.Type != JmesPathType.Object)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ foreach (var item in argi.EnumerateObject())
+ {
+ if (!dict.TryAdd(item.Name, item.Value))
+ {
+ dict.Remove(item.Name);
+ dict.Add(item.Name, item.Value);
+ }
+ }
+ }
+
+ result = new ObjectValue(dict);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "merge";
+ }
+ }
+
+ internal sealed class NotNullFunction : BaseFunction
+ {
+ internal NotNullFunction()
+ : base(null)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ foreach (var arg in args)
+ {
+ if (arg.Type == JmesPathType.Null) continue;
+ result = arg;
+ return true;
+ }
+
+ result = JsonConstants.Null;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "not_null";
+ }
+ }
+
+ internal sealed class ReverseFunction : BaseFunction
+ {
+ internal ReverseFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ switch (arg0.Type)
+ {
+ case JmesPathType.String:
+ {
+ result = new StringValue(string.Join("", GraphemeClusters(arg0.GetString()).Reverse().ToArray()));
+ return true;
+ }
+ case JmesPathType.Array:
+ {
+ var list = new List();
+ for (var i = arg0.GetArrayLength() - 1; i >= 0; --i)
+ {
+ list.Add(arg0[i]);
+ }
+
+ result = new ArrayValue(list);
+ return true;
+ }
+ default:
+ result = JsonConstants.Null;
+ return false;
+ }
+ }
+
+ private static IEnumerable GraphemeClusters(string s)
+ {
+ var enumerator = StringInfo.GetTextElementEnumerator(s);
+ while (enumerator.MoveNext())
+ {
+ yield return (string)enumerator.Current;
+ }
+ }
+
+ public override string ToString()
+ {
+ return "reverse";
+ }
+ }
+
+ internal sealed class MapFunction : BaseFunction
+ {
+ internal MapFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ if (!(args[0].Type == JmesPathType.Expression && args[1].Type == JmesPathType.Array))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var expr = args[0].GetExpression();
+ var arg0 = args[1];
+
+ var list = new List();
+
+ foreach (var item in arg0.EnumerateArray())
+ {
+ if (!expr.TryEvaluate(resources, item, out var val))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ list.Add(val);
+ }
+
+ result = new ArrayValue(list);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "map";
+ }
+ }
+
+ internal sealed class MinByFunction : BaseFunction
+ {
+ internal MinByFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var arg0 = args[0];
+ if (arg0.GetArrayLength() == 0)
+ {
+ result = JsonConstants.Null;
+ return true;
+ }
+
+ var expr = args[1].GetExpression();
+
+ if (!expr.TryEvaluate(resources, arg0[0], out var key1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var isNumber1 = key1.Type == JmesPathType.Number;
+ var isString1 = key1.Type == JmesPathType.String;
+ if (!(isNumber1 || isString1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var lessor = LtOperator.Instance;
+ var index = 0;
+ for (var i = 1; i < arg0.GetArrayLength(); ++i)
+ {
+ if (!expr.TryEvaluate(resources, arg0[i], out var key2))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var isNumber2 = key2.Type == JmesPathType.Number;
+ var isString2 = key2.Type == JmesPathType.String;
+ if (!(isNumber2 == isNumber1 && isString2 == isString1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (!lessor.TryEvaluate(key2, key1, out var value))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (value.Type == JmesPathType.True)
+ {
+ key1 = key2;
+ index = i;
+ }
+ }
+
+ result = arg0[index];
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "min_by";
+ }
+ }
+
+ internal sealed class SortFunction : BaseFunction
+ {
+ internal SortFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Array)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ if (arg0.GetArrayLength() <= 1)
+ {
+ result = arg0;
+ return true;
+ }
+
+ var isNumber1 = arg0[0].Type == JmesPathType.Number;
+ var isString1 = arg0[0].Type == JmesPathType.String;
+ if (!isNumber1 && !isString1)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var comparer = ValueComparer.Instance;
+
+ var list = new List();
+ foreach (var item in arg0.EnumerateArray())
+ {
+ var isNumber2 = item.Type == JmesPathType.Number;
+ var isString2 = item.Type == JmesPathType.String;
+ if (!(isNumber2 == isNumber1 && isString2 == isString1))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ list.Add(item);
+ }
+
+ list.Sort(comparer);
+ result = new ArrayValue(list);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "sort";
+ }
+ }
+
+ internal sealed class SortByFunction : BaseFunction
+ {
+ internal SortByFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var arg0 = args[0];
+ if (arg0.GetArrayLength() <= 1)
+ {
+ result = arg0;
+ return true;
+ }
+
+ var expr = args[1].GetExpression();
+
+ var list = new List();
+ foreach (var item in arg0.EnumerateArray())
+ {
+ list.Add(item);
+ }
+
+ var comparer = new SortByComparer(resources, expr);
+ list.Sort(comparer);
+ if (comparer.IsValid)
+ {
+ result = new ArrayValue(list);
+ return true;
+ }
+
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return "sort_by";
+ }
+ }
+
+ internal sealed class StartsWithFunction : BaseFunction
+ {
+ internal StartsWithFunction()
+ : base(2)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ var arg1 = args[1];
+ if (arg0.Type != JmesPathType.String
+ || arg1.Type != JmesPathType.String)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var s0 = arg0.GetString();
+ var s1 = arg1.GetString();
+ result = s0.StartsWith(s1) ? JsonConstants.True : JsonConstants.False;
+
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "starts_with";
+ }
+ }
+
+ internal sealed class SumFunction : BaseFunction
+ {
+ internal static SumFunction Instance { get; } = new();
+
+ internal SumFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Array)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ foreach (var item in arg0.EnumerateArray())
+ {
+ if (item.Type != JmesPathType.Number)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+ }
+
+ var success = true;
+ decimal decSum = 0;
+ foreach (var item in arg0.EnumerateArray())
+ {
+ if (!item.TryGetDecimal(out var dec))
+ {
+ success = false;
+ break;
+ }
+
+ decSum += dec;
+ }
+
+ if (success)
+ {
+ result = new DecimalValue(decSum);
+ return true;
+ }
+
+ double dblSum = 0;
+ foreach (var item in arg0.EnumerateArray())
+ {
+ if (!item.TryGetDouble(out var dbl))
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ dblSum += dbl;
+ }
+
+ result = new DoubleValue(dblSum);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "sum";
+ }
+ }
+
+ internal sealed class ToArrayFunction : BaseFunction
+ {
+ internal ToArrayFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type == JmesPathType.Array)
+ {
+ result = arg0;
+ return true;
+ }
+
+ var list = new List { arg0 };
+ result = new ArrayValue(list);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "to_array";
+ }
+ }
+
+ internal sealed class ToNumberFunction : BaseFunction
+ {
+ internal ToNumberFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args,
+ out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ switch (arg0.Type)
+ {
+ case JmesPathType.Number:
+ result = arg0;
+ return true;
+ case JmesPathType.String:
+ {
+ var s = arg0.GetString();
+ if (decimal.TryParse(s, out var dec))
+ {
+ result = new DecimalValue(dec);
+ return true;
+ }
+
+ if (double.TryParse(s, out var dbl))
+ {
+ result = new DoubleValue(dbl);
+ return true;
+ }
+ result = JsonConstants.Null;
+ return false;
+ }
+ default:
+ result = JsonConstants.Null;
+ return false;
+ }
+ }
+
+ public override string ToString()
+ {
+ return "to_number";
+ }
+ }
+
+ internal sealed class ToStringFunction : BaseFunction
+ {
+ internal ToStringFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ if (args[0].Type == JmesPathType.Expression)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var arg0 = args[0];
+ switch (arg0.Type)
+ {
+ case JmesPathType.String:
+ result = arg0;
+ return true;
+ case JmesPathType.Expression:
+ result = JsonConstants.Null;
+ return false;
+ default:
+ result = new StringValue(arg0.ToString());
+ return true;
+ }
+ }
+
+ public override string ToString()
+ {
+ return "to_string";
+ }
+ }
+
+ internal sealed class ValuesFunction : BaseFunction
+ {
+ internal ValuesFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+ if (arg0.Type != JmesPathType.Object)
+ {
+ result = JsonConstants.Null;
+ return false;
+ }
+
+ var list = new List();
+
+ foreach (var item in arg0.EnumerateObject())
+ {
+ list.Add(item.Value);
+ }
+
+ result = new ArrayValue(list);
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "values";
+ }
+ }
+
+ internal sealed class TypeFunction : BaseFunction
+ {
+ internal TypeFunction()
+ : base(1)
+ {
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var arg0 = args[0];
+
+ switch (arg0.Type)
+ {
+ case JmesPathType.Number:
+ result = new StringValue("number");
+ return true;
+ case JmesPathType.True:
+ case JmesPathType.False:
+ result = new StringValue("boolean");
+ return true;
+ case JmesPathType.String:
+ result = new StringValue("string");
+ return true;
+ case JmesPathType.Object:
+ result = new StringValue("object");
+ return true;
+ case JmesPathType.Array:
+ result = new StringValue("array");
+ return true;
+ case JmesPathType.Null:
+ result = new StringValue("null");
+ return true;
+ default:
+ result = JsonConstants.Null;
+ return false;
+ }
+ }
+
+ public override string ToString()
+ {
+ return "type";
+ }
+ }
+
+ internal sealed class JsonFunction : BaseFunction
+ {
+ ///
+ public JsonFunction()
+ : base(1)
+ {
+ }
+
+ public override string ToString()
+ {
+ return "powertools_json";
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+ result = args[0];
+
+ //result = new JsonElementValue(JsonNode.Parse(args[0].GetString()).Deserialize());
+ return true;
+ }
+ }
+
+ internal sealed class Base64Function : BaseFunction
+ {
+ ///
+ public Base64Function()
+ : base(1)
+ {
+ }
+
+ public override string ToString()
+ {
+ return "powertools_base64";
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+ var base64StringBytes = Convert.FromBase64String(args[0].GetString());
+ var doc = JsonDocument.Parse(base64StringBytes);
+ result = new JsonElementValue(doc.RootElement);
+ return true;
+ }
+ }
+
+ internal sealed class Base64GzipFunction : BaseFunction
+ {
+ ///
+ public Base64GzipFunction()
+ : base(1)
+ {
+ }
+
+ public override string ToString()
+ {
+ return "powertools_base64_gzip";
+ }
+
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ {
+ Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
+
+ var compressedBytes = Convert.FromBase64String(args[0].GetString());
+
+ using var compressedStream = new MemoryStream(compressedBytes);
+ using var decompressedStream = new MemoryStream();
+ using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
+ {
+ gzipStream.CopyTo(decompressedStream);
+ }
+
+ var doc = JsonDocument.Parse(Encoding.UTF8.GetString(decompressedStream.ToArray()));
+ result = new JsonElementValue(doc.RootElement);
+
+ return true;
+ }
+ }
+
+ internal sealed class BuiltInFunctions
+ {
+ internal static BuiltInFunctions Instance { get; } = new();
+
+ private readonly Dictionary _functions = new();
+
+ private BuiltInFunctions()
+ {
+ _functions.Add("abs", new AbsFunction());
+ _functions.Add("avg", new AvgFunction());
+ _functions.Add("ceil", new CeilFunction());
+ _functions.Add("contains", new ContainsFunction());
+ _functions.Add("ends_with", new EndsWithFunction());
+ _functions.Add("floor", new FloorFunction());
+ _functions.Add("join", new JoinFunction());
+ _functions.Add("keys", new KeysFunction());
+ _functions.Add("length", new LengthFunction());
+ _functions.Add("map", new MapFunction());
+ _functions.Add("max", new MaxFunction());
+ _functions.Add("max_by", new MaxByFunction());
+ _functions.Add("merge", new MergeFunction());
+ _functions.Add("min", new MinFunction());
+ _functions.Add("min_by", new MinByFunction());
+ _functions.Add("not_null", new NotNullFunction());
+ _functions.Add("reverse", new ReverseFunction());
+ _functions.Add("sort", new SortFunction());
+ _functions.Add("sort_by", new SortByFunction());
+ _functions.Add("starts_with", new StartsWithFunction());
+ _functions.Add("sum", new SumFunction());
+ _functions.Add("to_array", new ToArrayFunction());
+ _functions.Add("to_number", new ToNumberFunction());
+ _functions.Add("to_string", new ToStringFunction());
+ _functions.Add("type", new TypeFunction());
+ _functions.Add("values", new ValuesFunction());
+ _functions.Add("powertools_json", new JsonFunction());
+ _functions.Add("powertools_base64", new Base64Function());
+ _functions.Add("powertools_base64_gzip", new Base64GzipFunction());
+ }
+
+ internal bool TryGetFunction(string name, out IFunction func)
+ {
+ return _functions.TryGetValue(name, out func);
+ }
+ };
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
new file mode 100644
index 00000000..652795a2
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
@@ -0,0 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")]
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
new file mode 100644
index 00000000..d7e0efbc
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
@@ -0,0 +1,1749 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ ///
+ /// Defines a custom exception object that is thrown when JMESPath parsing fails.
+ ///
+
+ public sealed class JmesPathParseException : Exception
+ {
+ ///
+ /// The line in the JMESPath string where a parse error was detected.
+ ///
+ public int LineNumber {get;}
+
+ ///
+ /// The column in the JMESPath string where a parse error was detected.
+ ///
+ public int ColumnNumber {get;}
+
+ internal JmesPathParseException(string message, int line, int column)
+ : base(message)
+ {
+ LineNumber = line;
+ ColumnNumber = column;
+ }
+
+ ///
+ /// Returns an error message that describes the current exception.
+ ///
+ /// A string representation of the current exception.
+ public override string ToString ()
+ {
+ return $"{Message} at line {LineNumber} and column {ColumnNumber}";
+ }
+ };
+
+ internal enum JmesPathState
+ {
+ Start,
+ LhsExpression,
+ RhsExpression,
+ SubExpression,
+ ExpressionType,
+ ComparatorExpression,
+ FunctionExpression,
+ Argument,
+ ExpressionOrExpressionType,
+ QuotedString,
+ RawString,
+ RawStringEscapeChar,
+ QuotedStringEscapeChar,
+ EscapeU1,
+ EscapeU2,
+ EscapeU3,
+ EscapeU4,
+ EscapeExpectSurrogatePair1,
+ EscapeExpectSurrogatePair2,
+ EscapeU5,
+ EscapeU6,
+ EscapeU7,
+ EscapeU8,
+ Literal,
+ KeyExpr,
+ ValExpr,
+ IdentifierOrFunctionExpr,
+ UnquotedString,
+ KeyValExpr,
+ Number,
+ Digit,
+ IndexOrSliceExpression,
+ BracketSpecifier,
+ BracketSpecifierOrMultiSelectList,
+ Filter,
+ MultiSelectList,
+ MultiSelectHash,
+ RhsSliceExpressionStop,
+ RhsSliceExpressionStep,
+ ExpectRightBracket,
+ ExpectRightParen,
+ ExpectDot,
+ ExpectRightBrace,
+ ExpectColon,
+ ExpectMultiSelectList,
+ CmpLtOrLte,
+ CmpEq,
+ CmpGtOrGte,
+ CmpNe,
+ ExpectPipeOrOr,
+ ExpectAnd
+ }
+
+ internal ref struct JmesPathParser
+ {
+ private ReadOnlyMemory _source;
+ private ReadOnlySpan _span;
+ private int _index;
+ private int _column;
+ private int _line;
+ private Stack _stateStack;
+ private Stack_outputStack;
+ private Stack_operatorStack;
+
+ internal JmesPathParser(string input)
+ {
+ _source = input.AsMemory();
+ _span = input.AsSpan();
+ _index = 0;
+ _column = 1;
+ _line = 1;
+ _stateStack = new Stack();
+ _outputStack = new Stack();
+ _operatorStack = new Stack();
+ }
+
+ internal JsonTransformer Parse()
+ {
+ _stateStack.Clear();
+ _outputStack.Clear();
+ _operatorStack.Clear();
+ _index = 0;
+ _line = 1;
+ _column = 1;
+
+ var buffer = new StringBuilder();
+ int? sliceStart = null;
+ int? sliceStop = null;
+ var sliceStep = 1;
+ uint cp = 0;
+ uint cp2 = 0;
+
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Push(JmesPathState.Start);
+
+ while (_index < _span.Length)
+ {
+ switch (_stateStack.Peek())
+ {
+ default:
+ break;
+ case JmesPathState.Start:
+ {
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.RhsExpression);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ }
+ case JmesPathState.RhsExpression:
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '.':
+ ++_index;
+ ++_column;
+ _stateStack.Push(JmesPathState.SubExpression);
+ break;
+ case '|':
+ ++_index;
+ ++_column;
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.ExpectPipeOrOr);
+ break;
+ case '&':
+ ++_index;
+ ++_column;
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.ExpectAnd);
+ break;
+ case '<':
+ case '>':
+ case '=':
+ {
+ _stateStack.Push(JmesPathState.ComparatorExpression);
+ break;
+ }
+ case '!':
+ {
+ ++_index;
+ ++_column;
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.CmpNe);
+ break;
+ }
+ case ')':
+ {
+ _stateStack.Pop();
+ break;
+ }
+ case '[':
+ _stateStack.Push(JmesPathState.BracketSpecifier);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ if (_stateStack.Count > 1)
+ {
+ _stateStack.Pop();
+ }
+ else
+ {
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ break;
+ }
+ break;
+ case JmesPathState.ComparatorExpression:
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '<':
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.CmpLtOrLte);
+ break;
+ case '>':
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.CmpGtOrGte);
+ break;
+ case '=':
+ {
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.CmpEq);
+ break;
+ }
+ default:
+ if (_stateStack.Count > 1)
+ {
+ _stateStack.Pop();
+ }
+ else
+ {
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ break;
+ }
+ break;
+ case JmesPathState.LhsExpression:
+ {
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '\"':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ValExpr);
+ _stateStack.Push(JmesPathState.QuotedString);
+ ++_index;
+ ++_column;
+ break;
+ case '\'':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.RawString);
+ ++_index;
+ ++_column;
+ break;
+ case '`':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.Literal);
+ ++_index;
+ ++_column;
+ break;
+ case '{':
+ PushToken(new Token(TokenType.BeginMultiSelectHash));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.MultiSelectHash);
+ ++_index;
+ ++_column;
+ break;
+ case '*': // wildcard
+ PushToken(new Token(new ObjectProjection()));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ case '(':
+ {
+ ++_index;
+ ++_column;
+ PushToken(new Token(TokenType.LeftParen));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectRightParen);
+ _stateStack.Push(JmesPathState.RhsExpression);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ }
+ case '!':
+ {
+ ++_index;
+ ++_column;
+ PushToken(new Token(NotOperator.Instance));
+ break;
+ }
+ case '@':
+ ++_index;
+ ++_column;
+ PushToken(new Token(new CurrentNode()));
+ _stateStack.Pop();
+ break;
+ case '[':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.BracketSpecifierOrMultiSelectList);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ if ((_span[_index] >= 'A' && _span[_index] <= 'Z') || (_span[_index] >= 'a' && _span[_index] <= 'z') || (_span[_index] == '_'))
+ {
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.IdentifierOrFunctionExpr);
+ _stateStack.Push(JmesPathState.UnquotedString);
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ }
+ else
+ {
+ throw new JmesPathParseException("Expected identifier", _line, _column);
+ }
+ break;
+ };
+ break;
+ }
+
+ case JmesPathState.SubExpression:
+ {
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '\"':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ValExpr);
+ _stateStack.Push(JmesPathState.QuotedString);
+ ++_index;
+ ++_column;
+ break;
+ case '{':
+ PushToken(new Token(TokenType.BeginMultiSelectHash));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.MultiSelectHash);
+ ++_index;
+ ++_column;
+ break;
+ case '*':
+ PushToken(new Token(new ObjectProjection()));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ case '[':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectMultiSelectList);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ if ((_span[_index] >= 'A' && _span[_index] <= 'Z') || (_span[_index] >= 'a' && _span[_index] <= 'z') || (_span[_index] == '_'))
+ {
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.IdentifierOrFunctionExpr);
+ _stateStack.Push(JmesPathState.UnquotedString);
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ }
+ else
+ {
+ throw new JmesPathParseException("Expected identifier", _line, _column);
+ }
+ break;
+ };
+ break;
+ }
+ case JmesPathState.KeyExpr:
+ PushToken(new Token(TokenType.Key, buffer.ToString()));
+ buffer.Clear();
+ _stateStack.Pop();
+ break;
+ case JmesPathState.ValExpr:
+ PushToken(new Token(new IdentifierSelector(buffer.ToString())));
+ buffer.Clear();
+ _stateStack.Pop();
+ break;
+ case JmesPathState.ExpressionOrExpressionType:
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '&':
+ PushToken(new Token(TokenType.BeginExpressionType));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpressionType);
+ _stateStack.Push(JmesPathState.RhsExpression);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.Argument);
+ _stateStack.Push(JmesPathState.RhsExpression);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ }
+ break;
+
+ case JmesPathState.IdentifierOrFunctionExpr:
+ switch(_span[_index])
+ {
+ case '(':
+ {
+ var functionName = buffer.ToString();
+ if (!BuiltInFunctions.Instance.TryGetFunction(functionName, out var func))
+ {
+ throw new JmesPathParseException($"Function '{functionName}' not found", _line, _column);
+ }
+ buffer.Clear();
+ PushToken(new Token(func));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.FunctionExpression);
+ _stateStack.Push(JmesPathState.ExpressionOrExpressionType);
+ ++_index;
+ ++_column;
+ break;
+ }
+ default:
+ {
+ PushToken(new Token(new IdentifierSelector(buffer.ToString())));
+ buffer.Clear();
+ _stateStack.Pop();
+ break;
+ }
+ }
+ break;
+
+ case JmesPathState.FunctionExpression:
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case ',':
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Push(JmesPathState.ExpressionOrExpressionType);
+ ++_index;
+ ++_column;
+ break;
+ case ')':
+ {
+ PushToken(new Token(TokenType.EndArguments));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+
+ case JmesPathState.Argument:
+ PushToken(new Token(TokenType.Argument));
+ _stateStack.Pop();
+ break;
+
+ case JmesPathState.ExpressionType:
+ PushToken(new Token(TokenType.EndExpressionType));
+ PushToken(new Token(TokenType.Argument));
+ _stateStack.Pop();
+ break;
+
+ case JmesPathState.QuotedString:
+ switch (_span[_index])
+ {
+ case '\"':
+ _stateStack.Pop(); // quotedString
+ ++_index;
+ ++_column;
+ break;
+ case '\\':
+ _stateStack.Push(JmesPathState.QuotedStringEscapeChar);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ break;
+ };
+ break;
+
+ case JmesPathState.UnquotedString:
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ _stateStack.Pop(); // unquotedString
+ SkipWhiteSpace();
+ break;
+ default:
+ if ((_span[_index] >= '0' && _span[_index] <= '9') || (_span[_index] >= 'A' && _span[_index] <= 'Z') || (_span[_index] >= 'a' && _span[_index] <= 'z') || (_span[_index] == '_'))
+ {
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ }
+ else
+ {
+ _stateStack.Pop(); // unquotedString
+ }
+ break;
+ };
+ break;
+
+ case JmesPathState.RawStringEscapeChar:
+ switch (_span[_index])
+ {
+ case '\'':
+ buffer.Append(_span[_index]);
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ default:
+ buffer.Append('\\');
+ buffer.Append(_span[_index]);
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ }
+ break;
+
+ case JmesPathState.QuotedStringEscapeChar:
+ switch (_span[_index])
+ {
+ case '\"':
+ buffer.Append('\"');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case '\\':
+ buffer.Append('\\');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case '/':
+ buffer.Append('/');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case 'b':
+ buffer.Append('\b');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case 'f':
+ buffer.Append('\f');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case 'n':
+ buffer.Append('\n');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case 'r':
+ buffer.Append('\r');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case 't':
+ buffer.Append('\t');
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ break;
+ case 'u':
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU1);
+ break;
+ default:
+ throw new JmesPathParseException("Illegal escaped character", _line, _column);
+ }
+ break;
+
+ case JmesPathState.EscapeU1:
+ cp = AppendToCodepoint(0, _span[_index]);
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU2);
+ break;
+ case JmesPathState.EscapeU2:
+ cp = AppendToCodepoint(cp, _span[_index]);
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU3);
+ break;
+ case JmesPathState.EscapeU3:
+ cp = AppendToCodepoint(cp, _span[_index]);
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU4);
+ break;
+ case JmesPathState.EscapeU4:
+ cp = AppendToCodepoint(cp, _span[_index]);
+ if (char.IsHighSurrogate((char)cp))
+ {
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeExpectSurrogatePair1);
+ }
+ else
+ {
+ buffer.Append(char.ConvertFromUtf32((int)cp));
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ }
+ break;
+ case JmesPathState.EscapeExpectSurrogatePair1:
+ switch (_span[_index])
+ {
+ case '\\':
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeExpectSurrogatePair2);
+ break;
+ default:
+ throw new JmesPathParseException("Invalid codepoint", _line, _column);
+ }
+ break;
+ case JmesPathState.EscapeExpectSurrogatePair2:
+ switch (_span[_index])
+ {
+ case 'u':
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU5);
+ break;
+ default:
+ throw new JmesPathParseException("Invalid codepoint", _line, _column);
+ }
+ break;
+ case JmesPathState.EscapeU5:
+ cp2 = AppendToCodepoint(0, _span[_index]);
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU6);
+ break;
+ case JmesPathState.EscapeU6:
+ cp2 = AppendToCodepoint(cp2, _span[_index]);
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU7);
+ break;
+ case JmesPathState.EscapeU7:
+ cp2 = AppendToCodepoint(cp2, _span[_index]);
+ ++_index;
+ ++_column;
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.EscapeU8);
+ break;
+ case JmesPathState.EscapeU8:
+ {
+ cp2 = AppendToCodepoint(cp2, _span[_index]);
+ var codepoint = 0x10000 + ((cp & 0x3FF) << 10) + (cp2 & 0x3FF);
+ buffer.Append(char.ConvertFromUtf32((int)codepoint));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ }
+
+ case JmesPathState.RawString:
+ switch (_span[_index])
+ {
+ case '\'':
+ {
+ PushToken(new Token(new StringValue(buffer.ToString())));
+ buffer.Clear();
+ _stateStack.Pop(); // rawString
+ ++_index;
+ ++_column;
+ break;
+ }
+ case '\\':
+ _stateStack.Push(JmesPathState.RawStringEscapeChar);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ break;
+ };
+ break;
+
+ case JmesPathState.Literal:
+ switch (_span[_index])
+ {
+ case '`':
+ {
+ try
+ {
+ using (var doc = JsonDocument.Parse(buffer.ToString()))
+ {
+ PushToken(new Token(new JsonElementValue(doc.RootElement.Clone())));
+ buffer.Clear();
+ _stateStack.Pop();
+ ++_index;
+ }
+ }
+ catch (JsonException)
+ {
+ throw new JmesPathParseException("Invalid JSON literal", _line, _column);
+ }
+ break;
+ }
+ case '\\':
+ if (_index + 1 < _span.Length)
+ {
+ ++_index;
+ ++_column;
+ if (_span[_index] != '`')
+ {
+ buffer.Append('\\');
+ }
+ buffer.Append(_span[_index]);
+ }
+ else
+ {
+ throw new JmesPathParseException("Unexpected end of input", _line, _column);
+ }
+ ++_index;
+ ++_column;
+ break;
+ default:
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ break;
+ };
+ break;
+
+ case JmesPathState.Number:
+ switch(_span[_index])
+ {
+ case '-':
+ buffer.Append(_span[_index]);
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.Digit);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.Digit);
+ break;
+ }
+ break;
+ case JmesPathState.Digit:
+ switch(_span[_index])
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ _stateStack.Pop(); // digit
+ break;
+ }
+ break;
+
+ case JmesPathState.BracketSpecifier:
+ switch(_span[_index])
+ {
+ case '*':
+ PushToken(new Token(new ListProjection()));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectRightBracket);
+ ++_index;
+ ++_column;
+ break;
+ case ']': // []
+ PushToken(new Token(new FlattenProjection()));
+ _stateStack.Pop(); // bracketSpecifier
+ ++_index;
+ ++_column;
+ break;
+ case '?':
+ PushToken(new Token(TokenType.BeginFilter));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.Filter);
+ _stateStack.Push(JmesPathState.RhsExpression);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ ++_index;
+ ++_column;
+ break;
+ case ':': // sliceExpression
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.RhsSliceExpressionStop);
+ _stateStack.Push(JmesPathState.Number);
+ ++_index;
+ ++_column;
+ break;
+ // number
+ case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.IndexOrSliceExpression);
+ _stateStack.Push(JmesPathState.Number);
+ break;
+ default:
+ throw new JmesPathParseException("Expected index expression", _line, _column);
+ }
+ break;
+ case JmesPathState.BracketSpecifierOrMultiSelectList:
+ switch(_span[_index])
+ {
+ case '*':
+ if (_index+1 >= _span.Length)
+ {
+ throw new JmesPathParseException("Unexpected end of input", _line, _column);
+ }
+ if (_span[_index+1] == ']')
+ {
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.BracketSpecifier);
+ }
+ else
+ {
+ PushToken(new Token(TokenType.BeginMultiSelectList));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.MultiSelectList);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ }
+ break;
+ case ']': // []
+ case '?':
+ case ':': // sliceExpression
+ case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.BracketSpecifier);
+ break;
+ default:
+ PushToken(new Token(TokenType.BeginMultiSelectList));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.MultiSelectList);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ }
+ break;
+
+ case JmesPathState.ExpectMultiSelectList:
+ switch(_span[_index])
+ {
+ case ']':
+ case '?':
+ case ':':
+ case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
+ throw new JmesPathParseException("Expected MultiSelectList", _line, _column);
+ case '*':
+ PushToken(new Token(new ListProjection()));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectRightBracket);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ PushToken(new Token(TokenType.BeginMultiSelectList));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.MultiSelectList);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ }
+ break;
+
+ case JmesPathState.MultiSelectHash:
+ switch(_span[_index])
+ {
+ case '*':
+ case ']':
+ case '?':
+ case ':':
+ case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
+ break;
+ default:
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.KeyValExpr);
+ break;
+ }
+ break;
+
+ case JmesPathState.IndexOrSliceExpression:
+ switch(_span[_index])
+ {
+ case ']':
+ {
+ if (buffer.Length == 0)
+ {
+ PushToken(new Token(new FlattenProjection()));
+ }
+ else
+ {
+ if (!int.TryParse(buffer.ToString(), out var n))
+ {
+ throw new JmesPathParseException("Invalid number", _line, _column);
+ }
+ PushToken(new Token(new IndexSelector(n)));
+ buffer.Clear();
+ }
+ _stateStack.Pop(); // bracketSpecifier
+ ++_index;
+ ++_column;
+ break;
+ }
+ case ':':
+ {
+ var s = buffer.ToString();
+ if (!int.TryParse(s, out var n))
+ {
+ n = s.StartsWith("-") ? int.MinValue : int.MaxValue;
+ }
+ sliceStart = n;
+ buffer.Clear();
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.RhsSliceExpressionStop);
+ _stateStack.Push(JmesPathState.Number);
+ ++_index;
+ ++_column;
+ break;
+ }
+ default:
+ throw new JmesPathParseException("Expected right bracket", _line, _column);
+ }
+ break;
+ case JmesPathState.RhsSliceExpressionStop :
+ {
+ if (buffer.Length != 0)
+ {
+ var s = buffer.ToString();
+ if (!int.TryParse(s, out var n))
+ {
+ n = s.StartsWith("-") ? int.MinValue : int.MaxValue;
+ }
+ sliceStop = n;
+ buffer.Clear();
+ }
+ switch(_span[_index])
+ {
+ case ']':
+ PushToken(new Token(new SliceProjection(new Slice(sliceStart,sliceStop,sliceStep))));
+ sliceStart = null;
+ sliceStop = null;
+ sliceStep = 1;
+ _stateStack.Pop(); // bracketSpecifier2
+ ++_index;
+ ++_column;
+ break;
+ case ':':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.RhsSliceExpressionStep);
+ _stateStack.Push(JmesPathState.Number);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected right bracket", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.RhsSliceExpressionStep:
+ {
+ if (buffer.Length != 0)
+ {
+ if (!int.TryParse(buffer.ToString(), out var n))
+ {
+ throw new JmesPathParseException("Invalid slice stop", _line, _column);
+ }
+ buffer.Clear();
+ if (n == 0)
+ {
+ throw new JmesPathParseException("Slice step cannot be zero", _line, _column);
+ }
+ sliceStep = n;
+ buffer.Clear();
+ }
+ switch(_span[_index])
+ {
+ case ']':
+ PushToken(new Token(new SliceProjection(new Slice(sliceStart,sliceStop,sliceStep))));
+ sliceStart = null;
+ sliceStop = null;
+ sliceStep = 1;
+ buffer.Clear();
+ _stateStack.Pop(); // rhsSliceExpressionStep
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected right bracket", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.ExpectRightBracket:
+ {
+ switch(_span[_index])
+ {
+ case ']':
+ _stateStack.Pop(); // expectRightBracket
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected right bracket", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.ExpectRightParen:
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case ')':
+ ++_index;
+ ++_column;
+ PushToken(new Token(TokenType.RightParen));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.RhsExpression);
+ break;
+ default:
+ throw new JmesPathParseException("Expected right parenthesis", _line, _column);
+ }
+ break;
+ case JmesPathState.KeyValExpr:
+ {
+ switch (_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '\"':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectColon);
+ _stateStack.Push(JmesPathState.KeyExpr);
+ _stateStack.Push(JmesPathState.QuotedString);
+ ++_index;
+ ++_column;
+ break;
+ case '\'':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectColon);
+ _stateStack.Push(JmesPathState.RawString);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ if ((_span[_index] >= 'A' && _span[_index] <= 'Z') || (_span[_index] >= 'a' && _span[_index] <= 'z') || (_span[_index] == '_'))
+ {
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectColon);
+ _stateStack.Push(JmesPathState.KeyExpr);
+ _stateStack.Push(JmesPathState.UnquotedString);
+ buffer.Append(_span[_index]);
+ ++_index;
+ ++_column;
+ }
+ else
+ {
+ throw new JmesPathParseException("Expected key", _line, _column);
+ }
+ break;
+ };
+ break;
+ }
+ case JmesPathState.CmpLtOrLte:
+ {
+ switch(_span[_index])
+ {
+ case '=':
+ PushToken(new Token(LteOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ default:
+ PushToken(new Token(LtOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ break;
+ }
+ break;
+ }
+ case JmesPathState.CmpGtOrGte:
+ {
+ switch(_span[_index])
+ {
+ case '=':
+ PushToken(new Token(GteOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ default:
+ PushToken(new Token(GtOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ break;
+ }
+ break;
+ }
+ case JmesPathState.CmpEq:
+ {
+ switch(_span[_index])
+ {
+ case '=':
+ PushToken(new Token(EqOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected comparator", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.CmpNe:
+ {
+ switch(_span[_index])
+ {
+ case '=':
+ PushToken(new Token(NeOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected comparator", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.ExpectDot:
+ {
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case '.':
+ _stateStack.Pop(); // expect_dot
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected dot", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.ExpectPipeOrOr:
+ {
+ switch(_span[_index])
+ {
+ case '|':
+ PushToken(new Token(OrOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ default:
+ PushToken(new Token(TokenType.Pipe));
+ _stateStack.Pop();
+ break;
+ }
+ break;
+ }
+ case JmesPathState.ExpectAnd:
+ {
+ switch(_span[_index])
+ {
+ case '&':
+ PushToken(new Token(AndOperator.Instance));
+ PushToken(new Token(TokenType.CurrentNode));
+ _stateStack.Pop(); // expectAnd
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected &&", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.MultiSelectList:
+ {
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case ',':
+ PushToken(new Token(TokenType.Separator));
+ _stateStack.Push(JmesPathState.LhsExpression);
+ ++_index;
+ ++_column;
+ break;
+ case '[':
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ case '.':
+ _stateStack.Push(JmesPathState.SubExpression);
+ ++_index;
+ ++_column;
+ break;
+ case '|':
+ {
+ ++_index;
+ ++_column;
+ _stateStack.Push(JmesPathState.LhsExpression);
+ _stateStack.Push(JmesPathState.ExpectPipeOrOr);
+ break;
+ }
+ case ']':
+ {
+ PushToken(new Token(TokenType.EndMultiSelectList));
+ _stateStack.Pop();
+
+ ++_index;
+ ++_column;
+ break;
+ }
+ default:
+ throw new JmesPathParseException("Expected right bracket", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.Filter:
+ {
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case ']':
+ {
+ PushToken(new Token(TokenType.EndFilter));
+ _stateStack.Pop();
+ ++_index;
+ ++_column;
+ break;
+ }
+ default:
+ throw new JmesPathParseException("Expected right bracket", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.ExpectRightBrace:
+ {
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case ',':
+ PushToken(new Token(TokenType.Separator));
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.KeyValExpr);
+ ++_index;
+ ++_column;
+ break;
+ case '[':
+ case '{':
+ _stateStack.Push(JmesPathState.LhsExpression);
+ break;
+ case '.':
+ _stateStack.Push(JmesPathState.SubExpression);
+ ++_index;
+ ++_column;
+ break;
+ case '}':
+ {
+ _stateStack.Pop();
+ PushToken(new Token(TokenType.EndMultiSelectHash));
+ ++_index;
+ ++_column;
+ break;
+ }
+ default:
+ throw new JmesPathParseException("Expected right brace", _line, _column);
+ }
+ break;
+ }
+ case JmesPathState.ExpectColon:
+ {
+ switch(_span[_index])
+ {
+ case ' ':case '\t':case '\r':case '\n':
+ SkipWhiteSpace();
+ break;
+ case ':':
+ _stateStack.Pop();
+ _stateStack.Push(JmesPathState.ExpectRightBrace);
+ _stateStack.Push(JmesPathState.LhsExpression);
+ ++_index;
+ ++_column;
+ break;
+ default:
+ throw new JmesPathParseException("Expected colon", _line, _column);
+ }
+ break;
+ }
+ }
+ }
+
+ if (_stateStack.Count == 0)
+ {
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ while (_stateStack.Count > 1)
+ {
+ switch (_stateStack.Peek())
+ {
+ case JmesPathState.RhsExpression:
+ if (_stateStack.Count > 1)
+ {
+ _stateStack.Pop();
+ }
+ else
+ {
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ break;
+ case JmesPathState.ValExpr:
+ PushToken(new Token(new IdentifierSelector(buffer.ToString())));
+ _stateStack.Pop();
+ break;
+ case JmesPathState.IdentifierOrFunctionExpr:
+ PushToken(new Token(new IdentifierSelector(buffer.ToString())));
+ _stateStack.Pop();
+ break;
+ case JmesPathState.UnquotedString:
+ _stateStack.Pop();
+ break;
+ default:
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ }
+
+ if (!(_stateStack.Count == 1 && _stateStack.Peek() == JmesPathState.RhsExpression))
+ {
+ throw new JmesPathParseException("Unexpected end of input", _line, _column);
+ }
+
+ _stateStack.Pop();
+
+ PushToken(new Token(TokenType.EndOfExpression));
+
+ var a = _outputStack.ToArray();
+
+ return new JsonTransformer(new Expression(a));
+ }
+
+ private void SkipWhiteSpace()
+ {
+ switch (_span[_index])
+ {
+ case ' ':case '\t':
+ ++_index;
+ ++_column;
+ break;
+ case '\r':
+ if (_index+1 < _span.Length && _span[_index+1] == '\n')
+ ++_index;
+ ++_line;
+ _column = 1;
+ ++_index;
+ break;
+ case '\n':
+ ++_line;
+ _column = 1;
+ ++_index;
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void UnwindRightParen()
+ {
+ while (_operatorStack.Count > 1 && _operatorStack.Peek().Type != TokenType.LeftParen)
+ {
+ _outputStack.Push(_operatorStack.Pop());
+ }
+ if (_operatorStack.Count == 0)
+ {
+ throw new JmesPathParseException("Unbalanced parentheses", _line, _column);
+ }
+ _operatorStack.Pop(); // TokenType.LeftParen
+ }
+
+ private void PushToken(Token token)
+ {
+ switch (token.Type)
+ {
+ case TokenType.EndFilter:
+ {
+ UnwindRightParen();
+ var tokens = new List();
+ while (_outputStack.Count > 1 && _outputStack.Peek().Type != TokenType.BeginFilter)
+ {
+ tokens.Add(_outputStack.Pop());
+ }
+ if (_outputStack.Count == 0)
+ {
+ throw new JmesPathParseException("Unbalanced parentheses", _line, _column);
+ }
+ if (tokens[tokens.Count-1].Type != TokenType.Literal)
+ {
+ tokens.Add(new Token(TokenType.CurrentNode));
+ }
+ _outputStack.Pop();
+
+ if (_outputStack.Count != 0 && _outputStack.Peek().IsProjection &&
+ (token.PrecedenceLevel > _outputStack.Peek().PrecedenceLevel ||
+ (token.PrecedenceLevel == _outputStack.Peek().PrecedenceLevel && token.IsRightAssociative)))
+ {
+ _outputStack.Peek().GetExpression().AddExpression(new FilterExpression(new Expression(tokens.ToArray())));
+ }
+ else
+ {
+ _outputStack.Push(new Token(new FilterExpression(new Expression(tokens.ToArray()))));
+ }
+ break;
+ }
+ case TokenType.EndMultiSelectList:
+ {
+ UnwindRightParen();
+ var expressions = new List();
+ while (_outputStack.Count > 0 && _outputStack.Peek().Type != TokenType.BeginMultiSelectList)
+ {
+ var tokens = new List();
+ do
+ {
+ tokens.Add(_outputStack.Pop());
+ }
+ while (_outputStack.Count > 0 && _outputStack.Peek().Type != TokenType.BeginMultiSelectList && _outputStack.Peek().Type != TokenType.Separator);
+ if (_outputStack.Peek().Type == TokenType.Separator)
+ {
+ _outputStack.Pop();
+ }
+ if (tokens[tokens.Count-1].Type != TokenType.Literal)
+ {
+ tokens.Add(new Token(TokenType.CurrentNode));
+ }
+ expressions.Add(new Expression(tokens.ToArray()));
+ }
+ if (_outputStack.Count == 0)
+ {
+ throw new JmesPathParseException("Unbalanced braces", _line, _column);
+ }
+ _outputStack.Pop(); // TokenType.BeginMultiSelectList
+ expressions.Reverse();
+
+ if (_outputStack.Count != 0 && _outputStack.Peek().IsProjection &&
+ (token.PrecedenceLevel > _outputStack.Peek().PrecedenceLevel ||
+ (token.PrecedenceLevel == _outputStack.Peek().PrecedenceLevel && token.IsRightAssociative)))
+ {
+ _outputStack.Peek().GetExpression().AddExpression(new MultiSelectList(expressions));
+ }
+ else
+ {
+ _outputStack.Push(new Token(new MultiSelectList(expressions)));
+ }
+ break;
+ }
+
+ case TokenType.EndMultiSelectHash:
+ {
+ UnwindRightParen();
+ var keyExprPairs = new List();
+ while (_outputStack.Count > 1 && _outputStack.Peek().Type != TokenType.BeginMultiSelectHash)
+ {
+ var tokens = new List();
+ do
+ {
+ tokens.Add(_outputStack.Pop());
+ }
+ while (_outputStack.Peek().Type != TokenType.Key);
+ if (_outputStack.Peek().Type != TokenType.Key)
+ {
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ var key = _outputStack.Pop().GetKey();
+ if (_outputStack.Peek().Type == TokenType.Separator)
+ {
+ _outputStack.Pop();
+ }
+ if (tokens[tokens.Count-1].Type != TokenType.Literal)
+ {
+ tokens.Add(new Token(TokenType.CurrentNode));
+ }
+ keyExprPairs.Add(new KeyExpressionPair(key, new Expression(tokens.ToArray())));
+ }
+ if (_outputStack.Count == 0)
+ {
+ throw new JmesPathParseException("Syntax error", _line, _column);
+ }
+ keyExprPairs.Reverse();
+ _outputStack.Pop(); // TokenType.BeginMultiSelectHash
+
+ if (_outputStack.Count != 0 && _outputStack.Peek().IsProjection &&
+ (token.PrecedenceLevel > _outputStack.Peek().PrecedenceLevel ||
+ (token.PrecedenceLevel == _outputStack.Peek().PrecedenceLevel && token.IsRightAssociative)))
+ {
+ _outputStack.Peek().GetExpression().AddExpression(new MultiSelectHash(keyExprPairs));
+ }
+ else
+ {
+ _outputStack.Push(new Token(new MultiSelectHash(keyExprPairs)));
+ }
+ break;
+ }
+ case TokenType.EndExpressionType:
+ {
+ var tokens = new List();
+ while (_outputStack.Count > 1 && _outputStack.Peek().Type != TokenType.BeginExpressionType)
+ {
+ tokens.Add(_outputStack.Pop());
+ }
+ if (_outputStack.Count == 0)
+ {
+ throw new JmesPathParseException("Unbalanced braces", _line, _column);
+ }
+ if (tokens[tokens.Count-1].Type != TokenType.Literal)
+ {
+ tokens.Add(new Token(TokenType.CurrentNode));
+ }
+ _outputStack.Push(new Token(new FunctionExpression(new Expression(tokens.ToArray()))));
+ break;
+ }
+ case TokenType.Literal:
+ if (_outputStack.Count != 0 && _outputStack.Peek().Type == TokenType.CurrentNode)
+ {
+ _outputStack.Pop();
+ _outputStack.Push(token);
+ }
+ else
+ {
+ _outputStack.Push(token);
+ }
+ break;
+ case TokenType.Expression:
+ if (_outputStack.Count != 0 && _outputStack.Peek().IsProjection &&
+ (token.PrecedenceLevel > _outputStack.Peek().PrecedenceLevel ||
+ (token.PrecedenceLevel == _outputStack.Peek().PrecedenceLevel && token.IsRightAssociative)))
+ {
+ _outputStack.Peek().GetExpression().AddExpression(token.GetExpression());
+ }
+ else
+ {
+ _outputStack.Push(token);
+ }
+ break;
+ case TokenType.RightParen:
+ {
+ UnwindRightParen();
+ break;
+ }
+ case TokenType.EndArguments:
+ {
+ UnwindRightParen();
+ var argCount = 0;
+ var tokens = new List();
+ Debug.Assert(_operatorStack.Count > 0 && _operatorStack.Peek().Type == TokenType.Function);
+ tokens.Add(_operatorStack.Pop()); // Function
+ while (_outputStack.Count > 1 && _outputStack.Peek().Type != TokenType.BeginArguments)
+ {
+ if (_outputStack.Peek().Type == TokenType.Argument)
+ {
+ ++argCount;
+ }
+ tokens.Add(_outputStack.Pop());
+ }
+ if (_outputStack.Count == 0)
+ {
+ throw new JmesPathParseException("Expected parentheses", _line, _column);
+ }
+ _outputStack.Pop(); // TokenType.BeginArguments
+ if (tokens[tokens.Count-1].Type != TokenType.Literal)
+ {
+ tokens.Add(new Token(TokenType.CurrentNode));
+ }
+ if (tokens[0].GetFunction().Arity != null && argCount != tokens[0].GetFunction().Arity)
+ {
+ throw new JmesPathParseException($"Invalid arity (The number of arguments or operands a function or operation takes) calling function '{tokens[0].GetFunction()}', expected {tokens[0].GetFunction().Arity}, found {argCount}", _line, _column);
+ }
+
+ if (_outputStack.Count != 0 && _outputStack.Peek().IsProjection &&
+ (token.PrecedenceLevel > _outputStack.Peek().PrecedenceLevel ||
+ (token.PrecedenceLevel == _outputStack.Peek().PrecedenceLevel && token.IsRightAssociative)))
+ {
+ _outputStack.Peek().GetExpression().AddExpression(new FunctionExpression(new Expression(tokens.ToArray())));
+ }
+ else
+ {
+ _outputStack.Push(new Token(new FunctionExpression(new Expression(tokens.ToArray()))));
+ }
+ break;
+ }
+ case TokenType.EndOfExpression:
+ {
+ while (_operatorStack.Count != 0)
+ {
+ _outputStack.Push(_operatorStack.Pop());
+ }
+ break;
+ }
+ case TokenType.UnaryOperator:
+ case TokenType.BinaryOperator:
+ {
+ if (_operatorStack.Count == 0 || _operatorStack.Peek().Type == TokenType.LeftParen)
+ {
+ _operatorStack.Push(token);
+ }
+ else if (token.PrecedenceLevel > _operatorStack.Peek().PrecedenceLevel
+ || (token.PrecedenceLevel == _operatorStack.Peek().PrecedenceLevel && token.IsRightAssociative))
+ {
+ _operatorStack.Push(token);
+ }
+ else
+ {
+ while (_operatorStack.Count > 0 && _operatorStack.Peek().IsOperator
+ && (_operatorStack.Peek().PrecedenceLevel > token.PrecedenceLevel
+ || (token.PrecedenceLevel == _operatorStack.Peek().PrecedenceLevel && token.IsRightAssociative)))
+ {
+ _outputStack.Push(_operatorStack.Pop());
+ }
+
+ _operatorStack.Push(token);
+ }
+ break;
+ }
+ case TokenType.Separator:
+ {
+ UnwindRightParen();
+ _outputStack.Push(token);
+ _operatorStack.Push(new Token(TokenType.LeftParen));
+ break;
+ }
+ case TokenType.BeginFilter:
+ _outputStack.Push(token);
+ _operatorStack.Push(new Token(TokenType.LeftParen));
+ break;
+ case TokenType.BeginMultiSelectList:
+ _outputStack.Push(token);
+ _operatorStack.Push(new Token(TokenType.LeftParen));
+ break;
+ case TokenType.BeginMultiSelectHash:
+ _outputStack.Push(token);
+ _operatorStack.Push(new Token(TokenType.LeftParen));
+ break;
+ case TokenType.Function:
+ _outputStack.Push(new Token(TokenType.BeginArguments));
+ _operatorStack.Push(token);
+ _operatorStack.Push(new Token(TokenType.LeftParen));
+ break;
+ case TokenType.CurrentNode:
+ _outputStack.Push(token);
+ break;
+ case TokenType.Key:
+ case TokenType.Pipe:
+ case TokenType.Argument:
+ case TokenType.BeginExpressionType:
+ _outputStack.Push(token);
+ break;
+ case TokenType.LeftParen:
+ _operatorStack.Push(token);
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ private uint AppendToCodepoint(uint cp, uint c)
+ {
+ cp *= 16;
+ if (c >= '0' && c <= '9')
+ {
+ cp += c - '0';
+ }
+ else if (c >= 'a' && c <= 'f')
+ {
+ cp += c - 'a' + 10;
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ cp += c - 'A' + 10;
+ }
+ else
+ {
+ throw new JmesPathParseException("Invalid codepoint", _line, _column);
+ }
+ return cp;
+ }
+ }
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs
new file mode 100644
index 00000000..4fc29685
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal sealed class DynamicResources
+ {
+ }
+ ///
+ /// Provides functionality for applying a JMESPath expression to transform a JSON document into
+ /// another JSON document
+ ///
+ ///
+ /// The following example shows how to apply a JMESPath expression to transform a JSON document into
+ /// another JSON document.
+ ///
+ /// using System;
+ /// using System.Text.Json;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// string jsonString = @"
+ /// {
+ /// ""people"": [
+ /// {
+ /// ""age"": 20,
+ /// ""other"": ""foo"",
+ /// ""name"": ""Bob""
+ /// },
+ /// {
+ /// ""age"": 25,
+ /// ""other"": ""bar"",
+ /// ""name"": ""Fred""
+ /// },
+ /// {
+ /// ""age"": 30,
+ /// ""other"": ""baz"",
+ /// ""name"": ""George""
+ /// }
+ /// ]
+ /// }
+ /// ";
+ ///
+ /// using JsonDocument doc = JsonDocument.Parse(jsonString);
+ ///
+ /// var transformer = JsonTransformer.Parse("people[?age > `20`].[name, age]");
+ ///
+ /// using JsonDocument result = transformer.Transform(doc.RootElement);
+ ///
+ /// var serializerOptions = new JsonSerializerOptions() {WriteIndented = true};
+ /// Console.WriteLine(JsonSerializer.Serialize(result.RootElement, serializerOptions));
+ /// }
+ ///
+ /// Output:
+ ///
+ ///
+ /// [
+ /// [
+ /// "Fred",
+ /// 25
+ /// ],
+ /// [
+ /// "George",
+ /// 30
+ /// ]
+ /// ]
+ ///
+ ///
+
+ public sealed class JsonTransformer
+ {
+ ///
+ /// Parses a JMESPath string into a , for "parse once, use many times".
+ /// A instance is thread safe and has no mutable state.
+ ///
+ /// A JMESPath string.
+ /// A .
+ ///
+ /// The parameter is not a valid JMESPath expression.
+ ///
+ ///
+ /// The is .
+ ///
+ public static JsonTransformer Parse(string jmesPath)
+ {
+ if (jmesPath == null)
+ {
+ throw new ArgumentNullException(nameof(jmesPath));
+ }
+ var compiler = new JmesPathParser(jmesPath);
+ return compiler.Parse();
+ }
+
+ private Expression _expr;
+
+ internal JsonTransformer(Expression expr)
+ {
+ _expr = expr;
+ }
+
+ ///
+ /// Applies a JMESPath expression to a JSON document to transform it
+ /// into another Json document.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The provided JSON document.
+ /// The transformed JSON document. If a type error is detected in a function call,
+ /// a JSON null value is returned.
+
+ public JsonDocument Transform(JsonElement doc)
+ {
+ var resources = new DynamicResources();
+ _expr.TryEvaluate(resources, new JsonElementValue(doc), out var temp);
+ return JsonDocument.Parse(temp.ToString());
+ }
+
+ ///
+ /// Applies a JMESPath expression to a JSON document to transform it
+ /// into another Json document.
+ /// This method parses and applies the expression in one operation.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The provided JSON document.
+ /// A JMESPath string.
+ /// The transformed JSON document.
+ ///
+ /// The parameter is not a valid JMESPath expression.
+ ///
+ ///
+ /// The is .
+ ///
+
+ public static JsonDocument Transform(JsonElement doc, string jmesPath)
+ {
+ var searcher = Parse(jmesPath);
+ return searcher.Transform(doc);
+ }
+ }
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs
new file mode 100644
index 00000000..c0816e53
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs
@@ -0,0 +1,74 @@
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal enum Operator
+ {
+ Default, // Identifier, CurrentNode, Index, MultiSelectList, MultiSelectHash, FunctionExpression
+ Projection,
+ FlattenProjection, // FlattenProjection
+ Or,
+ And,
+ Eq,
+ Ne,
+ Lt,
+ Lte,
+ Gt,
+ Gte,
+ Not
+ }
+
+ internal static class OperatorTable
+ {
+ static internal int PrecedenceLevel(Operator oper)
+ {
+ switch (oper)
+ {
+ case Operator.Projection:
+ return 1;
+ case Operator.FlattenProjection:
+ return 1;
+ case Operator.Or:
+ return 2;
+ case Operator.And:
+ return 3;
+ case Operator.Eq:
+ case Operator.Ne:
+ return 4;
+ case Operator.Lt:
+ case Operator.Lte:
+ case Operator.Gt:
+ case Operator.Gte:
+ return 5;
+ case Operator.Not:
+ return 6;
+ default:
+ return 6;
+ }
+ }
+
+ static internal bool IsRightAssociative(Operator oper)
+ {
+ switch (oper)
+ {
+ case Operator.Not:
+ return true;
+ case Operator.Projection:
+ return true;
+ case Operator.FlattenProjection:
+ return false;
+ case Operator.Or:
+ case Operator.And:
+ case Operator.Eq:
+ case Operator.Ne:
+ case Operator.Lt:
+ case Operator.Lte:
+ case Operator.Gt:
+ case Operator.Gte:
+ return false;
+ default:
+ return false;
+ }
+ }
+ }
+
+}
+
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
new file mode 100644
index 00000000..bb9ca308
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
@@ -0,0 +1 @@
+JMESPath
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs
new file mode 100644
index 00000000..0d6fb21b
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal readonly struct Slice
+ {
+ private readonly int? _start;
+ private readonly int? _stop;
+
+ public int Step {get;}
+
+ public Slice(int? start, int? stop, int step)
+ {
+ _start = start;
+ _stop = stop;
+ Step = step;
+ }
+
+ public int GetStart(int size)
+ {
+ if (_start.HasValue)
+ {
+ var len = _start.Value >= 0 ? _start.Value : size + _start.Value;
+ return len <= size ? len : size;
+ }
+ else
+ {
+ if (Step >= 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return size;
+ }
+ }
+ }
+
+ public int GetStop(int size)
+ {
+ if (_stop.HasValue)
+ {
+ var len = _stop.Value >= 0 ? _stop.Value : size + _stop.Value;
+ return len <= size ? len : size;
+ }
+ else
+ {
+ return Step >= 0 ? size : -1;
+ }
+ }
+ };
+
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs
new file mode 100644
index 00000000..ab5d1b76
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Diagnostics;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal enum TokenType
+ {
+ CurrentNode,
+ LeftParen,
+ RightParen,
+ BeginMultiSelectHash,
+ EndMultiSelectHash,
+ BeginMultiSelectList,
+ EndMultiSelectList,
+ BeginFilter,
+ EndFilter,
+ Pipe,
+ Separator,
+ Key,
+ Literal,
+ Expression,
+ BinaryOperator,
+ UnaryOperator,
+ Function,
+ BeginArguments,
+ EndArguments,
+ Argument,
+ BeginExpressionType,
+ EndExpressionType,
+ EndOfExpression
+ }
+
+ internal readonly struct Token : IEquatable
+ {
+ private readonly object? _expr;
+
+ internal Token(TokenType type)
+ {
+ Type = type;
+ _expr = null;
+ }
+
+ internal Token(TokenType type, string s)
+ {
+ Type = type;
+ _expr = s;
+ }
+
+ internal Token(IExpression expr)
+ {
+ Type = TokenType.Expression;
+ _expr = expr;
+ }
+
+ internal Token(IUnaryOperator expr)
+ {
+ Type = TokenType.UnaryOperator;
+ _expr = expr;
+ }
+
+ internal Token(IBinaryOperator expr)
+ {
+ Type = TokenType.BinaryOperator;
+ _expr = expr;
+ }
+
+ internal Token(IFunction expr)
+ {
+ Type = TokenType.Function;
+ _expr = expr;
+ }
+
+ internal Token(IValue expr)
+ {
+ Type = TokenType.Literal;
+ _expr = expr;
+ }
+
+ internal TokenType Type{get;}
+
+ internal bool IsOperator
+ {
+ get
+ {
+ switch(Type)
+ {
+ case TokenType.UnaryOperator:
+ return true;
+ case TokenType.BinaryOperator:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ internal bool IsProjection
+ {
+ get
+ {
+ switch(Type)
+ {
+ case TokenType.Expression:
+ return GetExpression().IsProjection;
+ default:
+ return false;
+ }
+ }
+ }
+
+ internal bool IsRightAssociative
+ {
+ get
+ {
+ switch(Type)
+ {
+ case TokenType.Expression:
+ return GetExpression().IsRightAssociative;
+ case TokenType.UnaryOperator:
+ return GetUnaryOperator().IsRightAssociative;
+ case TokenType.BinaryOperator:
+ return GetBinaryOperator().IsRightAssociative;
+ default:
+ return false;
+ }
+ }
+ }
+
+ internal int PrecedenceLevel
+ {
+ get
+ {
+ switch(Type)
+ {
+ case TokenType.Expression:
+ return GetExpression().PrecedenceLevel;
+ case TokenType.UnaryOperator:
+ return GetUnaryOperator().PrecedenceLevel;
+ case TokenType.BinaryOperator:
+ return GetBinaryOperator().PrecedenceLevel;
+ default:
+ return 100;
+ }
+ }
+ }
+
+ internal string GetKey()
+ {
+ Debug.Assert(Type == TokenType.Key);
+ return _expr as string ?? throw new InvalidOperationException("Key cannot be null");
+ }
+
+ internal IUnaryOperator GetUnaryOperator()
+ {
+ Debug.Assert(Type == TokenType.UnaryOperator);
+ return _expr as IUnaryOperator ?? throw new InvalidOperationException("Unary operator cannot be null");
+ }
+
+ internal IBinaryOperator GetBinaryOperator()
+ {
+ Debug.Assert(Type == TokenType.BinaryOperator);
+ return _expr as IBinaryOperator ?? throw new InvalidOperationException("Binary operator cannot be null");
+ }
+
+ internal IValue GetValue()
+ {
+ Debug.Assert(Type == TokenType.Literal);
+ return _expr as IValue ?? throw new InvalidOperationException("Value cannot be null");
+ }
+
+ internal IFunction GetFunction()
+ {
+ Debug.Assert(Type == TokenType.Function);
+ return _expr as IFunction ?? throw new InvalidOperationException("Function cannot be null");
+ }
+
+ internal IExpression GetExpression()
+ {
+ Debug.Assert(Type == TokenType.Expression);
+ return _expr as IExpression ?? throw new InvalidOperationException("Expression cannot be null");
+ }
+ public bool Equals(Token other)
+ {
+ if (Type == other.Type)
+ return true;
+ else
+ return false;
+ }
+
+ public override string ToString()
+ {
+ switch(Type)
+ {
+ case TokenType.BeginArguments:
+ return "BeginArguments";
+ case TokenType.CurrentNode:
+ return "CurrentNode";
+ case TokenType.LeftParen:
+ return "LeftParen";
+ case TokenType.RightParen:
+ return "RightParen";
+ case TokenType.BeginMultiSelectHash:
+ return "BeginMultiSelectHash";
+ case TokenType.EndMultiSelectHash:
+ return "EndMultiSelectHash";
+ case TokenType.BeginMultiSelectList:
+ return "BeginMultiSelectList";
+ case TokenType.EndMultiSelectList:
+ return "EndMultiSelectList";
+ case TokenType.BeginFilter:
+ return "BeginFilter";
+ case TokenType.EndFilter:
+ return "EndFilter";
+ case TokenType.Pipe:
+ return $"Pipe";
+ case TokenType.Separator:
+ return "Separator";
+ case TokenType.Key:
+ return $"Key {_expr}";
+ case TokenType.Literal:
+ return $"Literal {_expr}";
+ case TokenType.Expression:
+ return "Expression";
+ case TokenType.BinaryOperator:
+ return $"BinaryOperator {_expr}";
+ case TokenType.UnaryOperator:
+ return $"UnaryOperator {_expr}";
+ case TokenType.Function:
+ return $"Function {_expr}";
+ case TokenType.EndArguments:
+ return "EndArguments";
+ case TokenType.Argument:
+ return "Argument";
+ case TokenType.BeginExpressionType:
+ return "BeginExpressionType";
+ case TokenType.EndExpressionType:
+ return "EndExpressionType";
+ case TokenType.EndOfExpression:
+ return "EndOfExpression";
+ default:
+ return "Other";
+ }
+ }
+ }
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs
new file mode 100644
index 00000000..c39df7eb
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs
@@ -0,0 +1,75 @@
+using System.Text.RegularExpressions;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal interface IUnaryOperator
+ {
+ int PrecedenceLevel {get;}
+ bool IsRightAssociative {get;}
+ bool TryEvaluate(IValue elem, out IValue result);
+ };
+
+ internal abstract class UnaryOperator : IUnaryOperator
+ {
+ internal UnaryOperator(Operator oper)
+ {
+ PrecedenceLevel = OperatorTable.PrecedenceLevel(oper);
+ IsRightAssociative = OperatorTable.IsRightAssociative(oper);
+ }
+
+ public int PrecedenceLevel {get;}
+
+ public bool IsRightAssociative {get;}
+
+ public abstract bool TryEvaluate(IValue elem, out IValue result);
+ };
+
+ internal sealed class NotOperator : UnaryOperator
+ {
+ internal static NotOperator Instance { get; } = new();
+
+ internal NotOperator()
+ : base(Operator.Not)
+ {}
+
+ public override bool TryEvaluate(IValue val, out IValue result)
+ {
+ result = Expression.IsFalse(val) ? JsonConstants.True : JsonConstants.False;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "Not";
+ }
+ };
+
+ internal sealed class RegexOperator : UnaryOperator
+ {
+ private Regex _regex;
+
+ internal RegexOperator(Regex regex)
+ : base(Operator.Not)
+ {
+ _regex = regex;
+ }
+
+ public override bool TryEvaluate(IValue val, out IValue result)
+ {
+ if (!(val.Type == JmesPathType.String))
+ {
+ result = JsonConstants.Null;
+ return false; // type error
+ }
+ result = _regex.IsMatch(val.GetString()) ? JsonConstants.True : JsonConstants.False;
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return "Regex";
+ }
+ };
+
+}
+
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs
new file mode 100644
index 00000000..cabfd2be
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ internal class JsonDocumentBuilder
+ {
+ internal static JsonDocumentBuilder Default { get; } = new();
+
+ internal JsonValueKind ValueKind {get;}
+
+ private object? _item;
+
+ private IList GetList()
+ {
+ return _item as IList ?? throw new InvalidOperationException("Item is null");
+ }
+
+ private IDictionary GetDictionary()
+ {
+ return _item as IDictionary ?? throw new InvalidOperationException("Item is null");
+ }
+ internal JsonDocumentBuilder()
+ : this(JsonValueKind.Null)
+ {
+ }
+
+ internal JsonDocumentBuilder(JsonValueKind valueKind)
+ {
+ ValueKind = valueKind;
+ switch (valueKind)
+ {
+ case JsonValueKind.Array:
+ _item = new List();
+ break;
+ case JsonValueKind.Object:
+ _item = new Dictionary();
+ break;
+ case JsonValueKind.True:
+ _item = true;
+ break;
+ case JsonValueKind.False:
+ _item = false;
+ break;
+ case JsonValueKind.Null:
+ _item = null;
+ break;
+ case JsonValueKind.String:
+ _item = "";
+ break;
+ case JsonValueKind.Number:
+ _item = 0;
+ break;
+ default:
+ _item = null;
+ break;
+ }
+ }
+
+ internal JsonDocumentBuilder(IList list)
+ {
+ ValueKind = JsonValueKind.Array;
+ _item = list;
+ }
+
+ internal JsonDocumentBuilder(IDictionary dict)
+ {
+ ValueKind = JsonValueKind.Object;
+ _item = dict;
+ }
+
+ internal JsonDocumentBuilder(string str)
+ {
+ ValueKind = JsonValueKind.String;
+ _item = str;
+ }
+
+ internal JsonDocumentBuilder(JsonElement element)
+ {
+ ValueKind = element.ValueKind;
+ switch (element.ValueKind)
+ {
+ case JsonValueKind.Array:
+ var list = new List();
+ foreach (var item in element.EnumerateArray())
+ {
+ list.Add(new JsonDocumentBuilder(item));
+ }
+ _item = list;
+ break;
+ case JsonValueKind.Object:
+ var dict = new Dictionary();
+ foreach (var property in element.EnumerateObject())
+ {
+ dict.Add(property.Name, new JsonDocumentBuilder(property.Value));
+ }
+ _item = dict;
+ break;
+ default:
+ _item = element;
+ break;
+ }
+ }
+
+ internal IEnumerable EnumerateArray()
+ {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ return GetList();
+ }
+
+ internal IEnumerable> EnumerateObject()
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ return GetDictionary();
+ }
+
+ internal JsonDocumentBuilder this[int i]
+ {
+ get {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ return GetList() [i];
+ }
+ set {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ GetList()[i] = value;
+ }
+ }
+
+ internal void AddArrayItem(JsonDocumentBuilder value)
+ {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ GetList().Add(value);
+ }
+
+ internal void InsertArrayItem(int index, JsonDocumentBuilder value)
+ {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ GetList().Insert(index, value);
+ }
+
+ internal void RemoveArrayItemAt(int index)
+ {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ GetList().RemoveAt(index);
+ }
+
+ internal void AddProperty(string name, JsonDocumentBuilder value)
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ GetDictionary().Add(name, value);
+ }
+
+ internal bool TryAddProperty(string name, JsonDocumentBuilder value)
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ return GetDictionary().TryAdd(name, value);
+ }
+
+ internal bool ContainsPropertyName(string name)
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ return GetDictionary().ContainsKey(name);
+ }
+
+ internal void RemoveProperty(string name)
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ GetDictionary().Remove(name);
+ }
+
+ internal int GetArrayLength()
+ {
+ if (ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Array.");
+ }
+ return GetList().Count();
+ }
+
+ internal int GetObjectLength()
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ return GetDictionary().Count();
+ }
+
+ internal bool TryGetProperty(string name, out JsonDocumentBuilder value)
+ {
+ if (ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("This value's ValueKind is not Object.");
+ }
+ if (ValueKind != JsonValueKind.Object)
+ {
+ value = Default;
+ return false;
+ }
+ return GetDictionary().TryGetValue(name, out value);
+ }
+
+ public override string ToString()
+ {
+ var buffer = new StringBuilder();
+ ToString(buffer);
+ return buffer.ToString();
+ }
+
+ private void ToString(StringBuilder buffer)
+ {
+ switch (ValueKind)
+ {
+ case JsonValueKind.Array:
+ {
+ buffer.Append("[");
+ var first = true;
+ foreach (var item in EnumerateArray())
+ {
+ if (!first)
+ {
+ buffer.Append(",");
+ }
+ else
+ {
+ first = false;
+ }
+ item.ToString(buffer);
+ }
+ buffer.Append("]");
+ break;
+ }
+ case JsonValueKind.Object:
+ {
+ buffer.Append("{");
+ var first = true;
+ foreach (var property in EnumerateObject())
+ {
+ if (!first)
+ {
+ buffer.Append(",");
+ }
+ else
+ {
+ first = false;
+ }
+ buffer.Append(JsonSerializer.Serialize(property.Key));
+ buffer.Append(":");
+ property.Value.ToString(buffer);
+ }
+ buffer.Append("}");
+ break;
+ }
+ default:
+ {
+ buffer.Append(JsonSerializer.Serialize(_item, (JsonSerializerOptions)null));
+ break;
+ }
+ }
+ }
+
+ internal JsonDocument ToJsonDocument()
+ {
+ var json = ToString();
+ return JsonDocument.Parse(json);
+ }
+ }
+
+} // namespace JsonCons.Utilities
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs
new file mode 100644
index 00000000..b40ab7c6
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ ///
+ /// Compares two instances.
+ ///
+
+ public sealed class JsonElementComparer : IComparer, System.Collections.IComparer
+ {
+ /// Gets a singleton instance of . This property is read-only.
+ public static JsonElementComparer Instance { get; } = new();
+
+ ///
+ /// Constructs a
+ ///
+ public JsonElementComparer() {}
+
+ ///
+ /// Compares two instances.
+ ///
+ /// If the two instances have different data types, they are
+ /// compared according to their ValueKind property, which gives this ordering:
+ ///
+ /// Undefined
+ /// Object
+ /// Array
+ /// String
+ /// Number
+ /// True
+ /// False
+ /// Null
+ ///
+ ///
+ /// If both instances are null, true, or false, they are equal.
+ ///
+ /// If both are strings, they are compared with the String.CompareTo method.
+ ///
+ /// If both are numbers, and both can be represented by a ,
+ /// they are compared with the Decimal.CompareTo method, otherwise they are
+ /// compared as doubles.
+ ///
+ /// If both are objects, they are compared accoring to the following rules:
+ ///
+ ///
+ /// - Order each object's properties by name and compare sequentially.
+ /// The properties are compared first by name with the String.CompareTo method, then by value with
+ /// - The first mismatching property defines which instance is less or greater than the other.
+ /// - If the two sequences have no mismatching properties until one of them ends, and the other is longer, the shorter sequence is less than the other.
+ /// - If the two sequences have no mismatching properties and have the same length, they are equal.
+ ///
+ ///
+ /// If both are arrays, they are compared element wise with .
+ /// The first mismatching element defines which instance is less or greater than the other.
+ /// If the two arrays have no mismatching elements until one of them ends, and the other is longer, the shorter array is less than the other.
+ /// If the two arrays have no mismatching elements and have the same length, they are equal.
+ ///
+ ///
+ /// The first object of type cref="JsonElement"/> to compare.
+ /// The second object of type cref="JsonElement"/> to compare.
+ ///
+ ///
+ /// Unable to compare numbers as either or double (shouldn't happen.)
+ ///
+ public int Compare(JsonElement lhs, JsonElement rhs)
+ {
+ if (lhs.ValueKind != rhs.ValueKind)
+ return (int)lhs.ValueKind - (int)rhs.ValueKind;
+
+ switch (lhs.ValueKind)
+ {
+ case JsonValueKind.Null:
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ case JsonValueKind.Undefined:
+ return 0;
+
+ case JsonValueKind.Number:
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ return dec1.CompareTo(dec2);
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ return val1.CompareTo(val2);
+ }
+ else
+ {
+ throw new InvalidOperationException("Unable to compare numbers");
+ }
+ }
+
+ case JsonValueKind.String:
+ {
+ return string.Compare(lhs.GetString(), rhs.GetString());
+ }
+
+ case JsonValueKind.Array:
+ {
+ var enumerator1 = lhs.EnumerateArray();
+ var enumerator2 = rhs.EnumerateArray();
+ var result1 = enumerator1.MoveNext();
+ var result2 = enumerator2.MoveNext();
+ while (result1 && result2)
+ {
+ var diff = Compare(enumerator1.Current, enumerator2.Current);
+ if (diff != 0)
+ {
+ return diff;
+ }
+ result1 = enumerator1.MoveNext();
+ result2 = enumerator2.MoveNext();
+ }
+ return result1 ? 1 : result2 ? -1 : 0;
+ }
+
+ case JsonValueKind.Object:
+ {
+ // OrderBy performs a stable sort (Note that supports duplicate property names)
+ var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+
+ var result1 = enumerator1.MoveNext();
+ var result2 = enumerator2.MoveNext();
+ while (result1 && result2)
+ {
+ if (enumerator1.Current.Name != enumerator2.Current.Name)
+ {
+ return enumerator1.Current.Name.CompareTo(enumerator2.Current.Name);
+ }
+ var diff = Compare(enumerator1.Current.Value, enumerator2.Current.Value);
+ if (diff != 0)
+ {
+ return diff;
+ }
+ result1 = enumerator1.MoveNext();
+ result2 = enumerator2.MoveNext();
+ }
+
+ return result1 ? 1 : result2 ? -1 : 0;
+ }
+
+ default:
+ throw new InvalidOperationException(string.Format("Unknown JsonValueKind {0}", lhs.ValueKind));
+ }
+ }
+
+ int System.Collections.IComparer.Compare(object x, object y)
+ {
+ return Compare((JsonElement)x, (JsonElement)y);
+ }
+ }
+
+
+} // namespace JsonCons.JsonPath
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs
new file mode 100644
index 00000000..f8e7bfd7
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ ///
+ /// Compares two instances for equality by using value-based comparison.
+ ///
+
+ public sealed class JsonElementEqualityComparer : IEqualityComparer
+ {
+ /// Gets a singleton instance of . This property is read-only.
+ public static JsonElementEqualityComparer Instance { get; } = new();
+
+ private int MaxHashDepth { get; } = 64;
+
+ private JsonElementEqualityComparer() {}
+
+ ///
+ /// Determines whether the provided objects are equal.
+ ///
+ /// If the two instances have different data types, they are different.
+ ///
+ /// If both instances are null, true, or false, they are equal.
+ ///
+ /// If both are strings, they are compared with the String.Equals method.
+ ///
+ /// If both are numbers, and both can be represented by a ,
+ /// they are compared with the Decimal.Equals method, otherwise they are
+ /// compared as doubles.
+ ///
+ /// If both are objects, they are compared accoring to the following rules:
+ ///
+ ///
+ /// - If the two objects have a different number of properties, they are different.
+ /// - Otherwise, order each object's properties by name and compare sequentially.
+ /// The properties are compared first by name with the String.Equals method, then by value with
+ /// - A mismatching property means the two instance are different.
+ ///
+ ///
+ /// If both are arrays, and both have the same length and compare equal element wise with ,
+ /// they are equal, otherwise they are different.
+ ///
+ /// The first object of type cref="JsonElement"/> to compare.
+ /// The second object of type cref="JsonElement"/> to compare.
+ ///
+ ///
+ /// Unable to compare numbers as either or double (shouldn't happen.)
+ ///
+ public bool Equals(JsonElement lhs, JsonElement rhs)
+ {
+ if (lhs.ValueKind != rhs.ValueKind)
+ return false;
+
+ switch (lhs.ValueKind)
+ {
+ case JsonValueKind.Null:
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ case JsonValueKind.Undefined:
+ return true;
+
+ case JsonValueKind.Number:
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ return dec1.Equals(dec2);
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ return val1 == val2;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ case JsonValueKind.String:
+ {
+ var str = lhs.GetString() ?? throw new InvalidOperationException("string cannot be null");
+ return str.Equals(rhs.GetString());
+ }
+
+ case JsonValueKind.Array:
+ return lhs.EnumerateArray().SequenceEqual(rhs.EnumerateArray(), this);
+
+ case JsonValueKind.Object:
+ {
+ // OrderBy performs a stable sort (Note that supports duplicate property names)
+ var baseEnumerator1 = lhs.EnumerateObject();
+ var baseEnumerator2 = rhs.EnumerateObject();
+ if (baseEnumerator1.Count() != baseEnumerator2.Count())
+ {
+ return false;
+ }
+
+ var enumerator1 = baseEnumerator1.OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ var enumerator2 = baseEnumerator2.OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+
+ var result1 = enumerator1.MoveNext();
+ var result2 = enumerator2.MoveNext();
+ while (result1 && result2)
+ {
+ if (enumerator1.Current.Name != enumerator2.Current.Name)
+ {
+ return false;
+ }
+ if (!(Equals(enumerator1.Current.Value,enumerator2.Current.Value)))
+ {
+ return false;
+ }
+ result1 = enumerator1.MoveNext();
+ result2 = enumerator2.MoveNext();
+ }
+
+ return result1 == false && result2 == false;
+ }
+
+ default:
+ throw new InvalidOperationException(string.Format("Unknown JsonValueKind {0}", lhs.ValueKind));
+ }
+ }
+
+ ///
+ /// Returns a hash code for the specified value.
+ ///
+ ///
+ /// An Int32 value representing the hash code of the value.
+ public int GetHashCode(JsonElement value)
+ {
+ return ComputeHashCode(value, 0);
+ }
+
+ private int ComputeHashCode(JsonElement element, int depth)
+ {
+ var hashCode = element.ValueKind.GetHashCode();
+
+ switch (element.ValueKind)
+ {
+ case JsonValueKind.Null:
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ case JsonValueKind.Undefined:
+ break;
+
+ case JsonValueKind.Number:
+ hashCode += 17*element.GetDouble().GetHashCode();
+ break;
+
+ case JsonValueKind.String:
+ {
+ var str = element.GetString() ?? throw new InvalidOperationException("string cannot be null");
+ hashCode += 17 * str.GetHashCode();
+ break;
+ }
+
+ case JsonValueKind.Array:
+ if (depth < MaxHashDepth)
+ foreach (var item in element.EnumerateArray())
+ hashCode += 17*ComputeHashCode(item, depth+1);
+ break;
+
+ case JsonValueKind.Object:
+ foreach (var property in element.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal))
+ {
+ hashCode += 17*property.Name.GetHashCode();
+ if (depth < MaxHashDepth)
+ hashCode += 17*ComputeHashCode(property.Value, depth+1);
+ }
+ break;
+
+ default:
+ throw new InvalidOperationException(string.Format("Unknown JsonValueKind {0}", element.ValueKind));
+ }
+ return hashCode;
+ }
+ }
+
+
+} // namespace JsonCons.JsonPath
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs
new file mode 100644
index 00000000..b5b8510b
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs
@@ -0,0 +1,503 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ ///
+ /// Defines how the unflatten operation handles integer tokens in a JSON Pointer
+ ///
+ ///
+ /// This example illustrates the use of
+ ///
+ /// using System;
+ /// using System.Diagnostics;
+ /// using System.Text.Json;
+ /// using JsonCons.Utilities;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// using var doc = JsonDocument.Parse(@"
+ /// {
+ /// ""discards"": {
+ /// ""1000"": ""Record does not exist"",
+ /// ""1004"": ""Queue limit exceeded"",
+ /// ""1010"": ""Discarding timed-out partial msg""
+ /// },
+ /// ""warnings"": {
+ /// ""0"": ""Phone number missing country code"",
+ /// ""1"": ""State code missing"",
+ /// ""2"": ""Zip code missing""
+ /// }
+ /// }
+ /// ");
+ ///
+ /// var options = new JsonSerializerOptions() { WriteIndented = true };
+ ///
+ /// using JsonDocument flattened = JsonFlattener.Flatten(doc.RootElement);
+ /// Console.WriteLine("The flattened document:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(flattened, options)}\n");
+ ///
+ /// Console.WriteLine("Unflatten integer tokens as array indices if possible:\n");
+ /// using JsonDocument unflattened1 = JsonFlattener.Unflatten(flattened.RootElement,
+ /// IntegerTokenUnflattening.TryIndex);
+ /// Console.WriteLine($"{JsonSerializer.Serialize(unflattened1, options)}\n");
+ ///
+ /// Console.WriteLine("Always unflatten integer tokens as object names:\n");
+ /// using JsonDocument unflattened2 = JsonFlattener.Unflatten(flattened.RootElement,
+ /// IntegerTokenUnflattening.AssumeName);
+ /// Console.WriteLine($"{JsonSerializer.Serialize(unflattened2, options)}\n");
+ /// }
+ /// }
+ ///
+ /// Output:
+ ///
+ /// The flattened document:
+ ///
+ /// {
+ /// "/discards/1000": "Record does not exist",
+ /// "/discards/1004": "Queue limit exceeded",
+ /// "/discards/1010": "Discarding timed-out partial msg",
+ /// "/warnings/0": "Phone number missing country code",
+ /// "/warnings/1": "State code missing",
+ /// "/warnings/2": "Zip code missing"
+ /// }
+ ///
+ /// Unflatten integer tokens as array indices if possible:
+ ///
+ /// {
+ /// "discards": {
+ /// "1000": "Record does not exist",
+ /// "1004": "Queue limit exceeded",
+ /// "1010": "Discarding timed-out partial msg"
+ /// },
+ /// "warnings": [
+ /// "Phone number missing country code",
+ /// "State code missing",
+ /// "Zip code missing"
+ /// ]
+ /// }
+ ///
+ /// Always unflatten integer tokens as object names:
+ ///
+ /// {
+ /// "discards": {
+ /// "1000": "Record does not exist",
+ /// "1004": "Queue limit exceeded",
+ /// "1010": "Discarding timed-out partial msg"
+ /// },
+ /// "warnings": {
+ /// "0": "Phone number missing country code",
+ /// "1": "State code missing",
+ /// "2": "Zip code missing"
+ /// }
+ /// }
+ ///
+ ///
+ public enum IntegerTokenUnflattening {
+ ///
+ /// The unflatten operation first tries to unflatten into a JSON array
+ /// using the integer tokens as sequential indices, and if that fails, unflattens into
+ /// a JSON object using the integer tokens as names.
+ ///
+ TryIndex,
+ ///
+ /// The unflatten operation always unflattens into a JSON object
+ /// using the integer tokens as names.
+ ///
+ AssumeName
+ }
+
+ ///
+ /// Provides functionality to flatten a JSON object or array to a single depth JSON object of JSON Pointer-value pairs,
+ /// and to unflatten a flattened JSON object.
+ ///
+ ///
+ /// This example shows how to flatten and unflatten a JSON value
+ ///
+ /// using System;
+ /// using System.Diagnostics;
+ /// using System.Text.Json;
+ /// using JsonCons.Utilities;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// using var doc = JsonDocument.Parse(@"
+ /// {
+ /// ""application"": ""hiking"",
+ /// ""reputons"": [
+ /// {
+ /// ""rater"": ""HikingAsylum"",
+ /// ""assertion"": ""advanced"",
+ /// ""rated"": ""Marilyn C"",
+ /// ""rating"": 0.90
+ /// },
+ /// {
+ /// ""rater"": ""HikingAsylum"",
+ /// ""assertion"": ""intermediate"",
+ /// ""rated"": ""Hongmin"",
+ /// ""rating"": 0.75
+ /// }
+ /// ]
+ /// }
+ /// ");
+ ///
+ /// using JsonDocument flattened = JsonFlattener.Flatten(doc.RootElement);
+ ///
+ /// var options = new JsonSerializerOptions() { WriteIndented = true };
+ ///
+ /// Console.WriteLine($"{JsonSerializer.Serialize(flattened, options)}\n");
+ ///
+ /// using JsonDocument unflattened = JsonFlattener.Unflatten(flattened.RootElement);
+ ///
+ /// var comparer = JsonElementEqualityComparer.Instance;
+ /// Debug.Assert(comparer.Equals(unflattened.RootElement,doc.RootElement));
+ /// }
+ /// }
+ ///
+ /// Output:
+ ///
+ /// {
+ /// "/application": "hiking",
+ /// "/reputons/0/rater": "HikingAsylum",
+ /// "/reputons/0/assertion": "advanced",
+ /// "/reputons/0/rated": "Marilyn C",
+ /// "/reputons/0/rating": 0.90,
+ /// "/reputons/1/rater": "HikingAsylum",
+ /// "/reputons/1/assertion": "intermediate",
+ /// "/reputons/1/rated": "Hongmin",
+ /// "/reputons/1/rating": 0.75
+ /// }
+ ///
+ ///
+
+ public static class JsonFlattener
+ {
+ ///
+ /// Converts a JSON object or array into a single depth JSON object of name-value pairs,
+ /// such that the names are JSON Pointer strings, and the values are either string,
+ /// number, true, false, null, empty object, or empty array.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The value to be flattened.
+ /// The flattened value
+ public static JsonDocument Flatten(JsonElement value)
+ {
+ var result = new JsonDocumentBuilder(JsonValueKind.Object);
+ var parentKey = "";
+ _Flatten(parentKey, value, result);
+ return result.ToJsonDocument();
+ }
+
+ ///
+ /// Recovers the orginal JSON value from a JSON object in flattened form, to the extent possible.
+ /// There may not be a unique solution, an integer token in a JSON Pointer could be an array index or
+ /// it could be an object name. The default behavior is to attempt to recover arrays. The
+ /// parameter can be used to recover objects with integer names instead.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The flattened value, which must be a JSON object of name-value pairs, such that
+ /// the names are JSON Pointer strings, and the values are either string,
+ /// number, true, false, null, empty object, or empty array.
+ /// Options for handling integer tokens in the JSON Pointer.
+ /// The unflattened value
+ ///
+ /// The is not a JSON object, or has a name that contains an invalid JSON pointer.
+ ///
+ public static JsonDocument Unflatten(JsonElement flattenedValue,
+ IntegerTokenUnflattening options = IntegerTokenUnflattening.TryIndex)
+ {
+ if (options == IntegerTokenUnflattening.TryIndex)
+ {
+ if (TryUnflattenArray(flattenedValue, out var val))
+ {
+ return val.ToJsonDocument();
+ }
+ else
+ {
+ return UnflattenToObject(flattenedValue, options).ToJsonDocument();
+ }
+ }
+ else
+ {
+ return UnflattenToObject(flattenedValue, options).ToJsonDocument();
+ }
+ }
+
+ private static void _Flatten(string parentKey,
+ JsonElement parentValue,
+ JsonDocumentBuilder result)
+ {
+ switch (parentValue.ValueKind)
+ {
+ case JsonValueKind.Array:
+ {
+ if (parentValue.GetArrayLength() == 0)
+ {
+ result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue));
+ }
+ else
+ {
+ for (var i = 0; i < parentValue.GetArrayLength(); ++i)
+ {
+ var buffer = new StringBuilder(parentKey);
+ buffer.Append('/');
+ buffer.Append(i.ToString());
+ _Flatten(buffer.ToString(), parentValue[i], result);
+ }
+ }
+ break;
+ }
+
+ case JsonValueKind.Object:
+ {
+ if (parentValue.EnumerateObject().Count() == 0)
+ {
+ result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue));
+ }
+ else
+ {
+ foreach (var item in parentValue.EnumerateObject())
+ {
+ var buffer = new StringBuilder(parentKey);
+ buffer.Append('/');
+ buffer.Append(JsonPointer.Escape(item.Name));
+ _Flatten(buffer.ToString(), item.Value, result);
+ }
+ }
+ break;
+ }
+
+ default:
+ {
+ result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue));
+ break;
+ }
+ }
+ }
+
+ // unflatten
+
+ private static JsonDocumentBuilder SafeUnflatten(JsonDocumentBuilder value)
+ {
+ if (value.ValueKind != JsonValueKind.Object || value.GetObjectLength() == 0)
+ {
+ return value;
+ }
+ var safe = true;
+ var index = 0;
+ foreach (var item in value.EnumerateObject())
+ {
+ if (!int.TryParse(item.Key, out var n) || index++ != n)
+ {
+ safe = false;
+ break;
+ }
+ }
+
+ if (safe)
+ {
+ var j = new JsonDocumentBuilder(JsonValueKind.Array);
+ foreach (var item in value.EnumerateObject())
+ {
+ j.AddArrayItem(item.Value);
+ }
+ var a = new JsonDocumentBuilder(JsonValueKind.Array);
+ foreach (var item in j.EnumerateArray())
+ {
+ a.AddArrayItem(SafeUnflatten(item));
+ }
+ return a;
+ }
+ else
+ {
+ var o = new JsonDocumentBuilder(JsonValueKind.Object);
+ foreach (var item in value.EnumerateObject())
+ {
+ //if (!o.ContainsPropertyName(item.Key))
+ //{
+ // o.AddProperty(item.Key, SafeUnflatten (item.Value));
+ //}
+ o.TryAddProperty(item.Key, SafeUnflatten (item.Value));
+ }
+ return o;
+ }
+ }
+
+ private static bool TryUnflattenArray(JsonElement value, out JsonDocumentBuilder result)
+ {
+ if (value.ValueKind != JsonValueKind.Object)
+ {
+ throw new ArgumentException("The value to unflatten is not a JSON object");
+ }
+
+ result = new JsonDocumentBuilder(JsonValueKind.Object);
+
+ foreach (var item in value.EnumerateObject())
+ {
+ JsonDocumentBuilder? parent = null;
+ var part = result;
+ var parentIndex = 0;
+ var parentName = "";
+
+ if (!JsonPointer.TryParse(item.Name, out var ptr))
+ {
+ throw new ArgumentException("Name contains invalid JSON Pointer");
+ }
+ var index = 0;
+
+ var it = ptr.GetEnumerator();
+ var more = it.MoveNext();
+ while (more)
+ {
+ var token = it.Current;
+
+ if (int.TryParse(token, out var n) && index++ == n)
+ {
+ if (part.ValueKind != JsonValueKind.Array)
+ {
+ if (parent != null && parent.ValueKind == JsonValueKind.Object)
+ {
+ parent.RemoveProperty(parentName);
+ var val = new JsonDocumentBuilder(JsonValueKind.Array);
+ parent.AddProperty(parentName, val);
+ part = val;
+ }
+ else if (parent != null && parent.ValueKind == JsonValueKind.Array)
+ {
+ var val = new JsonDocumentBuilder(JsonValueKind.Array);
+ parent[parentIndex] = val;
+ part = val;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ parent = part;
+ parentIndex = n;
+ parentName = token;
+ more = it.MoveNext();
+ if (more)
+ {
+ if (n >= part.GetArrayLength())
+ {
+ part.AddArrayItem(new JsonDocumentBuilder(JsonValueKind.Object));
+ part = part[part.GetArrayLength() - 1];
+ }
+ else
+ {
+ part = part[n];
+ }
+ }
+ else
+ {
+ part.AddArrayItem(new JsonDocumentBuilder(item.Value));
+ part = part[part.GetArrayLength() - 1];
+ }
+ }
+ else if (part.ValueKind == JsonValueKind.Object)
+ {
+ more = it.MoveNext();
+ if (more)
+ {
+ if (part.TryGetProperty(token, out var val))
+ {
+ part = val;
+ }
+ else
+ {
+ val = new JsonDocumentBuilder(JsonValueKind.Object);
+ part.AddProperty(token,val);
+ part = val;
+ }
+ }
+ else
+ {
+ if (part.TryGetProperty(token, out var val))
+ {
+ part = val;
+ }
+ else
+ {
+ val = new JsonDocumentBuilder(item.Value);
+ part.AddProperty(token,val);
+ part = val;
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static JsonDocumentBuilder UnflattenToObject(JsonElement value, IntegerTokenUnflattening options = IntegerTokenUnflattening.TryIndex)
+ {
+ if (value.ValueKind != JsonValueKind.Object)
+ {
+ throw new ArgumentException("The value to unflatten is not a JSON object");
+ }
+
+ var result = new JsonDocumentBuilder(JsonValueKind.Object);
+
+ foreach (var item in value.EnumerateObject())
+ {
+ var part = result;
+ if (!JsonPointer.TryParse(item.Name, out var ptr))
+ {
+ throw new ArgumentException("Name contains invalid JSON Pointer");
+ }
+ var it = ptr.GetEnumerator();
+ var more = it.MoveNext();
+ while (more)
+ {
+ var s = it.Current;
+ more = it.MoveNext();
+ if (more)
+ {
+ if (part.TryGetProperty(s, out var val))
+ {
+ part = val;
+ }
+ else
+ {
+ val = new JsonDocumentBuilder(JsonValueKind.Object);
+ part.AddProperty(s,val);
+ part = val;
+ }
+ }
+ else
+ {
+ if (part.TryGetProperty(s, out var val))
+ {
+ part = val;
+ }
+ else
+ {
+ val = new JsonDocumentBuilder(item.Value);
+ part.AddProperty(s,val);
+ part = val;
+ }
+ }
+ }
+ }
+
+ return options == IntegerTokenUnflattening.TryIndex ? SafeUnflatten (result) : result;
+ }
+ }
+
+} // namespace JsonCons.Utilities
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs
new file mode 100644
index 00000000..0f2d3a3e
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs
@@ -0,0 +1,213 @@
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ ///
+ /// Provides functionality for applying a JSON Merge Patch as
+ /// defined in RFC 7396
+ /// to a JSON value.
+ ///
+ ///
+ /// The following example borrowed from [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) shows how to apply a JSON Merge Patch to a JSON value
+ ///
+ /// using System;
+ /// using System.Diagnostics;
+ /// using System.Text.Json;
+ /// using JsonCons.Utilities;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// using var doc = JsonDocument.Parse(@"
+ /// {
+ /// ""title"": ""Goodbye!"",
+ /// ""author"" : {
+ /// ""givenName"" : ""John"",
+ /// ""familyName"" : ""Doe""
+ /// },
+ /// ""tags"":[ ""example"", ""sample"" ],
+ /// ""content"": ""This will be unchanged""
+ /// }
+ /// ");
+ ///
+ /// using var patch = JsonDocument.Parse(@"
+ /// {
+ /// ""title"": ""Hello!"",
+ /// ""phoneNumber"": ""+01-123-456-7890"",
+ /// ""author"": {
+ /// ""familyName"": null
+ /// },
+ /// ""tags"": [ ""example"" ]
+ /// }
+ /// ");
+ ///
+ /// using JsonDocument result = JsonMergePatch.ApplyMergePatch(doc.RootElement, patch.RootElement);
+ ///
+ /// var options = new JsonSerializerOptions() { WriteIndented = true };
+ ///
+ /// Console.WriteLine("The original document:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(doc, options)}\n");
+ /// Console.WriteLine("The patch:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(patch, options)}\n");
+ /// Console.WriteLine("The result:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(result, options)}\n");
+ /// ");
+ /// }
+ /// }
+ ///
+ /// The original document:
+ ///
+ ///
+ /// {
+ /// "title": "Goodbye!",
+ /// "author": {
+ /// "givenName": "John",
+ /// "familyName": "Doe"
+ /// },
+ /// "tags": [
+ /// "example",
+ /// "sample"
+ /// ],
+ /// "content": "This will be unchanged"
+ /// }
+ ///
+ ///
+ /// The patch:
+ ///
+ ///
+ /// {
+ /// "title": "Hello!",
+ /// "phoneNumber": "\u002B01-123-456-7890",
+ /// "author": {
+ /// "familyName": null
+ /// },
+ /// "tags": [
+ /// "example"
+ /// ]
+ /// }
+ ///
+ ///
+ /// The result:
+ ///
+ ///
+ /// {
+ /// "title": "Hello!",
+ /// "author": {
+ /// "givenName": "John"
+ /// },
+ /// "tags": [
+ /// "example"
+ /// ],
+ /// "content": "This will be unchanged",
+ /// "phoneNumber": "\u002B01-123-456-7890"
+ /// }
+ ///
+ ///
+
+ public static class JsonMergePatch
+ {
+ ///
+ /// Applies a JSON Merge Patch as defined in RFC 7396
+ /// to a source JSON value.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The source JSON value.
+ /// The JSON merge patch to be applied to the source JSON value.
+ /// The patched JSON value
+ public static JsonDocument ApplyMergePatch(JsonElement source, JsonElement patch)
+ {
+ var documentBuilder = new JsonDocumentBuilder(source);
+ var builder = ApplyMergePatch(ref documentBuilder, patch);
+ return builder.ToJsonDocument();
+ }
+
+ private static JsonDocumentBuilder ApplyMergePatch(ref JsonDocumentBuilder target, JsonElement patch)
+ {
+ if (patch.ValueKind == JsonValueKind.Object)
+ {
+ if (target.ValueKind != JsonValueKind.Object)
+ {
+ target = new JsonDocumentBuilder(JsonValueKind.Object);
+ }
+ foreach (var property in patch.EnumerateObject())
+ {
+ if (target.TryGetProperty(property.Name, out var item))
+ {
+ target.RemoveProperty(property.Name);
+ if (property.Value.ValueKind != JsonValueKind.Null)
+ {
+ target.AddProperty(property.Name, ApplyMergePatch(ref item, property.Value));
+ }
+ }
+ else if (property.Value.ValueKind != JsonValueKind.Null)
+ {
+ item = new JsonDocumentBuilder(JsonValueKind.Object);
+ target.AddProperty(property.Name, ApplyMergePatch(ref item, property.Value));
+ }
+ }
+ return target;
+ }
+ else
+ {
+ return new JsonDocumentBuilder(patch);
+ }
+ }
+
+ ///
+ /// Builds a JSON Merge Patch as defined in RFC 7396
+ /// given two JSON values, a source and a target.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The source JSON value.
+ /// The target JSON value.
+ /// A JSON Merge Patch to convert the source JSON value to the target JSON value
+ public static JsonDocument FromDiff(JsonElement source, JsonElement target)
+ {
+ return _FromDiff(source, target).ToJsonDocument();
+ }
+
+ private static JsonDocumentBuilder _FromDiff(JsonElement source, JsonElement target)
+ {
+ var comparer = JsonElementEqualityComparer.Instance;
+
+ if (source.ValueKind != JsonValueKind.Object || target.ValueKind != JsonValueKind.Object)
+ {
+ return new JsonDocumentBuilder(target);
+ }
+ var builder = new JsonDocumentBuilder(JsonValueKind.Object);
+
+ foreach (var property in source.EnumerateObject())
+ {
+ if (target.TryGetProperty(property.Name, out var value))
+ {
+ if (!comparer.Equals(property.Value,value))
+ {
+ builder.AddProperty(property.Name, _FromDiff(property.Value, value));
+ }
+ }
+ else
+ {
+ builder.AddProperty(property.Name, new JsonDocumentBuilder(JsonValueKind.Null));
+ }
+ }
+
+ foreach (var property in target.EnumerateObject())
+ {
+ JsonElement value;
+ if (!source.TryGetProperty(property.Name, out value))
+ {
+ builder.AddProperty(property.Name, new JsonDocumentBuilder(property.Value));
+ }
+ }
+
+ return builder;
+ }
+ }
+
+
+} // namespace JsonCons.Utilities
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs
new file mode 100644
index 00000000..bf1fc449
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs
@@ -0,0 +1,413 @@
+using System;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ ///
+ /// Captures error message and the operation that caused it.
+ ///
+ public sealed class JsonPatchException : Exception
+ {
+ ///
+ /// Constructs a .
+ ///
+ /// The operation that caused the error.
+ /// The error message.
+ public JsonPatchException(
+ string operation,
+ string message) : base(message)
+ {
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
+
+ Operation = operation;
+ }
+
+ ///
+ /// Gets the that caused the error.
+ ///
+ public string Operation { get; }
+ }
+
+ ///
+ /// Provides functionality for applying a JSON Patch as
+ /// defined in RFC 6902
+ /// to a JSON value.
+ ///
+ ///
+ /// The following example borrowed from [jsonpatch.com](http://jsonpatch.com/) shows how to apply a JSON Patch to a JSON value
+ ///
+ /// using System;
+ /// using System.Diagnostics;
+ /// using System.Text.Json;
+ /// using JsonCons.Utilities;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// using var doc = JsonDocument.Parse(@"
+ /// {
+ /// ""baz"": ""qux"",
+ /// ""foo"": ""bar""
+ /// }
+ /// ");
+ ///
+ /// using var patch = JsonDocument.Parse(@"
+ /// [
+ /// { ""op"": ""replace"", ""path"": ""/baz"", ""value"": ""boo"" },
+ /// { ""op"": ""add"", ""path"": ""/hello"", ""value"": [""world""] },
+ /// { ""op"": ""remove"", ""path"": ""/foo"" }
+ /// ]
+ /// ");
+ ///
+ /// using JsonDocument result = JsonPatch.ApplyPatch(doc.RootElement, patch.RootElement);
+ ///
+ /// var options = new JsonSerializerOptions() { WriteIndented = true };
+ ///
+ /// Console.WriteLine("The original document:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(doc, options)}\n");
+ /// Console.WriteLine("The patch:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(patch, options)}\n");
+ /// Console.WriteLine("The result:\n");
+ /// Console.WriteLine($"{JsonSerializer.Serialize(result, options)}\n");
+ /// ");
+ /// }
+ /// }
+ ///
+ /// The original document:
+ ///
+ ///
+ /// {
+ /// "baz": "qux",
+ /// "foo": "bar"
+ /// }
+ ///
+ ///
+ /// The patch:
+ ///
+ ///
+ /// [
+ /// {
+ /// "op": "replace",
+ /// "path": "/baz",
+ /// "value": "boo"
+ /// },
+ /// {
+ /// "op": "add",
+ /// "path": "/hello",
+ /// "value": [
+ /// "world"
+ /// ]
+ /// },
+ /// {
+ /// "op": "remove",
+ /// "path": "/foo"
+ /// }
+ /// ]
+ ///
+ ///
+ /// The result:
+ ///
+ /// {
+ /// "baz": "boo",
+ /// "hello": [
+ /// "world"
+ /// ]
+ /// }
+ ///
+ ///
+ public static class JsonPatch
+ {
+ ///
+ /// Applies a JSON Patch as defined in RFC 6902
+ /// to a source JSON value.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The source JSON value.
+ /// The patch to be applied to the source JSON value.
+ /// The patched JSON value
+ ///
+ /// The provided is invalid
+ ///
+ ///
+ /// A JSON Patch operation failed
+ ///
+ public static JsonDocument ApplyPatch(JsonElement source,
+ JsonElement patch)
+ {
+ var documentBuilder = new JsonDocumentBuilder(source);
+ ApplyPatch(ref documentBuilder, patch);
+ return documentBuilder.ToJsonDocument();
+ }
+
+ private static void ApplyPatch(ref JsonDocumentBuilder target,
+ JsonElement patch)
+ {
+ var comparer = JsonElementEqualityComparer.Instance;
+
+ Debug.Assert(target != null);
+
+ if (patch.ValueKind != JsonValueKind.Array)
+ {
+ throw new ArgumentException("Patch must be an array");
+ }
+
+ foreach (var operation in patch.EnumerateArray())
+ {
+ if (!operation.TryGetProperty("op", out var opElement))
+ {
+ throw new ArgumentException("Invalid patch");
+ }
+ var op = opElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null");
+
+ if (!operation.TryGetProperty("path", out var pathElement))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+ var path = pathElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null"); ;
+
+ if (!JsonPointer.TryParse(path, out var location))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+
+ if (op =="test")
+ {
+ if (!operation.TryGetProperty("value", out var value))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+
+ if (!location.TryGetValue(target, out var tested))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+
+ using (var doc = tested.ToJsonDocument())
+ {
+ if (!comparer.Equals(doc.RootElement, value))
+ {
+ throw new JsonPatchException(op, "Test failed");
+ }
+ }
+ }
+ else if (op =="add")
+ {
+ if (!operation.TryGetProperty("value", out var value))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+ var valueBuilder = new JsonDocumentBuilder(value);
+ if (!location.TryAddIfAbsent(ref target, valueBuilder)) // try insert without replace
+ {
+ if (!location.TryReplace(ref target, valueBuilder)) // try insert without replace
+ {
+ throw new JsonPatchException(op, "Add failed");
+ }
+ }
+ }
+ else if (op =="remove")
+ {
+ if (!location.TryRemove(ref target))
+ {
+ throw new JsonPatchException(op, "Add failed");
+ }
+ }
+ else if (op =="replace")
+ {
+ if (!operation.TryGetProperty("value", out var value))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+ var valueBuilder = new JsonDocumentBuilder(value);
+ if (!location.TryReplace(ref target, valueBuilder))
+ {
+ throw new JsonPatchException(op, "Replace failed");
+ }
+ }
+ else if (op =="move")
+ {
+ if (!operation.TryGetProperty("from", out var fromElement))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+ var from = fromElement.GetString() ?? throw new InvalidOperationException("From element cannot be null"); ;
+
+ if (!JsonPointer.TryParse(from, out var fromPointer))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+
+ if (!fromPointer.TryGetValue(target, out var value))
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
+
+ if (!fromPointer.TryRemove(ref target))
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
+ if (!location.TryAddIfAbsent(ref target, value))
+ {
+ if (!location.TryReplace(ref target, value)) // try insert without replace
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
+ }
+ }
+ else if (op =="copy")
+ {
+ if (!operation.TryGetProperty("from", out var fromElement))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+ var from = fromElement.GetString() ?? throw new InvalidOperationException("from cannot be null");
+ if (!JsonPointer.TryParse(from, out var fromPointer))
+ {
+ throw new ArgumentException(op, "Invalid patch");
+ }
+
+ if (!fromPointer.TryGetValue(target, out var value))
+ {
+ throw new JsonPatchException(op, "Copy failed");
+ }
+ if (!location.TryAddIfAbsent(ref target, value))
+ {
+ if (!location.TryReplace(ref target, value)) // try insert without replace
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Builds a JSON Patch as defined in RFC 6902
+ /// given two JSON values, a source and a target.
+ ///
+ ///
+ /// It is the users responsibilty to properly Dispose the returned value
+ ///
+ /// The source JSON value.
+ /// The target JSON value.
+ /// A JSON Merge Patch to convert the source JSON value to the target JSON value
+ public static JsonDocument FromDiff(JsonElement source,
+ JsonElement target)
+ {
+ return _FromDiff(source, target, "").ToJsonDocument();
+ }
+
+ private static JsonDocumentBuilder _FromDiff(JsonElement source,
+ JsonElement target,
+ string path)
+ {
+ var builder = new JsonDocumentBuilder(JsonValueKind.Array);
+
+ var comparer = JsonElementEqualityComparer.Instance;
+
+ if (comparer.Equals(source,target))
+ {
+ return builder;
+ }
+
+ if (source.ValueKind == JsonValueKind.Array && target.ValueKind == JsonValueKind.Array)
+ {
+ var common = Math.Min(source.GetArrayLength(),target.GetArrayLength());
+ for (var i = 0; i < common; ++i)
+ {
+ var buffer = new StringBuilder(path);
+ buffer.Append("/");
+ buffer.Append(i.ToString());
+ var temp_diff = _FromDiff(source[i], target[i], buffer.ToString());
+ foreach (var item in temp_diff.EnumerateArray())
+ {
+ builder.AddArrayItem(item);
+ }
+ }
+ // Element in source, not in target - remove
+ for (var i = source.GetArrayLength(); i-- > target.GetArrayLength();)
+ {
+ var buffer = new StringBuilder(path);
+ buffer.Append("/");
+ buffer.Append(i.ToString());
+ var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
+ valBuilder.AddProperty("op", new JsonDocumentBuilder("remove"));
+ valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
+ builder.AddArrayItem(valBuilder);
+ }
+ // Element in target, not in source - add,
+ for (var i = source.GetArrayLength(); i < target.GetArrayLength(); ++i)
+ {
+ var a = target[i];
+ var buffer = new StringBuilder(path);
+ buffer.Append("/");
+ buffer.Append(i.ToString());
+ var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
+ valBuilder.AddProperty("op", new JsonDocumentBuilder("add"));
+ valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
+ valBuilder.AddProperty("value", new JsonDocumentBuilder(a));
+ builder.AddArrayItem(valBuilder);
+ }
+ }
+ else if (source.ValueKind == JsonValueKind.Object && target.ValueKind == JsonValueKind.Object)
+ {
+ foreach (var a in source.EnumerateObject())
+ {
+ var buffer = new StringBuilder(path);
+ buffer.Append("/");
+ buffer.Append(JsonPointer.Escape(a.Name));
+
+ if (target.TryGetProperty(a.Name, out var element))
+ {
+ var temp_diff = _FromDiff(a.Value, element, buffer.ToString());
+ foreach (var item in temp_diff.EnumerateArray())
+ {
+ builder.AddArrayItem(item);
+ }
+ }
+ else
+ {
+ var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
+ valBuilder.AddProperty("op", new JsonDocumentBuilder("remove"));
+ valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
+ builder.AddArrayItem(valBuilder);
+ }
+ }
+ foreach (var a in target.EnumerateObject())
+ {
+ JsonElement element;
+ if (!source.TryGetProperty(a.Name, out element))
+ {
+ var buffer = new StringBuilder(path);
+ buffer.Append("/");
+ buffer.Append(JsonPointer.Escape(a.Name));
+ var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
+ valBuilder.AddProperty("op", new JsonDocumentBuilder("add"));
+ valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
+ valBuilder.AddProperty("value", new JsonDocumentBuilder(a.Value));
+ builder.AddArrayItem(valBuilder);
+ }
+ }
+ }
+ else
+ {
+ var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
+ valBuilder.AddProperty("op", new JsonDocumentBuilder("replace"));
+ valBuilder.AddProperty("path", new JsonDocumentBuilder(path));
+ valBuilder.AddProperty("value", new JsonDocumentBuilder(target));
+ builder.AddArrayItem(valBuilder);
+ }
+
+ return builder;
+ }
+ }
+
+} // namespace JsonCons.Utilities
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs
new file mode 100644
index 00000000..059dc613
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs
@@ -0,0 +1,576 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ ///
+ /// Represents a JSON Pointer as defined by RFC 6901
+ ///
+ ///
+ /// The following example shows how to get a value at a referenced location in a JSON document.
+ ///
+ /// using System;
+ /// using System.Diagnostics;
+ /// using System.Text.Json;
+ /// using JsonCons.Utilities;
+ ///
+ /// public class Example
+ /// {
+ /// public static void Main()
+ /// {
+ /// using var doc = JsonDocument.Parse(@"
+ /// [
+ /// { ""category"": ""reference"",
+ /// ""author"": ""Nigel Rees"",
+ /// ""title"": ""Sayings of the Century"",
+ /// ""price"": 8.95
+ /// },
+ /// { ""category"": ""fiction"",
+ /// ""author"": ""Evelyn Waugh"",
+ /// ""title"": ""Sword of Honour"",
+ /// ""price"": 12.99
+ /// }
+ /// ]
+ /// ");
+ ///
+ /// var options = new JsonSerializerOptions() { WriteIndented = true };
+ ///
+ /// JsonPointer pointer = JsonPointer.Parse("/1/author");
+ ///
+ /// JsonElement element;
+ ///
+ /// if (pointer.TryGetValue(doc.RootElement, out element))
+ /// {
+ /// Console.WriteLine($"{JsonSerializer.Serialize(element, options)}\n");
+ /// }
+ /// }
+ /// }
+ ///
+ /// Output:
+ ///
+ ///
+ /// "Evelyn Waugh"
+ ///
+ ///
+
+ public sealed class JsonPointer : IEnumerable, IEquatable
+ {
+ /// Gets a singleton instance of a to the root value of a JSON document.
+ public static JsonPointer Default {get;} = new();
+
+ private enum JsonPointerState {Start, Escaped, Delim}
+
+ ///
+ /// Returns a list of (unescaped) reference tokens
+ ///
+ public IReadOnlyList Tokens {get;}
+
+ ///
+ /// Constructs a JSON Pointer to the root value of a JSON document
+ ///
+
+ public JsonPointer()
+ {
+ Tokens = new List();
+ }
+
+ ///
+ /// Constructs a JSON Pointer from a list of (unescaped) reference tokens
+ ///
+ /// A list of (unescaped) reference tokens.
+
+ public JsonPointer(IReadOnlyList tokens)
+ {
+ Tokens = tokens;
+ }
+
+ ///
+ /// Parses a JSON Pointer represented as a string value or a
+ /// fragment identifier (starts with #) into a .
+ ///
+ /// A JSON Pointer represented as a string or a fragment identifier.
+ /// A .
+ ///
+ /// The is .
+ ///
+ ///
+ /// The is invalid.
+ ///
+ public static JsonPointer Parse(string input)
+ {
+ if (!TryParse(input, out var pointer))
+ {
+ throw new ArgumentException("The provided JSON Pointer is invalid.");
+ }
+ return pointer;
+ }
+
+ ///
+ /// Parses a JSON Pointer represented as a string value or a
+ /// fragment identifier (starts with #) into a .
+ ///
+ /// A JSON Pointer represented as a string or a fragment identifier.
+ /// The JsonPointer.
+ /// true if the input string can be parsed into a list of reference tokens, false otherwise.
+ ///
+ /// The is .
+ ///
+ public static bool TryParse(string input, out JsonPointer pointer)
+ {
+ if (input == null)
+ {
+ throw new ArgumentNullException(nameof(input));
+ }
+ var tokens = new List();
+
+ if (input.Length == 0 || input.Equals("#"))
+ {
+ pointer = new JsonPointer(tokens);
+ return true;
+ }
+
+ var state = JsonPointerState.Start;
+ var index = 0;
+ var buffer = new StringBuilder();
+
+ if (input[0] == '#')
+ {
+ input = Uri.UnescapeDataString(input);
+ index = 1;
+ }
+
+ while (index < input.Length)
+ {
+ var done = false;
+ while (index < input.Length && !done)
+ {
+ switch (state)
+ {
+ case JsonPointerState.Start:
+ switch (input[index])
+ {
+ case '/':
+ state = JsonPointerState.Delim;
+ break;
+ default:
+ pointer = Default;
+ return false;
+ };
+ break;
+ case JsonPointerState.Delim:
+ switch (input[index])
+ {
+ case '/':
+ done = true;
+ break;
+ case '~':
+ state = JsonPointerState.Escaped;
+ break;
+ default:
+ buffer.Append(input[index]);
+ break;
+ };
+ break;
+ case JsonPointerState.Escaped:
+ switch (input[index])
+ {
+ case '0':
+ buffer.Append('~');
+ state = JsonPointerState.Delim;
+ break;
+ case '1':
+ buffer.Append('/');
+ state = JsonPointerState.Delim;
+ break;
+ default:
+ pointer = Default;
+ return false;
+ };
+ break;
+ default:
+ pointer = Default;
+ return false;
+ }
+ ++index;
+ }
+ tokens.Add(buffer.ToString());
+ buffer.Clear();
+ }
+ if (buffer.Length > 0)
+ {
+ tokens.Add(buffer.ToString());
+ }
+ pointer = new JsonPointer(tokens);
+ return true;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a list of reference tokens.
+ ///
+ /// An IEnumerator<string> for a list of reference tokens.
+ public IEnumerator GetEnumerator()
+ {
+ return Tokens.GetEnumerator();
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return (System.Collections.IEnumerator) GetEnumerator();
+ }
+
+ ///
+ /// Returns a JSON Pointer represented as a string value.
+ ///
+ /// A JSON Pointer represented as a string value.
+
+ public override string ToString()
+ {
+ var buffer = new StringBuilder();
+ foreach (var token in Tokens)
+ {
+ buffer.Append("/");
+ Escape(token, buffer);
+ }
+ return buffer.ToString();
+ }
+
+ ///
+ /// Returns a string representing the JSON Pointer as a URI fragment identifier
+ ///
+ /// A JSON Pointer represented as a fragment identifier.
+
+ public string ToUriFragment()
+ {
+ var buffer = new StringBuilder();
+
+ buffer.Append("#");
+ foreach (var token in Tokens)
+ {
+ buffer.Append("/");
+ var s = Uri.EscapeUriString(token);
+ var span = s.AsSpan();
+ for (var i = 0; i < span.Length; ++i)
+ {
+ var c = span[i];
+ switch (c)
+ {
+ case '~':
+ buffer.Append('~');
+ buffer.Append('0');
+ break;
+ case '/':
+ buffer.Append('~');
+ buffer.Append('1');
+ break;
+ default:
+ buffer.Append(c);
+ break;
+ }
+ }
+ }
+ return buffer.ToString();
+ }
+
+ ///
+ /// Determines whether two JSONPointer objects have the same value.
+ ///
+ ///
+ /// true if other is a and has exactly the same reference tokens as this instance; otherwise, false.
+ /// If other is null, the method returns false.
+ public bool Equals(JsonPointer other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+ if (Tokens.Count != other.Tokens.Count)
+ {
+ return false;
+ }
+ for (var i = 0; i < Tokens.Count; ++i)
+ {
+ if (!Tokens[i].Equals(other.Tokens[i]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ ///
+ /// Determines whether this instance and a specified object, which must also be a JSONPointer object, have the same value.
+ ///
+ ///
+ ///
+ public override bool Equals(object other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return Equals((JsonPointer)other);
+ }
+
+ ///
+ /// Returns the hash code for this
+ ///
+ /// A 32-bit signed integer hash code.
+ ///
+ public override int GetHashCode()
+ {
+ var hashCode = Tokens.GetHashCode();
+ foreach (var token in Tokens)
+ {
+ hashCode += 17*token.GetHashCode();
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Returns true if the provided contains a value at the referenced location.
+ ///
+ /// The root that is to be queried.
+ /// true if the provided contains a value at the referenced location, otherwise false.
+ public bool ContainsValue(JsonElement root)
+ {
+ var value = root;
+
+ foreach (var token in Tokens)
+ {
+ if (value.ValueKind == JsonValueKind.Array)
+ {
+ if (token == "-")
+ {
+ return false;
+ }
+
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index >= value.GetArrayLength())
+ {
+ return false;
+ }
+ value = value[index];
+ }
+ else if (value.ValueKind == JsonValueKind.Object)
+ {
+ if (!value.TryGetProperty(token, out value))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Returns true if the provided contains a value at the referenced location.
+ ///
+ /// The root that is to be queried.
+ /// The JSON string or URI Fragment representation of the JSON pointer.
+ /// true if the provided contains a value at the referenced location, otherwise false.
+ ///
+ /// The is .
+ ///
+ public static bool ContainsValue(JsonElement root, string pointer)
+ {
+ if (pointer == null)
+ {
+ throw new ArgumentNullException(nameof(pointer));
+ }
+
+ if (!TryParse(pointer, out var location))
+ {
+ return false;
+ }
+ var value = root;
+
+ foreach (var token in location.Tokens)
+ {
+ if (value.ValueKind == JsonValueKind.Array)
+ {
+ if (token == "-")
+ {
+ return false;
+ }
+
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index >= value.GetArrayLength())
+ {
+ return false;
+ }
+ value = value[index];
+ }
+ else if (value.ValueKind == JsonValueKind.Object)
+ {
+ if (!value.TryGetProperty(token, out value))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Gets the value at the referenced location in the provided .
+ ///
+ /// The root that is to be queried.
+ /// Contains the value at the referenced location, if found.
+ /// true if the value was found at the referenced location, otherwise false.
+ public bool TryGetValue(JsonElement root, out JsonElement value)
+ {
+ value = root;
+
+ foreach (var token in Tokens)
+ {
+ if (value.ValueKind == JsonValueKind.Array)
+ {
+ if (token == "-")
+ {
+ return false;
+ }
+
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index >= value.GetArrayLength())
+ {
+ return false;
+ }
+ value = value[index];
+ }
+ else if (value.ValueKind == JsonValueKind.Object)
+ {
+ if (!value.TryGetProperty(token, out value))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Creates a new JsonPointer by appending a name token to the provided JsonPointer.
+ ///
+ /// The provided JsonPointer
+ /// A name token
+ /// A new JsonPointer
+ public static JsonPointer Append(JsonPointer pointer, string token)
+ {
+ var tokens = new List(pointer.Tokens);
+ tokens.Add(token);
+ return new JsonPointer(tokens);
+ }
+
+ ///
+ /// Creates a new JsonPointer by appending an index token to the provided JsonPointer.
+ ///
+ /// The provided JsonPointer
+ /// An index token
+ /// A new JsonPointer
+ public static JsonPointer Append(JsonPointer pointer, int token)
+ {
+ var tokens = new List(pointer.Tokens);
+ tokens.Add(token.ToString());
+ return new JsonPointer(tokens);
+ }
+
+ ///
+ /// Gets the value at the referenced location in the provided .
+ ///
+ /// The root that is to be queried.
+ /// The JSON string or URI Fragment representation of the JSON pointer.
+ /// Contains the value at the referenced location, if found.
+ /// true if the value was found at the referenced location, otherwise false.
+ ///
+ /// The is .
+ ///
+ public static bool TryGetValue(JsonElement root, string pointer, out JsonElement value)
+ {
+ if (pointer == null)
+ {
+ throw new ArgumentNullException(nameof(pointer));
+ }
+
+ if (!TryParse(pointer, out var location))
+ {
+ value = root;
+ return false;
+ }
+
+ return location.TryGetValue(root, out value);
+ }
+
+ ///
+ /// Escapes a JSON Pointer token
+ ///
+ ///
+ ///
+ ///
+ /// The is .
+ ///
+ public static string Escape(string token)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ var buffer = new StringBuilder();
+ Escape(token, buffer);
+ return buffer.ToString();
+ }
+
+ private static void Escape(string token, StringBuilder buffer)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ foreach (var c in token)
+ {
+ switch (c)
+ {
+ case '~':
+ buffer.Append('~');
+ buffer.Append('0');
+ break;
+ case '/':
+ buffer.Append('~');
+ buffer.Append('1');
+ break;
+ default:
+ buffer.Append(c);
+ break;
+ }
+ }
+ }
+ }
+
+} // namespace JsonCons.Utilities
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
new file mode 100644
index 00000000..a729c44e
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
@@ -0,0 +1,343 @@
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace AWS.Lambda.Powertools.JMESPath.Utilities
+{
+ internal static class JsonPointerExtensions
+ {
+ public static bool TryResolve(string token, JsonDocumentBuilder current, out JsonDocumentBuilder result)
+ {
+ result = current;
+
+ if (result.ValueKind == JsonValueKind.Array)
+ {
+ if (token == "-")
+ {
+ return false;
+ }
+
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index >= result.GetArrayLength())
+ {
+ return false;
+ }
+ result = result[index];
+ }
+ else if (result.ValueKind == JsonValueKind.Object)
+ {
+ if (!result.TryGetProperty(token, out result))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static JsonPointer ToDefinitePath(this JsonPointer pointer, JsonDocumentBuilder value)
+ {
+ if (value.ValueKind == JsonValueKind.Array && pointer.Tokens.Count > 0 && pointer.Tokens[pointer.Tokens.Count-1] == "-")
+ {
+ var tokens = new List();
+ for (var i = 0; i < pointer.Tokens.Count-1; ++i)
+ {
+ tokens.Add(pointer.Tokens[i]);
+ }
+ tokens.Add(value.GetArrayLength().ToString());
+ return new JsonPointer(tokens);
+ }
+ else
+ {
+ return pointer;
+ }
+ }
+
+ public static bool TryGetValue(this JsonPointer pointer, JsonDocumentBuilder root, out JsonDocumentBuilder value)
+ {
+ value = root;
+
+ foreach (var token in pointer)
+ {
+ if (!TryResolve(token,value,out value))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool TryAdd(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
+ {
+ var current = root;
+ var token = "";
+
+ var enumerator = location.GetEnumerator();
+ var more = enumerator.MoveNext();
+ if (!more)
+ {
+ return false;
+ }
+ while (more)
+ {
+ token = enumerator.Current;
+ more = enumerator.MoveNext();
+ if (more)
+ {
+ if (!TryResolve(token, current, out current))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (current.ValueKind == JsonValueKind.Array)
+ {
+ if (token.Length == 1 && token[0] == '-')
+ {
+ current.AddArrayItem(value);
+ current = current[current.GetArrayLength()-1];
+ }
+ else
+ {
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index > current.GetArrayLength())
+ {
+ return false;
+ }
+ if (index == current.GetArrayLength())
+ {
+ current.AddArrayItem(value);
+ current = value;
+ }
+ else
+ {
+ current.InsertArrayItem(index,value);
+ current = value;
+ }
+ }
+ }
+ else if (current.ValueKind == JsonValueKind.Object)
+ {
+ if (current.ContainsPropertyName(token))
+ {
+ current.RemoveProperty(token);
+ }
+ current.AddProperty(token, value);
+ current = value;
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool TryAddIfAbsent(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
+ {
+ var current = root;
+ var token = "";
+
+ var enumerator = location.GetEnumerator();
+ var more = enumerator.MoveNext();
+ if (!more)
+ {
+ return false;
+ }
+ while (more)
+ {
+ token = enumerator.Current;
+ more = enumerator.MoveNext();
+ if (more)
+ {
+ if (!TryResolve(token, current, out current))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (current.ValueKind == JsonValueKind.Array)
+ {
+ if (token.Length == 1 && token[0] == '-')
+ {
+ current.AddArrayItem(value);
+ current = current[current.GetArrayLength()-1];
+ }
+ else
+ {
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index > current.GetArrayLength())
+ {
+ return false;
+ }
+ if (index == current.GetArrayLength())
+ {
+ current.AddArrayItem(value);
+ current = value;
+ }
+ else
+ {
+ current.InsertArrayItem(index,value);
+ current = value;
+ }
+ }
+ }
+ else if (current.ValueKind == JsonValueKind.Object)
+ {
+ if (current.ContainsPropertyName(token))
+ {
+ return false;
+ }
+ current.AddProperty(token, value);
+ current = value;
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool TryRemove(this JsonPointer location,
+ ref JsonDocumentBuilder root)
+ {
+ var current = root;
+ var token = "";
+
+ var enumerator = location.GetEnumerator();
+ var more = enumerator.MoveNext();
+ if (!more)
+ {
+ return false;
+ }
+ while (more)
+ {
+ token = enumerator.Current;
+ more = enumerator.MoveNext();
+ if (more)
+ {
+ if (!TryResolve(token, current, out current))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (current.ValueKind == JsonValueKind.Array)
+ {
+ if (token.Length == 1 && token[0] == '-')
+ {
+ return false;
+ }
+ else
+ {
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index >= current.GetArrayLength())
+ {
+ return false;
+ }
+ current.RemoveArrayItemAt(index);
+ }
+ }
+ else if (current.ValueKind == JsonValueKind.Object)
+ {
+ if (current.ContainsPropertyName(token))
+ {
+ current.RemoveProperty(token);
+ }
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool TryReplace(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
+ {
+ var current = root;
+ var token = "";
+
+ var enumerator = location.GetEnumerator();
+ var more = enumerator.MoveNext();
+ if (!more)
+ {
+ return false;
+ }
+ while (more)
+ {
+ token = enumerator.Current;
+ more = enumerator.MoveNext();
+ if (more)
+ {
+ if (!TryResolve(token, current, out current))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (current.ValueKind == JsonValueKind.Array)
+ {
+ if (token.Length == 1 && token[0] == '-')
+ {
+ return false;
+ }
+ else
+ {
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
+ if (index >= current.GetArrayLength())
+ {
+ return false;
+ }
+ current[index] = value;
+ }
+ }
+ else if (current.ValueKind == JsonValueKind.Object)
+ {
+ if (current.ContainsPropertyName(token))
+ {
+ current.RemoveProperty(token);
+ }
+ else
+ {
+ return false;
+ }
+ current.AddProperty(token, value);
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+} // namespace JsonCons.Utilities
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
new file mode 100644
index 00000000..8ec005d6
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
@@ -0,0 +1,816 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal readonly struct NameValuePair
+ {
+ public string Name { get; }
+ public IValue Value { get; }
+
+ public NameValuePair(string name, IValue value)
+ {
+ Name = name;
+ Value = value;
+ }
+ }
+
+ internal interface IArrayValueEnumerator : IEnumerator, IEnumerable
+ {
+ }
+
+ internal interface IObjectValueEnumerator : IEnumerator, IEnumerable
+ {
+ }
+
+ internal enum JmesPathType
+ {
+ Null,
+ Array,
+ False,
+ Number,
+ Object,
+ String,
+ True,
+ Expression
+ }
+
+ internal interface IValue
+ {
+ JmesPathType Type {get;}
+ IValue this[int index] {get;}
+ int GetArrayLength();
+ string GetString();
+ bool TryGetDecimal(out decimal value);
+ bool TryGetDouble(out double value);
+ bool TryGetProperty(string propertyName, out IValue property);
+ IArrayValueEnumerator EnumerateArray();
+ IObjectValueEnumerator EnumerateObject();
+ IExpression GetExpression();
+ };
+
+ internal readonly struct JsonElementValue : IValue
+ {
+ private class ArrayEnumerator : IArrayValueEnumerator
+ {
+ private JsonElement.ArrayEnumerator _enumerator;
+
+ public ArrayEnumerator(JsonElement.ArrayEnumerator enumerator)
+ {
+ _enumerator = enumerator;
+ }
+
+ public bool MoveNext()
+ {
+ return _enumerator.MoveNext();
+ }
+
+ public void Reset() { _enumerator.Reset(); }
+
+ void IDisposable.Dispose() { _enumerator.Dispose();}
+
+ public IValue Current => new JsonElementValue(_enumerator.Current);
+
+ object System.Collections.IEnumerator.Current => Current;
+
+ public IEnumerator GetEnumerator()
+ {
+ return new ArrayEnumerator(_enumerator.GetEnumerator());
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ private class ObjectEnumerator : IObjectValueEnumerator
+ {
+ private JsonElement.ObjectEnumerator _enumerator;
+
+ public ObjectEnumerator(JsonElement.ObjectEnumerator enumerator)
+ {
+ _enumerator = enumerator;
+ }
+
+ public bool MoveNext()
+ {
+ return _enumerator.MoveNext();
+ }
+
+ public void Reset() { _enumerator.Reset(); }
+
+ void IDisposable.Dispose() { _enumerator.Dispose();}
+
+ public NameValuePair Current => new(_enumerator.Current.Name, new JsonElementValue(_enumerator.Current.Value));
+
+ object System.Collections.IEnumerator.Current => Current;
+
+ public IEnumerator GetEnumerator()
+ {
+ return new ObjectEnumerator(_enumerator);
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ private readonly JsonElement _element;
+
+ internal JsonElementValue(JsonElement element)
+ {
+ _element = element;
+ }
+
+ public JmesPathType Type
+ {
+ get
+ {
+ switch (_element.ValueKind)
+ {
+ case JsonValueKind.Array:
+ return JmesPathType.Array;
+ case JsonValueKind.False:
+ return JmesPathType.False;
+ case JsonValueKind.Number:
+ return JmesPathType.Number;
+ case JsonValueKind.Object:
+ return JmesPathType.Object;
+ case JsonValueKind.String:
+ return JmesPathType.String;
+ case JsonValueKind.True:
+ return JmesPathType.True;
+ default:
+ return JmesPathType.Null;
+ }
+ }
+ }
+
+ public IValue this[int index] => new JsonElementValue(_element[index]);
+
+ public int GetArrayLength() {return _element.GetArrayLength();}
+
+ public string GetString()
+ {
+ return _element.GetString() ?? throw new InvalidOperationException("String cannot be null");
+ }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ return _element.TryGetDecimal(out value);
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ return _element.TryGetDouble(out value);
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ var r = _element.TryGetProperty(propertyName, out var prop);
+
+ property = prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString()) ?
+ new JsonElementValue(JsonNode.Parse(prop.GetString() ?? string.Empty).Deserialize()) :
+ new JsonElementValue(prop);
+
+ return r;
+ }
+
+ private static bool IsJsonValid(string json)
+ {
+ if (string.IsNullOrWhiteSpace(json))
+ return false;
+
+ try
+ {
+ using var jsonDoc = JsonDocument.Parse(json);
+ return true;
+ }
+ catch (JsonException)
+ {
+ return false;
+ }
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ return new ArrayEnumerator(_element.EnumerateArray());
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ return new ObjectEnumerator(_element.EnumerateObject());
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ var s = JsonSerializer.Serialize(_element);
+ return s;
+ }
+ };
+
+ internal readonly struct DoubleValue : IValue
+ {
+ private readonly double _value;
+
+ internal DoubleValue(double value)
+ {
+ _value = value;
+ }
+
+ public JmesPathType Type => JmesPathType.Number;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ if (!(double.IsNaN(_value) || double.IsInfinity(_value)) && _value is >= (double)decimal.MinValue and <= (double)decimal.MaxValue)
+ {
+ value = decimal.MinValue;
+ return false;
+ }
+
+ value = new decimal(_value);
+ return true;
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ value = _value;
+ return true;
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ var s = JsonSerializer.Serialize(_value);
+ return s;
+ }
+ }
+
+ internal readonly struct DecimalValue : IValue
+ {
+ private readonly decimal _value;
+
+ internal DecimalValue(decimal value)
+ {
+ _value = value;
+ }
+
+ public JmesPathType Type => JmesPathType.Number;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ value = _value;
+ return true;
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ value = (double)_value;
+ return true;
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ var s = JsonSerializer.Serialize(_value);
+ return s;
+ }
+ }
+
+ internal readonly struct StringValue : IValue
+ {
+ private readonly string _value;
+
+ internal StringValue(string value)
+ {
+ _value = value;
+ }
+
+ public JmesPathType Type => JmesPathType.String;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString()
+ {
+ return _value;
+ }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ var s = JsonSerializer.Serialize(_value);
+ return s;
+ }
+ }
+
+ internal readonly struct TrueValue : IValue
+ {
+ public JmesPathType Type => JmesPathType.True;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString() { throw new InvalidOperationException(); }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ return "true";
+ }
+ }
+
+ internal readonly struct FalseValue : IValue
+ {
+ public JmesPathType Type => JmesPathType.False;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString() { throw new InvalidOperationException(); }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ return "false";
+ }
+ }
+
+ internal readonly struct NullValue : IValue
+ {
+ public JmesPathType Type => JmesPathType.Null;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString() { throw new InvalidOperationException(); }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ return "null";
+ }
+ }
+
+ internal readonly struct ArrayValue : IValue
+ {
+ private class ArrayEnumerator : IArrayValueEnumerator
+ {
+ private readonly IList _value;
+ private readonly System.Collections.IEnumerator _enumerator;
+
+ public ArrayEnumerator(IList value)
+ {
+ _value = value;
+ _enumerator = value.GetEnumerator();
+ }
+
+ public bool MoveNext()
+ {
+ return _enumerator.MoveNext();
+ }
+
+ public void Reset() { _enumerator.Reset(); }
+
+ void IDisposable.Dispose() {}
+
+ public IValue Current => _enumerator.Current as IValue ?? throw new InvalidOperationException("Current cannot be null");
+
+ object System.Collections.IEnumerator.Current => Current;
+
+ public IEnumerator GetEnumerator()
+ {
+ return _value.GetEnumerator();
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ private readonly IList _value;
+
+ internal ArrayValue(IList value)
+ {
+ _value = value;
+ }
+
+ public JmesPathType Type => JmesPathType.Array;
+
+ public IValue this[int index] => _value[index];
+
+ public int GetArrayLength() { return _value.Count; }
+
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ return new ArrayEnumerator(_value);
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ var buffer = new StringBuilder();
+ buffer.Append('[');
+ var first = true;
+ foreach (var item in _value)
+ {
+ if (!first)
+ {
+ buffer.Append(',');
+ }
+ else
+ {
+ first = false;
+ }
+ buffer.Append(item.ToString());
+ }
+ buffer.Append(']');
+ return buffer.ToString();
+ }
+ }
+
+ internal readonly struct ObjectValue : IValue
+ {
+ private class ObjectEnumerator : IObjectValueEnumerator
+ {
+ private readonly IDictionary _value;
+ private readonly System.Collections.IEnumerator _enumerator;
+
+ public ObjectEnumerator(IDictionary value)
+ {
+ _value = value;
+ _enumerator = value.GetEnumerator();
+ }
+
+ public bool MoveNext()
+ {
+ return _enumerator.MoveNext();
+ }
+
+ public void Reset() { _enumerator.Reset(); }
+
+ void IDisposable.Dispose() {}
+
+ public NameValuePair Current
+ {
+ get {var pair = (KeyValuePair)_enumerator.Current!;
+ return new NameValuePair(pair.Key, pair.Value); }
+ }
+
+ object System.Collections.IEnumerator.Current => Current;
+
+ public IEnumerator GetEnumerator()
+ {
+ return new ObjectEnumerator(_value);
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ private readonly IDictionary _value;
+
+ internal ObjectValue(IDictionary value)
+ {
+ _value = value;
+ }
+
+ public JmesPathType Type => JmesPathType.Object;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ return _value.TryGetValue(propertyName, out property);
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ return new ObjectEnumerator(_value);
+ }
+
+ public IExpression GetExpression()
+ {
+ throw new InvalidOperationException("Not an expression");
+ }
+
+ public override string ToString()
+ {
+ var buffer = new StringBuilder();
+ buffer.Append('{');
+ var first = true;
+ foreach (var property in _value)
+ {
+ if (!first)
+ {
+ buffer.Append(',');
+ }
+ else
+ {
+ first = false;
+ }
+ buffer.Append(JsonSerializer.Serialize(property.Key));
+ buffer.Append(':');
+ buffer.Append(property.Value.ToString());
+ }
+ buffer.Append('}');
+ return buffer.ToString();
+ }
+ }
+
+ internal readonly struct ExpressionValue : IValue
+ {
+ private readonly IExpression _expr;
+
+ internal ExpressionValue(IExpression expr)
+ {
+ _expr = expr;
+ }
+
+ public JmesPathType Type => JmesPathType.Expression;
+
+ public IValue this[int index] => throw new InvalidOperationException();
+
+ public int GetArrayLength() { throw new InvalidOperationException(); }
+
+ public string GetString() { throw new InvalidOperationException(); }
+
+ public bool TryGetDecimal(out decimal value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetDouble(out double value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetProperty(string propertyName, out IValue property)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IArrayValueEnumerator EnumerateArray()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IObjectValueEnumerator EnumerateObject()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IExpression GetExpression()
+ {
+ return _expr;
+ }
+
+ public override string ToString()
+ {
+ return "expression";
+ }
+ };
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs
new file mode 100644
index 00000000..bfd785a3
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ ///
+ /// Compares two instances.
+ ///
+ internal sealed class ValueComparer : IComparer, System.Collections.IComparer
+ {
+ /// Gets a singleton instance of . This property is read-only.
+ public static ValueComparer Instance { get; } = new();
+
+ ///
+ /// Constructs a
+ ///
+ public ValueComparer() {}
+
+ ///
+ /// Compares two instances.
+ ///
+ /// If the two instances have different data types, they are
+ /// compared according to their Type property, which gives this ordering:
+ ///
+ /// Undefined
+ /// Object
+ /// Array
+ /// String
+ /// Number
+ /// True
+ /// False
+ /// Null
+ ///
+ ///
+ /// If both instances are null, true, or false, they are equal.
+ ///
+ /// If both are strings, they are compared with the String.CompareTo method.
+ ///
+ /// If both are numbers, and both can be represented by a ,
+ /// they are compared with the Decimal.CompareTo method, otherwise they are
+ /// compared as doubles.
+ ///
+ /// If both are objects, they are compared accoring to the following rules:
+ ///
+ ///
+ /// - Order each object's properties by name and compare sequentially.
+ /// The properties are compared first by name with the String.CompareTo method, then by value with
+ /// - The first mismatching property defines which instance is less or greater than the other.
+ /// - If the two sequences have no mismatching properties until one of them ends, and the other is longer, the shorter sequence is less than the other.
+ /// - If the two sequences have no mismatching properties and have the same length, they are equal.
+ ///
+ ///
+ /// If both are arrays, they are compared element wise with .
+ /// The first mismatching element defines which instance is less or greater than the other.
+ /// If the two arrays have no mismatching elements until one of them ends, and the other is longer, the shorter array is less than the other.
+ /// If the two arrays have no mismatching elements and have the same length, they are equal.
+ ///
+ ///
+ /// The first object of type cref="IValue"/> to compare.
+ /// The second object of type cref="IValue"/> to compare.
+ ///
+ ///
+ /// Unable to compare numbers as either or double (shouldn't happen.)
+ ///
+ public int Compare(IValue lhs, IValue rhs)
+ {
+ if (lhs.Type != rhs.Type)
+ return (int)lhs.Type - (int)rhs.Type;
+
+ switch (lhs.Type)
+ {
+ case JmesPathType.Null:
+ case JmesPathType.True:
+ case JmesPathType.False:
+ return 0;
+
+ case JmesPathType.Number:
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ return dec1.CompareTo(dec2);
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ return val1.CompareTo(val2);
+ }
+ else
+ {
+ throw new InvalidOperationException("Unable to compare numbers");
+ }
+ }
+
+ case JmesPathType.String:
+ return lhs.GetString().CompareTo(rhs.GetString());
+
+ case JmesPathType.Array:
+ {
+ var enumerator1 = lhs.EnumerateArray();
+ var enumerator2 = rhs.EnumerateArray();
+ var result1 = enumerator1.MoveNext();
+ var result2 = enumerator2.MoveNext();
+ while (result1 && result2)
+ {
+ var diff = Compare(enumerator1.Current, enumerator2.Current);
+ if (diff != 0)
+ {
+ return diff;
+ }
+ result1 = enumerator1.MoveNext();
+ result2 = enumerator2.MoveNext();
+ }
+ return result1 ? 1 : result2 ? -1 : 0;
+ }
+
+ case JmesPathType.Object:
+ {
+ // OrderBy performs a stable sort (Note that supports duplicate property names)
+ var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+
+ var result1 = enumerator1.MoveNext();
+ var result2 = enumerator2.MoveNext();
+ while (result1 && result2)
+ {
+ if (enumerator1.Current.Name != enumerator2.Current.Name)
+ {
+ return enumerator1.Current.Name.CompareTo(enumerator2.Current.Name);
+ }
+ var diff = Compare(enumerator1.Current.Value, enumerator2.Current.Value);
+ if (diff != 0)
+ {
+ return diff;
+ }
+ result1 = enumerator1.MoveNext();
+ result2 = enumerator2.MoveNext();
+ }
+
+ return result1 ? 1 : result2 ? -1 : 0;
+ }
+
+ default:
+ throw new InvalidOperationException(string.Format("Unknown JmesPathType {0}", lhs.Type));
+ }
+ }
+
+ int System.Collections.IComparer.Compare(object x, object y)
+ {
+ return Compare((IValue)x, (IValue)y);
+ }
+ }
+
+
+} // namespace JsonCons.JsonPath
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs
new file mode 100644
index 00000000..78679579
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AWS.Lambda.Powertools.JMESPath
+{
+ internal sealed class ValueEqualityComparer : IEqualityComparer
+ {
+ internal static ValueEqualityComparer Instance { get; } = new();
+
+ private int _maxHashDepth = 100;
+
+ private ValueEqualityComparer() {}
+
+ public bool Equals(IValue lhs, IValue rhs)
+ {
+ if (lhs.Type != rhs.Type)
+ return false;
+
+ switch (lhs.Type)
+ {
+ case JmesPathType.Null:
+ case JmesPathType.True:
+ case JmesPathType.False:
+ return true;
+
+ case JmesPathType.Number:
+ {
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ return dec1 == dec2;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ return val1 == val2;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ case JmesPathType.String:
+ return lhs.GetString().Equals(rhs.GetString());
+
+ case JmesPathType.Array:
+ return lhs.EnumerateArray().SequenceEqual(rhs.EnumerateArray(), this);
+
+ case JmesPathType.Object:
+ {
+ // OrderBy performs a stable sort (Note that IValue supports duplicate property names)
+ var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+
+ var result1 = enumerator1.MoveNext();
+ var result2 = enumerator2.MoveNext();
+ while (result1 && result2)
+ {
+ if (enumerator1.Current.Name != enumerator2.Current.Name)
+ {
+ return false;
+ }
+ if (!(Equals(enumerator1.Current.Value,enumerator2.Current.Value)))
+ {
+ return false;
+ }
+ result1 = enumerator1.MoveNext();
+ result2 = enumerator2.MoveNext();
+ }
+
+ return result1 == false && result2 == false;
+ }
+
+ default:
+ throw new InvalidOperationException(string.Format("Unknown JmesPathType {0}", lhs.Type));
+ }
+ }
+
+ public int GetHashCode(IValue obj)
+ {
+ return ComputeHashCode(obj, 0);
+ }
+
+ private int ComputeHashCode(IValue element, int depth)
+ {
+ var hashCode = element.Type.GetHashCode();
+
+ switch (element.Type)
+ {
+ case JmesPathType.Null:
+ case JmesPathType.True:
+ case JmesPathType.False:
+ break;
+
+ case JmesPathType.Number:
+ {
+ element.TryGetDouble(out var dbl);
+ hashCode += 17 * dbl.GetHashCode();
+ break;
+ }
+
+ case JmesPathType.String:
+ hashCode += 17 * element.GetString().GetHashCode();
+ break;
+
+ case JmesPathType.Array:
+ if (depth < _maxHashDepth)
+ foreach (var item in element.EnumerateArray())
+ hashCode += 17*ComputeHashCode(item, depth+1);
+ break;
+
+ case JmesPathType.Object:
+ foreach (var property in element.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal))
+ {
+ hashCode += 17*property.Name.GetHashCode();
+ if (depth < _maxHashDepth)
+ hashCode += 17*ComputeHashCode(property.Value, depth+1);
+ }
+ break;
+
+ default:
+ throw new InvalidOperationException(string.Format("Unknown JmesPathType {0}", element.Type));
+ }
+ return hashCode;
+ }
+ }
+
+
+}
diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props
index e73f3288..d9c404bd 100644
--- a/libraries/src/Directory.Packages.props
+++ b/libraries/src/Directory.Packages.props
@@ -10,7 +10,6 @@
-
diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs
index 0b0ff4ad..366aac77 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs
@@ -545,7 +545,15 @@ private static APIGatewayProxyRequest LoadApiGatewayProxyRequest()
};
var eventJson = File.ReadAllText("./resources/apigw_event.json");
- var request = JsonSerializer.Deserialize(eventJson, options);
- return request!;
+ try
+ {
+ var request = JsonSerializer.Deserialize(eventJson, options);
+ return request!;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
}
}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj
new file mode 100644
index 00000000..2f385c45
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj
@@ -0,0 +1,94 @@
+
+
+
+ default
+ AWS.Lambda.Powertools.JMESpath.Tests
+ AWS.Lambda.Powertools.JMESpath.Tests
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs
new file mode 100644
index 00000000..8c927eb7
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
new file mode 100644
index 00000000..52f5db99
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
@@ -0,0 +1,113 @@
+using System.Text.Json;
+using AWS.Lambda.Powertools.JMESPath.Utilities;
+using Xunit.Abstractions;
+
+namespace AWS.Lambda.Powertools.JMESPath.Tests;
+
+public class JmesPathTests
+{
+ private readonly ITestOutputHelper _output;
+
+ public JmesPathTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Theory]
+ [InlineData("test_files/basic.json")]
+ [InlineData("test_files/benchmarks.json")]
+ [InlineData("test_files/boolean.json")]
+ [InlineData("test_files/current.json")]
+ [InlineData("test_files/escape.json")]
+ [InlineData("test_files/filters.json")]
+ [InlineData("test_files/identifiers.json")]
+ [InlineData("test_files/indices.json")]
+ [InlineData("test_files/literal.json")]
+ [InlineData("test_files/multiselect.json")]
+ [InlineData("test_files/pipe.json")]
+ [InlineData("test_files/slice.json")]
+ [InlineData("test_files/unicode.json")]
+ [InlineData("test_files/syntax.json")]
+ [InlineData("test_files/wildcard.json")]
+ [InlineData("test_files/example.json")]
+ [InlineData("test_files/functions.json")]
+ [InlineData("test_files/test.json")]
+ [InlineData("test_files/apigw_event.json")]
+ [InlineData("test_files/apigw_event_2.json")]
+ public void RunJmesPathTests(string path)
+ {
+ _output.WriteLine($"Test {path}");
+
+ var text = File.ReadAllText(path);
+ var jsonOptions = new JsonDocumentOptions
+ {
+ CommentHandling = JsonCommentHandling.Skip
+ };
+ using var doc = JsonDocument.Parse(text, jsonOptions);
+
+ var testsEnumerable = doc.RootElement.EnumerateArray();
+ var comparer = JsonElementEqualityComparer.Instance;
+
+ foreach (var testGroup in testsEnumerable)
+ {
+ var given = testGroup.GetProperty("given");
+ var testCases = testGroup.GetProperty("cases");
+ var testCasesEnumerable = testCases.EnumerateArray();
+ foreach (var testCase in testCasesEnumerable)
+ {
+ var exprElement = testCase.GetProperty("expression");
+
+ try
+ {
+ if (testCase.TryGetProperty("error", out var expected))
+ {
+ var msg = expected.GetString();
+ //Debug.WriteLine($"message: {msg}");
+ if (msg != null && (msg.Equals("syntax") || msg.Equals("invalid-arity") || msg.Equals("unknown-function") || msg.Equals("invalid-value")))
+ {
+ Assert.Throws(() => JsonTransformer.Parse(exprElement.ToString()));
+ }
+ else
+ {
+ var expr = JsonTransformer.Parse(exprElement.ToString());
+ try
+ {
+ var result = expr.Transform(given);
+ using var nullValue = JsonDocument.Parse("null");
+ var success = comparer.Equals(result.RootElement, nullValue.RootElement);
+ Assert.True(success);
+ }
+ catch (InvalidOperationException)
+ { }
+ }
+ }
+ else if (testCase.TryGetProperty("result", out expected))
+ {
+ var expr = JsonTransformer.Parse(exprElement.ToString());
+ var result = expr.Transform(given);
+ var success = comparer.Equals(result.RootElement, expected);
+ if (!success)
+ {
+ _output.WriteLine("File: {0}", path);
+
+ _output.WriteLine($"Document: {given}");
+ _output.WriteLine($"Path: {exprElement}");
+ _output.WriteLine($"Expected: {JsonSerializer.Serialize(expected)}");
+ _output.WriteLine($"Result: {JsonSerializer.Serialize(result)}");
+ }
+ Assert.True(comparer.Equals(result.RootElement,expected));
+
+ }
+ }
+ catch (Exception e)
+ {
+ _output.WriteLine("File: {0}", path);
+ _output.WriteLine($"Document: {given}");
+ _output.WriteLine($"Path: {exprElement}");
+ _output.WriteLine("Error: {0}", e.Message);
+ throw;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event.json
new file mode 100644
index 00000000..159bc270
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event.json
@@ -0,0 +1,76 @@
+[
+ {
+ "given": {
+ "body": "{\"message\": \"Lambda rocks\", \"id\": 43876123454654}",
+ "resource": "/{proxy+}",
+ "path": "/path/to/resource",
+ "httpMethod": "POST",
+ "isBase64Encoded": false,
+ "queryStringParameters": {
+ "foo": "bar"
+ },
+ "pathParameters": {
+ "proxy": "/path/to/resource"
+ },
+ "stageVariables": {
+ "baz": "qux"
+ },
+ "headers": {
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Encoding": "gzip, deflate, sdch",
+ "Accept-Language": "en-US,en;q=0.8",
+ "Cache-Control": "max-age=0",
+ "CloudFront-Forwarded-Proto": "https",
+ "CloudFront-Is-Desktop-Viewer": "true",
+ "CloudFront-Is-Mobile-Viewer": "false",
+ "CloudFront-Is-SmartTV-Viewer": "false",
+ "CloudFront-Is-Tablet-Viewer": "false",
+ "CloudFront-Viewer-Country": "US",
+ "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Custom User Agent String",
+ "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
+ "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "resourceId": "123456",
+ "stage": "prod",
+ "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
+ "requestTime": "09/Apr/2015:12:34:56 +0000",
+ "requestTimeEpoch": 1428582896000,
+ "identity": {
+ "cognitoIdentityPoolId": null,
+ "accountId": null,
+ "cognitoIdentityId": null,
+ "caller": null,
+ "accessKey": null,
+ "sourceIp": "127.0.0.1",
+ "cognitoAuthenticationType": null,
+ "cognitoAuthenticationProvider": null,
+ "userArn": null,
+ "userAgent": "Custom User Agent String",
+ "user": null
+ },
+ "path": "/prod/path/to/resource",
+ "resourcePath": "/{proxy+}",
+ "httpMethod": "POST",
+ "apiId": "1234567890",
+ "protocol": "HTTP/1.1"
+ }
+ },
+ "cases": [
+ {
+ "expression": "body.id",
+ "result": 43876123454654
+ },
+ {
+ "expression": "powertools_json(body).id",
+ "result": 43876123454654
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event_2.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event_2.json
new file mode 100644
index 00000000..432697d9
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/apigw_event_2.json
@@ -0,0 +1,54 @@
+[
+ {
+ "given": {
+ "version": "2.0",
+ "routeKey": "ANY /createpayment",
+ "rawPath": "/createpayment",
+ "rawQueryString": "",
+ "headers": {
+ "Header1": "value1",
+ "Header2": "value2"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "apiId": "api-id",
+ "domainName": "id.execute-api.us-east-1.amazonaws.com",
+ "domainPrefix": "id",
+ "http": {
+ "method": "POST",
+ "path": "/createpayment",
+ "protocol": "HTTP/1.1",
+ "sourceIp": "ip",
+ "userAgent": "agent"
+ },
+ "requestId": "id",
+ "routeKey": "ANY /createpayment",
+ "stage": "$default",
+ "time": "10/Feb/2021:13:40:43 +0000",
+ "timeEpoch": 1612964443723
+ },
+ "body": {"user_id":"xyz","product_id":"123456789"},
+ "body64": "eyJ1c2VyX2lkIjoieHl6IiwicHJvZHVjdF9pZCI6IjEyMzQ1Njc4OSJ9",
+ "bodygzip": "H4sIAAAAAAAAA6tWKi1OLYrPTFGyUqqorFLSUSooyk8pTS6BCBkaGZuYmplbWCrVAgApzA/LKgAAAA==",
+ "isBase64Encoded": false
+ },
+ "cases": [
+ {
+ "expression": "body.[user_id,product_id]",
+ "result": ["xyz",123456789]
+ },
+ {
+ "expression": "powertools_json(body).[user_id,product_id]",
+ "result": ["xyz",123456789]
+ },
+ {
+ "expression": "powertools_base64(body64).[user_id,product_id]",
+ "result": ["xyz",123456789]
+ },
+ {
+ "expression": "powertools_base64_gzip(bodygzip).[user_id,product_id]",
+ "result": ["xyz",123456789]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/basic.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/basic.json
new file mode 100644
index 00000000..d550e969
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/basic.json
@@ -0,0 +1,96 @@
+[{
+ "given":
+ {"foo": {"bar": {"baz": "correct"}}},
+ "cases": [
+ {
+ "expression": "foo",
+ "result": {"bar": {"baz": "correct"}}
+ },
+ {
+ "expression": "foo.bar",
+ "result": {"baz": "correct"}
+ },
+ {
+ "expression": "foo.bar.baz",
+ "result": "correct"
+ },
+ {
+ "expression": "foo\n.\nbar\n.baz",
+ "result": "correct"
+ },
+ {
+ "expression": "foo.bar.baz.bad",
+ "result": null
+ },
+ {
+ "expression": "foo.bar.bad",
+ "result": null
+ },
+ {
+ "expression": "foo.bad",
+ "result": null
+ },
+ {
+ "expression": "bad",
+ "result": null
+ },
+ {
+ "expression": "bad.morebad.morebad",
+ "result": null
+ }
+ ]
+},
+{
+ "given":
+ {"foo": {"bar": ["one", "two", "three"]}},
+ "cases": [
+ {
+ "expression": "foo",
+ "result": {"bar": ["one", "two", "three"]}
+ },
+ {
+ "expression": "foo.bar",
+ "result": ["one", "two", "three"]
+ }
+ ]
+},
+{
+ "given": ["one", "two", "three"],
+ "cases": [
+ {
+ "expression": "one",
+ "result": null
+ },
+ {
+ "expression": "two",
+ "result": null
+ },
+ {
+ "expression": "three",
+ "result": null
+ },
+ {
+ "expression": "one.two",
+ "result": null
+ }
+ ]
+},
+{
+ "given":
+ {"foo": {"1": ["one", "two", "three"], "-1": "bar"}},
+ "cases": [
+ {
+ "expression": "foo.\"1\"",
+ "result": ["one", "two", "three"]
+ },
+ {
+ "expression": "foo.\"1\"[0]",
+ "result": "one"
+ },
+ {
+ "expression": "foo.\"-1\"",
+ "result": "bar"
+ }
+ ]
+}
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/benchmarks.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/benchmarks.json
new file mode 100644
index 00000000..024a5904
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/benchmarks.json
@@ -0,0 +1,138 @@
+[
+ {
+ "given": {
+ "long_name_for_a_field": true,
+ "a": {
+ "b": {
+ "c": {
+ "d": {
+ "e": {
+ "f": {
+ "g": {
+ "h": {
+ "i": {
+ "j": {
+ "k": {
+ "l": {
+ "m": {
+ "n": {
+ "o": {
+ "p": true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "b": true,
+ "c": {
+ "d": true
+ }
+ },
+ "cases": [
+ {
+ "comment": "simple field",
+ "expression": "b",
+ "bench": "full"
+ },
+ {
+ "comment": "simple subexpression",
+ "expression": "c.d",
+ "bench": "full"
+ },
+ {
+ "comment": "deep field selection no match",
+ "expression": "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s",
+ "bench": "full"
+ },
+ {
+ "comment": "deep field selection",
+ "expression": "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p",
+ "bench": "full"
+ },
+ {
+ "comment": "simple or",
+ "expression": "not_there || b",
+ "bench": "full"
+ }
+ ]
+ },
+ {
+ "given": {
+ "a":0,"b":1,"c":2,"d":3,"e":4,"f":5,"g":6,"h":7,"i":8,"j":9,"k":10,
+ "l":11,"m":12,"n":13,"o":14,"p":15,"q":16,"r":17,"s":18,"t":19,"u":20,
+ "v":21,"w":22,"x":23,"y":24,"z":25
+ },
+ "cases": [
+ {
+ "comment": "deep ands",
+ "expression": "a && b && c && d && e && f && g && h && i && j && k && l && m && n && o && p && q && r && s && t && u && v && w && x && y && z",
+ "bench": "full"
+ },
+ {
+ "comment": "deep ors",
+ "expression": "z || y || x || w || v || u || t || s || r || q || p || o || n || m || l || k || j || i || h || g || f || e || d || c || b || a",
+ "bench": "full"
+ },
+ {
+ "comment": "lots of summing",
+ "expression": "sum([z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a])",
+ "bench": "full"
+ },
+ {
+ "comment": "lots of function application",
+ "expression": "sum([z, sum([y, sum([x, sum([w, sum([v, sum([u, sum([t, sum([s, sum([r, sum([q, sum([p, sum([o, sum([n, sum([m, sum([l, sum([k, sum([j, sum([i, sum([h, sum([g, sum([f, sum([e, sum([d, sum([c, sum([b, a])])])])])])])])])])])])])])])])])])])])])])])])])",
+ "bench": "full"
+ },
+ {
+ "comment": "lots of multi list",
+ "expression": "[z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a]",
+ "bench": "full"
+ }
+ ]
+ },
+ {
+ "given": {},
+ "cases": [
+ {
+ "comment": "field 50",
+ "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0",
+ "bench": "parse"
+ },
+ {
+ "comment": "pipe 50",
+ "expression": "j49|j48|j47|j46|j45|j44|j43|j42|j41|j40|j39|j38|j37|j36|j35|j34|j33|j32|j31|j30|j29|j28|j27|j26|j25|j24|j23|j22|j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10|j9|j8|j7|j6|j5|j4|j3|j2|j1|j0",
+ "bench": "parse"
+ },
+ {
+ "comment": "index 50",
+ "expression": "[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]",
+ "bench": "parse"
+ },
+ {
+ "comment": "long raw string literal",
+ "expression": "'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'",
+ "bench": "parse"
+ },
+ {
+ "comment": "deep projection 104",
+ "expression": "a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*]",
+ "bench": "parse"
+ },
+ {
+ "comment": "filter projection",
+ "expression": "foo[?bar > baz][?qux > baz]",
+ "bench": "parse"
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/boolean.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/boolean.json
new file mode 100644
index 00000000..60635acb
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/boolean.json
@@ -0,0 +1,275 @@
+[
+ {
+ "given": {
+ "outer": {
+ "foo": "foo",
+ "bar": "bar",
+ "baz": "baz"
+ }
+ },
+ "cases": [
+ {
+ "expression": "outer.foo || outer.bar",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.foo||outer.bar",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.bar || outer.baz",
+ "result": "bar"
+ },
+ {
+ "expression": "outer.bar||outer.baz",
+ "result": "bar"
+ },
+ {
+ "expression": "outer.bad || outer.foo",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.bad||outer.foo",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.foo || outer.bad",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.foo||outer.bad",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.bad || outer.alsobad",
+ "result": null
+ },
+ {
+ "expression": "outer.bad||outer.alsobad",
+ "result": null
+ }
+ ]
+ },
+ {
+ "given": {
+ "outer": {
+ "foo": "foo",
+ "bool": false,
+ "empty_list": [],
+ "empty_string": ""
+ }
+ },
+ "cases": [
+ {
+ "expression": "outer.empty_string || outer.foo",
+ "result": "foo"
+ },
+ {
+ "expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo",
+ "result": "foo"
+ }
+ ]
+ },
+ {
+ "given": {
+ "True": true,
+ "False": false,
+ "Number": 5,
+ "EmptyList": [],
+ "Zero": 0
+ },
+ "cases": [
+ {
+ "expression": "True && False",
+ "result": false
+ },
+ {
+ "expression": "False && True",
+ "result": false
+ },
+ {
+ "expression": "True && True",
+ "result": true
+ },
+ {
+ "expression": "False && False",
+ "result": false
+ },
+ {
+ "expression": "True && Number",
+ "result": 5
+ },
+ {
+ "expression": "Number && True",
+ "result": true
+ },
+ {
+ "expression": "Number && False",
+ "result": false
+ },
+ {
+ "expression": "Number && EmptyList",
+ "result": []
+ },
+ {
+ "expression": "Number && True",
+ "result": true
+ },
+ {
+ "expression": "EmptyList && True",
+ "result": []
+ },
+ {
+ "expression": "EmptyList && False",
+ "result": []
+ },
+ {
+ "expression": "True || False",
+ "result": true
+ },
+ {
+ "expression": "True || True",
+ "result": true
+ },
+ {
+ "expression": "False || True",
+ "result": true
+ },
+ {
+ "expression": "False || False",
+ "result": false
+ },
+ {
+ "expression": "Number || EmptyList",
+ "result": 5
+ },
+ {
+ "expression": "Number || True",
+ "result": 5
+ },
+ {
+ "expression": "Number || True && False",
+ "result": 5
+ },
+ {
+ "expression": "(Number || True) && False",
+ "result": false
+ },
+ {
+ "expression": "Number || (True && False)",
+ "result": 5
+ },
+ {
+ "expression": "!True",
+ "result": false
+ },
+ {
+ "expression": "!False",
+ "result": true
+ },
+ {
+ "expression": "!Number",
+ "result": false
+ },
+ {
+ "expression": "!EmptyList",
+ "result": true
+ },
+ {
+ "expression": "True && !False",
+ "result": true
+ },
+ {
+ "expression": "True && !EmptyList",
+ "result": true
+ },
+ {
+ "expression": "!False && !EmptyList",
+ "result": true
+ },
+ {
+ "expression": "!(True && False)",
+ "result": true
+ },
+ {
+ "expression": "!Zero",
+ "result": false
+ },
+ {
+ "expression": "!!Zero",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "one": 1,
+ "two": 2,
+ "three": 3,
+ "emptylist": [],
+ "boolvalue": false
+ },
+ "cases": [
+ {
+ "expression": "one < two",
+ "result": true
+ },
+ {
+ "expression": "one <= two",
+ "result": true
+ },
+ {
+ "expression": "one == one",
+ "result": true
+ },
+ {
+ "expression": "one == two",
+ "result": false
+ },
+ {
+ "expression": "one > two",
+ "result": false
+ },
+ {
+ "expression": "one >= two",
+ "result": false
+ },
+ {
+ "expression": "one != two",
+ "result": true
+ },
+ {
+ "expression": "emptylist < one",
+ "result": null
+ },
+ {
+ "expression": "emptylist < nullvalue",
+ "result": null
+ },
+ {
+ "expression": "emptylist < boolvalue",
+ "result": null
+ },
+ {
+ "expression": "one < boolvalue",
+ "result": null
+ },
+ {
+ "expression": "one < two && three > one",
+ "result": true
+ },
+ {
+ "expression": "one < two || three > one",
+ "result": true
+ },
+ {
+ "expression": "one < two || three < one",
+ "result": true
+ },
+ {
+ "expression": "two < one || three < one",
+ "result": false
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/current.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/current.json
new file mode 100644
index 00000000..0c26248d
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/current.json
@@ -0,0 +1,25 @@
+[
+ {
+ "given": {
+ "foo": [{"name": "a"}, {"name": "b"}],
+ "bar": {"baz": "qux"}
+ },
+ "cases": [
+ {
+ "expression": "@",
+ "result": {
+ "foo": [{"name": "a"}, {"name": "b"}],
+ "bar": {"baz": "qux"}
+ }
+ },
+ {
+ "expression": "@.bar",
+ "result": {"baz": "qux"}
+ },
+ {
+ "expression": "@.foo[0]",
+ "result": {"name": "a"}
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/escape.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/escape.json
new file mode 100644
index 00000000..4a62d951
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/escape.json
@@ -0,0 +1,46 @@
+[{
+ "given": {
+ "foo.bar": "dot",
+ "foo bar": "space",
+ "foo\nbar": "newline",
+ "foo\"bar": "doublequote",
+ "c:\\\\windows\\path": "windows",
+ "/unix/path": "unix",
+ "\"\"\"": "threequotes",
+ "bar": {"baz": "qux"}
+ },
+ "cases": [
+ {
+ "expression": "\"foo.bar\"",
+ "result": "dot"
+ },
+ {
+ "expression": "\"foo bar\"",
+ "result": "space"
+ },
+ {
+ "expression": "\"foo\\nbar\"",
+ "result": "newline"
+ },
+ {
+ "expression": "\"foo\\\"bar\"",
+ "result": "doublequote"
+ },
+ {
+ "expression": "\"c:\\\\\\\\windows\\\\path\"",
+ "result": "windows"
+ },
+ {
+ "expression": "\"/unix/path\"",
+ "result": "unix"
+ },
+ {
+ "expression": "\"\\\"\\\"\\\"\"",
+ "result": "threequotes"
+ },
+ {
+ "expression": "\"bar\".\"baz\"",
+ "result": "qux"
+ }
+ ]
+}]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/example.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/example.json
new file mode 100644
index 00000000..871aa8dc
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/example.json
@@ -0,0 +1,50 @@
+[
+ {
+ "given": {
+ "_id": "63ba60670fe420f2fb346866",
+ "isActive": true,
+ "balance": "$2,285.51",
+ "age": 20,
+ "eyeColor": "blue",
+ "name": "Eva Sharpe",
+ "email": "evasharpe@zaggles.com",
+ "phone": "+1 (950) 479-2130",
+ "registered": "2023-01-08T08:07:44.1787922+00:00",
+ "latitude": 46.325291,
+ "longitude": 5.211461,
+ "friends": [
+ {
+ "id": 0,
+ "name": "Nielsen Casey",
+ "age": 19
+ },
+ {
+ "id": 1,
+ "name": "Carlene Long",
+ "age": 38
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "balance",
+ "result": "$2,285.51"
+ },
+ {
+ "expression": "to_string(latitude)",
+ "result": "46.325291"
+ },
+ {
+ "expression": "friends[*].name",
+ "result": [
+ "Nielsen Casey",
+ "Carlene Long"
+ ]
+ },
+ {
+ "expression": "{email: email, name: name}",
+ "result": {"email": "evasharpe@zaggles.com", "name": "Eva Sharpe"}
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/filters.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/filters.json
new file mode 100644
index 00000000..b2141a4e
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/filters.json
@@ -0,0 +1,468 @@
+[
+ {
+ "given": {"foo": [{"name": "a"}, {"name": "b"}]},
+ "cases": [
+ {
+ "comment": "Matching a literal `a`",
+ "expression": "foo[?name == 'a']",
+ "result": [{"name": "a"}]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [0, 1], "bar": [2, 3]},
+ "cases": [
+ {
+ "comment": "Matching a literal `0`",
+ "expression": "*[?[0] == `0`]",
+ "result": [[], []]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"first": "foo", "last": "bar"},
+ {"first": "foo", "last": "foo"},
+ {"first": "foo", "last": "baz"}]},
+ "cases": [
+ {
+ "comment": "Matching an expression",
+ "expression": "foo[?first == last]",
+ "result": [{"first": "foo", "last": "foo"}]
+ },
+ {
+ "comment": "Verify projection created from filter",
+ "expression": "foo[?first == last].first",
+ "result": ["foo"]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"age": 20},
+ {"age": 25},
+ {"age": 30}]},
+ "cases": [
+ {
+ "comment": "Greater than with a number",
+ "expression": "foo[?age > `25`]",
+ "result": [{"age": 30}]
+ },
+ {
+ "expression": "foo[?age >= `25`]",
+ "result": [{"age": 25}, {"age": 30}]
+ },
+ {
+ "comment": "Greater than with a number",
+ "expression": "foo[?age > `30`]",
+ "result": []
+ },
+ {
+ "comment": "Greater than with a number",
+ "expression": "foo[?age < `25`]",
+ "result": [{"age": 20}]
+ },
+ {
+ "comment": "Greater than with a number",
+ "expression": "foo[?age <= `25`]",
+ "result": [{"age": 20}, {"age": 25}]
+ },
+ {
+ "comment": "Greater than with a number",
+ "expression": "foo[?age < `20`]",
+ "result": []
+ },
+ {
+ "expression": "foo[?age == `20`]",
+ "result": [{"age": 20}]
+ },
+ {
+ "expression": "foo[?age != `20`]",
+ "result": [{"age": 25}, {"age": 30}]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"top": {"name": "a"}},
+ {"top": {"name": "b"}}]},
+ "cases": [
+ {
+ "comment": "Filter with subexpression",
+ "expression": "foo[?top.name == 'a']",
+ "result": [{"top": {"name": "a"}}]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"top": {"first": "foo", "last": "bar"}},
+ {"top": {"first": "foo", "last": "foo"}},
+ {"top": {"first": "foo", "last": "baz"}}]},
+ "cases": [
+ {
+ "comment": "Matching an expression",
+ "expression": "foo[?top.first == top.last]",
+ "result": [{"top": {"first": "foo", "last": "foo"}}]
+ },
+ {
+ "comment": "Matching a JSON array",
+ "expression": "foo[?top == `{\"first\": \"foo\", \"last\": \"bar\"}`]",
+ "result": [{"top": {"first": "foo", "last": "bar"}}]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [
+ {"key": true},
+ {"key": false},
+ {"key": 0},
+ {"key": 1},
+ {"key": [0]},
+ {"key": {"bar": [0]}},
+ {"key": null},
+ {"key": [1]},
+ {"key": {"a":2}}
+ ]},
+ "cases": [
+ {
+ "expression": "foo[?key == `true`]",
+ "result": [{"key": true}]
+ },
+ {
+ "expression": "foo[?key == `false`]",
+ "result": [{"key": false}]
+ },
+ {
+ "expression": "foo[?key == `0`]",
+ "result": [{"key": 0}]
+ },
+ {
+ "expression": "foo[?key == `1`]",
+ "result": [{"key": 1}]
+ },
+ {
+ "expression": "foo[?key == `[0]`]",
+ "result": [{"key": [0]}]
+ },
+ {
+ "expression": "foo[?key == `{\"bar\": [0]}`]",
+ "result": [{"key": {"bar": [0]}}]
+ },
+ {
+ "expression": "foo[?key == `null`]",
+ "result": [{"key": null}]
+ },
+ {
+ "expression": "foo[?key == `[1]`]",
+ "result": [{"key": [1]}]
+ },
+ {
+ "expression": "foo[?key == `{\"a\":2}`]",
+ "result": [{"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`true` == key]",
+ "result": [{"key": true}]
+ },
+ {
+ "expression": "foo[?`false` == key]",
+ "result": [{"key": false}]
+ },
+ {
+ "expression": "foo[?`0` == key]",
+ "result": [{"key": 0}]
+ },
+ {
+ "expression": "foo[?`1` == key]",
+ "result": [{"key": 1}]
+ },
+ {
+ "expression": "foo[?`[0]` == key]",
+ "result": [{"key": [0]}]
+ },
+ {
+ "expression": "foo[?`{\"bar\": [0]}` == key]",
+ "result": [{"key": {"bar": [0]}}]
+ },
+ {
+ "expression": "foo[?`null` == key]",
+ "result": [{"key": null}]
+ },
+ {
+ "expression": "foo[?`[1]` == key]",
+ "result": [{"key": [1]}]
+ },
+ {
+ "expression": "foo[?`{\"a\":2}` == key]",
+ "result": [{"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `true`]",
+ "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `false`]",
+ "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `0`]",
+ "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `1`]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `null`]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `[1]`]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?key != `{\"a\":2}`]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}]
+ },
+ {
+ "expression": "foo[?`true` != key]",
+ "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`false` != key]",
+ "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`0` != key]",
+ "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`1` != key]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`null` != key]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`[1]` != key]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}]
+ },
+ {
+ "expression": "foo[?`{\"a\":2}` != key]",
+ "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]},
+ {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}]
+ }
+ ]
+ },
+ {
+ "given": {"reservations": [
+ {"instances": [
+ {"foo": 1, "bar": 2}, {"foo": 1, "bar": 3},
+ {"foo": 1, "bar": 2}, {"foo": 2, "bar": 1}]}]},
+ "cases": [
+ {
+ "expression": "reservations[].instances[?bar==`1`]",
+ "result": [[{"foo": 2, "bar": 1}]]
+ },
+ {
+ "expression": "reservations[*].instances[?bar==`1`]",
+ "result": [[{"foo": 2, "bar": 1}]]
+ },
+ {
+ "expression": "reservations[].instances[?bar==`1`][]",
+ "result": [{"foo": 2, "bar": 1}]
+ }
+ ]
+ },
+ {
+ "given": {
+ "baz": "other",
+ "foo": [
+ {"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 1, "baz": 2}
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo[?bar==`1`].bar[0]",
+ "result": []
+ }
+ ]
+ },
+ {
+ "given": {
+ "foo": [
+ {"a": 1, "b": {"c": "x"}},
+ {"a": 1, "b": {"c": "y"}},
+ {"a": 1, "b": {"c": "z"}},
+ {"a": 2, "b": {"c": "z"}},
+ {"a": 1, "baz": 2}
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo[?a==`1`].b.c",
+ "result": ["x", "y", "z"]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"name": "a"}, {"name": "b"}, {"name": "c"}]},
+ "cases": [
+ {
+ "comment": "Filter with or expression",
+ "expression": "foo[?name == 'a' || name == 'b']",
+ "result": [{"name": "a"}, {"name": "b"}]
+ },
+ {
+ "expression": "foo[?name == 'a' || name == 'e']",
+ "result": [{"name": "a"}]
+ },
+ {
+ "expression": "foo[?name == 'a' || name == 'b' || name == 'c']",
+ "result": [{"name": "a"}, {"name": "b"}, {"name": "c"}]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]},
+ "cases": [
+ {
+ "comment": "Filter with and expression",
+ "expression": "foo[?a == `1` && b == `2`]",
+ "result": [{"a": 1, "b": 2}]
+ },
+ {
+ "expression": "foo[?a == `1` && b == `4`]",
+ "result": []
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]},
+ "cases": [
+ {
+ "comment": "Filter with Or and And expressions",
+ "expression": "foo[?c == `3` || a == `1` && b == `4`]",
+ "result": [{"a": 1, "b": 2, "c": 3}]
+ },
+ {
+ "expression": "foo[?b == `2` || a == `3` && b == `4`]",
+ "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
+ },
+ {
+ "expression": "foo[?a == `3` && b == `4` || b == `2`]",
+ "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
+ },
+ {
+ "expression": "foo[?(a == `3` && b == `4`) || b == `2`]",
+ "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
+ },
+ {
+ "expression": "foo[?((a == `3` && b == `4`)) || b == `2`]",
+ "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]
+ },
+ {
+ "expression": "foo[?a == `3` && (b == `4` || b == `2`)]",
+ "result": [{"a": 3, "b": 4}]
+ },
+ {
+ "expression": "foo[?a == `3` && ((b == `4` || b == `2`))]",
+ "result": [{"a": 3, "b": 4}]
+ }
+ ]
+ },
+ {
+ "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]},
+ "cases": [
+ {
+ "comment": "Verify precedence of or/and expressions",
+ "expression": "foo[?a == `1` || b ==`2` && c == `5`]",
+ "result": [{"a": 1, "b": 2, "c": 3}]
+ },
+ {
+ "comment": "Parentheses can alter precedence",
+ "expression": "foo[?(a == `1` || b ==`2`) && c == `5`]",
+ "result": []
+ },
+ {
+ "comment": "Not expressions combined with and/or",
+ "expression": "foo[?!(a == `1` || b ==`2`)]",
+ "result": [{"a": 3, "b": 4}]
+ }
+ ]
+ },
+ {
+ "given": {
+ "foo": [
+ {"key": true},
+ {"key": false},
+ {"key": []},
+ {"key": {}},
+ {"key": [0]},
+ {"key": {"a": "b"}},
+ {"key": 0},
+ {"key": 1},
+ {"key": null},
+ {"notkey": true}
+ ]
+ },
+ "cases": [
+ {
+ "comment": "Unary filter expression",
+ "expression": "foo[?key]",
+ "result": [
+ {"key": true}, {"key": [0]}, {"key": {"a": "b"}},
+ {"key": 0}, {"key": 1}
+ ]
+ },
+ {
+ "comment": "Unary not filter expression",
+ "expression": "foo[?!key]",
+ "result": [
+ {"key": false}, {"key": []}, {"key": {}},
+ {"key": null}, {"notkey": true}
+ ]
+ },
+ {
+ "comment": "Equality with null RHS",
+ "expression": "foo[?key == `null`]",
+ "result": [
+ {"key": null}, {"notkey": true}
+ ]
+ }
+ ]
+ },
+ {
+ "given": {
+ "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ "cases": [
+ {
+ "comment": "Using @ in a filter expression",
+ "expression": "foo[?@ < `5`]",
+ "result": [0, 1, 2, 3, 4]
+ },
+ {
+ "comment": "Using @ in a filter expression",
+ "expression": "foo[?`5` > @]",
+ "result": [0, 1, 2, 3, 4]
+ },
+ {
+ "comment": "Using @ in a filter expression",
+ "expression": "foo[?@ == @]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/functions.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/functions.json
new file mode 100644
index 00000000..d2ec9369
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/functions.json
@@ -0,0 +1,829 @@
+[{
+ "given":
+ {
+ "foo": -1,
+ "zero": 0,
+ "numbers": [-1, 3, 4, 5],
+ "array": [-1, 3, 4, 5, "a", "100"],
+ "strings": ["a", "b", "c"],
+ "decimals": [1.01, 1.2, -1.5],
+ "str": "Str",
+ "false": false,
+ "empty_list": [],
+ "empty_hash": {},
+ "objects": {"foo": "bar", "bar": "baz"},
+ "null_key": null
+ },
+ "cases": [
+ {
+ "expression": "abs(foo)",
+ "result": 1
+ },
+ {
+ "expression": "abs(foo)",
+ "result": 1
+ },
+ {
+ "expression": "abs(str)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "abs(array[1])",
+ "result": 3
+ },
+ {
+ "expression": "abs(array[1])",
+ "result": 3
+ },
+ {
+ "expression": "abs(`false`)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "abs(`-24`)",
+ "result": 24
+ },
+ {
+ "expression": "abs(`-24`)",
+ "result": 24
+ },
+ {
+ "expression": "abs(`1`, `2`)",
+ "error": "invalid-arity"
+ },
+ {
+ "expression": "abs()",
+ "error": "invalid-arity"
+ },
+ {
+ "expression": "unknown_function(`1`, `2`)",
+ "error": "unknown-function"
+ },
+ {
+ "expression": "avg(numbers)",
+ "result": 2.75
+ },
+ {
+ "expression": "avg(array)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "avg('abc')",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "avg(foo)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "avg(@)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "avg(strings)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "avg(empty_list)",
+ "result": null
+ },
+ {
+ "expression": "ceil(`1.2`)",
+ "result": 2
+ },
+ {
+ "expression": "ceil(decimals[0])",
+ "result": 2
+ },
+ {
+ "expression": "ceil(decimals[1])",
+ "result": 2
+ },
+ {
+ "expression": "ceil(decimals[2])",
+ "result": -1
+ },
+ {
+ "expression": "ceil('string')",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "contains('abc', 'a')",
+ "result": true
+ },
+ {
+ "expression": "contains('abc', 'd')",
+ "result": false
+ },
+ {
+ "expression": "contains(`false`, 'd')",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "contains(strings, 'a')",
+ "result": true
+ },
+ {
+ "expression": "contains(decimals, `1.2`)",
+ "result": true
+ },
+ {
+ "expression": "contains(decimals, `false`)",
+ "result": false
+ },
+ {
+ "expression": "ends_with(str, 'r')",
+ "result": true
+ },
+ {
+ "expression": "ends_with(str, 'tr')",
+ "result": true
+ },
+ {
+ "expression": "ends_with(str, 'Str')",
+ "result": true
+ },
+ {
+ "expression": "ends_with(str, 'SStr')",
+ "result": false
+ },
+ {
+ "expression": "ends_with(str, 'foo')",
+ "result": false
+ },
+ {
+ "expression": "ends_with(str, `0`)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "floor(`1.2`)",
+ "result": 1
+ },
+ {
+ "expression": "floor('string')",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "floor(decimals[0])",
+ "result": 1
+ },
+ {
+ "expression": "floor(foo)",
+ "result": -1
+ },
+ {
+ "expression": "floor(str)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "length('abc')",
+ "result": 3
+ },
+ {
+ "expression": "length('✓foo')",
+ "result": 4
+ },
+ {
+ "expression": "length('')",
+ "result": 0
+ },
+ {
+ "expression": "length(@)",
+ "result": 12
+ },
+ {
+ "expression": "length(strings[0])",
+ "result": 1
+ },
+ {
+ "expression": "length(str)",
+ "result": 3
+ },
+ {
+ "expression": "length(array)",
+ "result": 6
+ },
+ {
+ "expression": "length(objects)",
+ "result": 2
+ },
+ {
+ "expression": "length(`false`)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "length(foo)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "length(strings[0])",
+ "result": 1
+ },
+ {
+ "expression": "max(numbers)",
+ "result": 5
+ },
+ {
+ "expression": "max(decimals)",
+ "result": 1.2
+ },
+ {
+ "expression": "max(strings)",
+ "result": "c"
+ },
+ {
+ "expression": "max(abc)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "max(array)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "max(decimals)",
+ "result": 1.2
+ },
+ {
+ "expression": "max(empty_list)",
+ "result": null
+ },
+ {
+ "expression": "merge(`{}`)",
+ "result": {}
+ },
+ {
+ "expression": "merge(`{}`, `{}`)",
+ "result": {}
+ },
+ {
+ "expression": "merge(`{\"a\": 1}`, `{\"b\": 2}`)",
+ "result": {"a": 1, "b": 2}
+ },
+ {
+ "expression": "merge(`{\"a\": 1}`, `{\"a\": 2}`)",
+ "result": {"a": 2}
+ },
+ {
+ "expression": "merge(`{\"a\": 1, \"b\": 2}`, `{\"a\": 2, \"c\": 3}`, `{\"d\": 4}`)",
+ "result": {"a": 2, "b": 2, "c": 3, "d": 4}
+ },
+ {
+ "expression": "min(numbers)",
+ "result": -1
+ },
+ {
+ "expression": "min(decimals)",
+ "result": -1.5
+ },
+ {
+ "expression": "min(abc)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "min(array)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "min(empty_list)",
+ "result": null
+ },
+ {
+ "expression": "min(decimals)",
+ "result": -1.5
+ },
+ {
+ "expression": "min(strings)",
+ "result": "a"
+ },
+ {
+ "expression": "type('abc')",
+ "result": "string"
+ },
+ {
+ "expression": "type(`1.0`)",
+ "result": "number"
+ },
+ {
+ "expression": "type(`2`)",
+ "result": "number"
+ },
+ {
+ "expression": "type(`true`)",
+ "result": "boolean"
+ },
+ {
+ "expression": "type(`false`)",
+ "result": "boolean"
+ },
+ {
+ "expression": "type(`null`)",
+ "result": "null"
+ },
+ {
+ "expression": "type(`[0]`)",
+ "result": "array"
+ },
+ {
+ "expression": "type(`{\"a\": \"b\"}`)",
+ "result": "object"
+ },
+ {
+ "expression": "type(@)",
+ "result": "object"
+ },
+ {
+ "expression": "sort(keys(objects))",
+ "result": ["bar", "foo"]
+ },
+ {
+ "expression": "keys(foo)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "keys(strings)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "keys(`false`)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort(values(objects))",
+ "result": ["bar", "baz"]
+ },
+ {
+ "expression": "keys(empty_hash)",
+ "result": []
+ },
+ {
+ "expression": "values(foo)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "join(', ', strings)",
+ "result": "a, b, c"
+ },
+ {
+ "expression": "join(', ', strings)",
+ "result": "a, b, c"
+ },
+ {
+ "expression": "join(',', `[\"a\", \"b\"]`)",
+ "result": "a,b"
+ },
+ {
+ "expression": "join(',', `[\"a\", 0]`)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "join(', ', str)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "join('|', strings)",
+ "result": "a|b|c"
+ },
+ {
+ "expression": "join(`2`, strings)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "join('|', decimals)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "join('|', decimals[].to_string(@))",
+ "result": "1.01|1.2|-1.5"
+ },
+ {
+ "expression": "join('|', empty_list)",
+ "result": ""
+ },
+ {
+ "expression": "reverse(numbers)",
+ "result": [5, 4, 3, -1]
+ },
+ {
+ "expression": "reverse(array)",
+ "result": ["100", "a", 5, 4, 3, -1]
+ },
+ {
+ "expression": "reverse(`[]`)",
+ "result": []
+ },
+ {
+ "expression": "reverse('')",
+ "result": ""
+ },
+ {
+ "expression": "reverse('hello world')",
+ "result": "dlrow olleh"
+ },
+ {
+ "expression": "starts_with(str, 'S')",
+ "result": true
+ },
+ {
+ "expression": "starts_with(str, 'St')",
+ "result": true
+ },
+ {
+ "expression": "starts_with(str, 'Str')",
+ "result": true
+ },
+ {
+ "expression": "starts_with(str, 'String')",
+ "result": false
+ },
+ {
+ "expression": "starts_with(str, `0`)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sum(numbers)",
+ "result": 11
+ },
+ {
+ "expression": "sum(decimals)",
+ "result": 0.71
+ },
+ {
+ "expression": "sum(array)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sum(array[].to_number(@))",
+ "result": 111
+ },
+ {
+ "expression": "sum(`[]`)",
+ "result": 0
+ },
+ {
+ "expression": "to_array('foo')",
+ "result": ["foo"]
+ },
+ {
+ "expression": "to_array(`0`)",
+ "result": [0]
+ },
+ {
+ "expression": "to_array(objects)",
+ "result": [{"foo": "bar", "bar": "baz"}]
+ },
+ {
+ "expression": "to_array(`[1, 2, 3]`)",
+ "result": [1, 2, 3]
+ },
+ {
+ "expression": "to_array(false)",
+ "result": [false]
+ },
+ {
+ "expression": "to_string('foo')",
+ "result": "foo"
+ },
+ {
+ "expression": "to_string(`1.2`)",
+ "result": "1.2"
+ },
+ {
+ "expression": "to_string(`[0, 1]`)",
+ "result": "[0,1]"
+ },
+ {
+ "expression": "to_number('1.0')",
+ "result": 1.0
+ },
+ {
+ "expression": "to_number('1.1')",
+ "result": 1.1
+ },
+ {
+ "expression": "to_number('4')",
+ "result": 4
+ },
+ {
+ "expression": "to_number('notanumber')",
+ "result": null
+ },
+ {
+ "expression": "to_number(`false`)",
+ "result": null
+ },
+ {
+ "expression": "to_number(`null`)",
+ "result": null
+ },
+ {
+ "expression": "to_number(`[0]`)",
+ "result": null
+ },
+ {
+ "expression": "to_number(`{\"foo\": 0}`)",
+ "result": null
+ },
+ {
+ "expression": "\"to_string\"(`1.0`)",
+ "error": "syntax"
+ },
+ {
+ "expression": "sort(numbers)",
+ "result": [-1, 3, 4, 5]
+ },
+ {
+ "expression": "sort(strings)",
+ "result": ["a", "b", "c"]
+ },
+ {
+ "expression": "sort(decimals)",
+ "result": [-1.5, 1.01, 1.2]
+ },
+ {
+ "expression": "sort(array)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort(abc)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort(empty_list)",
+ "result": []
+ },
+ {
+ "expression": "sort(@)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "not_null(unknown_key, str)",
+ "result": "Str"
+ },
+ {
+ "expression": "not_null(unknown_key, foo.bar, empty_list, str)",
+ "result": []
+ },
+ {
+ "expression": "not_null(unknown_key, null_key, empty_list, str)",
+ "result": []
+ },
+ {
+ "expression": "not_null(all, expressions, are_null)",
+ "result": null
+ },
+ {
+ "expression": "not_null()",
+ "error": "invalid-arity"
+ },
+ {
+ "comment": "function projection on single arg function",
+ "expression": "numbers[].to_string(@)",
+ "result": ["-1", "3", "4", "5"]
+ },
+ {
+ "comment": "function projection on single arg function",
+ "expression": "array[].to_number(@)",
+ "result": [-1, 3, 4, 5, 100]
+ }
+ ]
+}, {
+ "given":
+ {
+ "foo": [
+ {"b": "b", "a": "a"},
+ {"c": "c", "b": "b"},
+ {"d": "d", "c": "c"},
+ {"e": "e", "d": "d"},
+ {"f": "f", "e": "e"}
+ ]
+ },
+ "cases": [
+ {
+ "comment": "function projection on variadic function",
+ "expression": "foo[].not_null(f, e, d, c, b, a)",
+ "result": ["b", "c", "d", "e", "f"]
+ }
+ ]
+}, {
+ "given":
+ {
+ "people": [
+ {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
+ {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
+ {"age": 30, "age_str": "30", "bool": true, "name": "c"},
+ {"age": 50, "age_str": "50", "bool": false, "name": "d"},
+ {"age": 10, "age_str": "10", "bool": true, "name": 3}
+ ]
+ },
+ "cases": [
+ {
+ "comment": "sort by field expression",
+ "expression": "sort_by(people, &age)",
+ "result": [
+ {"age": 10, "age_str": "10", "bool": true, "name": 3},
+ {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
+ {"age": 30, "age_str": "30", "bool": true, "name": "c"},
+ {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
+ {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ ]
+ },
+ {
+ "expression": "sort_by(people, &age_str)",
+ "result": [
+ {"age": 10, "age_str": "10", "bool": true, "name": 3},
+ {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
+ {"age": 30, "age_str": "30", "bool": true, "name": "c"},
+ {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
+ {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ ]
+ },
+ {
+ "comment": "sort by function expression",
+ "expression": "sort_by(people, &to_number(age_str))",
+ "result": [
+ {"age": 10, "age_str": "10", "bool": true, "name": 3},
+ {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
+ {"age": 30, "age_str": "30", "bool": true, "name": "c"},
+ {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
+ {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ ]
+ },
+ {
+ "comment": "function projection on sort_by function",
+ "expression": "sort_by(people, &age)[].name",
+ "result": [3, "a", "c", "b", "d"]
+ },
+ {
+ "expression": "sort_by(people, &extra)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort_by(people, &bool)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort_by(people, &name)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort_by(people, name)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "sort_by(people, &age)[].extra",
+ "result": ["foo", "bar"]
+ },
+ {
+ "expression": "sort_by(`[]`, &age)",
+ "result": []
+ },
+ {
+ "expression": "max_by(people, &age)",
+ "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ },
+ {
+ "expression": "max_by(people, &age_str)",
+ "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ },
+ {
+ "expression": "max_by(people, &bool)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "max_by(people, &extra)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "max_by(people, &to_number(age_str))",
+ "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ },
+ {
+ "expression": "min_by(people, &age)",
+ "result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
+ },
+ {
+ "expression": "min_by(people, &age_str)",
+ "result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
+ },
+ {
+ "expression": "min_by(people, &bool)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "min_by(people, &extra)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "min_by(people, &to_number(age_str))",
+ "result": {"age": 10, "age_str": "10", "bool": true, "name": 3}
+ }
+ ]
+}, {
+ "given":
+ {
+ "people": [
+ {"age": 10, "order": "1"},
+ {"age": 10, "order": "2"},
+ {"age": 10, "order": "3"},
+ {"age": 10, "order": "4"},
+ {"age": 10, "order": "5"},
+ {"age": 10, "order": "6"},
+ {"age": 10, "order": "7"},
+ {"age": 10, "order": "8"},
+ {"age": 10, "order": "9"},
+ {"age": 10, "order": "10"},
+ {"age": 10, "order": "11"}
+ ]
+ },
+ "cases": [
+ {
+ "comment": "stable sort order",
+ "expression": "sort_by(people, &age)",
+ "result": [
+ {"age": 10, "order": "1"},
+ {"age": 10, "order": "2"},
+ {"age": 10, "order": "3"},
+ {"age": 10, "order": "4"},
+ {"age": 10, "order": "5"},
+ {"age": 10, "order": "6"},
+ {"age": 10, "order": "7"},
+ {"age": 10, "order": "8"},
+ {"age": 10, "order": "9"},
+ {"age": 10, "order": "10"},
+ {"age": 10, "order": "11"}
+ ]
+ }
+ ]
+}, {
+ "given":
+ {
+ "people": [
+ {"a": 10, "b": 1, "c": "z"},
+ {"a": 10, "b": 2, "c": null},
+ {"a": 10, "b": 3},
+ {"a": 10, "b": 4, "c": "z"},
+ {"a": 10, "b": 5, "c": null},
+ {"a": 10, "b": 6},
+ {"a": 10, "b": 7, "c": "z"},
+ {"a": 10, "b": 8, "c": null},
+ {"a": 10, "b": 9}
+ ],
+ "empty": []
+ },
+ "cases": [
+ {
+ "expression": "map(&a, people)",
+ "result": [10, 10, 10, 10, 10, 10, 10, 10, 10]
+ },
+ {
+ "expression": "map(&c, people)",
+ "result": ["z", null, null, "z", null, null, "z", null, null]
+ },
+ {
+ "expression": "map(&a, badkey)",
+ "error": "invalid-type"
+ },
+ {
+ "expression": "map(&foo, empty)",
+ "result": []
+ }
+ ]
+}, {
+ "given": {
+ "array": [
+ {
+ "foo": {"bar": "yes1"}
+ },
+ {
+ "foo": {"bar": "yes2"}
+ },
+ {
+ "foo1": {"bar": "no"}
+ }
+ ]},
+ "cases": [
+ {
+ "expression": "map(&foo.bar, array)",
+ "result": ["yes1", "yes2", null]
+ },
+ {
+ "expression": "map(&foo1.bar, array)",
+ "result": [null, null, "no"]
+ },
+ {
+ "expression": "map(&foo.bar.baz, array)",
+ "result": [null, null, null]
+ }
+ ]
+}, {
+ "given": {
+ "array": [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]]
+ },
+ "cases": [
+ {
+ "expression": "map(&[], array)",
+ "result": [[1, 2, 3, 4], [5, 6, 7, 8, 9]]
+ }
+ ]
+}
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/identifiers.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/identifiers.json
new file mode 100644
index 00000000..7998a41a
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/identifiers.json
@@ -0,0 +1,1377 @@
+[
+ {
+ "given": {
+ "__L": true
+ },
+ "cases": [
+ {
+ "expression": "__L",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "!\r": true
+ },
+ "cases": [
+ {
+ "expression": "\"!\\r\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "Y_1623": true
+ },
+ "cases": [
+ {
+ "expression": "Y_1623",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "x": true
+ },
+ "cases": [
+ {
+ "expression": "x",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\tF\uCebb": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\tF\\uCebb\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ " \t": true
+ },
+ "cases": [
+ {
+ "expression": "\" \\t\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ " ": true
+ },
+ "cases": [
+ {
+ "expression": "\" \"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "v2": true
+ },
+ "cases": [
+ {
+ "expression": "v2",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\t": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\t\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_X": true
+ },
+ "cases": [
+ {
+ "expression": "_X",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\t4\ud9da\udd15": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\t4\\ud9da\\udd15\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "v24_W": true
+ },
+ "cases": [
+ {
+ "expression": "v24_W",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "H": true
+ },
+ "cases": [
+ {
+ "expression": "\"H\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\f": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\f\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "E4": true
+ },
+ "cases": [
+ {
+ "expression": "\"E4\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "!": true
+ },
+ "cases": [
+ {
+ "expression": "\"!\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "tM": true
+ },
+ "cases": [
+ {
+ "expression": "tM",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ " [": true
+ },
+ "cases": [
+ {
+ "expression": "\" [\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "R!": true
+ },
+ "cases": [
+ {
+ "expression": "\"R!\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_6W": true
+ },
+ "cases": [
+ {
+ "expression": "_6W",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\uaBA1\r": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\uaBA1\\r\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "tL7": true
+ },
+ "cases": [
+ {
+ "expression": "tL7",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "<": true
+ },
+ "cases": [
+ {
+ "expression": "\">\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "hvu": true
+ },
+ "cases": [
+ {
+ "expression": "hvu",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "; !": true
+ },
+ "cases": [
+ {
+ "expression": "\"; !\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "hU": true
+ },
+ "cases": [
+ {
+ "expression": "hU",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "!I\n\/": true
+ },
+ "cases": [
+ {
+ "expression": "\"!I\\n\\/\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\uEEbF": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\uEEbF\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "U)\t": true
+ },
+ "cases": [
+ {
+ "expression": "\"U)\\t\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "fa0_9": true
+ },
+ "cases": [
+ {
+ "expression": "fa0_9",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "/": true
+ },
+ "cases": [
+ {
+ "expression": "\"/\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "Gy": true
+ },
+ "cases": [
+ {
+ "expression": "Gy",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\b": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\b\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "<": true
+ },
+ "cases": [
+ {
+ "expression": "\"<\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\t": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\t\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\t&\\\r": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\t&\\\\\\r\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "#": true
+ },
+ "cases": [
+ {
+ "expression": "\"#\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "B__": true
+ },
+ "cases": [
+ {
+ "expression": "B__",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\nS \n": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\nS \\n\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "Bp": true
+ },
+ "cases": [
+ {
+ "expression": "Bp",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ ",\t;": true
+ },
+ "cases": [
+ {
+ "expression": "\",\\t;\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "B_q": true
+ },
+ "cases": [
+ {
+ "expression": "B_q",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\/+\t\n\b!Z": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\/+\\t\\n\\b!Z\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\udadd\udfc7\\ueFAc": true
+ },
+ "cases": [
+ {
+ "expression": "\"\udadd\udfc7\\\\ueFAc\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ ":\f": true
+ },
+ "cases": [
+ {
+ "expression": "\":\\f\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\/": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\/\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_BW_6Hg_Gl": true
+ },
+ "cases": [
+ {
+ "expression": "_BW_6Hg_Gl",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\udbcf\udc02": true
+ },
+ "cases": [
+ {
+ "expression": "\"\udbcf\udc02\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "zs1DC": true
+ },
+ "cases": [
+ {
+ "expression": "zs1DC",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "__434": true
+ },
+ "cases": [
+ {
+ "expression": "__434",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\udb94\udd41": true
+ },
+ "cases": [
+ {
+ "expression": "\"\udb94\udd41\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "Z_5": true
+ },
+ "cases": [
+ {
+ "expression": "Z_5",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "z_M_": true
+ },
+ "cases": [
+ {
+ "expression": "z_M_",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "YU_2": true
+ },
+ "cases": [
+ {
+ "expression": "YU_2",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_0": true
+ },
+ "cases": [
+ {
+ "expression": "_0",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\b+": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\b+\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\"": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\\"\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "D7": true
+ },
+ "cases": [
+ {
+ "expression": "D7",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_62L": true
+ },
+ "cases": [
+ {
+ "expression": "_62L",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\tK\t": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\tK\\t\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\n\\\f": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\n\\\\\\f\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "I_": true
+ },
+ "cases": [
+ {
+ "expression": "I_",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "W_a0_": true
+ },
+ "cases": [
+ {
+ "expression": "W_a0_",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "BQ": true
+ },
+ "cases": [
+ {
+ "expression": "BQ",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\tX$\uABBb": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\tX$\\uABBb\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "Z9": true
+ },
+ "cases": [
+ {
+ "expression": "Z9",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\b%\"\uda38\udd0f": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\b%\\\"\uda38\udd0f\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_F": true
+ },
+ "cases": [
+ {
+ "expression": "_F",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "!,": true
+ },
+ "cases": [
+ {
+ "expression": "\"!,\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\"!": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\\"!\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "Hh": true
+ },
+ "cases": [
+ {
+ "expression": "Hh",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "&": true
+ },
+ "cases": [
+ {
+ "expression": "\"&\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "9\r\\R": true
+ },
+ "cases": [
+ {
+ "expression": "\"9\\r\\\\R\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "M_k": true
+ },
+ "cases": [
+ {
+ "expression": "M_k",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "!\b\n\udb06\ude52\"\"": true
+ },
+ "cases": [
+ {
+ "expression": "\"!\\b\\n\udb06\ude52\\\"\\\"\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "6": true
+ },
+ "cases": [
+ {
+ "expression": "\"6\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_7": true
+ },
+ "cases": [
+ {
+ "expression": "_7",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "0": true
+ },
+ "cases": [
+ {
+ "expression": "\"0\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\\8\\": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\\\8\\\\\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "b7eo": true
+ },
+ "cases": [
+ {
+ "expression": "b7eo",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "xIUo9": true
+ },
+ "cases": [
+ {
+ "expression": "xIUo9",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "5": true
+ },
+ "cases": [
+ {
+ "expression": "\"5\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "?": true
+ },
+ "cases": [
+ {
+ "expression": "\"?\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "sU": true
+ },
+ "cases": [
+ {
+ "expression": "sU",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "VH2&H\\\/": true
+ },
+ "cases": [
+ {
+ "expression": "\"VH2&H\\\\\\/\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_C": true
+ },
+ "cases": [
+ {
+ "expression": "_C",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "_": true
+ },
+ "cases": [
+ {
+ "expression": "_",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "<\t": true
+ },
+ "cases": [
+ {
+ "expression": "\"<\\t\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {
+ "\uD834\uDD1E": true
+ },
+ "cases": [
+ {
+ "expression": "\"\\uD834\\uDD1E\"",
+ "result": true
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/indices.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/indices.json
new file mode 100644
index 00000000..aa03b35d
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/indices.json
@@ -0,0 +1,346 @@
+[{
+ "given":
+ {"foo": {"bar": ["zero", "one", "two"]}},
+ "cases": [
+ {
+ "expression": "foo.bar[0]",
+ "result": "zero"
+ },
+ {
+ "expression": "foo.bar[1]",
+ "result": "one"
+ },
+ {
+ "expression": "foo.bar[2]",
+ "result": "two"
+ },
+ {
+ "expression": "foo.bar[3]",
+ "result": null
+ },
+ {
+ "expression": "foo.bar[-1]",
+ "result": "two"
+ },
+ {
+ "expression": "foo.bar[-2]",
+ "result": "one"
+ },
+ {
+ "expression": "foo.bar[-3]",
+ "result": "zero"
+ },
+ {
+ "expression": "foo.bar[-4]",
+ "result": null
+ }
+ ]
+},
+{
+ "given":
+ {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]},
+ "cases": [
+ {
+ "expression": "foo.bar",
+ "result": null
+ },
+ {
+ "expression": "foo[0].bar",
+ "result": "one"
+ },
+ {
+ "expression": "foo[1].bar",
+ "result": "two"
+ },
+ {
+ "expression": "foo[2].bar",
+ "result": "three"
+ },
+ {
+ "expression": "foo[3].notbar",
+ "result": "four"
+ },
+ {
+ "expression": "foo[3].bar",
+ "result": null
+ },
+ {
+ "expression": "foo[0]",
+ "result": {"bar": "one"}
+ },
+ {
+ "expression": "foo[1]",
+ "result": {"bar": "two"}
+ },
+ {
+ "expression": "foo[2]",
+ "result": {"bar": "three"}
+ },
+ {
+ "expression": "foo[3]",
+ "result": {"notbar": "four"}
+ },
+ {
+ "expression": "foo[4]",
+ "result": null
+ }
+ ]
+},
+{
+ "given": [
+ "one", "two", "three"
+ ],
+ "cases": [
+ {
+ "expression": "[0]",
+ "result": "one"
+ },
+ {
+ "expression": "[1]",
+ "result": "two"
+ },
+ {
+ "expression": "[2]",
+ "result": "three"
+ },
+ {
+ "expression": "[-1]",
+ "result": "three"
+ },
+ {
+ "expression": "[-2]",
+ "result": "two"
+ },
+ {
+ "expression": "[-3]",
+ "result": "one"
+ }
+ ]
+},
+{
+ "given": {"reservations": [
+ {"instances": [{"foo": 1}, {"foo": 2}]}
+ ]},
+ "cases": [
+ {
+ "expression": "reservations[].instances[].foo",
+ "result": [1, 2]
+ },
+ {
+ "expression": "reservations[].instances[].bar",
+ "result": []
+ },
+ {
+ "expression": "reservations[].notinstances[].foo",
+ "result": []
+ },
+ {
+ "expression": "reservations[].notinstances[].foo",
+ "result": []
+ }
+ ]
+},
+{
+ "given": {"reservations": [{
+ "instances": [
+ {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]},
+ {"foo": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]},
+ {"foo": "bar"},
+ {"notfoo": [{"bar": 20}, {"bar": 21}, {"notbar": [7]}, {"bar": 22}]},
+ {"bar": [{"baz": [1]}, {"baz": [2]}, {"baz": [3]}, {"baz": [4]}]},
+ {"baz": [{"baz": [1, 2]}, {"baz": []}, {"baz": []}, {"baz": [3, 4]}]},
+ {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]}
+ ],
+ "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}
+ }, {
+ "instances": [
+ {"a": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]},
+ {"b": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]},
+ {"c": "bar"},
+ {"notfoo": [{"bar": 23}, {"bar": 24}, {"notbar": [7]}, {"bar": 25}]},
+ {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]}
+ ],
+ "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}
+ }
+ ]},
+ "cases": [
+ {
+ "expression": "reservations[].instances[].foo[].bar",
+ "result": [1, 2, 4, 5, 6, 8]
+ },
+ {
+ "expression": "reservations[].instances[].foo[].baz",
+ "result": []
+ },
+ {
+ "expression": "reservations[].instances[].notfoo[].bar",
+ "result": [20, 21, 22, 23, 24, 25]
+ },
+ {
+ "expression": "reservations[].instances[].notfoo[].notbar",
+ "result": [[7], [7]]
+ },
+ {
+ "expression": "reservations[].notinstances[].foo",
+ "result": []
+ },
+ {
+ "expression": "reservations[].instances[].foo[].notbar",
+ "result": [3, [7]]
+ },
+ {
+ "expression": "reservations[].instances[].bar[].baz",
+ "result": [[1], [2], [3], [4]]
+ },
+ {
+ "expression": "reservations[].instances[].baz[].baz",
+ "result": [[1, 2], [], [], [3, 4]]
+ },
+ {
+ "expression": "reservations[].instances[].qux[].baz",
+ "result": [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []]
+ },
+ {
+ "expression": "reservations[].instances[].qux[].baz[]",
+ "result": [1, 2, 3, 4, 1, 2, 3, 4]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [
+ [["one", "two"], ["three", "four"]],
+ [["five", "six"], ["seven", "eight"]],
+ [["nine"], ["ten"]]
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo[]",
+ "result": [["one", "two"], ["three", "four"], ["five", "six"],
+ ["seven", "eight"], ["nine"], ["ten"]]
+ },
+ {
+ "expression": "foo[][0]",
+ "result": ["one", "three", "five", "seven", "nine", "ten"]
+ },
+ {
+ "expression": "foo[][1]",
+ "result": ["two", "four", "six", "eight"]
+ },
+ {
+ "expression": "foo[][0][0]",
+ "result": []
+ },
+ {
+ "expression": "foo[][2][2]",
+ "result": []
+ },
+ {
+ "expression": "foo[][0][0][100]",
+ "result": []
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [{
+ "bar": [
+ {
+ "qux": 2,
+ "baz": 1
+ },
+ {
+ "qux": 4,
+ "baz": 3
+ }
+ ]
+ },
+ {
+ "bar": [
+ {
+ "qux": 6,
+ "baz": 5
+ },
+ {
+ "qux": 8,
+ "baz": 7
+ }
+ ]
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo",
+ "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
+ {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
+ },
+ {
+ "expression": "foo[]",
+ "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
+ {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
+ },
+ {
+ "expression": "foo[].bar",
+ "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}],
+ [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]]
+ },
+ {
+ "expression": "foo[].bar[]",
+ "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3},
+ {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]
+ },
+ {
+ "expression": "foo[].bar[].baz",
+ "result": [1, 3, 5, 7]
+ }
+ ]
+},
+{
+ "given": {
+ "string": "string",
+ "hash": {"foo": "bar", "bar": "baz"},
+ "number": 23,
+ "nullvalue": null
+ },
+ "cases": [
+ {
+ "expression": "string[]",
+ "result": null
+ },
+ {
+ "expression": "hash[]",
+ "result": null
+ },
+ {
+ "expression": "number[]",
+ "result": null
+ },
+ {
+ "expression": "nullvalue[]",
+ "result": null
+ },
+ {
+ "expression": "string[].foo",
+ "result": null
+ },
+ {
+ "expression": "hash[].foo",
+ "result": null
+ },
+ {
+ "expression": "number[].foo",
+ "result": null
+ },
+ {
+ "expression": "nullvalue[].foo",
+ "result": null
+ },
+ {
+ "expression": "nullvalue[].foo[].bar",
+ "result": null
+ }
+ ]
+}
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/literal.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/literal.json
new file mode 100644
index 00000000..b5ddbeda
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/literal.json
@@ -0,0 +1,200 @@
+[
+ {
+ "given": {
+ "foo": [{"name": "a"}, {"name": "b"}],
+ "bar": {"baz": "qux"}
+ },
+ "cases": [
+ {
+ "expression": "`\"foo\"`",
+ "result": "foo"
+ },
+ {
+ "comment": "Interpret escaped unicode.",
+ "expression": "`\"\\u03a6\"`",
+ "result": "Φ"
+ },
+ {
+ "expression": "`\"✓\"`",
+ "result": "✓"
+ },
+ {
+ "expression": "`[1, 2, 3]`",
+ "result": [1, 2, 3]
+ },
+ {
+ "expression": "`{\"a\": \"b\"}`",
+ "result": {"a": "b"}
+ },
+ {
+ "expression": "`true`",
+ "result": true
+ },
+ {
+ "expression": "`false`",
+ "result": false
+ },
+ {
+ "expression": "`null`",
+ "result": null
+ },
+ {
+ "expression": "`0`",
+ "result": 0
+ },
+ {
+ "expression": "`1`",
+ "result": 1
+ },
+ {
+ "expression": "`2`",
+ "result": 2
+ },
+ {
+ "expression": "`3`",
+ "result": 3
+ },
+ {
+ "expression": "`4`",
+ "result": 4
+ },
+ {
+ "expression": "`5`",
+ "result": 5
+ },
+ {
+ "expression": "`6`",
+ "result": 6
+ },
+ {
+ "expression": "`7`",
+ "result": 7
+ },
+ {
+ "expression": "`8`",
+ "result": 8
+ },
+ {
+ "expression": "`9`",
+ "result": 9
+ },
+ {
+ "comment": "Escaping a backtick in quotes",
+ "expression": "`\"foo\\`bar\"`",
+ "result": "foo`bar"
+ },
+ {
+ "comment": "Double quote in literal",
+ "expression": "`\"foo\\\"bar\"`",
+ "result": "foo\"bar"
+ },
+ {
+ "expression": "`\"1\\`\"`",
+ "result": "1`"
+ },
+ {
+ "comment": "Multiple literal expressions with escapes",
+ "expression": "`\"\\\\\"`.{a:`\"b\"`}",
+ "result": {"a": "b"}
+ },
+ {
+ "comment": "literal . identifier",
+ "expression": "`{\"a\": \"b\"}`.a",
+ "result": "b"
+ },
+ {
+ "comment": "literal . identifier . identifier",
+ "expression": "`{\"a\": {\"b\": \"c\"}}`.a.b",
+ "result": "c"
+ },
+ {
+ "comment": "literal . identifier bracket-expr",
+ "expression": "`[0, 1, 2]`[1]",
+ "result": 1
+ }
+ ]
+ },
+ {
+ "comment": "Literals",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "comment": "Literal with leading whitespace",
+ "expression": "` {\"foo\": true}`",
+ "result": {"foo": true}
+ },
+ {
+ "comment": "Literal with trailing whitespace",
+ "expression": "`{\"foo\": true} `",
+ "result": {"foo": true}
+ },
+ {
+ "comment": "Literal on RHS of subexpr not allowed",
+ "expression": "foo.`\"bar\"`",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Raw String Literals",
+ "given": {},
+ "cases": [
+ {
+ "expression": "'foo'",
+ "result": "foo"
+ },
+ {
+ "expression": "' foo '",
+ "result": " foo "
+ },
+ {
+ "expression": "'0'",
+ "result": "0"
+ },
+ {
+ "expression": "'newline\n'",
+ "result": "newline\n"
+ },
+ {
+ "expression": "'\n'",
+ "result": "\n"
+ },
+ {
+ "expression": "'✓'",
+ "result": "✓"
+ },
+ {
+ "expression": "'𝄞'",
+ "result": "𝄞"
+ },
+ {
+ "expression": "' [foo] '",
+ "result": " [foo] "
+ },
+ {
+ "expression": "'[foo]'",
+ "result": "[foo]"
+ },
+ {
+ "comment": "Do not interpret escaped unicode.",
+ "expression": "'\\u03a6'",
+ "result": "\\u03a6"
+ },
+ {
+ "comment": "Can escape the single quote",
+ "expression": "'foo\\'bar'",
+ "result": "foo'bar"
+ },
+ {
+ "comment": "Backslash not followed by single quote is treated as any other character",
+ "expression": "'\\z'",
+ "result": "\\z"
+ },
+ {
+ "comment": "Backslash not followed by single quote is treated as any other character",
+ "expression": "'\\\\'",
+ "result": "\\\\"
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/multiselect.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/multiselect.json
new file mode 100644
index 00000000..4f464822
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/multiselect.json
@@ -0,0 +1,398 @@
+[{
+ "given": {
+ "foo": {
+ "bar": "bar",
+ "baz": "baz",
+ "qux": "qux",
+ "nested": {
+ "one": {
+ "a": "first",
+ "b": "second",
+ "c": "third"
+ },
+ "two": {
+ "a": "first",
+ "b": "second",
+ "c": "third"
+ },
+ "three": {
+ "a": "first",
+ "b": "second",
+ "c": {"inner": "third"}
+ }
+ }
+ },
+ "bar": 1,
+ "baz": 2,
+ "qux\"": 3
+ },
+ "cases": [
+ {
+ "expression": "foo.{bar: bar}",
+ "result": {"bar": "bar"}
+ },
+ {
+ "expression": "foo.{\"bar\": bar}",
+ "result": {"bar": "bar"}
+ },
+ {
+ "expression": "foo.{\"foo.bar\": bar}",
+ "result": {"foo.bar": "bar"}
+ },
+ {
+ "expression": "foo.{bar: bar, baz: baz}",
+ "result": {"bar": "bar", "baz": "baz"}
+ },
+ {
+ "expression": "foo.{\"bar\": bar, \"baz\": baz}",
+ "result": {"bar": "bar", "baz": "baz"}
+ },
+ {
+ "expression": "{\"baz\": baz, \"qux\\\"\": \"qux\\\"\"}",
+ "result": {"baz": 2, "qux\"": 3}
+ },
+ {
+ "expression": "foo.{bar:bar,baz:baz}",
+ "result": {"bar": "bar", "baz": "baz"}
+ },
+ {
+ "expression": "foo.{bar: bar,qux: qux}",
+ "result": {"bar": "bar", "qux": "qux"}
+ },
+ {
+ "expression": "foo.{bar: bar, noexist: noexist}",
+ "result": {"bar": "bar", "noexist": null}
+ },
+ {
+ "expression": "foo.{noexist: noexist, alsonoexist: alsonoexist}",
+ "result": {"noexist": null, "alsonoexist": null}
+ },
+ {
+ "expression": "foo.badkey.{nokey: nokey, alsonokey: alsonokey}",
+ "result": null
+ },
+ {
+ "expression": "foo.nested.*.{a: a,b: b}",
+ "result": [{"a": "first", "b": "second"},
+ {"a": "first", "b": "second"},
+ {"a": "first", "b": "second"}]
+ },
+ {
+ "expression": "foo.nested.three.{a: a, cinner: c.inner}",
+ "result": {"a": "first", "cinner": "third"}
+ },
+ {
+ "expression": "foo.nested.three.{a: a, c: c.inner.bad.key}",
+ "result": {"a": "first", "c": null}
+ },
+ {
+ "expression": "foo.{a: nested.one.a, b: nested.two.b}",
+ "result": {"a": "first", "b": "second"}
+ },
+ {
+ "expression": "{bar: bar, baz: baz}",
+ "result": {"bar": 1, "baz": 2}
+ },
+ {
+ "expression": "{bar: bar}",
+ "result": {"bar": 1}
+ },
+ {
+ "expression": "{otherkey: bar}",
+ "result": {"otherkey": 1}
+ },
+ {
+ "expression": "{no: no, exist: exist}",
+ "result": {"no": null, "exist": null}
+ },
+ {
+ "expression": "foo.[bar]",
+ "result": ["bar"]
+ },
+ {
+ "expression": "foo.[bar,baz]",
+ "result": ["bar", "baz"]
+ },
+ {
+ "expression": "foo.[bar,qux]",
+ "result": ["bar", "qux"]
+ },
+ {
+ "expression": "foo.[bar,noexist]",
+ "result": ["bar", null]
+ },
+ {
+ "expression": "foo.[noexist,alsonoexist]",
+ "result": [null, null]
+ }
+ ]
+}, {
+ "given": {
+ "foo": {"bar": 1, "baz": [2, 3, 4]}
+ },
+ "cases": [
+ {
+ "expression": "foo.{bar:bar,baz:baz}",
+ "result": {"bar": 1, "baz": [2, 3, 4]}
+ },
+ {
+ "expression": "foo.[bar,baz[0]]",
+ "result": [1, 2]
+ },
+ {
+ "expression": "foo.[bar,baz[1]]",
+ "result": [1, 3]
+ },
+ {
+ "expression": "foo.[bar,baz[2]]",
+ "result": [1, 4]
+ },
+ {
+ "expression": "foo.[bar,baz[3]]",
+ "result": [1, null]
+ },
+ {
+ "expression": "foo.[bar[0],baz[3]]",
+ "result": [null, null]
+ }
+ ]
+}, {
+ "given": {
+ "foo": {"bar": 1, "baz": 2}
+ },
+ "cases": [
+ {
+ "expression": "foo.{bar: bar, baz: baz}",
+ "result": {"bar": 1, "baz": 2}
+ },
+ {
+ "expression": "foo.[bar,baz]",
+ "result": [1, 2]
+ }
+ ]
+}, {
+ "given": {
+ "foo": {
+ "bar": {"baz": [{"common": "first", "one": 1},
+ {"common": "second", "two": 2}]},
+ "ignoreme": 1,
+ "includeme": true
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.{bar: bar.baz[1],includeme: includeme}",
+ "result": {"bar": {"common": "second", "two": 2}, "includeme": true}
+ },
+ {
+ "expression": "foo.{\"bar.baz.two\": bar.baz[1].two, includeme: includeme}",
+ "result": {"bar.baz.two": 2, "includeme": true}
+ },
+ {
+ "expression": "foo.[includeme, bar.baz[*].common]",
+ "result": [true, ["first", "second"]]
+ },
+ {
+ "expression": "foo.[includeme, bar.baz[*].none]",
+ "result": [true, []]
+ },
+ {
+ "expression": "foo.[includeme, bar.baz[].common]",
+ "result": [true, ["first", "second"]]
+ }
+ ]
+}, {
+ "given": {
+ "reservations": [{
+ "instances": [
+ {"id": "id1",
+ "name": "first"},
+ {"id": "id2",
+ "name": "second"}
+ ]}, {
+ "instances": [
+ {"id": "id3",
+ "name": "third"},
+ {"id": "id4",
+ "name": "fourth"}
+ ]}
+ ]},
+ "cases": [
+ {
+ "expression": "reservations[*].instances[*].{id: id, name: name}",
+ "result": [[{"id": "id1", "name": "first"}, {"id": "id2", "name": "second"}],
+ [{"id": "id3", "name": "third"}, {"id": "id4", "name": "fourth"}]]
+ },
+ {
+ "expression": "reservations[].instances[].{id: id, name: name}",
+ "result": [{"id": "id1", "name": "first"},
+ {"id": "id2", "name": "second"},
+ {"id": "id3", "name": "third"},
+ {"id": "id4", "name": "fourth"}]
+ },
+ {
+ "expression": "reservations[].instances[].[id, name]",
+ "result": [["id1", "first"],
+ ["id2", "second"],
+ ["id3", "third"],
+ ["id4", "fourth"]]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [{
+ "bar": [
+ {
+ "qux": 2,
+ "baz": 1
+ },
+ {
+ "qux": 4,
+ "baz": 3
+ }
+ ]
+ },
+ {
+ "bar": [
+ {
+ "qux": 6,
+ "baz": 5
+ },
+ {
+ "qux": 8,
+ "baz": 7
+ }
+ ]
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo",
+ "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
+ {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
+ },
+ {
+ "expression": "foo[]",
+ "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]},
+ {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}]
+ },
+ {
+ "expression": "foo[].bar",
+ "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}],
+ [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]]
+ },
+ {
+ "expression": "foo[].bar[]",
+ "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3},
+ {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]
+ },
+ {
+ "expression": "foo[].bar[].[baz, qux]",
+ "result": [[1, 2], [3, 4], [5, 6], [7, 8]]
+ },
+ {
+ "expression": "foo[].bar[].[baz]",
+ "result": [[1], [3], [5], [7]]
+ },
+ {
+ "expression": "foo[].bar[].[baz, qux][]",
+ "result": [1, 2, 3, 4, 5, 6, 7, 8]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": {
+ "baz": [
+ {
+ "bar": "abc"
+ }, {
+ "bar": "def"
+ }
+ ],
+ "qux": ["zero"]
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.[baz[*].bar, qux[0]]",
+ "result": [["abc", "def"], "zero"]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": {
+ "baz": [
+ {
+ "bar": "a",
+ "bam": "b",
+ "boo": "c"
+ }, {
+ "bar": "d",
+ "bam": "e",
+ "boo": "f"
+ }
+ ],
+ "qux": ["zero"]
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.[baz[*].[bar, boo], qux[0]]",
+ "result": [[["a", "c" ], ["d", "f" ]], "zero"]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": {
+ "baz": [
+ {
+ "bar": "a",
+ "bam": "b",
+ "boo": "c"
+ }, {
+ "bar": "d",
+ "bam": "e",
+ "boo": "f"
+ }
+ ],
+ "qux": ["zero"]
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.[baz[*].not_there || baz[*].bar, qux[0]]",
+ "result": [["a", "d"], "zero"]
+ }
+ ]
+},
+{
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "comment": "Nested multiselect",
+ "expression": "[[*],*]",
+ "result": [null, ["object"]]
+ }
+ ]
+},
+{
+ "given": [],
+ "cases": [
+ {
+ "comment": "Nested multiselect",
+ "expression": "[[*]]",
+ "result": [[]]
+ },
+ {
+ "comment": "Select on null",
+ "expression": "missing.{foo: bar}",
+ "result": null
+ }
+ ]
+}
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/pipe.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/pipe.json
new file mode 100644
index 00000000..b10c0a49
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/pipe.json
@@ -0,0 +1,131 @@
+[{
+ "given": {
+ "foo": {
+ "bar": {
+ "baz": "subkey"
+ },
+ "other": {
+ "baz": "subkey"
+ },
+ "other2": {
+ "baz": "subkey"
+ },
+ "other3": {
+ "notbaz": ["a", "b", "c"]
+ },
+ "other4": {
+ "notbaz": ["a", "b", "c"]
+ }
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.*.baz | [0]",
+ "result": "subkey"
+ },
+ {
+ "expression": "foo.*.baz | [1]",
+ "result": "subkey"
+ },
+ {
+ "expression": "foo.*.baz | [2]",
+ "result": "subkey"
+ },
+ {
+ "expression": "foo.bar.* | [0]",
+ "result": "subkey"
+ },
+ {
+ "expression": "foo.*.notbaz | [*]",
+ "result": [["a", "b", "c"], ["a", "b", "c"]]
+ },
+ {
+ "expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz",
+ "result": ["subkey", "subkey"]
+ }
+ ]
+}, {
+ "given": {
+ "foo": {
+ "bar": {
+ "baz": "one"
+ },
+ "other": {
+ "baz": "two"
+ },
+ "other2": {
+ "baz": "three"
+ },
+ "other3": {
+ "notbaz": ["a", "b", "c"]
+ },
+ "other4": {
+ "notbaz": ["d", "e", "f"]
+ }
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo | bar",
+ "result": {"baz": "one"}
+ },
+ {
+ "expression": "foo | bar | baz",
+ "result": "one"
+ },
+ {
+ "expression": "foo|bar| baz",
+ "result": "one"
+ },
+ {
+ "expression": "not_there | [0]",
+ "result": null
+ },
+ {
+ "expression": "not_there | [0]",
+ "result": null
+ },
+ {
+ "expression": "[foo.bar, foo.other] | [0]",
+ "result": {"baz": "one"}
+ },
+ {
+ "expression": "{\"a\": foo.bar, \"b\": foo.other} | a",
+ "result": {"baz": "one"}
+ },
+ {
+ "expression": "{\"a\": foo.bar, \"b\": foo.other} | b",
+ "result": {"baz": "two"}
+ },
+ {
+ "expression": "foo.bam || foo.bar | baz",
+ "result": "one"
+ },
+ {
+ "expression": "foo | not_there || bar",
+ "result": {"baz": "one"}
+ }
+ ]
+}, {
+ "given": {
+ "foo": [{
+ "bar": [{
+ "baz": "one"
+ }, {
+ "baz": "two"
+ }]
+ }, {
+ "bar": [{
+ "baz": "three"
+ }, {
+ "baz": "four"
+ }]
+ }]
+ },
+ "cases": [
+ {
+ "expression": "foo[*].bar[*] | [0][0]",
+ "result": {"baz": "one"}
+ }
+ ]
+}]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/slice.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/slice.json
new file mode 100644
index 00000000..35947727
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/slice.json
@@ -0,0 +1,187 @@
+[{
+ "given": {
+ "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ "bar": {
+ "baz": 1
+ }
+ },
+ "cases": [
+ {
+ "expression": "bar[0:10]",
+ "result": null
+ },
+ {
+ "expression": "foo[0:10:1]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[0:10]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[0:10:]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[0::1]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[0::]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[0:]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[:10:1]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[::1]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[:10:]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[::]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[:]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[1:9]",
+ "result": [1, 2, 3, 4, 5, 6, 7, 8]
+ },
+ {
+ "expression": "foo[0:10:2]",
+ "result": [0, 2, 4, 6, 8]
+ },
+ {
+ "expression": "foo[5:]",
+ "result": [5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[5::2]",
+ "result": [5, 7, 9]
+ },
+ {
+ "expression": "foo[::2]",
+ "result": [0, 2, 4, 6, 8]
+ },
+ {
+ "expression": "foo[::-1]",
+ "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
+ },
+ {
+ "expression": "foo[1::2]",
+ "result": [1, 3, 5, 7, 9]
+ },
+ {
+ "expression": "foo[10:0:-1]",
+ "result": [9, 8, 7, 6, 5, 4, 3, 2, 1]
+ },
+ {
+ "expression": "foo[10:5:-1]",
+ "result": [9, 8, 7, 6]
+ },
+ {
+ "expression": "foo[8:2:-2]",
+ "result": [8, 6, 4]
+ },
+ {
+ "expression": "foo[0:20]",
+ "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ },
+ {
+ "expression": "foo[10:-20:-1]",
+ "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
+ },
+ {
+ "expression": "foo[10:-20]",
+ "result": []
+ },
+ {
+ "expression": "foo[-4:-1]",
+ "result": [6, 7, 8]
+ },
+ {
+ "expression": "foo[:-5:-1]",
+ "result": [9, 8, 7, 6]
+ },
+ {
+ "expression": "foo[8:2:0]",
+ "error": "invalid-value"
+ },
+ {
+ "expression": "foo[8:2:0:1]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[8:2&]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[2:a:3]",
+ "error": "syntax"
+ }
+ ]
+}, {
+ "given": {
+ "foo": [{"a": 1}, {"a": 2}, {"a": 3}],
+ "bar": [{"a": {"b": 1}}, {"a": {"b": 2}},
+ {"a": {"b": 3}}],
+ "baz": 50
+ },
+ "cases": [
+ {
+ "expression": "foo[:2].a",
+ "result": [1, 2]
+ },
+ {
+ "expression": "foo[:2].b",
+ "result": []
+ },
+ {
+ "expression": "foo[:2].a.b",
+ "result": []
+ },
+ {
+ "expression": "bar[::-1].a.b",
+ "result": [3, 2, 1]
+ },
+ {
+ "expression": "bar[:2].a.b",
+ "result": [1, 2]
+ },
+ {
+ "expression": "baz[:2].a",
+ "result": null
+ }
+ ]
+}, {
+ "given": [{"a": 1}, {"a": 2}, {"a": 3}],
+ "cases": [
+ {
+ "expression": "[:]",
+ "result": [{"a": 1}, {"a": 2}, {"a": 3}]
+ },
+ {
+ "expression": "[:2].a",
+ "result": [1, 2]
+ },
+ {
+ "expression": "[::-1].a",
+ "result": [3, 2, 1]
+ },
+ {
+ "expression": "[:2].b",
+ "result": []
+ }
+ ]
+}]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/syntax.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/syntax.json
new file mode 100644
index 00000000..9318901f
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/syntax.json
@@ -0,0 +1,678 @@
+[{
+ "comment": "Dot syntax",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "foo.bar",
+ "result": null
+ },
+ {
+ "expression": "foo.1",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.-11",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.",
+ "error": "syntax"
+ },
+ {
+ "expression": ".foo",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo..bar",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.bar.",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[.]",
+ "error": "syntax"
+ }
+ ]
+},
+ {
+ "comment": "Simple token errors",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": ".",
+ "error": "syntax"
+ },
+ {
+ "expression": ":",
+ "error": "syntax"
+ },
+ {
+ "expression": ",",
+ "error": "syntax"
+ },
+ {
+ "expression": "]",
+ "error": "syntax"
+ },
+ {
+ "expression": "[",
+ "error": "syntax"
+ },
+ {
+ "expression": "}",
+ "error": "syntax"
+ },
+ {
+ "expression": "{",
+ "error": "syntax"
+ },
+ {
+ "expression": ")",
+ "error": "syntax"
+ },
+ {
+ "expression": "(",
+ "error": "syntax"
+ },
+ {
+ "expression": "((&",
+ "error": "syntax"
+ },
+ {
+ "expression": "a[",
+ "error": "syntax"
+ },
+ {
+ "expression": "a]",
+ "error": "syntax"
+ },
+ {
+ "expression": "a][",
+ "error": "syntax"
+ },
+ {
+ "expression": "!",
+ "error": "syntax"
+ },
+ {
+ "expression": "@=",
+ "error": "syntax"
+ },
+ {
+ "expression": "@``",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Boolean syntax errors",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "![!(!",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Paren syntax errors",
+ "given": {},
+ "cases": [
+ {
+ "comment": "missing closing paren",
+ "expression": "(@",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Function syntax errors",
+ "given": {},
+ "cases": [
+ {
+ "comment": "invalid start of function",
+ "expression": "@(foo)",
+ "error": "syntax"
+ },
+ {
+ "comment": "function names cannot be quoted",
+ "expression": "\"foo\"(bar)",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Wildcard syntax",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "*",
+ "result": ["object"]
+ },
+ {
+ "expression": "*.*",
+ "result": []
+ },
+ {
+ "expression": "*.foo",
+ "result": []
+ },
+ {
+ "expression": "*[0]",
+ "result": []
+ },
+ {
+ "expression": ".*",
+ "error": "syntax"
+ },
+ {
+ "expression": "*foo",
+ "error": "syntax"
+ },
+ {
+ "expression": "*0",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[*]bar",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[*]*",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Flatten syntax",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "[]",
+ "result": null
+ }
+ ]
+ },
+ {
+ "comment": "Simple bracket syntax",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "[0]",
+ "result": null
+ },
+ {
+ "expression": "[*]",
+ "result": null
+ },
+ {
+ "expression": "*.[0]",
+ "error": "syntax"
+ },
+ {
+ "expression": "*.[\"0\"]",
+ "result": [[null]]
+ },
+ {
+ "expression": "[*].bar",
+ "result": null
+ },
+ {
+ "expression": "[*][0]",
+ "result": null
+ },
+ {
+ "expression": "foo[#]",
+ "error": "syntax"
+ },
+ {
+ "comment": "missing rbracket for led wildcard index",
+ "expression": "led[*",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "slice syntax",
+ "given": {},
+ "cases": [
+ {
+ "comment": "slice expected colon or rbracket",
+ "expression": "[:@]",
+ "error": "syntax"
+ },
+ {
+ "comment": "slice has too many colons",
+ "expression": "[:::]",
+ "error": "syntax"
+ },
+ {
+ "comment": "slice expected number",
+ "expression": "[:@:]",
+ "error": "syntax"
+ },
+ {
+ "comment": "slice expected number of colon",
+ "expression": "[:1@]",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Multi-select list syntax",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "foo[0]",
+ "result": null
+ },
+ {
+ "comment": "Valid multi-select of a list",
+ "expression": "foo[0, 1]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.[0]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.[*]",
+ "result": null
+ },
+ {
+ "comment": "Multi-select of a list with trailing comma",
+ "expression": "foo[0, ]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list with trailing comma and no close",
+ "expression": "foo[0,",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list with trailing comma and no close",
+ "expression": "foo.[a",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list with extra comma",
+ "expression": "foo[0,, 1]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list using an identifier index",
+ "expression": "foo[abc]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list using identifier indices",
+ "expression": "foo[abc, def]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list using an identifier index",
+ "expression": "foo[abc, 1]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a list using an identifier index with trailing comma",
+ "expression": "foo[abc, ]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Valid multi-select of a hash using an identifier index",
+ "expression": "foo.[abc]",
+ "result": null
+ },
+ {
+ "comment": "Valid multi-select of a hash",
+ "expression": "foo.[abc, def]",
+ "result": null
+ },
+ {
+ "comment": "Multi-select of a hash using a numeric index",
+ "expression": "foo.[abc, 1]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a hash with a trailing comma",
+ "expression": "foo.[abc, ]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a hash with extra commas",
+ "expression": "foo.[abc,, def]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Multi-select of a hash using number indices",
+ "expression": "foo.[0, 1]",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Multi-select hash syntax",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "comment": "No key or value",
+ "expression": "a{}",
+ "error": "syntax"
+ },
+ {
+ "comment": "No closing token",
+ "expression": "a{",
+ "error": "syntax"
+ },
+ {
+ "comment": "Not a key value pair",
+ "expression": "a{foo}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing value and closing character",
+ "expression": "a{foo:",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing closing character",
+ "expression": "a{foo: 0",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing value",
+ "expression": "a{foo:}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Trailing comma and no closing character",
+ "expression": "a{foo: 0, ",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing value with trailing comma",
+ "expression": "a{foo: ,}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Accessing Array using an identifier",
+ "expression": "a{foo: bar}",
+ "error": "syntax"
+ },
+ {
+ "expression": "a{foo: 0}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing key-value pair",
+ "expression": "a.{}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Not a key-value pair",
+ "expression": "a.{foo}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Valid multi-select hash extraction",
+ "expression": "a.{foo: bar}",
+ "result": null
+ },
+ {
+ "comment": "Valid multi-select hash extraction",
+ "expression": "a.{foo: bar, baz: bam}",
+ "result": null
+ },
+ {
+ "comment": "Trailing comma",
+ "expression": "a.{foo: bar, }",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing key in second key-value pair",
+ "expression": "a.{foo: bar, baz}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Missing value in second key-value pair",
+ "expression": "a.{foo: bar, baz:}",
+ "error": "syntax"
+ },
+ {
+ "comment": "Trailing comma",
+ "expression": "a.{foo: bar, baz: bam, }",
+ "error": "syntax"
+ },
+ {
+ "comment": "Nested multi select",
+ "expression": "{\"\\\\\":{\" \":*}}",
+ "result": {"\\": {" ": ["object"]}}
+ },
+ {
+ "comment": "Missing closing } after a valid nud",
+ "expression": "{a: @",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Or expressions",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "foo || bar",
+ "result": null
+ },
+ {
+ "expression": "foo ||",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.|| bar",
+ "error": "syntax"
+ },
+ {
+ "expression": " || foo",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo || || foo",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo.[a || b]",
+ "result": null
+ },
+ {
+ "expression": "foo.[a ||]",
+ "error": "syntax"
+ },
+ {
+ "expression": "\"foo",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Filter expressions",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "foo[?bar==`\"baz\"`]",
+ "result": null
+ },
+ {
+ "expression": "foo[? bar == `\"baz\"` ]",
+ "result": null
+ },
+ {
+ "expression": "foo[ ?bar==`\"baz\"`]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[?bar==]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[?==]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[?==bar]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[?bar==baz?]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[?a.b.c==d.e.f]",
+ "result": null
+ },
+ {
+ "expression": "foo[?bar==`[0, 1, 2]`]",
+ "result": null
+ },
+ {
+ "expression": "foo[?bar==`[\"a\", \"b\", \"c\"]`]",
+ "result": null
+ },
+ {
+ "comment": "Literal char not escaped",
+ "expression": "foo[?bar==`[\"foo`bar\"]`]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Literal char escaped",
+ "expression": "foo[?bar==`[\"foo\\`bar\"]`]",
+ "result": null
+ },
+ {
+ "comment": "Unknown comparator",
+ "expression": "foo[?bar<>baz]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Unknown comparator",
+ "expression": "foo[?bar^baz]",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[bar==baz]",
+ "error": "syntax"
+ },
+ {
+ "comment": "Quoted identifier in filter expression no spaces",
+ "expression": "[?\"\\\\\">`\"foo\"`]",
+ "result": null
+ },
+ {
+ "comment": "Quoted identifier in filter expression with spaces",
+ "expression": "[?\"\\\\\" > `\"foo\"`]",
+ "result": null
+ }
+ ]
+ },
+ {
+ "comment": "Filter expression errors",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "bar.`\"anything\"`",
+ "error": "syntax"
+ },
+ {
+ "expression": "bar.baz.noexists.`\"literal\"`",
+ "error": "syntax"
+ },
+ {
+ "comment": "Literal wildcard projection",
+ "expression": "foo[*].`\"literal\"`",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[*].name.`\"literal\"`",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[].name.`\"literal\"`",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[].name.`\"literal\"`.`\"subliteral\"`",
+ "error": "syntax"
+ },
+ {
+ "comment": "Projecting a literal onto an empty list",
+ "expression": "foo[*].name.noexist.`\"literal\"`",
+ "error": "syntax"
+ },
+ {
+ "expression": "foo[].name.noexist.`\"literal\"`",
+ "error": "syntax"
+ },
+ {
+ "expression": "twolen[*].`\"foo\"`",
+ "error": "syntax"
+ },
+ {
+ "comment": "Two level projection of a literal",
+ "expression": "twolen[*].threelen[*].`\"bar\"`",
+ "error": "syntax"
+ },
+ {
+ "comment": "Two level flattened projection of a literal",
+ "expression": "twolen[].threelen[].`\"bar\"`",
+ "error": "syntax"
+ },
+ {
+ "comment": "expects closing ]",
+ "expression": "foo[? @ | @",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Identifiers",
+ "given": {"type": "object"},
+ "cases": [
+ {
+ "expression": "foo",
+ "result": null
+ },
+ {
+ "expression": "\"foo\"",
+ "result": null
+ },
+ {
+ "expression": "\"\\\\\"",
+ "result": null
+ },
+ {
+ "expression": "\"\\u\"",
+ "error": "syntax"
+ }
+ ]
+ },
+ {
+ "comment": "Combined syntax",
+ "given": [],
+ "cases": [
+ {
+ "expression": "*||*|*|*",
+ "result": null
+ },
+ {
+ "expression": "*[]||[*]",
+ "result": []
+ },
+ {
+ "expression": "[*.*]",
+ "result": [null]
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/test.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/test.json
new file mode 100644
index 00000000..aa8953c8
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/test.json
@@ -0,0 +1,19 @@
+[
+ {"given":
+ {
+ "people": [
+ {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"},
+ {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"},
+ {"age": 30, "age_str": "30", "bool": true, "name": "c"},
+ {"age": 50, "age_str": "50", "bool": false, "name": "d"},
+ {"age": 10, "age_str": "10", "bool": true, "name": 3}
+ ]
+ },
+ "cases": [
+ {
+ "expression": "max_by(people, &age)",
+ "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"}
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/unicode.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/unicode.json
new file mode 100644
index 00000000..6b07b0b6
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/unicode.json
@@ -0,0 +1,38 @@
+[
+ {
+ "given": {"foo": [{"✓": "✓"}, {"✓": "✗"}]},
+ "cases": [
+ {
+ "expression": "foo[].\"✓\"",
+ "result": ["✓", "✗"]
+ }
+ ]
+ },
+ {
+ "given": {"☯": true},
+ "cases": [
+ {
+ "expression": "\"☯\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪": true},
+ "cases": [
+ {
+ "expression": "\"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪\"",
+ "result": true
+ }
+ ]
+ },
+ {
+ "given": {"☃": true},
+ "cases": [
+ {
+ "expression": "\"☃\"",
+ "result": true
+ }
+ ]
+ }
+]
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/wildcard.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/wildcard.json
new file mode 100644
index 00000000..3bcec302
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/wildcard.json
@@ -0,0 +1,460 @@
+[{
+ "given": {
+ "foo": {
+ "bar": {
+ "baz": "val"
+ },
+ "other": {
+ "baz": "val"
+ },
+ "other2": {
+ "baz": "val"
+ },
+ "other3": {
+ "notbaz": ["a", "b", "c"]
+ },
+ "other4": {
+ "notbaz": ["a", "b", "c"]
+ },
+ "other5": {
+ "other": {
+ "a": 1,
+ "b": 1,
+ "c": 1
+ }
+ }
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.*.baz",
+ "result": ["val", "val", "val"]
+ },
+ {
+ "expression": "foo.bar.*",
+ "result": ["val"]
+ },
+ {
+ "expression": "foo.*.notbaz",
+ "result": [["a", "b", "c"], ["a", "b", "c"]]
+ },
+ {
+ "expression": "foo.*.notbaz[0]",
+ "result": ["a", "a"]
+ },
+ {
+ "expression": "foo.*.notbaz[-1]",
+ "result": ["c", "c"]
+ }
+ ]
+}, {
+ "given": {
+ "foo": {
+ "first-1": {
+ "second-1": "val"
+ },
+ "first-2": {
+ "second-1": "val"
+ },
+ "first-3": {
+ "second-1": "val"
+ }
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.*",
+ "result": [{"second-1": "val"}, {"second-1": "val"},
+ {"second-1": "val"}]
+ },
+ {
+ "expression": "foo.*.*",
+ "result": [["val"], ["val"], ["val"]]
+ },
+ {
+ "expression": "foo.*.*.*",
+ "result": [[], [], []]
+ },
+ {
+ "expression": "foo.*.*.*.*",
+ "result": [[], [], []]
+ }
+ ]
+}, {
+ "given": {
+ "foo": {
+ "bar": "one"
+ },
+ "other": {
+ "bar": "one"
+ },
+ "nomatch": {
+ "notbar": "three"
+ }
+ },
+ "cases": [
+ {
+ "expression": "*.bar",
+ "result": ["one", "one"]
+ }
+ ]
+}, {
+ "given": {
+ "top1": {
+ "sub1": {"foo": "one"}
+ },
+ "top2": {
+ "sub1": {"foo": "one"}
+ }
+ },
+ "cases": [
+ {
+ "expression": "*",
+ "result": [{"sub1": {"foo": "one"}},
+ {"sub1": {"foo": "one"}}]
+ },
+ {
+ "expression": "*.sub1",
+ "result": [{"foo": "one"},
+ {"foo": "one"}]
+ },
+ {
+ "expression": "*.*",
+ "result": [[{"foo": "one"}],
+ [{"foo": "one"}]]
+ },
+ {
+ "expression": "*.*.foo[]",
+ "result": ["one", "one"]
+ },
+ {
+ "expression": "*.sub1.foo",
+ "result": ["one", "one"]
+ }
+ ]
+},
+{
+ "given":
+ {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]},
+ "cases": [
+ {
+ "expression": "foo[*].bar",
+ "result": ["one", "two", "three"]
+ },
+ {
+ "expression": "foo[*].notbar",
+ "result": ["four"]
+ }
+ ]
+},
+{
+ "given":
+ [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}],
+ "cases": [
+ {
+ "expression": "[*]",
+ "result": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]
+ },
+ {
+ "expression": "[*].bar",
+ "result": ["one", "two", "three"]
+ },
+ {
+ "expression": "[*].notbar",
+ "result": ["four"]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": {
+ "bar": [
+ {"baz": ["one", "two", "three"]},
+ {"baz": ["four", "five", "six"]},
+ {"baz": ["seven", "eight", "nine"]}
+ ]
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.bar[*].baz",
+ "result": [["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]]
+ },
+ {
+ "expression": "foo.bar[*].baz[0]",
+ "result": ["one", "four", "seven"]
+ },
+ {
+ "expression": "foo.bar[*].baz[1]",
+ "result": ["two", "five", "eight"]
+ },
+ {
+ "expression": "foo.bar[*].baz[2]",
+ "result": ["three", "six", "nine"]
+ },
+ {
+ "expression": "foo.bar[*].baz[3]",
+ "result": []
+ }
+ ]
+},
+{
+ "given": {
+ "foo": {
+ "bar": [["one", "two"], ["three", "four"]]
+ }
+ },
+ "cases": [
+ {
+ "expression": "foo.bar[*]",
+ "result": [["one", "two"], ["three", "four"]]
+ },
+ {
+ "expression": "foo.bar[0]",
+ "result": ["one", "two"]
+ },
+ {
+ "expression": "foo.bar[0][0]",
+ "result": "one"
+ },
+ {
+ "expression": "foo.bar[0][0][0]",
+ "result": null
+ },
+ {
+ "expression": "foo.bar[0][0][0][0]",
+ "result": null
+ },
+ {
+ "expression": "foo[0][0]",
+ "result": null
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [
+ {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]},
+ {"bar": [{"kind": "advanced"}, {"kind": "expert"}]},
+ {"bar": "string"}
+ ]
+
+ },
+ "cases": [
+ {
+ "expression": "foo[*].bar[*].kind",
+ "result": [["basic", "intermediate"], ["advanced", "expert"]]
+ },
+ {
+ "expression": "foo[*].bar[0].kind",
+ "result": ["basic", "advanced"]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [
+ {"bar": {"kind": "basic"}},
+ {"bar": {"kind": "intermediate"}},
+ {"bar": {"kind": "advanced"}},
+ {"bar": {"kind": "expert"}},
+ {"bar": "string"}
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo[*].bar.kind",
+ "result": ["basic", "intermediate", "advanced", "expert"]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [{"bar": ["one", "two"]}, {"bar": ["three", "four"]}, {"bar": ["five"]}]
+ },
+ "cases": [
+ {
+ "expression": "foo[*].bar[0]",
+ "result": ["one", "three", "five"]
+ },
+ {
+ "expression": "foo[*].bar[1]",
+ "result": ["two", "four"]
+ },
+ {
+ "expression": "foo[*].bar[2]",
+ "result": []
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [{"bar": []}, {"bar": []}, {"bar": []}]
+ },
+ "cases": [
+ {
+ "expression": "foo[*].bar[0]",
+ "result": []
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [["one", "two"], ["three", "four"], ["five"]]
+ },
+ "cases": [
+ {
+ "expression": "foo[*][0]",
+ "result": ["one", "three", "five"]
+ },
+ {
+ "expression": "foo[*][1]",
+ "result": ["two", "four"]
+ }
+ ]
+},
+{
+ "given": {
+ "foo": [
+ [
+ ["one", "two"], ["three", "four"]
+ ], [
+ ["five", "six"], ["seven", "eight"]
+ ], [
+ ["nine"], ["ten"]
+ ]
+ ]
+ },
+ "cases": [
+ {
+ "expression": "foo[*][0]",
+ "result": [["one", "two"], ["five", "six"], ["nine"]]
+ },
+ {
+ "expression": "foo[*][1]",
+ "result": [["three", "four"], ["seven", "eight"], ["ten"]]
+ },
+ {
+ "expression": "foo[*][0][0]",
+ "result": ["one", "five", "nine"]
+ },
+ {
+ "expression": "foo[*][1][0]",
+ "result": ["three", "seven", "ten"]
+ },
+ {
+ "expression": "foo[*][0][1]",
+ "result": ["two", "six"]
+ },
+ {
+ "expression": "foo[*][1][1]",
+ "result": ["four", "eight"]
+ },
+ {
+ "expression": "foo[*][2]",
+ "result": []
+ },
+ {
+ "expression": "foo[*][2][2]",
+ "result": []
+ },
+ {
+ "expression": "bar[*]",
+ "result": null
+ },
+ {
+ "expression": "bar[*].baz[*]",
+ "result": null
+ }
+ ]
+},
+{
+ "given": {
+ "string": "string",
+ "hash": {"foo": "bar", "bar": "baz"},
+ "number": 23,
+ "nullvalue": null
+ },
+ "cases": [
+ {
+ "expression": "string[*]",
+ "result": null
+ },
+ {
+ "expression": "hash[*]",
+ "result": null
+ },
+ {
+ "expression": "number[*]",
+ "result": null
+ },
+ {
+ "expression": "nullvalue[*]",
+ "result": null
+ },
+ {
+ "expression": "string[*].foo",
+ "result": null
+ },
+ {
+ "expression": "hash[*].foo",
+ "result": null
+ },
+ {
+ "expression": "number[*].foo",
+ "result": null
+ },
+ {
+ "expression": "nullvalue[*].foo",
+ "result": null
+ },
+ {
+ "expression": "nullvalue[*].foo[*].bar",
+ "result": null
+ }
+ ]
+},
+{
+ "given": {
+ "string": "string",
+ "hash": {"foo": "val", "bar": "val"},
+ "number": 23,
+ "array": [1, 2, 3],
+ "nullvalue": null
+ },
+ "cases": [
+ {
+ "expression": "string.*",
+ "result": null
+ },
+ {
+ "expression": "hash.*",
+ "result": ["val", "val"]
+ },
+ {
+ "expression": "number.*",
+ "result": null
+ },
+ {
+ "expression": "array.*",
+ "result": null
+ },
+ {
+ "expression": "nullvalue.*",
+ "result": null
+ }
+ ]
+},
+{
+ "given": {
+ "a": [0, 1, 2],
+ "b": [0, 1, 2]
+ },
+ "cases": [
+ {
+ "expression": "*[0]",
+ "result": [0, 0]
+ }
+ ]
+}
+]
From 93ede40544891f57c9bd6086ab8982e048d91419 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Wed, 17 Apr 2024 16:31:53 +0100
Subject: [PATCH 02/28] cleanup, refactoring and tests
---
.../BinaryOperator.cs | 35 ++-
.../Expression.cs | 38 ++-
.../Function.cs | 49 +++-
.../InternalsVisibleTo.cs | 3 +-
.../JmesPathParser.cs | 63 +++--
.../JsonTransformer.cs | 21 +-
.../Operator.cs | 21 +-
.../AWS.Lambda.Powertools.JMESPath/README.md | 44 ++-
.../AWS.Lambda.Powertools.JMESPath/Slice.cs | 48 ++--
.../AWS.Lambda.Powertools.JMESPath/Token.cs | 22 +-
.../UnaryOperator.cs | 32 ++-
.../Utilities/JsonDocumentBuilder.cs | 55 ++--
.../Utilities/JsonElementComparer.cs | 40 ++-
.../Utilities/JsonElementEqualityComparer.cs | 42 ++-
.../Utilities/JsonFlattener.cs | 80 +++---
.../Utilities/JsonMergePatch.cs | 27 +-
.../Utilities/JsonPatch.cs | 266 ++++++++++--------
.../Utilities/JsonPointer.cs | 47 ++--
.../Utilities/JsonPointerExtensions.cs | 135 +++++----
.../AWS.Lambda.Powertools.JMESPath/Value.cs | 27 +-
.../ValueComparer.cs | 40 ++-
.../ValueEqualityComparer.cs | 50 +++-
.../GlobalUsings.cs | 15 +
.../JmesPathTests.cs | 15 +
24 files changed, 782 insertions(+), 433 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
index f2c911b0..86c04554 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
@@ -1,4 +1,17 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
namespace AWS.Lambda.Powertools.JMESPath
{
@@ -7,7 +20,7 @@ internal interface IBinaryOperator
int PrecedenceLevel {get;}
bool IsRightAssociative {get;}
bool TryEvaluate(IValue lhs, IValue rhs, out IValue result);
- };
+ }
internal abstract class BinaryOperator : IBinaryOperator
{
@@ -21,7 +34,7 @@ internal BinaryOperator(Operator oper)
public bool IsRightAssociative => false;
public abstract bool TryEvaluate(IValue lhs, IValue rhs, out IValue result);
- };
+ }
internal sealed class OrOperator : BinaryOperator
{
@@ -47,7 +60,7 @@ public override string ToString()
{
return "OrOperator";
}
- };
+ }
internal sealed class AndOperator : BinaryOperator
{
@@ -68,7 +81,7 @@ public override string ToString()
{
return "AndOperator";
}
- };
+ }
internal sealed class EqOperator : BinaryOperator
{
@@ -90,7 +103,7 @@ public override string ToString()
{
return "EqOperator";
}
- };
+ }
internal sealed class NeOperator : BinaryOperator
{
@@ -117,7 +130,7 @@ public override string ToString()
{
return "NeOperator";
}
- };
+ }
internal sealed class LtOperator : BinaryOperator
{
@@ -160,7 +173,7 @@ public override string ToString()
{
return "LtOperator";
}
- };
+ }
internal sealed class LteOperator : BinaryOperator
{
@@ -204,7 +217,7 @@ public override string ToString()
{
return "LteOperator";
}
- };
+ }
internal sealed class GtOperator : BinaryOperator
{
@@ -247,7 +260,7 @@ public override string ToString()
{
return "GtOperator";
}
- };
+ }
internal sealed class GteOperator : BinaryOperator
{
@@ -290,6 +303,6 @@ public override string ToString()
{
return "GteOperator";
}
- };
+ }
}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
index 7d993671..83ad6c1c 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
@@ -1,4 +1,18 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -91,7 +105,7 @@ public override string ToString()
{
return $"IdentifierSelector {_identifier}";
}
- };
+ }
internal sealed class CurrentNode : BaseExpression
{
@@ -112,7 +126,7 @@ public override string ToString()
{
return "CurrentNode";
}
- };
+ }
internal sealed class IndexSelector : BaseExpression
{
@@ -153,7 +167,7 @@ public override string ToString()
{
return $"Index Selector {_index}";
}
- };
+ }
internal abstract class Projection : BaseExpression
{
@@ -190,7 +204,7 @@ internal bool TryApplyExpressions(DynamicResources resources, IValue current, ou
}
return true;
}
- };
+ }
internal sealed class ObjectProjection : Projection
{
@@ -230,7 +244,7 @@ public override string ToString()
{
return "ObjectProjection";
}
- };
+ }
internal sealed class ListProjection : Projection
{
@@ -273,7 +287,7 @@ public override string ToString()
{
return "ListProjection";
}
- };
+ }
internal sealed class FlattenProjection : Projection
{
@@ -338,7 +352,7 @@ public override string ToString()
{
return "FlattenProjection";
}
- };
+ }
internal sealed class SliceProjection : Projection
{
@@ -426,7 +440,7 @@ public override string ToString()
{
return "SliceProjection";
}
- };
+ }
internal sealed class FilterExpression : Projection
{
@@ -477,7 +491,7 @@ public override string ToString()
{
return "FilterExpression";
}
- };
+ }
internal sealed class MultiSelectList : BaseExpression
{
@@ -517,7 +531,7 @@ public override string ToString()
{
return "MultiSelectList";
}
- };
+ }
internal struct KeyExpressionPair
{
@@ -529,7 +543,7 @@ internal KeyExpressionPair(string key, Expression expression)
Key = key;
Expression = expression;
}
- };
+ }
internal sealed class MultiSelectHash : BaseExpression
{
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
index 2ee2d28b..4eddb7dc 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -70,7 +85,7 @@ internal interface IFunction
{
int? Arity { get; }
bool TryEvaluate(DynamicResources resources, IList args, out IValue element);
- };
+ }
internal abstract class BaseFunction : IFunction
{
@@ -82,7 +97,7 @@ internal BaseFunction(int? argCount)
public int? Arity { get; }
public abstract bool TryEvaluate(DynamicResources resources, IList args, out IValue element);
- };
+ }
internal sealed class AbsFunction : BaseFunction
{
@@ -117,7 +132,7 @@ public override string ToString()
{
return "abs";
}
- };
+ }
internal sealed class AvgFunction : BaseFunction
{
@@ -154,6 +169,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
result = new DoubleValue(dblVal / arg0.GetArrayLength());
return true;
}
+
result = JsonConstants.Null;
return false;
}
@@ -162,7 +178,7 @@ public override string ToString()
{
return "avg";
}
- };
+ }
internal sealed class CeilFunction : BaseFunction
{
@@ -194,6 +210,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
result = new DoubleValue(Math.Ceiling(dblVal));
return true;
}
+
result = JsonConstants.Null;
return false;
}
@@ -202,7 +219,7 @@ public override string ToString()
{
return "ceil";
}
- };
+ }
internal sealed class ContainsFunction : BaseFunction
{
@@ -266,7 +283,7 @@ public override string ToString()
{
return "contains";
}
- };
+ }
internal sealed class EndsWithFunction : BaseFunction
{
@@ -308,7 +325,7 @@ public override string ToString()
{
return "ends_with";
}
- };
+ }
internal sealed class FloorFunction : BaseFunction
{
@@ -340,6 +357,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
result = new DoubleValue(Math.Floor(dblVal));
return true;
}
+
result = JsonConstants.Null;
return false;
}
@@ -348,7 +366,7 @@ public override string ToString()
{
return "floor";
}
- };
+ }
internal sealed class JoinFunction : BaseFunction
{
@@ -483,7 +501,7 @@ public override string ToString()
{
return "length";
}
- };
+ }
internal sealed class MaxFunction : BaseFunction
{
@@ -1221,17 +1239,18 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
case JmesPathType.String:
{
var s = arg0.GetString();
- if (decimal.TryParse(s, out var dec))
+ if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dec))
{
result = new DecimalValue(dec);
return true;
}
- if (double.TryParse(s, out var dbl))
+ if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dbl))
{
result = new DoubleValue(dbl);
return true;
}
+
result = JsonConstants.Null;
return false;
}
@@ -1383,7 +1402,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
result = args[0];
-
+
//result = new JsonElementValue(JsonNode.Parse(args[0].GetString()).Deserialize());
return true;
}
@@ -1428,7 +1447,7 @@ public override string ToString()
public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
-
+
var compressedBytes = Convert.FromBase64String(args[0].GetString());
using var compressedStream = new MemoryStream(compressedBytes);
@@ -1488,5 +1507,5 @@ internal bool TryGetFunction(string name, out IFunction func)
{
return _functions.TryGetValue(name, out func);
}
- };
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
index 652795a2..62746e87 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
@@ -15,4 +15,5 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")]
\ No newline at end of file
+[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")]
+[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.JMESPath.Tests")]
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
index d7e0efbc..e8bde3c6 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
@@ -15,12 +30,12 @@ public sealed class JmesPathParseException : Exception
///
/// The line in the JMESPath string where a parse error was detected.
///
- public int LineNumber {get;}
+ private int LineNumber {get;}
///
/// The column in the JMESPath string where a parse error was detected.
///
- public int ColumnNumber {get;}
+ private int ColumnNumber {get;}
internal JmesPathParseException(string message, int line, int column)
: base(message)
@@ -37,7 +52,7 @@ public override string ToString ()
{
return $"{Message} at line {LineNumber} and column {ColumnNumber}";
}
- };
+ }
internal enum JmesPathState
{
@@ -334,7 +349,7 @@ internal JsonTransformer Parse()
throw new JmesPathParseException("Expected identifier", _line, _column);
}
break;
- };
+ }
break;
}
@@ -386,7 +401,7 @@ internal JsonTransformer Parse()
throw new JmesPathParseException("Expected identifier", _line, _column);
}
break;
- };
+ }
break;
}
case JmesPathState.KeyExpr:
@@ -506,7 +521,7 @@ internal JsonTransformer Parse()
++_index;
++_column;
break;
- };
+ }
break;
case JmesPathState.UnquotedString:
@@ -528,7 +543,7 @@ internal JsonTransformer Parse()
_stateStack.Pop(); // unquotedString
}
break;
- };
+ }
break;
case JmesPathState.RawStringEscapeChar:
@@ -730,7 +745,7 @@ internal JsonTransformer Parse()
++_index;
++_column;
break;
- };
+ }
break;
case JmesPathState.Literal:
@@ -777,7 +792,7 @@ internal JsonTransformer Parse()
++_index;
++_column;
break;
- };
+ }
break;
case JmesPathState.Number:
@@ -1109,7 +1124,7 @@ internal JsonTransformer Parse()
throw new JmesPathParseException("Expected key", _line, _column);
}
break;
- };
+ }
break;
}
case JmesPathState.CmpLtOrLte:
@@ -1727,21 +1742,19 @@ private void PushToken(Token token)
private uint AppendToCodepoint(uint cp, uint c)
{
cp *= 16;
- if (c >= '0' && c <= '9')
+ switch (c)
{
- cp += c - '0';
- }
- else if (c >= 'a' && c <= 'f')
- {
- cp += c - 'a' + 10;
- }
- else if (c >= 'A' && c <= 'F')
- {
- cp += c - 'A' + 10;
- }
- else
- {
- throw new JmesPathParseException("Invalid codepoint", _line, _column);
+ case >= '0' and <= '9':
+ cp += c - '0';
+ break;
+ case >= 'a' and <= 'f':
+ cp += c - 'a' + 10;
+ break;
+ case >= 'A' and <= 'F':
+ cp += c - 'A' + 10;
+ break;
+ default:
+ throw new JmesPathParseException("Invalid codepoint", _line, _column);
}
return cp;
}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs
index 4fc29685..56aaa176 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Text.Json;
namespace AWS.Lambda.Powertools.JMESPath
@@ -93,7 +108,7 @@ public static JsonTransformer Parse(string jmesPath)
return compiler.Parse();
}
- private Expression _expr;
+ private readonly Expression _expr;
internal JsonTransformer(Expression expr)
{
@@ -115,7 +130,7 @@ public JsonDocument Transform(JsonElement doc)
{
var resources = new DynamicResources();
_expr.TryEvaluate(resources, new JsonElementValue(doc), out var temp);
- return JsonDocument.Parse(temp.ToString());
+ return JsonDocument.Parse(temp.ToString() ?? string.Empty);
}
///
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs
index c0816e53..bff60479 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs
@@ -1,4 +1,19 @@
-namespace AWS.Lambda.Powertools.JMESPath
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+namespace AWS.Lambda.Powertools.JMESPath
{
internal enum Operator
{
@@ -18,7 +33,7 @@ internal enum Operator
internal static class OperatorTable
{
- static internal int PrecedenceLevel(Operator oper)
+ internal static int PrecedenceLevel(Operator oper)
{
switch (oper)
{
@@ -45,7 +60,7 @@ static internal int PrecedenceLevel(Operator oper)
}
}
- static internal bool IsRightAssociative(Operator oper)
+ internal static bool IsRightAssociative(Operator oper)
{
switch (oper)
{
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
index bb9ca308..66d2c222 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
@@ -1 +1,43 @@
-JMESPath
\ No newline at end of file
+# Powertools JMESPath support
+
+JMESPath is a query language for JSON used by AWS CLI, AWS Python SDK, and Powertools for AWS Lambda.
+ With built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions.
+
+## Key features
+
+- Deserialize JSON from JSON strings, base64, and compressed data
+- Use JMESPath to extract and combine data recursively
+- Provides commonly used JMESPath expression with popular event sources
+
+JMESPath allows you to transform a JsonDocument into another JsonDocument.
+
+For example, consider the JSON data
+
+```csharp
+
+string jsonString = """
+{
+ "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+}
+""";
+
+using JsonDocument doc = JsonDocument.Parse(jsonString);
+
+string expr = "powertools_json(body).customerId";
+//also works for fetching and flattening deeply nested data
+// string expr = "deeply_nested[*].some_data[]";
+
+JsonDocument result = JsonTransformer.Transform(doc.RootElement, expr);
+
+
+
+```
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs
index 0d6fb21b..6d5dca86 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs
@@ -1,4 +1,17 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
namespace AWS.Lambda.Powertools.JMESPath
{
@@ -18,36 +31,17 @@ public Slice(int? start, int? stop, int step)
public int GetStart(int size)
{
- if (_start.HasValue)
- {
- var len = _start.Value >= 0 ? _start.Value : size + _start.Value;
- return len <= size ? len : size;
- }
- else
- {
- if (Step >= 0)
- {
- return 0;
- }
- else
- {
- return size;
- }
- }
+ if (!_start.HasValue) return Step >= 0 ? 0 : size;
+ var len = _start.Value >= 0 ? _start.Value : size + _start.Value;
+ return len <= size ? len : size;
}
public int GetStop(int size)
{
- if (_stop.HasValue)
- {
- var len = _stop.Value >= 0 ? _stop.Value : size + _stop.Value;
- return len <= size ? len : size;
- }
- else
- {
- return Step >= 0 ? size : -1;
- }
+ if (!_stop.HasValue) return Step >= 0 ? size : -1;
+ var len = _stop.Value >= 0 ? _stop.Value : size + _stop.Value;
+ return len <= size ? len : size;
}
- };
+ }
}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs
index ab5d1b76..553f9140 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Diagnostics;
namespace AWS.Lambda.Powertools.JMESPath
@@ -181,10 +196,7 @@ internal IExpression GetExpression()
}
public bool Equals(Token other)
{
- if (Type == other.Type)
- return true;
- else
- return false;
+ return Type == other.Type;
}
public override string ToString()
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs
index c39df7eb..c28c2246 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs
@@ -1,4 +1,19 @@
-using System.Text.RegularExpressions;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Text.RegularExpressions;
namespace AWS.Lambda.Powertools.JMESPath
{
@@ -7,7 +22,7 @@ internal interface IUnaryOperator
int PrecedenceLevel {get;}
bool IsRightAssociative {get;}
bool TryEvaluate(IValue elem, out IValue result);
- };
+ }
internal abstract class UnaryOperator : IUnaryOperator
{
@@ -22,13 +37,13 @@ internal UnaryOperator(Operator oper)
public bool IsRightAssociative {get;}
public abstract bool TryEvaluate(IValue elem, out IValue result);
- };
+ }
internal sealed class NotOperator : UnaryOperator
{
internal static NotOperator Instance { get; } = new();
- internal NotOperator()
+ private NotOperator()
: base(Operator.Not)
{}
@@ -42,11 +57,11 @@ public override string ToString()
{
return "Not";
}
- };
+ }
internal sealed class RegexOperator : UnaryOperator
{
- private Regex _regex;
+ private readonly Regex _regex;
internal RegexOperator(Regex regex)
: base(Operator.Not)
@@ -56,7 +71,7 @@ internal RegexOperator(Regex regex)
public override bool TryEvaluate(IValue val, out IValue result)
{
- if (!(val.Type == JmesPathType.String))
+ if (val.Type != JmesPathType.String)
{
result = JsonConstants.Null;
return false; // type error
@@ -69,7 +84,6 @@ public override string ToString()
{
return "Regex";
}
- };
-
+ }
}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs
index cabfd2be..609f17e9 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -8,11 +23,11 @@ namespace AWS.Lambda.Powertools.JMESPath.Utilities
{
internal class JsonDocumentBuilder
{
- internal static JsonDocumentBuilder Default { get; } = new();
+ private static JsonDocumentBuilder Default { get; } = new();
internal JsonValueKind ValueKind {get;}
- private object? _item;
+ private readonly object? _item;
private IList GetList()
{
@@ -23,7 +38,8 @@ private IDictionary GetDictionary()
{
return _item as IDictionary ?? throw new InvalidOperationException("Item is null");
}
- internal JsonDocumentBuilder()
+
+ private JsonDocumentBuilder()
: this(JsonValueKind.Null)
{
}
@@ -210,7 +226,7 @@ internal int GetArrayLength()
{
throw new InvalidOperationException("This value's ValueKind is not Array.");
}
- return GetList().Count();
+ return GetList().Count;
}
internal int GetObjectLength()
@@ -219,7 +235,7 @@ internal int GetObjectLength()
{
throw new InvalidOperationException("This value's ValueKind is not Object.");
}
- return GetDictionary().Count();
+ return GetDictionary().Count;
}
internal bool TryGetProperty(string name, out JsonDocumentBuilder value)
@@ -228,12 +244,10 @@ internal bool TryGetProperty(string name, out JsonDocumentBuilder value)
{
throw new InvalidOperationException("This value's ValueKind is not Object.");
}
- if (ValueKind != JsonValueKind.Object)
- {
- value = Default;
- return false;
- }
- return GetDictionary().TryGetValue(name, out value);
+
+ if (ValueKind == JsonValueKind.Object) return GetDictionary().TryGetValue(name, out value);
+ value = Default;
+ return false;
}
public override string ToString()
@@ -249,13 +263,13 @@ private void ToString(StringBuilder buffer)
{
case JsonValueKind.Array:
{
- buffer.Append("[");
+ buffer.Append('[');
var first = true;
foreach (var item in EnumerateArray())
{
if (!first)
{
- buffer.Append(",");
+ buffer.Append(',');
}
else
{
@@ -263,28 +277,28 @@ private void ToString(StringBuilder buffer)
}
item.ToString(buffer);
}
- buffer.Append("]");
+ buffer.Append(']');
break;
}
case JsonValueKind.Object:
{
- buffer.Append("{");
+ buffer.Append('{');
var first = true;
foreach (var property in EnumerateObject())
{
if (!first)
{
- buffer.Append(",");
+ buffer.Append(',');
}
else
{
first = false;
}
buffer.Append(JsonSerializer.Serialize(property.Key));
- buffer.Append(":");
+ buffer.Append(':');
property.Value.ToString(buffer);
}
- buffer.Append("}");
+ buffer.Append('}');
break;
}
default:
@@ -301,5 +315,4 @@ internal JsonDocument ToJsonDocument()
return JsonDocument.Parse(json);
}
}
-
-} // namespace JsonCons.Utilities
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs
index b40ab7c6..22d44c6c 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs
@@ -1,3 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -43,7 +58,7 @@ public JsonElementComparer() {}
/// they are compared with the Decimal.CompareTo method, otherwise they are
/// compared as doubles.
///
- /// If both are objects, they are compared accoring to the following rules:
+ /// If both are objects, they are compared according to the following rules:
///
///
/// - Order each object's properties by name and compare sequentially.
@@ -84,19 +99,18 @@ public int Compare(JsonElement lhs, JsonElement rhs)
{
return dec1.CompareTo(dec2);
}
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+
+ if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
{
return val1.CompareTo(val2);
}
- else
- {
- throw new InvalidOperationException("Unable to compare numbers");
- }
+
+ throw new InvalidOperationException("Unable to compare numbers");
}
case JsonValueKind.String:
{
- return string.Compare(lhs.GetString(), rhs.GetString());
+ return string.CompareOrdinal(lhs.GetString(), rhs.GetString());
}
case JsonValueKind.Array:
@@ -121,8 +135,8 @@ public int Compare(JsonElement lhs, JsonElement rhs)
case JsonValueKind.Object:
{
// OrderBy performs a stable sort (Note that supports duplicate property names)
- var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
- var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
var result1 = enumerator1.MoveNext();
var result2 = enumerator2.MoveNext();
@@ -130,7 +144,7 @@ public int Compare(JsonElement lhs, JsonElement rhs)
{
if (enumerator1.Current.Name != enumerator2.Current.Name)
{
- return enumerator1.Current.Name.CompareTo(enumerator2.Current.Name);
+ return string.Compare(enumerator1.Current.Name, enumerator2.Current.Name, StringComparison.Ordinal);
}
var diff = Compare(enumerator1.Current.Value, enumerator2.Current.Value);
if (diff != 0)
@@ -145,7 +159,7 @@ public int Compare(JsonElement lhs, JsonElement rhs)
}
default:
- throw new InvalidOperationException(string.Format("Unknown JsonValueKind {0}", lhs.ValueKind));
+ throw new InvalidOperationException($"Unknown JsonValueKind {lhs.ValueKind}");
}
}
@@ -154,6 +168,4 @@ int System.Collections.IComparer.Compare(object x, object y)
return Compare((JsonElement)x, (JsonElement)y);
}
}
-
-
-} // namespace JsonCons.JsonPath
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs
index f8e7bfd7..6141fb58 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs
@@ -1,3 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,7 +29,7 @@ public sealed class JsonElementEqualityComparer : IEqualityComparer
/// Gets a singleton instance of . This property is read-only.
public static JsonElementEqualityComparer Instance { get; } = new();
- private int MaxHashDepth { get; } = 64;
+ private static int MaxHashDepth => 64;
private JsonElementEqualityComparer() {}
@@ -31,7 +46,7 @@ private JsonElementEqualityComparer() {}
/// they are compared with the Decimal.Equals method, otherwise they are
/// compared as doubles.
///
- /// If both are objects, they are compared accoring to the following rules:
+ /// If both are objects, they are compared according to the following rules:
///
///
/// - If the two objects have a different number of properties, they are different.
@@ -68,14 +83,13 @@ public bool Equals(JsonElement lhs, JsonElement rhs)
{
return dec1.Equals(dec2);
}
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
- {
- return val1 == val2;
- }
- else
+
+ if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
{
- return false;
+ return Math.Abs(val1 - val2) < 0.000000001;
}
+
+ return false;
}
case JsonValueKind.String:
@@ -97,8 +111,8 @@ public bool Equals(JsonElement lhs, JsonElement rhs)
return false;
}
- var enumerator1 = baseEnumerator1.OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
- var enumerator2 = baseEnumerator2.OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator1 = baseEnumerator1.OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator2 = baseEnumerator2.OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
var result1 = enumerator1.MoveNext();
var result2 = enumerator2.MoveNext();
@@ -120,7 +134,7 @@ public bool Equals(JsonElement lhs, JsonElement rhs)
}
default:
- throw new InvalidOperationException(string.Format("Unknown JsonValueKind {0}", lhs.ValueKind));
+ throw new InvalidOperationException($"Unknown JsonValueKind {lhs.ValueKind}");
}
}
@@ -173,11 +187,9 @@ private int ComputeHashCode(JsonElement element, int depth)
break;
default:
- throw new InvalidOperationException(string.Format("Unknown JsonValueKind {0}", element.ValueKind));
+ throw new InvalidOperationException($"Unknown JsonValueKind {element.ValueKind}");
}
return hashCode;
}
}
-
-
-} // namespace JsonCons.JsonPath
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs
index b5b8510b..719f6a1a 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Linq;
using System.Text;
using System.Text.Json;
@@ -14,7 +29,6 @@ namespace AWS.Lambda.Powertools.JMESPath.Utilities
/// using System;
/// using System.Diagnostics;
/// using System.Text.Json;
- /// using JsonCons.Utilities;
///
/// public class Example
/// {
@@ -121,7 +135,6 @@ public enum IntegerTokenUnflattening {
/// using System;
/// using System.Diagnostics;
/// using System.Text.Json;
- /// using JsonCons.Utilities;
///
/// public class Example
/// {
@@ -184,26 +197,26 @@ public static class JsonFlattener
/// number, true, false, null, empty object, or empty array.
///
///
- /// It is the users responsibilty to properly Dispose the returned value
+ /// It is the users responsibility to properly Dispose the returned value
///
/// The value to be flattened.
/// The flattened value
public static JsonDocument Flatten(JsonElement value)
{
var result = new JsonDocumentBuilder(JsonValueKind.Object);
- var parentKey = "";
+ const string parentKey = "";
_Flatten(parentKey, value, result);
return result.ToJsonDocument();
}
///
- /// Recovers the orginal JSON value from a JSON object in flattened form, to the extent possible.
+ /// Recovers the original JSON value from a JSON object in flattened form, to the extent possible.
/// There may not be a unique solution, an integer token in a JSON Pointer could be an array index or
/// it could be an object name. The default behavior is to attempt to recover arrays. The
/// parameter can be used to recover objects with integer names instead.
///
///
- /// It is the users responsibilty to properly Dispose the returned value
+ /// It is the users responsibility to properly Dispose the returned value
///
/// The flattened value, which must be a JSON object of name-value pairs, such that
/// the names are JSON Pointer strings, and the values are either string,
@@ -218,19 +231,10 @@ public static JsonDocument Unflatten(JsonElement flattenedValue,
{
if (options == IntegerTokenUnflattening.TryIndex)
{
- if (TryUnflattenArray(flattenedValue, out var val))
- {
- return val.ToJsonDocument();
- }
- else
- {
- return UnflattenToObject(flattenedValue, options).ToJsonDocument();
- }
- }
- else
- {
- return UnflattenToObject(flattenedValue, options).ToJsonDocument();
+ return TryUnflattenArray(flattenedValue, out var val) ? val.ToJsonDocument() : UnflattenToObject(flattenedValue, options).ToJsonDocument();
}
+
+ return UnflattenToObject(flattenedValue, options).ToJsonDocument();
}
private static void _Flatten(string parentKey,
@@ -260,7 +264,7 @@ private static void _Flatten(string parentKey,
case JsonValueKind.Object:
{
- if (parentValue.EnumerateObject().Count() == 0)
+ if (!parentValue.EnumerateObject().Any())
{
result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue));
}
@@ -297,11 +301,9 @@ private static JsonDocumentBuilder SafeUnflatten(JsonDocumentBuilder value)
var index = 0;
foreach (var item in value.EnumerateObject())
{
- if (!int.TryParse(item.Key, out var n) || index++ != n)
- {
- safe = false;
- break;
- }
+ if (int.TryParse(item.Key, out var n) && index++ == n) continue;
+ safe = false;
+ break;
}
if (safe)
@@ -318,19 +320,17 @@ private static JsonDocumentBuilder SafeUnflatten(JsonDocumentBuilder value)
}
return a;
}
- else
+
+ var o = new JsonDocumentBuilder(JsonValueKind.Object);
+ foreach (var item in value.EnumerateObject())
{
- var o = new JsonDocumentBuilder(JsonValueKind.Object);
- foreach (var item in value.EnumerateObject())
- {
- //if (!o.ContainsPropertyName(item.Key))
- //{
- // o.AddProperty(item.Key, SafeUnflatten (item.Value));
- //}
- o.TryAddProperty(item.Key, SafeUnflatten (item.Value));
- }
- return o;
+ //if (!o.ContainsPropertyName(item.Key))
+ //{
+ // o.AddProperty(item.Key, SafeUnflatten (item.Value));
+ //}
+ o.TryAddProperty(item.Key, SafeUnflatten (item.Value));
}
+ return o;
}
private static bool TryUnflattenArray(JsonElement value, out JsonDocumentBuilder result)
@@ -355,7 +355,7 @@ private static bool TryUnflattenArray(JsonElement value, out JsonDocumentBuilder
}
var index = 0;
- var it = ptr.GetEnumerator();
+ using var it = ptr.GetEnumerator();
var more = it.MoveNext();
while (more)
{
@@ -461,7 +461,8 @@ private static JsonDocumentBuilder UnflattenToObject(JsonElement value, IntegerT
{
throw new ArgumentException("Name contains invalid JSON Pointer");
}
- var it = ptr.GetEnumerator();
+
+ using var it = ptr.GetEnumerator();
var more = it.MoveNext();
while (more)
{
@@ -499,5 +500,4 @@ private static JsonDocumentBuilder UnflattenToObject(JsonElement value, IntegerT
return options == IntegerTokenUnflattening.TryIndex ? SafeUnflatten (result) : result;
}
}
-
-} // namespace JsonCons.Utilities
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs
index 0f2d3a3e..350128ca 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs
@@ -1,3 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System.Text.Json;
namespace AWS.Lambda.Powertools.JMESPath.Utilities
@@ -13,7 +28,6 @@ namespace AWS.Lambda.Powertools.JMESPath.Utilities
/// using System;
/// using System.Diagnostics;
/// using System.Text.Json;
- /// using JsonCons.Utilities;
///
/// public class Example
/// {
@@ -112,7 +126,7 @@ public static class JsonMergePatch
/// to a source JSON value.
///
///
- /// It is the users responsibilty to properly Dispose the returned value
+ /// It is the users responsibility to properly Dispose the returned value
///
/// The source JSON value.
/// The JSON merge patch to be applied to the source JSON value.
@@ -161,7 +175,7 @@ private static JsonDocumentBuilder ApplyMergePatch(ref JsonDocumentBuilder targe
/// given two JSON values, a source and a target.
///
///
- /// It is the users responsibilty to properly Dispose the returned value
+ /// It is the users responsibility to properly Dispose the returned value
///
/// The source JSON value.
/// The target JSON value.
@@ -198,8 +212,7 @@ private static JsonDocumentBuilder _FromDiff(JsonElement source, JsonElement tar
foreach (var property in target.EnumerateObject())
{
- JsonElement value;
- if (!source.TryGetProperty(property.Name, out value))
+ if (!source.TryGetProperty(property.Name, out _))
{
builder.AddProperty(property.Name, new JsonDocumentBuilder(property.Value));
}
@@ -208,6 +221,4 @@ private static JsonDocumentBuilder _FromDiff(JsonElement source, JsonElement tar
return builder;
}
}
-
-
-} // namespace JsonCons.Utilities
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs
index bf1fc449..5164bb2b 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
@@ -44,7 +59,6 @@ public JsonPatchException(
/// using System;
/// using System.Diagnostics;
/// using System.Text.Json;
- /// using JsonCons.Utilities;
///
/// public class Example
/// {
@@ -128,7 +142,7 @@ public static class JsonPatch
/// to a source JSON value.
///
///
- /// It is the users responsibilty to properly Dispose the returned value
+ /// It is the users responsibility to properly Dispose the returned value
///
/// The source JSON value.
/// The patch to be applied to the source JSON value.
@@ -139,16 +153,16 @@ public static class JsonPatch
///
/// A JSON Patch operation failed
///
- public static JsonDocument ApplyPatch(JsonElement source,
- JsonElement patch)
+ public static JsonDocument ApplyPatch(JsonElement source,
+ JsonElement patch)
{
var documentBuilder = new JsonDocumentBuilder(source);
ApplyPatch(ref documentBuilder, patch);
return documentBuilder.ToJsonDocument();
}
- private static void ApplyPatch(ref JsonDocumentBuilder target,
- JsonElement patch)
+ private static void ApplyPatch(ref JsonDocumentBuilder target,
+ JsonElement patch)
{
var comparer = JsonElementEqualityComparer.Instance;
@@ -158,132 +172,146 @@ private static void ApplyPatch(ref JsonDocumentBuilder target,
{
throw new ArgumentException("Patch must be an array");
}
-
+
foreach (var operation in patch.EnumerateArray())
{
if (!operation.TryGetProperty("op", out var opElement))
{
throw new ArgumentException("Invalid patch");
}
+
var op = opElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null");
if (!operation.TryGetProperty("path", out var pathElement))
{
- throw new ArgumentException(op, "Invalid patch");
+ throw new ArgumentException(op, nameof(patch));
}
- var path = pathElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null"); ;
+
+ var path = pathElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null");
if (!JsonPointer.TryParse(path, out var location))
{
- throw new ArgumentException(op, "Invalid patch");
+ throw new ArgumentException(op, nameof(patch));
}
- if (op =="test")
+ switch (op)
{
- if (!operation.TryGetProperty("value", out var value))
+ case "test":
{
- throw new ArgumentException(op, "Invalid patch");
- }
+ if (!operation.TryGetProperty("value", out var value))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
- if (!location.TryGetValue(target, out var tested))
- {
- throw new ArgumentException(op, "Invalid patch");
- }
+ if (!location.TryGetValue(target, out var tested))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
- using (var doc = tested.ToJsonDocument())
- {
+ using var doc = tested.ToJsonDocument();
if (!comparer.Equals(doc.RootElement, value))
{
throw new JsonPatchException(op, "Test failed");
}
+
+ break;
}
- }
- else if (op =="add")
- {
- if (!operation.TryGetProperty("value", out var value))
- {
- throw new ArgumentException(op, "Invalid patch");
- }
- var valueBuilder = new JsonDocumentBuilder(value);
- if (!location.TryAddIfAbsent(ref target, valueBuilder)) // try insert without replace
+ case "add":
{
+ if (!operation.TryGetProperty("value", out var value))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
+
+ var valueBuilder = new JsonDocumentBuilder(value);
+ if (location.TryAddIfAbsent(ref target, valueBuilder)) continue; // try insert without replace
if (!location.TryReplace(ref target, valueBuilder)) // try insert without replace
{
throw new JsonPatchException(op, "Add failed");
}
+
+ break;
}
- }
- else if (op =="remove")
- {
- if (!location.TryRemove(ref target))
- {
+ case "remove" when !location.TryRemove(ref target):
throw new JsonPatchException(op, "Add failed");
- }
- }
- else if (op =="replace")
- {
- if (!operation.TryGetProperty("value", out var value))
- {
- throw new ArgumentException(op, "Invalid patch");
- }
- var valueBuilder = new JsonDocumentBuilder(value);
- if (!location.TryReplace(ref target, valueBuilder))
+ case "replace":
{
- throw new JsonPatchException(op, "Replace failed");
+ if (!operation.TryGetProperty("value", out var value))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
+
+ var valueBuilder = new JsonDocumentBuilder(value);
+ if (!location.TryReplace(ref target, valueBuilder))
+ {
+ throw new JsonPatchException(op, "Replace failed");
+ }
+
+ break;
}
- }
- else if (op =="move")
- {
- if (!operation.TryGetProperty("from", out var fromElement))
+ case "move":
{
- throw new ArgumentException(op, "Invalid patch");
- }
- var from = fromElement.GetString() ?? throw new InvalidOperationException("From element cannot be null"); ;
+ if (!operation.TryGetProperty("from", out var fromElement))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
- if (!JsonPointer.TryParse(from, out var fromPointer))
- {
- throw new ArgumentException(op, "Invalid patch");
- }
+ var from = fromElement.GetString() ??
+ throw new InvalidOperationException("From element cannot be null");
+
+ if (!JsonPointer.TryParse(from, out var fromPointer))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
- if (!fromPointer.TryGetValue(target, out var value))
- {
- throw new JsonPatchException(op, "Move failed");
- }
+ if (!fromPointer.TryGetValue(target, out var value))
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
- if (!fromPointer.TryRemove(ref target))
- {
- throw new JsonPatchException(op, "Move failed");
- }
- if (!location.TryAddIfAbsent(ref target, value))
- {
- if (!location.TryReplace(ref target, value)) // try insert without replace
+ if (!fromPointer.TryRemove(ref target))
{
throw new JsonPatchException(op, "Move failed");
}
- }
- }
- else if (op =="copy")
- {
- if (!operation.TryGetProperty("from", out var fromElement))
- {
- throw new ArgumentException(op, "Invalid patch");
- }
- var from = fromElement.GetString() ?? throw new InvalidOperationException("from cannot be null");
- if (!JsonPointer.TryParse(from, out var fromPointer))
- {
- throw new ArgumentException(op, "Invalid patch");
- }
- if (!fromPointer.TryGetValue(target, out var value))
- {
- throw new JsonPatchException(op, "Copy failed");
+ if (!location.TryAddIfAbsent(ref target, value))
+ {
+ if (!location.TryReplace(ref target, value)) // try insert without replace
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
+ }
+
+ break;
}
- if (!location.TryAddIfAbsent(ref target, value))
+ case "copy":
{
- if (!location.TryReplace(ref target, value)) // try insert without replace
+ if (!operation.TryGetProperty("from", out var fromElement))
{
- throw new JsonPatchException(op, "Move failed");
+ throw new ArgumentException(op, nameof(patch));
+ }
+
+ var from = fromElement.GetString() ??
+ throw new InvalidOperationException("from cannot be null");
+ if (!JsonPointer.TryParse(from, out var fromPointer))
+ {
+ throw new ArgumentException(op, nameof(patch));
+ }
+
+ if (!fromPointer.TryGetValue(target, out var value))
+ {
+ throw new JsonPatchException(op, "Copy failed");
+ }
+
+ if (!location.TryAddIfAbsent(ref target, value))
+ {
+ if (!location.TryReplace(ref target, value)) // try insert without replace
+ {
+ throw new JsonPatchException(op, "Move failed");
+ }
}
+
+ break;
}
}
}
@@ -294,60 +322,62 @@ private static void ApplyPatch(ref JsonDocumentBuilder target,
/// given two JSON values, a source and a target.
///
///
- /// It is the users responsibilty to properly Dispose the returned value
+ /// It is the users responsibility to properly Dispose the returned value
///
/// The source JSON value.
/// The target JSON value.
/// A JSON Merge Patch to convert the source JSON value to the target JSON value
- public static JsonDocument FromDiff(JsonElement source,
- JsonElement target)
+ public static JsonDocument FromDiff(JsonElement source,
+ JsonElement target)
{
return _FromDiff(source, target, "").ToJsonDocument();
}
- private static JsonDocumentBuilder _FromDiff(JsonElement source,
- JsonElement target,
- string path)
+ private static JsonDocumentBuilder _FromDiff(JsonElement source,
+ JsonElement target,
+ string path)
{
var builder = new JsonDocumentBuilder(JsonValueKind.Array);
var comparer = JsonElementEqualityComparer.Instance;
- if (comparer.Equals(source,target))
+ if (comparer.Equals(source, target))
{
return builder;
}
if (source.ValueKind == JsonValueKind.Array && target.ValueKind == JsonValueKind.Array)
{
- var common = Math.Min(source.GetArrayLength(),target.GetArrayLength());
+ var common = Math.Min(source.GetArrayLength(), target.GetArrayLength());
for (var i = 0; i < common; ++i)
{
- var buffer = new StringBuilder(path);
+ var buffer = new StringBuilder(path);
buffer.Append("/");
buffer.Append(i.ToString());
- var temp_diff = _FromDiff(source[i], target[i], buffer.ToString());
- foreach (var item in temp_diff.EnumerateArray())
+ var tempDiff = _FromDiff(source[i], target[i], buffer.ToString());
+ foreach (var item in tempDiff.EnumerateArray())
{
builder.AddArrayItem(item);
}
}
+
// Element in source, not in target - remove
for (var i = source.GetArrayLength(); i-- > target.GetArrayLength();)
{
- var buffer = new StringBuilder(path);
- buffer.Append("/");
+ var buffer = new StringBuilder(path);
+ buffer.Append('/');
buffer.Append(i.ToString());
var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
valBuilder.AddProperty("op", new JsonDocumentBuilder("remove"));
valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
builder.AddArrayItem(valBuilder);
}
+
// Element in target, not in source - add,
for (var i = source.GetArrayLength(); i < target.GetArrayLength(); ++i)
{
var a = target[i];
- var buffer = new StringBuilder(path);
+ var buffer = new StringBuilder(path);
buffer.Append("/");
buffer.Append(i.ToString());
var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
@@ -362,13 +392,13 @@ private static JsonDocumentBuilder _FromDiff(JsonElement source,
foreach (var a in source.EnumerateObject())
{
var buffer = new StringBuilder(path);
- buffer.Append("/");
+ buffer.Append("/");
buffer.Append(JsonPointer.Escape(a.Name));
if (target.TryGetProperty(a.Name, out var element))
- {
- var temp_diff = _FromDiff(a.Value, element, buffer.ToString());
- foreach (var item in temp_diff.EnumerateArray())
+ {
+ var tempDiff = _FromDiff(a.Value, element, buffer.ToString());
+ foreach (var item in tempDiff.EnumerateArray())
{
builder.AddArrayItem(item);
}
@@ -381,20 +411,19 @@ private static JsonDocumentBuilder _FromDiff(JsonElement source,
builder.AddArrayItem(valBuilder);
}
}
+
foreach (var a in target.EnumerateObject())
{
- JsonElement element;
- if (!source.TryGetProperty(a.Name, out element))
- {
- var buffer = new StringBuilder(path);
- buffer.Append("/");
- buffer.Append(JsonPointer.Escape(a.Name));
- var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
- valBuilder.AddProperty("op", new JsonDocumentBuilder("add"));
- valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
- valBuilder.AddProperty("value", new JsonDocumentBuilder(a.Value));
- builder.AddArrayItem(valBuilder);
- }
+ if (source.TryGetProperty(a.Name, out _)) continue;
+
+ var buffer = new StringBuilder(path);
+ buffer.Append("/");
+ buffer.Append(JsonPointer.Escape(a.Name));
+ var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object);
+ valBuilder.AddProperty("op", new JsonDocumentBuilder("add"));
+ valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString()));
+ valBuilder.AddProperty("value", new JsonDocumentBuilder(a.Value));
+ builder.AddArrayItem(valBuilder);
}
}
else
@@ -409,5 +438,4 @@ private static JsonDocumentBuilder _FromDiff(JsonElement source,
return builder;
}
}
-
-} // namespace JsonCons.Utilities
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs
index 059dc613..1b933a50 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
@@ -14,7 +29,6 @@ namespace AWS.Lambda.Powertools.JMESPath.Utilities
/// using System;
/// using System.Diagnostics;
/// using System.Text.Json;
- /// using JsonCons.Utilities;
///
/// public class Example
/// {
@@ -58,7 +72,7 @@ namespace AWS.Lambda.Powertools.JMESPath.Utilities
public sealed class JsonPointer : IEnumerable, IEquatable
{
/// Gets a singleton instance of a to the root value of a JSON document.
- public static JsonPointer Default {get;} = new();
+ private static JsonPointer Default {get;} = new();
private enum JsonPointerState {Start, Escaped, Delim}
@@ -70,8 +84,7 @@ private enum JsonPointerState {Start, Escaped, Delim}
///
/// Constructs a JSON Pointer to the root value of a JSON document
///
-
- public JsonPointer()
+ private JsonPointer()
{
Tokens = new List();
}
@@ -157,7 +170,7 @@ public static bool TryParse(string input, out JsonPointer pointer)
default:
pointer = Default;
return false;
- };
+ }
break;
case JsonPointerState.Delim:
switch (input[index])
@@ -171,7 +184,7 @@ public static bool TryParse(string input, out JsonPointer pointer)
default:
buffer.Append(input[index]);
break;
- };
+ }
break;
case JsonPointerState.Escaped:
switch (input[index])
@@ -187,11 +200,13 @@ public static bool TryParse(string input, out JsonPointer pointer)
default:
pointer = Default;
return false;
- };
+ }
break;
default:
+ {
pointer = Default;
return false;
+ }
}
++index;
}
@@ -217,7 +232,7 @@ public IEnumerator GetEnumerator()
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
- return (System.Collections.IEnumerator) GetEnumerator();
+ return GetEnumerator();
}
///
@@ -230,7 +245,7 @@ public override string ToString()
var buffer = new StringBuilder();
foreach (var token in Tokens)
{
- buffer.Append("/");
+ buffer.Append('/');
Escape(token, buffer);
}
return buffer.ToString();
@@ -240,16 +255,15 @@ public override string ToString()
/// Returns a string representing the JSON Pointer as a URI fragment identifier
///
/// A JSON Pointer represented as a fragment identifier.
-
public string ToUriFragment()
{
var buffer = new StringBuilder();
- buffer.Append("#");
+ buffer.Append('#');
foreach (var token in Tokens)
{
- buffer.Append("/");
- var s = Uri.EscapeUriString(token);
+ buffer.Append('/');
+ var s = Uri.EscapeDataString(token);
var span = s.AsSpan();
for (var i = 0; i < span.Length; ++i)
{
@@ -435,7 +449,7 @@ public static bool ContainsValue(JsonElement root, string pointer)
/// The root that is to be queried.
/// Contains the value at the referenced location, if found.
/// true if the value was found at the referenced location, otherwise false.
- public bool TryGetValue(JsonElement root, out JsonElement value)
+ private bool TryGetValue(JsonElement root, out JsonElement value)
{
value = root;
@@ -572,5 +586,4 @@ private static void Escape(string token, StringBuilder buffer)
}
}
}
-
-} // namespace JsonCons.Utilities
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
index a729c44e..0f7fa997 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
@@ -1,11 +1,26 @@
-using System.Collections.Generic;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Collections.Generic;
using System.Text.Json;
namespace AWS.Lambda.Powertools.JMESPath.Utilities
{
internal static class JsonPointerExtensions
{
- public static bool TryResolve(string token, JsonDocumentBuilder current, out JsonDocumentBuilder result)
+ private static bool TryResolve(string token, JsonDocumentBuilder current, out JsonDocumentBuilder result)
{
result = current;
@@ -20,10 +35,12 @@ public static bool TryResolve(string token, JsonDocumentBuilder current, out Jso
{
return false;
}
+
if (index >= result.GetArrayLength())
{
return false;
}
+
result = result[index];
}
else if (result.ValueKind == JsonValueKind.Object)
@@ -43,29 +60,30 @@ public static bool TryResolve(string token, JsonDocumentBuilder current, out Jso
public static JsonPointer ToDefinitePath(this JsonPointer pointer, JsonDocumentBuilder value)
{
- if (value.ValueKind == JsonValueKind.Array && pointer.Tokens.Count > 0 && pointer.Tokens[pointer.Tokens.Count-1] == "-")
+ if (value.ValueKind == JsonValueKind.Array && pointer.Tokens.Count > 0 &&
+ pointer.Tokens[pointer.Tokens.Count - 1] == "-")
{
var tokens = new List();
- for (var i = 0; i < pointer.Tokens.Count-1; ++i)
+ for (var i = 0; i < pointer.Tokens.Count - 1; ++i)
{
tokens.Add(pointer.Tokens[i]);
}
+
tokens.Add(value.GetArrayLength().ToString());
return new JsonPointer(tokens);
}
- else
- {
- return pointer;
- }
+
+ return pointer;
}
- public static bool TryGetValue(this JsonPointer pointer, JsonDocumentBuilder root, out JsonDocumentBuilder value)
+ public static bool TryGetValue(this JsonPointer pointer, JsonDocumentBuilder root,
+ out JsonDocumentBuilder value)
{
value = root;
foreach (var token in pointer)
{
- if (!TryResolve(token,value,out value))
+ if (!TryResolve(token, value, out value))
{
return false;
}
@@ -74,19 +92,20 @@ public static bool TryGetValue(this JsonPointer pointer, JsonDocumentBuilder roo
return true;
}
- public static bool TryAdd(this JsonPointer location,
- ref JsonDocumentBuilder root,
- JsonDocumentBuilder value)
+ public static bool TryAdd(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
{
var current = root;
var token = "";
- var enumerator = location.GetEnumerator();
+ using var enumerator = location.GetEnumerator();
var more = enumerator.MoveNext();
if (!more)
{
return false;
}
+
while (more)
{
token = enumerator.Current;
@@ -105,7 +124,7 @@ public static bool TryAdd(this JsonPointer location,
if (token.Length == 1 && token[0] == '-')
{
current.AddArrayItem(value);
- current = current[current.GetArrayLength()-1];
+ current = current[current.GetArrayLength() - 1];
}
else
{
@@ -113,10 +132,12 @@ public static bool TryAdd(this JsonPointer location,
{
return false;
}
+
if (index > current.GetArrayLength())
{
return false;
}
+
if (index == current.GetArrayLength())
{
current.AddArrayItem(value);
@@ -124,7 +145,7 @@ public static bool TryAdd(this JsonPointer location,
}
else
{
- current.InsertArrayItem(index,value);
+ current.InsertArrayItem(index, value);
current = value;
}
}
@@ -135,6 +156,7 @@ public static bool TryAdd(this JsonPointer location,
{
current.RemoveProperty(token);
}
+
current.AddProperty(token, value);
current = value;
}
@@ -142,22 +164,24 @@ public static bool TryAdd(this JsonPointer location,
{
return false;
}
+
return true;
}
- public static bool TryAddIfAbsent(this JsonPointer location,
- ref JsonDocumentBuilder root,
- JsonDocumentBuilder value)
+ public static bool TryAddIfAbsent(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
{
var current = root;
var token = "";
- var enumerator = location.GetEnumerator();
+ using var enumerator = location.GetEnumerator();
var more = enumerator.MoveNext();
if (!more)
{
return false;
}
+
while (more)
{
token = enumerator.Current;
@@ -176,7 +200,7 @@ public static bool TryAddIfAbsent(this JsonPointer location,
if (token.Length == 1 && token[0] == '-')
{
current.AddArrayItem(value);
- current = current[current.GetArrayLength()-1];
+ current = current[current.GetArrayLength() - 1];
}
else
{
@@ -184,10 +208,12 @@ public static bool TryAddIfAbsent(this JsonPointer location,
{
return false;
}
+
if (index > current.GetArrayLength())
{
return false;
}
+
if (index == current.GetArrayLength())
{
current.AddArrayItem(value);
@@ -195,7 +221,7 @@ public static bool TryAddIfAbsent(this JsonPointer location,
}
else
{
- current.InsertArrayItem(index,value);
+ current.InsertArrayItem(index, value);
current = value;
}
}
@@ -206,6 +232,7 @@ public static bool TryAddIfAbsent(this JsonPointer location,
{
return false;
}
+
current.AddProperty(token, value);
current = value;
}
@@ -213,21 +240,23 @@ public static bool TryAddIfAbsent(this JsonPointer location,
{
return false;
}
+
return true;
}
- public static bool TryRemove(this JsonPointer location,
- ref JsonDocumentBuilder root)
+ public static bool TryRemove(this JsonPointer location,
+ ref JsonDocumentBuilder root)
{
var current = root;
var token = "";
- var enumerator = location.GetEnumerator();
+ using var enumerator = location.GetEnumerator();
var more = enumerator.MoveNext();
if (!more)
{
return false;
}
+
while (more)
{
token = enumerator.Current;
@@ -247,18 +276,18 @@ public static bool TryRemove(this JsonPointer location,
{
return false;
}
- else
+
+ if (!int.TryParse(token, out var index))
{
- if (!int.TryParse(token, out var index))
- {
- return false;
- }
- if (index >= current.GetArrayLength())
- {
- return false;
- }
- current.RemoveArrayItemAt(index);
+ return false;
}
+
+ if (index >= current.GetArrayLength())
+ {
+ return false;
+ }
+
+ current.RemoveArrayItemAt(index);
}
else if (current.ValueKind == JsonValueKind.Object)
{
@@ -271,22 +300,24 @@ public static bool TryRemove(this JsonPointer location,
{
return false;
}
+
return true;
}
- public static bool TryReplace(this JsonPointer location,
- ref JsonDocumentBuilder root,
- JsonDocumentBuilder value)
+ public static bool TryReplace(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
{
var current = root;
var token = "";
- var enumerator = location.GetEnumerator();
+ using var enumerator = location.GetEnumerator();
var more = enumerator.MoveNext();
if (!more)
{
return false;
}
+
while (more)
{
token = enumerator.Current;
@@ -306,18 +337,18 @@ public static bool TryReplace(this JsonPointer location,
{
return false;
}
- else
+
+ if (!int.TryParse(token, out var index))
{
- if (!int.TryParse(token, out var index))
- {
- return false;
- }
- if (index >= current.GetArrayLength())
- {
- return false;
- }
- current[index] = value;
+ return false;
+ }
+
+ if (index >= current.GetArrayLength())
+ {
+ return false;
}
+
+ current[index] = value;
}
else if (current.ValueKind == JsonValueKind.Object)
{
@@ -329,15 +360,15 @@ public static bool TryReplace(this JsonPointer location,
{
return false;
}
+
current.AddProperty(token, value);
}
else
{
return false;
}
+
return true;
}
-
}
-
-} // namespace JsonCons.Utilities
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
index 8ec005d6..e0fae410 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
@@ -1,4 +1,19 @@
-using System;
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
@@ -50,7 +65,7 @@ internal interface IValue
IArrayValueEnumerator EnumerateArray();
IObjectValueEnumerator EnumerateObject();
IExpression GetExpression();
- };
+ }
internal readonly struct JsonElementValue : IValue
{
@@ -217,7 +232,7 @@ public override string ToString()
var s = JsonSerializer.Serialize(_element);
return s;
}
- };
+ }
internal readonly struct DoubleValue : IValue
{
@@ -638,7 +653,7 @@ public override string ToString()
{
first = false;
}
- buffer.Append(item.ToString());
+ buffer.Append(item);
}
buffer.Append(']');
return buffer.ToString();
@@ -754,7 +769,7 @@ public override string ToString()
}
buffer.Append(JsonSerializer.Serialize(property.Key));
buffer.Append(':');
- buffer.Append(property.Value.ToString());
+ buffer.Append(property.Value);
}
buffer.Append('}');
return buffer.ToString();
@@ -812,5 +827,5 @@ public override string ToString()
{
return "expression";
}
- };
+ }
}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs
index bfd785a3..a09d6bff 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs
@@ -1,3 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,7 +30,7 @@ internal sealed class ValueComparer : IComparer, System.Collections.ICom
///
/// Constructs a
///
- public ValueComparer() {}
+ private ValueComparer() {}
///
/// Compares two instances.
@@ -81,18 +96,17 @@ public int Compare(IValue lhs, IValue rhs)
{
return dec1.CompareTo(dec2);
}
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+
+ if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
{
return val1.CompareTo(val2);
}
- else
- {
- throw new InvalidOperationException("Unable to compare numbers");
- }
+
+ throw new InvalidOperationException("Unable to compare numbers");
}
case JmesPathType.String:
- return lhs.GetString().CompareTo(rhs.GetString());
+ return string.Compare(lhs.GetString(), rhs.GetString(), StringComparison.Ordinal);
case JmesPathType.Array:
{
@@ -116,8 +130,8 @@ public int Compare(IValue lhs, IValue rhs)
case JmesPathType.Object:
{
// OrderBy performs a stable sort (Note that supports duplicate property names)
- var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
- var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
var result1 = enumerator1.MoveNext();
var result2 = enumerator2.MoveNext();
@@ -125,7 +139,7 @@ public int Compare(IValue lhs, IValue rhs)
{
if (enumerator1.Current.Name != enumerator2.Current.Name)
{
- return enumerator1.Current.Name.CompareTo(enumerator2.Current.Name);
+ return string.Compare(enumerator1.Current.Name, enumerator2.Current.Name, StringComparison.Ordinal);
}
var diff = Compare(enumerator1.Current.Value, enumerator2.Current.Value);
if (diff != 0)
@@ -140,7 +154,7 @@ public int Compare(IValue lhs, IValue rhs)
}
default:
- throw new InvalidOperationException(string.Format("Unknown JmesPathType {0}", lhs.Type));
+ throw new InvalidOperationException($"Unknown JmesPathType {lhs.Type}");
}
}
@@ -149,6 +163,4 @@ int System.Collections.IComparer.Compare(object x, object y)
return Compare((IValue)x, (IValue)y);
}
}
-
-
-} // namespace JsonCons.JsonPath
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs
index 78679579..ef04d261 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs
@@ -1,3 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,15 +23,17 @@ internal sealed class ValueEqualityComparer : IEqualityComparer
{
internal static ValueEqualityComparer Instance { get; } = new();
- private int _maxHashDepth = 100;
+ private readonly int _maxHashDepth = 100;
private ValueEqualityComparer() {}
public bool Equals(IValue lhs, IValue rhs)
{
- if (lhs.Type != rhs.Type)
+ if (lhs != null && rhs != null && lhs.Type != rhs.Type)
return false;
+ if (rhs == null || lhs == null) return false;
+
switch (lhs.Type)
{
case JmesPathType.Null:
@@ -30,18 +47,17 @@ public bool Equals(IValue lhs, IValue rhs)
{
return dec1 == dec2;
}
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
- {
- return val1 == val2;
- }
- else
+
+ if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
{
- return false;
+ return Math.Abs(val1 - val2) < 0.000000001;
}
+
+ return false;
}
case JmesPathType.String:
- return lhs.GetString().Equals(rhs.GetString());
+ return lhs.GetString().Equals(rhs.GetString());
case JmesPathType.Array:
return lhs.EnumerateArray().SequenceEqual(rhs.EnumerateArray(), this);
@@ -49,8 +65,10 @@ public bool Equals(IValue lhs, IValue rhs)
case JmesPathType.Object:
{
// OrderBy performs a stable sort (Note that IValue supports duplicate property names)
- var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
- var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal).GetEnumerator();
+ using var enumerator1 = lhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal)
+ .GetEnumerator();
+ using var enumerator2 = rhs.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal)
+ .GetEnumerator();
var result1 = enumerator1.MoveNext();
var result2 = enumerator2.MoveNext();
@@ -60,19 +78,21 @@ public bool Equals(IValue lhs, IValue rhs)
{
return false;
}
- if (!(Equals(enumerator1.Current.Value,enumerator2.Current.Value)))
+
+ if (!(Equals(enumerator1.Current.Value, enumerator2.Current.Value)))
{
return false;
}
+
result1 = enumerator1.MoveNext();
result2 = enumerator2.MoveNext();
- }
+ }
return result1 == false && result2 == false;
}
default:
- throw new InvalidOperationException(string.Format("Unknown JmesPathType {0}", lhs.Type));
+ throw new InvalidOperationException($"Unknown JmesPathType {lhs.Type}");
}
}
@@ -119,7 +139,7 @@ private int ComputeHashCode(IValue element, int depth)
break;
default:
- throw new InvalidOperationException(string.Format("Unknown JmesPathType {0}", element.Type));
+ throw new InvalidOperationException($"Unknown JmesPathType {element.Type}");
}
return hashCode;
}
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs
index 8c927eb7..2cdb71da 100644
--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs
@@ -1 +1,16 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
global using Xunit;
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
index 52f5db99..4da08962 100644
--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
@@ -1,3 +1,18 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
using System.Text.Json;
using AWS.Lambda.Powertools.JMESPath.Utilities;
using Xunit.Abstractions;
From 4170aba52dfa98edaa18253f8b50e5d3455c190d Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 11:54:52 +0100
Subject: [PATCH 03/28] Add README.md. More tests, examples and events.
---
.../AWS.Lambda.Powertools.JMESPath/README.md | 35 +-
...WS.Lambda.Powertools.JMESPath.Tests.csproj | 12 +
.../JmesPathExamples.cs | 489 ++++++++++++++++++
.../JmesPathTests.cs | 20 +-
.../test_files/cloud_watch_logs.json | 15 +
.../test_files/kinesis_data_stream.json | 74 +++
.../test_files/sns.json | 41 ++
.../test_files/sqs.json | 78 +++
8 files changed, 754 insertions(+), 10 deletions(-)
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/cloud_watch_logs.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/kinesis_data_stream.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sns.json
create mode 100644 libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sqs.json
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
index 66d2c222..3ad4bca4 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
@@ -40,4 +40,37 @@ JsonDocument result = JsonTransformer.Transform(doc.RootElement, expr);
-```
\ No newline at end of file
+```
+
+It produces the result
+```json
+"dd4649e6-2484-4993-acb8-0f9123103394"
+```
+ You can find more examples [here](../../tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs)
+
+## Built-in envelopes
+
+We provide built-in envelopes for popular AWS Lambda event sources to easily decode and/or deserialize JSON objects.
+
+| Envelop | JMESPath expression |
+|---------------------|-----------------------------------------------------------------------------|
+| API_GATEWAY_HTTP | powertools_json(body) |
+| API_GATEWAY_REST | powertools_json(body) |
+| CLOUDWATCH_LOGS | awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*] |
+| KINESIS_DATA_STREAM | Records[*].kinesis.powertools_json(powertools_base64(data)) |
+| SNS | Records[*].Sns.Message | powertools_json(@) |
+| SQS | Records[*].powertools_json(body) |
+
+More examples of events can be found [here](../../tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files)
+
+## Built-in JMESPath functions
+You can use our built-in JMESPath functions within your envelope expression. They handle deserialization for common data formats found in AWS Lambda event sources such as JSON strings, base64, and uncompress gzip data.
+
+### powertools_json function
+Use powertools_json function to decode any JSON string anywhere a JMESPath expression is allowed.
+
+### powertools_base64 function
+Use powertools_base64 function to decode any base64 data.
+
+### powertools_base64_gzip function
+Use powertools_base64_gzip function to decompress and decode base64 data.
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj
index 2f385c45..bbea95c1 100644
--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/AWS.Lambda.Powertools.JMESPath.Tests.csproj
@@ -89,6 +89,18 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs
new file mode 100644
index 00000000..a1386ea6
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs
@@ -0,0 +1,489 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+using System.Text.Json;
+using Xunit.Abstractions;
+
+namespace AWS.Lambda.Powertools.JMESPath.Tests;
+
+public class JmesPathExamples
+{
+ private readonly ITestOutputHelper _output;
+ private readonly JsonSerializerOptions _serializerOptions = new() { WriteIndented = false };
+
+ public JmesPathExamples(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public void Select_With_Powertools_Json_Function()
+ {
+ var jsonString = """
+ {
+ "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+ }
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+
+ var transformer = JsonTransformer.Parse("powertools_json(body).customerId");
+ using var result = transformer.Transform(doc.RootElement);
+
+ _output.WriteLine(result.RootElement.GetRawText());
+
+ Assert.Equal("dd4649e6-2484-4993-acb8-0f9123103394", result.RootElement.GetString());
+ }
+
+ [Fact]
+ public void Select_With_Powertools_Base64_Function()
+ {
+ var jsonString = """
+ {
+ "body": "eyJjdXN0b21lcklkIjoiZGQ0NjQ5ZTYtMjQ4NC00OTkzLWFjYjgtMGY5MTIzMTAzMzk0In0=",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+ }
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+
+ var transformer = JsonTransformer.Parse("powertools_base64(body).customerId");
+ using var result = transformer.Transform(doc.RootElement);
+
+ _output.WriteLine(result.RootElement.GetRawText());
+
+ Assert.Equal("dd4649e6-2484-4993-acb8-0f9123103394", result.RootElement.GetString());
+ }
+
+ [Fact]
+ public void Select_With_Powertools_Base64_Gzip_Function()
+ {
+ var jsonString = """
+ {
+ "body": "H4sIAAAAAAAAA6tWSi4tLsnPTS3yTFGyUkpJMTEzsUw10zUysTDRNbG0NNZNTE6y0DVIszQ0MjY0MDa2NFGqBQCMzDWgNQAAAA==",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+ }
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+
+ var transformer = JsonTransformer.Parse("powertools_base64_gzip(body).customerId");
+ using var result = transformer.Transform(doc.RootElement);
+
+ _output.WriteLine(result.RootElement.GetRawText());
+
+ Assert.Equal("dd4649e6-2484-4993-acb8-0f9123103394", result.RootElement.GetString());
+ }
+
+ [Fact]
+ public void FiltersAndMultiselectLists()
+ {
+ //Arrange
+
+ var jsonString = """
+ {
+ "people": [
+ {
+ "age": 20,
+ "other": "foo",
+ "name": "Bob"
+ },
+ {
+ "age": 25,
+ "other": "bar",
+ "name": "Fred"
+ },
+ {
+ "age": 30,
+ "other": "baz",
+ "name": "George"
+ }
+ ]
+ }
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+
+ var expectedJson = """[["Fred",25],["George",30]]""";
+
+ //Act
+
+ var transformer = JsonTransformer.Parse("people[?age > `20`].[name, age]");
+
+ using var result = transformer.Transform(doc.RootElement);
+
+ var actualJson = JsonSerializer.Serialize(result.RootElement, _serializerOptions);
+
+ //Assert
+
+ _output.WriteLine(actualJson);
+ Assert.Equal(expectedJson, actualJson);
+ }
+
+ // Source: https://jmespath.org/examples.html#filters-and-multiselect-hashes
+ [Fact]
+ public void FiltersAndMultiselectHashes()
+ {
+ //Arrange
+
+ var jsonString = """
+
+ {
+ "people": [
+ {
+ "age": 20,
+ "other": "foo",
+ "name": "Bob"
+ },
+ {
+ "age": 25,
+ "other": "bar",
+ "name": "Fred"
+ },
+ {
+ "age": 30,
+ "other": "baz",
+ "name": "George"
+ }
+ ]
+ }
+
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+ var expectedJson = """[{"name":"Fred","age":25},{"name":"George","age":30}]""";
+
+ // Act
+
+ var transformer = JsonTransformer.Parse("people[?age > `20`].{name: name, age: age}");
+
+ using var result = transformer.Transform(doc.RootElement);
+ var actualJson = JsonSerializer.Serialize(result.RootElement, _serializerOptions);
+
+ //Assert
+
+ _output.WriteLine(actualJson);
+ Assert.Equal(expectedJson, actualJson);
+ }
+
+ // Source: https://jmespath.org/examples.html#working-with-nested-data
+ [Fact]
+ public void WorkingWithNestedData()
+ {
+ // Arrange
+
+ var jsonString = """
+
+ {
+ "reservations": [
+ {
+ "instances": [
+ {"type": "small",
+ "state": {"name": "running"},
+ "tags": [{"Key": "Name",
+ "Values": ["Web"]},
+ {"Key": "version",
+ "Values": ["1"]}]},
+ {"type": "large",
+ "state": {"name": "stopped"},
+ "tags": [{"Key": "Name",
+ "Values": ["Web"]},
+ {"Key": "version",
+ "Values": ["1"]}]}
+ ]
+ }, {
+ "instances": [
+ {"type": "medium",
+ "state": {"name": "terminated"},
+ "tags": [{"Key": "Name",
+ "Values": ["Web"]},
+ {"Key": "version",
+ "Values": ["1"]}]},
+ {"type": "xlarge",
+ "state": {"name": "running"},
+ "tags": [{"Key": "Name",
+ "Values": ["DB"]},
+ {"Key": "version",
+ "Values": ["1"]}]}
+ ]
+ }
+ ]
+ }
+
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+ var expectedJson = """[["Web","small","running"],["Web","large","stopped"],["Web","medium","terminated"],["DB","xlarge","running"]]""";
+
+ // Act
+
+ var transformer =
+ JsonTransformer.Parse("reservations[].instances[].[tags[?Key=='Name'].Values[] | [0], type, state.name]");
+
+ using var result = transformer.Transform(doc.RootElement);
+ var actualJson = JsonSerializer.Serialize(result.RootElement, _serializerOptions);
+
+ //Assert
+
+ _output.WriteLine(actualJson);
+ Assert.Equal(expectedJson, actualJson);
+ }
+
+ // Source: https://jmespath.org/examples.html#filtering-and-selecting-nested-data
+ [Fact]
+ public void FilteringAndSelectingNestedData()
+ {
+ //Arrange
+
+ var jsonString = """
+
+ {
+ "people": [
+ {
+ "general": {
+ "id": 100,
+ "age": 20,
+ "other": "foo",
+ "name": "Bob"
+ },
+ "history": {
+ "first_login": "2014-01-01",
+ "last_login": "2014-01-02"
+ }
+ },
+ {
+ "general": {
+ "id": 101,
+ "age": 30,
+ "other": "bar",
+ "name": "Bill"
+ },
+ "history": {
+ "first_login": "2014-05-01",
+ "last_login": "2014-05-02"
+ }
+ }
+ ]
+ }
+
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+ var expectedJson = """{"id":100,"age":20,"other":"foo","name":"Bob"}""";
+
+ // Act
+
+ var transformer = JsonTransformer.Parse("people[?general.id==`100`].general | [0]");
+ using var result = transformer.Transform(doc.RootElement);
+ var actualJson = JsonSerializer.Serialize(result.RootElement, _serializerOptions);
+
+ //Assert
+
+ _output.WriteLine(actualJson);
+ Assert.Equal(expectedJson, actualJson);
+ }
+
+ // Source: https://jmespath.org/examples.html#using-functions
+ [Fact]
+ public void UsingFunctions()
+ {
+ // Arrange
+
+ var jsonString = """
+
+ {
+ "Contents": [
+ {
+ "Date": "2014-12-21T05:18:08.000Z",
+ "Key": "logs/bb",
+ "Size": 303
+ },
+ {
+ "Date": "2014-12-20T05:19:10.000Z",
+ "Key": "logs/aa",
+ "Size": 308
+ },
+ {
+ "Date": "2014-12-20T05:19:12.000Z",
+ "Key": "logs/qux",
+ "Size": 297
+ },
+ {
+ "Date": "2014-11-20T05:22:23.000Z",
+ "Key": "logs/baz",
+ "Size": 329
+ },
+ {
+ "Date": "2014-12-20T05:25:24.000Z",
+ "Key": "logs/bar",
+ "Size": 604
+ },
+ {
+ "Date": "2014-12-20T05:27:12.000Z",
+ "Key": "logs/foo",
+ "Size": 647
+ }
+ ]
+ }
+
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+ var expectedJson = """[{"Key":"logs/baz","Size":329},{"Key":"logs/aa","Size":308},{"Key":"logs/qux","Size":297},{"Key":"logs/bar","Size":604},{"Key":"logs/foo","Size":647},{"Key":"logs/bb","Size":303}]""";
+
+ // Act
+
+ var transformer = JsonTransformer.Parse("sort_by(Contents, &Date)[*].{Key: Key, Size: Size}");
+ using var result = transformer.Transform(doc.RootElement);
+ var actualJson = JsonSerializer.Serialize(result.RootElement, _serializerOptions);
+
+ //Assert
+
+ _output.WriteLine(actualJson);
+ Assert.Equal(expectedJson, actualJson);
+ }
+
+ [Fact]
+ public void SortBySize()
+ {
+ // Arrange
+
+ var jsonString = """
+
+ {
+ "Contents": [
+ {
+ "Date": "2014-12-21T05:18:08.000Z",
+ "Key": "logs/bb",
+ "Size": 303
+ },
+ {
+ "Date": "2014-12-20T05:19:10.000Z",
+ "Key": "logs/aa",
+ "Size": 308
+ },
+ {
+ "Date": "2014-12-20T05:19:12.000Z",
+ "Key": "logs/qux",
+ "Size": 297
+ },
+ {
+ "Date": "2014-11-20T05:22:23.000Z",
+ "Key": "logs/baz",
+ "Size": 329
+ },
+ {
+ "Date": "2014-12-20T05:25:24.000Z",
+ "Key": "logs/bar",
+ "Size": 604
+ },
+ {
+ "Date": "2014-12-20T05:27:12.000Z",
+ "Key": "logs/foo",
+ "Size": 647
+ }
+ ]
+ }
+
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+ var expectedJson = """[{"Size":297},{"Size":303},{"Size":308},{"Size":329},{"Size":604},{"Size":647}]""";
+
+ // Act
+
+ var transformer = JsonTransformer.Parse("sort_by(Contents, &Size)[*].{Size: Size}");
+ using var result = transformer.Transform(doc.RootElement);
+ var actualJson = JsonSerializer.Serialize(result.RootElement, _serializerOptions);
+
+ //Assert
+
+ _output.WriteLine(actualJson);
+ Assert.Equal(expectedJson, actualJson);
+ }
+
+ [Fact]
+ public void KeyOfInterest()
+ {
+ var jsonString = """
+
+ {
+ "Data":[
+ {
+ "KeyOfInterest":true,
+ "AnotherKey":true
+ },
+ {
+ "KeyOfInterest":false,
+ "AnotherKey":true
+ },
+ {
+ "KeyOfInterest":true,
+ "AnotherKey":true
+ }
+ ]
+ }
+
+ """;
+
+ using var doc = JsonDocument.Parse(jsonString);
+
+ var expectedJson1 = "[true,false,true]";
+ var expectedJson2 = """[{"Key of Interest":true,"Another Key":true},{"Key of Interest":false,"Another Key":true},{"Key of Interest":true,"Another Key":true}]""";
+
+ // Act
+
+ var result1 = JsonTransformer.Transform(doc.RootElement,
+ "Data[*].KeyOfInterest");
+ var result2 = JsonTransformer.Transform(doc.RootElement,
+ "Data[*].{\"Key of Interest\" : KeyOfInterest, \"Another Key\": AnotherKey}");
+
+ var actualJson1 = JsonSerializer.Serialize(result1);
+ var actualJson2 = JsonSerializer.Serialize(result2, _serializerOptions);
+
+ // Assert
+
+ _output.WriteLine(JsonSerializer.Serialize(result1));
+ _output.WriteLine(JsonSerializer.Serialize(result2, _serializerOptions));
+
+ Assert.Equal(expectedJson1, actualJson1);
+ Assert.Equal(expectedJson2, actualJson2);
+ }
+}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
index 4da08962..7a82c697 100644
--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs
@@ -49,6 +49,10 @@ public JmesPathTests(ITestOutputHelper output)
[InlineData("test_files/test.json")]
[InlineData("test_files/apigw_event.json")]
[InlineData("test_files/apigw_event_2.json")]
+ [InlineData("test_files/sns.json")]
+ [InlineData("test_files/sqs.json")]
+ [InlineData("test_files/cloud_watch_logs.json")]
+ [InlineData("test_files/kinesis_data_stream.json")]
public void RunJmesPathTests(string path)
{
_output.WriteLine($"Test {path}");
@@ -100,16 +104,14 @@ public void RunJmesPathTests(string path)
{
var expr = JsonTransformer.Parse(exprElement.ToString());
var result = expr.Transform(given);
- var success = comparer.Equals(result.RootElement, expected);
- if (!success)
- {
- _output.WriteLine("File: {0}", path);
- _output.WriteLine($"Document: {given}");
- _output.WriteLine($"Path: {exprElement}");
- _output.WriteLine($"Expected: {JsonSerializer.Serialize(expected)}");
- _output.WriteLine($"Result: {JsonSerializer.Serialize(result)}");
- }
+ _output.WriteLine("File: {0}", path);
+
+ // _output.WriteLine($"Document: {given}");
+ _output.WriteLine($"Path: {exprElement}");
+ _output.WriteLine($"Expected: {JsonSerializer.Serialize(expected)}");
+ _output.WriteLine($"Result: {JsonSerializer.Serialize(result)}");
+
Assert.True(comparer.Equals(result.RootElement,expected));
}
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/cloud_watch_logs.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/cloud_watch_logs.json
new file mode 100644
index 00000000..f779f00d
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/cloud_watch_logs.json
@@ -0,0 +1,15 @@
+[
+ {
+ "given": {
+ "awslogs": {
+ "data": "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA=="
+ }
+ },
+ "cases": [
+ {
+ "expression": "awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]",
+ "result": [{"id":"eventId1","timestamp":1440442987000,"message":"[ERROR] First test message"},{"id":"eventId2","timestamp":1440442987001,"message":"[ERROR] Second test message"}]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/kinesis_data_stream.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/kinesis_data_stream.json
new file mode 100644
index 00000000..a9e7f236
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/kinesis_data_stream.json
@@ -0,0 +1,74 @@
+[
+ {
+ "given": {
+ "Records": [
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "IlRlc3QgZnJvbSBLaW5lc2lzIg==",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1428537600
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "us-east-1"
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "Records[0].kinesis.powertools_json(powertools_base64(data))",
+ "result": "Test from Kinesis"
+ }
+ ]
+ },
+ {
+ "given": {
+ "Records": [
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "IlRlc3QgZnJvbSBLaW5lc2lzIg==",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1428537600
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "us-east-1"
+ },
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "IlNlY29uZCBSZWNvcmQgU3RyZWFtIg==",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1428537600
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "us-east-1"
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "Records[*].kinesis.powertools_json(powertools_base64(data))",
+ "result": ["Test from Kinesis", "Second Record Stream"]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sns.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sns.json
new file mode 100644
index 00000000..45a9c346
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sns.json
@@ -0,0 +1,41 @@
+[
+ {
+ "given": {
+ "Records": [
+ {
+ "EventSource": "aws:sns",
+ "EventVersion": "1.0",
+ "EventSubscriptionArn": "arn:aws:sns:us-east-1:{{{accountId}}}:ExampleTopic",
+ "Sns": {
+ "Type": "Notification",
+ "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
+ "TopicArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic",
+ "Subject": "example subject",
+ "Message": "example message",
+ "Timestamp": "1970-01-01T00:00:00.000Z",
+ "SignatureVersion": "1",
+ "Signature": "EXAMPLE",
+ "SigningCertUrl": "EXAMPLE",
+ "UnsubscribeUrl": "EXAMPLE",
+ "MessageAttributes": {
+ "Test": {
+ "Type": "String",
+ "Value": "TestString"
+ },
+ "TestBinary": {
+ "Type": "Binary",
+ "Value": "TestBinary"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "Records[0].Sns.Message | powertools_json(@)",
+ "result": "example message"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sqs.json b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sqs.json
new file mode 100644
index 00000000..d0f45c69
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/test_files/sqs.json
@@ -0,0 +1,78 @@
+[
+ {
+ "given": {
+ "Records": [
+ {
+ "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
+ "receiptHandle": "MessageReceiptHandle",
+ "body": "Hello from SQS!",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1523232000000",
+ "SenderId": "123456789012",
+ "ApproximateFirstReceiveTimestamp": "1523232000001"
+ },
+ "messageAttributes": {},
+ "md5OfBody": "{{{md5_of_body}}}",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
+ "awsRegion": "us-east-1"
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "Records[0].powertools_json(body)",
+ "result": "Hello from SQS!"
+ }
+ ]
+ },
+ {
+ "given": {
+ "Records": [
+ {
+ "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
+ "receiptHandle": "MessageReceiptHandle",
+ "body": "Hello from SQS!",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1523232000000",
+ "SenderId": "123456789012",
+ "ApproximateFirstReceiveTimestamp": "1523232000001"
+ },
+ "messageAttributes": {},
+ "md5OfBody": "{{{md5_of_body}}}",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
+ "awsRegion": "us-east-1"
+ },
+ {
+ "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
+ "receiptHandle": "MessageReceiptHandle",
+ "body": "2nd Message",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1523232000000",
+ "SenderId": "123456789012",
+ "ApproximateFirstReceiveTimestamp": "1523232000001"
+ },
+ "messageAttributes": {},
+ "md5OfBody": "{{{md5_of_body}}}",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
+ "awsRegion": "us-east-1"
+ }
+ ]
+ },
+ "cases": [
+ {
+ "expression": "Records[0].powertools_json(body)",
+ "result": "Hello from SQS!"
+ },
+ {
+ "expression": "Records[*].powertools_json(body)",
+ "result": ["Hello from SQS!","2nd Message"]
+ }
+ ]
+ }
+]
\ No newline at end of file
From 0055589be2c2970f0140ec80655d5791562a7f20 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 13:00:28 +0100
Subject: [PATCH 04/28] mkdocs update to move sidebar to the right and year
update
---
mkdocs.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mkdocs.yml b/mkdocs.yml
index 4862dbf5..42177854 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -19,6 +19,7 @@ nav:
- utilities/parameters.md
- utilities/idempotency.md
- utilities/batch-processing.md
+ - utilities/jmespath-functions.md
theme:
name: material
@@ -47,7 +48,6 @@ theme:
- navigation.tracking
- content.code.annotate
- toc.follow
- - toc.integrate
- announce.dismiss
icon:
repo: fontawesome/brands/github
@@ -81,7 +81,7 @@ markdown_extensions:
format: !!python/name:pymdownx.superfences.fence_code_format
- md_in_html
-copyright: Copyright © 2023 Amazon Web Services
+copyright: Copyright © 2024 Amazon Web Services
plugins:
- git-revision-date
From 5e8ba98339cd8ea18d534ba37d5f89e21c1c62cb Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 13:01:28 +0100
Subject: [PATCH 05/28] update examples nuget packages
---
examples/Idempotency/src/HelloWorld/HelloWorld.csproj | 4 ++--
examples/Logging/src/HelloWorld/HelloWorld.csproj | 2 +-
examples/Metrics/src/HelloWorld/HelloWorld.csproj | 4 ++--
.../src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj | 6 +++---
examples/Tracing/src/HelloWorld/HelloWorld.csproj | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj
index 9f776ce0..4a3f8ed3 100644
--- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj
@@ -8,7 +8,7 @@
-
-
+
+
diff --git a/examples/Logging/src/HelloWorld/HelloWorld.csproj b/examples/Logging/src/HelloWorld/HelloWorld.csproj
index a970a2f0..be745788 100644
--- a/examples/Logging/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Logging/src/HelloWorld/HelloWorld.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/examples/Metrics/src/HelloWorld/HelloWorld.csproj b/examples/Metrics/src/HelloWorld/HelloWorld.csproj
index d6eee6b5..9116bbec 100644
--- a/examples/Metrics/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Metrics/src/HelloWorld/HelloWorld.csproj
@@ -8,8 +8,8 @@
-
-
+
+
diff --git a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj
index ce9c7cb4..100f2d2c 100644
--- a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj
+++ b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj
@@ -13,8 +13,8 @@
-
-
-
+
+
+
diff --git a/examples/Tracing/src/HelloWorld/HelloWorld.csproj b/examples/Tracing/src/HelloWorld/HelloWorld.csproj
index e6bf4310..f63df00c 100644
--- a/examples/Tracing/src/HelloWorld/HelloWorld.csproj
+++ b/examples/Tracing/src/HelloWorld/HelloWorld.csproj
@@ -8,8 +8,8 @@
-
-
+
+
From 568caa4d1f9b0882b7def0fcaccc2aa8e63acf13 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 13:01:49 +0100
Subject: [PATCH 06/28] update jmespath readme
---
libraries/src/AWS.Lambda.Powertools.JMESPath/README.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
index 3ad4bca4..7f63b79f 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
@@ -73,4 +73,7 @@ Use powertools_json function to decode any JSON string anywhere a JMESPath expre
Use powertools_base64 function to decode any base64 data.
### powertools_base64_gzip function
-Use powertools_base64_gzip function to decompress and decode base64 data.
\ No newline at end of file
+Use powertools_base64_gzip function to decompress and decode base64 data.
+
+## Credit
+We took heavy inspiration in the https://github.com/danielaparker/JsonCons.Net repository.
\ No newline at end of file
From 6953ee1af8148380eb6dd1289a2e501948b32816 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 14:56:39 +0100
Subject: [PATCH 07/28] Add documentation and update README.md
---
docs/utilities/jmespath-functions.md | 197 ++++++++++++++++++
.../AWS.Lambda.Powertools.JMESPath/README.md | 3 +-
2 files changed, 199 insertions(+), 1 deletion(-)
create mode 100644 docs/utilities/jmespath-functions.md
diff --git a/docs/utilities/jmespath-functions.md b/docs/utilities/jmespath-functions.md
new file mode 100644
index 00000000..411b7fec
--- /dev/null
+++ b/docs/utilities/jmespath-functions.md
@@ -0,0 +1,197 @@
+---
+title: JMESPath Functions
+description: Utility
+---
+
+
+
+???+ tip
+ JMESPath is a query language for JSON used by AWS CLI, AWS Python SDK, and Powertools for AWS Lambda.
+
+Built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions.
+
+## Key features
+
+* Deserialize JSON from JSON strings, base64, and compressed data
+* Use JMESPath to extract and combine data recursively
+* Provides commonly used JMESPath expression with popular event sources
+
+## Getting started
+
+???+ tip
+ All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-dotnet/tree/develop/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs){target="_blank"}.
+
+You might have events that contains encoded JSON payloads as string, base64, or even in compressed format. It is a common use case to decode and extract them partially or fully as part of your Lambda function invocation.
+
+???+ info "Terminology"
+ **Envelope** is the terminology we use for the **JMESPath expression** to extract your JSON object from your data input. We might use those two terms interchangeably.
+
+### Extracting data
+
+You can use the `JsonTransformer.Transform` function with any [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank" rel="nofollow"}.
+
+???+ tip
+ Another common use case is to fetch deeply nested data, filter, flatten, and more.
+
+=== "Transform"
+ ```csharp hl_lines="1 2"
+ var transformer = JsonTransformer.Parse("powertools_json(body).customerId");
+ using var result = transformer.Transform(doc.RootElement);
+
+ Logger.LogInformation(result.RootElement.GetRawText()); // "dd4649e6-2484-4993-acb8-0f9123103394"
+ ```
+
+=== "Payload"
+ ```json
+ {
+ "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+ }
+ ```
+
+### Built-in envelopes
+
+We provide built-in envelopes for popular AWS Lambda event sources to easily decode and/or deserialize JSON objects.
+
+| Envelop | JMESPath expression |
+|---------------------|-----------------------------------------------------------------------------|
+| API_GATEWAY_HTTP | powertools_json(body) |
+| API_GATEWAY_REST | powertools_json(body) |
+| CLOUDWATCH_LOGS | awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*] |
+| KINESIS_DATA_STREAM | Records[*].kinesis.powertools_json(powertools_base64(data)) |
+| SNS | Records[*].Sns.Message | powertools_json(@) |
+| SQS | Records[*].powertools_json(body) |
+
+???+ tip "Using SNS?"
+ If you don't require SNS metadata, enable [raw message delivery](https://docs.aws.amazon.com/sns/latest/dg/sns-large-payload-raw-message-delivery.html). It will reduce multiple payload layers and size, when using SNS in combination with other services (_e.g., SQS, S3, etc_).
+
+## Advanced
+
+### Built-in JMESPath functions
+
+You can use our built-in JMESPath functions within your envelope expression. They handle deserialization for common data formats found in AWS Lambda event sources such as JSON strings, base64, and uncompress gzip data.
+
+#### powertools_json function
+
+Use `powertools_json` function to decode any JSON string anywhere a JMESPath expression is allowed.
+
+> **Idempotency scenario**
+
+This sample will deserialize the JSON string within the `body` key before [Idempotency](./idempotency.md){target="_blank"} processes it.
+
+=== "Idempotency utility: WithEventKeyJmesPath"
+
+ ```csharp hl_lines="4"
+ Idempotency.Configure(builder =>
+ builder
+ .WithOptions(optionsBuilder =>
+ optionsBuilder.WithEventKeyJmesPath("powertools_json(Body).[\"user_id\", \"product_id\"]"))
+ .UseDynamoDb("idempotency_table"));
+ ```
+
+=== "Payload"
+
+ ```json hl_lines="28"
+ {
+ "version": "2.0",
+ "routeKey": "ANY /createpayment",
+ "rawPath": "/createpayment",
+ "rawQueryString": "",
+ "headers": {
+ "Header1": "value1",
+ "Header2": "value2"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "apiId": "api-id",
+ "domainName": "id.execute-api.us-east-1.amazonaws.com",
+ "domainPrefix": "id",
+ "http": {
+ "method": "POST",
+ "path": "/createpayment",
+ "protocol": "HTTP/1.1",
+ "sourceIp": "ip",
+ "userAgent": "agent"
+ },
+ "requestId": "id",
+ "routeKey": "ANY /createpayment",
+ "stage": "$default",
+ "time": "10/Feb/2021:13:40:43 +0000",
+ "timeEpoch": 1612964443723
+ },
+ "body": "{\"user_id\":\"xyz\",\"product_id\":\"123456789\"}",
+ "isBase64Encoded": false
+ }
+ ```
+
+#### powertools_base64 function
+
+Use `powertools_base64` function to decode any base64 data.
+
+This sample will decode the base64 value within the `data` key, and deserialize the JSON string before validation.
+
+=== "Function"
+
+ ```csharp
+ var transformer = JsonTransformer.Parse("powertools_base64(body).customerId");
+ using var result = transformer.Transform(doc.RootElement);
+
+ Logger.LogInformation(result.RootElement.GetRawText()); // "dd4649e6-2484-4993-acb8-0f9123103394"
+ ```
+
+=== "Payload"
+
+ ```json
+ {
+ "body": "eyJjdXN0b21lcklkIjoiZGQ0NjQ5ZTYtMjQ4NC00OTkzLWFjYjgtMGY5MTIzMTAzMzk0In0=",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+ }
+ ```
+
+#### powertools_base64_gzip function
+
+Use `powertools_base64_gzip` function to decompress and decode base64 data.
+
+This sample will decompress and decode base64 data from Cloudwatch Logs, then use JMESPath pipeline expression to pass the result for decoding its JSON string.
+
+=== "Function"
+
+ ```csharp
+ var transformer = JsonTransformer.Parse("powertools_base64_gzip(body).customerId");
+ using var result = transformer.Transform(doc.RootElement);
+
+ Logger.LogInformation(result.RootElement.GetRawText()); // "dd4649e6-2484-4993-acb8-0f9123103394"
+ ```
+
+=== "Payload"
+
+ ```json
+ {
+ "body": "H4sIAAAAAAAAA6tWSi4tLsnPTS3yTFGyUkpJMTEzsUw10zUysTDRNbG0NNZNTE6y0DVIszQ0MjY0MDa2NFGqBQCMzDWgNQAAAA==",
+ "deeply_nested": [
+ {
+ "some_data": [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ]
+ }
+ ```
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
index 7f63b79f..2ec00c16 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/README.md
@@ -1,7 +1,8 @@
# Powertools JMESPath support
JMESPath is a query language for JSON used by AWS CLI, AWS Python SDK, and Powertools for AWS Lambda.
- With built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions.
+
+With built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions.
## Key features
From 1e9808c79c83378714e5767b3652e478e4971476 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 15:35:49 +0100
Subject: [PATCH 08/28] Add project properties. Prevent JMESPath project to add
Common project.
---
.../AWS.Lambda.Powertools.JMESPath.csproj | 6 +++++-
.../AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs | 1 -
libraries/src/Directory.Build.targets | 2 +-
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj b/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj
index 891af5d4..f4ce628b 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/AWS.Lambda.Powertools.JMESPath.csproj
@@ -1,7 +1,11 @@
-
+
+ AWS.Lambda.Powertools.JMESPath
+ Powertools for AWS Lambda (.NET) - JMESPath package.
+ AWS.Lambda.Powertools.JMESPath
+ AWS.Lambda.Powertools.JMESPath
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
index 62746e87..4a4695be 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs
@@ -15,5 +15,4 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")]
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.JMESPath.Tests")]
\ No newline at end of file
diff --git a/libraries/src/Directory.Build.targets b/libraries/src/Directory.Build.targets
index 20593976..5844e458 100644
--- a/libraries/src/Directory.Build.targets
+++ b/libraries/src/Directory.Build.targets
@@ -1,6 +1,6 @@
-
+
From bdc5cbfb021a76975b910e4f6a60b3314b3833bc Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 16:18:08 +0100
Subject: [PATCH 09/28] Tackle SonarCloud raised issues
---
.../BinaryOperator.cs | 164 +++++-----
.../Expression.cs | 55 ++--
.../Function.cs | 296 +++++++++---------
3 files changed, 261 insertions(+), 254 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
index 86c04554..66102648 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs
@@ -141,31 +141,35 @@ private LtOperator()
{
}
- public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
{
- if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ switch (lhs.Type)
{
- if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
- {
- result = dec1 < dec2 ? JsonConstants.True : JsonConstants.False;
- }
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ case JmesPathType.Number when rhs.Type == JmesPathType.Number:
{
- result = val1 < val2 ? JsonConstants.True : JsonConstants.False;
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 < dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 < val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+
+ break;
}
- else
- {
+ case JmesPathType.String when rhs.Type == JmesPathType.String:
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) < 0 ? JsonConstants.True : JsonConstants.False;
+ break;
+ default:
result = JsonConstants.Null;
- }
- }
- else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
- {
- result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) < 0 ? JsonConstants.True : JsonConstants.False;
- }
- else
- {
- result = JsonConstants.Null;
+ break;
}
+
return true;
}
@@ -184,31 +188,35 @@ private LteOperator()
{
}
- public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
+ public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
{
- if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ switch (lhs.Type)
{
- if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ case JmesPathType.Number when rhs.Type == JmesPathType.Number:
{
- result = dec1 <= dec2 ? JsonConstants.True : JsonConstants.False;
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 <= dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 <= val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+
+ break;
}
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
- {
- result = val1 <= val2 ? JsonConstants.True : JsonConstants.False;
- }
- else
- {
+ case JmesPathType.String when rhs.Type == JmesPathType.String:
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) <= 0 ? JsonConstants.True : JsonConstants.False;
+ break;
+ default:
result = JsonConstants.Null;
- }
- }
- else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
- {
- result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) <= 0 ? JsonConstants.True : JsonConstants.False;
- }
- else
- {
- result = JsonConstants.Null;
+ break;
}
+
return true;
}
@@ -230,29 +238,33 @@ private GtOperator()
public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
{
- if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ switch (lhs.Type)
{
- if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
- {
- result = dec1 > dec2 ? JsonConstants.True : JsonConstants.False;
- }
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ case JmesPathType.Number when rhs.Type == JmesPathType.Number:
{
- result = val1 > val2 ? JsonConstants.True : JsonConstants.False;
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 > dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 > val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+
+ break;
}
- else
- {
+ case JmesPathType.String when rhs.Type == JmesPathType.String:
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) > 0 ? JsonConstants.True : JsonConstants.False;
+ break;
+ default:
result = JsonConstants.Null;
- }
- }
- else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
- {
- result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) > 0 ? JsonConstants.True : JsonConstants.False;
- }
- else
- {
- result = JsonConstants.Null;
+ break;
}
+
return true;
}
@@ -273,29 +285,33 @@ private GteOperator()
public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result)
{
- if (lhs.Type == JmesPathType.Number && rhs.Type == JmesPathType.Number)
+ switch (lhs.Type)
{
- if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
- {
- result = dec1 >= dec2 ? JsonConstants.True : JsonConstants.False;
- }
- else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ case JmesPathType.Number when rhs.Type == JmesPathType.Number:
{
- result = val1 >= val2 ? JsonConstants.True : JsonConstants.False;
+ if (lhs.TryGetDecimal(out var dec1) && rhs.TryGetDecimal(out var dec2))
+ {
+ result = dec1 >= dec2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2))
+ {
+ result = val1 >= val2 ? JsonConstants.True : JsonConstants.False;
+ }
+ else
+ {
+ result = JsonConstants.Null;
+ }
+
+ break;
}
- else
- {
+ case JmesPathType.String when rhs.Type == JmesPathType.String:
+ result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) >= 0 ? JsonConstants.True : JsonConstants.False;
+ break;
+ default:
result = JsonConstants.Null;
- }
- }
- else if (lhs.Type == JmesPathType.String && rhs.Type == JmesPathType.String)
- {
- result = string.CompareOrdinal(lhs.GetString(), rhs.GetString()) >= 0 ? JsonConstants.True : JsonConstants.False;
- }
- else
- {
- result = JsonConstants.Null;
+ break;
}
+
return true;
}
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
index 83ad6c1c..08ce6234 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs
@@ -68,7 +68,7 @@ public abstract bool TryEvaluate(DynamicResources resources,
IValue current,
out IValue value);
- public virtual void AddExpression(IExpression expressions)
+ public virtual void AddExpression(IExpression expr)
{
}
@@ -313,25 +313,8 @@ public override bool TryEvaluate(DynamicResources resources,
{
foreach (var elem in item.EnumerateArray())
{
- if (elem.Type != JmesPathType.Null)
- {
- if (!TryApplyExpressions(resources, elem, out var val))
- {
- value = JsonConstants.Null;
- return false;
- }
- if (val.Type != JmesPathType.Null)
- {
- result.Add(val);
- }
- }
- }
- }
- else
- {
- if (item.Type != JmesPathType.Null)
- {
- if (!TryApplyExpressions(resources, item, out var val))
+ if (elem.Type == JmesPathType.Null) continue;
+ if (!TryApplyExpressions(resources, elem, out var val))
{
value = JsonConstants.Null;
return false;
@@ -342,6 +325,19 @@ public override bool TryEvaluate(DynamicResources resources,
}
}
}
+ else
+ {
+ if (item.Type == JmesPathType.Null) continue;
+ if (!TryApplyExpressions(resources, item, out var val))
+ {
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
+ }
+ }
}
value = new ArrayValue(result);
@@ -470,17 +466,16 @@ public override bool TryEvaluate(DynamicResources resources,
value = JsonConstants.Null;
return false;
}
- if (Expression.IsTrue(test))
+
+ if (!Expression.IsTrue(test)) continue;
+ if (!TryApplyExpressions(resources, item, out var val))
{
- if (!TryApplyExpressions(resources, item, out var val))
- {
- value = JsonConstants.Null;
- return false;
- }
- if (val.Type != JmesPathType.Null)
- {
- result.Add(val);
- }
+ value = JsonConstants.Null;
+ return false;
+ }
+ if (val.Type != JmesPathType.Null)
+ {
+ result.Add(val);
}
}
value = new ArrayValue(result);
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
index 4eddb7dc..5c62ef18 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
@@ -106,7 +106,7 @@ internal AbsFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -114,17 +114,17 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (arg.TryGetDecimal(out var decVal))
{
- result = new DecimalValue(decVal >= 0 ? decVal : -decVal);
+ element = new DecimalValue(decVal >= 0 ? decVal : -decVal);
return true;
}
if (arg.TryGetDouble(out var dblVal))
{
- result = new DecimalValue(dblVal >= 0 ? decVal : new decimal(-dblVal));
+ element = new DecimalValue(dblVal >= 0 ? decVal : new decimal(-dblVal));
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -141,36 +141,36 @@ internal AvgFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Array || arg0.GetArrayLength() == 0)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (!SumFunction.Instance.TryEvaluate(resources, args, out var sum))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (sum.TryGetDecimal(out var decVal))
{
- result = new DecimalValue(decVal / arg0.GetArrayLength());
+ element = new DecimalValue(decVal / arg0.GetArrayLength());
return true;
}
if (sum.TryGetDouble(out var dblVal))
{
- result = new DoubleValue(dblVal / arg0.GetArrayLength());
+ element = new DoubleValue(dblVal / arg0.GetArrayLength());
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -188,30 +188,30 @@ internal CeilFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var val = args[0];
if (val.Type != JmesPathType.Number)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (val.TryGetDecimal(out var decVal))
{
- result = new DecimalValue(decimal.Ceiling(decVal));
+ element = new DecimalValue(decimal.Ceiling(decVal));
return true;
}
if (val.TryGetDouble(out var dblVal))
{
- result = new DoubleValue(Math.Ceiling(dblVal));
+ element = new DoubleValue(Math.Ceiling(dblVal));
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -229,7 +229,7 @@ internal ContainsFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -245,18 +245,18 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (comparer.Equals(item, arg1))
{
- result = JsonConstants.True;
+ element = JsonConstants.True;
return true;
}
}
- result = JsonConstants.False;
+ element = JsonConstants.False;
return true;
case JmesPathType.String:
{
if (arg1.Type != JmesPathType.String)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -264,16 +264,16 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var s1 = arg1.GetString();
if (s0.Contains(s1))
{
- result = JsonConstants.True;
+ element = JsonConstants.True;
return true;
}
- result = JsonConstants.False;
+ element = JsonConstants.False;
return true;
}
default:
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
}
@@ -293,7 +293,7 @@ internal EndsWithFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -302,7 +302,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (arg0.Type != JmesPathType.String
|| arg1.Type != JmesPathType.String)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -311,11 +311,11 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (s0.EndsWith(s1))
{
- result = JsonConstants.True;
+ element = JsonConstants.True;
}
else
{
- result = JsonConstants.False;
+ element = JsonConstants.False;
}
return true;
@@ -335,30 +335,30 @@ internal FloorFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var val = args[0];
if (val.Type != JmesPathType.Number)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (val.TryGetDecimal(out var decVal))
{
- result = new DecimalValue(decimal.Floor(decVal));
+ element = new DecimalValue(decimal.Floor(decVal));
return true;
}
if (val.TryGetDouble(out var dblVal))
{
- result = new DoubleValue(Math.Floor(dblVal));
+ element = new DoubleValue(Math.Floor(dblVal));
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -375,7 +375,7 @@ internal JoinFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -384,7 +384,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (!(arg0.Type == JmesPathType.String && args[1].Type == JmesPathType.Array))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -394,7 +394,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (j.Type != JmesPathType.String)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -407,7 +407,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
buf.Append(sv);
}
- result = new StringValue(buf.ToString());
+ element = new StringValue(buf.ToString());
return true;
}
@@ -425,14 +425,14 @@ internal KeysFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Object)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -443,7 +443,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
values.Add(new StringValue(property.Name));
}
- result = new ArrayValue(values);
+ element = new ArrayValue(values);
return true;
}
@@ -461,7 +461,7 @@ internal LengthFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -477,21 +477,21 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
++count;
}
- result = new DecimalValue(new decimal(count));
+ element = new DecimalValue(new decimal(count));
return true;
}
case JmesPathType.Array:
- result = new DecimalValue(new decimal(arg0.GetArrayLength()));
+ element = new DecimalValue(new decimal(arg0.GetArrayLength()));
return true;
case JmesPathType.String:
{
var bytes = Encoding.UTF32.GetBytes(arg0.GetString().ToCharArray());
- result = new DecimalValue(new decimal(bytes.Length / 4));
+ element = new DecimalValue(new decimal(bytes.Length / 4));
return true;
}
default:
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
}
@@ -511,20 +511,20 @@ internal MaxFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Array)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (arg0.GetArrayLength() == 0)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -532,7 +532,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString = arg0[0].Type == JmesPathType.String;
if (!isNumber && !isString)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -543,13 +543,13 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (!(((arg0[i].Type == JmesPathType.Number) == isNumber) &&
(arg0[i].Type == JmesPathType.String) == isString))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (!greater.TryEvaluate(arg0[i], arg0[index], out var value))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -559,7 +559,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
}
}
- result = arg0[index];
+ element = arg0[index];
return true;
}
@@ -576,20 +576,20 @@ internal MaxByFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
var arg0 = args[0];
if (arg0.GetArrayLength() == 0)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return true;
}
@@ -597,7 +597,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (!expr.TryEvaluate(resources, arg0[0], out var key1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -605,7 +605,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString1 = key1.Type == JmesPathType.String;
if (!(isNumber1 || isString1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -615,7 +615,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (!expr.TryEvaluate(resources, arg0[i], out var key2))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -623,24 +623,22 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString2 = key2.Type == JmesPathType.String;
if (!(isNumber2 == isNumber1 && isString2 == isString1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (!greater.TryEvaluate(key2, key1, out var value))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
- if (value.Type == JmesPathType.True)
- {
- key1 = key2;
- index = i;
- }
+ if (value.Type != JmesPathType.True) continue;
+ key1 = key2;
+ index = i;
}
- result = arg0[index];
+ element = arg0[index];
return true;
}
@@ -658,20 +656,20 @@ internal MinFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Array)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (arg0.GetArrayLength() == 0)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -679,7 +677,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString = arg0[0].Type == JmesPathType.String;
if (!isNumber && !isString)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -690,13 +688,13 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (!(((arg0[i].Type == JmesPathType.Number) == isNumber) &&
(arg0[i].Type == JmesPathType.String) == isString))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (!less.TryEvaluate(arg0[i], arg0[index], out var value))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -706,7 +704,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
}
}
- result = arg0[index];
+ element = arg0[index];
return true;
}
@@ -723,24 +721,24 @@ internal MergeFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
if (!args.Any())
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
var arg0 = args[0];
if (arg0.Type != JmesPathType.Object)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (args.Count == 1)
{
- result = arg0;
+ element = arg0;
return true;
}
@@ -750,7 +748,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var argi = args[i];
if (argi.Type != JmesPathType.Object)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -764,7 +762,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
}
}
- result = new ObjectValue(dict);
+ element = new ObjectValue(dict);
return true;
}
@@ -781,16 +779,16 @@ internal NotNullFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
foreach (var arg in args)
{
if (arg.Type == JmesPathType.Null) continue;
- result = arg;
+ element = arg;
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return true;
}
@@ -807,7 +805,7 @@ internal ReverseFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -816,7 +814,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
case JmesPathType.String:
{
- result = new StringValue(string.Join("", GraphemeClusters(arg0.GetString()).Reverse().ToArray()));
+ element = new StringValue(string.Join("", GraphemeClusters(arg0.GetString()).Reverse().ToArray()));
return true;
}
case JmesPathType.Array:
@@ -827,11 +825,11 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
list.Add(arg0[i]);
}
- result = new ArrayValue(list);
+ element = new ArrayValue(list);
return true;
}
default:
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
}
@@ -858,13 +856,13 @@ internal MapFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
if (!(args[0].Type == JmesPathType.Expression && args[1].Type == JmesPathType.Array))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -877,14 +875,14 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (!expr.TryEvaluate(resources, item, out var val))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
list.Add(val);
}
- result = new ArrayValue(list);
+ element = new ArrayValue(list);
return true;
}
@@ -901,20 +899,20 @@ internal MinByFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
var arg0 = args[0];
if (arg0.GetArrayLength() == 0)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return true;
}
@@ -922,7 +920,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (!expr.TryEvaluate(resources, arg0[0], out var key1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -930,7 +928,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString1 = key1.Type == JmesPathType.String;
if (!(isNumber1 || isString1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -940,7 +938,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (!expr.TryEvaluate(resources, arg0[i], out var key2))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -948,13 +946,13 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString2 = key2.Type == JmesPathType.String;
if (!(isNumber2 == isNumber1 && isString2 == isString1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (!lessor.TryEvaluate(key2, key1, out var value))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -965,7 +963,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
}
}
- result = arg0[index];
+ element = arg0[index];
return true;
}
@@ -982,20 +980,20 @@ internal SortFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Array)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
if (arg0.GetArrayLength() <= 1)
{
- result = arg0;
+ element = arg0;
return true;
}
@@ -1003,7 +1001,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString1 = arg0[0].Type == JmesPathType.String;
if (!isNumber1 && !isString1)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -1016,7 +1014,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
var isString2 = item.Type == JmesPathType.String;
if (!(isNumber2 == isNumber1 && isString2 == isString1))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -1024,7 +1022,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
}
list.Sort(comparer);
- result = new ArrayValue(list);
+ element = new ArrayValue(list);
return true;
}
@@ -1041,20 +1039,20 @@ internal SortByFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
var arg0 = args[0];
if (arg0.GetArrayLength() <= 1)
{
- result = arg0;
+ element = arg0;
return true;
}
@@ -1070,11 +1068,11 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
list.Sort(comparer);
if (comparer.IsValid)
{
- result = new ArrayValue(list);
+ element = new ArrayValue(list);
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -1092,7 +1090,7 @@ internal StartsWithFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -1101,13 +1099,13 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (arg0.Type != JmesPathType.String
|| arg1.Type != JmesPathType.String)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
var s0 = arg0.GetString();
var s1 = arg1.GetString();
- result = s0.StartsWith(s1) ? JsonConstants.True : JsonConstants.False;
+ element = s0.StartsWith(s1) ? JsonConstants.True : JsonConstants.False;
return true;
}
@@ -1128,14 +1126,14 @@ internal SumFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Array)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -1143,7 +1141,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (item.Type != JmesPathType.Number)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
}
@@ -1163,7 +1161,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
if (success)
{
- result = new DecimalValue(decSum);
+ element = new DecimalValue(decSum);
return true;
}
@@ -1172,14 +1170,14 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
{
if (!item.TryGetDouble(out var dbl))
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
dblSum += dbl;
}
- result = new DoubleValue(dblSum);
+ element = new DoubleValue(dblSum);
return true;
}
@@ -1196,19 +1194,19 @@ internal ToArrayFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type == JmesPathType.Array)
{
- result = arg0;
+ element = arg0;
return true;
}
var list = new List { arg0 };
- result = new ArrayValue(list);
+ element = new ArrayValue(list);
return true;
}
@@ -1226,7 +1224,7 @@ internal ToNumberFunction()
}
public override bool TryEvaluate(DynamicResources resources, IList args,
- out IValue result)
+ out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -1234,28 +1232,28 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
switch (arg0.Type)
{
case JmesPathType.Number:
- result = arg0;
+ element = arg0;
return true;
case JmesPathType.String:
{
var s = arg0.GetString();
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dec))
{
- result = new DecimalValue(dec);
+ element = new DecimalValue(dec);
return true;
}
if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dbl))
{
- result = new DoubleValue(dbl);
+ element = new DoubleValue(dbl);
return true;
}
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
default:
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
}
@@ -1273,13 +1271,13 @@ internal ToStringFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
if (args[0].Type == JmesPathType.Expression)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -1287,13 +1285,13 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
switch (arg0.Type)
{
case JmesPathType.String:
- result = arg0;
+ element = arg0;
return true;
case JmesPathType.Expression:
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
default:
- result = new StringValue(arg0.ToString());
+ element = new StringValue(arg0.ToString());
return true;
}
}
@@ -1311,14 +1309,14 @@ internal ValuesFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var arg0 = args[0];
if (arg0.Type != JmesPathType.Object)
{
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
@@ -1329,7 +1327,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
list.Add(item.Value);
}
- result = new ArrayValue(list);
+ element = new ArrayValue(list);
return true;
}
@@ -1346,7 +1344,7 @@ internal TypeFunction()
{
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -1355,26 +1353,26 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
switch (arg0.Type)
{
case JmesPathType.Number:
- result = new StringValue("number");
+ element = new StringValue("number");
return true;
case JmesPathType.True:
case JmesPathType.False:
- result = new StringValue("boolean");
+ element = new StringValue("boolean");
return true;
case JmesPathType.String:
- result = new StringValue("string");
+ element = new StringValue("string");
return true;
case JmesPathType.Object:
- result = new StringValue("object");
+ element = new StringValue("object");
return true;
case JmesPathType.Array:
- result = new StringValue("array");
+ element = new StringValue("array");
return true;
case JmesPathType.Null:
- result = new StringValue("null");
+ element = new StringValue("null");
return true;
default:
- result = JsonConstants.Null;
+ element = JsonConstants.Null;
return false;
}
}
@@ -1398,12 +1396,10 @@ public override string ToString()
return "powertools_json";
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
- result = args[0];
-
- //result = new JsonElementValue(JsonNode.Parse(args[0].GetString()).Deserialize());
+ element = args[0];
return true;
}
}
@@ -1421,12 +1417,12 @@ public override string ToString()
return "powertools_base64";
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
var base64StringBytes = Convert.FromBase64String(args[0].GetString());
var doc = JsonDocument.Parse(base64StringBytes);
- result = new JsonElementValue(doc.RootElement);
+ element = new JsonElementValue(doc.RootElement);
return true;
}
}
@@ -1444,7 +1440,7 @@ public override string ToString()
return "powertools_base64_gzip";
}
- public override bool TryEvaluate(DynamicResources resources, IList args, out IValue result)
+ public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element)
{
Debug.Assert(Arity.HasValue && args.Count == Arity!.Value);
@@ -1458,7 +1454,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
}
var doc = JsonDocument.Parse(Encoding.UTF8.GetString(decompressedStream.ToArray()));
- result = new JsonElementValue(doc.RootElement);
+ element = new JsonElementValue(doc.RootElement);
return true;
}
From 4b5c6fdd4c3ff4020d175f7efae9996f4d196797 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 16:58:54 +0100
Subject: [PATCH 10/28] more Sonar fixes
---
.../Utilities/JsonPointerExtensions.cs | 355 +++++++-----------
1 file changed, 137 insertions(+), 218 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
index 0f7fa997..8926f510 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs
@@ -24,35 +24,36 @@ private static bool TryResolve(string token, JsonDocumentBuilder current, out Js
{
result = current;
- if (result.ValueKind == JsonValueKind.Array)
+ switch (result.ValueKind)
{
- if (token == "-")
- {
+ case JsonValueKind.Array when token == "-":
return false;
- }
-
- if (!int.TryParse(token, out var index))
+ case JsonValueKind.Array:
{
- return false;
- }
+ if (!int.TryParse(token, out var index))
+ {
+ return false;
+ }
- if (index >= result.GetArrayLength())
- {
- return false;
- }
+ if (index >= result.GetArrayLength())
+ {
+ return false;
+ }
- result = result[index];
- }
- else if (result.ValueKind == JsonValueKind.Object)
- {
- if (!result.TryGetProperty(token, out result))
+ result = result[index];
+ break;
+ }
+ case JsonValueKind.Object:
{
- return false;
+ if (!result.TryGetProperty(token, out result))
+ {
+ return false;
+ }
+
+ break;
}
- }
- else
- {
- return false;
+ default:
+ return false;
}
return true;
@@ -60,20 +61,17 @@ private static bool TryResolve(string token, JsonDocumentBuilder current, out Js
public static JsonPointer ToDefinitePath(this JsonPointer pointer, JsonDocumentBuilder value)
{
- if (value.ValueKind == JsonValueKind.Array && pointer.Tokens.Count > 0 &&
- pointer.Tokens[pointer.Tokens.Count - 1] == "-")
+ if (value.ValueKind != JsonValueKind.Array || pointer.Tokens.Count <= 0 ||
+ pointer.Tokens[pointer.Tokens.Count - 1] != "-") return pointer;
+ var tokens = new List();
+ for (var i = 0; i < pointer.Tokens.Count - 1; ++i)
{
- var tokens = new List();
- for (var i = 0; i < pointer.Tokens.Count - 1; ++i)
- {
- tokens.Add(pointer.Tokens[i]);
- }
-
- tokens.Add(value.GetArrayLength().ToString());
- return new JsonPointer(tokens);
+ tokens.Add(pointer.Tokens[i]);
}
- return pointer;
+ tokens.Add(value.GetArrayLength().ToString());
+ return new JsonPointer(tokens);
+
}
public static bool TryGetValue(this JsonPointer pointer, JsonDocumentBuilder root,
@@ -96,84 +94,41 @@ public static bool TryAdd(this JsonPointer location,
ref JsonDocumentBuilder root,
JsonDocumentBuilder value)
{
- var current = root;
- var token = "";
+ if (!TryGetToken(location, root, out var current, out var token)) return false;
- using var enumerator = location.GetEnumerator();
- var more = enumerator.MoveNext();
- if (!more)
+ switch (current.ValueKind)
{
- return false;
- }
-
- while (more)
- {
- token = enumerator.Current;
- more = enumerator.MoveNext();
- if (more)
+ case JsonValueKind.Array when token.Length == 1 && token[0] == '-':
+ current.AddArrayItem(value);
+ break;
+ case JsonValueKind.Array:
{
- if (!TryResolve(token, current, out current))
- {
- return false;
- }
- }
- }
+ if (!TryGetArray(value, token, current)) return false;
- if (current.ValueKind == JsonValueKind.Array)
- {
- if (token.Length == 1 && token[0] == '-')
- {
- current.AddArrayItem(value);
- current = current[current.GetArrayLength() - 1];
+ break;
}
- else
+ case JsonValueKind.Object:
{
- if (!int.TryParse(token, out var index))
+ if (current.ContainsPropertyName(token))
{
- return false;
- }
-
- if (index > current.GetArrayLength())
- {
- return false;
+ current.RemoveProperty(token);
}
- if (index == current.GetArrayLength())
- {
- current.AddArrayItem(value);
- current = value;
- }
- else
- {
- current.InsertArrayItem(index, value);
- current = value;
- }
+ current.AddProperty(token, value);
+ break;
}
- }
- else if (current.ValueKind == JsonValueKind.Object)
- {
- if (current.ContainsPropertyName(token))
- {
- current.RemoveProperty(token);
- }
-
- current.AddProperty(token, value);
- current = value;
- }
- else
- {
- return false;
+ default:
+ return false;
}
return true;
}
- public static bool TryAddIfAbsent(this JsonPointer location,
- ref JsonDocumentBuilder root,
- JsonDocumentBuilder value)
+ private static bool TryGetToken(JsonPointer location, JsonDocumentBuilder root, out JsonDocumentBuilder current,
+ out string token)
{
- var current = root;
- var token = "";
+ current = root;
+ token = "";
using var enumerator = location.GetEnumerator();
var more = enumerator.MoveNext();
@@ -186,59 +141,64 @@ public static bool TryAddIfAbsent(this JsonPointer location,
{
token = enumerator.Current;
more = enumerator.MoveNext();
- if (more)
+ if (!more) continue;
+ if (!TryResolve(token, current, out current))
{
- if (!TryResolve(token, current, out current))
- {
- return false;
- }
+ return false;
}
}
- if (current.ValueKind == JsonValueKind.Array)
+ return true;
+ }
+
+ public static bool TryAddIfAbsent(this JsonPointer location,
+ ref JsonDocumentBuilder root,
+ JsonDocumentBuilder value)
+ {
+ if (!TryGetToken(location, root, out var current, out var token)) return false;
+
+ switch (current.ValueKind)
{
- if (token.Length == 1 && token[0] == '-')
- {
+ case JsonValueKind.Array when token.Length == 1 && token[0] == '-':
current.AddArrayItem(value);
- current = current[current.GetArrayLength() - 1];
- }
- else
+ break;
+ case JsonValueKind.Array:
{
- if (!int.TryParse(token, out var index))
- {
- return false;
- }
+ if (!TryGetArray(value, token, current)) return false;
- if (index > current.GetArrayLength())
- {
- return false;
- }
-
- if (index == current.GetArrayLength())
- {
- current.AddArrayItem(value);
- current = value;
- }
- else
- {
- current.InsertArrayItem(index, value);
- current = value;
- }
+ break;
}
+ case JsonValueKind.Object when current.ContainsPropertyName(token):
+ return false;
+ case JsonValueKind.Object:
+ current.AddProperty(token, value);
+ break;
+ default:
+ return false;
}
- else if (current.ValueKind == JsonValueKind.Object)
+
+ return true;
+ }
+
+ private static bool TryGetArray(JsonDocumentBuilder value, string token, JsonDocumentBuilder current)
+ {
+ if (!int.TryParse(token, out var index))
{
- if (current.ContainsPropertyName(token))
- {
- return false;
- }
+ return false;
+ }
+
+ if (index > current.GetArrayLength())
+ {
+ return false;
+ }
- current.AddProperty(token, value);
- current = value;
+ if (index == current.GetArrayLength())
+ {
+ current.AddArrayItem(value);
}
else
{
- return false;
+ current.InsertArrayItem(index, value);
}
return true;
@@ -247,58 +207,38 @@ public static bool TryAddIfAbsent(this JsonPointer location,
public static bool TryRemove(this JsonPointer location,
ref JsonDocumentBuilder root)
{
- var current = root;
- var token = "";
-
- using var enumerator = location.GetEnumerator();
- var more = enumerator.MoveNext();
- if (!more)
- {
- return false;
- }
+ if (!TryGetToken(location, root, out var current, out var token)) return false;
- while (more)
+ switch (current.ValueKind)
{
- token = enumerator.Current;
- more = enumerator.MoveNext();
- if (more)
+ case JsonValueKind.Array when token.Length == 1 && token[0] == '-':
+ return false;
+ case JsonValueKind.Array:
{
- if (!TryResolve(token, current, out current))
+ if (!int.TryParse(token, out var index))
{
return false;
}
- }
- }
- if (current.ValueKind == JsonValueKind.Array)
- {
- if (token.Length == 1 && token[0] == '-')
- {
- return false;
- }
+ if (index >= current.GetArrayLength())
+ {
+ return false;
+ }
- if (!int.TryParse(token, out var index))
- {
- return false;
+ current.RemoveArrayItemAt(index);
+ break;
}
-
- if (index >= current.GetArrayLength())
+ case JsonValueKind.Object:
{
- return false;
- }
+ if (current.ContainsPropertyName(token))
+ {
+ current.RemoveProperty(token);
+ }
- current.RemoveArrayItemAt(index);
- }
- else if (current.ValueKind == JsonValueKind.Object)
- {
- if (current.ContainsPropertyName(token))
- {
- current.RemoveProperty(token);
+ break;
}
- }
- else
- {
- return false;
+ default:
+ return false;
}
return true;
@@ -308,64 +248,43 @@ public static bool TryReplace(this JsonPointer location,
ref JsonDocumentBuilder root,
JsonDocumentBuilder value)
{
- var current = root;
- var token = "";
+ if (!TryGetToken(location, root, out var current, out var token)) return false;
- using var enumerator = location.GetEnumerator();
- var more = enumerator.MoveNext();
- if (!more)
+ switch (current.ValueKind)
{
- return false;
- }
-
- while (more)
- {
- token = enumerator.Current;
- more = enumerator.MoveNext();
- if (more)
+ case JsonValueKind.Array when token.Length == 1 && token[0] == '-':
+ return false;
+ case JsonValueKind.Array:
{
- if (!TryResolve(token, current, out current))
+ if (!int.TryParse(token, out var index))
{
return false;
}
- }
- }
- if (current.ValueKind == JsonValueKind.Array)
- {
- if (token.Length == 1 && token[0] == '-')
- {
- return false;
- }
+ if (index >= current.GetArrayLength())
+ {
+ return false;
+ }
- if (!int.TryParse(token, out var index))
- {
- return false;
+ current[index] = value;
+ break;
}
-
- if (index >= current.GetArrayLength())
+ case JsonValueKind.Object:
{
- return false;
- }
+ if (current.ContainsPropertyName(token))
+ {
+ current.RemoveProperty(token);
+ }
+ else
+ {
+ return false;
+ }
- current[index] = value;
- }
- else if (current.ValueKind == JsonValueKind.Object)
- {
- if (current.ContainsPropertyName(token))
- {
- current.RemoveProperty(token);
+ current.AddProperty(token, value);
+ break;
}
- else
- {
+ default:
return false;
- }
-
- current.AddProperty(token, value);
- }
- else
- {
- return false;
}
return true;
From 5927c52d731b01b6f4dc6a2b0ba452a4b8e463dd Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 18:37:29 +0100
Subject: [PATCH 11/28] more sonarcloud
---
.../Function.cs | 11 ++----
.../JmesPathParser.cs | 37 +++++--------------
2 files changed, 13 insertions(+), 35 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
index 5c62ef18..294633a1 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs
@@ -89,7 +89,7 @@ internal interface IFunction
internal abstract class BaseFunction : IFunction
{
- internal BaseFunction(int? argCount)
+ private protected BaseFunction(int? argCount)
{
Arity = argCount;
}
@@ -241,13 +241,10 @@ public override bool TryEvaluate(DynamicResources resources, IList args,
switch (arg0.Type)
{
case JmesPathType.Array:
- foreach (var item in arg0.EnumerateArray())
+ if (arg0.EnumerateArray().Any(item => comparer.Equals(item, arg1)))
{
- if (comparer.Equals(item, arg1))
- {
- element = JsonConstants.True;
- return true;
- }
+ element = JsonConstants.True;
+ return true;
}
element = JsonConstants.False;
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
index e8bde3c6..43de7f04 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs
@@ -111,18 +111,16 @@ internal enum JmesPathState
internal ref struct JmesPathParser
{
- private ReadOnlyMemory _source;
- private ReadOnlySpan _span;
+ private readonly ReadOnlySpan _span;
private int _index;
private int _column;
private int _line;
- private Stack _stateStack;
- private Stack_outputStack;
- private Stack_operatorStack;
+ private readonly Stack _stateStack;
+ private readonly Stack_outputStack;
+ private readonly Stack_operatorStack;
internal JmesPathParser(string input)
{
- _source = input.AsMemory();
_span = input.AsSpan();
_index = 0;
_column = 1;
@@ -155,8 +153,6 @@ internal JsonTransformer Parse()
{
switch (_stateStack.Peek())
{
- default:
- break;
case JmesPathState.Start:
{
_stateStack.Pop();
@@ -487,8 +483,6 @@ internal JsonTransformer Parse()
++_column;
break;
}
- default:
- break;
}
break;
@@ -972,7 +966,7 @@ internal JsonTransformer Parse()
var s = buffer.ToString();
if (!int.TryParse(s, out var n))
{
- n = s.StartsWith("-") ? int.MinValue : int.MaxValue;
+ n = s.StartsWith('-') ? int.MinValue : int.MaxValue;
}
sliceStart = n;
buffer.Clear();
@@ -994,7 +988,7 @@ internal JsonTransformer Parse()
var s = buffer.ToString();
if (!int.TryParse(s, out var n))
{
- n = s.StartsWith("-") ? int.MinValue : int.MaxValue;
+ n = s.StartsWith('-') ? int.MinValue : int.MaxValue;
}
sliceStop = n;
buffer.Clear();
@@ -1437,8 +1431,6 @@ private void SkipWhiteSpace()
_column = 1;
++_index;
break;
- default:
- break;
}
}
@@ -1704,15 +1696,9 @@ private void PushToken(Token token)
_operatorStack.Push(new Token(TokenType.LeftParen));
break;
}
- case TokenType.BeginFilter:
- _outputStack.Push(token);
- _operatorStack.Push(new Token(TokenType.LeftParen));
- break;
- case TokenType.BeginMultiSelectList:
- _outputStack.Push(token);
- _operatorStack.Push(new Token(TokenType.LeftParen));
- break;
case TokenType.BeginMultiSelectHash:
+ case TokenType.BeginMultiSelectList:
+ case TokenType.BeginFilter:
_outputStack.Push(token);
_operatorStack.Push(new Token(TokenType.LeftParen));
break;
@@ -1722,8 +1708,6 @@ private void PushToken(Token token)
_operatorStack.Push(new Token(TokenType.LeftParen));
break;
case TokenType.CurrentNode:
- _outputStack.Push(token);
- break;
case TokenType.Key:
case TokenType.Pipe:
case TokenType.Argument:
@@ -1733,12 +1717,9 @@ private void PushToken(Token token)
case TokenType.LeftParen:
_operatorStack.Push(token);
break;
- default:
- break;
}
}
-
-
+
private uint AppendToCodepoint(uint cp, uint c)
{
cp *= 16;
From 11202d4bc67b2bb87abe288a3626d2419a7ee660 Mon Sep 17 00:00:00 2001
From: Henrique Graca <999396+hjgraca@users.noreply.github.com>
Date: Thu, 18 Apr 2024 19:13:52 +0100
Subject: [PATCH 12/28] sonarcloud fixes
---
.../AWS.Lambda.Powertools.JMESPath/Value.cs | 198 +++++++++++++-----
1 file changed, 140 insertions(+), 58 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
index e0fae410..b6e17a8d 100644
--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
+++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs
@@ -43,20 +43,20 @@ internal interface IObjectValueEnumerator : IEnumerator, IEnumera
internal enum JmesPathType
{
- Null,
+ Null,
Array,
- False,
- Number,
- Object,
- String,
- True,
+ False,
+ Number,
+ Object,
+ String,
+ True,
Expression
}
- internal interface IValue
+ internal interface IValue
{
- JmesPathType Type {get;}
- IValue this[int index] {get;}
+ JmesPathType Type { get; }
+ IValue this[int index] { get; }
int GetArrayLength();
string GetString();
bool TryGetDecimal(out decimal value);
@@ -83,9 +83,25 @@ public bool MoveNext()
return _enumerator.MoveNext();
}
- public void Reset() { _enumerator.Reset(); }
+ public void Reset()
+ {
+ _enumerator.Reset();
+ }
- void IDisposable.Dispose() { _enumerator.Dispose();}
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ // Cleanup
+ if (disposing)
+ {
+ _enumerator.Dispose();
+ }
+ }
public IValue Current => new JsonElementValue(_enumerator.Current);
@@ -98,7 +114,7 @@ public IEnumerator GetEnumerator()
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
- return GetEnumerator();
+ return GetEnumerator();
}
}
@@ -116,11 +132,28 @@ public bool MoveNext()
return _enumerator.MoveNext();
}
- public void Reset() { _enumerator.Reset(); }
+ public void Reset()
+ {
+ _enumerator.Reset();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- void IDisposable.Dispose() { _enumerator.Dispose();}
+ protected virtual void Dispose(bool disposing)
+ {
+ // Cleanup
+ if (disposing)
+ {
+ _enumerator.Dispose();
+ }
+ }
- public NameValuePair Current => new(_enumerator.Current.Name, new JsonElementValue(_enumerator.Current.Value));
+ public NameValuePair Current =>
+ new(_enumerator.Current.Name, new JsonElementValue(_enumerator.Current.Value));
object System.Collections.IEnumerator.Current => Current;
@@ -131,7 +164,7 @@ public IEnumerator GetEnumerator()
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
- return GetEnumerator();
+ return GetEnumerator();
}
}
@@ -142,7 +175,7 @@ internal JsonElementValue(JsonElement element)
_element = element;
}
- public JmesPathType Type
+ public JmesPathType Type
{
get
{
@@ -168,7 +201,10 @@ public JmesPathType Type
public IValue this[int index] => new JsonElementValue(_element[index]);
- public int GetArrayLength() {return _element.GetArrayLength();}
+ public int GetArrayLength()
+ {
+ return _element.GetArrayLength();
+ }
public string GetString()
{
@@ -188,11 +224,11 @@ public bool TryGetDouble(out double value)
public bool TryGetProperty(string propertyName, out IValue property)
{
var r = _element.TryGetProperty(propertyName, out var prop);
-
- property = prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString()) ?
- new JsonElementValue(JsonNode.Parse(prop.GetString() ?? string.Empty).Deserialize()) :
- new JsonElementValue(prop);
-
+
+ property = prop.ValueKind == JsonValueKind.String && IsJsonValid(prop.GetString())
+ ? new JsonElementValue(JsonNode.Parse(prop.GetString() ?? string.Empty).Deserialize())
+ : new JsonElementValue(prop);
+
return r;
}
@@ -225,7 +261,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -247,7 +283,10 @@ internal DoubleValue(double value)
public IValue this[int index] => throw new InvalidOperationException();
- public int GetArrayLength() { throw new InvalidOperationException(); }
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
public string GetString()
{
@@ -256,7 +295,8 @@ public string GetString()
public bool TryGetDecimal(out decimal value)
{
- if (!(double.IsNaN(_value) || double.IsInfinity(_value)) && _value is >= (double)decimal.MinValue and <= (double)decimal.MaxValue)
+ if (!(double.IsNaN(_value) || double.IsInfinity(_value)) &&
+ _value is >= (double)decimal.MinValue and <= (double)decimal.MaxValue)
{
value = decimal.MinValue;
return false;
@@ -290,7 +330,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -312,7 +352,10 @@ internal DecimalValue(decimal value)
public IValue this[int index] => throw new InvalidOperationException();
- public int GetArrayLength() { throw new InvalidOperationException(); }
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
public string GetString()
{
@@ -349,7 +392,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -371,7 +414,10 @@ internal StringValue(string value)
public IValue this[int index] => throw new InvalidOperationException();
- public int GetArrayLength() { throw new InvalidOperationException(); }
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
public string GetString()
{
@@ -406,7 +452,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -421,9 +467,15 @@ public override string ToString()
public IValue this[int index] => throw new InvalidOperationException();
- public int GetArrayLength() { throw new InvalidOperationException(); }
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
- public string GetString() { throw new InvalidOperationException(); }
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
public bool TryGetDecimal(out decimal value)
{
@@ -453,7 +505,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -467,9 +519,15 @@ public override string ToString()
public IValue this[int index] => throw new InvalidOperationException();
- public int GetArrayLength() { throw new InvalidOperationException(); }
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
- public string GetString() { throw new InvalidOperationException(); }
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
public bool TryGetDecimal(out decimal value)
{
@@ -499,7 +557,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -513,9 +571,15 @@ public override string ToString()
public IValue this[int index] => throw new InvalidOperationException();
- public int GetArrayLength() { throw new InvalidOperationException(); }
+ public int GetArrayLength()
+ {
+ throw new InvalidOperationException();
+ }
- public string GetString() { throw new InvalidOperationException(); }
+ public string GetString()
+ {
+ throw new InvalidOperationException();
+ }
public bool TryGetDecimal(out decimal value)
{
@@ -545,7 +609,7 @@ public IObjectValueEnumerator EnumerateObject()
public IExpression GetExpression()
{
throw new InvalidOperationException("Not an expression");
- }
+ }
public override string ToString()
{
@@ -586,7 +650,7 @@ public IEnumerator