Skip to content

Commit 783f70d

Browse files
committed
fix: fix error thrown when template includes intrinsic function
1 parent 3332474 commit 783f70d

File tree

10 files changed

+193
-3
lines changed

10 files changed

+193
-3
lines changed

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ private void RemoveOrphanedLambdaFunctions(HashSet<string> processedLambdaFuncti
284284
}
285285

286286
var toRemove = new List<string>();
287-
foreach (var resourceName in _templateWriter.GetToken<Dictionary<string, object>>("Resources").Keys)
287+
foreach (var resourceName in _templateWriter.GetKeys("Resources"))
288288
{
289289
var resourcePath = $"Resources.{resourceName}";
290290
var type = _templateWriter.GetToken<string>($"{resourcePath}.Type", string.Empty);

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/ITemplateWriter.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
namespace Amazon.Lambda.Annotations.SourceGenerator.Writers
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
4+
namespace Amazon.Lambda.Annotations.SourceGenerator.Writers
25
{
36
/// <summary>
47
/// This interface contains utility methods to manipulate a YAML or JSON blob
@@ -56,5 +59,11 @@ public interface ITemplateWriter
5659
/// If a string value starts with '@' then a reference node is created and returned.
5760
/// </summary>
5861
object GetValueOrRef(string value);
62+
63+
/// <summary>
64+
/// Retrieves a list of keys for a specified template path.
65+
/// </summary>
66+
/// <param name="path">dot(.) seperated path. Example "Person.Name.FirstName"</param>
67+
IList<string> GetKeys(string path);
5968
}
6069
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/JsonWriter.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using Newtonsoft.Json;
@@ -225,5 +226,17 @@ private T GetDeserializedToken<T>(object token)
225226
}
226227
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(token));
227228
}
229+
230+
public IList<string> GetKeys(string path)
231+
{
232+
try
233+
{
234+
return GetToken<Dictionary<string, object>>(path).Keys.ToList();
235+
}
236+
catch (Exception ex)
237+
{
238+
throw new InvalidOperationException($"Unable to retrieve keys for the specified JSON path '{path}'.", ex);
239+
}
240+
}
228241
}
229242
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/YamlWriter.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,5 +271,17 @@ private T GetDeserializedToken<T>(object token)
271271

272272
return _deserializer.Deserialize<T>(_serializer.Serialize(token));
273273
}
274+
275+
public IList<string> GetKeys(string path)
276+
{
277+
try
278+
{
279+
return GetToken<Dictionary<string, YamlMappingNode>>(path).Keys.ToList();
280+
}
281+
catch (Exception ex)
282+
{
283+
throw new InvalidOperationException($"Unable to retrieve keys for the specified YAML path '{path}'.", ex);
284+
}
285+
}
274286
}
275287
}

Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<ItemGroup>
6161
<None Remove="Snapshots\ServerlessTemplates\customizeResponse.template" />
6262
<None Remove="Snapshots\ServerlessTemplates\dynamicexample.template" />
63+
<None Remove="Snapshots\ServerlessTemplates\intrinsicexample.template" />
6364
<None Remove="Snapshots\ServerlessTemplates\nullreferenceexample.template" />
6465
<None Remove="Snapshots\ServerlessTemplates\subnamespace.template" />
6566
<None Remove="Snapshots\ServerlessTemplates\taskexample.template" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Linq;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
using Amazon.Lambda.Core;
6+
7+
namespace TestServerlessApp
8+
{
9+
public class IntrinsicExample_HasIntrinsic_Generated
10+
{
11+
private readonly IntrinsicExample intrinsicExample;
12+
13+
public IntrinsicExample_HasIntrinsic_Generated()
14+
{
15+
SetExecutionEnvironment();
16+
intrinsicExample = new IntrinsicExample();
17+
}
18+
19+
public void HasIntrinsic(string text, Amazon.Lambda.Core.ILambdaContext __context__)
20+
{
21+
intrinsicExample.HasIntrinsic(text, __context__);
22+
}
23+
24+
private static void SetExecutionEnvironment()
25+
{
26+
const string envName = "AWS_EXECUTION_ENV";
27+
28+
var envValue = new StringBuilder();
29+
30+
// If there is an existing execution environment variable add the annotations package as a suffix.
31+
if(!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(envName)))
32+
{
33+
envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_");
34+
}
35+
36+
envValue.Append("amazon-lambda-annotations_0.13.4.0");
37+
38+
Environment.SetEnvironmentVariable(envName, envValue.ToString());
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: 'AWS::Serverless-2016-10-31'
3+
Description: This template is partially managed by Amazon.Lambda.Annotations (v0.13.4.0).
4+
Resources:
5+
TestServerlessAppIntrinsicExampleHasIntrinsicGenerated:
6+
Type: AWS::Serverless::Function
7+
Metadata:
8+
Tool: Amazon.Lambda.Annotations
9+
Properties:
10+
Description: !Sub 'This is deployed in region ${AWS::Region}'
11+
MemorySize: 256
12+
Timeout: 30
13+
Policies:
14+
- AWSLambdaBasicExecutionRole
15+
PackageType: Image
16+
ImageUri: .
17+
ImageConfig:
18+
Command:
19+
- TestProject::TestServerlessApp.IntrinsicExample_HasIntrinsic_Generated::HasIntrinsic

Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Amazon.Lambda.Annotations.SourceGenerators.Tests
1313
{
14-
public class SourceGeneratorTests
14+
public class SourceGeneratorTests : IDisposable
1515
{
1616
[Fact]
1717
public async Task Greeter()
@@ -298,6 +298,45 @@ public async Task VerifyFunctionReturnVoid()
298298
Assert.Equal(expectedTemplateContent, actualTemplateContent);
299299
}
300300

301+
[Fact]
302+
public async Task VerifyNoErrorWithIntrinsicInTemplate()
303+
{
304+
var expectedTemplateContent = File.ReadAllText(Path.Combine("Snapshots", "ServerlessTemplates", "intrinsicexample.template")).ToEnvironmentLineEndings();
305+
var expectedSubNamespaceGenerated = File.ReadAllText(Path.Combine("Snapshots", "IntrinsicExample_HasIntrinsic_Generated.g.cs")).ToEnvironmentLineEndings();
306+
File.WriteAllText(Path.Combine("TestServerlessApp", "serverless.template"), expectedTemplateContent);
307+
308+
await new VerifyCS.Test
309+
{
310+
TestState =
311+
{
312+
Sources =
313+
{
314+
(Path.Combine("TestServerlessApp", "IntrinsicExample.cs"), File.ReadAllText(Path.Combine("TestServerlessApp", "IntrinsicExample.cs"))),
315+
(Path.Combine("Amazon.Lambda.Annotations", "LambdaFunctionAttribute.cs"), File.ReadAllText(Path.Combine("Amazon.Lambda.Annotations", "LambdaFunctionAttribute.cs"))),
316+
(Path.Combine("Amazon.Lambda.Annotations", "LambdaStartupAttribute.cs"), File.ReadAllText(Path.Combine("Amazon.Lambda.Annotations", "LambdaStartupAttribute.cs"))),
317+
(Path.Combine("TestServerlessApp", "AssemblyAttributes.cs"), File.ReadAllText(Path.Combine("TestServerlessApp", "AssemblyAttributes.cs"))),
318+
},
319+
GeneratedSources =
320+
{
321+
(
322+
typeof(SourceGenerator.Generator),
323+
"IntrinsicExample_HasIntrinsic_Generated.g.cs",
324+
SourceText.From(expectedSubNamespaceGenerated, Encoding.UTF8, SourceHashAlgorithm.Sha256)
325+
)
326+
},
327+
ExpectedDiagnostics =
328+
{
329+
new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("IntrinsicExample_HasIntrinsic_Generated.g.cs", expectedSubNamespaceGenerated),
330+
new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent)
331+
},
332+
ReferenceAssemblies = ReferenceAssemblies.Net.Net60
333+
}
334+
}.RunAsync();
335+
336+
var actualTemplateContent = File.ReadAllText(Path.Combine("TestServerlessApp", "serverless.template"));
337+
Assert.Equal(expectedTemplateContent, actualTemplateContent);
338+
}
339+
301340
[Fact]
302341
public async Task VerifyFunctionReturnTask()
303342
{
@@ -646,5 +685,10 @@ public async Task ComplexQueryParameters_AreNotSupported()
646685

647686
}.RunAsync();
648687
}
688+
689+
public void Dispose()
690+
{
691+
File.Delete(Path.Combine("TestServerlessApp", "serverless.template"));
692+
}
649693
}
650694
}

Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/YamlWriterTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Amazon.Lambda.Annotations.SourceGenerators.Tests.WriterTests
1010
public class YamlWriterTests
1111
{
1212
const string yamlContent = @"
13+
Description: !Sub '${AWS::Region}'
1314
Person:
1415
Name:
1516
FirstName: John
@@ -29,12 +30,14 @@ public void Exists()
2930
yamlWriter.Parse(yamlContent);
3031

3132
// ACT and ASSERT
33+
Assert.True(yamlWriter.Exists("Description"));
3234
Assert.True(yamlWriter.Exists("Person"));
3335
Assert.True(yamlWriter.Exists("Person.Name"));
3436
Assert.True(yamlWriter.Exists("Person.Name.LastName"));
3537
Assert.True(yamlWriter.Exists("Person.Age"));
3638
Assert.True(yamlWriter.Exists("Person.PhoneNumbers"));
3739

40+
Assert.False(yamlWriter.Exists("description"));
3841
Assert.False(yamlWriter.Exists("person"));
3942
Assert.False(yamlWriter.Exists("Person.FirstName"));
4043
Assert.False(yamlWriter.Exists("Person.DOB"));
@@ -109,10 +112,12 @@ public void RemoveToken()
109112
yamlWriter.Parse(yamlContent);
110113

111114
// ACT
115+
yamlWriter.RemoveToken("Description");
112116
yamlWriter.RemoveToken("Person.Name.LastName");
113117
yamlWriter.RemoveToken("Person.Age");
114118

115119
// ASSERT
120+
Assert.False(yamlWriter.Exists("Description"));
116121
Assert.False(yamlWriter.Exists("Person.Name.LastName"));
117122
Assert.False(yamlWriter.Exists("Person.Age"));
118123
Assert.True(yamlWriter.Exists("Person.Name"));
@@ -162,5 +167,37 @@ private string SanitizeFileContents(string content)
162167
.Replace("\r\r\n", Environment.NewLine)
163168
.Trim();
164169
}
170+
171+
[Fact]
172+
public void GetKeys()
173+
{
174+
// ARRANGE
175+
const string yamlContent = @"
176+
Resources:
177+
Function1:
178+
Description:
179+
Fn::Sub:
180+
- '${var1}'
181+
- var1: Test
182+
Function2:
183+
Description: !Sub '${AWS::Region}'
184+
";
185+
ITemplateWriter yamlWriter = new YamlWriter();
186+
yamlWriter.Parse(yamlContent);
187+
188+
// ACT
189+
var functionNames = yamlWriter.GetKeys("Resources");
190+
var function1Keys = yamlWriter.GetKeys("Resources.Function1");
191+
192+
// ASSERT
193+
Assert.NotEmpty(functionNames);
194+
Assert.Equal(2, functionNames.Count);
195+
Assert.Equal("Function1", functionNames[0]);
196+
Assert.Equal("Function2", functionNames[1]);
197+
Assert.NotEmpty(function1Keys);
198+
var description = Assert.Single(function1Keys);
199+
Assert.Equal("Description", description);
200+
Assert.Throws<InvalidOperationException>(() => { yamlWriter.GetKeys("Resources.Function2"); });
201+
}
165202
}
166203
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Amazon.Lambda.Annotations;
2+
using Amazon.Lambda.Core;
3+
4+
namespace TestServerlessApp
5+
{
6+
public class IntrinsicExample
7+
{
8+
[LambdaFunction(PackageType = LambdaPackageType.Image)]
9+
public void HasIntrinsic(string text, ILambdaContext context)
10+
{
11+
context.Logger.LogLine(text);
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)