From 6196292a8e21087247b6c1680780ef601689c795 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:18:07 +0000 Subject: [PATCH 01/82] Update batch examples Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj index 0589010c..ddbd7f4e 100644 --- a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj +++ b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj @@ -7,8 +7,8 @@ - - + + From 21ea1f4357c1e4a0ce8a6e2dd92298e822eccce9 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:19:55 +0000 Subject: [PATCH 02/82] Update idempotency Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Idempotency/src/HelloWorld/HelloWorld.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj index 1b62c992..645f82af 100644 --- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj @@ -8,7 +8,7 @@ - - + + From 7381be500da01e8c4c56e82c4a35cfb1704a7fef Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:20:22 +0000 Subject: [PATCH 03/82] Update Logging Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Logging/src/HelloWorld/HelloWorld.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Logging/src/HelloWorld/HelloWorld.csproj b/examples/Logging/src/HelloWorld/HelloWorld.csproj index 53323ac8..57b2016b 100644 --- a/examples/Logging/src/HelloWorld/HelloWorld.csproj +++ b/examples/Logging/src/HelloWorld/HelloWorld.csproj @@ -8,7 +8,7 @@ - + From 2c18aec9168aaae8119d6bd7a8001e9563d9aab0 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:20:48 +0000 Subject: [PATCH 04/82] Update Metrics Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Metrics/src/HelloWorld/HelloWorld.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Metrics/src/HelloWorld/HelloWorld.csproj b/examples/Metrics/src/HelloWorld/HelloWorld.csproj index b3262af2..a3bd7722 100644 --- a/examples/Metrics/src/HelloWorld/HelloWorld.csproj +++ b/examples/Metrics/src/HelloWorld/HelloWorld.csproj @@ -8,8 +8,8 @@ - - + + From 726033d8df348420ade9254f46f908d76c39a22a Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:21:09 +0000 Subject: [PATCH 05/82] Update HelloWorld.csproj Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Parameters/src/HelloWorld/HelloWorld.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Parameters/src/HelloWorld/HelloWorld.csproj b/examples/Parameters/src/HelloWorld/HelloWorld.csproj index d9709c68..8d258c52 100644 --- a/examples/Parameters/src/HelloWorld/HelloWorld.csproj +++ b/examples/Parameters/src/HelloWorld/HelloWorld.csproj @@ -8,6 +8,6 @@ - + From ba11561a3888e6ebac8774451196b6ca8df80616 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:21:49 +0000 Subject: [PATCH 06/82] Update Api example Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj index 3609e442..e3ad28df 100644 --- a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj +++ b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj @@ -13,8 +13,8 @@ - - - + + + From 3ffc13992998b7251ff69ca7e6c43b3afb0201bb Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:22:17 +0000 Subject: [PATCH 07/82] Update HelloWorld.csproj Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Tracing/src/HelloWorld/HelloWorld.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Tracing/src/HelloWorld/HelloWorld.csproj b/examples/Tracing/src/HelloWorld/HelloWorld.csproj index 32beb88b..e7338a4a 100644 --- a/examples/Tracing/src/HelloWorld/HelloWorld.csproj +++ b/examples/Tracing/src/HelloWorld/HelloWorld.csproj @@ -8,8 +8,8 @@ - - + + From db8bac8ff924479fbf61c679bbc748d1923698d6 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sat, 17 Feb 2024 20:05:26 +0000 Subject: [PATCH 08/82] first try to AOT for Logging --- libraries/AWS.Lambda.Powertools.sln | 15 ++ ...S.Lambda.Powertools.BatchProcessing.csproj | 1 + .../AWS.Lambda.Powertools.Common.csproj | 1 + .../Aspects/UniversalWrapperAspect.cs | 40 ++--- .../Core/PowertoolsSourceGenerationContext.cs | 43 +++++ .../AWS.Lambda.Powertools.Logging.csproj | 4 + .../Internal/Converters/ExceptionConverter.cs | 6 +- .../Internal/LoggingAspectHandler.cs | 27 +++- .../Internal/PowertoolsLambdaContext.cs | 151 ++++++++++++++++++ .../Internal/PowertoolsLogger.cs | 86 +++++++--- .../LogEntryLambdaContext.cs | 22 ++- .../AWS.Lambda.Powertools.Metrics.csproj | 2 + .../Internal}/PowertoolsLambdaContext.cs | 112 +++++++------ .../AWS.Lambda.Powertools.Parameters.csproj | 1 + .../AWS.Lambda.Powertools.Tracing.csproj | 1 + libraries/src/Directory.Build.props | 2 + libraries/src/Directory.Packages.props | 4 +- ....Lambda.Powertools.AotCompatibility.csproj | 43 +++++ .../GlobalUsings.cs | 1 + .../Handlers/Handler.cs | 37 +++++ .../UnitTest1.cs | 28 ++++ ...AWS.Lambda.Powertools.Logging.Tests.csproj | 1 + .../Handlers/ExceptionFunctionHandler.cs | 2 +- .../LogFormatterTest.cs | 1 + .../PowertoolsLambdaContextTest.cs | 31 ++-- .../PowertoolsLoggerTest.cs | 3 +- 26 files changed, 549 insertions(+), 116 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLambdaContext.cs rename libraries/src/{AWS.Lambda.Powertools.Common/Core => AWS.Lambda.Powertools.Metrics/Internal}/PowertoolsLambdaContext.cs (50%) create mode 100644 libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj create mode 100644 libraries/tests/AWS.Lambda.Powertools.AotCompatibility/GlobalUsings.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs rename libraries/tests/{AWS.Lambda.Powertools.Common.Tests/Core => AWS.Lambda.Powertools.Logging.Tests}/PowertoolsLambdaContextTest.cs (78%) diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index 62c1cec1..d3ed829b 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -35,6 +35,8 @@ 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.AotCompatibility", "tests\AWS.Lambda.Powertools.AotCompatibility\AWS.Lambda.Powertools.AotCompatibility.csproj", "{2B782D22-675D-42D4-A041-90A27275A403}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -216,6 +218,18 @@ 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 + {2B782D22-675D-42D4-A041-90A27275A403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Debug|x64.Build.0 = Debug|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Debug|x86.Build.0 = Debug|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Release|Any CPU.Build.0 = Release|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Release|x64.ActiveCfg = Release|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Release|x64.Build.0 = Release|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Release|x86.ActiveCfg = Release|Any CPU + {2B782D22-675D-42D4-A041-90A27275A403}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution @@ -233,5 +247,6 @@ 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} + {2B782D22-675D-42D4-A041-90A27275A403} = {1CFF5568-8486-475F-81F6-06105C437528} EndGlobalSection EndGlobal diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj index ed58f6f6..c766cac7 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj @@ -13,5 +13,6 @@ + diff --git a/libraries/src/AWS.Lambda.Powertools.Common/AWS.Lambda.Powertools.Common.csproj b/libraries/src/AWS.Lambda.Powertools.Common/AWS.Lambda.Powertools.Common.csproj index 9d3e6682..3084c61f 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/AWS.Lambda.Powertools.Common.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Common/AWS.Lambda.Powertools.Common.csproj @@ -12,6 +12,7 @@ + diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs index fedf4503..d076909f 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs @@ -1,12 +1,12 @@ /* * 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 @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -32,19 +33,19 @@ public class UniversalWrapperAspect /// /// The delegate cache /// - private static readonly Dictionary _delegateCache = new(); + private static readonly Dictionary DelegateCache = new(); /// /// The asynchronous generic handler /// - private static readonly MethodInfo _asyncGenericHandler = + private static readonly MethodInfo AsyncGenericHandler = typeof(UniversalWrapperAttribute).GetMethod(nameof(UniversalWrapperAttribute.WrapAsync), BindingFlags.NonPublic | BindingFlags.Instance); /// /// The synchronize generic handler /// - private static readonly MethodInfo _syncGenericHandler = + private static readonly MethodInfo SyncGenericHandler = typeof(UniversalWrapperAttribute).GetMethod(nameof(UniversalWrapperAttribute.WrapSync), BindingFlags.NonPublic | BindingFlags.Instance); @@ -93,6 +94,7 @@ public object Handle( /// Type of the return. /// The wrappers. /// Handler. + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] private static Handler CreateMethodHandler(Type returnType, IEnumerable wrappers) { var targetParam = Expression.Parameter(typeof(Func), "orig"); @@ -106,13 +108,13 @@ private static Handler CreateMethodHandler(Type returnType, IEnumerable).MakeGenericType(taskType); - wrapperMethod = _asyncGenericHandler.MakeGenericMethod(taskType); + wrapperMethod = AsyncGenericHandler.MakeGenericMethod(taskType); } else { if (returnType == typeof(void)) returnType = typeof(object); - wrapperMethod = _syncGenericHandler.MakeGenericMethod(returnType); + wrapperMethod = SyncGenericHandler.MakeGenericMethod(returnType); } var converArgs = Expression.Parameter(typeof(object[]), "args"); @@ -127,9 +129,9 @@ private static Handler CreateMethodHandler(Type returnType, IEnumerable(Expression.Convert(Expression.Invoke(next, orig_args), typeof(object)), - targetParam, orig_args, eventArgsParam); + var origArgs = Expression.Parameter(typeof(object[]), "orig_args"); + var handler = Expression.Lambda(Expression.Convert(Expression.Invoke(next, origArgs), typeof(object)), + targetParam, origArgs, eventArgsParam); var handlerCompiled = handler.Compile(); @@ -146,14 +148,14 @@ private static Handler CreateMethodHandler(Type returnType, IEnumerable wrappers) { - if (!_delegateCache.TryGetValue(method, out var handler)) - lock (method) - { - if (!_delegateCache.TryGetValue(method, out handler)) - _delegateCache[method] = handler = CreateMethodHandler(returnType, wrappers); - } - - return handler; + lock (method) + { + if (!DelegateCache.TryGetValue(method, out var handler)) + if (!DelegateCache.TryGetValue(method, out handler)) + DelegateCache[method] = handler = CreateMethodHandler(returnType, wrappers); + + return handler; + } } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs new file mode 100644 index 00000000..92a7c2e1 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs @@ -0,0 +1,43 @@ +/* + * 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.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.Common; + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(bool))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(Int32))] +[JsonSerializable(typeof(Double))] +[JsonSerializable(typeof(DateOnly))] +[JsonSerializable(typeof(TimeOnly))] +[JsonSerializable(typeof(InvalidOperationException))] +[JsonSerializable(typeof(Exception))] +[JsonSerializable(typeof(IEnumerable))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(IEnumerable))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(Byte[]))] +[JsonSerializable(typeof(MemoryStream))] +public partial class PowertoolsSourceGenerationContext : JsonSerializerContext +{ + // make public +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj index 6feca1a2..4cf86feb 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj @@ -6,11 +6,15 @@ Powertools for AWS Lambda (.NET) - Logging package. AWS.Lambda.Powertools.Logging AWS.Lambda.Powertools.Logging + 1.7.1 + + + diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs index 7a85abf7..d7bf1c71 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs @@ -14,9 +14,12 @@ */ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using AWS.Lambda.Powertools.Common; +using System.Collections.Generic; namespace AWS.Lambda.Powertools.Logging.Internal.Converters; @@ -63,7 +66,7 @@ public override void Write(Utf8JsonWriter writer, Exception value, JsonSerialize if (options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull) properties = properties.Where(prop => prop.Value != null); - + var props = properties.ToArray(); if (!props.Any()) return; @@ -86,6 +89,7 @@ public override void Write(Utf8JsonWriter writer, Exception value, JsonSerialize break; default: writer.WritePropertyName(ApplyPropertyNamingPolicy(prop.Name, options)); + JsonSerializer.Serialize(writer, prop.Value, options); break; } diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs index 441cdc22..64541827 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs @@ -14,17 +14,21 @@ */ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.ExceptionServices; using System.Text.Json; using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.ApplicationLoadBalancerEvents; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal.Converters; using Microsoft.Extensions.Logging; namespace AWS.Lambda.Powertools.Logging.Internal; + /// /// Class LoggingAspectHandler. /// Implements the @@ -283,6 +287,9 @@ private static JsonSerializerOptions BuildJsonSerializerOptions() var jsonOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, +#if NET8_0_OR_GREATER + TypeInfoResolver = PowertoolsSourceGenerationContext.Default +#endif }; jsonOptions.Converters.Add(new ByteArrayConverter()); jsonOptions.Converters.Add(new ExceptionConverter()); @@ -319,7 +326,15 @@ private void CaptureCorrelationId(object eventArg) try { var correlationId = string.Empty; + +#if NET8_0_OR_GREATER + + JsonSerializerOptions.TypeInfoResolver = PowertoolsSourceGenerationContext2.Default; + var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg,eventArg.GetType(), JsonSerializerOptions)); + // var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg, PowertoolsSourceGenerationContext2.Default.ApplicationLoadBalancerRequest)); +#else var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg, JsonSerializerOptions)); +#endif var element = jsonDoc.RootElement; for (var i = 0; i < correlationIdPaths.Length; i++) @@ -394,4 +409,14 @@ internal static void ResetForTest() Logger.LoggerProvider = null; Logger.RemoveAllKeys(); } -} \ No newline at end of file +} + +#if NET8_0_OR_GREATER +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ApplicationLoadBalancerRequest))] +[JsonSerializable(typeof(APIGatewayProxyRequest))] +public partial class PowertoolsSourceGenerationContext2 : PowertoolsSourceGenerationContext +{ + // make public +} +#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLambdaContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLambdaContext.cs new file mode 100644 index 00000000..ada35393 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLambdaContext.cs @@ -0,0 +1,151 @@ +using System.Linq; +using Amazon.Lambda.Core; +using AWS.Lambda.Powertools.Common; + +namespace AWS.Lambda.Powertools.Logging.Internal; + +internal class PowertoolsLambdaContext +{ + /// + /// The AWS request ID associated with the request. + /// This is the same ID returned to the client that called invoke(). + /// This ID is reused for retries on the same request. + /// + internal string AwsRequestId { get; private set; } + + /// Name of the Lambda function that is running. + internal string FunctionName { get; private set; } + + /// + /// The Lambda function version that is executing. + /// If an alias is used to invoke the function, then this will be + /// the version the alias points to. + /// + internal string FunctionVersion { get; private set; } + + /// + /// The ARN used to invoke this function. + /// It can be function ARN or alias ARN. + /// An unqualified ARN executes the $LATEST version and aliases execute + /// the function version they are pointing to. + /// + internal string InvokedFunctionArn { get; private set; } + + /// + /// The CloudWatch log group name associated with the invoked function. + /// It can be null if the IAM user provided does not have permission for + /// CloudWatch actions. + /// + internal string LogGroupName { get; private set; } + + /// + /// The CloudWatch log stream name for this function execution. + /// It can be null if the IAM user provided does not have permission + /// for CloudWatch actions. + /// + internal string LogStreamName { get; private set; } + + /// + /// Memory limit, in MB, you configured for the Lambda function. + /// + internal int MemoryLimitInMB { get; private set; } + + /// + /// The instance + /// + internal static PowertoolsLambdaContext Instance { get; private set; } + + /// + /// Extract the lambda context from Lambda handler arguments. + /// + /// + /// The instance containing the + /// event data. + /// + internal static bool Extract(AspectEventArgs eventArgs) + { + if (Instance is not null) + return false; + + if (eventArgs?.Args is null) + return false; + + // foreach (var arg in eventArgs.Args) + // { + // if (arg is null) + // continue; + // + // var argType = arg.GetType(); + // if (!argType.Name.EndsWith("LambdaContext")) + // continue; + // + + // + // foreach (var prop in argType.GetProperties()) + // { + // if (prop.Name.Equals("AwsRequestId", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.AwsRequestId = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("FunctionName", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.FunctionName = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("FunctionVersion", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.FunctionVersion = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("InvokedFunctionArn", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.InvokedFunctionArn = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("LogGroupName", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.LogGroupName = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("LogStreamName", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.LogStreamName = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("MemoryLimitInMB", StringComparison.CurrentCultureIgnoreCase)) + // { + // var propVal = prop.GetValue(arg); + // if (propVal is null || !int.TryParse(propVal.ToString(), out var intVal)) continue; + // Instance.MemoryLimitInMB = intVal; + // } + // } + + // if (!IsPlacedOnRequestHandler(eventArgs.Method)) + // { + // return false; + // } + + var context = (ILambdaContext)eventArgs.Args.FirstOrDefault(x => x is ILambdaContext); + + if (context == null) + { + return false; + } + + Instance = new PowertoolsLambdaContext + { + AwsRequestId = context.AwsRequestId, + FunctionName = context.FunctionName, + FunctionVersion = context.FunctionVersion, + InvokedFunctionArn = context.InvokedFunctionArn, + LogGroupName = context.LogGroupName, + LogStreamName = context.LogStreamName, + MemoryLimitInMB = context.MemoryLimitInMB + }; + + return true; + } + + /// + /// Clear the extracted lambda context. + /// + internal static void Clear() + { + Instance = null; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs index e723ed22..ada89f89 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs @@ -1,12 +1,12 @@ /* * 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 @@ -15,12 +15,15 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal.Converters; using Microsoft.Extensions.Logging; +using DateOnlyConverter = AWS.Lambda.Powertools.Logging.Internal.Converters.DateOnlyConverter; +using TimeOnlyConverter = AWS.Lambda.Powertools.Logging.Internal.Converters.TimeOnlyConverter; namespace AWS.Lambda.Powertools.Logging.Internal; @@ -40,7 +43,7 @@ internal sealed class PowertoolsLogger : ILogger /// The name /// private readonly string _name; - + /// /// The current configuration /// @@ -55,7 +58,7 @@ internal sealed class PowertoolsLogger : ILogger /// The system wrapper /// private readonly ISystemWrapper _systemWrapper; - + /// /// The JsonSerializer options /// @@ -80,10 +83,10 @@ public PowertoolsLogger( { (_name, _powertoolsConfigurations, _systemWrapper, _getCurrentConfig) = (name, powertoolsConfigurations, systemWrapper, getCurrentConfig); - + _powertoolsConfigurations.SetExecutionEnvironment(this); _currentConfig = GetCurrentConfig(); - + if (_lambdaLogLevelEnabled && _logLevel < _lambdaLogLevel) { var message = @@ -148,10 +151,10 @@ internal void EndScope() private static Dictionary GetScopeKeys(TState state) { var keys = new Dictionary(); - - if (state is null) + + if (state is null) return keys; - + switch (state) { case IEnumerable> pairs: @@ -161,6 +164,7 @@ private static Dictionary GetScopeKeys(TState state) if (!string.IsNullOrWhiteSpace(key)) keys.TryAdd(key, value); } + break; } case IEnumerable> pairs: @@ -170,6 +174,7 @@ private static Dictionary GetScopeKeys(TState state) if (!string.IsNullOrWhiteSpace(key)) keys.TryAdd(key, value); } + break; } default: @@ -178,10 +183,11 @@ private static Dictionary GetScopeKeys(TState state) { keys.TryAdd(property.Name, property.GetValue(state)); } + break; } } - + return keys; } @@ -219,11 +225,48 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except : formatter(state, exception); var logFormatter = Logger.GetFormatter(); - var logEntry = logFormatter is null? - GetLogEntry(logLevel, timestamp, message, exception) : - GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter); + +#if NET8_0_OR_GREATER + + var logEntry = logFormatter is null + ? GetLogEntry(logLevel, timestamp, ToDictionary(message), exception) + : ToDictionary(GetFormattedLogEntry(logLevel, timestamp, ToDictionary(message), exception, logFormatter)); + + _systemWrapper.LogLine(JsonSerializer.Serialize(logEntry, JsonSerializerOptions)); + + static object ToDictionary(object values) + { + if (values is string) + { + return values; + } + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (values != null) + { + foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(values)) + { + object obj = propertyDescriptor.GetValue(values); + if (obj.GetType().Name.Contains("Anonymous")) + { + dict.Add(propertyDescriptor.Name,ToDictionary(obj)); + } + else + { + dict.Add(propertyDescriptor.Name, obj); + } + } + } + + return dict; + } +#else + var logEntry = logFormatter is null + ? GetLogEntry(logLevel, timestamp, message, exception) + : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter); _systemWrapper.LogLine(JsonSerializer.Serialize(logEntry, JsonSerializerOptions)); +#endif } /// @@ -387,16 +430,16 @@ private LoggerConfiguration GetCurrentConfig() var currConfig = _getCurrentConfig(); _logLevel = _powertoolsConfigurations.GetLogLevel(currConfig?.MinimumLevel); var samplingRate = currConfig?.SamplingRate ?? _powertoolsConfigurations.LoggerSampleRate; - var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(currConfig?.LoggerOutputCase); + var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(currConfig?.LoggerOutputCase); _lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel(); _lambdaLogLevelEnabled = _lambdaLogLevel != LogLevel.None; - + var minLogLevel = _logLevel; if (_lambdaLogLevelEnabled) { minLogLevel = _lambdaLogLevel; } - + var config = new LoggerConfiguration { Service = currConfig?.Service, @@ -460,7 +503,7 @@ private static bool CustomFormatter(TState state, Exception exception, o message = stateKeys.First(k => k.Key != "{OriginalFormat}").Value; return true; } - + /// /// Builds JsonSerializer options. /// @@ -468,7 +511,7 @@ private JsonSerializerOptions BuildJsonSerializerOptions() { var jsonOptions = CurrentConfig.LoggerOutputCase switch { - LoggerOutputCase.CamelCase => new JsonSerializerOptions(JsonSerializerDefaults.Web) + LoggerOutputCase.CamelCase => new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DictionaryKeyPolicy = JsonNamingPolicy.CamelCase @@ -492,7 +535,10 @@ private JsonSerializerOptions BuildJsonSerializerOptions() jsonOptions.Converters.Add(new TimeOnlyConverter()); jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - + +#if NET8_0_OR_GREATER + jsonOptions.TypeInfoResolver = PowertoolsSourceGenerationContext.Default; +#endif return jsonOptions; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs index b83c933e..d0135dad 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs @@ -13,12 +13,15 @@ * permissions and limitations under the License. */ +using System; +using Amazon.Lambda.Core; + namespace AWS.Lambda.Powertools.Logging; /// /// Powertools Log Entry Lambda Context /// -public class LogEntryLambdaContext +public class LogEntryLambdaContext : ILambdaContext { /// /// The AWS request ID associated with the request. @@ -26,7 +29,9 @@ public class LogEntryLambdaContext /// This ID is reused for retries on the same request. /// public string AwsRequestId { get; internal set; } - + + public IClientContext ClientContext { get; } + /// /// Name of the Lambda function that is running. /// @@ -38,7 +43,11 @@ public class LogEntryLambdaContext /// the version the alias points to. /// public string FunctionVersion { get; internal set; } - + + public ICognitoIdentity Identity { get; } + + public string LogStreamName { get; } + /// /// The ARN used to invoke this function. /// It can be function ARN or alias ARN. @@ -46,11 +55,16 @@ public class LogEntryLambdaContext /// the function version they are pointing to. /// public int MemoryLimitInMB { get; internal set; } - + + public TimeSpan RemainingTime { get; } + /// /// The CloudWatch log group name associated with the invoked function. /// It can be null if the IAM user provided does not have permission for /// CloudWatch actions. /// public string InvokedFunctionArn { get; internal set; } + + public ILambdaLogger Logger { get; } + public string LogGroupName { get; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj b/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj index 77110d09..7d69ddbf 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj @@ -5,9 +5,11 @@ Powertools for AWS Lambda (.NET) - Metrics package. AWS.Lambda.Powertools.Metrics AWS.Lambda.Powertools.Metrics + 1.6.1 + diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsLambdaContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/PowertoolsLambdaContext.cs similarity index 50% rename from libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsLambdaContext.cs rename to libraries/src/AWS.Lambda.Powertools.Metrics/Internal/PowertoolsLambdaContext.cs index 7f9b3fee..323306ea 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsLambdaContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/PowertoolsLambdaContext.cs @@ -1,4 +1,7 @@ using System; +using System.Linq; +using System.Reflection; +using Amazon.Lambda.Core; namespace AWS.Lambda.Powertools.Common; @@ -47,12 +50,12 @@ internal class PowertoolsLambdaContext /// Memory limit, in MB, you configured for the Lambda function. /// internal int MemoryLimitInMB { get; private set; } - + /// /// The instance /// internal static PowertoolsLambdaContext Instance { get; private set; } - + /// /// Extract the lambda context from Lambda handler arguments. /// @@ -68,55 +71,68 @@ internal static bool Extract(AspectEventArgs eventArgs) if (eventArgs?.Args is null) return false; - foreach (var arg in eventArgs.Args) - { - if (arg is null) - continue; - - var argType = arg.GetType(); - if (!argType.Name.EndsWith("LambdaContext")) - continue; + // foreach (var arg in eventArgs.Args) + // { + // if (arg is null) + // continue; + // + // var argType = arg.GetType(); + // if (!argType.Name.EndsWith("LambdaContext")) + // continue; + // + Instance = new PowertoolsLambdaContext(); + // + // foreach (var prop in argType.GetProperties()) + // { + // if (prop.Name.Equals("AwsRequestId", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.AwsRequestId = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("FunctionName", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.FunctionName = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("FunctionVersion", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.FunctionVersion = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("InvokedFunctionArn", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.InvokedFunctionArn = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("LogGroupName", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.LogGroupName = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("LogStreamName", StringComparison.CurrentCultureIgnoreCase)) + // { + // Instance.LogStreamName = prop.GetValue(arg) as string; + // } + // else if (prop.Name.Equals("MemoryLimitInMB", StringComparison.CurrentCultureIgnoreCase)) + // { + // var propVal = prop.GetValue(arg); + // if (propVal is null || !int.TryParse(propVal.ToString(), out var intVal)) continue; + // Instance.MemoryLimitInMB = intVal; + // } + // } - Instance = new PowertoolsLambdaContext(); - - foreach (var prop in argType.GetProperties()) - { - if (prop.Name.Equals("AwsRequestId", StringComparison.CurrentCultureIgnoreCase)) - { - Instance.AwsRequestId = prop.GetValue(arg) as string; - } - else if (prop.Name.Equals("FunctionName", StringComparison.CurrentCultureIgnoreCase)) - { - Instance.FunctionName = prop.GetValue(arg) as string; - } - else if (prop.Name.Equals("FunctionVersion", StringComparison.CurrentCultureIgnoreCase)) - { - Instance.FunctionVersion = prop.GetValue(arg) as string; - } - else if (prop.Name.Equals("InvokedFunctionArn", StringComparison.CurrentCultureIgnoreCase)) - { - Instance.InvokedFunctionArn = prop.GetValue(arg) as string; - } - else if (prop.Name.Equals("LogGroupName", StringComparison.CurrentCultureIgnoreCase)) - { - Instance.LogGroupName = prop.GetValue(arg) as string; - } - else if (prop.Name.Equals("LogStreamName", StringComparison.CurrentCultureIgnoreCase)) - { - Instance.LogStreamName = prop.GetValue(arg) as string; - } - else if (prop.Name.Equals("MemoryLimitInMB", StringComparison.CurrentCultureIgnoreCase)) - { - var propVal = prop.GetValue(arg); - if (propVal is null || !int.TryParse(propVal.ToString(), out var intVal)) continue; - Instance.MemoryLimitInMB = intVal; - } - } + var context = (ILambdaContext)eventArgs.Args.FirstOrDefault(x => x is ILambdaContext); - return true; + if (context == null) + { + return false; } - - return false; + + var pt = (ILambdaContext)eventArgs.Args[1]; + Instance.AwsRequestId = pt.AwsRequestId; + Instance.FunctionName = pt.FunctionName; + Instance.FunctionVersion = pt.FunctionVersion; + Instance.InvokedFunctionArn = pt.InvokedFunctionArn; + Instance.LogGroupName = pt.LogGroupName; + Instance.LogStreamName = pt.LogStreamName; + Instance.MemoryLimitInMB = pt.MemoryLimitInMB; + + return true; } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj b/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj index ba1020ce..b9f4f921 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj @@ -18,6 +18,7 @@ + diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj b/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj index 9698055a..3209ef3a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj @@ -15,6 +15,7 @@ + diff --git a/libraries/src/Directory.Build.props b/libraries/src/Directory.Build.props index 0260344d..5fa585bc 100644 --- a/libraries/src/Directory.Build.props +++ b/libraries/src/Directory.Build.props @@ -17,6 +17,8 @@ true true + true + diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props index ca1d81f4..f64acd2b 100644 --- a/libraries/src/Directory.Packages.props +++ b/libraries/src/Directory.Packages.props @@ -3,7 +3,9 @@ true - + + + diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj new file mode 100644 index 00000000..409d2a14 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj @@ -0,0 +1,43 @@ + + + + net8.0;net8.0 + enable + enable + + false + true + Exe + + + + false + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/GlobalUsings.cs b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/GlobalUsings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs new file mode 100644 index 00000000..b1b3a6c1 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs @@ -0,0 +1,37 @@ +/* + * 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 Amazon.Lambda.Core; +using AWS.Lambda.Powertools.Logging; + +namespace AWS.Lambda.Powertools.AotCompatibility.Handlers; + +public class Handler +{ + [Logging(LogEvent = true)] + public async Task Handle(string input, ILambdaContext context) + { + Logger.LogInformation("Hello world!"); + ThisThrows(); + + await Task.Delay(1); + return input; + } + + private void ThisThrows() + { + throw new NullReferenceException(); + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs new file mode 100644 index 00000000..e2c8f95a --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs @@ -0,0 +1,28 @@ +using Amazon.Lambda.TestUtilities; + +namespace AWS.Lambda.Powertools.AotCompatibility; + +public class UnitTest1 +{ + [Fact] + public async Task Test1() + { + var handler = new Handlers.Handler(); + + + var context = new TestLambdaContext() + { + FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1", + FunctionVersion = "1", + MemoryLimitInMB = 215, + AwsRequestId = Guid.NewGuid().ToString("D") + }; + + // Act + Task Handle() => handler.Handle("whatever", context); + + var tracedException = await Assert.ThrowsAsync(Handle); + Assert.StartsWith("at AWS.Lambda.Powertools.AotCompatibility.Handlers.Handler.ThisThrows()", + tracedException.StackTrace?.TrimStart()); + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj index c9baf07b..3ae7311d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj @@ -11,6 +11,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs index a2928252..ada06b2d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs @@ -6,7 +6,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Handlers; public class ExceptionFunctionHandler { - [Logging] + [Logging(LogEvent = true)] public async Task Handle(string input) { ThisThrows(); diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/LogFormatterTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/LogFormatterTest.cs index 82c93e34..004392b3 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/LogFormatterTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/LogFormatterTest.cs @@ -127,6 +127,7 @@ public void Log_WhenCustomFormatter_LogsCustomFormat() logger.LogInformation(scopeExtraKeys, message); // Assert + logFormatter.Received(1).FormatLogEntry(Arg.Is ( x => diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsLambdaContextTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLambdaContextTest.cs similarity index 78% rename from libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsLambdaContextTest.cs rename to libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLambdaContextTest.cs index d6f87fae..a6fe8e86 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsLambdaContextTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLambdaContextTest.cs @@ -1,24 +1,20 @@ using System; +using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; +using AWS.Lambda.Powertools.Logging.Internal; using Xunit; namespace AWS.Lambda.Powertools.Common.Tests; public class PowertoolsLambdaContextTest { - private class TestLambdaContext - { - public string AwsRequestId { get; set; } - public string FunctionName { get; set; } - public string FunctionVersion { get; set; } - public string InvokedFunctionArn { get; set; } - public string LogGroupName { get; set; } - public string LogStreamName { get; set; } - public int MemoryLimitInMB { get; set; } - } - - private static TestLambdaContext NewLambdaContext() + + + [Fact] + public void Extract_WhenHasLambdaContextArgument_InitializesLambdaContextInfo() { - return new TestLambdaContext + // Arrange + var lambdaContext = new TestLambdaContext { AwsRequestId = Guid.NewGuid().ToString(), FunctionName = Guid.NewGuid().ToString(), @@ -28,16 +24,11 @@ private static TestLambdaContext NewLambdaContext() LogStreamName = Guid.NewGuid().ToString(), MemoryLimitInMB = new Random().Next() }; - } - - [Fact] - public void Extract_WhenHasLambdaContextArgument_InitializesLambdaContextInfo() - { - // Arrange - var lambdaContext = NewLambdaContext(); + var eventArg = new {Source = "Test"}; var eventArgs = new AspectEventArgs { + Method = GetType().GetMethod(nameof(Extract_WhenHasLambdaContextArgument_InitializesLambdaContextInfo)), Name = Guid.NewGuid().ToString(), Args = new object [] { diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs index c49852af..d58a8165 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs @@ -19,6 +19,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Json.Serialization; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal; using AWS.Lambda.Powertools.Logging.Tests.Utilities; @@ -431,7 +432,7 @@ public void Log_AttributeSetsCaseToCamelCase_OutputsCamelCaseLog() PropOne = "Value 1", PropTwo = "Value 2" }; - + logger.LogInformation(message); // Assert From a484b78ae3a1b1d12f48fb3dc823abca15666390 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:34:45 +0000 Subject: [PATCH 09/82] update AOT tests --- .../AWS.Lambda.Powertools.Logging.csproj | 1 - .../AWS.Lambda.Powertools.AotCompatibility.csproj | 11 ++++++----- .../{UnitTest1.cs => HandlerTest.cs} | 8 +++----- .../Handlers/Handler.cs | 10 ++++------ 4 files changed, 13 insertions(+), 17 deletions(-) rename libraries/tests/AWS.Lambda.Powertools.AotCompatibility/{UnitTest1.cs => HandlerTest.cs} (59%) diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj index 4cf86feb..cfa87700 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj @@ -6,7 +6,6 @@ Powertools for AWS Lambda (.NET) - Logging package. AWS.Lambda.Powertools.Logging AWS.Lambda.Powertools.Logging - 1.7.1 diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj index 409d2a14..0b72e238 100644 --- a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj @@ -15,11 +15,11 @@ - - - - - + + true + true + false + @@ -38,6 +38,7 @@ + diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/HandlerTest.cs similarity index 59% rename from libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs rename to libraries/tests/AWS.Lambda.Powertools.AotCompatibility/HandlerTest.cs index e2c8f95a..576510aa 100644 --- a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/UnitTest1.cs +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/HandlerTest.cs @@ -2,7 +2,7 @@ namespace AWS.Lambda.Powertools.AotCompatibility; -public class UnitTest1 +public class HandlerTest { [Fact] public async Task Test1() @@ -19,10 +19,8 @@ public async Task Test1() }; // Act - Task Handle() => handler.Handle("whatever", context); + var response = await handler.Handle("whatever", context); - var tracedException = await Assert.ThrowsAsync(Handle); - Assert.StartsWith("at AWS.Lambda.Powertools.AotCompatibility.Handlers.Handler.ThisThrows()", - tracedException.StackTrace?.TrimStart()); + Assert.Equal("whatever", response); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs index b1b3a6c1..2cefdebd 100644 --- a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs @@ -15,23 +15,21 @@ using Amazon.Lambda.Core; using AWS.Lambda.Powertools.Logging; +using AWS.Lambda.Powertools.Metrics; namespace AWS.Lambda.Powertools.AotCompatibility.Handlers; public class Handler { [Logging(LogEvent = true)] + [Metrics(CaptureColdStart = true, Namespace = "PT Demo NS")] public async Task Handle(string input, ILambdaContext context) { Logger.LogInformation("Hello world!"); - ThisThrows(); + + Metrics.Metrics.AddMetric("Metric1", 1, MetricUnit.Count); await Task.Delay(1); return input; } - - private void ThisThrows() - { - throw new NullReferenceException(); - } } \ No newline at end of file From 855d55c8e56b50a480e380d6bd9896e89937467c Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:41:42 +0000 Subject: [PATCH 10/82] metrics serialization support for AOT --- .../AWS.Lambda.Powertools.Metrics.csproj | 5 --- .../Model/MetricResolution.cs | 6 +++- .../Model/MetricUnit.cs | 4 +++ .../Model/RootNode.cs | 8 +++++ .../Serializer/MetricsSerializationContext.cs | 35 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj b/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj index 7d69ddbf..1217ec9e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj @@ -5,7 +5,6 @@ Powertools for AWS Lambda (.NET) - Metrics package. AWS.Lambda.Powertools.Metrics AWS.Lambda.Powertools.Metrics - 1.6.1 @@ -13,8 +12,4 @@ - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs index 3c6536ff..b638a8b3 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs @@ -6,7 +6,11 @@ namespace AWS.Lambda.Powertools.Metrics; /// /// Enum MetricResolution /// -// [JsonConverter(typeof(StringEnumConverter))] +#if NET8_0_OR_GREATER +[JsonConverter(typeof(JsonStringEnumConverter))] +#else +[JsonConverter(typeof(StringEnumConverter))] +#endif public enum MetricResolution { /// diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs index fd4b9760..132b10f3 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs @@ -21,7 +21,11 @@ namespace AWS.Lambda.Powertools.Metrics; /// /// Enum MetricUnit /// +#if NET8_0_OR_GREATER +[JsonConverter(typeof(JsonStringEnumConverter))] +#else [JsonConverter(typeof(StringEnumConverter))] +#endif public enum MetricUnit { /// diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs index a7783819..1e7189e2 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs @@ -65,6 +65,14 @@ public string Serialize() { if (string.IsNullOrWhiteSpace(AWS.GetNamespace())) throw new SchemaValidationException("namespace"); +#if NET8_0_OR_GREATER + var options = new JsonSerializerOptions + { + TypeInfoResolver = MetricsSerializationContext.Default + }; + return JsonSerializer.Serialize(this, options); +#else return JsonSerializer.Serialize(this); +#endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs new file mode 100644 index 00000000..bf7fdf75 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs @@ -0,0 +1,35 @@ +/* + * 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.Serialization; + +namespace AWS.Lambda.Powertools.Metrics; + +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(double))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(MetricUnit))] +[JsonSerializable(typeof(MetricDefinition))] +[JsonSerializable(typeof(DimensionSet))] +[JsonSerializable(typeof(Metadata))] +[JsonSerializable(typeof(MetricDirective))] +[JsonSerializable(typeof(MetricResolution))] +[JsonSerializable(typeof(MetricsContext))] +[JsonSerializable(typeof(RootNode))] +public partial class MetricsSerializationContext : JsonSerializerContext +{ + +} \ No newline at end of file From 6c7558ba7c85547d718aaf8d582eb2e542a7e8aa Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:33:00 +0000 Subject: [PATCH 11/82] add tracing to AOT tests. Remove JsonConverter from MetricResolution.cs --- .../Model/MetricResolution.cs | 6 +----- .../AWS.Lambda.Powertools.AotCompatibility.csproj | 1 + .../Handlers/Handler.cs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs index b638a8b3..34d3141f 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs @@ -6,11 +6,7 @@ namespace AWS.Lambda.Powertools.Metrics; /// /// Enum MetricResolution /// -#if NET8_0_OR_GREATER -[JsonConverter(typeof(JsonStringEnumConverter))] -#else -[JsonConverter(typeof(StringEnumConverter))] -#endif + public enum MetricResolution { /// diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj index 0b72e238..ded061ef 100644 --- a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/AWS.Lambda.Powertools.AotCompatibility.csproj @@ -39,6 +39,7 @@ + diff --git a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs index 2cefdebd..98f25d74 100644 --- a/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.AotCompatibility/Handlers/Handler.cs @@ -16,6 +16,7 @@ using Amazon.Lambda.Core; using AWS.Lambda.Powertools.Logging; using AWS.Lambda.Powertools.Metrics; +using AWS.Lambda.Powertools.Tracing; namespace AWS.Lambda.Powertools.AotCompatibility.Handlers; @@ -23,13 +24,24 @@ public class Handler { [Logging(LogEvent = true)] [Metrics(CaptureColdStart = true, Namespace = "PT Demo NS")] + [Tracing(Namespace = "PT Demo NS", CaptureMode = TracingCaptureMode.ResponseAndError)] public async Task Handle(string input, ILambdaContext context) { Logger.LogInformation("Hello world!"); Metrics.Metrics.AddMetric("Metric1", 1, MetricUnit.Count); + + DoSomething(); + + Tracing.Tracing.AddAnnotation("Annotation1", "My Anottation"); await Task.Delay(1); return input; } + + [Tracing(SegmentName = "Do Something Method")] + public void DoSomething() + { + Tracing.Tracing.AddAnnotation("Annotation1", "My Anottation"); + } } \ No newline at end of file From cc264efc86510d0657c8e26ef0d5104b976999ba Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:46:21 +0000 Subject: [PATCH 12/82] fix JsonSerializerOptions instances cannot be modified. --- .../Internal/LoggingAspectHandler.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs index 64541827..872acc49 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs @@ -288,7 +288,7 @@ private static JsonSerializerOptions BuildJsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, #if NET8_0_OR_GREATER - TypeInfoResolver = PowertoolsSourceGenerationContext.Default + TypeInfoResolver = PowertoolsLoggerSourceGenerationContext.Default #endif }; jsonOptions.Converters.Add(new ByteArrayConverter()); @@ -328,10 +328,7 @@ private void CaptureCorrelationId(object eventArg) var correlationId = string.Empty; #if NET8_0_OR_GREATER - - JsonSerializerOptions.TypeInfoResolver = PowertoolsSourceGenerationContext2.Default; var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg,eventArg.GetType(), JsonSerializerOptions)); - // var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg, PowertoolsSourceGenerationContext2.Default.ApplicationLoadBalancerRequest)); #else var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg, JsonSerializerOptions)); #endif @@ -415,7 +412,7 @@ internal static void ResetForTest() [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(ApplicationLoadBalancerRequest))] [JsonSerializable(typeof(APIGatewayProxyRequest))] -public partial class PowertoolsSourceGenerationContext2 : PowertoolsSourceGenerationContext +public partial class PowertoolsLoggerSourceGenerationContext : PowertoolsSourceGenerationContext { // make public } From 3a9ec175723867fc92fe0840b2d1b79005ce9859 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:17:34 +0000 Subject: [PATCH 13/82] Moving source generators behind #if NET8_0_OR_GREATER. Add XML comments. refactor --- .../Core/PowertoolsSourceGenerationContext.cs | 10 ++++- .../Internal/LoggingAspectHandler.cs | 39 +++++++------------ ...PowertoolsLoggerSourceGenerationContext.cs | 33 ++++++++++++++++ .../LogEntryLambdaContext.cs | 9 +++++ .../Serializer/MetricsSerializationContext.cs | 7 +++- 5 files changed, 69 insertions(+), 29 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerSourceGenerationContext.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs index 92a7c2e1..9f2d9417 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsSourceGenerationContext.cs @@ -16,11 +16,16 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text.Json; using System.Text.Json.Serialization; namespace AWS.Lambda.Powertools.Common; + + +#if NET8_0_OR_GREATER +/// +/// +/// [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(bool))] @@ -40,4 +45,5 @@ namespace AWS.Lambda.Powertools.Common; public partial class PowertoolsSourceGenerationContext : JsonSerializerContext { // make public -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs index 872acc49..2139b80a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs @@ -1,12 +1,12 @@ /* * 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 @@ -14,21 +14,17 @@ */ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.ExceptionServices; using System.Text.Json; using System.Text.Json.Serialization; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.ApplicationLoadBalancerEvents; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal.Converters; using Microsoft.Extensions.Logging; namespace AWS.Lambda.Powertools.Logging.Internal; - /// /// Class LoggingAspectHandler. /// Implements the @@ -65,7 +61,7 @@ internal class LoggingAspectHandler : IMethodAspectHandler /// The log level /// private readonly LogLevel? _logLevel; - + /// /// The logger output case /// @@ -95,17 +91,17 @@ internal class LoggingAspectHandler : IMethodAspectHandler /// The is context initialized /// private bool _isContextInitialized; - + /// /// Specify to clear Lambda Context on exit /// private bool _clearLambdaContext; - + /// /// The JsonSerializer options /// private static JsonSerializerOptions _jsonSerializerOptions; - + /// /// Get JsonSerializer options. /// @@ -172,7 +168,7 @@ public void OnEntry(AspectEventArgs eventArgs) Logger.LoggerProvider = new LoggerProvider(loggerConfig); break; case LoggerProvider: - ((LoggerProvider) Logger.LoggerProvider).Configure(loggerConfig); + ((LoggerProvider)Logger.LoggerProvider).Configure(loggerConfig); break; } @@ -278,7 +274,7 @@ private void CaptureLambdaContext(AspectEventArgs eventArgs) _systemWrapper.LogLine( "Skipping Lambda Context injection because ILambdaContext context parameter not found."); } - + /// /// Builds JsonSerializer options. /// @@ -326,9 +322,10 @@ private void CaptureCorrelationId(object eventArg) try { var correlationId = string.Empty; - + #if NET8_0_OR_GREATER - var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg,eventArg.GetType(), JsonSerializerOptions)); + var jsonDoc = + JsonDocument.Parse(JsonSerializer.Serialize(eventArg,eventArg.GetType(), JsonSerializerOptions)); #else var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg, JsonSerializerOptions)); #endif @@ -406,14 +403,4 @@ internal static void ResetForTest() Logger.LoggerProvider = null; Logger.RemoveAllKeys(); } -} - -#if NET8_0_OR_GREATER -[JsonSourceGenerationOptions(WriteIndented = true)] -[JsonSerializable(typeof(ApplicationLoadBalancerRequest))] -[JsonSerializable(typeof(APIGatewayProxyRequest))] -public partial class PowertoolsLoggerSourceGenerationContext : PowertoolsSourceGenerationContext -{ - // make public -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerSourceGenerationContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerSourceGenerationContext.cs new file mode 100644 index 00000000..6b0b0d4e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerSourceGenerationContext.cs @@ -0,0 +1,33 @@ +/* + * 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.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using AWS.Lambda.Powertools.Common; + +namespace AWS.Lambda.Powertools.Logging.Internal; + +#if NET8_0_OR_GREATER +/// +/// Powertools source generator that extends common generator +/// +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ApplicationLoadBalancerRequest))] +[JsonSerializable(typeof(APIGatewayProxyRequest))] +public partial class PowertoolsLoggerSourceGenerationContext : PowertoolsSourceGenerationContext +{ +} +#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs index d0135dad..56ea4139 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/LogEntryLambdaContext.cs @@ -30,6 +30,7 @@ public class LogEntryLambdaContext : ILambdaContext /// public string AwsRequestId { get; internal set; } + /// public IClientContext ClientContext { get; } /// @@ -44,8 +45,10 @@ public class LogEntryLambdaContext : ILambdaContext /// public string FunctionVersion { get; internal set; } + /// public ICognitoIdentity Identity { get; } + /// public string LogStreamName { get; } /// @@ -56,6 +59,7 @@ public class LogEntryLambdaContext : ILambdaContext /// public int MemoryLimitInMB { get; internal set; } + /// public TimeSpan RemainingTime { get; } /// @@ -65,6 +69,11 @@ public class LogEntryLambdaContext : ILambdaContext /// public string InvokedFunctionArn { get; internal set; } + /// + /// Lambda Logger + /// public ILambdaLogger Logger { get; } + + /// public string LogGroupName { get; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs index bf7fdf75..e8a421ac 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs @@ -18,6 +18,10 @@ namespace AWS.Lambda.Powertools.Metrics; +#if NET8_0_OR_GREATER +/// +/// Source generator for Metrics types +/// [JsonSerializable(typeof(string))] [JsonSerializable(typeof(double))] [JsonSerializable(typeof(List))] @@ -32,4 +36,5 @@ namespace AWS.Lambda.Powertools.Metrics; public partial class MetricsSerializationContext : JsonSerializerContext { -} \ No newline at end of file +} +#endif \ No newline at end of file From 1a9e11dea5f291d2c9c520296aceb6b1fb4e07da Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Wed, 28 Feb 2024 12:54:54 +0000 Subject: [PATCH 14/82] add AppConfig provider --- .../AWS.Lambda.Powertools.Parameters.csproj | 10 +- .../AppConfig/AppConfigProvider.cs | 482 +++++++ .../AppConfigProviderConfigurationBuilder.cs | 167 +++ .../AppConfig/IAppConfigProvider.cs | 81 ++ .../AppConfigDictionaryTransformer.cs | 62 + .../AppConfigJsonConfigurationParser.cs | 164 +++ .../AppConfig/AppConfigProviderCacheHelper.cs | 42 + .../AppConfigProviderConfiguration.cs | 39 + .../Internal/AppConfig/AppConfigResult.cs | 37 + .../ParametersManager.cs | 35 + libraries/src/Directory.Packages.props | 4 +- .../AppConfig/AppConfigProviderTest.cs | 1157 +++++++++++++++++ 12 files changed, 2275 insertions(+), 5 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs create mode 100644 libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj b/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj index ba1020ce..fda9cc02 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AWS.Lambda.Powertools.Parameters.csproj @@ -13,10 +13,12 @@ - - - - + + + + + + diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs new file mode 100644 index 00000000..951e7e29 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs @@ -0,0 +1,482 @@ +/* + * 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.Concurrent; +using Amazon; +using Amazon.AppConfigData; +using Amazon.AppConfigData.Model; +using Amazon.Runtime; +using AWS.Lambda.Powertools.Parameters.Internal.AppConfig; +using AWS.Lambda.Powertools.Parameters.Cache; +using AWS.Lambda.Powertools.Parameters.Configuration; +using AWS.Lambda.Powertools.Parameters.Internal.Cache; +using AWS.Lambda.Powertools.Parameters.Provider; + +namespace AWS.Lambda.Powertools.Parameters.AppConfig; + +/// +/// The AppConfigProvider to retrieve parameter values from a AWS AppConfig. +/// +public class AppConfigProvider : ParameterProvider, IAppConfigProvider +{ + /// + /// The default application Id. + /// + private string _defaultApplicationId = string.Empty; + + /// + /// The default environment Id. + /// + private string _defaultEnvironmentId = string.Empty; + + /// + /// The default configuration profile Id. + /// + private string _defaultConfigProfileId = string.Empty; + + /// + /// Instance of datetime wrapper. + /// + private readonly IDateTimeWrapper _dateTimeWrapper; + + /// + /// Thread safe dictionary to store results. + /// + private readonly ConcurrentDictionary _results = new(StringComparer.OrdinalIgnoreCase); + + /// + /// The client instance. + /// + private IAmazonAppConfigData? _client; + + /// + /// Gets the client instance. + /// + private IAmazonAppConfigData Client => _client ??= new AmazonAppConfigDataClient(); + + /// + /// AppConfigProvider constructor. + /// + public AppConfigProvider() + { + _dateTimeWrapper = DateTimeWrapper.Instance; + } + + /// + /// AppConfigProvider constructor for test. + /// + internal AppConfigProvider( + IDateTimeWrapper dateTimeWrapper, + string? appConfigResultKey = null, + AppConfigResult? appConfigResult = null) + { + _dateTimeWrapper = dateTimeWrapper; + if (appConfigResultKey is not null && appConfigResult is not null) + _results.TryAdd(appConfigResultKey, appConfigResult); + } + + #region IParameterProviderConfigurableClient implementation + + /// + /// Use a custom client + /// + /// The custom client + /// Provider instance + public IAppConfigProvider UseClient(IAmazonAppConfigData client) + { + _client = client; + return this; + } + + /// + /// Configure client with the credentials loaded from the application's default configuration. + /// + /// The region to connect. + /// Provider instance + public IAppConfigProvider ConfigureClient(RegionEndpoint region) + { + _client = new AmazonAppConfigDataClient(region); + return this; + } + + /// + /// Configure client with the credentials loaded from the application's default configuration. + /// + /// The client configuration object. + /// Provider instance + public IAppConfigProvider ConfigureClient(AmazonAppConfigDataConfig config) + { + _client = new AmazonAppConfigDataClient(config); + return this; + } + + /// + /// Configure client with AWS credentials. + /// + /// AWS credentials. + /// Provider instance + public IAppConfigProvider ConfigureClient(AWSCredentials credentials) + { + _client = new AmazonAppConfigDataClient(credentials); + return this; + } + + /// + /// Configure client with AWS credentials. + /// + /// AWS credentials. + /// The region to connect. + /// Provider instance + public IAppConfigProvider ConfigureClient(AWSCredentials credentials, RegionEndpoint region) + { + _client = new AmazonAppConfigDataClient(credentials, region); + return this; + } + + /// + /// Configure client with AWS credentials and a client configuration object. + /// + /// AWS credentials. + /// The client configuration object. + /// Provider instance + public IAppConfigProvider ConfigureClient(AWSCredentials credentials, AmazonAppConfigDataConfig config) + { + _client = new AmazonAppConfigDataClient(credentials, config); + return this; + } + + /// + /// Configure client with AWS Access Key ID and AWS Secret Key. + /// + /// AWS Access Key ID + /// AWS Secret Access Key + /// Provider instance + public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey) + { + _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey); + return this; + } + + /// + /// Configure client with AWS Access Key ID and AWS Secret Key. + /// + /// AWS Access Key ID + /// AWS Secret Access Key + /// The region to connect. + /// Provider instance + public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, RegionEndpoint region) + { + _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, region); + return this; + } + + /// + /// Configure client with AWS Access Key ID and AWS Secret Key and a client configuration object. + /// + /// AWS Access Key ID + /// AWS Secret Access Key + /// The client configuration object. + /// Provider instance + public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, + AmazonAppConfigDataConfig config) + { + _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, config); + return this; + } + + /// + /// Configure client with AWS Access Key ID and AWS Secret Key. + /// + /// AWS Access Key ID + /// AWS Secret Access Key + /// AWS Session Token + /// Provider instance + public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, string awsSessionToken) + { + _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken); + return this; + } + + /// + /// Configure client with AWS Access Key ID and AWS Secret Key. + /// + /// AWS Access Key ID + /// AWS Secret Access Key + /// AWS Session Token + /// The region to connect. + /// Provider instance + public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, string awsSessionToken, + RegionEndpoint region) + { + _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken, region); + return this; + } + + /// + /// Configure client with AWS Access Key ID and AWS Secret Key and a client configuration object. + /// + /// AWS Access Key ID + /// AWS Secret Access Key + /// AWS Session Token + /// The client configuration object. + /// Provider instance + public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecretAccessKey, string awsSessionToken, + AmazonAppConfigDataConfig config) + { + _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken, config); + return this; + } + + #endregion + + /// + /// Sets the default application ID or name. + /// + /// The application ID or name. + /// The AppConfigProvider instance. + public IAppConfigProvider DefaultApplication(string applicationId) + { + if (string.IsNullOrWhiteSpace(applicationId)) + throw new ArgumentNullException(nameof(applicationId)); + _defaultApplicationId = applicationId; + return this; + } + + /// + /// Sets the default environment ID or name. + /// + /// The environment ID or name. + /// The AppConfigProvider instance. + public IAppConfigProvider DefaultEnvironment(string environmentId) + { + if (string.IsNullOrWhiteSpace(environmentId)) + throw new ArgumentNullException(nameof(environmentId)); + _defaultEnvironmentId = environmentId; + return this; + } + + /// + /// Sets the default configuration profile ID or name. + /// + /// The configuration profile ID or name. + /// The AppConfigProvider instance. + public IAppConfigProvider DefaultConfigProfile(string configProfileId) + { + _defaultConfigProfileId = configProfileId; + return this; + } + + /// + /// Sets the application ID or name. + /// + /// The application ID or name. + /// The AppConfigProvider configuration builder. + public AppConfigProviderConfigurationBuilder WithApplication(string applicationId) + { + return NewConfigurationBuilder().WithApplication(applicationId); + } + + /// + /// Sets the environment ID or name. + /// + /// The environment ID or name. + /// The AppConfigProvider configuration builder. + public AppConfigProviderConfigurationBuilder WithEnvironment(string environmentId) + { + return NewConfigurationBuilder().WithEnvironment(environmentId); + } + + /// + /// Sets the configuration profile ID or name. + /// + /// The configuration profile ID or name. + /// The AppConfigProvider configuration builder. + public AppConfigProviderConfigurationBuilder WithConfigProfile(string configProfileId) + { + return NewConfigurationBuilder().WithConfigProfile(configProfileId); + } + + /// + /// Creates and configures a new AppConfigProviderConfigurationBuilder + /// + /// + protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder() + { + return new AppConfigProviderConfigurationBuilder(this) + .WithApplication(_defaultApplicationId) + .WithEnvironment(_defaultEnvironmentId) + .WithConfigProfile(_defaultConfigProfileId); + + } + + /// + /// Get AppConfig transformed value for the provided key. + /// + /// The parameter key. + /// Target transformation type. + /// The AppConfig transformed value. + public override async Task GetAsync(string key) where T : class + { + return await NewConfigurationBuilder() + .GetAsync(key) + .ConfigureAwait(false); + } + + /// + /// Get last AppConfig value. + /// + /// Application Configuration. + public IDictionary Get() + { + return GetAsync().GetAwaiter().GetResult(); + } + + /// + /// Get last AppConfig value. + /// + /// The AppConfig value. + public async Task> GetAsync() + { + return await NewConfigurationBuilder() + .GetAsync() + .ConfigureAwait(false); + } + + /// + /// Get last AppConfig value and transform it to JSON value. + /// + /// JSON value type. + /// The AppConfig JSON value. + public T? Get() where T: class + { + return GetAsync().GetAwaiter().GetResult(); + } + + /// + /// Get last AppConfig value and transform it to JSON value. + /// + /// JSON value type. + /// The AppConfig JSON value. + public async Task GetAsync() where T: class + { + return await NewConfigurationBuilder() + .GetAsync() + .ConfigureAwait(false); + } + + /// + /// Get parameter value for the provided key. + /// + /// The parameter key. + /// The parameter provider configuration + /// The parameter value. + protected override async Task GetAsync(string key, ParameterProviderConfiguration? config) + { + if(config is not AppConfigProviderConfiguration configuration) + throw new ArgumentNullException(nameof(config)); + + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey + ( + configuration.ApplicationId, + configuration.EnvironmentId, + configuration.ConfigProfileId + ); + + var result = GetAppConfigResult(cacheKey); + + if (_dateTimeWrapper.UtcNow < result.NextAllowedPollTime) + { + if (!config.ForceFetch) + return result.LastConfig; + + result.PollConfigurationToken = string.Empty; + result.NextAllowedPollTime = DateTime.MinValue; + } + + if (string.IsNullOrWhiteSpace(result.PollConfigurationToken)) + result.PollConfigurationToken = + await GetInitialConfigurationTokenAsync(configuration) + .ConfigureAwait(false); + + var request = new GetLatestConfigurationRequest + { + ConfigurationToken = result.PollConfigurationToken + }; + + var response = + await Client.GetLatestConfigurationAsync(request) + .ConfigureAwait(false); + + result.PollConfigurationToken = response.NextPollConfigurationToken; + result.NextAllowedPollTime = _dateTimeWrapper.UtcNow.AddSeconds(response.NextPollIntervalInSeconds); + + if (!string.Equals(response.ContentType, "application/json", StringComparison.CurrentCultureIgnoreCase)) + throw new NotImplementedException($"Not implemented AppConfig type: {response.ContentType}"); + + using (var reader = new StreamReader(response.Configuration)) + { + result.LastConfig = + await reader.ReadToEndAsync() + .ConfigureAwait(false); + } + + return result.LastConfig; + } + + /// + /// Get multiple parameter values for the provided key. + /// + /// The parameter key. + /// The parameter provider configuration + /// Returns a collection parameter key/value pairs. + protected override Task> GetMultipleAsync(string key, + ParameterProviderConfiguration? config) + { + throw new NotSupportedException("Impossible to get multiple values from AWS AppConfig"); + } + + /// + /// Gets Or Adds AppConfigResult with provided key + /// + /// The cache key + /// AppConfigResult + private AppConfigResult GetAppConfigResult(string cacheKey) + { + if (_results.TryGetValue(cacheKey, out var cachedResult)) + return cachedResult; + + cachedResult = new AppConfigResult(); + _results.TryAdd(cacheKey, cachedResult); + + return cachedResult; + } + + /// + /// Starts a configuration session used to retrieve a deployed configuration. + /// + /// Teh AppConfig provider configuration + /// The initial configuration token + private async Task GetInitialConfigurationTokenAsync(AppConfigProviderConfiguration config) + { + var request = new StartConfigurationSessionRequest + { + ApplicationIdentifier = config.ApplicationId, + EnvironmentIdentifier = config.EnvironmentId, + ConfigurationProfileIdentifier = config.ConfigProfileId + }; + + return (await Client.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs new file mode 100644 index 00000000..87df9515 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs @@ -0,0 +1,167 @@ +/* + * 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 AWS.Lambda.Powertools.Parameters.Internal.AppConfig; +using AWS.Lambda.Powertools.Parameters.Configuration; +using AWS.Lambda.Powertools.Parameters.Provider; +using AWS.Lambda.Powertools.Parameters.Transform; + +namespace AWS.Lambda.Powertools.Parameters.AppConfig; + +/// +/// AppConfigProviderConfigurationBuilder class. +/// +public class AppConfigProviderConfigurationBuilder : ParameterProviderConfigurationBuilder +{ + /// + /// The application Id. + /// + private string? _applicationId; + + /// + /// The environment Id. + /// + private string? _environmentId; + + /// + /// The configuration profile Id. + /// + private string? _configProfileId; + + /// + /// AppConfigProviderConfigurationBuilder constructor + /// + /// The AppConfigProvider instance + public AppConfigProviderConfigurationBuilder(ParameterProvider parameterProvider) : + base(parameterProvider) + { + } + + /// + /// Sets the application ID or name. + /// + /// The application ID or name. + /// The AppConfigProvider configuration builder. + public AppConfigProviderConfigurationBuilder WithApplication(string applicationId) + { + _applicationId = applicationId; + return this; + } + + /// + /// Sets the environment ID or name. + /// + /// The environment ID or name. + /// The AppConfigProvider configuration builder. + public AppConfigProviderConfigurationBuilder WithEnvironment(string environmentId) + { + _environmentId = environmentId; + return this; + } + + /// + /// Sets the configuration profile ID or name. + /// + /// The configuration profile ID or name. + /// The AppConfigProvider configuration builder. + public AppConfigProviderConfigurationBuilder WithConfigProfile(string configProfileId) + { + _configProfileId = configProfileId; + return this; + } + + /// + /// Creates and configures new AppConfigProviderConfiguration instance. + /// + /// + protected override ParameterProviderConfiguration NewConfiguration() + { + return new AppConfigProviderConfiguration + { + EnvironmentId = _environmentId, + ApplicationId = _applicationId, + ConfigProfileId = _configProfileId + }; + } + + /// + /// Get AppConfig transformed value for the provided key. + /// + /// The parameter key. + /// Target transformation type. + /// The AppConfig transformed value. + public override async Task GetAsync(string key) where T : class + { + if (string.IsNullOrWhiteSpace(key)) + return default; + + if (typeof(T) != typeof(string)) + return default; + + var dictionary = await GetAsync().ConfigureAwait(false); + if (dictionary.TryGetValue(key, out var value) && value != null) + return (T)(object)value; + + return default; + } + + /// + /// Get last AppConfig value. + /// + /// Application Configuration. + public IDictionary Get() + { + return GetAsync().GetAwaiter().GetResult(); + } + + /// + /// Get last AppConfig value. + /// + /// The AppConfig value. + public async Task> GetAsync() + { + return await GetAsync>().ConfigureAwait(false) ?? + new Dictionary(); + } + + /// + /// Get last AppConfig value and transform it to JSON value. + /// + /// JSON value type. + /// The AppConfig JSON value. + public T? Get() where T : class + { + return GetAsync().GetAwaiter().GetResult(); + } + + /// + /// Get last AppConfig value and transform it to JSON value. + /// + /// JSON value type. + /// The AppConfig JSON value. + public async Task GetAsync() where T : class + { + if (!HasTransformation) + { + if (typeof(T) == typeof(IDictionary)) + SetTransformer(AppConfigDictionaryTransformer.Instance); + else + SetTransformation(Transformation.Json); + } + + return await base.GetAsync(AppConfigProviderCacheHelper.GetCacheKey(_applicationId, _environmentId, + _configProfileId)).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs new file mode 100644 index 00000000..32debbea --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs @@ -0,0 +1,81 @@ +using Amazon.AppConfigData; +using AWS.Lambda.Powertools.Parameters.Internal.Provider; +using AWS.Lambda.Powertools.Parameters.Provider; + +namespace AWS.Lambda.Powertools.Parameters.AppConfig; + +/// +/// Represents a type used to retrieve parameter values from a AWS AppConfig. +/// +public interface IAppConfigProvider : IParameterProvider, + IParameterProviderConfigurableClient +{ + /// + /// Sets the default application ID or name. + /// + /// The application ID or name. + /// The AppConfigProvider instance. + IAppConfigProvider DefaultApplication(string applicationId); + + /// + /// Sets the default environment ID or name. + /// + /// The environment ID or name. + /// The AppConfigProvider instance. + IAppConfigProvider DefaultEnvironment(string environmentId); + + /// + /// Sets the default configuration profile ID or name. + /// + /// The configuration profile ID or name. + /// The AppConfigProvider instance. + IAppConfigProvider DefaultConfigProfile(string configProfileId); + + /// + /// Sets the application ID or name. + /// + /// The application ID or name. + /// The AppConfigProvider configuration builder. + AppConfigProviderConfigurationBuilder WithApplication(string applicationId); + + /// + /// Sets the environment ID or name. + /// + /// The environment ID or name. + /// The AppConfigProvider configuration builder. + AppConfigProviderConfigurationBuilder WithEnvironment(string environmentId); + + /// + /// Sets the configuration profile ID or name. + /// + /// The configuration profile ID or name. + /// The AppConfigProvider configuration builder. + AppConfigProviderConfigurationBuilder WithConfigProfile(string configProfileId); + + /// + /// Get last AppConfig value. + /// + /// Application Configuration. + IDictionary Get(); + + /// + /// Get last AppConfig value. + /// + /// The AppConfig value. + Task> GetAsync(); + + /// + /// Get last AppConfig value and transform it to JSON value. + /// + /// JSON value type. + /// The AppConfig JSON value. + T? Get() where T : class; + + /// + /// Get last AppConfig value and transform it to JSON value. + /// + /// JSON value type. + /// The AppConfig JSON value. + Task GetAsync() where T : class; +} + diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs new file mode 100644 index 00000000..021cc572 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigDictionaryTransformer.cs @@ -0,0 +1,62 @@ +/* + * 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 AWS.Lambda.Powertools.Parameters.Transform; + +namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig; + +/// +/// Transformer to deserialize dictionary from JSON string. +/// +internal class AppConfigDictionaryTransformer : ITransformer +{ + /// + /// The transformer instance. + /// + private static AppConfigDictionaryTransformer? _instance; + + /// + /// Gets the transformer instance. + /// + internal static AppConfigDictionaryTransformer Instance => _instance ??= new AppConfigDictionaryTransformer(); + + /// + /// AppConfigDictionaryTransformer constructor. + /// + private AppConfigDictionaryTransformer() + { + + } + + /// + /// Deserialize a dictionary from a JSON string. + /// + /// JSON string. + /// JSON value type. + /// Key/value pair collection. + public T? Transform(string value) + { + if (typeof(T) == typeof(string)) + return (T)(object)value; + + if (string.IsNullOrWhiteSpace(value)) + return default; + + if (typeof(T) != typeof(IDictionary)) + return default; + + return (T)AppConfigJsonConfigurationParser.Parse(value); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs new file mode 100644 index 00000000..b84ce03d --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigJsonConfigurationParser.cs @@ -0,0 +1,164 @@ +/* + * 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.Globalization; +using System.Text.Json; +using Microsoft.Extensions.Configuration; + +namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig; + +/// +/// AppConfigJsonConfigurationParser class +/// +internal class AppConfigJsonConfigurationParser +{ + /// + /// The processed data. + /// + private readonly IDictionary _data = + new SortedDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Stack for processing the document. + /// + private readonly Stack _context = new(); + + /// + /// Pointer to the current path. + /// + private string _currentPath = string.Empty; + + /// + /// Parse dictionary from AppConfig JSON stream. + /// + /// AppConfig JSON stream. + /// JSON Dictionary. + public static IDictionary Parse(Stream input) + { + using var doc = JsonDocument.Parse(input); + var parser = new AppConfigJsonConfigurationParser(); + parser.VisitElement(doc.RootElement); + return parser._data; + } + + /// + /// Parse dictionary from AppConfig JSON string. + /// + /// AppConfig JSON string. + /// JSON Dictionary. + public static IDictionary Parse(string input) + { + using var doc = JsonDocument.Parse(input); + var parser = new AppConfigJsonConfigurationParser(); + parser.VisitElement(doc.RootElement); + return parser._data; + } + + /// + /// Process single JSON element. + /// + /// The JSON element + private void VisitElement(JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.Undefined: + break; + case JsonValueKind.Object: + foreach (var property in element.EnumerateObject()) + { + EnterContext(property.Name); + VisitElement(property.Value); + ExitContext(); + } + + break; + case JsonValueKind.Array: + VisitArray(element); + break; + case JsonValueKind.String: + case JsonValueKind.Number: + case JsonValueKind.True: + case JsonValueKind.False: + VisitPrimitive(element); + break; + case JsonValueKind.Null: + VisitNull(); + break; + } + } + + /// + /// Process array JSON element. + /// + /// The JSON array + private void VisitArray(JsonElement array) + { + var index = 0; + foreach (var item in array.EnumerateArray()) + { + EnterContext(index.ToString(CultureInfo.InvariantCulture)); + VisitElement(item); + ExitContext(); + + index++; + } + } + + /// + /// Process JSON null element. + /// + private void VisitNull() + { + var key = _currentPath; + _data[key] = null; + } + + /// + /// Process JSON primitive element. + /// + /// The JSON element. + /// + private void VisitPrimitive(JsonElement data) + { + var key = _currentPath; + + if (_data.ContainsKey(key)) + { + throw new FormatException($"A duplicate key '{key}' was found."); + } + + _data[key] = data.ToString(); + } + + /// + /// Enter into a context of a new element to process. + /// + /// The context + private void EnterContext(string context) + { + _context.Push(context); + _currentPath = ConfigurationPath.Combine(_context.Reverse()); + } + + /// + /// Enter from context of an element which is processed. + /// + private void ExitContext() + { + _context.Pop(); + _currentPath = ConfigurationPath.Combine(_context.Reverse()); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs new file mode 100644 index 00000000..acbfe0b1 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderCacheHelper.cs @@ -0,0 +1,42 @@ +/* + * 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.Parameters.Internal.AppConfig; + +/// +/// AppConfigProviderCacheHelper class. +/// +internal static class AppConfigProviderCacheHelper +{ + /// + /// Gets a new key for caching from provided inputs. + /// + /// The application Id. + /// The environment Id. + /// the configuration profile Id. + /// The cache key + /// + internal static string GetCacheKey(string? applicationId, string? environmentId, string? configProfileId) + { + if (string.IsNullOrWhiteSpace(applicationId)) + throw new ArgumentNullException(nameof(applicationId)); + if (string.IsNullOrWhiteSpace(environmentId)) + throw new ArgumentNullException(nameof(environmentId)); + if (string.IsNullOrWhiteSpace(configProfileId)) + throw new ArgumentNullException(nameof(configProfileId)); + + return $"{applicationId}_{environmentId}_{configProfileId}"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs new file mode 100644 index 00000000..9d638058 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigProviderConfiguration.cs @@ -0,0 +1,39 @@ +/* + * 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 AWS.Lambda.Powertools.Parameters.Configuration; + +namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig; + +/// +/// AppConfigProviderConfiguration class. +/// +internal class AppConfigProviderConfiguration : ParameterProviderConfiguration +{ + /// + /// The application Id. + /// + internal string? ApplicationId { get; set; } + + /// + /// The environment Id. + /// + internal string? EnvironmentId { get; set; } + + /// + /// The configuration profile Id. + /// + internal string? ConfigProfileId { get; set; } +} diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs new file mode 100644 index 00000000..61467e19 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigResult.cs @@ -0,0 +1,37 @@ +/* + * 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.Parameters.Internal.AppConfig; + +/// +/// AppConfigResult class. +/// +internal class AppConfigResult +{ + /// + /// Token for polling the configuration. + /// + internal string PollConfigurationToken { get; set; } = string.Empty; + + /// + /// Next time poll is allowed. + /// + internal DateTime NextAllowedPollTime { get; set; } = DateTime.MinValue; + + /// + /// Last configuration value + /// + internal string? LastConfig { get; set; } = null; +} diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs index 99915210..456de66d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/ParametersManager.cs @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ + +using AWS.Lambda.Powertools.Parameters.AppConfig; using AWS.Lambda.Powertools.Parameters.Cache; using AWS.Lambda.Powertools.Parameters.DynamoDB; using AWS.Lambda.Powertools.Parameters.Internal.Cache; @@ -43,6 +45,11 @@ public static class ParametersManager /// The DynamoDBProvider instance /// private static IDynamoDBProvider? _dynamoDBProvider; + + /// + /// The AppConfigProvider instance + /// + private static IAppConfigProvider? _appConfigProvider; /// /// The CacheManager instance @@ -89,6 +96,12 @@ public static class ParametersManager /// The DynamoDBProvider instance. public static IDynamoDBProvider DynamoDBProvider => _dynamoDBProvider ??= CreateDynamoDBProvider(); + /// + /// Gets the AppConfigProvider instance. + /// + /// The AppConfigProvider instance. + public static IAppConfigProvider AppConfigProvider => _appConfigProvider ??= CreateAppConfigProvider(); + /// /// Set the caching default maximum age for all providers. /// @@ -104,6 +117,7 @@ public static void DefaultMaxAge(TimeSpan maxAge) _ssmProvider?.DefaultMaxAge(maxAge); _secretsProvider?.DefaultMaxAge(maxAge); _dynamoDBProvider?.DefaultMaxAge(maxAge); + _appConfigProvider?.DefaultMaxAge(maxAge); } /// @@ -116,6 +130,7 @@ public static void UseCacheManager(ICacheManager cacheManager) _ssmProvider?.UseCacheManager(cacheManager); _secretsProvider?.UseCacheManager(cacheManager); _dynamoDBProvider?.UseCacheManager(cacheManager); + _appConfigProvider?.UseCacheManager(cacheManager); } /// @@ -128,6 +143,7 @@ public static void UseTransformerManager(ITransformerManager transformerManager) _ssmProvider?.UseTransformerManager(transformerManager); _secretsProvider?.UseTransformerManager(transformerManager); _dynamoDBProvider?.UseTransformerManager(transformerManager); + _appConfigProvider?.UseTransformerManager(transformerManager); } /// @@ -141,6 +157,7 @@ public static void AddTransformer(string name, ITransformer transformer) _ssmProvider?.AddTransformer(name, transformer); _secretsProvider?.AddTransformer(name, transformer); _dynamoDBProvider?.AddTransformer(name, transformer); + _appConfigProvider?.AddTransformer(name, transformer); } /// @@ -151,6 +168,7 @@ public static void RaiseTransformationError() _ssmProvider?.RaiseTransformationError(); _secretsProvider?.RaiseTransformationError(); _dynamoDBProvider?.RaiseTransformationError(); + _appConfigProvider?.RaiseTransformationError(); } /// @@ -162,6 +180,7 @@ public static void RaiseTransformationError(bool raiseError) _ssmProvider?.RaiseTransformationError(raiseError); _secretsProvider?.RaiseTransformationError(raiseError); _dynamoDBProvider?.RaiseTransformationError(raiseError); + _appConfigProvider?.RaiseTransformationError(raiseError); } /// @@ -211,4 +230,20 @@ public static IDynamoDBProvider CreateDynamoDBProvider() return provider; } + + /// + /// Create a new instance of AppConfigProvider. + /// + /// The AppConfigProvider instance. + public static IAppConfigProvider CreateAppConfigProvider() + { + var provider = new AppConfigProvider() + .UseCacheManager(Cache) + .UseTransformerManager(TransformManager); + + if (_defaultMaxAge.HasValue) + provider = provider.DefaultMaxAge(_defaultMaxAge.Value); + + return provider; + } } \ No newline at end of file diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props index ca1d81f4..cb1ab08f 100644 --- a/libraries/src/Directory.Packages.props +++ b/libraries/src/Directory.Packages.props @@ -3,9 +3,11 @@ true - + + + diff --git a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs new file mode 100644 index 00000000..ee9b2c46 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs @@ -0,0 +1,1157 @@ +/* + * 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; +using System.Text.Json; +using Amazon.AppConfigData; +using Amazon.AppConfigData.Model; +using AWS.Lambda.Powertools.Parameters.AppConfig; +using AWS.Lambda.Powertools.Parameters.Cache; +using AWS.Lambda.Powertools.Parameters.Configuration; +using AWS.Lambda.Powertools.Parameters.Internal.AppConfig; +using AWS.Lambda.Powertools.Parameters.Internal.Cache; +using AWS.Lambda.Powertools.Parameters.Provider; +using AWS.Lambda.Powertools.Parameters.Internal.Provider; +using AWS.Lambda.Powertools.Parameters.Transform; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace AWS.Lambda.Powertools.Parameters.Tests.AppConfig; + +public class AppConfigProviderTest +{ + [Fact] + public async Task GetAsync_SetupProvider_CallsHandler() + { + var key = Guid.NewGuid().ToString(); + var value = Guid.NewGuid().ToString(); + var transformerName = Guid.NewGuid().ToString(); + var duration = CacheManager.DefaultMaxAge.Add(TimeSpan.FromHours(10)); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var transformer = Substitute.For(); + var providerHandler = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + var appConfig = new Dictionary { { key, value } }; + + providerHandler + .GetAsync>(cacheKey, Arg.Any(), null, null) + .Returns(appConfig); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper); + appConfigProvider.SetHandler(providerHandler); + appConfigProvider.UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager); + appConfigProvider.DefaultMaxAge(duration); + appConfigProvider.AddTransformer(transformerName, transformer); + appConfigProvider.DefaultApplication(applicationId); + appConfigProvider.DefaultEnvironment(environmentId); + appConfigProvider.DefaultConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.GetAsync(key); + + // Assert + await providerHandler.Received(1).GetAsync>(cacheKey, + Arg.Is(x => + x != null && x.ApplicationId == applicationId && x.EnvironmentId == environmentId && + x.ConfigProfileId == configProfileId), null, null); + providerHandler.Received(1).SetCacheManager(cacheManager); + providerHandler.Received(1).SetTransformerManager(transformerManager); + providerHandler.Received(1).SetDefaultMaxAge(duration); + providerHandler.Received(1).AddCustomTransformer(transformerName, transformer); + Assert.NotNull(result); + Assert.Equal(value, result); + } + + [Fact] + public async Task GetAsync_WhenForceFetch_CallsHandlerWithConfiguredParameters() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var value = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var providerHandler = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + var appConfig = new Dictionary { { key, value } }; + + providerHandler + .GetAsync>(cacheKey, Arg.Any(), null, null) + .Returns(appConfig); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper); + appConfigProvider.SetHandler(providerHandler); + appConfigProvider.UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager); + appConfigProvider.DefaultApplication(applicationId); + appConfigProvider.DefaultEnvironment(environmentId); + appConfigProvider.DefaultConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider + .ForceFetch() + .GetAsync(key); + + // Assert + await providerHandler.Received(1).GetAsync>(cacheKey, + Arg.Is(x => + x != null && x.ForceFetch + ), null, + null); + Assert.NotNull(result); + Assert.Equal(value, result); + } + + [Fact] + public async Task GetAsync_WithMaxAge_CallsHandlerWithConfiguredParameters() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var value = Guid.NewGuid().ToString(); + var duration = CacheManager.DefaultMaxAge.Add(TimeSpan.FromHours(10)); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var providerHandler = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + var appConfig = new Dictionary { { key, value } }; + + providerHandler + .GetAsync>(cacheKey, Arg.Any(), null, null) + .Returns(appConfig); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper); + appConfigProvider.SetHandler(providerHandler); + appConfigProvider.UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager); + appConfigProvider.DefaultApplication(applicationId); + appConfigProvider.DefaultEnvironment(environmentId); + appConfigProvider.DefaultConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider + .WithMaxAge(duration) + .GetAsync(key); + + // Assert + await providerHandler.Received(1).GetAsync>(cacheKey, + Arg.Is(x => + x != null && x.MaxAge == duration + ), null, + null); + Assert.NotNull(result); + Assert.Equal(value, result); + } + + [Fact] + public async Task GetAsync_WhenCachedObjectExists_ReturnsCachedObject() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var valueFromCache = Guid.NewGuid().ToString(); + var appConfig = new Dictionary { { key, valueFromCache } }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(new GetLatestConfigurationResponse()); + + cacheManager.Get(cacheKey).Returns(appConfig); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager); + + appConfigProvider.DefaultApplication(applicationId); + appConfigProvider.DefaultEnvironment(environmentId); + appConfigProvider.DefaultConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.GetAsync(key); + + // Assert + await client.DidNotReceiveWithAnyArgs().GetLatestConfigurationAsync(null); + Assert.NotNull(result); + Assert.Equal(valueFromCache, result); + } + + [Fact] + public async Task GetAsync_WhenForceFetch_IgnoresCachedObject() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var value = new + { + Config1 = Guid.NewGuid().ToString(), + Config2 = Guid.NewGuid().ToString() + }; + + var valueFromCache = new Dictionary + { + { value.Config1, Guid.NewGuid().ToString() }, + { value.Config2, Guid.NewGuid().ToString() } + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).Returns(valueFromCache); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.ForceFetch().GetAsync(); + + // Assert + cacheManager.DidNotReceive().Get(cacheKey); + await client.Received(1).StartConfigurationSessionAsync(Arg.Is( + x => x.ApplicationIdentifier == applicationId && + x.EnvironmentIdentifier == environmentId && + x.ConfigurationProfileIdentifier == configProfileId), Arg.Any()); + await client.Received(1).GetLatestConfigurationAsync(Arg.Is( + x => x.ConfigurationToken == configurationToken), Arg.Any()); + Assert.NotNull(result); + Assert.Equal("Config1", result.First().Key); + Assert.Equal(value.Config1, result.First().Value); + Assert.Equal("Config2", result.Last().Key); + Assert.Equal(value.Config2, result.Last().Value); + } + + [Fact] + public async Task GetAsync_WhenMaxAgeNotSet_StoresCachedObjectWithDefaultMaxAge() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var duration = CacheManager.DefaultMaxAge; + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var value = new + { + Config1 = Guid.NewGuid().ToString(), + Config2 = Guid.NewGuid().ToString() + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).ReturnsNull(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.GetAsync(); + + // Assert + cacheManager.Received(1).Get(cacheKey); + cacheManager.Received(1).Set(cacheKey, Arg.Is>(d => + d.First().Key == "Config1" && + d.First().Value == value.Config1 && + d.Last().Key == "Config2" && + d.Last().Value == value.Config2 + ), duration); + Assert.NotNull(result); + Assert.Equal("Config1", result.First().Key); + Assert.Equal(value.Config1, result.First().Value); + Assert.Equal("Config2", result.Last().Key); + Assert.Equal(value.Config2, result.Last().Value); + } + + [Fact] + public async Task GetAsync_WhenMaxAgeClientSet_StoresCachedObjectWithDefaultMaxAge() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var duration = CacheManager.DefaultMaxAge.Add(TimeSpan.FromHours(10)); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var value = new + { + Config1 = Guid.NewGuid().ToString(), + Config2 = Guid.NewGuid().ToString() + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).ReturnsNull(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId) + .WithMaxAge(duration); + + // Act + var result = await appConfigProvider.GetAsync(); + + // Assert + cacheManager.Received(1).Get(cacheKey); + cacheManager.Received(1).Set(cacheKey, Arg.Is>(d => + d.First().Key == "Config1" && + d.First().Value == value.Config1 && + d.Last().Key == "Config2" && + d.Last().Value == value.Config2 + ), duration); + Assert.NotNull(result); + Assert.Equal("Config1", result.First().Key); + Assert.Equal(value.Config1, result.First().Value); + Assert.Equal("Config2", result.Last().Key); + Assert.Equal(value.Config2, result.Last().Value); + } + + [Fact] + public async Task GetAsync_WhenMaxAgeSet_StoresCachedObjectWithMaxAge() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var defaultMaxAge = CacheManager.DefaultMaxAge; + var duration = defaultMaxAge.Add(TimeSpan.FromHours(10)); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var value = new + { + Config1 = Guid.NewGuid().ToString(), + Config2 = Guid.NewGuid().ToString() + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var jsonStr = JsonSerializer.Serialize(value); + var content = Encoding.UTF8.GetBytes(jsonStr); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).ReturnsNull(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .DefaultMaxAge(defaultMaxAge) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.WithMaxAge(duration).GetAsync(); + + // Assert + cacheManager.Received(1).Get(cacheKey); + cacheManager.Received(1).Set(cacheKey, Arg.Is>(d => + d.First().Key == "Config1" && + d.First().Value == value.Config1 && + d.Last().Key == "Config2" && + d.Last().Value == value.Config2 + ), duration); + Assert.NotNull(result); + Assert.Equal("Config1", result.First().Key); + Assert.Equal(value.Config1, result.First().Value); + Assert.Equal("Config2", result.Last().Key); + Assert.Equal(value.Config2, result.Last().Value); + } + + [Fact] + public async Task GetAsync_WhenKeyExists_ReturnsKeyValue() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var value = new + { + Config1 = Guid.NewGuid().ToString(), + Config2 = Guid.NewGuid().ToString() + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).ReturnsNull(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.GetAsync("Config1"); + + // Assert + Assert.NotNull(result); + Assert.Equal(value.Config1, result); + } + + [Fact] + public async Task GetAsync_WhenKeyDoesNotExist_ReturnsNull() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var value = new + { + Config1 = Guid.NewGuid().ToString(), + Config2 = Guid.NewGuid().ToString() + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).ReturnsNull(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var result = await appConfigProvider.GetAsync("Config3"); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task GetAsync_DefaultApplicationIdDoesNotSet_ThrowsException() + { + // Arrange + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .DefaultEnvironment(environmentId) + .DefaultConfigProfile(configProfileId); + + // Act + Task> Act() => appConfigProvider.GetAsync(); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetAsync_DefaultEnvironmentIdDoesNotSet_ThrowsException() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .DefaultApplication(applicationId) + .DefaultConfigProfile(configProfileId); + + // Act + Task> Act() => appConfigProvider.GetAsync(); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetAsync_DefaultConfigProfileIdDoesNotSet_ThrowsException() + { + // Arrange + var environmentId = Guid.NewGuid().ToString(); + var applicationId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .DefaultApplication(applicationId) + .DefaultEnvironment(environmentId); + + // Act + Task> Act() => appConfigProvider.GetAsync(); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetAsync_WhenApplicationIdDoesNotSet_ThrowsException() + { + // Arrange + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + Task> Act() => appConfigProvider.GetAsync(); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetAsync_WhenEnvironmentIdDoesNotSet_ThrowsException() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithConfigProfile(configProfileId); + + // Act + Task> Act() => appConfigProvider.GetAsync(); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetAsync_WhenConfigProfileIdDoesNotSet_ThrowsException() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId); + + // Act + Task> Act() => appConfigProvider.GetAsync(); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetMultipleAsync_ThrowsException() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager); + + // Act + Task> Act() => appConfigProvider.GetMultipleAsync(key); + + // Assert + await Assert.ThrowsAsync(Act); + } + + [Fact] + public async Task GetAsync_PriorToNextAllowedPollTime_ReturnsLastConfig() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var lastConfig = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + cacheManager.Get(cacheKey).Returns(null); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = JsonSerializer.Serialize(lastConfig) + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var currentConfig = await appConfigProvider.GetAsync(); + + // Assert + cacheManager.Received(1).Get(cacheKey); + await client.DidNotReceiveWithAnyArgs().StartConfigurationSessionAsync(null); + await client.DidNotReceiveWithAnyArgs().GetLatestConfigurationAsync(null); + Assert.NotNull(lastConfig); + Assert.NotNull(currentConfig); + Assert.Equal(lastConfig, currentConfig); + } + + [Fact] + public async Task GetAsync_AfterNextAllowedPollTime_RetrieveNewConfig() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(-1); + var nextPollInterval = TimeSpan.FromHours(24); + var nextPollConfigurationToken = Guid.NewGuid().ToString(); + + var lastConfig = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var value = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length, + NextPollConfigurationToken = nextPollConfigurationToken, + NextPollIntervalInSeconds = Convert.ToInt32(nextPollInterval.TotalSeconds) + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).Returns(null); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = JsonSerializer.Serialize(lastConfig) + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var currentConfig = await appConfigProvider.GetAsync(); + + // Assert + cacheManager.Received(1).Get(cacheKey); + await client.DidNotReceiveWithAnyArgs().StartConfigurationSessionAsync(null); + await client.Received(1).GetLatestConfigurationAsync( + Arg.Is(x => x.ConfigurationToken == configurationToken), + Arg.Any()); + Assert.NotNull(lastConfig); + Assert.NotNull(currentConfig); + Assert.NotEqual(lastConfig, currentConfig); + } + + [Fact] + public async Task GetAsync_WhenNoToken_StartsASessionAndRetrieveNewConfig() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = string.Empty; + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(-1); + var nextPollInterval = TimeSpan.FromHours(24); + var nextPollConfigurationToken = Guid.NewGuid().ToString(); + + var lastConfig = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var value = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length, + NextPollConfigurationToken = nextPollConfigurationToken, + NextPollIntervalInSeconds = Convert.ToInt32(nextPollInterval.TotalSeconds) + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).Returns(null); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = JsonSerializer.Serialize(lastConfig) + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var currentConfig = await appConfigProvider.GetAsync(); + + // Assert + cacheManager.Received(1).Get(cacheKey); + await client.Received(1).StartConfigurationSessionAsync( + Arg.Is(x => + x.ApplicationIdentifier == applicationId && + x.EnvironmentIdentifier == environmentId && + x.ConfigurationProfileIdentifier == configProfileId), + Arg.Any()); + await client.Received(1).GetLatestConfigurationAsync( + Arg.Is(x => x.ConfigurationToken == configurationToken), + Arg.Any()); + Assert.NotNull(lastConfig); + Assert.NotNull(currentConfig); + Assert.NotEqual(lastConfig, currentConfig); + } + + [Fact] + public async Task GetAsync_WhenForceFetch_RetrieveNewConfig() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var nextPollInterval = TimeSpan.FromHours(24); + var nextPollConfigurationToken = Guid.NewGuid().ToString(); + + var lastConfig = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var value = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() } + }; + + var response1 = new StartConfigurationSessionResponse + { + InitialConfigurationToken = configurationToken + }; + + var contentType = "application/json"; + var content = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)); + var response2 = new GetLatestConfigurationResponse + { + Configuration = new MemoryStream(content), + ContentType = contentType, + ContentLength = content.Length, + NextPollConfigurationToken = nextPollConfigurationToken, + NextPollIntervalInSeconds = Convert.ToInt32(nextPollInterval.TotalSeconds) + }; + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + client.StartConfigurationSessionAsync(Arg.Any(), Arg.Any()) + .Returns(response1); + + client.GetLatestConfigurationAsync(Arg.Any(), Arg.Any()) + .Returns(response2); + + cacheManager.Get(cacheKey).Returns(null); + + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = JsonSerializer.Serialize(lastConfig) + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId) + .ForceFetch(); + + // Act + var currentConfig = await appConfigProvider.GetAsync(); + + // Assert + await client.Received(1).StartConfigurationSessionAsync( + Arg.Is(x => + x.ApplicationIdentifier == applicationId && + x.EnvironmentIdentifier == environmentId && + x.ConfigurationProfileIdentifier == configProfileId), + Arg.Any()); + await client.Received(1).GetLatestConfigurationAsync( + Arg.Is(x => x.ConfigurationToken == configurationToken), + Arg.Any()); + Assert.NotNull(lastConfig); + Assert.NotNull(currentConfig); + Assert.NotEqual(lastConfig, currentConfig); + } + + [Fact] + public async Task GetMultipleAsync_WithArguments_ThrowsException() + { + // Arrange + var key = Guid.NewGuid().ToString(); + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + Task> Act() => appConfigProvider.GetMultipleAsync(key); + + await Assert.ThrowsAsync(Act); + } +} \ No newline at end of file From b538e1e1cf75c04eac8e56a66df71ccf266b94b7 Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Wed, 28 Feb 2024 13:18:37 +0000 Subject: [PATCH 15/82] add AppConfig provider documentation --- docs/utilities/parameters.md | 62 +++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index c958bf9d..ee89c762 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -4,7 +4,7 @@ description: Utility --- -The Parameters utility provides high-level functionality to retrieve one or multiple parameter values from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html){target="_blank"}, [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/){target="_blank"}, or [Amazon DynamoDB](https://aws.amazon.com/dynamodb/){target="_blank"}. We also provide extensibility to bring your own providers. +The Parameters utility provides high-level functionality to retrieve one or multiple parameter values from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html){target="_blank"}, [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/){target="_blank"}, [Amazon DynamoDB](https://aws.amazon.com/dynamodb/){target="_blank"}, or [AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html){target="_blank"}. We also provide extensibility to bring your own providers. ## Key features @@ -33,6 +33,7 @@ This utility requires additional permissions to work as expected. See the table | Secrets Manager | `SecretsProvider.Get(string)` `SecretsProvider.Get(string)` | `secretsmanager:GetSecretValue` | | DynamoDB | `DynamoDBProvider.Get(string)` `DynamoDBProvider.Get(string)` | `dynamodb:GetItem` | | DynamoDB | `DynamoDBProvider.GetMultiple(string)` `DynamoDBProvider.GetMultiple(string)` | `dynamodb:Query` | +| App Config | `AppConfigProvider.Get()` | `appconfig:StartConfigurationSession` `appconfig:GetLatestConfiguration` | ## SSM Parameter Store @@ -132,6 +133,65 @@ in order to get data from other regions or use specific credentials. } ``` +## App Configurations + +For application configurations in AWS AppConfig, use `AppConfigProvider`. + +Alternatively, you can retrieve the instance of provider and configure its underlying SDK client, +in order to get data from other regions or use specific credentials. + + +=== "AppConfigProvider" + + ```c# hl_lines="10-13 16-18" + using AWS.Lambda.Powertools.Parameters; + using AWS.Lambda.Powertools.Parameters.AppConfig; + + public class Function + { + public async Task FunctionHandler + (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + // Get AppConfig Provider instance + IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider + .DefaultApplication("MyApplicationId") + .DefaultEnvironment("MyEnvironmentId") + .DefaultConfigProfile("MyConfigProfileId"); + + // Retrieve a single configuration, latest version + IDictionary value = await appConfigProvider + .GetAsync() + .ConfigureAwait(false); + } + } + ``` + +=== "AppConfigProvider with an explicit region" + + ```c# hl_lines="10-14" + using AWS.Lambda.Powertools.Parameters; + using AWS.Lambda.Powertools.Parameters.AppConfig; + + public class Function + { + public async Task FunctionHandler + (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + // Get AppConfig Provider instance + IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider + .ConfigureClient(RegionEndpoint.EUCentral1) + .DefaultApplication("MyApplicationId") + .DefaultEnvironment("MyEnvironmentId") + .DefaultConfigProfile("MyConfigProfileId"); + + // Retrieve a single configuration, latest version + IDictionary value = await appConfigProvider + .GetAsync() + .ConfigureAwait(false); + } + } + ``` + ### Additional arguments The AWS Systems Manager Parameter Store provider supports two additional arguments for the `Get()` and `GetMultiple()` methods: From 4593f424d2576d40d2b81b1ecf25525a88670e28 Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Thu, 29 Feb 2024 17:06:47 +0000 Subject: [PATCH 16/82] add feature flags helper methods --- .../AppConfig/AppConfigProvider.cs | 96 +++++++++++++++---- .../AppConfigProviderConfigurationBuilder.cs | 53 ++++++++++ .../AppConfig/IAppConfigProvider.cs | 36 +++++++ .../AppConfig/AppConfigFeatureFlagHelper.cs | 52 ++++++++++ 4 files changed, 216 insertions(+), 21 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigFeatureFlagHelper.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs index 951e7e29..ea597939 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProvider.cs @@ -14,6 +14,7 @@ */ using System.Collections.Concurrent; +using System.Text.Json.Nodes; using Amazon; using Amazon.AppConfigData; using Amazon.AppConfigData.Model; @@ -35,32 +36,32 @@ public class AppConfigProvider : ParameterProvider private string _defaultApplicationId = string.Empty; - + /// /// The default environment Id. /// private string _defaultEnvironmentId = string.Empty; - + /// /// The default configuration profile Id. /// private string _defaultConfigProfileId = string.Empty; - + /// /// Instance of datetime wrapper. /// private readonly IDateTimeWrapper _dateTimeWrapper; - + /// /// Thread safe dictionary to store results. /// private readonly ConcurrentDictionary _results = new(StringComparer.OrdinalIgnoreCase); - + /// /// The client instance. /// private IAmazonAppConfigData? _client; - + /// /// Gets the client instance. /// @@ -88,7 +89,7 @@ internal AppConfigProvider( } #region IParameterProviderConfigurableClient implementation - + /// /// Use a custom client /// @@ -238,7 +239,7 @@ public IAppConfigProvider ConfigureClient(string awsAccessKeyId, string awsSecre _client = new AmazonAppConfigDataClient(awsAccessKeyId, awsSecretAccessKey, awsSessionToken, config); return this; } - + #endregion /// @@ -253,7 +254,7 @@ public IAppConfigProvider DefaultApplication(string applicationId) _defaultApplicationId = applicationId; return this; } - + /// /// Sets the default environment ID or name. /// @@ -266,7 +267,7 @@ public IAppConfigProvider DefaultEnvironment(string environmentId) _defaultEnvironmentId = environmentId; return this; } - + /// /// Sets the default configuration profile ID or name. /// @@ -318,7 +319,7 @@ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder .WithApplication(_defaultApplicationId) .WithEnvironment(_defaultEnvironmentId) .WithConfigProfile(_defaultConfigProfileId); - + } /// @@ -353,29 +354,29 @@ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder .GetAsync() .ConfigureAwait(false); } - + /// /// Get last AppConfig value and transform it to JSON value. /// /// JSON value type. /// The AppConfig JSON value. - public T? Get() where T: class + public T? Get() where T : class { return GetAsync().GetAwaiter().GetResult(); } - + /// /// Get last AppConfig value and transform it to JSON value. /// /// JSON value type. /// The AppConfig JSON value. - public async Task GetAsync() where T: class + public async Task GetAsync() where T : class { return await NewConfigurationBuilder() .GetAsync() .ConfigureAwait(false); } - + /// /// Get parameter value for the provided key. /// @@ -384,7 +385,7 @@ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder /// The parameter value. protected override async Task GetAsync(string key, ParameterProviderConfiguration? config) { - if(config is not AppConfigProviderConfiguration configuration) + if (config is not AppConfigProviderConfiguration configuration) throw new ArgumentNullException(nameof(config)); var cacheKey = AppConfigProviderCacheHelper.GetCacheKey @@ -395,7 +396,7 @@ protected override AppConfigProviderConfigurationBuilder NewConfigurationBuilder ); var result = GetAppConfigResult(cacheKey); - + if (_dateTimeWrapper.UtcNow < result.NextAllowedPollTime) { if (!config.ForceFetch) @@ -427,7 +428,7 @@ await Client.GetLatestConfigurationAsync(request) using (var reader = new StreamReader(response.Configuration)) { - result.LastConfig = + result.LastConfig = await reader.ReadToEndAsync() .ConfigureAwait(false); } @@ -446,7 +447,7 @@ await reader.ReadToEndAsync() { throw new NotSupportedException("Impossible to get multiple values from AWS AppConfig"); } - + /// /// Gets Or Adds AppConfigResult with provided key /// @@ -462,7 +463,7 @@ private AppConfigResult GetAppConfigResult(string cacheKey) return cachedResult; } - + /// /// Starts a configuration session used to retrieve a deployed configuration. /// @@ -479,4 +480,57 @@ private async Task GetInitialConfigurationTokenAsync(AppConfigProviderCo return (await Client.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken; } + + /// + /// Check if the feature flag is enabled. + /// + /// The unique feature key for the feature flag + /// The default value of the flag + /// The feature flag value, or defaultValue if the flag cannot be evaluated + public bool IsFeatureFlagEnabled(string key, bool defaultValue = false) + { + return IsFeatureFlagEnabledAsync(key, defaultValue).GetAwaiter().GetResult(); + } + + /// + /// Check if the feature flag is enabled. + /// + /// The unique feature key for the feature flag + /// The default value of the flag + /// The feature flag value, or defaultValue if the flag cannot be evaluated + public async Task IsFeatureFlagEnabledAsync(string key, bool defaultValue = false) + { + return await GetFeatureFlagAttributeValueAsync(key, AppConfigFeatureFlagHelper.EnabledAttributeKey, + defaultValue).ConfigureAwait(false); + } + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + public T? GetFeatureFlagAttributeValue(string key, string attributeKey, T? defaultValue = default) + { + return GetFeatureFlagAttributeValueAsync(key, attributeKey, defaultValue).GetAwaiter().GetResult(); + } + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + public async Task GetFeatureFlagAttributeValueAsync(string key, string attributeKey, + T? defaultValue = default) + { + return string.IsNullOrWhiteSpace(key) + ? defaultValue + : AppConfigFeatureFlagHelper.GetFeatureFlagAttributeValueAsync(key, attributeKey, defaultValue, + await GetAsync().ConfigureAwait(false)); + } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs index 87df9515..2e7f4da7 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/AppConfigProviderConfigurationBuilder.cs @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +using System.Text.Json.Nodes; using AWS.Lambda.Powertools.Parameters.Internal.AppConfig; using AWS.Lambda.Powertools.Parameters.Configuration; using AWS.Lambda.Powertools.Parameters.Provider; @@ -164,4 +165,56 @@ protected override ParameterProviderConfiguration NewConfiguration() return await base.GetAsync(AppConfigProviderCacheHelper.GetCacheKey(_applicationId, _environmentId, _configProfileId)).ConfigureAwait(false); } + + /// + /// Check if the feature flag is enabled. + /// + /// The unique feature key for the feature flag + /// The default value of the flag + /// The feature flag value, or defaultValue if the flag cannot be evaluated + public bool IsFeatureFlagEnabled(string key, bool defaultValue = false) + { + return IsFeatureFlagEnabledAsync(key, defaultValue).GetAwaiter().GetResult(); + } + + /// + /// Check if the feature flag is enabled. + /// + /// The unique feature key for the feature flag + /// The default value of the flag + /// The feature flag value, or defaultValue if the flag cannot be evaluated + public async Task IsFeatureFlagEnabledAsync(string key, bool defaultValue = false) + { + return await GetFeatureFlagAttributeValueAsync(key, AppConfigFeatureFlagHelper.EnabledAttributeKey, + defaultValue).ConfigureAwait(false); + } + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + public T? GetFeatureFlagAttributeValue(string key, string attributeKey, T? defaultValue = default) + { + return GetFeatureFlagAttributeValueAsync(key, attributeKey, defaultValue).GetAwaiter().GetResult(); + } + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + public async Task GetFeatureFlagAttributeValueAsync(string key, string attributeKey, T? defaultValue = default) + { + return string.IsNullOrWhiteSpace(key) + ? defaultValue + : AppConfigFeatureFlagHelper.GetFeatureFlagAttributeValueAsync(key, attributeKey, defaultValue, + await GetAsync().ConfigureAwait(false)); + } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs index 32debbea..83118d9a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/AppConfig/IAppConfigProvider.cs @@ -77,5 +77,41 @@ public interface IAppConfigProvider : IParameterProviderJSON value type. /// The AppConfig JSON value. Task GetAsync() where T : class; + + /// + /// Check if the feature flag is enabled. + /// + /// The unique feature key for the feature flag + /// The default value of the flag + /// The feature flag value, or defaultValue if the flag cannot be evaluated + bool IsFeatureFlagEnabled(string key, bool defaultValue = false); + + /// + /// Check if the feature flag is enabled. + /// + /// The unique feature key for the feature flag + /// The default value of the flag + /// The feature flag value, or defaultValue if the flag cannot be evaluated + Task IsFeatureFlagEnabledAsync(string key, bool defaultValue = false); + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + T? GetFeatureFlagAttributeValue(string key, string attributeKey, T? defaultValue = default); + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + Task GetFeatureFlagAttributeValueAsync(string key, string attributeKey, T? defaultValue = default); } diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigFeatureFlagHelper.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigFeatureFlagHelper.cs new file mode 100644 index 00000000..d43788b1 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/AppConfig/AppConfigFeatureFlagHelper.cs @@ -0,0 +1,52 @@ +/* + * 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.Nodes; + +namespace AWS.Lambda.Powertools.Parameters.Internal.AppConfig; + +/// +/// AppConfigProviderCacheHelper class. +/// +internal static class AppConfigFeatureFlagHelper +{ + internal const string EnabledAttributeKey = "enabled"; + + /// + /// Get feature flag's attribute value. + /// + /// The unique feature key for the feature flag + /// The unique attribute key for the feature flag + /// The default value of the feature flag's attribute value + /// The AppConfig JSON value of the feature flag + /// The type of the value to obtain from feature flag's attribute. + /// The feature flag's attribute value. + internal static T? GetFeatureFlagAttributeValueAsync(string key, string attributeKey, T? defaultValue, + JsonObject? featureFlag) + { + if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(attributeKey) || featureFlag is null) + return defaultValue; + + var keyElement = featureFlag[key]; + if (keyElement is null) + return defaultValue; + + var attributeElement = keyElement[attributeKey]; + if (attributeElement is null) + return defaultValue; + + return attributeElement.GetValue(); + } +} \ No newline at end of file From 433a059b0d02198b769599baa58fdd7c99cc2c26 Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 1 Mar 2024 09:54:28 +0000 Subject: [PATCH 17/82] add feature flags unit tests --- .../AppConfig/AppConfigProviderTest.cs | 353 ++++++++++++++++++ 1 file changed, 353 insertions(+) diff --git a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs index ee9b2c46..80a4feeb 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs @@ -15,6 +15,7 @@ using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; using Amazon.AppConfigData; using Amazon.AppConfigData.Model; using AWS.Lambda.Powertools.Parameters.AppConfig; @@ -1154,4 +1155,356 @@ public async Task GetMultipleAsync_WithArguments_ThrowsException() await Assert.ThrowsAsync(Act); } + + [Fact] + public async Task IsFeatureFlagEnabled_WhenKeyIsEmptyOrNull_ReturnsDefaultValue() + { + // Arrange + var featureFlagKey = string.Empty; + var defaultFlagValue = false; + + var appConfigProvider = new AppConfigProvider(Substitute.For()); + + // Act + var flagValue = await appConfigProvider.IsFeatureFlagEnabledAsync(featureFlagKey) + .ConfigureAwait(false); + + // Assert + Assert.Equal(flagValue, defaultFlagValue); + } + + [Fact] + public async Task IsFeatureFlagEnabled_WhenKeyIsEmptyOrNull_ReturnsSpecifiedDefaultValue() + { + // Arrange + var featureFlagKey = string.Empty; + var defaultFlagValue = true; + + var appConfigProvider = new AppConfigProvider(Substitute.For()); + + // Act + var flagValue = await appConfigProvider.IsFeatureFlagEnabledAsync(featureFlagKey, defaultFlagValue) + .ConfigureAwait(false); + + // Assert + Assert.Equal(flagValue, defaultFlagValue); + } + + [Fact] + public async Task IsFeatureFlagEnabled_WhenFlagIsEnabled_ReturnsTrue() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var lastConfig = new + { + FeatureFlagOne = new + { + enabled = true, + attributeOne = "TestOne", + attributeTwo = 10 + }, + FeatureFlagTwo = new + { + enabled = false, + attributeOne = "TestTwo", + attributeTwo = 20 + }, + }; + + var lastConfigStr = JsonSerializer.Serialize(lastConfig); + var transformedValue = JsonSerializer.Deserialize(lastConfigStr); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + cacheManager.Get(cacheKey).Returns(transformedValue); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = lastConfigStr + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var currentConfig = await appConfigProvider.IsFeatureFlagEnabledAsync("FeatureFlagOne").ConfigureAwait(false); + + // Assert + Assert.Equal(currentConfig, lastConfig.FeatureFlagOne.enabled); + } + + [Fact] + public async Task IsFeatureFlagEnabled_WhenFlagIsDisabled_ReturnsFalse() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var lastConfig = new + { + FeatureFlagOne = new + { + enabled = true, + attributeOne = "TestOne", + attributeTwo = 10 + }, + FeatureFlagTwo = new + { + enabled = false, + attributeOne = "TestTwo", + attributeTwo = 20 + }, + }; + + var lastConfigStr = JsonSerializer.Serialize(lastConfig); + var transformedValue = JsonSerializer.Deserialize(lastConfigStr); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + cacheManager.Get(cacheKey).Returns(transformedValue); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = lastConfigStr + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var currentConfig = await appConfigProvider.IsFeatureFlagEnabledAsync("FeatureFlagTwo").ConfigureAwait(false); + + // Assert + Assert.Equal(currentConfig, lastConfig.FeatureFlagTwo.enabled); + } + + [Fact] + public async Task GetFeatureFlagAttribute_WhenHasAttribute_ReturnsAttributeValue() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var lastConfig = new + { + FeatureFlagOne = new + { + enabled = true, + attributeOne = "TestOne", + attributeTwo = 10 + }, + FeatureFlagTwo = new + { + enabled = false, + attributeOne = "TestTwo", + attributeTwo = 20 + }, + }; + + var lastConfigStr = JsonSerializer.Serialize(lastConfig); + var transformedValue = JsonSerializer.Deserialize(lastConfigStr); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + cacheManager.Get(cacheKey).Returns(transformedValue); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = lastConfigStr + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var attributeValue1 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "attributeOne").ConfigureAwait(false); + var attributeValue2 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "attributeTwo").ConfigureAwait(false); + + // Assert + Assert.Equal(attributeValue1, lastConfig.FeatureFlagOne.attributeOne); + Assert.Equal(attributeValue2, lastConfig.FeatureFlagOne.attributeTwo); + } + + [Fact] + public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsNull() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var lastConfig = new + { + FeatureFlagOne = new + { + enabled = true, + attributeOne = "TestOne", + attributeTwo = 10 + }, + FeatureFlagTwo = new + { + enabled = false, + attributeOne = "TestTwo", + attributeTwo = 20 + }, + }; + + var lastConfigStr = JsonSerializer.Serialize(lastConfig); + var transformedValue = JsonSerializer.Deserialize(lastConfigStr); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + cacheManager.Get(cacheKey).Returns(transformedValue); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = lastConfigStr + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var attributeValue1 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "INVALID").ConfigureAwait(false); + + // Assert + Assert.Null(attributeValue1); + } + + [Fact] + public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsSpecifiedDefaultValue() + { + // Arrange + var applicationId = Guid.NewGuid().ToString(); + var environmentId = Guid.NewGuid().ToString(); + var configProfileId = Guid.NewGuid().ToString(); + var configurationToken = Guid.NewGuid().ToString(); + var defaultAttributeVale = Guid.NewGuid().ToString(); + var cacheKey = AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId); + + var dateTimeNow = DateTime.UtcNow; + var nextAllowedPollTime = dateTimeNow.AddSeconds(10); + var lastConfig = new + { + FeatureFlagOne = new + { + enabled = true, + attributeOne = "TestOne", + attributeTwo = 10 + }, + FeatureFlagTwo = new + { + enabled = false, + attributeOne = "TestTwo", + attributeTwo = 20 + }, + }; + + var lastConfigStr = JsonSerializer.Serialize(lastConfig); + var transformedValue = JsonSerializer.Deserialize(lastConfigStr); + + var cacheManager = Substitute.For(); + var client = Substitute.For(); + var transformerManager = Substitute.For(); + var dateTimeWrapper = Substitute.For(); + + cacheManager.Get(cacheKey).Returns(transformedValue); + dateTimeWrapper.UtcNow.Returns(dateTimeNow); + + var appConfigResult = new AppConfigResult + { + PollConfigurationToken = configurationToken, + NextAllowedPollTime = nextAllowedPollTime, + LastConfig = lastConfigStr + }; + + var appConfigProvider = new AppConfigProvider(dateTimeWrapper, + AppConfigProviderCacheHelper.GetCacheKey(applicationId, environmentId, configProfileId), + appConfigResult) + .UseClient(client) + .UseCacheManager(cacheManager) + .UseTransformerManager(transformerManager) + .WithApplication(applicationId) + .WithEnvironment(environmentId) + .WithConfigProfile(configProfileId); + + // Act + var attributeValue1 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "INVALID", defaultAttributeVale).ConfigureAwait(false); + + // Assert + Assert.Equal(attributeValue1, defaultAttributeVale); + } } \ No newline at end of file From 070f907f381491b316a9f1803df8f546e4665460 Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 1 Mar 2024 09:55:27 +0000 Subject: [PATCH 18/82] add feature flags unit tests clean up --- .../AppConfig/AppConfigProviderTest.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs index 80a4feeb..8c664e4e 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs @@ -1155,14 +1155,14 @@ public async Task GetMultipleAsync_WithArguments_ThrowsException() await Assert.ThrowsAsync(Act); } - + [Fact] public async Task IsFeatureFlagEnabled_WhenKeyIsEmptyOrNull_ReturnsDefaultValue() { // Arrange var featureFlagKey = string.Empty; var defaultFlagValue = false; - + var appConfigProvider = new AppConfigProvider(Substitute.For()); // Act @@ -1189,7 +1189,7 @@ public async Task IsFeatureFlagEnabled_WhenKeyIsEmptyOrNull_ReturnsSpecifiedDefa // Assert Assert.Equal(flagValue, defaultFlagValue); } - + [Fact] public async Task IsFeatureFlagEnabled_WhenFlagIsEnabled_ReturnsTrue() { @@ -1217,7 +1217,7 @@ public async Task IsFeatureFlagEnabled_WhenFlagIsEnabled_ReturnsTrue() attributeTwo = 20 }, }; - + var lastConfigStr = JsonSerializer.Serialize(lastConfig); var transformedValue = JsonSerializer.Deserialize(lastConfigStr); @@ -1252,7 +1252,7 @@ public async Task IsFeatureFlagEnabled_WhenFlagIsEnabled_ReturnsTrue() // Assert Assert.Equal(currentConfig, lastConfig.FeatureFlagOne.enabled); } - + [Fact] public async Task IsFeatureFlagEnabled_WhenFlagIsDisabled_ReturnsFalse() { @@ -1280,7 +1280,7 @@ public async Task IsFeatureFlagEnabled_WhenFlagIsDisabled_ReturnsFalse() attributeTwo = 20 }, }; - + var lastConfigStr = JsonSerializer.Serialize(lastConfig); var transformedValue = JsonSerializer.Deserialize(lastConfigStr); @@ -1343,7 +1343,7 @@ public async Task GetFeatureFlagAttribute_WhenHasAttribute_ReturnsAttributeValue attributeTwo = 20 }, }; - + var lastConfigStr = JsonSerializer.Serialize(lastConfig); var transformedValue = JsonSerializer.Deserialize(lastConfigStr); @@ -1373,14 +1373,16 @@ public async Task GetFeatureFlagAttribute_WhenHasAttribute_ReturnsAttributeValue .WithConfigProfile(configProfileId); // Act - var attributeValue1 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "attributeOne").ConfigureAwait(false); - var attributeValue2 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "attributeTwo").ConfigureAwait(false); + var attributeValue1 = await appConfigProvider + .GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "attributeOne").ConfigureAwait(false); + var attributeValue2 = await appConfigProvider + .GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "attributeTwo").ConfigureAwait(false); // Assert Assert.Equal(attributeValue1, lastConfig.FeatureFlagOne.attributeOne); Assert.Equal(attributeValue2, lastConfig.FeatureFlagOne.attributeTwo); } - + [Fact] public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsNull() { @@ -1408,7 +1410,7 @@ public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsNull( attributeTwo = 20 }, }; - + var lastConfigStr = JsonSerializer.Serialize(lastConfig); var transformedValue = JsonSerializer.Deserialize(lastConfigStr); @@ -1438,12 +1440,13 @@ public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsNull( .WithConfigProfile(configProfileId); // Act - var attributeValue1 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "INVALID").ConfigureAwait(false); + var attributeValue1 = await appConfigProvider + .GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "INVALID").ConfigureAwait(false); // Assert Assert.Null(attributeValue1); } - + [Fact] public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsSpecifiedDefaultValue() { @@ -1472,7 +1475,7 @@ public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsSpeci attributeTwo = 20 }, }; - + var lastConfigStr = JsonSerializer.Serialize(lastConfig); var transformedValue = JsonSerializer.Deserialize(lastConfigStr); @@ -1502,7 +1505,8 @@ public async Task GetFeatureFlagAttribute_WhenAttributeDoesNotExist_ReturnsSpeci .WithConfigProfile(configProfileId); // Act - var attributeValue1 = await appConfigProvider.GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "INVALID", defaultAttributeVale).ConfigureAwait(false); + var attributeValue1 = await appConfigProvider + .GetFeatureFlagAttributeValueAsync("FeatureFlagOne", "INVALID", defaultAttributeVale).ConfigureAwait(false); // Assert Assert.Equal(attributeValue1, defaultAttributeVale); From 215aeeb0208a0b3a83b24e3b667a4a86d5a0e70e Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 1 Mar 2024 15:32:20 +0000 Subject: [PATCH 19/82] update documentation --- docs/utilities/parameters.md | 160 ++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 59 deletions(-) diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index ee89c762..0f9895f3 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -133,65 +133,6 @@ in order to get data from other regions or use specific credentials. } ``` -## App Configurations - -For application configurations in AWS AppConfig, use `AppConfigProvider`. - -Alternatively, you can retrieve the instance of provider and configure its underlying SDK client, -in order to get data from other regions or use specific credentials. - - -=== "AppConfigProvider" - - ```c# hl_lines="10-13 16-18" - using AWS.Lambda.Powertools.Parameters; - using AWS.Lambda.Powertools.Parameters.AppConfig; - - public class Function - { - public async Task FunctionHandler - (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) - { - // Get AppConfig Provider instance - IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider - .DefaultApplication("MyApplicationId") - .DefaultEnvironment("MyEnvironmentId") - .DefaultConfigProfile("MyConfigProfileId"); - - // Retrieve a single configuration, latest version - IDictionary value = await appConfigProvider - .GetAsync() - .ConfigureAwait(false); - } - } - ``` - -=== "AppConfigProvider with an explicit region" - - ```c# hl_lines="10-14" - using AWS.Lambda.Powertools.Parameters; - using AWS.Lambda.Powertools.Parameters.AppConfig; - - public class Function - { - public async Task FunctionHandler - (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) - { - // Get AppConfig Provider instance - IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider - .ConfigureClient(RegionEndpoint.EUCentral1) - .DefaultApplication("MyApplicationId") - .DefaultEnvironment("MyEnvironmentId") - .DefaultConfigProfile("MyConfigProfileId"); - - // Retrieve a single configuration, latest version - IDictionary value = await appConfigProvider - .GetAsync() - .ConfigureAwait(false); - } - } - ``` - ### Additional arguments The AWS Systems Manager Parameter Store provider supports two additional arguments for the `Get()` and `GetMultiple()` methods: @@ -401,6 +342,7 @@ You can retrieve multiple parameters sharing the same `id` by having a sort key "param-b": "my-value-b", "param-c": "my-value-c" } + ``` **Customizing DynamoDBProvider** @@ -437,6 +379,106 @@ DynamoDB provider can be customized at initialization to match your table struct } ``` +## App Configurations + +For application configurations in AWS AppConfig, use `AppConfigProvider`. + +Alternatively, you can retrieve the instance of provider and configure its underlying SDK client, +in order to get data from other regions or use specific credentials. + +=== "AppConfigProvider" + + ```c# hl_lines="10-13 16-18" + using AWS.Lambda.Powertools.Parameters; + using AWS.Lambda.Powertools.Parameters.AppConfig; + + public class Function + { + public async Task FunctionHandler + (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + // Get AppConfig Provider instance + IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider + .DefaultApplication("MyApplicationId") + .DefaultEnvironment("MyEnvironmentId") + .DefaultConfigProfile("MyConfigProfileId"); + + // Retrieve a single configuration, latest version + IDictionary value = await appConfigProvider + .GetAsync() + .ConfigureAwait(false); + } + } + ``` + +=== "AppConfigProvider with an explicit region" + + ```c# hl_lines="10-14" + using AWS.Lambda.Powertools.Parameters; + using AWS.Lambda.Powertools.Parameters.AppConfig; + + public class Function + { + public async Task FunctionHandler + (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + // Get AppConfig Provider instance + IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider + .ConfigureClient(RegionEndpoint.EUCentral1) + .DefaultApplication("MyApplicationId") + .DefaultEnvironment("MyEnvironmentId") + .DefaultConfigProfile("MyConfigProfileId"); + + // Retrieve a single configuration, latest version + IDictionary value = await appConfigProvider + .GetAsync() + .ConfigureAwait(false); + } + } + ``` + +**Using AWS AppConfig Feature Flags** + +Feature flagging is a powerful tool that allows safely pushing out new features in a measured and usually gradual way. AppConfig provider offers helper methods to make it easier to work with feature flags. + +=== "AppConfigProvider" + + ```c# hl_lines="10-13 16-18 23-25" + using AWS.Lambda.Powertools.Parameters; + using AWS.Lambda.Powertools.Parameters.AppConfig; + + public class Function + { + public async Task FunctionHandler + (APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + // Get AppConfig Provider instance + IAppConfigProvider appConfigProvider = ParametersManager.AppConfigProvider + .DefaultApplication("MyApplicationId") + .DefaultEnvironment("MyEnvironmentId") + .DefaultConfigProfile("MyConfigProfileId"); + + // Check if feature flag is enabled + var isFeatureFlagEnabled = await appConfigProvider + .IsFeatureFlagEnabledAsync("MyFeatureFlag") + .ConfigureAwait(false); + + if (isFeatureFlagEnabled) + { + // Retrieve an attribute value of the feature flag + var strAttValue = await appConfigProvider + .GetFeatureFlagAttributeValueAsync("MyFeatureFlag", "StringAttribute") + .ConfigureAwait(false); + + // Retrieve another attribute value of the feature flag + var numberAttValue = await appConfigProvider + .GetFeatureFlagAttributeValueAsync("MyFeatureFlag", "NumberAttribute") + .ConfigureAwait(false); + } + } + } + ``` + ## Advanced configuration ### Caching From 5b078ed06a506ec134d3c1bec1fb0555804218f4 Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 1 Mar 2024 15:37:28 +0000 Subject: [PATCH 20/82] update documentation --- docs/utilities/parameters.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index 0f9895f3..6b617084 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -388,7 +388,7 @@ in order to get data from other regions or use specific credentials. === "AppConfigProvider" - ```c# hl_lines="10-13 16-18" + ```c# hl_lines="10-13 16-18" using AWS.Lambda.Powertools.Parameters; using AWS.Lambda.Powertools.Parameters.AppConfig; @@ -413,7 +413,7 @@ in order to get data from other regions or use specific credentials. === "AppConfigProvider with an explicit region" - ```c# hl_lines="10-14" + ```c# hl_lines="10-14" using AWS.Lambda.Powertools.Parameters; using AWS.Lambda.Powertools.Parameters.AppConfig; @@ -443,7 +443,7 @@ Feature flagging is a powerful tool that allows safely pushing out new features === "AppConfigProvider" - ```c# hl_lines="10-13 16-18 23-25" + ```c# hl_lines="10-13 16-18 23-25" using AWS.Lambda.Powertools.Parameters; using AWS.Lambda.Powertools.Parameters.AppConfig; From 12be37a4b92004ba26a4ee6569575accc512d8bd Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 8 Mar 2024 09:30:08 +0000 Subject: [PATCH 21/82] update nuget versions --- .../AWS.Lambda.Powertools.Idempotency.csproj | 4 +- .../AWS.Lambda.Powertools.Tracing.csproj | 4 +- libraries/src/Directory.Packages.props | 26 +++++----- .../Helpers/Helpers.cs | 51 +++++++++---------- ...AWS.Lambda.Powertools.Logging.Tests.csproj | 4 +- libraries/tests/Directory.Packages.props | 24 ++++----- 6 files changed, 56 insertions(+), 57 deletions(-) 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 51e3a820..d12fe902 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 @@ -13,8 +13,8 @@ - - + + diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj b/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj index 9698055a..176cc9d0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props index cb1ab08f..e73f3288 100644 --- a/libraries/src/Directory.Packages.props +++ b/libraries/src/Directory.Packages.props @@ -5,20 +5,20 @@ - - - - + + + + - - - - - - + + + + + + - - - + + + \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs index 2729b507..5b3cc8df 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs @@ -17,7 +17,6 @@ using System.IO; using System.Linq; using System.Text; -using Amazon.DynamoDBv2.Model; using Amazon.Lambda.DynamoDBEvents; using Amazon.Lambda.KinesisEvents; using Amazon.Lambda.SQSEvents; @@ -101,15 +100,15 @@ internal static class Helpers new DynamoDBEvent.DynamodbStreamRecord { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "1" } @@ -117,15 +116,15 @@ internal static class Helpers new DynamoDBEvent.DynamodbStreamRecord { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "2" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "2" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "failure" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "failure" } } }, SequenceNumber = "2" } @@ -133,15 +132,15 @@ internal static class Helpers new DynamoDBEvent.DynamodbStreamRecord { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "3" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "3" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":3,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":3,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "3" } @@ -149,15 +148,15 @@ internal static class Helpers new DynamoDBEvent.DynamodbStreamRecord { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "4" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "4" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":4,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":4,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "4" } @@ -165,15 +164,15 @@ internal static class Helpers new DynamoDBEvent.DynamodbStreamRecord { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "5" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "5" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":5,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":5,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "5" } diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj index 9d49d514..f98e40b6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/AWS.Lambda.Powertools.Logging.Tests.csproj @@ -13,14 +13,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libraries/tests/Directory.Packages.props b/libraries/tests/Directory.Packages.props index f5515ff4..53241e7b 100644 --- a/libraries/tests/Directory.Packages.props +++ b/libraries/tests/Directory.Packages.props @@ -3,20 +3,20 @@ true - - + + - - - + + + - - + + - - - - - + + + + + \ No newline at end of file From 6a866f4f8785d66b3285d91ac398c622322baa6e Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 8 Mar 2024 11:19:10 +0000 Subject: [PATCH 22/82] update package versions for release 1.9.0 --- version.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/version.json b/version.json index 4c8f04bb..57abf181 100644 --- a/version.json +++ b/version.json @@ -1,12 +1,12 @@ { "Core": { - "Logging": "1.5.0", - "Metrics": "1.6.0", - "Tracing": "1.4.0" + "Logging": "1.5.1", + "Metrics": "1.6.1", + "Tracing": "1.4.1" }, "Utilities": { - "Parameters": "1.2.0", - "Idempotency": "1.1.0", - "BatchProcessing": "1.1.0" + "Parameters": "1.3.0", + "Idempotency": "1.1.1", + "BatchProcessing": "1.1.1" } } From e4e8179500bdf18b2005163907b0b2be24ddfc31 Mon Sep 17 00:00:00 2001 From: Amir Khairalomoum Date: Fri, 8 Mar 2024 11:46:03 +0000 Subject: [PATCH 23/82] update package versions for examples --- .../src/HelloWorld/HelloWorld.csproj | 6 +++--- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 14 +++++++------- .../Idempotency/src/HelloWorld/HelloWorld.csproj | 6 +++--- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 12 ++++++------ examples/Logging/src/HelloWorld/HelloWorld.csproj | 8 ++++---- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 12 ++++++------ examples/Metrics/src/HelloWorld/HelloWorld.csproj | 8 ++++---- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 12 ++++++------ .../cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj | 4 ++-- .../Parameters/src/HelloWorld/HelloWorld.csproj | 6 +++--- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 10 +++++----- .../LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj | 2 +- .../LambdaPowertoolsAPI.Tests.csproj | 12 ++++++------ examples/Tracing/src/HelloWorld/HelloWorld.csproj | 8 ++++---- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 12 ++++++------ 15 files changed, 66 insertions(+), 66 deletions(-) diff --git a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj index ddbd7f4e..fbc9d358 100644 --- a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj +++ b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj @@ -5,10 +5,10 @@ enable - - + + - + diff --git a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj index fe9b08e0..c27c750c 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,14 +3,14 @@ net6.0;net8.0 - + - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj index 645f82af..9f776ce0 100644 --- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj @@ -5,9 +5,9 @@ enable - - - + + + diff --git a/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj index 6afa095c..124434c8 100644 --- a/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,13 +3,13 @@ net6.0;net8.0 - + - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/examples/Logging/src/HelloWorld/HelloWorld.csproj b/examples/Logging/src/HelloWorld/HelloWorld.csproj index 57b2016b..a970a2f0 100644 --- a/examples/Logging/src/HelloWorld/HelloWorld.csproj +++ b/examples/Logging/src/HelloWorld/HelloWorld.csproj @@ -5,10 +5,10 @@ enable - - - + + + - + diff --git a/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj index 7a509010..fd219221 100644 --- a/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,14 +3,14 @@ net6.0;net8.0 - + - - - + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/examples/Metrics/src/HelloWorld/HelloWorld.csproj b/examples/Metrics/src/HelloWorld/HelloWorld.csproj index a3bd7722..d6eee6b5 100644 --- a/examples/Metrics/src/HelloWorld/HelloWorld.csproj +++ b/examples/Metrics/src/HelloWorld/HelloWorld.csproj @@ -5,11 +5,11 @@ enable - - - + + + - + diff --git a/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj index 7a509010..fd219221 100644 --- a/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,14 +3,14 @@ net6.0;net8.0 - + - - - + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj b/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj index cf56c361..f58f530c 100644 --- a/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj +++ b/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj @@ -6,8 +6,8 @@ HelloWorld.Cfn - - + + diff --git a/examples/Parameters/src/HelloWorld/HelloWorld.csproj b/examples/Parameters/src/HelloWorld/HelloWorld.csproj index 8d258c52..f49341df 100644 --- a/examples/Parameters/src/HelloWorld/HelloWorld.csproj +++ b/examples/Parameters/src/HelloWorld/HelloWorld.csproj @@ -5,9 +5,9 @@ enable - - - + + + diff --git a/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj index 0c7418eb..75fef51b 100644 --- a/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,13 +3,13 @@ net6.0;net8.0 - + - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj index e3ad28df..ce9c7cb4 100644 --- a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj +++ b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj @@ -12,7 +12,7 @@ true - + diff --git a/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj b/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj index de239d45..d9cdaef4 100644 --- a/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj +++ b/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj @@ -16,13 +16,13 @@ - + - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/examples/Tracing/src/HelloWorld/HelloWorld.csproj b/examples/Tracing/src/HelloWorld/HelloWorld.csproj index e7338a4a..e6bf4310 100644 --- a/examples/Tracing/src/HelloWorld/HelloWorld.csproj +++ b/examples/Tracing/src/HelloWorld/HelloWorld.csproj @@ -5,11 +5,11 @@ enable - - - + + + - + diff --git a/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj index 7a509010..fd219221 100644 --- a/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,14 +3,14 @@ net6.0;net8.0 - + - - - + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From ac03a89d6278aa70db10ccd082ccab3248d12bb6 Mon Sep 17 00:00:00 2001 From: Mariya Koseva Date: Fri, 8 Mar 2024 15:45:54 +0100 Subject: [PATCH 24/82] Add check for null property value --- .../AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs index ada89f89..d4a61e92 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs @@ -247,7 +247,7 @@ static object ToDictionary(object values) foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(values)) { object obj = propertyDescriptor.GetValue(values); - if (obj.GetType().Name.Contains("Anonymous")) + if (obj is not null && obj.GetType().Name.Contains("Anonymous")) { dict.Add(propertyDescriptor.Name,ToDictionary(obj)); } From a0e07fcf0d1e83e97e46d7e8c75a7e4abc39d825 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 14:45:38 +0000 Subject: [PATCH 25/82] Update HelloWorld.Tests.csproj Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj index c27c750c..fd097307 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -4,6 +4,9 @@ + + + @@ -18,4 +21,4 @@ - \ No newline at end of file + From 7617ec75e5ce1f5b9df9de5715bf33a5ab889705 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 14:46:13 +0000 Subject: [PATCH 26/82] Update FunctionTest.cs Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../test/HelloWorld.Test/FunctionTest.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs b/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs index 6cc8af5d..205c7edc 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs +++ b/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs @@ -17,7 +17,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; -using Amazon.DynamoDBv2.Model; using Amazon.Lambda.DynamoDBEvents; using Amazon.Lambda.KinesisEvents; using Amazon.Lambda.SQSEvents; @@ -238,15 +237,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "1" } @@ -254,15 +253,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "failure" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "failure" } } }, SequenceNumber = "2" } @@ -270,15 +269,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "3" } @@ -301,4 +300,4 @@ public Task TestDynamoDb() return Task.CompletedTask; } } -} \ No newline at end of file +} From 2ebdc8b6e2c2419d37004dcc8e4ad7a403a29468 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:35:37 +0000 Subject: [PATCH 27/82] Update HelloWorld.Tests.csproj Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj index fd097307..fd375620 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -4,9 +4,6 @@ - - - From 95517338bb19f19162cecd4ebfec87bf6cdd2ec5 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:35:55 +0000 Subject: [PATCH 28/82] Update FunctionTest.cs Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../test/HelloWorld.Test/FunctionTest.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs b/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs index 205c7edc..aedfd39e 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs +++ b/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs @@ -17,6 +17,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; +using Amazon.DynamoDBv2.Model; using Amazon.Lambda.DynamoDBEvents; using Amazon.Lambda.KinesisEvents; using Amazon.Lambda.SQSEvents; @@ -237,15 +238,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new DynamoDBEvent.StreamRecord + Dynamodb = new StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } + { "Id", new AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "1" } @@ -253,15 +254,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new DynamoDBEvent.StreamRecord + Dynamodb = new StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } + { "Id", new AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new DynamoDBEvent.AttributeValue { S = "failure" } } + { "Product", new AttributeValue { S = "failure" } } }, SequenceNumber = "2" } @@ -269,15 +270,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new DynamoDBEvent.StreamRecord + Dynamodb = new StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } + { "Id", new AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "3" } From 92a9ce80933e809763ba2973f53e6c7bb1c3a5c0 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:05:58 +0000 Subject: [PATCH 29/82] Update FunctionTest.cs Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../test/HelloWorld.Test/FunctionTest.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs b/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs index aedfd39e..205c7edc 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs +++ b/examples/BatchProcessing/test/HelloWorld.Test/FunctionTest.cs @@ -17,7 +17,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; -using Amazon.DynamoDBv2.Model; using Amazon.Lambda.DynamoDBEvents; using Amazon.Lambda.KinesisEvents; using Amazon.Lambda.SQSEvents; @@ -238,15 +237,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "1" } @@ -254,15 +253,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "failure" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "failure" } } }, SequenceNumber = "2" } @@ -270,15 +269,15 @@ public Task TestDynamoDb() new() { EventID = "1", - Dynamodb = new StreamRecord + Dynamodb = new DynamoDBEvent.StreamRecord { - Keys = new Dictionary + Keys = new Dictionary { - { "Id", new AttributeValue { N = "1" } } + { "Id", new DynamoDBEvent.AttributeValue { N = "1" } } }, - NewImage = new Dictionary + NewImage = new Dictionary { - { "Product", new AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } + { "Product", new DynamoDBEvent.AttributeValue { S = "{\"Id\":1,\"Name\":\"product-name\",\"Price\":14}" } } }, SequenceNumber = "3" } From be541688a1284bae24c79b0e7807dc4007be773d Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:06:12 +0000 Subject: [PATCH 30/82] Update HelloWorld.Tests.csproj Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .../test/HelloWorld.Test/HelloWorld.Tests.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj index fd375620..fd097307 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -4,6 +4,9 @@ + + + From 3aaf67dba60cd7171f7b600eeebbc967c3d60dac Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 18:16:33 +0000 Subject: [PATCH 31/82] Update HelloWorld.csproj Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj index fbc9d358..1d970ca9 100644 --- a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj +++ b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj @@ -7,8 +7,8 @@ - - + + From 87b27da9e281b1a42c911a787f6917b4e575c8c3 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Sun, 10 Mar 2024 19:58:29 +0000 Subject: [PATCH 32/82] update api docs build workflow Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .github/workflows/docs.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index da331674..89dc3a19 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -57,10 +57,12 @@ jobs: uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a with: dotnet-version: 6.0.405 - - uses: nikeee/docfx-action@b9c2cf92e3b4aa06878a1410833a8828b4bdcd26 #1.0.0 - name: Build Documentation - with: - args: apidocs/docfx.json + + - name: Build Api Docs + run: | + dotnet tool install -g docfx + docfx apidocs/docfx.json + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e1e17a757e536f70e52b5a12b2e8d1d1c60e04ef with: From b3486676c6d3a2ffa3c2f432e50599bbbb4ad1ca Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:09:10 +0000 Subject: [PATCH 33/82] Update index to add supported runtimes Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- docs/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 515bd07d..b78a36c7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,10 @@ description: Powertools for AWS Lambda (.NET) # Powertools for AWS Lambda (.NET) -Powertools for AWS Lambda (.NET) (which from here will be referred as Powertools) is a suite of utilities for [AWS Lambda](https://aws.amazon.com/lambda/) functions to ease adopting best practices such as tracing, structured logging, custom metrics, and more. Please note, **Powertools for AWS Lambda (.NET) is optimized for .NET 6+**. +Powertools for AWS Lambda (.NET) (which from here will be referred as Powertools) is a suite of utilities for [AWS Lambda](https://aws.amazon.com/lambda/) functions to ease adopting best practices such as tracing, structured logging, custom metrics, and more. + +!!! info + **Supports .NET 6 and .NET 8 runtimes** ???+ tip Powertools is also available for [Python](https://docs.powertools.aws.dev/lambda/python/){target="_blank"}, [Java](https://docs.powertools.aws.dev/lambda/java/){target="_blank"}, and [TypeScript](https://docs.powertools.aws.dev/lambda/typescript/latest/){target="_blank"}. From cf31888f731bac0f7ebb9f8f4dbd2f838e1b0fc9 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:15:14 +0000 Subject: [PATCH 34/82] Update README.md Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41b3cb34..59ffc737 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Powertools for AWS Lambda (.NET) provides three core utilities: ### Installation -The Powertools for AWS Lambda (.NET) utilities (.NET 6) are available as NuGet packages. You can install the packages from [NuGet Gallery](https://www.nuget.org/packages?q=AWS+Lambda+Powertools*) or from Visual Studio editor by searching `AWS.Lambda.Powertools*` to see various utilities available. +The Powertools for AWS Lambda (.NET) utilities (.NET 6 and .NET 8) are available as NuGet packages. You can install the packages from [NuGet Gallery](https://www.nuget.org/packages?q=AWS+Lambda+Powertools*) or from Visual Studio editor by searching `AWS.Lambda.Powertools*` to see various utilities available. * [AWS.Lambda.Powertools.Logging](https://www.nuget.org/packages?q=AWS.Lambda.Powertools.Logging): @@ -61,7 +61,7 @@ The Powertools for AWS Lambda (.NET) utilities (.NET 6) are available as NuGet p ## Examples -We have provided examples focused specifically on each of the utilities. Each solution comes with an AWS Serverless Application Model (AWS SAM) templates to run your functions as a Zip package using the AWS Lambda .NET 6 managed runtime; or as a container package using the AWS base images for .NET. +We have provided examples focused specifically on each of the utilities. Each solution comes with an AWS Serverless Application Model (AWS SAM) templates to run your functions as a Zip package using the AWS Lambda .NET 6 or .NET 8 managed runtime; or as a container package using the AWS base images for .NET. * **[Logging example](examples/Logging/)** * **[Metrics example](examples/Metrics/)** From b2c6988d925a3ddcddb695543dc81a5227886ad7 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:17:56 +0000 Subject: [PATCH 35/82] Update badges Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 59ffc737..f87c2d11 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ ![aws provider](https://img.shields.io/badge/provider-AWS-orange?logo=amazon-aws&color=ff9900) [![Build](https://github.com/aws-powertools/powertools-lambda-dotnet/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/aws-powertools/powertools-lambda-dotnet/actions/workflows/build.yml) [![codecov.io](https://codecov.io/github/aws-powertools/powertools-lambda-dotnet/branch/develop/graphs/badge.svg)](https://app.codecov.io/gh/aws-powertools/powertools-lambda-dotnet) -[![dotnet support](https://img.shields.io/static/v1?label=dotnet&message=%20NET6.0&color=blue?style=flat-square&logo=dotnet)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) +[![dotnet support](https://img.shields.io/static/v1?label=dotnet&message=%20NET6.0|NET8.0&color=blue?style=flat-square&logo=dotnet)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) [![NuGet Downloads](https://img.shields.io/nuget/dt/AWS.Lambda.Powertools.Logging.svg)](https://www.nuget.org/packages?q=AWS.Lambda.Powertools) -[![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET)](https://discord.gg/B8zZKbbyET) +[![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET?style=flat-square)](https://discord.gg/B8zZKbbyET) Powertools for AWS Lambda (.NET) is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda-dotnet/#features). From bdb02b7bafedbf3ef7fe9deae67b286932c31bca Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:09:07 +0000 Subject: [PATCH 36/82] GetMethodHandler: replace method parameter with target.Method parameter which is more precise and avoids collision when decorating generic methods with tracing attribute. Add tests. --- .../Aspects/UniversalWrapperAspect.cs | 3 +- .../Handlers/FunctionHandlerForGeneric.cs | 46 +++++++++++++++++++ ...unctionHandlerTests.cs => HandlerTests.cs} | 15 +++++- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FunctionHandlerForGeneric.cs rename libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/{ExceptionFunctionHandlerTests.cs => HandlerTests.cs} (66%) diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs index fedf4503..8061ef7d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs @@ -83,7 +83,8 @@ public object Handle( }; var wrappers = triggers.OfType().ToArray(); - var handler = GetMethodHandler(method, returnType, wrappers); + // Target.Method is more precise for cases when decorating generic methods + var handler = GetMethodHandler(target.Method, returnType, wrappers); return handler(target, args, eventArgs); } diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FunctionHandlerForGeneric.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FunctionHandlerForGeneric.cs new file mode 100644 index 00000000..78f9c52d --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FunctionHandlerForGeneric.cs @@ -0,0 +1,46 @@ +using System.Globalization; +using System.Threading.Tasks; + +namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; + +public class FunctionHandlerForGeneric +{ + [Tracing(CaptureMode = TracingCaptureMode.ResponseAndError)] + public async Task Handle(string input) + { + GenericMethod(1); + GenericMethod(2); + + GenericMethod(1); + + GenericMethod(); + GenericMethod(); + + GenericMethod2(1); + GenericMethod2(2); + + GenericMethod(); + GenericMethod(); + + await Task.Delay(1); + + return input.ToUpper(CultureInfo.InvariantCulture); + } + + [Tracing] + private T GenericMethod(T x) + { + return default; + } + + [Tracing] + private T GenericMethod() + { + return default; + } + + [Tracing] + private void GenericMethod2(T x) + { + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/ExceptionFunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs similarity index 66% rename from libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/ExceptionFunctionHandlerTests.cs rename to libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs index 2f336abc..066d0743 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/ExceptionFunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs @@ -4,7 +4,7 @@ namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; -public sealed class ExceptionFunctionHandlerTests +public sealed class HandlerTests { [Fact] public async Task Stack_Trace_Included_When_Decorator_Present() @@ -20,4 +20,17 @@ public async Task Stack_Trace_Included_When_Decorator_Present() Assert.StartsWith("at AWS.Lambda.Powertools.Tracing.Tests.Handlers.ExceptionFunctionHandler.ThisThrows()", tracedException.StackTrace?.TrimStart()); } + + [Fact] + public async Task When_Decorator_Present_In_Generic_Method_Should_Not_Throw_When_Type_Changes() + { + // Arrange + var handler = new FunctionHandlerForGeneric(); + + // Act + await handler.Handle("whatever"); + + // Assert + + } } \ No newline at end of file From a2d6aaab38a18673157dd9f679b9ea3bc6d0f045 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:36:01 +0000 Subject: [PATCH 37/82] Update version for release Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 57abf181..07e4f191 100644 --- a/version.json +++ b/version.json @@ -2,7 +2,7 @@ "Core": { "Logging": "1.5.1", "Metrics": "1.6.1", - "Tracing": "1.4.1" + "Tracing": "1.4.2" }, "Utilities": { "Parameters": "1.3.0", 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 38/82] 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 39/82] 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 40/82] 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 41/82] 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 42/82] 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 43/82] 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 44/82] 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 45/82] 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 46/82] 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 47/82] 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 48/82] 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 49/82] 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 GetEnumerator() System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return GetEnumerator(); + return GetEnumerator(); } } @@ -636,7 +700,7 @@ public IObjectValueEnumerator EnumerateObject() public IExpression GetExpression() { throw new InvalidOperationException("Not an expression"); - } + } public override string ToString() { @@ -653,8 +717,10 @@ public override string ToString() { first = false; } + buffer.Append(item); } + buffer.Append(']'); return buffer.ToString(); } @@ -664,10 +730,10 @@ public override string ToString() { private class ObjectEnumerator : IObjectValueEnumerator { - private readonly IDictionary _value; + private readonly IDictionary _value; private readonly System.Collections.IEnumerator _enumerator; - public ObjectEnumerator(IDictionary value) + public ObjectEnumerator(IDictionary value) { _value = value; _enumerator = value.GetEnumerator(); @@ -678,14 +744,22 @@ public bool MoveNext() return _enumerator.MoveNext(); } - public void Reset() { _enumerator.Reset(); } + public void Reset() + { + _enumerator.Reset(); + } - void IDisposable.Dispose() {} + void IDisposable.Dispose() + { + } public NameValuePair Current { - get {var pair = (KeyValuePair)_enumerator.Current!; - return new NameValuePair(pair.Key, pair.Value); } + get + { + var pair = (KeyValuePair)_enumerator.Current!; + return new NameValuePair(pair.Key, pair.Value); + } } object System.Collections.IEnumerator.Current => Current; @@ -697,13 +771,13 @@ public IEnumerator GetEnumerator() System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return GetEnumerator(); + return GetEnumerator(); } } - private readonly IDictionary _value; + private readonly IDictionary _value; - internal ObjectValue(IDictionary value) + internal ObjectValue(IDictionary value) { _value = value; } @@ -712,8 +786,8 @@ internal ObjectValue(IDictionary value) public IValue this[int index] => throw new InvalidOperationException(); - public int GetArrayLength() - { + public int GetArrayLength() + { throw new InvalidOperationException(); } @@ -750,7 +824,7 @@ public IObjectValueEnumerator EnumerateObject() public IExpression GetExpression() { throw new InvalidOperationException("Not an expression"); - } + } public override string ToString() { @@ -767,10 +841,12 @@ public override string ToString() { first = false; } + buffer.Append(JsonSerializer.Serialize(property.Key)); buffer.Append(':'); buffer.Append(property.Value); } + buffer.Append('}'); return buffer.ToString(); } @@ -789,9 +865,15 @@ internal ExpressionValue(IExpression expr) 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) { @@ -821,11 +903,11 @@ public IObjectValueEnumerator EnumerateObject() public IExpression GetExpression() { return _expr; - } + } public override string ToString() { return "expression"; } } -} +} \ No newline at end of file From ce881d4abb11d1bd8310c29561b11e244ad22794 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:23:21 +0100 Subject: [PATCH 50/82] Add continue-on-error to build workflow. very flaky service --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e831d366..4d96df9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,7 @@ jobs: run: dotnet test --collect:"XPlat Code Coverage" --results-directory ./codecov --verbosity normal - name: Codecov uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # 3.1.0 + continue-on-error: true # prevent build fail when codecov service is down (very frequent) with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests From 333d4b1f0e039043293ca871246b8c094a7e8818 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:30:09 +0100 Subject: [PATCH 51/82] more sonar fixes --- libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs index 08ce6234..9e3265ba 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs @@ -55,9 +55,9 @@ internal abstract class BaseExpression : IExpression public bool IsRightAssociative {get;} - public bool IsProjection {get;} + public bool IsProjection {get;} - internal BaseExpression(Operator oper, bool isProjection) + private protected BaseExpression(Operator oper, bool isProjection) { PrecedenceLevel = OperatorTable.PrecedenceLevel(oper); IsRightAssociative = OperatorTable.IsRightAssociative(oper); @@ -173,7 +173,7 @@ internal abstract class Projection : BaseExpression { private readonly List _expressions; - internal Projection(Operator oper) + private protected Projection(Operator oper) : base(oper, true) { _expressions = new List(); From 418386ef8c7ffae8dcd4618c1be3751e1573c845 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:38:26 +0100 Subject: [PATCH 52/82] sonar fixes --- libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs | 2 +- libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs index 66102648..5e93629a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs @@ -24,7 +24,7 @@ internal interface IBinaryOperator internal abstract class BinaryOperator : IBinaryOperator { - internal BinaryOperator(Operator oper) + private protected BinaryOperator(Operator oper) { PrecedenceLevel = OperatorTable.PrecedenceLevel(oper); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs index c28c2246..d7b02b7d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs @@ -26,7 +26,7 @@ internal interface IUnaryOperator internal abstract class UnaryOperator : IUnaryOperator { - internal UnaryOperator(Operator oper) + private protected UnaryOperator(Operator oper) { PrecedenceLevel = OperatorTable.PrecedenceLevel(oper); IsRightAssociative = OperatorTable.IsRightAssociative(oper); From b39a1c4ce73c7771fdb9927f905e34b07157da68 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:03:05 +0100 Subject: [PATCH 53/82] sonar fixes --- .../Utilities/JsonPointer.cs | 91 ++++++++++--------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs index 1b933a50..af42a36d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs @@ -159,55 +159,56 @@ public static bool TryParse(string input, out JsonPointer pointer) var done = false; while (index < input.Length && !done) { - switch (state) + if (state == JsonPointerState.Start) { - 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: + switch (input[index]) + { + case '/': + state = JsonPointerState.Delim; + break; + default: + pointer = Default; + return false; + } + } + else if (state == JsonPointerState.Delim) + { + switch (input[index]) { - pointer = Default; - return false; + case '/': + done = true; + break; + case '~': + state = JsonPointerState.Escaped; + break; + default: + buffer.Append(input[index]); + break; } } + else if (state == 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; + } + } + else + { + pointer = Default; + return false; + } + ++index; } tokens.Add(buffer.ToString()); From e353d7d6ea364ba2e9bd0977dc67b8a6edfff233 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:14:30 +0100 Subject: [PATCH 54/82] remove unused files --- .../Utilities/JsonDocumentBuilder.cs | 318 ---------- .../Utilities/JsonElementComparer.cs | 171 ----- .../Utilities/JsonFlattener.cs | 503 --------------- .../Utilities/JsonMergePatch.cs | 224 ------- .../Utilities/JsonPatch.cs | 441 ------------- .../Utilities/JsonPointer.cs | 590 ------------------ .../Utilities/JsonPointerExtensions.cs | 293 --------- 7 files changed, 2540 deletions(-) delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs deleted file mode 100644 index 609f17e9..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonDocumentBuilder.cs +++ /dev/null @@ -1,318 +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; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json; - -namespace AWS.Lambda.Powertools.JMESPath.Utilities -{ - internal class JsonDocumentBuilder - { - private static JsonDocumentBuilder Default { get; } = new(); - - internal JsonValueKind ValueKind {get;} - - private readonly 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"); - } - - private 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) return GetDictionary().TryGetValue(name, out value); - value = Default; - return false; - } - - 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); - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs deleted file mode 100644 index 22d44c6c..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementComparer.cs +++ /dev/null @@ -1,171 +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; -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 according 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); - } - - if (lhs.TryGetDouble(out var val1) && rhs.TryGetDouble(out var val2)) - { - return val1.CompareTo(val2); - } - - throw new InvalidOperationException("Unable to compare numbers"); - } - - case JsonValueKind.String: - { - return string.CompareOrdinal(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) - 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(); - while (result1 && result2) - { - if (enumerator1.Current.Name != 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) - { - return diff; - } - result1 = enumerator1.MoveNext(); - result2 = enumerator2.MoveNext(); - } - - return result1 ? 1 : result2 ? -1 : 0; - } - - default: - throw new InvalidOperationException($"Unknown JsonValueKind {lhs.ValueKind}"); - } - } - - int System.Collections.IComparer.Compare(object x, object y) - { - return Compare((JsonElement)x, (JsonElement)y); - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs deleted file mode 100644 index 719f6a1a..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonFlattener.cs +++ /dev/null @@ -1,503 +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; -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; - /// - /// 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; - /// - /// 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 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); - const string parentKey = ""; - _Flatten(parentKey, value, result); - return result.ToJsonDocument(); - } - - /// - /// 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 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, - /// 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) - { - return TryUnflattenArray(flattenedValue, out var val) ? val.ToJsonDocument() : UnflattenToObject(flattenedValue, options).ToJsonDocument(); - } - - 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().Any()) - { - 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) continue; - 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; - } - - 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; - - using 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"); - } - - using 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; - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs deleted file mode 100644 index 350128ca..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonMergePatch.cs +++ /dev/null @@ -1,224 +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.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; - /// - /// 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 responsibility 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 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) - { - 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()) - { - if (!source.TryGetProperty(property.Name, out _)) - { - builder.AddProperty(property.Name, new JsonDocumentBuilder(property.Value)); - } - } - - return builder; - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs deleted file mode 100644 index 5164bb2b..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPatch.cs +++ /dev/null @@ -1,441 +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; -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; - /// - /// 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 responsibility 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, nameof(patch)); - } - - var path = pathElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null"); - - if (!JsonPointer.TryParse(path, out var location)) - { - throw new ArgumentException(op, nameof(patch)); - } - - switch (op) - { - case "test": - { - if (!operation.TryGetProperty("value", out var value)) - { - throw new ArgumentException(op, nameof(patch)); - } - - if (!location.TryGetValue(target, out var tested)) - { - throw new ArgumentException(op, nameof(patch)); - } - - using var doc = tested.ToJsonDocument(); - if (!comparer.Equals(doc.RootElement, value)) - { - throw new JsonPatchException(op, "Test failed"); - } - - break; - } - 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; - } - case "remove" when !location.TryRemove(ref target): - throw new JsonPatchException(op, "Add failed"); - case "replace": - { - 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; - } - case "move": - { - if (!operation.TryGetProperty("from", out var fromElement)) - { - throw new ArgumentException(op, nameof(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.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"); - } - } - - break; - } - case "copy": - { - if (!operation.TryGetProperty("from", out var fromElement)) - { - 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; - } - } - } - } - - /// - /// Builds a JSON Patch as defined in RFC 6902 - /// given two JSON values, a source and a target. - /// - /// - /// 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) - { - 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 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('/'); - 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 tempDiff = _FromDiff(a.Value, element, buffer.ToString()); - foreach (var item in tempDiff.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()) - { - 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 - { - 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; - } - } -} \ 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 deleted file mode 100644 index af42a36d..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointer.cs +++ /dev/null @@ -1,590 +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; -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; - /// - /// 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. - private 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 - /// - private 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) - { - if (state == JsonPointerState.Start) - { - switch (input[index]) - { - case '/': - state = JsonPointerState.Delim; - break; - default: - pointer = Default; - return false; - } - } - else if (state == JsonPointerState.Delim) - { - switch (input[index]) - { - case '/': - done = true; - break; - case '~': - state = JsonPointerState.Escaped; - break; - default: - buffer.Append(input[index]); - break; - } - } - else if (state == 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; - } - } - else - { - 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 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.EscapeDataString(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. - private 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; - } - } - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs deleted file mode 100644 index 8926f510..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonPointerExtensions.cs +++ /dev/null @@ -1,293 +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.Collections.Generic; -using System.Text.Json; - -namespace AWS.Lambda.Powertools.JMESPath.Utilities -{ - internal static class JsonPointerExtensions - { - private static bool TryResolve(string token, JsonDocumentBuilder current, out JsonDocumentBuilder result) - { - result = current; - - switch (result.ValueKind) - { - case JsonValueKind.Array when token == "-": - return false; - case JsonValueKind.Array: - { - if (!int.TryParse(token, out var index)) - { - return false; - } - - if (index >= result.GetArrayLength()) - { - return false; - } - - result = result[index]; - break; - } - case JsonValueKind.Object: - { - if (!result.TryGetProperty(token, out result)) - { - return false; - } - - break; - } - default: - 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] != "-") return pointer; - 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); - - } - - 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) - { - if (!TryGetToken(location, root, out var current, out var token)) return false; - - switch (current.ValueKind) - { - case JsonValueKind.Array when token.Length == 1 && token[0] == '-': - current.AddArrayItem(value); - break; - case JsonValueKind.Array: - { - if (!TryGetArray(value, token, current)) return false; - - break; - } - case JsonValueKind.Object: - { - if (current.ContainsPropertyName(token)) - { - current.RemoveProperty(token); - } - - current.AddProperty(token, value); - break; - } - default: - return false; - } - - return true; - } - - private static bool TryGetToken(JsonPointer location, JsonDocumentBuilder root, out JsonDocumentBuilder current, - out string token) - { - current = root; - token = ""; - - using var enumerator = location.GetEnumerator(); - var more = enumerator.MoveNext(); - if (!more) - { - return false; - } - - while (more) - { - token = enumerator.Current; - more = enumerator.MoveNext(); - if (!more) continue; - if (!TryResolve(token, current, out current)) - { - return false; - } - } - - 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) - { - case JsonValueKind.Array when token.Length == 1 && token[0] == '-': - current.AddArrayItem(value); - break; - case JsonValueKind.Array: - { - if (!TryGetArray(value, token, current)) return false; - - break; - } - case JsonValueKind.Object when current.ContainsPropertyName(token): - return false; - case JsonValueKind.Object: - current.AddProperty(token, value); - break; - default: - return false; - } - - return true; - } - - private static bool TryGetArray(JsonDocumentBuilder value, string token, JsonDocumentBuilder current) - { - if (!int.TryParse(token, out var index)) - { - return false; - } - - if (index > current.GetArrayLength()) - { - return false; - } - - if (index == current.GetArrayLength()) - { - current.AddArrayItem(value); - } - else - { - current.InsertArrayItem(index, value); - } - - return true; - } - - public static bool TryRemove(this JsonPointer location, - ref JsonDocumentBuilder root) - { - if (!TryGetToken(location, root, out var current, out var token)) return false; - - switch (current.ValueKind) - { - case JsonValueKind.Array when token.Length == 1 && token[0] == '-': - return false; - case JsonValueKind.Array: - { - if (!int.TryParse(token, out var index)) - { - return false; - } - - if (index >= current.GetArrayLength()) - { - return false; - } - - current.RemoveArrayItemAt(index); - break; - } - case JsonValueKind.Object: - { - if (current.ContainsPropertyName(token)) - { - current.RemoveProperty(token); - } - - break; - } - default: - return false; - } - - return true; - } - - public static bool TryReplace(this JsonPointer location, - ref JsonDocumentBuilder root, - JsonDocumentBuilder value) - { - if (!TryGetToken(location, root, out var current, out var token)) return false; - - switch (current.ValueKind) - { - case JsonValueKind.Array when token.Length == 1 && token[0] == '-': - return false; - case JsonValueKind.Array: - { - if (!int.TryParse(token, out var index)) - { - return false; - } - - if (index >= current.GetArrayLength()) - { - return false; - } - - current[index] = value; - break; - } - case JsonValueKind.Object: - { - if (current.ContainsPropertyName(token)) - { - current.RemoveProperty(token); - } - else - { - return false; - } - - current.AddProperty(token, value); - break; - } - default: - return false; - } - - return true; - } - } -} \ No newline at end of file From 4678224e6c310de8fdccd47c3662ff99fe1b0c1b Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:44:53 +0100 Subject: [PATCH 55/82] sonar fixes --- .../JmesPathParser.cs | 3 - .../UnaryOperator.cs | 10 +-- .../ValueComparer.cs | 84 +++++++++++-------- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs index 43de7f04..7ca72351 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -1382,9 +1382,6 @@ internal JsonTransformer Parse() } 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(); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs index d7b02b7d..6e21c0fd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs @@ -47,9 +47,9 @@ private NotOperator() : base(Operator.Not) {} - public override bool TryEvaluate(IValue val, out IValue result) + public override bool TryEvaluate(IValue elem, out IValue result) { - result = Expression.IsFalse(val) ? JsonConstants.True : JsonConstants.False; + result = Expression.IsFalse(elem) ? JsonConstants.True : JsonConstants.False; return true; } @@ -69,14 +69,14 @@ internal RegexOperator(Regex regex) _regex = regex; } - public override bool TryEvaluate(IValue val, out IValue result) + public override bool TryEvaluate(IValue elem, out IValue result) { - if (val.Type != JmesPathType.String) + if (elem.Type != JmesPathType.String) { result = JsonConstants.Null; return false; // type error } - result = _regex.IsMatch(val.GetString()) ? JsonConstants.True : JsonConstants.False; + result = _regex.IsMatch(elem.GetString()) ? JsonConstants.True : JsonConstants.False; return true; } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs index a09d6bff..fb1b8c48 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs @@ -110,47 +110,12 @@ public int Compare(IValue lhs, IValue rhs) 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; + return ArrayComparer(lhs, rhs); } case JmesPathType.Object: { - // OrderBy performs a stable sort (Note that supports duplicate property names) - 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(); - while (result1 && result2) - { - if (enumerator1.Current.Name != 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) - { - return diff; - } - result1 = enumerator1.MoveNext(); - result2 = enumerator2.MoveNext(); - } - - return result1 ? 1 : result2 ? -1 : 0; + return ObjectComparer(lhs, rhs); } default: @@ -158,6 +123,51 @@ public int Compare(IValue lhs, IValue rhs) } } + private int ArrayComparer(IValue lhs, IValue rhs) + { + 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; + } + + private int ObjectComparer(IValue lhs, IValue rhs) + { + // OrderBy performs a stable sort (Note that supports duplicate property names) + 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(); + while (result1 && result2) + { + if (enumerator1.Current.Name != 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) + { + return diff; + } + result1 = enumerator1.MoveNext(); + result2 = enumerator2.MoveNext(); + } + + return result1 ? 1 : result2 ? -1 : 0; + } + int System.Collections.IComparer.Compare(object x, object y) { return Compare((IValue)x, (IValue)y); From 910e8592fe7ba2445931bf108934d5daf0b0b499 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:04:14 +0100 Subject: [PATCH 56/82] sonar fixes --- .../Expression.cs | 1 - .../JmesPathParser.cs | 24 ++++++++++--------- .../JsonTransformer.cs | 4 +--- .../AWS.Lambda.Powertools.JMESPath/Value.cs | 4 ++-- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs index 9e3265ba..426278ea 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs @@ -728,7 +728,6 @@ public bool TryEvaluate(DynamicResources resources, internal static bool IsFalse(IValue val) { - var comparer = ValueEqualityComparer.Instance; switch (val.Type) { case JmesPathType.False: diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs index 7ca72351..4b7a37f7 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -149,8 +149,10 @@ internal JsonTransformer Parse() PushToken(new Token(TokenType.CurrentNode)); _stateStack.Push(JmesPathState.Start); + var syntaxErrorMsg = "Syntax error"; while (_index < _span.Length) { + var expectedRightBracket = "Expected right bracket"; switch (_stateStack.Peek()) { case JmesPathState.Start: @@ -215,7 +217,7 @@ internal JsonTransformer Parse() } else { - throw new JmesPathParseException("Syntax error", _line, _column); + throw new JmesPathParseException(syntaxErrorMsg, _line, _column); } break; } @@ -256,7 +258,7 @@ internal JsonTransformer Parse() } else { - throw new JmesPathParseException("Syntax error", _line, _column); + throw new JmesPathParseException(syntaxErrorMsg, _line, _column); } break; } @@ -978,7 +980,7 @@ internal JsonTransformer Parse() break; } default: - throw new JmesPathParseException("Expected right bracket", _line, _column); + throw new JmesPathParseException(expectedRightBracket, _line, _column); } break; case JmesPathState.RhsSliceExpressionStop : @@ -1012,7 +1014,7 @@ internal JsonTransformer Parse() ++_column; break; default: - throw new JmesPathParseException("Expected right bracket", _line, _column); + throw new JmesPathParseException(expectedRightBracket, _line, _column); } break; } @@ -1045,7 +1047,7 @@ internal JsonTransformer Parse() ++_column; break; default: - throw new JmesPathParseException("Expected right bracket", _line, _column); + throw new JmesPathParseException(expectedRightBracket, _line, _column); } break; } @@ -1059,7 +1061,7 @@ internal JsonTransformer Parse() ++_column; break; default: - throw new JmesPathParseException("Expected right bracket", _line, _column); + throw new JmesPathParseException(expectedRightBracket, _line, _column); } break; } @@ -1281,7 +1283,7 @@ internal JsonTransformer Parse() break; } default: - throw new JmesPathParseException("Expected right bracket", _line, _column); + throw new JmesPathParseException(expectedRightBracket, _line, _column); } break; } @@ -1301,7 +1303,7 @@ internal JsonTransformer Parse() break; } default: - throw new JmesPathParseException("Expected right bracket", _line, _column); + throw new JmesPathParseException(expectedRightBracket, _line, _column); } break; } @@ -1365,7 +1367,7 @@ internal JsonTransformer Parse() if (_stateStack.Count == 0) { - throw new JmesPathParseException("Syntax error", _line, _column); + throw new JmesPathParseException(syntaxErrorMsg, _line, _column); } while (_stateStack.Count > 1) { @@ -1378,7 +1380,7 @@ internal JsonTransformer Parse() } else { - throw new JmesPathParseException("Syntax error", _line, _column); + throw new JmesPathParseException(syntaxErrorMsg, _line, _column); } break; case JmesPathState.ValExpr: @@ -1390,7 +1392,7 @@ internal JsonTransformer Parse() _stateStack.Pop(); break; default: - throw new JmesPathParseException("Syntax error", _line, _column); + throw new JmesPathParseException(syntaxErrorMsg, _line, _column); } } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs index 56aaa176..f7146737 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs @@ -18,9 +18,7 @@ namespace AWS.Lambda.Powertools.JMESPath { - internal sealed class DynamicResources - { - } + internal sealed class DynamicResources; /// /// Provides functionality for applying a JMESPath expression to transform a JSON document into /// another JSON document diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs index b6e17a8d..4b157cbe 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs @@ -619,7 +619,7 @@ public override string ToString() internal readonly struct ArrayValue : IValue { - private class ArrayEnumerator : IArrayValueEnumerator + private sealed class ArrayEnumerator : IArrayValueEnumerator { private readonly IList _value; private readonly System.Collections.IEnumerator _enumerator; @@ -728,7 +728,7 @@ public override string ToString() internal readonly struct ObjectValue : IValue { - private class ObjectEnumerator : IObjectValueEnumerator + private sealed class ObjectEnumerator : IObjectValueEnumerator { private readonly IDictionary _value; private readonly System.Collections.IEnumerator _enumerator; From 0c92aae470a16a62b5235e09756c4cd9e95bbf20 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:53:05 +0100 Subject: [PATCH 57/82] sonar fixes and refactor --- .../Function.cs | 1504 ----------------- .../Functions/AbsFunction.cs | 39 + .../Functions/AvgFunction.cs | 50 + .../Functions/Base64Function.cs | 29 + .../Functions/Base64GzipFunction.cs | 42 + .../Functions/BaseFunction.cs | 15 + .../Functions/BuiltInFunctions.cs | 48 + .../Functions/CeilFunction.cs | 46 + .../Functions/ContainsFunction.cs | 66 + .../Functions/EndsWithFunction.cs | 39 + .../Functions/FloorFunction.cs | 46 + .../Functions/IFunction.cs | 9 + .../Functions/JoinFunction.cs | 54 + .../Functions/JsonFunction.cs | 25 + .../Functions/KeysFunction.cs | 40 + .../Functions/LengthFunction.cs | 55 + .../Functions/MapFunction.cs | 47 + .../Functions/MaxByFunction.cs | 83 + .../Functions/MaxFunction.cs | 70 + .../Functions/MergeFunction.cs | 59 + .../Functions/MinByFunction.cs | 83 + .../Functions/MinFunction.cs | 70 + .../Functions/NotNullFunction.cs | 29 + .../Functions/ReverseFunction.cs | 57 + .../Functions/SortByComparer.cs | 75 + .../Functions/SortByFunction.cs | 54 + .../Functions/SortFunction.cs | 63 + .../Functions/StartsWithFunction.cs | 38 + .../Functions/SumFunction.cs | 73 + .../Functions/ToArrayFunction.cs | 33 + .../Functions/ToNumberFunction.cs | 53 + .../Functions/ToStringFunction.cs | 42 + .../Functions/TypeFunction.cs | 50 + .../Functions/ValuesFunction.cs | 39 + .../JmesPathParser.cs | 1 + .../AWS.Lambda.Powertools.JMESPath/Token.cs | 1 + 36 files changed, 1623 insertions(+), 1504 deletions(-) delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs deleted file mode 100644 index 294633a1..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Function.cs +++ /dev/null @@ -1,1504 +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; -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 - { - private protected 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg = args[0]; - - if (arg.TryGetDecimal(out var decVal)) - { - element = new DecimalValue(decVal >= 0 ? decVal : -decVal); - return true; - } - - if (arg.TryGetDouble(out var dblVal)) - { - element = new DecimalValue(dblVal >= 0 ? decVal : new decimal(-dblVal)); - return true; - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array || arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return false; - } - - if (!SumFunction.Instance.TryEvaluate(resources, args, out var sum)) - { - element = JsonConstants.Null; - return false; - } - - if (sum.TryGetDecimal(out var decVal)) - { - element = new DecimalValue(decVal / arg0.GetArrayLength()); - return true; - } - - if (sum.TryGetDouble(out var dblVal)) - { - element = new DoubleValue(dblVal / arg0.GetArrayLength()); - return true; - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var val = args[0]; - if (val.Type != JmesPathType.Number) - { - element = JsonConstants.Null; - return false; - } - - if (val.TryGetDecimal(out var decVal)) - { - element = new DecimalValue(decimal.Ceiling(decVal)); - return true; - } - - if (val.TryGetDouble(out var dblVal)) - { - element = new DoubleValue(Math.Ceiling(dblVal)); - return true; - } - - element = 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 element) - { - 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: - if (arg0.EnumerateArray().Any(item => comparer.Equals(item, arg1))) - { - element = JsonConstants.True; - return true; - } - - element = JsonConstants.False; - return true; - case JmesPathType.String: - { - if (arg1.Type != JmesPathType.String) - { - element = JsonConstants.Null; - return false; - } - - var s0 = arg0.GetString(); - var s1 = arg1.GetString(); - if (s0.Contains(s1)) - { - element = JsonConstants.True; - return true; - } - - element = JsonConstants.False; - return true; - } - default: - { - element = 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 element) - { - 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) - { - element = JsonConstants.Null; - return false; - } - - var s0 = arg0.GetString(); - var s1 = arg1.GetString(); - - if (s0.EndsWith(s1)) - { - element = JsonConstants.True; - } - else - { - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var val = args[0]; - if (val.Type != JmesPathType.Number) - { - element = JsonConstants.Null; - return false; - } - - if (val.TryGetDecimal(out var decVal)) - { - element = new DecimalValue(decimal.Floor(decVal)); - return true; - } - - if (val.TryGetDouble(out var dblVal)) - { - element = new DoubleValue(Math.Floor(dblVal)); - return true; - } - - element = 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 element) - { - 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)) - { - element = JsonConstants.Null; - return false; - } - - var sep = arg0.GetString(); - var buf = new StringBuilder(); - foreach (var j in arg1.EnumerateArray()) - { - if (j.Type != JmesPathType.String) - { - element = JsonConstants.Null; - return false; - } - - if (buf.Length != 0) - { - buf.Append(sep); - } - - var sv = j.GetString(); - buf.Append(sv); - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Object) - { - element = JsonConstants.Null; - return false; - } - - var values = new List(); - - foreach (var property in arg0.EnumerateObject()) - { - values.Add(new StringValue(property.Name)); - } - - element = 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 element) - { - 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; - } - - element = new DecimalValue(new decimal(count)); - return true; - } - case JmesPathType.Array: - element = new DecimalValue(new decimal(arg0.GetArrayLength())); - return true; - case JmesPathType.String: - { - var bytes = Encoding.UTF32.GetBytes(arg0.GetString().ToCharArray()); - element = new DecimalValue(new decimal(bytes.Length / 4)); - return true; - } - default: - { - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array) - { - element = JsonConstants.Null; - return false; - } - - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return false; - } - - var isNumber = arg0[0].Type == JmesPathType.Number; - var isString = arg0[0].Type == JmesPathType.String; - if (!isNumber && !isString) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - if (!greater.TryEvaluate(arg0[i], arg0[index], out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (Expression.IsTrue(value)) - { - index = i; - } - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression)) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return true; - } - - var expr = args[1].GetExpression(); - - if (!expr.TryEvaluate(resources, arg0[0], out var key1)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber1 = key1.Type == JmesPathType.Number; - var isString1 = key1.Type == JmesPathType.String; - if (!(isNumber1 || isString1)) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber2 = key2.Type == JmesPathType.Number; - var isString2 = key2.Type == JmesPathType.String; - if (!(isNumber2 == isNumber1 && isString2 == isString1)) - { - element = JsonConstants.Null; - return false; - } - - if (!greater.TryEvaluate(key2, key1, out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (value.Type != JmesPathType.True) continue; - key1 = key2; - index = i; - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array) - { - element = JsonConstants.Null; - return false; - } - - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return false; - } - - var isNumber = arg0[0].Type == JmesPathType.Number; - var isString = arg0[0].Type == JmesPathType.String; - if (!isNumber && !isString) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - if (!less.TryEvaluate(arg0[i], arg0[index], out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (value.Type == JmesPathType.True) - { - index = i; - } - } - - element = 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 element) - { - if (!args.Any()) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Object) - { - element = JsonConstants.Null; - return false; - } - - if (args.Count == 1) - { - element = arg0; - return true; - } - - var dict = new Dictionary(); - for (var i = 0; i < args.Count; ++i) - { - var argi = args[i]; - if (argi.Type != JmesPathType.Object) - { - element = 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); - } - } - } - - element = 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 element) - { - foreach (var arg in args) - { - if (arg.Type == JmesPathType.Null) continue; - element = arg; - return true; - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - switch (arg0.Type) - { - case JmesPathType.String: - { - element = 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]); - } - - element = new ArrayValue(list); - return true; - } - default: - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - if (!(args[0].Type == JmesPathType.Expression && args[1].Type == JmesPathType.Array)) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - list.Add(val); - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression)) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return true; - } - - var expr = args[1].GetExpression(); - - if (!expr.TryEvaluate(resources, arg0[0], out var key1)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber1 = key1.Type == JmesPathType.Number; - var isString1 = key1.Type == JmesPathType.String; - if (!(isNumber1 || isString1)) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber2 = key2.Type == JmesPathType.Number; - var isString2 = key2.Type == JmesPathType.String; - if (!(isNumber2 == isNumber1 && isString2 == isString1)) - { - element = JsonConstants.Null; - return false; - } - - if (!lessor.TryEvaluate(key2, key1, out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (value.Type == JmesPathType.True) - { - key1 = key2; - index = i; - } - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array) - { - element = JsonConstants.Null; - return false; - } - - if (arg0.GetArrayLength() <= 1) - { - element = arg0; - return true; - } - - var isNumber1 = arg0[0].Type == JmesPathType.Number; - var isString1 = arg0[0].Type == JmesPathType.String; - if (!isNumber1 && !isString1) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - list.Add(item); - } - - list.Sort(comparer); - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression)) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - if (arg0.GetArrayLength() <= 1) - { - element = 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) - { - element = new ArrayValue(list); - return true; - } - - element = 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 element) - { - 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) - { - element = JsonConstants.Null; - return false; - } - - var s0 = arg0.GetString(); - var s1 = arg1.GetString(); - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array) - { - element = JsonConstants.Null; - return false; - } - - foreach (var item in arg0.EnumerateArray()) - { - if (item.Type != JmesPathType.Number) - { - element = 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) - { - element = new DecimalValue(decSum); - return true; - } - - double dblSum = 0; - foreach (var item in arg0.EnumerateArray()) - { - if (!item.TryGetDouble(out var dbl)) - { - element = JsonConstants.Null; - return false; - } - - dblSum += dbl; - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type == JmesPathType.Array) - { - element = arg0; - return true; - } - - var list = new List { arg0 }; - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - switch (arg0.Type) - { - case JmesPathType.Number: - element = arg0; - return true; - case JmesPathType.String: - { - var s = arg0.GetString(); - if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dec)) - { - element = new DecimalValue(dec); - return true; - } - - if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dbl)) - { - element = new DoubleValue(dbl); - return true; - } - - element = JsonConstants.Null; - return false; - } - default: - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - if (args[0].Type == JmesPathType.Expression) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - switch (arg0.Type) - { - case JmesPathType.String: - element = arg0; - return true; - case JmesPathType.Expression: - element = JsonConstants.Null; - return false; - default: - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Object) - { - element = JsonConstants.Null; - return false; - } - - var list = new List(); - - foreach (var item in arg0.EnumerateObject()) - { - list.Add(item.Value); - } - - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - - var arg0 = args[0]; - - switch (arg0.Type) - { - case JmesPathType.Number: - element = new StringValue("number"); - return true; - case JmesPathType.True: - case JmesPathType.False: - element = new StringValue("boolean"); - return true; - case JmesPathType.String: - element = new StringValue("string"); - return true; - case JmesPathType.Object: - element = new StringValue("object"); - return true; - case JmesPathType.Array: - element = new StringValue("array"); - return true; - case JmesPathType.Null: - element = new StringValue("null"); - return true; - default: - element = 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - element = args[0]; - 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 element) - { - Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - var base64StringBytes = Convert.FromBase64String(args[0].GetString()); - var doc = JsonDocument.Parse(base64StringBytes); - element = 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 element) - { - 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())); - element = 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/Functions/AbsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs new file mode 100644 index 00000000..e1f42165 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class AbsFunction : BaseFunction +{ + internal AbsFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var arg = args[0]; + + if (arg.TryGetDecimal(out var decVal)) + { + element = new DecimalValue(decVal >= 0 ? decVal : -decVal); + return true; + } + + if (arg.TryGetDouble(out var dblVal)) + { + element = new DecimalValue(dblVal >= 0 ? decVal : new decimal(-dblVal)); + return true; + } + + element = JsonConstants.Null; + return false; + } + + public override string ToString() + { + return "abs"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs new file mode 100644 index 00000000..a34be4a6 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class AvgFunction : BaseFunction +{ + internal AvgFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + if (!SumFunction.Instance.TryEvaluate(resources, args, out var sum)) + { + element = JsonConstants.Null; + return false; + } + + if (sum.TryGetDecimal(out var decVal)) + { + element = new DecimalValue(decVal / arg0.GetArrayLength()); + return true; + } + + if (sum.TryGetDouble(out var dblVal)) + { + element = new DoubleValue(dblVal / arg0.GetArrayLength()); + return true; + } + + element = JsonConstants.Null; + return false; + } + + public override string ToString() + { + return "avg"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs new file mode 100644 index 00000000..45b7b696 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Json; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +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 element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + var base64StringBytes = Convert.FromBase64String(args[0].GetString()); + var doc = JsonDocument.Parse(base64StringBytes); + element = new JsonElementValue(doc.RootElement); + return true; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs new file mode 100644 index 00000000..1f6a8b6d --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Text.Json; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +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 element) + { + 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())); + element = new JsonElementValue(doc.RootElement); + + return true; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs new file mode 100644 index 00000000..33008545 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal abstract class BaseFunction : IFunction +{ + private protected BaseFunction(int? argCount) + { + Arity = argCount; + } + + public int? Arity { get; } + + public abstract bool TryEvaluate(DynamicResources resources, IList args, out IValue element); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs new file mode 100644 index 00000000..b19a2be3 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +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/Functions/CeilFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs new file mode 100644 index 00000000..164e80d4 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class CeilFunction : BaseFunction +{ + internal CeilFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var val = args[0]; + if (val.Type != JmesPathType.Number) + { + element = JsonConstants.Null; + return false; + } + + if (val.TryGetDecimal(out var decVal)) + { + element = new DecimalValue(decimal.Ceiling(decVal)); + return true; + } + + if (val.TryGetDouble(out var dblVal)) + { + element = new DoubleValue(Math.Ceiling(dblVal)); + return true; + } + + element = JsonConstants.Null; + return false; + } + + public override string ToString() + { + return "ceil"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs new file mode 100644 index 00000000..2eb9080d --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class ContainsFunction : BaseFunction +{ + internal ContainsFunction() + : base(2) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + 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: + if (arg0.EnumerateArray().Any(item => comparer.Equals(item, arg1))) + { + element = JsonConstants.True; + return true; + } + + element = JsonConstants.False; + return true; + case JmesPathType.String: + { + if (arg1.Type != JmesPathType.String) + { + element = JsonConstants.Null; + return false; + } + + var s0 = arg0.GetString(); + var s1 = arg1.GetString(); + if (s0.Contains(s1)) + { + element = JsonConstants.True; + return true; + } + + element = JsonConstants.False; + return true; + } + default: + { + element = JsonConstants.Null; + return false; + } + } + } + + public override string ToString() + { + return "contains"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs new file mode 100644 index 00000000..9d3cc4b2 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class EndsWithFunction : BaseFunction +{ + internal EndsWithFunction() + : base(2) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + 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) + { + element = JsonConstants.Null; + return false; + } + + var s0 = arg0.GetString(); + var s1 = arg1.GetString(); + + element = s0.EndsWith(s1) ? JsonConstants.True : JsonConstants.False; + + return true; + } + + public override string ToString() + { + return "ends_with"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs new file mode 100644 index 00000000..c65e72a2 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class FloorFunction : BaseFunction +{ + internal FloorFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var val = args[0]; + if (val.Type != JmesPathType.Number) + { + element = JsonConstants.Null; + return false; + } + + if (val.TryGetDecimal(out var decVal)) + { + element = new DecimalValue(decimal.Floor(decVal)); + return true; + } + + if (val.TryGetDouble(out var dblVal)) + { + element = new DoubleValue(Math.Floor(dblVal)); + return true; + } + + element = JsonConstants.Null; + return false; + } + + public override string ToString() + { + return "floor"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs new file mode 100644 index 00000000..ddbd8634 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal interface IFunction +{ + int? Arity { get; } + bool TryEvaluate(DynamicResources resources, IList args, out IValue element); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs new file mode 100644 index 00000000..827e2316 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class JoinFunction : BaseFunction +{ + internal JoinFunction() + : base(2) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) + { + 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)) + { + element = JsonConstants.Null; + return false; + } + + var sep = arg0.GetString(); + var buf = new StringBuilder(); + foreach (var j in arg1.EnumerateArray()) + { + if (j.Type != JmesPathType.String) + { + element = JsonConstants.Null; + return false; + } + + if (buf.Length != 0) + { + buf.Append(sep); + } + + var sv = j.GetString(); + buf.Append(sv); + } + + element = new StringValue(buf.ToString()); + return true; + } + + public override string ToString() + { + return "join"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs new file mode 100644 index 00000000..560947c1 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +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 element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + element = args[0]; + return true; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs new file mode 100644 index 00000000..915013df --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class KeysFunction : BaseFunction +{ + internal KeysFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + var values = new List(); + + foreach (var property in arg0.EnumerateObject()) + { + values.Add(new StringValue(property.Name)); + } + + element = new ArrayValue(values); + return true; + } + + public override string ToString() + { + return "keys"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs new file mode 100644 index 00000000..078621f2 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class LengthFunction : BaseFunction +{ + internal LengthFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + 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; + } + + element = new DecimalValue(new decimal(count)); + return true; + } + case JmesPathType.Array: + element = new DecimalValue(new decimal(arg0.GetArrayLength())); + return true; + case JmesPathType.String: + { + var bytes = Encoding.UTF32.GetBytes(arg0.GetString().ToCharArray()); + element = new DecimalValue(new decimal(bytes.Length / 4)); + return true; + } + default: + { + element = JsonConstants.Null; + return false; + } + } + } + + public override string ToString() + { + return "length"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs new file mode 100644 index 00000000..10ca0c96 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class MapFunction : BaseFunction +{ + internal MapFunction() + : base(2) + { + } + + 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)) + { + element = 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)) + { + element = JsonConstants.Null; + return false; + } + + list.Add(val); + } + + element = new ArrayValue(list); + return true; + } + + public override string ToString() + { + return "map"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs new file mode 100644 index 00000000..cdca7214 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class MaxByFunction : BaseFunction +{ + internal MaxByFunction() + : base(2) + { + } + + 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)) + { + element = JsonConstants.Null; + return false; + } + + var arg0 = args[0]; + if (arg0.GetArrayLength() == 0) + { + element = JsonConstants.Null; + return true; + } + + var expr = args[1].GetExpression(); + + if (!expr.TryEvaluate(resources, arg0[0], out var key1)) + { + element = JsonConstants.Null; + return false; + } + + var isNumber1 = key1.Type == JmesPathType.Number; + var isString1 = key1.Type == JmesPathType.String; + if (!(isNumber1 || isString1)) + { + element = 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)) + { + element = JsonConstants.Null; + return false; + } + + var isNumber2 = key2.Type == JmesPathType.Number; + var isString2 = key2.Type == JmesPathType.String; + if (!(isNumber2 == isNumber1 && isString2 == isString1)) + { + element = JsonConstants.Null; + return false; + } + + if (!greater.TryEvaluate(key2, key1, out var value)) + { + element = JsonConstants.Null; + return false; + } + + if (value.Type != JmesPathType.True) continue; + key1 = key2; + index = i; + } + + element = arg0[index]; + return true; + } + + public override string ToString() + { + return "max_by"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs new file mode 100644 index 00000000..551ccf2c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class MaxFunction : BaseFunction +{ + internal MaxFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + if (arg0.GetArrayLength() == 0) + { + element = JsonConstants.Null; + return false; + } + + var isNumber = arg0[0].Type == JmesPathType.Number; + var isString = arg0[0].Type == JmesPathType.String; + if (!isNumber && !isString) + { + element = 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)) + { + element = JsonConstants.Null; + return false; + } + + if (!greater.TryEvaluate(arg0[i], arg0[index], out var value)) + { + element = JsonConstants.Null; + return false; + } + + if (Expression.IsTrue(value)) + { + index = i; + } + } + + element = arg0[index]; + return true; + } + + public override string ToString() + { + return "max"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs new file mode 100644 index 00000000..60b488ec --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class MergeFunction : BaseFunction +{ + internal MergeFunction() + : base(null) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) + { + if (!args.Any()) + { + element = JsonConstants.Null; + return false; + } + + var arg0 = args[0]; + if (arg0.Type != JmesPathType.Object) + { + element = JsonConstants.Null; + return false; + } + + if (args.Count == 1) + { + element = arg0; + return true; + } + + var dict = new Dictionary(); + foreach (var argi in args) + { + if (argi.Type != JmesPathType.Object) + { + element = JsonConstants.Null; + return false; + } + + foreach (var item in argi.EnumerateObject()) + { + if (dict.TryAdd(item.Name, item.Value)) continue; + dict.Remove(item.Name); + dict.Add(item.Name, item.Value); + } + } + + element = new ObjectValue(dict); + return true; + } + + public override string ToString() + { + return "merge"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs new file mode 100644 index 00000000..9196c03b --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class MinByFunction : BaseFunction +{ + internal MinByFunction() + : base(2) + { + } + + 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)) + { + element = JsonConstants.Null; + return false; + } + + var arg0 = args[0]; + if (arg0.GetArrayLength() == 0) + { + element = JsonConstants.Null; + return true; + } + + var expr = args[1].GetExpression(); + + if (!expr.TryEvaluate(resources, arg0[0], out var key1)) + { + element = JsonConstants.Null; + return false; + } + + var isNumber1 = key1.Type == JmesPathType.Number; + var isString1 = key1.Type == JmesPathType.String; + if (!(isNumber1 || isString1)) + { + element = 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)) + { + element = JsonConstants.Null; + return false; + } + + var isNumber2 = key2.Type == JmesPathType.Number; + var isString2 = key2.Type == JmesPathType.String; + if (!(isNumber2 == isNumber1 && isString2 == isString1)) + { + element = JsonConstants.Null; + return false; + } + + if (!lessor.TryEvaluate(key2, key1, out var value)) + { + element = JsonConstants.Null; + return false; + } + + if (value.Type != JmesPathType.True) continue; + key1 = key2; + index = i; + } + + element = arg0[index]; + return true; + } + + public override string ToString() + { + return "min_by"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs new file mode 100644 index 00000000..6a8612dc --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class MinFunction : BaseFunction +{ + internal MinFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + if (arg0.GetArrayLength() == 0) + { + element = JsonConstants.Null; + return false; + } + + var isNumber = arg0[0].Type == JmesPathType.Number; + var isString = arg0[0].Type == JmesPathType.String; + if (!isNumber && !isString) + { + element = 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)) + { + element = JsonConstants.Null; + return false; + } + + if (!less.TryEvaluate(arg0[i], arg0[index], out var value)) + { + element = JsonConstants.Null; + return false; + } + + if (value.Type == JmesPathType.True) + { + index = i; + } + } + + element = arg0[index]; + return true; + } + + public override string ToString() + { + return "min"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs new file mode 100644 index 00000000..fc8afcfb --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class NotNullFunction : BaseFunction +{ + internal NotNullFunction() + : base(null) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) + { + foreach (var arg in args) + { + if (arg.Type == JmesPathType.Null) continue; + element = arg; + return true; + } + + element = JsonConstants.Null; + return true; + } + + public override string ToString() + { + return "not_null"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs new file mode 100644 index 00000000..0c6837d8 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class ReverseFunction : BaseFunction +{ + internal ReverseFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var arg0 = args[0]; + switch (arg0.Type) + { + case JmesPathType.String: + { + element = 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]); + } + + element = new ArrayValue(list); + return true; + } + default: + element = 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs new file mode 100644 index 00000000..70c5167e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs @@ -0,0 +1,75 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Functions +{ + internal sealed class SortByComparer : IComparer, System.Collections.IComparer + { + private readonly DynamicResources _resources; + private readonly IExpression _expr; + + internal bool IsValid { get; private 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); + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs new file mode 100644 index 00000000..7af9b922 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class SortByFunction : BaseFunction +{ + internal SortByFunction() + : base(2) + { + } + + 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)) + { + element = JsonConstants.Null; + return false; + } + + var arg0 = args[0]; + if (arg0.GetArrayLength() <= 1) + { + element = 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) + { + element = new ArrayValue(list); + return true; + } + + element = JsonConstants.Null; + return false; + } + + public override string ToString() + { + return "sort_by"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs new file mode 100644 index 00000000..b697a0a7 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class SortFunction : BaseFunction +{ + internal SortFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + if (arg0.GetArrayLength() <= 1) + { + element = arg0; + return true; + } + + var isNumber1 = arg0[0].Type == JmesPathType.Number; + var isString1 = arg0[0].Type == JmesPathType.String; + if (!isNumber1 && !isString1) + { + element = 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)) + { + element = JsonConstants.Null; + return false; + } + + list.Add(item); + } + + list.Sort(comparer); + element = new ArrayValue(list); + return true; + } + + public override string ToString() + { + return "sort"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs new file mode 100644 index 00000000..02b7d86d --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class StartsWithFunction : BaseFunction +{ + internal StartsWithFunction() + : base(2) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + 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) + { + element = JsonConstants.Null; + return false; + } + + var s0 = arg0.GetString(); + var s1 = arg1.GetString(); + element = s0.StartsWith(s1) ? JsonConstants.True : JsonConstants.False; + + return true; + } + + public override string ToString() + { + return "starts_with"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs new file mode 100644 index 00000000..8c3956f4 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +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 element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var arg0 = args[0]; + if (arg0.Type != JmesPathType.Array) + { + element = JsonConstants.Null; + return false; + } + + foreach (var item in arg0.EnumerateArray()) + { + if (item.Type == JmesPathType.Number) continue; + element = 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) + { + element = new DecimalValue(decSum); + return true; + } + + double dblSum = 0; + foreach (var item in arg0.EnumerateArray()) + { + if (!item.TryGetDouble(out var dbl)) + { + element = JsonConstants.Null; + return false; + } + + dblSum += dbl; + } + + element = new DoubleValue(dblSum); + return true; + } + + public override string ToString() + { + return "sum"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs new file mode 100644 index 00000000..4cad1f76 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class ToArrayFunction : BaseFunction +{ + internal ToArrayFunction() + : base(1) + { + } + + 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) + { + element = arg0; + return true; + } + + var list = new List { arg0 }; + element = new ArrayValue(list); + return true; + } + + public override string ToString() + { + return "to_array"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs new file mode 100644 index 00000000..49194fb5 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class ToNumberFunction : BaseFunction +{ + internal ToNumberFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, + out IValue element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var arg0 = args[0]; + switch (arg0.Type) + { + case JmesPathType.Number: + element = arg0; + return true; + case JmesPathType.String: + { + var s = arg0.GetString(); + if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dec)) + { + element = new DecimalValue(dec); + return true; + } + + if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var dbl)) + { + element = new DoubleValue(dbl); + return true; + } + + element = JsonConstants.Null; + return false; + } + default: + element = JsonConstants.Null; + return false; + } + } + + public override string ToString() + { + return "to_number"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs new file mode 100644 index 00000000..caaff747 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class ToStringFunction : BaseFunction +{ + internal ToStringFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + var arg0 = args[0]; + switch (arg0.Type) + { + case JmesPathType.String: + element = arg0; + return true; + case JmesPathType.Expression: + element = JsonConstants.Null; + return false; + default: + element = new StringValue(arg0.ToString()); + return true; + } + } + + public override string ToString() + { + return "to_string"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs new file mode 100644 index 00000000..c8d61538 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class TypeFunction : BaseFunction +{ + internal TypeFunction() + : base(1) + { + } + + public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) + { + Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + + var arg0 = args[0]; + + switch (arg0.Type) + { + case JmesPathType.Number: + element = new StringValue("number"); + return true; + case JmesPathType.True: + case JmesPathType.False: + element = new StringValue("boolean"); + return true; + case JmesPathType.String: + element = new StringValue("string"); + return true; + case JmesPathType.Object: + element = new StringValue("object"); + return true; + case JmesPathType.Array: + element = new StringValue("array"); + return true; + case JmesPathType.Null: + element = new StringValue("null"); + return true; + default: + element = JsonConstants.Null; + return false; + } + } + + public override string ToString() + { + return "type"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs new file mode 100644 index 00000000..e46c05b9 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal sealed class ValuesFunction : BaseFunction +{ + internal ValuesFunction() + : base(1) + { + } + + 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) + { + element = JsonConstants.Null; + return false; + } + + var list = new List(); + + foreach (var item in arg0.EnumerateObject()) + { + list.Add(item.Value); + } + + element = new ArrayValue(list); + return true; + } + + public override string ToString() + { + return "values"; + } +} \ 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 4b7a37f7..a7641fe3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -18,6 +18,7 @@ using System.Diagnostics; using System.Text; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Functions; namespace AWS.Lambda.Powertools.JMESPath { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs index 553f9140..7ac5433b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs @@ -15,6 +15,7 @@ using System; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Functions; namespace AWS.Lambda.Powertools.JMESPath { From 942aaa97f6139ec04a0be15a3781d2eaf4bdf3ce Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:11:52 +0100 Subject: [PATCH 58/82] refactor. include license text, remove duplication to make sonar happy --- .../Functions/AbsFunction.cs | 15 ++++ .../Functions/AvgFunction.cs | 15 ++++ .../Functions/Base64Function.cs | 15 ++++ .../Functions/Base64GzipFunction.cs | 15 ++++ .../Functions/BaseFunction.cs | 15 ++++ .../Functions/BuiltInFunctions.cs | 15 ++++ .../Functions/CeilFunction.cs | 15 ++++ .../Functions/ContainsFunction.cs | 15 ++++ .../Functions/EndsWithFunction.cs | 31 +++---- .../Functions/EvaluateMinMax.cs | 70 ++++++++++++++++ .../Functions/EvaluateMinMaxBy.cs | 84 +++++++++++++++++++ .../Functions/EvaluateStartEndWith.cs | 40 +++++++++ .../Functions/FloorFunction.cs | 15 ++++ .../Functions/IFunction.cs | 15 ++++ .../Functions/JoinFunction.cs | 15 ++++ .../Functions/JsonFunction.cs | 15 ++++ .../Functions/KeysFunction.cs | 15 ++++ .../Functions/LengthFunction.cs | 15 ++++ .../Functions/MapFunction.cs | 15 ++++ .../Functions/MaxByFunction.cs | 78 ++++------------- .../Functions/MaxFunction.cs | 62 ++++---------- .../Functions/MergeFunction.cs | 15 ++++ .../Functions/MinByFunction.cs | 76 ++++------------- .../Functions/MinFunction.cs | 62 ++++---------- .../Functions/NotNullFunction.cs | 15 ++++ .../Functions/ReverseFunction.cs | 15 ++++ .../Functions/SortByFunction.cs | 15 ++++ .../Functions/SortFunction.cs | 15 ++++ .../Functions/StartsWithFunction.cs | 30 +++---- .../Functions/SumFunction.cs | 15 ++++ .../Functions/ToArrayFunction.cs | 15 ++++ .../Functions/ToNumberFunction.cs | 15 ++++ .../Functions/ToStringFunction.cs | 15 ++++ .../Functions/TypeFunction.cs | 15 ++++ .../Functions/ValuesFunction.cs | 15 ++++ 35 files changed, 681 insertions(+), 242 deletions(-) create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs index e1f42165..e1733344 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs index a34be4a6..9dc83294 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs index 45b7b696..6db33b66 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.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.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs index 1f6a8b6d..a68cd5c8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.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.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs index 33008545..b4162cea 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.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.Collections.Generic; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs index b19a2be3..b953e136 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.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.Collections.Generic; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs index 164e80d4..2f0e773c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.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.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs index 2eb9080d..2953eb98 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.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.Collections.Generic; using System.Diagnostics; using System.Linq; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs index 9d3cc4b2..c0af8786 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.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.Collections.Generic; using System.Diagnostics; @@ -15,21 +30,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { 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) - { - element = JsonConstants.Null; - return false; - } - - var s0 = arg0.GetString(); - var s1 = arg1.GetString(); - - element = s0.EndsWith(s1) ? JsonConstants.True : JsonConstants.False; - - return true; + return EvaluateStartEndWith.TryEvaluate(args, out element, s0 => s0.EndsWith); } public override string ToString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs new file mode 100644 index 00000000..fd8de403 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs @@ -0,0 +1,70 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal static class EvaluateMinMax +{ + internal static bool TryEvaluate(IList args, IBinaryOperator binaryOperator, out IValue element) + { + var arg0 = args[0]; + if (arg0.Type != JmesPathType.Array) + { + element = JsonConstants.Null; + return false; + } + + if (arg0.GetArrayLength() == 0) + { + element = JsonConstants.Null; + return false; + } + + var isNumber = arg0[0].Type == JmesPathType.Number; + var isString = arg0[0].Type == JmesPathType.String; + if (!isNumber && !isString) + { + element = JsonConstants.Null; + return false; + } + + var index = 0; + for (var i = 1; i < arg0.GetArrayLength(); ++i) + { + if (!(arg0[i].Type == JmesPathType.Number == isNumber && + arg0[i].Type == JmesPathType.String == isString)) + { + element = JsonConstants.Null; + return false; + } + + if (!binaryOperator.TryEvaluate(arg0[i], arg0[index], out var value)) + { + element = JsonConstants.Null; + return false; + } + + if (Expression.IsTrue(value)) + { + index = i; + } + } + + element = arg0[index]; + return true; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs new file mode 100644 index 00000000..a177a5ac --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs @@ -0,0 +1,84 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal static class EvaluateMinMaxBy +{ + internal static bool TryEvaluate(DynamicResources resources, IList args, IBinaryOperator binaryOperator, out IValue element) + { + if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression)) + { + element = JsonConstants.Null; + return false; + } + + var arg0 = args[0]; + if (arg0.GetArrayLength() == 0) + { + element = JsonConstants.Null; + return true; + } + + var expr = args[1].GetExpression(); + + if (!expr.TryEvaluate(resources, arg0[0], out var key1)) + { + element = JsonConstants.Null; + return false; + } + + var isNumber1 = key1.Type == JmesPathType.Number; + var isString1 = key1.Type == JmesPathType.String; + if (!(isNumber1 || isString1)) + { + element = JsonConstants.Null; + return false; + } + + var index = 0; + for (var i = 1; i < arg0.GetArrayLength(); ++i) + { + if (!expr.TryEvaluate(resources, arg0[i], out var key2)) + { + element = JsonConstants.Null; + return false; + } + + var isNumber2 = key2.Type == JmesPathType.Number; + var isString2 = key2.Type == JmesPathType.String; + if (!(isNumber2 == isNumber1 && isString2 == isString1)) + { + element = JsonConstants.Null; + return false; + } + + if (!binaryOperator.TryEvaluate(key2, key1, out var value)) + { + element = JsonConstants.Null; + return false; + } + + if (value.Type != JmesPathType.True) continue; + key1 = key2; + index = i; + } + + element = arg0[index]; + return true; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs new file mode 100644 index 00000000..11ae9ee2 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs @@ -0,0 +1,40 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Functions; + +internal static class EvaluateStartEndWith +{ + internal static bool TryEvaluate(IList args, out IValue element, Func> method) + { + var arg0 = args[0]; + var arg1 = args[1]; + if (arg0.Type != JmesPathType.String + || arg1.Type != JmesPathType.String) + { + element = JsonConstants.Null; + return false; + } + + var s0 = arg0.GetString(); + var s1 = arg1.GetString(); + element = method(s0)(s1) ? JsonConstants.True : JsonConstants.False; + + return true; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs index c65e72a2..4eef9848 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.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.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs index ddbd8634..476838e8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.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.Collections.Generic; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs index 827e2316..b32b6001 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.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.Collections.Generic; using System.Diagnostics; using System.Text; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs index 560947c1..aba2b558 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs index 915013df..257812cd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs index 078621f2..e1779c3b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.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.Collections.Generic; using System.Diagnostics; using System.Text; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs index 10ca0c96..100c4790 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs index cdca7214..0cfd4464 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.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.Collections.Generic; using System.Diagnostics; @@ -13,67 +28,8 @@ internal MaxByFunction() 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)) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return true; - } - - var expr = args[1].GetExpression(); - - if (!expr.TryEvaluate(resources, arg0[0], out var key1)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber1 = key1.Type == JmesPathType.Number; - var isString1 = key1.Type == JmesPathType.String; - if (!(isNumber1 || isString1)) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber2 = key2.Type == JmesPathType.Number; - var isString2 = key2.Type == JmesPathType.String; - if (!(isNumber2 == isNumber1 && isString2 == isString1)) - { - element = JsonConstants.Null; - return false; - } - - if (!greater.TryEvaluate(key2, key1, out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (value.Type != JmesPathType.True) continue; - key1 = key2; - index = i; - } - - element = arg0[index]; - return true; + + return EvaluateMinMaxBy.TryEvaluate(resources, args, GtOperator.Instance, out element); } public override string ToString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs index 551ccf2c..ffa99756 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.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.Collections.Generic; using System.Diagnostics; @@ -15,52 +30,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array) - { - element = JsonConstants.Null; - return false; - } - - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return false; - } - - var isNumber = arg0[0].Type == JmesPathType.Number; - var isString = arg0[0].Type == JmesPathType.String; - if (!isNumber && !isString) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - if (!greater.TryEvaluate(arg0[i], arg0[index], out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (Expression.IsTrue(value)) - { - index = i; - } - } - - element = arg0[index]; - return true; + return EvaluateMinMax.TryEvaluate(args, GtOperator.Instance, out element); } public override string ToString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs index 60b488ec..e0dde1ac 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.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.Collections.Generic; using System.Linq; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs index 9196c03b..82f7c38f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.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.Collections.Generic; using System.Diagnostics; @@ -14,66 +29,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression)) - { - element = JsonConstants.Null; - return false; - } - - var arg0 = args[0]; - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return true; - } - - var expr = args[1].GetExpression(); - - if (!expr.TryEvaluate(resources, arg0[0], out var key1)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber1 = key1.Type == JmesPathType.Number; - var isString1 = key1.Type == JmesPathType.String; - if (!(isNumber1 || isString1)) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - var isNumber2 = key2.Type == JmesPathType.Number; - var isString2 = key2.Type == JmesPathType.String; - if (!(isNumber2 == isNumber1 && isString2 == isString1)) - { - element = JsonConstants.Null; - return false; - } - - if (!lessor.TryEvaluate(key2, key1, out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (value.Type != JmesPathType.True) continue; - key1 = key2; - index = i; - } - - element = arg0[index]; - return true; + return EvaluateMinMaxBy.TryEvaluate(resources, args, LtOperator.Instance, out element); } public override string ToString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs index 6a8612dc..2fd25e97 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.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.Collections.Generic; using System.Diagnostics; @@ -15,52 +30,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); - var arg0 = args[0]; - if (arg0.Type != JmesPathType.Array) - { - element = JsonConstants.Null; - return false; - } - - if (arg0.GetArrayLength() == 0) - { - element = JsonConstants.Null; - return false; - } - - var isNumber = arg0[0].Type == JmesPathType.Number; - var isString = arg0[0].Type == JmesPathType.String; - if (!isNumber && !isString) - { - element = 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)) - { - element = JsonConstants.Null; - return false; - } - - if (!less.TryEvaluate(arg0[i], arg0[index], out var value)) - { - element = JsonConstants.Null; - return false; - } - - if (value.Type == JmesPathType.True) - { - index = i; - } - } - - element = arg0[index]; - return true; + return EvaluateMinMax.TryEvaluate(args, LtOperator.Instance, out element); } public override string ToString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs index fc8afcfb..d2eee194 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.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.Collections.Generic; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs index 0c6837d8..cba14dc6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.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.Collections.Generic; using System.Diagnostics; using System.Globalization; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs index 7af9b922..4b54a400 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs index b697a0a7..009bf66d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs index 02b7d86d..a59261a3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.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.Collections.Generic; using System.Diagnostics; @@ -15,20 +30,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { 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) - { - element = JsonConstants.Null; - return false; - } - - var s0 = arg0.GetString(); - var s1 = arg1.GetString(); - element = s0.StartsWith(s1) ? JsonConstants.True : JsonConstants.False; - - return true; + return EvaluateStartEndWith.TryEvaluate(args, out element, s0 => s0.StartsWith); } public override string ToString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs index 8c3956f4..554e91db 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs index 4cad1f76..9c79e35f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs index 49194fb5..d00f8eb0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.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.Collections.Generic; using System.Diagnostics; using System.Globalization; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs index caaff747..bea47423 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs index c8d61538..aac891ba 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.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.Collections.Generic; using System.Diagnostics; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs index e46c05b9..1942c9aa 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.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.Collections.Generic; using System.Diagnostics; From ea6b795b7fb8f6e8b3646c9787ecda230d220900 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:23:05 +0100 Subject: [PATCH 59/82] refactor values --- .../BinaryOperator.cs | 2 + .../Expression.cs | 1 + .../Functions/AbsFunction.cs | 1 + .../Functions/AvgFunction.cs | 1 + .../Functions/Base64Function.cs | 1 + .../Functions/Base64GzipFunction.cs | 1 + .../Functions/BaseFunction.cs | 1 + .../Functions/CeilFunction.cs | 1 + .../Functions/ContainsFunction.cs | 1 + .../Functions/EndsWithFunction.cs | 1 + .../Functions/EvaluateMinMax.cs | 1 + .../Functions/EvaluateMinMaxBy.cs | 1 + .../Functions/EvaluateStartEndWith.cs | 1 + .../Functions/FloorFunction.cs | 1 + .../Functions/IFunction.cs | 1 + .../Functions/JoinFunction.cs | 1 + .../Functions/JsonFunction.cs | 1 + .../Functions/KeysFunction.cs | 1 + .../Functions/LengthFunction.cs | 1 + .../Functions/MapFunction.cs | 1 + .../Functions/MaxByFunction.cs | 1 + .../Functions/MaxFunction.cs | 1 + .../Functions/MergeFunction.cs | 1 + .../Functions/MinByFunction.cs | 1 + .../Functions/MinFunction.cs | 1 + .../Functions/NotNullFunction.cs | 1 + .../Functions/ReverseFunction.cs | 1 + .../Functions/SortByComparer.cs | 1 + .../Functions/SortByFunction.cs | 1 + .../Functions/SortFunction.cs | 1 + .../Functions/StartsWithFunction.cs | 1 + .../Functions/SumFunction.cs | 1 + .../Functions/ToArrayFunction.cs | 1 + .../Functions/ToNumberFunction.cs | 1 + .../Functions/ToStringFunction.cs | 1 + .../Functions/TypeFunction.cs | 1 + .../Functions/ValuesFunction.cs | 1 + .../JmesPathParser.cs | 1 + .../JsonTransformer.cs | 1 + .../AWS.Lambda.Powertools.JMESPath/Token.cs | 1 + .../UnaryOperator.cs | 1 + .../AWS.Lambda.Powertools.JMESPath/Value.cs | 913 ------------------ .../Values/ArrayValue.cs | 129 +++ .../Values/DecimalValue.cs | 81 ++ .../Values/DoubleValue.cs | 88 ++ .../Values/ExpressionValue.cs | 77 ++ .../Values/FalseValue.cs | 70 ++ .../Values/IArrayValueEnumerator.cs | 22 + .../Values/IObjectValueEnumerator.cs | 22 + .../Values/IValue.cs | 30 + .../Values/JmesPathType.cs | 28 + .../Values/JsonElementValue.cs | 224 +++++ .../Values/NameValuePair.cs | 29 + .../Values/NullValue.cs | 70 ++ .../Values/ObjectValue.cs | 147 +++ .../Values/StringValue.cs | 79 ++ .../Values/TrueValue.cs | 70 ++ .../{ => Values}/ValueComparer.cs | 2 +- .../{ => Values}/ValueEqualityComparer.cs | 2 +- 59 files changed, 1210 insertions(+), 915 deletions(-) delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs rename libraries/src/AWS.Lambda.Powertools.JMESPath/{ => Values}/ValueComparer.cs (99%) rename libraries/src/AWS.Lambda.Powertools.JMESPath/{ => Values}/ValueEqualityComparer.cs (99%) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs index 5e93629a..9e01cffa 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +using AWS.Lambda.Powertools.JMESPath.Values; + namespace AWS.Lambda.Powertools.JMESPath { internal interface IBinaryOperator diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs index 426278ea..41b2cd23 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs index e1733344..08dafa02 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs index 9dc83294..0ee6e5b3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs index 6db33b66..8902aadf 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs index a68cd5c8..eacec5a3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs @@ -20,6 +20,7 @@ using System.IO.Compression; using System.Text; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs index b4162cea..f3925a2c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs index 2f0e773c..81f45289 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs index 2953eb98..01b5e051 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs index c0af8786..25873f32 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs index fd8de403..233b7cde 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs index a177a5ac..423b6ab4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs index 11ae9ee2..52e14913 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs index 4eef9848..2c559328 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs index 476838e8..610b02bd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs index b32b6001..4e62ab84 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs index aba2b558..b2d63595 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs index 257812cd..0dd72074 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs index e1779c3b..a3104b64 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs index 100c4790..e08680af 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs index 0cfd4464..ffe3dcc3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs index ffa99756..16cde15c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs index e0dde1ac..17b60d45 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs index 82f7c38f..ccc8a9b8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs index 2fd25e97..005bb8bd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs index d2eee194..8d2cdcf4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs index cba14dc6..94af36a0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs @@ -17,6 +17,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs index 70c5167e..af54b9fd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs index 4b54a400..5d9c1481 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs index 009bf66d..af8f092c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs index a59261a3..4ba5f87e 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs index 554e91db..e634a7d5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs index 9c79e35f..02e6e22b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs index d00f8eb0..83e5f98f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs index bea47423..d6745944 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs index aac891ba..16ab2b46 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs index 1942c9aa..6ddf44dc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs index a7641fe3..9b609368 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -19,6 +19,7 @@ using System.Text; using System.Text.Json; using AWS.Lambda.Powertools.JMESPath.Functions; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs index f7146737..80530cc2 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs @@ -15,6 +15,7 @@ using System; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs index 7ac5433b..8a27fb20 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs @@ -16,6 +16,7 @@ using System; using System.Diagnostics; using AWS.Lambda.Powertools.JMESPath.Functions; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs index 6e21c0fd..b769a1e1 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs @@ -14,6 +14,7 @@ */ using System.Text.RegularExpressions; +using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs deleted file mode 100644 index 4b157cbe..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Value.cs +++ /dev/null @@ -1,913 +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; -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(); - } - - 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); - - 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(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - // Cleanup - if (disposing) - { - _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 sealed 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); - } - - buffer.Append(']'); - return buffer.ToString(); - } - } - - internal readonly struct ObjectValue : IValue - { - private sealed 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); - } - - 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"; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs new file mode 100644 index 00000000..43008b80 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs @@ -0,0 +1,129 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +internal readonly struct ArrayValue : IValue +{ + private sealed 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); + } + + buffer.Append(']'); + return buffer.ToString(); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs new file mode 100644 index 00000000..3cdf685d --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs @@ -0,0 +1,81 @@ +/* + * 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.Values; + +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; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs new file mode 100644 index 00000000..56649d55 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs @@ -0,0 +1,88 @@ +/* + * 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.Values; + +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; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs new file mode 100644 index 00000000..b9a1e425 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs @@ -0,0 +1,77 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs new file mode 100644 index 00000000..6b06928c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs @@ -0,0 +1,70 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs new file mode 100644 index 00000000..a96a311c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs @@ -0,0 +1,22 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +internal interface IArrayValueEnumerator : IEnumerator, IEnumerable +{ +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs new file mode 100644 index 00000000..05f6b075 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs @@ -0,0 +1,22 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +internal interface IObjectValueEnumerator : IEnumerator, IEnumerable +{ +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs new file mode 100644 index 00000000..a0295f62 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs @@ -0,0 +1,30 @@ +/* + * 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.Values; + +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(); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs new file mode 100644 index 00000000..20f154f3 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs @@ -0,0 +1,28 @@ +/* + * 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.Values; + +internal enum JmesPathType +{ + Null, + Array, + False, + Number, + Object, + String, + True, + Expression +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs new file mode 100644 index 00000000..61028276 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs @@ -0,0 +1,224 @@ +/* + * 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.Json; +using System.Text.Json.Nodes; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +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(); + } + + 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); + + 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(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + // Cleanup + if (disposing) + { + _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; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs new file mode 100644 index 00000000..2ffe2717 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs @@ -0,0 +1,29 @@ +/* + * 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.Values +{ + internal readonly struct NameValuePair + { + public string Name { get; } + public IValue Value { get; } + + public NameValuePair(string name, IValue value) + { + Name = name; + Value = value; + } + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs new file mode 100644 index 00000000..fc509571 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs @@ -0,0 +1,70 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs new file mode 100644 index 00000000..c1ff0c9e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs @@ -0,0 +1,147 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +internal readonly struct ObjectValue : IValue +{ + private sealed 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); + } + + buffer.Append('}'); + return buffer.ToString(); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs new file mode 100644 index 00000000..1146cc21 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs @@ -0,0 +1,79 @@ +/* + * 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.Values; + +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; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs new file mode 100644 index 00000000..60309692 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs @@ -0,0 +1,70 @@ +/* + * 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; + +namespace AWS.Lambda.Powertools.JMESPath.Values; + +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs similarity index 99% rename from libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs rename to libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs index fb1b8c48..20738000 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs @@ -17,7 +17,7 @@ using System.Collections.Generic; using System.Linq; -namespace AWS.Lambda.Powertools.JMESPath +namespace AWS.Lambda.Powertools.JMESPath.Values { /// /// Compares two instances. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs similarity index 99% rename from libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs rename to libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs index ef04d261..bdc83197 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/ValueEqualityComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs @@ -17,7 +17,7 @@ using System.Collections.Generic; using System.Linq; -namespace AWS.Lambda.Powertools.JMESPath +namespace AWS.Lambda.Powertools.JMESPath.Values { internal sealed class ValueEqualityComparer : IEqualityComparer { From 83560afa4501894ca52be66bbac948c7cb5dbbbb Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:15:38 +0100 Subject: [PATCH 60/82] Add comments to code. Refactor Expressions and Operators --- .../BinaryOperator.cs | 326 -------- .../Expression.cs | 757 ------------------ .../Expressions/BaseExpression.cs | 55 ++ .../Expressions/CurrentNode.cs | 43 + .../Expressions/Expression.cs | 178 ++++ .../Expressions/FilterExpression.cs | 76 ++ .../Expressions/FlattenProjection.cs | 84 ++ .../Expressions/FunctionExpression.cs | 54 ++ .../Expressions/IExpression.cs | 55 ++ .../Expressions/IdentifierSelector.cs | 54 ++ .../Expressions/IndexSelector.cs | 66 ++ .../Expressions/JsonConstants.cs | 35 + .../Expressions/KeyExpressionPair.cs | 31 + .../Expressions/ListProjection.cs | 66 ++ .../Expressions/MultiSelectHash.cs | 66 ++ .../Expressions/MultiSelectList.cs | 66 ++ .../Expressions/ObjectProjection.cs | 63 ++ .../Expressions/Projection.cs | 74 ++ .../Expressions/SliceProjection.cs | 114 +++ .../Functions/AbsFunction.cs | 5 + .../Functions/AvgFunction.cs | 5 + .../Functions/Base64Function.cs | 4 + .../Functions/Base64GzipFunction.cs | 4 + .../Functions/BaseFunction.cs | 5 + .../Functions/BuiltInFunctions.cs | 3 + .../Functions/CeilFunction.cs | 5 + .../Functions/ContainsFunction.cs | 5 + .../Functions/EndsWithFunction.cs | 4 + .../Functions/EvaluateMinMax.cs | 12 + .../Functions/EvaluateMinMaxBy.cs | 13 + .../Functions/EvaluateStartEndWith.cs | 13 + .../Functions/FloorFunction.cs | 5 + .../Functions/IFunction.cs | 10 + .../Functions/JoinFunction.cs | 5 + .../Functions/JsonFunction.cs | 4 + .../Functions/KeysFunction.cs | 5 + .../Functions/LengthFunction.cs | 5 + .../Functions/MapFunction.cs | 5 + .../Functions/MaxByFunction.cs | 5 + .../Functions/MaxFunction.cs | 5 + .../Functions/MergeFunction.cs | 5 + .../Functions/MinByFunction.cs | 5 + .../Functions/MinFunction.cs | 5 + .../Functions/NotNullFunction.cs | 5 + .../Functions/ReverseFunction.cs | 5 + .../Functions/SortByComparer.cs | 4 + .../Functions/SortByFunction.cs | 5 + .../Functions/SortFunction.cs | 5 + .../Functions/StartsWithFunction.cs | 4 + .../Functions/SumFunction.cs | 5 + .../Functions/ToArrayFunction.cs | 4 + .../Functions/ToNumberFunction.cs | 5 + .../Functions/ToStringFunction.cs | 5 + .../Functions/TypeFunction.cs | 5 + .../Functions/ValuesFunction.cs | 5 + .../JmesPathParseException.cs | 51 ++ .../JmesPathParser.cs | 108 +-- .../JmesPathState.cs | 74 ++ .../JsonTransformer.cs | 1 + .../Operator.cs | 2 +- .../Operators/AndOperator.cs | 47 ++ .../Operators/BinaryOperator.cs | 40 + .../Operators/EqOperator.cs | 48 ++ .../Operators/GtOperator.cs | 73 ++ .../Operators/GteOperator.cs | 73 ++ .../Operators/IBinaryOperator.cs | 39 + .../Operators/IUnaryOperator.cs | 42 + .../Operators/LtOperator.cs | 73 ++ .../Operators/LteOperator.cs | 74 ++ .../Operators/NeOperator.cs | 53 ++ .../Operators/NotOperator.cs | 46 ++ .../Operators/OrOperator.cs | 52 ++ .../Operators/RegexOperator.cs | 32 + .../Operators/UnaryOperator.cs | 41 + .../AWS.Lambda.Powertools.JMESPath/Slice.cs | 22 + .../AWS.Lambda.Powertools.JMESPath/Token.cs | 48 ++ .../UnaryOperator.cs | 90 --- .../Values/ArrayValue.cs | 50 ++ .../Values/DecimalValue.cs | 23 + .../Values/DoubleValue.cs | 23 +- .../Values/ExpressionValue.cs | 17 + .../Values/FalseValue.cs | 14 + .../Values/IArrayValueEnumerator.cs | 3 + .../Values/IObjectValueEnumerator.cs | 3 + .../Values/IValue.cs | 54 ++ .../Values/JmesPathType.cs | 3 + .../Values/JsonElementValue.cs | 26 + .../Values/NameValuePair.cs | 3 + .../Values/NullValue.cs | 14 + .../Values/ObjectValue.cs | 28 +- .../Values/StringValue.cs | 17 + .../Values/TrueValue.cs | 14 + .../Values/ValueEqualityComparer.cs | 12 +- 93 files changed, 2629 insertions(+), 1266 deletions(-) delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs create mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs deleted file mode 100644 index 9e01cffa..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/BinaryOperator.cs +++ /dev/null @@ -1,326 +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 AWS.Lambda.Powertools.JMESPath.Values; - -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 - { - private protected 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) - { - switch (lhs.Type) - { - case JmesPathType.Number when 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; - } - - break; - } - 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; - break; - } - - 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) - { - switch (lhs.Type) - { - case JmesPathType.Number when 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; - } - - break; - } - 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; - break; - } - - 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) - { - switch (lhs.Type) - { - case JmesPathType.Number when 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; - } - - break; - } - 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; - break; - } - - 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) - { - switch (lhs.Type) - { - case JmesPathType.Number when 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; - } - - break; - } - 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; - break; - } - - 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 deleted file mode 100644 index 41b2cd23..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expression.cs +++ /dev/null @@ -1,757 +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.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using AWS.Lambda.Powertools.JMESPath.Values; - -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;} - - private protected 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 expr) - { - } - - 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; - - private protected 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) continue; - 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) 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); - 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)) 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); - 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) - { - 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/Expressions/BaseExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs new file mode 100644 index 00000000..925f8c97 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs @@ -0,0 +1,55 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Base class for all expressions. +/// +internal abstract class BaseExpression : IExpression +{ + /// + public int PrecedenceLevel {get;} + + /// + public bool IsRightAssociative {get;} + + /// + public bool IsProjection {get;} + + private protected 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 expr) + { + } + + public override string ToString() + { + return "ToString not implemented"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs new file mode 100644 index 00000000..1794d8b4 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs @@ -0,0 +1,43 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents the current node. +/// +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs new file mode 100644 index 00000000..8ae2d52a --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs @@ -0,0 +1,178 @@ +/* + * 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; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions +{ + // BaseExpression + + /// + /// Represents a JMESPath expression. + /// + internal class Expression + { + /// + /// The tokens in the expression. + /// + private readonly Token[] _tokens; + + internal Expression(Token[] tokens) + { + _tokens = tokens; + } + + /// + /// Evaluates the expression against the given resources. + /// + 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) + { + 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/Expressions/FilterExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs new file mode 100644 index 00000000..38d0e4ed --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs @@ -0,0 +1,76 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a filter expression. +/// +internal sealed class FilterExpression : Projection +{ + /// + /// The expression to evaluate. + /// + 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)) 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); + return true; + } + + public override string ToString() + { + return "FilterExpression"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs new file mode 100644 index 00000000..d3946321 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs @@ -0,0 +1,84 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents the flatten projection operator. +/// +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) continue; + 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) 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); + return true; + } + + public override string ToString() + { + return "FlattenProjection"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs new file mode 100644 index 00000000..e5630582 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs @@ -0,0 +1,54 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a function expression. +/// +internal sealed class FunctionExpression : BaseExpression +{ + /// + /// The expression to evaluate. + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs new file mode 100644 index 00000000..1e083366 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs @@ -0,0 +1,55 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a JMESPath expression. +/// +internal interface IExpression +{ + /// + /// Evaluates the expression against the provided resources. + /// + /// + /// + /// + /// + bool TryEvaluate(DynamicResources resources, + IValue current, + out IValue value); + + /// + /// The precedence level of the expression. + /// + int PrecedenceLevel {get;} + + /// + /// True if the expression is a projection, false otherwise. + /// + bool IsProjection {get;} + + /// + /// True if the expression is right-associative, false otherwise. + /// + bool IsRightAssociative {get;} + + /// + /// Adds an expression to the expression. + /// + void AddExpression(IExpression expr); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs new file mode 100644 index 00000000..1f64ee00 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs @@ -0,0 +1,54 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a JMESPath identifier expression. +/// +internal sealed class IdentifierSelector : BaseExpression +{ + /// + /// The identifier to select. + /// + 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}"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs new file mode 100644 index 00000000..0a21b4f6 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs @@ -0,0 +1,66 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a single index selector. +/// +internal sealed class IndexSelector : BaseExpression +{ + /// + /// The index of the selector. + /// + 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}"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs new file mode 100644 index 00000000..71f6a394 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs @@ -0,0 +1,35 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Constants used by the JMESPath parser. +/// +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;} +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs new file mode 100644 index 00000000..32d21edc --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs @@ -0,0 +1,31 @@ +/* + * 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.Expressions; + +/// +/// A pair of a JMESPath key and an expression. +/// +internal struct KeyExpressionPair +{ + internal string Key {get;} + internal Expression Expression {get;} + + internal KeyExpressionPair(string key, Expression expression) + { + Key = key; + Expression = expression; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs new file mode 100644 index 00000000..a36e6292 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs @@ -0,0 +1,66 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a projection that returns a list of values. +/// +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs new file mode 100644 index 00000000..21404a1c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs @@ -0,0 +1,66 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a multi-select hash expression. +/// +internal sealed class MultiSelectHash : BaseExpression +{ + /// + /// The list of key expression pairs. + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs new file mode 100644 index 00000000..0b7cf183 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs @@ -0,0 +1,66 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a multi-select list expression. +/// +internal sealed class MultiSelectList : BaseExpression +{ + /// + /// The list of expressions to evaluate. + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs new file mode 100644 index 00000000..a6ec0035 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs @@ -0,0 +1,63 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents the projection of an object. +/// +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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs new file mode 100644 index 00000000..28d1ea4e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs @@ -0,0 +1,74 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Base class for projection expressions. +/// +internal abstract class Projection : BaseExpression +{ + /// + /// List of expressions to be applied to the current value. + /// + private readonly List _expressions; + + private protected Projection(Operator oper) + : base(oper, true) + { + _expressions = new List(); + } + + /// + /// Adds an expression to the list of expressions to be applied to the current value. + /// + /// + 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); + } + } + + /// + /// Tries to apply the list of expressions to the current value. + /// + /// + /// + /// + /// + 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; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs new file mode 100644 index 00000000..5c895033 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs @@ -0,0 +1,114 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Expressions; + +/// +/// Represents a projection of a slice of an array. +/// +internal sealed class SliceProjection : Projection +{ + /// + /// The slice to project. + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs index 08dafa02..ef58d85b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the absolute value of a number. +/// internal sealed class AbsFunction : BaseFunction { internal AbsFunction() @@ -26,6 +30,7 @@ internal AbsFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs index 0ee6e5b3..70c5b3ee 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the average of the values +/// internal sealed class AvgFunction : BaseFunction { internal AvgFunction() @@ -26,6 +30,7 @@ internal AvgFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs index 8902aadf..9eab21ff 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs @@ -21,6 +21,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the Base64 encoded value of a string. powertools_base64 +/// internal sealed class Base64Function : BaseFunction { /// @@ -34,6 +37,7 @@ public override string ToString() return "powertools_base64"; } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs index eacec5a3..e3f27b96 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs @@ -24,6 +24,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Decodes a base64-encoded, gzip-compressed string into a JSON document. +/// internal sealed class Base64GzipFunction : BaseFunction { /// @@ -37,6 +40,7 @@ public override string ToString() return "powertools_base64_gzip"; } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs index f3925a2c..987c88ce 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs @@ -18,6 +18,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Base class for JMESPath functions. +/// internal abstract class BaseFunction : IFunction { private protected BaseFunction(int? argCount) @@ -25,7 +28,9 @@ private protected BaseFunction(int? argCount) Arity = argCount; } + /// public int? Arity { get; } + /// public abstract bool TryEvaluate(DynamicResources resources, IList args, out IValue element); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs index b953e136..5555e0b5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs @@ -17,6 +17,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// A registry of built-in functions. +/// internal sealed class BuiltInFunctions { internal static BuiltInFunctions Instance { get; } = new(); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs index 81f45289..cfd55eba 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs @@ -16,10 +16,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the smallest integer greater than or equal to the argument. +/// internal sealed class CeilFunction : BaseFunction { internal CeilFunction() @@ -27,6 +31,7 @@ internal CeilFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs index 01b5e051..8bd8b80c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs @@ -16,10 +16,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns true if the first argument contains the second argument. +/// internal sealed class ContainsFunction : BaseFunction { internal ContainsFunction() @@ -27,6 +31,7 @@ internal ContainsFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs index 25873f32..92ccfdfd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs @@ -19,6 +19,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns true if the first argument ends with the second argument. +/// internal sealed class EndsWithFunction : BaseFunction { internal EndsWithFunction() @@ -26,6 +29,7 @@ internal EndsWithFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs index 233b7cde..d1c9b0f3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs @@ -14,12 +14,24 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Evaluates the min and max functions. +/// internal static class EvaluateMinMax { + /// + /// Evaluates the min and max functions. + /// + /// + /// + /// + /// internal static bool TryEvaluate(IList args, IBinaryOperator binaryOperator, out IValue element) { var arg0 = args[0]; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs index 423b6ab4..064521f9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs @@ -14,12 +14,25 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Evaluates the min and max functions by resources. +/// internal static class EvaluateMinMaxBy { + /// + /// Evaluates the min and max function by resources. + /// + /// + /// + /// + /// + /// internal static bool TryEvaluate(DynamicResources resources, IList args, IBinaryOperator binaryOperator, out IValue element) { if (!(args[0].Type == JmesPathType.Array && args[1].Type == JmesPathType.Expression)) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs index 52e14913..c64b4545 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs @@ -15,12 +15,25 @@ using System; using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Evaluates true if the first string starts with the second string. +/// Evaluates true if the first string ends with the second string. +/// internal static class EvaluateStartEndWith { + /// + /// Evaluates true if the first string starts with the second string. + /// Evaluates true if the first string ends with the second string. + /// + /// + /// + /// + /// internal static bool TryEvaluate(IList args, out IValue element, Func> method) { var arg0 = args[0]; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs index 2c559328..763a7100 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs @@ -16,10 +16,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the largest integer less than or equal to the argument. +/// internal sealed class FloorFunction : BaseFunction { internal FloorFunction() @@ -27,6 +31,7 @@ internal FloorFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs index 610b02bd..aa7198fc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs @@ -18,8 +18,18 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Represents a JMESPath function. +/// internal interface IFunction { + /// + /// The number of arguments the function takes. + /// int? Arity { get; } + + /// + /// Evaluates the function. + /// bool TryEvaluate(DynamicResources resources, IList args, out IValue element); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs index 4e62ab84..52df606b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs @@ -16,10 +16,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns a string consisting of a list of strings joined by a separator. +/// internal sealed class JoinFunction : BaseFunction { internal JoinFunction() @@ -27,6 +31,7 @@ internal JoinFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs index b2d63595..80e4daf9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs @@ -19,6 +19,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the JSON representation of a value. +/// internal sealed class JsonFunction : BaseFunction { /// @@ -32,6 +35,7 @@ public override string ToString() return "powertools_json"; } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs index 0dd72074..75c773ac 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the keys of the object as an array. +/// internal sealed class KeysFunction : BaseFunction { internal KeysFunction() @@ -26,6 +30,7 @@ internal KeysFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs index a3104b64..7be3c5f9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs @@ -16,10 +16,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the number of elements in a value. +/// internal sealed class LengthFunction : BaseFunction { internal LengthFunction() @@ -27,6 +31,7 @@ internal LengthFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs index e08680af..5ce795a9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns a new array with the results of calling a provided function on every element in the calling array. +/// internal sealed class MapFunction : BaseFunction { internal MapFunction() @@ -26,6 +30,7 @@ internal MapFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs index ffe3dcc3..4804bce6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the maximum value of the expression by resource. +/// internal sealed class MaxByFunction : BaseFunction { internal MaxByFunction() @@ -26,6 +30,7 @@ internal MaxByFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs index 16cde15c..e37e85ef 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the maximum value in a list. +/// internal sealed class MaxFunction : BaseFunction { internal MaxFunction() @@ -26,6 +30,7 @@ internal MaxFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs index 17b60d45..5af18d99 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Merges multiple objects into a single object. +/// internal sealed class MergeFunction : BaseFunction { internal MergeFunction() @@ -26,6 +30,7 @@ internal MergeFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { if (!args.Any()) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs index ccc8a9b8..08fffc2a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the element with the minimum value by resource +/// internal sealed class MinByFunction : BaseFunction { internal MinByFunction() @@ -26,6 +30,7 @@ internal MinByFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs index 005bb8bd..c8ae1c20 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the minimum value in a element. +/// internal sealed class MinFunction : BaseFunction { internal MinFunction() @@ -26,6 +30,7 @@ internal MinFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs index 8d2cdcf4..b362da73 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs @@ -14,10 +14,14 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the first non-null argument. +/// internal sealed class NotNullFunction : BaseFunction { internal NotNullFunction() @@ -25,6 +29,7 @@ internal NotNullFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { foreach (var arg in args) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs index 94af36a0..f66f1e90 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs @@ -17,10 +17,14 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the elements of the input array in reverse order. +/// internal sealed class ReverseFunction : BaseFunction { internal ReverseFunction() @@ -28,6 +32,7 @@ internal ReverseFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs index af54b9fd..eea78e56 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs @@ -14,10 +14,14 @@ */ using System.Collections.Generic; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions { + /// + /// Implements the sort_by function. + /// internal sealed class SortByComparer : IComparer, System.Collections.IComparer { private readonly DynamicResources _resources; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs index 5d9c1481..a4ae4fcf 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the input array sorted by the value of the expression by resources. +/// internal sealed class SortByFunction : BaseFunction { internal SortByFunction() @@ -26,6 +30,7 @@ internal SortByFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs index af8f092c..2bb5fffd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the sorted elements of the input array. +/// internal sealed class SortFunction : BaseFunction { internal SortFunction() @@ -26,6 +30,7 @@ internal SortFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs index 4ba5f87e..0b7d7c74 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs @@ -19,6 +19,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns true if the first argument starts with the second argument. +/// internal sealed class StartsWithFunction : BaseFunction { internal StartsWithFunction() @@ -26,6 +29,7 @@ internal StartsWithFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs index e634a7d5..15d59853 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the sum of the values in a list. +/// internal sealed class SumFunction : BaseFunction { internal static SumFunction Instance { get; } = new(); @@ -28,6 +32,7 @@ internal SumFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs index 02e6e22b..fa7d7f6a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs @@ -19,6 +19,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the input as an array if it is not already an array. +/// internal sealed class ToArrayFunction : BaseFunction { internal ToArrayFunction() @@ -26,6 +29,7 @@ internal ToArrayFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs index 83e5f98f..fb68800d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs @@ -16,10 +16,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Converts a string to a number. +/// internal sealed class ToNumberFunction : BaseFunction { internal ToNumberFunction() @@ -27,6 +31,7 @@ internal ToNumberFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs index d6745944..41c18ddc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the string representation of a value. +/// internal sealed class ToStringFunction : BaseFunction { internal ToStringFunction() @@ -26,6 +30,7 @@ internal ToStringFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs index 16ab2b46..24529604 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the type of the value as a string. +/// internal sealed class TypeFunction : BaseFunction { internal TypeFunction() @@ -26,6 +30,7 @@ internal TypeFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs index 6ddf44dc..60e472c4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs @@ -15,10 +15,14 @@ using System.Collections.Generic; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath.Functions; +/// +/// Returns the values of an object. +/// internal sealed class ValuesFunction : BaseFunction { internal ValuesFunction() @@ -26,6 +30,7 @@ internal ValuesFunction() { } + /// public override bool TryEvaluate(DynamicResources resources, IList args, out IValue element) { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs new file mode 100644 index 00000000..b1d5b8d4 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs @@ -0,0 +1,51 @@ +/* + * 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; + +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. + /// + private int LineNumber {get;} + + /// + /// The column in the JMESPath string where a parse error was detected. + /// + private 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}"; + } +} \ 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 9b609368..a73a5a02 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -18,99 +18,16 @@ using System.Diagnostics; using System.Text; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Functions; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; 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. - /// - private int LineNumber {get;} - - /// - /// The column in the JMESPath string where a parse error was detected. - /// - private 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 - } - + /// Parses a JMESPath expression and returns a . + /// internal ref struct JmesPathParser { private readonly ReadOnlySpan _span; @@ -132,6 +49,9 @@ internal JmesPathParser(string input) _operatorStack = new Stack(); } + /// + /// Parses a JMESPath expression and returns a . + /// internal JsonTransformer Parse() { _stateStack.Clear(); @@ -1411,7 +1331,7 @@ internal JsonTransformer Parse() return new JsonTransformer(new Expression(a)); } - + private void SkipWhiteSpace() { switch (_span[_index]) @@ -1448,6 +1368,11 @@ private void UnwindRightParen() _operatorStack.Pop(); // TokenType.LeftParen } + /// + /// Pushes a token onto the output stack. + /// + /// + /// private void PushToken(Token token) { switch (token.Type) @@ -1721,6 +1646,13 @@ private void PushToken(Token token) } } + /// + /// Appends the given codepoint to the current codepoint. + /// + /// + /// + /// + /// private uint AppendToCodepoint(uint cp, uint c) { cp *= 16; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs new file mode 100644 index 00000000..a8bb4fb3 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs @@ -0,0 +1,74 @@ +/* + * 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; + +/// +/// The state of the JMESPath parser. +/// +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 +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs index 80530cc2..79409d3d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs @@ -15,6 +15,7 @@ using System; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs index bff60479..4f245603 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs @@ -30,7 +30,7 @@ internal enum Operator Gte, Not } - + internal static class OperatorTable { internal static int PrecedenceLevel(Operator oper) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs new file mode 100644 index 00000000..7535e40b --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs @@ -0,0 +1,47 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are and. +/// +internal sealed class AndOperator : BinaryOperator +{ + /// + /// Singleton instance of the class. + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs new file mode 100644 index 00000000..3dbd70a4 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs @@ -0,0 +1,40 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators +{ + /// + /// Base class for all binary operators. + /// + internal abstract class BinaryOperator : IBinaryOperator + { + private protected 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); + } +} + diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs new file mode 100644 index 00000000..730408f3 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs @@ -0,0 +1,48 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are equality +/// +internal sealed class EqOperator : BinaryOperator +{ + /// + /// Singleton instance of the + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs new file mode 100644 index 00000000..86071d41 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs @@ -0,0 +1,73 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are comparison +/// +internal sealed class GtOperator : BinaryOperator +{ + /// + /// Singleton instance of the class + /// + internal static GtOperator Instance { get; } = new(); + + private GtOperator() + : base(Operator.Gt) + { + } + + /// + public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result) + { + switch (lhs.Type) + { + case JmesPathType.Number when 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; + } + + break; + } + 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; + break; + } + + return true; + } + + public override string ToString() + { + return "GtOperator"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs new file mode 100644 index 00000000..e17e2f7e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs @@ -0,0 +1,73 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are comparison +/// +internal sealed class GteOperator : BinaryOperator +{ + /// + /// Singleton instance of the + /// + internal static GteOperator Instance { get; } = new(); + + private GteOperator() + : base(Operator.Gte) + { + } + + /// + public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result) + { + switch (lhs.Type) + { + case JmesPathType.Number when 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; + } + + break; + } + 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; + break; + } + + return true; + } + + public override string ToString() + { + return "GteOperator"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs new file mode 100644 index 00000000..f9a08433 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs @@ -0,0 +1,39 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Interface for all binary operators. +/// +internal interface IBinaryOperator +{ + /// + /// The precedence level of the operator. + /// + int PrecedenceLevel {get;} + + /// + /// Whether the operator is right-associative. + /// + bool IsRightAssociative {get;} + + /// + /// Evaluates the expression. + /// + bool TryEvaluate(IValue lhs, IValue rhs, out IValue result); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs new file mode 100644 index 00000000..ab6da0d4 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs @@ -0,0 +1,42 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Interface for unary operators. +/// +internal interface IUnaryOperator +{ + /// + /// The precedence level of the operator. + /// + int PrecedenceLevel {get;} + + /// + /// Whether the operator is right-associative or not. + /// + bool IsRightAssociative {get;} + + /// + /// Evaluates the expression. + /// + /// + /// + /// + bool TryEvaluate(IValue elem, out IValue result); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs new file mode 100644 index 00000000..64d5f465 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs @@ -0,0 +1,73 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are comparison +/// +internal sealed class LtOperator : BinaryOperator +{ + /// + /// Singleton instance of the + /// + internal static LtOperator Instance { get; } = new(); + + private LtOperator() + : base(Operator.Lt) + { + } + + /// + public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result) + { + switch (lhs.Type) + { + case JmesPathType.Number when 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; + } + + break; + } + 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; + break; + } + + return true; + } + + public override string ToString() + { + return "LtOperator"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs new file mode 100644 index 00000000..72ba49ce --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs @@ -0,0 +1,74 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are comparison +/// +internal sealed class LteOperator : BinaryOperator +{ + /// + /// Singleton instance of the class + /// + internal static LteOperator Instance { get; } = new(); + + private LteOperator() + : base(Operator.Lte) + { + } + + /// + public override bool TryEvaluate(IValue lhs, IValue rhs, out IValue result) + { + switch (lhs.Type) + { + case JmesPathType.Number when 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; + } + + break; + } + 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; + break; + } + + return true; + } + + + public override string ToString() + { + return "LteOperator"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs new file mode 100644 index 00000000..7a880047 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs @@ -0,0 +1,53 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are inequality +/// +internal sealed class NeOperator : BinaryOperator +{ + /// + /// Singleton instance of the class + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs new file mode 100644 index 00000000..095c653b --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs @@ -0,0 +1,46 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Represents the not operator. +/// +internal sealed class NotOperator : UnaryOperator +{ + /// + /// The singleton instance of the class. + /// + internal static NotOperator Instance { get; } = new(); + + private NotOperator() + : base(Operator.Not) + {} + + /// + public override bool TryEvaluate(IValue elem, out IValue result) + { + result = Expression.IsFalse(elem) ? JsonConstants.True : JsonConstants.False; + return true; + } + + public override string ToString() + { + return "Not"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs new file mode 100644 index 00000000..0ead0bba --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs @@ -0,0 +1,52 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +/// +/// Base class for all binary operators that are left associative. +/// +internal sealed class OrOperator : BinaryOperator +{ + /// + /// Singleton instance of the class. + /// + 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"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs new file mode 100644 index 00000000..c6d1251c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs @@ -0,0 +1,32 @@ +using System.Text.RegularExpressions; +using AWS.Lambda.Powertools.JMESPath.Expressions; +using AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators; + +internal sealed class RegexOperator : UnaryOperator +{ + private readonly Regex _regex; + + internal RegexOperator(Regex regex) + : base(Operator.Not) + { + _regex = regex; + } + + public override bool TryEvaluate(IValue elem, out IValue result) + { + if (elem.Type != JmesPathType.String) + { + result = JsonConstants.Null; + return false; // type error + } + result = _regex.IsMatch(elem.GetString()) ? JsonConstants.True : JsonConstants.False; + return true; + } + + public override string ToString() + { + return "Regex"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs new file mode 100644 index 00000000..daf88f17 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs @@ -0,0 +1,41 @@ +/* + * 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 AWS.Lambda.Powertools.JMESPath.Values; + +namespace AWS.Lambda.Powertools.JMESPath.Operators +{ + /// + /// Base class for unary operators. + /// + internal abstract class UnaryOperator : IUnaryOperator + { + private protected 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); + } +} + diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs index 6d5dca86..938f2c01 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs @@ -15,11 +15,23 @@ namespace AWS.Lambda.Powertools.JMESPath { + /// + /// A slice of a list or string. + /// internal readonly struct Slice { + /// + /// The start of the slice. + /// private readonly int? _start; + /// + /// The stop of the slice. + /// private readonly int? _stop; + /// + /// The step of the slice. + /// public int Step {get;} public Slice(int? start, int? stop, int step) @@ -29,6 +41,11 @@ public Slice(int? start, int? stop, int step) Step = step; } + /// + /// Gets the start of the slice. + /// + /// + /// public int GetStart(int size) { if (!_start.HasValue) return Step >= 0 ? 0 : size; @@ -36,6 +53,11 @@ public int GetStart(int size) return len <= size ? len : size; } + /// + /// Gets the stop of the slice. + /// + /// + /// public int GetStop(int size) { if (!_stop.HasValue) 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 index 8a27fb20..d333c69e 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs @@ -15,7 +15,9 @@ using System; using System.Diagnostics; +using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Functions; +using AWS.Lambda.Powertools.JMESPath.Operators; using AWS.Lambda.Powertools.JMESPath.Values; namespace AWS.Lambda.Powertools.JMESPath @@ -47,8 +49,14 @@ internal enum TokenType EndOfExpression } + /// + /// Represents a token in the JMESPath expression. + /// internal readonly struct Token : IEquatable { + /// + /// The expression associated with this token. + /// private readonly object? _expr; internal Token(TokenType type) @@ -95,6 +103,9 @@ internal Token(IValue expr) internal TokenType Type{get;} + /// + /// Return if it is an Operator + /// internal bool IsOperator { get @@ -111,6 +122,9 @@ internal bool IsOperator } } + /// + /// True if the expression is a projection, false otherwise. + /// internal bool IsProjection { get @@ -125,6 +139,9 @@ internal bool IsProjection } } + /// + /// True if the expression is right-associative, false otherwise. + /// internal bool IsRightAssociative { get @@ -143,6 +160,9 @@ internal bool IsRightAssociative } } + /// + /// The precedence level of the operator. + /// internal int PrecedenceLevel { get @@ -161,36 +181,64 @@ internal int PrecedenceLevel } } + /// + /// Returns the token expression key if Type is Key + /// + /// + /// internal string GetKey() { Debug.Assert(Type == TokenType.Key); return _expr as string ?? throw new InvalidOperationException("Key cannot be null"); } + /// + /// Returns the token expression key if Type is UnaryOperator + /// + /// + /// internal IUnaryOperator GetUnaryOperator() { Debug.Assert(Type == TokenType.UnaryOperator); return _expr as IUnaryOperator ?? throw new InvalidOperationException("Unary operator cannot be null"); } + /// + /// Returns the token expression key if Type is BinaryOperator + /// + /// + /// internal IBinaryOperator GetBinaryOperator() { Debug.Assert(Type == TokenType.BinaryOperator); return _expr as IBinaryOperator ?? throw new InvalidOperationException("Binary operator cannot be null"); } + /// + /// Returns the token expression key if Type is Literal + /// + /// + /// internal IValue GetValue() { Debug.Assert(Type == TokenType.Literal); return _expr as IValue ?? throw new InvalidOperationException("Value cannot be null"); } + /// + /// Returns the token expression key if Type is Function + /// + /// + /// internal IFunction GetFunction() { Debug.Assert(Type == TokenType.Function); return _expr as IFunction ?? throw new InvalidOperationException("Function cannot be null"); } + /// + /// Returns the token expression key if Type is Expression + /// internal IExpression GetExpression() { Debug.Assert(Type == TokenType.Expression); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs deleted file mode 100644 index b769a1e1..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/UnaryOperator.cs +++ /dev/null @@ -1,90 +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.Text.RegularExpressions; -using AWS.Lambda.Powertools.JMESPath.Values; - -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 - { - private protected 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(); - - private NotOperator() - : base(Operator.Not) - {} - - public override bool TryEvaluate(IValue elem, out IValue result) - { - result = Expression.IsFalse(elem) ? JsonConstants.True : JsonConstants.False; - return true; - } - - public override string ToString() - { - return "Not"; - } - } - - internal sealed class RegexOperator : UnaryOperator - { - private readonly Regex _regex; - - internal RegexOperator(Regex regex) - : base(Operator.Not) - { - _regex = regex; - } - - public override bool TryEvaluate(IValue elem, out IValue result) - { - if (elem.Type != JmesPathType.String) - { - result = JsonConstants.Null; - return false; // type error - } - result = _regex.IsMatch(elem.GetString()) ? JsonConstants.True : JsonConstants.False; - return true; - } - - public override string ToString() - { - return "Regex"; - } - } -} - diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs index 43008b80..504d86e7 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs @@ -16,14 +16,27 @@ using System; using System.Collections.Generic; using System.Text; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a JMESPath array value. +/// internal readonly struct ArrayValue : IValue { + /// + /// An enumerator for a JMESPath array value. + /// private sealed class ArrayEnumerator : IArrayValueEnumerator { + /// + /// The list of values in the array. + /// private readonly IList _value; + /// + /// The enumerator for the list of values in the array. + /// private readonly System.Collections.IEnumerator _enumerator; public ArrayEnumerator(IList value) @@ -32,41 +45,78 @@ public ArrayEnumerator(IList value) _enumerator = value.GetEnumerator(); } + /// + /// Gets the current value in the array. + /// public bool MoveNext() { return _enumerator.MoveNext(); } + /// + /// Resets the enumerator to the beginning of the array. + /// public void Reset() { _enumerator.Reset(); } void IDisposable.Dispose() {} + /// + /// Gets the current value in the array. + /// + /// public IValue Current => _enumerator.Current as IValue ?? throw new InvalidOperationException("Current cannot be null"); + /// + /// Gets the current value in the array. + /// object System.Collections.IEnumerator.Current => Current; + /// + /// Gets an enumerator for the array. + /// + /// public IEnumerator GetEnumerator() { return _value.GetEnumerator(); } + /// + /// Gets an enumerator for the array. + /// + /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } + /// + /// The list of values in the array. + /// private readonly IList _value; + /// + /// Creates a new array value. + /// internal ArrayValue(IList value) { _value = value; } + /// + /// Gets the type of the value. + /// public JmesPathType Type => JmesPathType.Array; + /// + /// Gets the value at the specified index. + /// public IValue this[int index] => _value[index]; + /// + /// Gets the length of the array. + /// + /// public int GetArrayLength() { return _value.Count; } public string GetString() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs index 3cdf685d..7612befe 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs @@ -15,11 +15,18 @@ using System; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a JMESPath number value. +/// internal readonly struct DecimalValue : IValue { + /// + /// The value of the JMESPath number. + /// private readonly decimal _value; internal DecimalValue(decimal value) @@ -27,47 +34,63 @@ internal DecimalValue(decimal value) _value = value; } + /// + /// The type of the JMESPath value. + /// public JmesPathType Type => JmesPathType.Number; + /// + /// Gets the value at the specified index. + /// + /// + /// 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs index 56649d55..15966a58 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs @@ -15,11 +15,18 @@ using System; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a double value. +/// internal readonly struct DoubleValue : IValue { + /// + /// The value of this . + /// private readonly double _value; internal DoubleValue(double value) @@ -27,20 +34,29 @@ internal DoubleValue(double value) _value = value; } + /// public JmesPathType Type => JmesPathType.Number; + /// + /// Gets the value at the specified index. + /// + /// + /// 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)) && @@ -54,27 +70,32 @@ public bool TryGetDecimal(out 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs index b9a1e425..141c59b3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs @@ -14,11 +14,18 @@ */ using System; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a JMESPath expression. +/// internal readonly struct ExpressionValue : IValue { + /// + /// The expression to evaluate. + /// private readonly IExpression _expr; internal ExpressionValue(IExpression expr) @@ -26,45 +33,55 @@ 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; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs index 6b06928c..cc1f2409 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs @@ -14,50 +14,64 @@ */ using System; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a JMESPath false value. +/// 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs index a96a311c..02d16d1c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs @@ -17,6 +17,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents an enumerator for an array value. +/// internal interface IArrayValueEnumerator : IEnumerator, IEnumerable { } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs index 05f6b075..61d88868 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs @@ -17,6 +17,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Interface for object value enumerator. +/// internal interface IObjectValueEnumerator : IEnumerator, IEnumerable { } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs index a0295f62..052e184b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs @@ -13,18 +13,72 @@ * permissions and limitations under the License. */ +using AWS.Lambda.Powertools.JMESPath.Expressions; + namespace AWS.Lambda.Powertools.JMESPath.Values; internal interface IValue { + /// + /// The type of the JMESPath value + /// JmesPathType Type { get; } + + /// + /// The value of the JMESPath value + /// + /// IValue this[int index] { get; } + + /// + /// The length of the array + /// + /// int GetArrayLength(); + + /// + /// Get the value as a string + /// + /// string GetString(); + + /// + /// Try to get the value as a decimal + /// + /// + /// bool TryGetDecimal(out decimal value); + + /// + /// Try to get the value as a double + /// + /// + /// bool TryGetDouble(out double value); + + /// + /// Try to get the property value + /// + /// + /// + /// bool TryGetProperty(string propertyName, out IValue property); + + /// + /// Enumerate the array values + /// + /// IArrayValueEnumerator EnumerateArray(); + + + /// + /// Enumerate the object values + /// IObjectValueEnumerator EnumerateObject(); + + + /// + /// Get the expression for this value + /// IExpression GetExpression(); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs index 20f154f3..0af98387 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs @@ -15,6 +15,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents the type of a JMESPath value. +/// internal enum JmesPathType { Null, diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs index 61028276..b28382b4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs @@ -17,13 +17,23 @@ using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Nodes; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a value. +/// internal readonly struct JsonElementValue : IValue { + /// + /// The underlying value. + /// private class ArrayEnumerator : IArrayValueEnumerator { + /// + /// The underlying value. + /// private JsonElement.ArrayEnumerator _enumerator; public ArrayEnumerator(JsonElement.ArrayEnumerator enumerator) @@ -56,8 +66,14 @@ protected virtual void Dispose(bool disposing) } } + /// + /// The current in the . + /// public IValue Current => new JsonElementValue(_enumerator.Current); + /// + /// The current in the . + /// object System.Collections.IEnumerator.Current => Current; public IEnumerator GetEnumerator() @@ -73,6 +89,9 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() private class ObjectEnumerator : IObjectValueEnumerator { + /// + /// The underlying value. + /// private JsonElement.ObjectEnumerator _enumerator; public ObjectEnumerator(JsonElement.ObjectEnumerator enumerator) @@ -105,9 +124,15 @@ protected virtual void Dispose(bool disposing) } } + /// + /// The current in the . + /// public NameValuePair Current => new(_enumerator.Current.Name, new JsonElementValue(_enumerator.Current.Value)); + /// + /// The current in the . + /// object System.Collections.IEnumerator.Current => Current; public IEnumerator GetEnumerator() @@ -152,6 +177,7 @@ public JmesPathType Type } } + /// public IValue this[int index] => new JsonElementValue(_element[index]); public int GetArrayLength() diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs index 2ffe2717..0d7712ce 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs @@ -15,6 +15,9 @@ namespace AWS.Lambda.Powertools.JMESPath.Values { + /// + /// Represents a name-value pair. + /// internal readonly struct NameValuePair { public string Name { get; } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs index fc509571..88d2bcab 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs @@ -14,50 +14,64 @@ */ using System; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a null value. +/// 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs index c1ff0c9e..809e53d8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs @@ -17,14 +17,28 @@ using System.Collections.Generic; using System.Text; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents an object value. +/// internal readonly struct ObjectValue : IValue { + /// + /// An that can be used to iterate over the + /// private sealed class ObjectEnumerator : IObjectValueEnumerator { + /// + /// The underlying that is being enumerated. + /// private readonly IDictionary _value; + + /// + /// The underlying that is being enumerated. + /// private readonly System.Collections.IEnumerator _enumerator; public ObjectEnumerator(IDictionary value) @@ -47,6 +61,7 @@ void IDisposable.Dispose() { } + /// public NameValuePair Current { get @@ -56,6 +71,7 @@ public NameValuePair Current } } + /// object System.Collections.IEnumerator.Current => Current; public IEnumerator GetEnumerator() @@ -76,45 +92,55 @@ 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs index 1146cc21..135e0715 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs @@ -15,11 +15,18 @@ using System; using System.Text.Json; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// Represents a string value. +/// internal readonly struct StringValue : IValue { + /// + /// The string value. + /// private readonly string _value; internal StringValue(string value) @@ -27,45 +34,55 @@ 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs index 60309692..14d46b45 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs @@ -14,50 +14,64 @@ */ using System; +using AWS.Lambda.Powertools.JMESPath.Expressions; namespace AWS.Lambda.Powertools.JMESPath.Values; +/// +/// True value +/// 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"); diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs index bdc83197..b0ce0a2c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs @@ -19,14 +19,24 @@ namespace AWS.Lambda.Powertools.JMESPath.Values { + /// + /// Compares two instances for equality. + /// internal sealed class ValueEqualityComparer : IEqualityComparer { + /// + /// Singleton instance of . + /// internal static ValueEqualityComparer Instance { get; } = new(); + /// + /// Max Hash depth + /// private readonly int _maxHashDepth = 100; private ValueEqualityComparer() {} + /// public bool Equals(IValue lhs, IValue rhs) { if (lhs != null && rhs != null && lhs.Type != rhs.Type) @@ -144,6 +154,4 @@ private int ComputeHashCode(IValue element, int depth) return hashCode; } } - - } From 6af56d99d8be22156aae6f302e49d1c625c29a70 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 2 May 2024 16:23:37 +0100 Subject: [PATCH 61/82] Update Attribution - update LICENSE-THIRD-PARTY --- LICENSE-THIRD-PARTY | 201 +++++++++++++++++- .../Expressions/BaseExpression.cs | 2 +- .../Expressions/CurrentNode.cs | 2 +- .../Expressions/Expression.cs | 2 +- .../Expressions/FilterExpression.cs | 2 +- .../Expressions/FlattenProjection.cs | 2 +- .../Expressions/FunctionExpression.cs | 2 +- .../Expressions/IExpression.cs | 2 +- .../Expressions/IdentifierSelector.cs | 2 +- .../Expressions/IndexSelector.cs | 2 +- .../Expressions/JsonConstants.cs | 2 +- .../Expressions/KeyExpressionPair.cs | 2 +- .../Expressions/ListProjection.cs | 2 +- .../Expressions/MultiSelectHash.cs | 2 +- .../Expressions/MultiSelectList.cs | 2 +- .../Expressions/ObjectProjection.cs | 2 +- .../Expressions/Projection.cs | 2 +- .../Expressions/SliceProjection.cs | 2 +- .../Functions/AbsFunction.cs | 2 +- .../Functions/AvgFunction.cs | 2 +- .../Functions/Base64Function.cs | 6 +- .../Functions/Base64GzipFunction.cs | 6 +- .../Functions/BaseFunction.cs | 2 +- .../Functions/BuiltInFunctions.cs | 6 +- .../Functions/CeilFunction.cs | 2 +- .../Functions/ContainsFunction.cs | 2 +- .../Functions/EndsWithFunction.cs | 3 +- .../Functions/EvaluateMinMax.cs | 2 +- .../Functions/EvaluateMinMaxBy.cs | 2 +- .../Functions/EvaluateStartEndWith.cs | 2 +- .../Functions/FloorFunction.cs | 2 +- .../Functions/IFunction.cs | 2 +- .../Functions/JoinFunction.cs | 2 +- .../Functions/JsonFunction.cs | 6 +- .../Functions/KeysFunction.cs | 2 +- .../Functions/LengthFunction.cs | 2 +- .../Functions/MapFunction.cs | 2 +- .../Functions/MaxByFunction.cs | 3 +- .../Functions/MaxFunction.cs | 3 +- .../Functions/MergeFunction.cs | 2 +- .../Functions/MinByFunction.cs | 3 +- .../Functions/MinFunction.cs | 3 +- .../Functions/NotNullFunction.cs | 2 +- .../Functions/ReverseFunction.cs | 2 +- .../Functions/SortByComparer.cs | 2 +- .../Functions/SortByFunction.cs | 2 +- .../Functions/SortFunction.cs | 2 +- .../Functions/StartsWithFunction.cs | 3 +- .../Functions/SumFunction.cs | 2 +- .../Functions/ToArrayFunction.cs | 2 +- .../Functions/ToNumberFunction.cs | 2 +- .../Functions/ToStringFunction.cs | 2 +- .../Functions/TypeFunction.cs | 2 +- .../Functions/ValuesFunction.cs | 2 +- .../InternalsVisibleTo.cs | 2 +- .../JmesPathParseException.cs | 2 +- .../JmesPathParser.cs | 5 +- .../JmesPathState.cs | 2 +- .../JsonTransformer.cs | 7 +- .../Operator.cs | 2 +- .../Operators/AndOperator.cs | 2 +- .../Operators/BinaryOperator.cs | 2 +- .../Operators/EqOperator.cs | 2 +- .../Operators/GtOperator.cs | 2 +- .../Operators/GteOperator.cs | 2 +- .../Operators/IBinaryOperator.cs | 2 +- .../Operators/IUnaryOperator.cs | 2 +- .../Operators/LtOperator.cs | 2 +- .../Operators/LteOperator.cs | 2 +- .../Operators/NeOperator.cs | 2 +- .../Operators/NotOperator.cs | 2 +- .../Operators/OrOperator.cs | 2 +- .../Operators/RegexOperator.cs | 15 ++ .../Operators/UnaryOperator.cs | 2 +- .../AWS.Lambda.Powertools.JMESPath/Slice.cs | 5 +- .../AWS.Lambda.Powertools.JMESPath/Token.cs | 2 +- .../Utilities/JsonElementEqualityComparer.cs | 2 +- .../Values/ArrayValue.cs | 2 +- .../Values/DecimalValue.cs | 2 +- .../Values/DoubleValue.cs | 2 +- .../Values/ExpressionValue.cs | 2 +- .../Values/FalseValue.cs | 2 +- .../Values/IArrayValueEnumerator.cs | 2 +- .../Values/IObjectValueEnumerator.cs | 2 +- .../Values/IValue.cs | 2 +- .../Values/JmesPathType.cs | 2 +- .../Values/JsonElementValue.cs | 2 +- .../Values/NameValuePair.cs | 2 +- .../Values/NullValue.cs | 2 +- .../Values/ObjectValue.cs | 2 +- .../Values/StringValue.cs | 2 +- .../Values/TrueValue.cs | 2 +- .../Values/ValueComparer.cs | 6 +- .../Values/ValueEqualityComparer.cs | 2 +- 94 files changed, 333 insertions(+), 104 deletions(-) diff --git a/LICENSE-THIRD-PARTY b/LICENSE-THIRD-PARTY index ece76399..23f6acaf 100644 --- a/LICENSE-THIRD-PARTY +++ b/LICENSE-THIRD-PARTY @@ -202,4 +202,203 @@ Apache License distributed under the License 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. \ No newline at end of file + limitations under the License. + + +-------------------------------------------------------------------------------- + +danielaparker/JsonCons.Net +v1.1.0 + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2013-2018 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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. + + \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs index 925f8c97..a88705dc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs index 1794d8b4..f741224a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs index 8ae2d52a..701d1522 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs index 38d0e4ed..2f4f77c0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs index d3946321..058785b3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs index e5630582..1fb69ffe 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs index 1e083366..c02dd13b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs index 1f64ee00..958346b6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs index 0a21b4f6..360e9452 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs index 71f6a394..3c12a488 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs index 32d21edc..a186f95a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs index a36e6292..3311a265 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs index 21404a1c..c0941002 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs index 0b7cf183..5d99c4f4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs index a6ec0035..95675502 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs index 28d1ea4e..2f75b14b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs index 5c895033..18e1f386 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs index ef58d85b..0b72576a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs index 70c5b3ee..8740255a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs index 9eab21ff..589600a4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64Function.cs @@ -1,12 +1,12 @@ /* * 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 diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs index e3f27b96..18617695 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/Base64GzipFunction.cs @@ -1,12 +1,12 @@ /* * 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 diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs index 987c88ce..d2890cfb 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs index 5555e0b5..c0035054 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BuiltInFunctions.cs @@ -1,12 +1,12 @@ /* * 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 diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs index cfd55eba..e4ce63fc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs index 8bd8b80c..c9cc1cfd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs index 92ccfdfd..caf3c166 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -35,6 +35,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + // 2024-04-19: Powertools addition. return EvaluateStartEndWith.TryEvaluate(args, out element, s0 => s0.EndsWith); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs index d1c9b0f3..1a2bada5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs index 064521f9..fe3b3249 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs index c64b4545..8adad2e0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs index 763a7100..cced2d05 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs index aa7198fc..49d9b886 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs index 52df606b..ae9199cc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs index 80e4daf9..a1381cc6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JsonFunction.cs @@ -1,12 +1,12 @@ /* * 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 diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs index 75c773ac..edb9dcfd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs index 7be3c5f9..e54493e7 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs index 5ce795a9..3e0095f5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs index 4804bce6..8fd68e7e 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -35,6 +35,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + // 2024-04-19: Powertools addition. return EvaluateMinMaxBy.TryEvaluate(resources, args, GtOperator.Instance, out element); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs index e37e85ef..24820895 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -36,6 +36,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + // 2024-04-19: Powertools addition. return EvaluateMinMax.TryEvaluate(args, GtOperator.Instance, out element); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs index 5af18d99..f91990b8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs index 08fffc2a..71f1fb3c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -35,6 +35,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + // 2024-04-19: Powertools addition. return EvaluateMinMaxBy.TryEvaluate(resources, args, LtOperator.Instance, out element); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs index c8ae1c20..2d377327 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -36,6 +36,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + // 2024-04-19: Powertools addition. return EvaluateMinMax.TryEvaluate(args, LtOperator.Instance, out element); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs index b362da73..0d835e35 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs index f66f1e90..8570e0c9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs index eea78e56..81508620 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs index a4ae4fcf..f42293de 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs index 2bb5fffd..3c5e461a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs index 0b7d7c74..7ed8e1e2 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -35,6 +35,7 @@ public override bool TryEvaluate(DynamicResources resources, IList args, { Debug.Assert(Arity.HasValue && args.Count == Arity!.Value); + // 2024-04-19: Powertools addition. return EvaluateStartEndWith.TryEvaluate(args, out element, s0 => s0.StartsWith); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs index 15d59853..a3d74105 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs index fa7d7f6a..54ef92c5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs index fb68800d..70fe7736 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs index 41c18ddc..5df71056 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs index 24529604..375adaa4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs index 60e472c4..ce518f62 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs index 4a4695be..a1510e77 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs index b1d5b8d4..7ae21860 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs index a73a5a02..c1657f0f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -71,9 +71,11 @@ internal JsonTransformer Parse() PushToken(new Token(TokenType.CurrentNode)); _stateStack.Push(JmesPathState.Start); + // 2024-04-19: Powertools addition. var syntaxErrorMsg = "Syntax error"; while (_index < _span.Length) { + // 2024-04-19: Powertools addition. var expectedRightBracket = "Expected right bracket"; switch (_stateStack.Peek()) { @@ -1655,6 +1657,7 @@ private void PushToken(Token token) /// private uint AppendToCodepoint(uint cp, uint c) { + // 2024-04-19: Powertools addition. cp *= 16; switch (c) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs index a8bb4fb3..b01b06cc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs index 79409d3d..229b9d10 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -20,6 +20,7 @@ namespace AWS.Lambda.Powertools.JMESPath { + // 2024-04-19: Powertools addition. internal sealed class DynamicResources; /// /// Provides functionality for applying a JMESPath expression to transform a JSON document into @@ -125,9 +126,9 @@ internal JsonTransformer(Expression expr) /// 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) { + // 2024-04-19: Powertools addition. var resources = new DynamicResources(); _expr.TryEvaluate(resources, new JsonElementValue(doc), out var temp); return JsonDocument.Parse(temp.ToString() ?? string.Empty); @@ -150,9 +151,9 @@ public JsonDocument Transform(JsonElement doc) /// /// The is . /// - public static JsonDocument Transform(JsonElement doc, string jmesPath) { + // 2024-04-19: Powertools addition. 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 index 4f245603..b56ec6d3 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs index 7535e40b..290a5016 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs index 3dbd70a4..c5902520 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs index 730408f3..508d2309 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs index 86071d41..4ae65e2a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs index e17e2f7e..c53a173a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs index f9a08433..779179be 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs index ab6da0d4..dde47419 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs index 64d5f465..ee5feedf 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs index 72ba49ce..2e4c7b5e 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs index 7a880047..137fd939 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs index 095c653b..c15d2dc0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs index 0ead0bba..06fa4596 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs index c6d1251c..805c32cc 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs @@ -1,3 +1,18 @@ +/* + * Copyright JsonCons.Net authors. 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; using AWS.Lambda.Powertools.JMESPath.Expressions; using AWS.Lambda.Powertools.JMESPath.Values; diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs index daf88f17..ecd77016 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs index 938f2c01..b76afb33 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -48,6 +48,7 @@ public Slice(int? start, int? stop, int step) /// public int GetStart(int size) { + // 2024-04-19: Powertools addition. if (!_start.HasValue) return Step >= 0 ? 0 : size; var len = _start.Value >= 0 ? _start.Value : size + _start.Value; return len <= size ? len : size; @@ -60,10 +61,10 @@ public int GetStart(int size) /// public int GetStop(int size) { + // 2024-04-19: Powertools addition. 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 d333c69e..a37df5cd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs index 6141fb58..418d13c0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs index 504d86e7..dc6b2460 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs index 7612befe..64d67f34 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs index 15966a58..fd147fe6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs index 141c59b3..bb8c7e97 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs index cc1f2409..7f9d7171 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs index 02d16d1c..7a6d54dd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs index 61d88868..018f197e 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs index 052e184b..fafa6b95 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs index 0af98387..6e0a512b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs index b28382b4..4ef0b228 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs index 0d7712ce..7086a759 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs index 88d2bcab..e1cdade0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs index 809e53d8..aabbdd4d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs index 135e0715..734c0cfb 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs index 14d46b45..56b2098d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs index 20738000..b7cf20b9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -110,11 +110,13 @@ public int Compare(IValue lhs, IValue rhs) case JmesPathType.Array: { + // 2024-04-19: Powertools addition. return ArrayComparer(lhs, rhs); } case JmesPathType.Object: { + // 2024-04-19: Powertools addition. return ObjectComparer(lhs, rhs); } @@ -123,6 +125,7 @@ public int Compare(IValue lhs, IValue rhs) } } + // 2024-04-19: Powertools addition. private int ArrayComparer(IValue lhs, IValue rhs) { var enumerator1 = lhs.EnumerateArray(); @@ -142,6 +145,7 @@ private int ArrayComparer(IValue lhs, IValue rhs) return result1 ? 1 : result2 ? -1 : 0; } + // 2024-04-19: Powertools addition. private int ObjectComparer(IValue lhs, IValue rhs) { // OrderBy performs a stable sort (Note that supports duplicate property names) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs index b0ce0a2c..226accad 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. From 930c32605defd16b00d53d11c1ffd7cdd0e312b4 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 2 May 2024 16:28:25 +0100 Subject: [PATCH 62/82] rename THIRD-PARTY-LICENSES --- LICENSE-THIRD-PARTY => THIRD-PARTY-LICENSES | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE-THIRD-PARTY => THIRD-PARTY-LICENSES (100%) diff --git a/LICENSE-THIRD-PARTY b/THIRD-PARTY-LICENSES similarity index 100% rename from LICENSE-THIRD-PARTY rename to THIRD-PARTY-LICENSES From 11827dca2cbcba46b84917d2202af6be96d22be5 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 6 May 2024 15:15:30 +0100 Subject: [PATCH 63/82] remove unused operator --- .../Operators/RegexOperator.cs | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs deleted file mode 100644 index 805c32cc..00000000 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/RegexOperator.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright JsonCons.Net authors. 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; -using AWS.Lambda.Powertools.JMESPath.Expressions; -using AWS.Lambda.Powertools.JMESPath.Values; - -namespace AWS.Lambda.Powertools.JMESPath.Operators; - -internal sealed class RegexOperator : UnaryOperator -{ - private readonly Regex _regex; - - internal RegexOperator(Regex regex) - : base(Operator.Not) - { - _regex = regex; - } - - public override bool TryEvaluate(IValue elem, out IValue result) - { - if (elem.Type != JmesPathType.String) - { - result = JsonConstants.Null; - return false; // type error - } - result = _regex.IsMatch(elem.GetString()) ? JsonConstants.True : JsonConstants.False; - return true; - } - - public override string ToString() - { - return "Regex"; - } -} \ No newline at end of file From 4a15617227edd76b42dc44b03824310b12544dfc Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 6 May 2024 15:17:46 +0100 Subject: [PATCH 64/82] move slice to expressions --- .../AWS.Lambda.Powertools.JMESPath/{ => Expressions}/Slice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename libraries/src/AWS.Lambda.Powertools.JMESPath/{ => Expressions}/Slice.cs (97%) diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs similarity index 97% rename from libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs rename to libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs index b76afb33..ea2902b0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Slice.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -namespace AWS.Lambda.Powertools.JMESPath +namespace AWS.Lambda.Powertools.JMESPath.Expressions { /// /// A slice of a list or string. From b1f53c5c7a3f61e4a47d06e5925c85b71d8123aa Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 7 May 2024 14:16:29 +0200 Subject: [PATCH 65/82] docs: add link to Powertools for AWS Lambda workshop --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 4862dbf5..bcb5c176 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,6 +11,7 @@ nav: - Roadmap: roadmap.md - API Reference: api/" target="_blank - We Made This (Community): we_made_this.md + - Workshop 🆕: https://s12d.com/powertools-for-aws-lambda-workshop" target="_blank - Core utilities: - core/logging.md - core/metrics.md From 8ab564f4da26c8df642c552119cf4efe7eee71ea Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 8 May 2024 11:22:15 +0100 Subject: [PATCH 66/82] Update build pipeline Update setup dotnet and codecov actions Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e831d366..ad907ae1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup .NET 6.0 & 8.0 - uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a + uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # 4.0.0 with: dotnet-version: | 6.0.405 @@ -29,11 +29,11 @@ jobs: - name: Test & Code Coverage run: dotnet test --collect:"XPlat Code Coverage" --results-directory ./codecov --verbosity normal - name: Codecov - uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # 3.1.0 + uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # 4.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests - fail_ci_if_error: true + fail_ci_if_error: false name: codecov-lambda-powertools-dotnet verbose: true directory: ./libraries/codecov From a8f4f4f2ed97e2a6abfd652dcbd876cee14c7f7f Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 8 May 2024 11:50:46 +0100 Subject: [PATCH 67/82] Update build.yml Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad907ae1..4d7f4d69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,6 @@ jobs: - name: Codecov uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # 4.3.1 with: - token: ${{ secrets.CODECOV_TOKEN }} flags: unittests fail_ci_if_error: false name: codecov-lambda-powertools-dotnet From bf85ba9920652e49e31c062456761b18040ec100 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 8 May 2024 12:01:36 +0100 Subject: [PATCH 68/82] add permissions Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d7f4d69..c156f052 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,9 @@ defaults: run: working-directory: ./libraries +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest From 5f2078df1925ccddef5b0f44ab2f26d34dd82455 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Wed, 8 May 2024 12:19:49 +0100 Subject: [PATCH 69/82] add token env Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c156f052..ae6c9fac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,8 @@ jobs: run: dotnet test --collect:"XPlat Code Coverage" --results-directory ./codecov --verbosity normal - name: Codecov uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # 4.3.1 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: flags: unittests fail_ci_if_error: false From 0560aa0fac96e84d4e7aaffe21d321b98189b1f7 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 10:04:53 +0100 Subject: [PATCH 70/82] add targetframework condition to error on specific warnings --- libraries/src/Directory.Build.props | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/src/Directory.Build.props b/libraries/src/Directory.Build.props index 5fa585bc..453b05ab 100644 --- a/libraries/src/Directory.Build.props +++ b/libraries/src/Directory.Build.props @@ -17,10 +17,16 @@ true true - true + true + + IL2026,IL2067,IL2075 + true + true + + From c7448709ffe3a88c96298a7c176316eabfef9e01 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 10:22:41 +0100 Subject: [PATCH 71/82] adressing pr comments --- .../Persistence/BasePersistenceStore.cs | 16 +++++++--------- .../Expressions/BaseExpression.cs | 2 +- .../Expressions/CurrentNode.cs | 2 +- .../Expressions/Expression.cs | 2 +- .../Expressions/FilterExpression.cs | 2 +- .../Expressions/FlattenProjection.cs | 2 +- .../Expressions/FunctionExpression.cs | 2 +- .../Expressions/IExpression.cs | 2 +- .../Expressions/IdentifierSelector.cs | 2 +- .../Expressions/IndexSelector.cs | 2 +- .../Expressions/JsonConstants.cs | 2 +- .../Expressions/KeyExpressionPair.cs | 2 +- .../Expressions/ListProjection.cs | 2 +- .../Expressions/MultiSelectHash.cs | 2 +- .../Expressions/MultiSelectList.cs | 2 +- .../Expressions/ObjectProjection.cs | 2 +- .../Expressions/Projection.cs | 2 +- .../Expressions/Slice.cs | 2 +- .../Expressions/SliceProjection.cs | 2 +- .../Functions/AbsFunction.cs | 2 +- .../Functions/AvgFunction.cs | 2 +- .../Functions/BaseFunction.cs | 2 +- .../Functions/CeilFunction.cs | 2 +- .../Functions/ContainsFunction.cs | 2 +- .../Functions/EndsWithFunction.cs | 2 +- .../Functions/EvaluateMinMax.cs | 2 +- .../Functions/EvaluateMinMaxBy.cs | 2 +- .../Functions/EvaluateStartEndWith.cs | 2 +- .../Functions/FloorFunction.cs | 2 +- .../Functions/IFunction.cs | 2 +- .../Functions/JoinFunction.cs | 2 +- .../Functions/KeysFunction.cs | 2 +- .../Functions/LengthFunction.cs | 2 +- .../Functions/MapFunction.cs | 2 +- .../Functions/MaxByFunction.cs | 2 +- .../Functions/MaxFunction.cs | 2 +- .../Functions/MergeFunction.cs | 2 +- .../Functions/MinByFunction.cs | 2 +- .../Functions/MinFunction.cs | 2 +- .../Functions/NotNullFunction.cs | 2 +- .../Functions/ReverseFunction.cs | 2 +- .../Functions/SortByComparer.cs | 2 +- .../Functions/SortByFunction.cs | 2 +- .../Functions/SortFunction.cs | 2 +- .../Functions/StartsWithFunction.cs | 2 +- .../Functions/SumFunction.cs | 2 +- .../Functions/ToArrayFunction.cs | 2 +- .../Functions/ToNumberFunction.cs | 2 +- .../Functions/ToStringFunction.cs | 2 +- .../Functions/TypeFunction.cs | 2 +- .../Functions/ValuesFunction.cs | 2 +- .../InternalsVisibleTo.cs | 2 +- .../JmesPathParseException.cs | 2 +- .../JmesPathParser.cs | 2 +- .../JmesPathState.cs | 2 +- .../JsonTransformer.cs | 8 +++----- .../AWS.Lambda.Powertools.JMESPath/Operator.cs | 2 +- .../Operators/AndOperator.cs | 2 +- .../Operators/BinaryOperator.cs | 2 +- .../Operators/EqOperator.cs | 2 +- .../Operators/GtOperator.cs | 2 +- .../Operators/GteOperator.cs | 2 +- .../Operators/IBinaryOperator.cs | 2 +- .../Operators/IUnaryOperator.cs | 2 +- .../Operators/LtOperator.cs | 2 +- .../Operators/LteOperator.cs | 2 +- .../Operators/NeOperator.cs | 2 +- .../Operators/NotOperator.cs | 2 +- .../Operators/OrOperator.cs | 2 +- .../Operators/UnaryOperator.cs | 2 +- .../src/AWS.Lambda.Powertools.JMESPath/Token.cs | 4 ++-- .../Utilities/JsonElementEqualityComparer.cs | 2 +- .../Values/ArrayValue.cs | 2 +- .../Values/DecimalValue.cs | 2 +- .../Values/DoubleValue.cs | 2 +- .../Values/ExpressionValue.cs | 2 +- .../Values/FalseValue.cs | 2 +- .../Values/IArrayValueEnumerator.cs | 2 +- .../Values/IObjectValueEnumerator.cs | 2 +- .../Values/IValue.cs | 2 +- .../Values/JmesPathType.cs | 2 +- .../Values/JsonElementValue.cs | 2 +- .../Values/NameValuePair.cs | 2 +- .../Values/NullValue.cs | 2 +- .../Values/ObjectValue.cs | 2 +- .../Values/StringValue.cs | 2 +- .../Values/TrueValue.cs | 2 +- .../Values/ValueComparer.cs | 2 +- .../Values/ValueEqualityComparer.cs | 2 +- 89 files changed, 98 insertions(+), 102 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs index 75191c5a..c71dc9d9 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs @@ -225,15 +225,13 @@ private DataRecord RetrieveFromCache(string idempotencyKey, DateTimeOffset now) { if (!_idempotencyOptions.UseLocalCache) return null; - - if (_cache.TryGet(idempotencyKey, out var record) && record!=null) + + if (!_cache.TryGet(idempotencyKey, out var record) || record == null) return null; + if (!record.IsExpired(now)) { - if (!record.IsExpired(now)) - { - return record; - } - DeleteFromCache(idempotencyKey); + return record; } + DeleteFromCache(idempotencyKey); return null; } @@ -262,7 +260,7 @@ private string GetHashedPayload(JsonDocument data) } var transformer = JsonTransformer.Parse(_idempotencyOptions.PayloadValidationJmesPath); - JsonDocument result = transformer.Transform(data.RootElement); + var result = transformer.Transform(data.RootElement); return GenerateHash(result.RootElement); } @@ -289,7 +287,7 @@ private string GetHashedIdempotencyKey(JsonDocument data) if (eventKeyJmesPath != null) { var transformer = JsonTransformer.Parse(eventKeyJmesPath); - JsonDocument result = transformer.Transform(node); + var result = transformer.Transform(node); node = result.RootElement; } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs index a88705dc..843d6f8c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/BaseExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs index f741224a..2566e00c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/CurrentNode.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs index 701d1522..7e0163dd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Expression.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs index 2f4f77c0..4389b588 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FilterExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs index 058785b3..4c0746d1 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FlattenProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs index 1fb69ffe..2627df88 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/FunctionExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs index c02dd13b..011a6f4a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IExpression.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs index 958346b6..4648c54d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IdentifierSelector.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs index 360e9452..d6233ce9 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/IndexSelector.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs index 3c12a488..1d8e7006 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/JsonConstants.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs index a186f95a..2cc65cdb 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/KeyExpressionPair.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs index 3311a265..a8f9bfc6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ListProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs index c0941002..454550c4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectHash.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs index 5d99c4f4..4b5a577c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/MultiSelectList.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs index 95675502..64704b3a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/ObjectProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs index 2f75b14b..d6a7e62c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Projection.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs index ea2902b0..c534c34c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/Slice.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs index 18e1f386..5250a2fd 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Expressions/SliceProjection.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs index 0b72576a..d1b9da51 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AbsFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs index 8740255a..3820b089 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/AvgFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs index d2890cfb..5168da76 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/BaseFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs index e4ce63fc..5d3cbb3c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/CeilFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs index c9cc1cfd..94f41326 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ContainsFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs index caf3c166..738a29f0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EndsWithFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs index 1a2bada5..a7fa04e5 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMax.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs index fe3b3249..dc04356a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateMinMaxBy.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs index 8adad2e0..c2a2d50c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/EvaluateStartEndWith.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs index cced2d05..22c59813 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/FloorFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs index 49d9b886..16e1b852 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/IFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs index ae9199cc..a30671db 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/JoinFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs index edb9dcfd..f3136ef6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/KeysFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs index e54493e7..fb6ef5e4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/LengthFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs index 3e0095f5..13c6ce67 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MapFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs index 8fd68e7e..7cd67189 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxByFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs index 24820895..a5325d97 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MaxFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs index f91990b8..29afd5d6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MergeFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs index 71f1fb3c..f98def98 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinByFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs index 2d377327..e686fb5a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/MinFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs index 0d835e35..bc8b86cf 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/NotNullFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs index 8570e0c9..70a33ff8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ReverseFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs index 81508620..a307ae06 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs index f42293de..5ca2e64a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortByFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs index 3c5e461a..5c489eac 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SortFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs index 7ed8e1e2..d2b15033 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/StartsWithFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs index a3d74105..f88327ba 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/SumFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs index 54ef92c5..5832a208 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToArrayFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs index 70fe7736..b5389cf4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToNumberFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs index 5df71056..bd179c3d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ToStringFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs index 375adaa4..fbf3f399 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/TypeFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs index ce518f62..263201f6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Functions/ValuesFunction.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs index a1510e77..89436184 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/InternalsVisibleTo.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs index 7ae21860..1cd7bfa4 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParseException.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs index c1657f0f..dd6e004f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathParser.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs index b01b06cc..eb595e93 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JmesPathState.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs index 229b9d10..46059aea 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/JsonTransformer.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -101,10 +101,8 @@ public sealed class JsonTransformer /// public static JsonTransformer Parse(string jmesPath) { - if (jmesPath == null) - { - throw new ArgumentNullException(nameof(jmesPath)); - } + ArgumentNullException.ThrowIfNull(jmesPath); + var compiler = new JmesPathParser(jmesPath); return compiler.Parse(); } diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs index b56ec6d3..1e24bbf0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs index 290a5016..901aece8 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/AndOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs index c5902520..f7aa3f07 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/BinaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs index 508d2309..d1fc3d4b 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/EqOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs index 4ae65e2a..07b8ff0a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GtOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs index c53a173a..7c25c6eb 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/GteOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs index 779179be..cff04a8e 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IBinaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs index dde47419..a44b1392 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/IUnaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs index ee5feedf..6405e593 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LtOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs index 2e4c7b5e..f0f3ba33 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/LteOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs index 137fd939..410f3b66 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NeOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs index c15d2dc0..429b1a5d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/NotOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs index 06fa4596..3db2145f 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/OrOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs index ecd77016..2a156989 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Operators/UnaryOperator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs index a37df5cd..46465f84 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Token.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. @@ -57,7 +57,7 @@ internal enum TokenType /// /// The expression associated with this token. /// - private readonly object? _expr; + private readonly object _expr; internal Token(TokenType type) { diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs index 418d13c0..d96c61f2 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Utilities/JsonElementEqualityComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs index dc6b2460..e07d8b8d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ArrayValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs index 64d67f34..b03ff5db 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DecimalValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs index fd147fe6..2962f28d 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/DoubleValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs index bb8c7e97..53dc3a7c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ExpressionValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs index 7f9d7171..5a06320c 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/FalseValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs index 7a6d54dd..778e3403 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IArrayValueEnumerator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs index 018f197e..407f04d0 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IObjectValueEnumerator.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs index fafa6b95..49950ed6 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/IValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs index 6e0a512b..86a71209 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JmesPathType.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs index 4ef0b228..b215d339 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/JsonElementValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs index 7086a759..e0666fe2 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NameValuePair.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs index e1cdade0..458bf892 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/NullValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs index aabbdd4d..86153435 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ObjectValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs index 734c0cfb..02fb2890 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/StringValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs index 56b2098d..457012ee 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/TrueValue.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs index b7cf20b9..0996d6c7 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs index 226accad..73363874 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Values/ValueEqualityComparer.cs @@ -1,5 +1,5 @@ /* - * Copyright JsonCons.Net authors. All Rights Reserved. + * Copyright JsonCons.Net authors. 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. From a455fa8659daa3ab38264eded6c166446ef85ca7 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 12:05:53 +0100 Subject: [PATCH 72/82] Update idempotency version for release 1.10.0 Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 07e4f191..ec27c4db 100644 --- a/version.json +++ b/version.json @@ -6,7 +6,7 @@ }, "Utilities": { "Parameters": "1.3.0", - "Idempotency": "1.1.1", + "Idempotency": "1.2.0", "BatchProcessing": "1.1.1" } } From af850da36a21f90fe897c63aa8fd2d559828ab8e Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 12:50:26 +0100 Subject: [PATCH 73/82] Update idempotency examples for release 1..10.0 Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Idempotency/src/HelloWorld/HelloWorld.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj index 4a3f8ed3..e06bc532 100644 --- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj @@ -8,7 +8,7 @@ - + From 0a6ab629dd3bee631a06d810f6800d56a442a01c Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 15:17:58 +0100 Subject: [PATCH 74/82] fix Idempotency jmespath dependency missing --- .../AWS.Lambda.Powertools.Idempotency.csproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 50a99381..6f49ab0e 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 @@ -6,6 +6,8 @@ Powertools for AWS Lambda (.NET) - Idempotency package. AWS.Lambda.Powertools.Idempotency AWS.Lambda.Powertools.Idempotency + 1.2.13 + $(TargetsForTfmSpecificBuildOutput);IncludeInOutput @@ -15,7 +17,12 @@ - + - + + + + + + From e51934019359436b70b977aba789fe34693c27d9 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 15:18:18 +0100 Subject: [PATCH 75/82] remove version --- .../AWS.Lambda.Powertools.Idempotency.csproj | 1 - 1 file changed, 1 deletion(-) 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 6f49ab0e..5aff80e2 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 @@ -6,7 +6,6 @@ Powertools for AWS Lambda (.NET) - Idempotency package. AWS.Lambda.Powertools.Idempotency AWS.Lambda.Powertools.Idempotency - 1.2.13 $(TargetsForTfmSpecificBuildOutput);IncludeInOutput From b7f5ee98643f90ec21ac902fb92ddf0309c35b90 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 15:29:54 +0100 Subject: [PATCH 76/82] update version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index ec27c4db..ff4acc58 100644 --- a/version.json +++ b/version.json @@ -6,7 +6,7 @@ }, "Utilities": { "Parameters": "1.3.0", - "Idempotency": "1.2.0", + "Idempotency": "1.2.1", "BatchProcessing": "1.1.1" } } From 16667a0a91aaac1cec51a94e77476581420dcdc7 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 17:24:14 +0100 Subject: [PATCH 77/82] add build target to copy files to idempotency --- .../AWS.Lambda.Powertools.Idempotency.csproj | 7 ------- libraries/src/Directory.Build.targets | 11 +++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) 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 5aff80e2..ea442565 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 @@ -6,7 +6,6 @@ Powertools for AWS Lambda (.NET) - Idempotency package. AWS.Lambda.Powertools.Idempotency AWS.Lambda.Powertools.Idempotency - $(TargetsForTfmSpecificBuildOutput);IncludeInOutput @@ -18,10 +17,4 @@ - - - - - - diff --git a/libraries/src/Directory.Build.targets b/libraries/src/Directory.Build.targets index 5844e458..3e00ba03 100644 --- a/libraries/src/Directory.Build.targets +++ b/libraries/src/Directory.Build.targets @@ -12,4 +12,15 @@ + + + + + + + JMESPath\%(RecursiveDir)%(Filename)%(Extension) + + + + \ No newline at end of file From e07c64d61abb9f9b0ceba063e6fe1b6832359a6d Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 9 May 2024 19:06:43 +0100 Subject: [PATCH 78/82] Update examples idempotency Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- examples/Idempotency/src/HelloWorld/HelloWorld.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj index e06bc532..2fb80281 100644 --- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj @@ -8,7 +8,7 @@ - + From 44ae98fbbaca3c753e90947c88dda75367e422ad Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 20 May 2024 11:21:30 +0100 Subject: [PATCH 79/82] add thread safety test. AddMetric lock. --- .../AWS.Lambda.Powertools.Metrics/Metrics.cs | 26 ++++++++++++------- .../Handlers/FunctionHandler.cs | 15 +++++++++++ .../Handlers/FunctionHandlerTests.cs | 12 +++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index af72bd89..51b86e37 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -52,6 +52,11 @@ public class Metrics : IMetrics, IDisposable /// private readonly bool _captureColdStartEnabled; + // + // Shared synchronization object + // + private readonly object _lockObj = new(); + /// /// Creates a Metrics object that provides features to send metrics to Amazon Cloudwatch using the Embedded metric /// format (EMF). See @@ -98,17 +103,20 @@ void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolut "'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value)); } - var metrics = _context.GetMetrics(); - - if (metrics.Count > 0 && - (metrics.Count == PowertoolsConfigurations.MaxMetrics || - metrics.FirstOrDefault(x => x.Name == key) - ?.Values.Count == PowertoolsConfigurations.MaxMetrics)) + lock (_lockObj) { - _instance.Flush(true); + var metrics = _context.GetMetrics(); + + if (metrics.Count > 0 && + (metrics.Count == PowertoolsConfigurations.MaxMetrics || + metrics.FirstOrDefault(x => x.Name == key) + ?.Values.Count == PowertoolsConfigurations.MaxMetrics)) + { + _instance.Flush(true); + } + + _context.AddMetric(key, value, unit, metricResolution); } - - _context.AddMetric(key, value, unit, metricResolution); } /// diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs index 50bb050f..c781731d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs @@ -13,8 +13,11 @@ * permissions and limitations under the License. */ +using System; using System.Globalization; +using System.Linq; using System.Threading.Tasks; +using AWS.Lambda.Powertools.Common; namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; @@ -41,4 +44,16 @@ public async Task HandleTestSecondCall(string input) return input.ToUpper(CultureInfo.InvariantCulture); } + + [Metrics(Namespace = "ns", Service = "svc")] + public async Task HandleMultipleThreads(string input) + { + await Parallel.ForEachAsync(Enumerable.Range(0, Environment.ProcessorCount * 2), async (x, _) => + { + Metrics.AddMetric("MyMetric", 1); + await Task.Delay(1); + }); + + return input.ToUpper(CultureInfo.InvariantCulture); + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs index 9531e3c9..4afba753 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs @@ -49,4 +49,16 @@ public async Task When_Metrics_Add_Metadata_Second_Invocation_Should_Not_Throw_E exception = await Record.ExceptionAsync( () => handler.HandleTestSecondCall("whatever")); Assert.Null(exception); } + + [Fact] + public async Task When_Metrics_Add_Metadata_FromMultipleThread_Should_Not_Throw_Exception() + { + // Arrange + Metrics.ResetForTest(); + var handler = new FunctionHandler(); + + // Act + var exception = await Record.ExceptionAsync(() => handler.HandleMultipleThreads("whatever")); + Assert.Null(exception); + } } \ No newline at end of file From ab95e85e61cbf8ed934e78dc56122dadb18af3e9 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Mon, 20 May 2024 11:33:46 +0100 Subject: [PATCH 80/82] remove unused --- .../Handlers/FunctionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs index c781731d..271b2e21 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs @@ -17,7 +17,6 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; -using AWS.Lambda.Powertools.Common; namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; From a522ea9c0061fd984af0a28d2673071ae249ab81 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Tue, 21 May 2024 14:26:06 +0100 Subject: [PATCH 81/82] Update version for release 1.10.1 Signed-off-by: Henrique Graca <999396+hjgraca@users.noreply.github.com> --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index ff4acc58..7f866299 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,7 @@ { "Core": { "Logging": "1.5.1", - "Metrics": "1.6.1", + "Metrics": "1.6.2", "Tracing": "1.4.2" }, "Utilities": { From ce3e40758b9849d43edbe07447dd82edfcca90eb Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:32:22 +0100 Subject: [PATCH 82/82] remove warnings as errors for now --- libraries/src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/src/Directory.Build.props b/libraries/src/Directory.Build.props index 453b05ab..40577e59 100644 --- a/libraries/src/Directory.Build.props +++ b/libraries/src/Directory.Build.props @@ -22,7 +22,7 @@ - IL2026,IL2067,IL2075 + true true