Skip to content

Commit 5e3ae34

Browse files
committed
Merge branch 'dev' of https://github.com/aws/aws-lambda-dotnet into dev
2 parents 8290b72 + d5c6783 commit 5e3ae34

42 files changed

Lines changed: 2502 additions & 54 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
---
2+
name: new-event-source
3+
description: Add a new AWS event source attribute (e.g., Kinesis, Kafka, MQ) to the Lambda .NET Annotations framework, including the attribute class, source generator integration, CloudFormation writer, unit tests, writer tests, source generator tests, and integration tests
4+
---
5+
6+
# Adding a New Event Source to Lambda Annotations
7+
8+
This skill guides you through adding a complete new event source attribute to the AWS Lambda .NET Annotations framework. Use this when a user asks to add support for a new AWS event source like Kinesis, Kafka, MQ, etc.
9+
10+
## Prerequisites
11+
12+
Before starting, gather from the user:
13+
1. **Service name** (e.g., "Kinesis", "Kafka", "MQ")
14+
2. **Primary resource identifier** (e.g., stream ARN, topic ARN, broker ARN)
15+
3. **CloudFormation event type string** (e.g., "Kinesis", "MSK", "MQ")
16+
4. **Event class name** from the corresponding `Amazon.Lambda.*Events` NuGet package (e.g., `KinesisEvent`)
17+
5. **Optional properties** the attribute should support (e.g., BatchSize, StartingPosition, Filters)
18+
6. **Whether `@` references use `Fn::GetAtt` or `Ref`** — event source mappings use `Fn::GetAtt`, subscriptions use `Ref`
19+
20+
## Reference Examples
21+
22+
Read these files to understand existing patterns before creating new ones:
23+
- **SNS (simplest, subscription-based)**: `Libraries/src/Amazon.Lambda.Annotations/SNS/SNSEventAttribute.cs`
24+
- **SQS (event source mapping with batching)**: `Libraries/src/Amazon.Lambda.Annotations/SQS/SQSEventAttribute.cs`
25+
- **DynamoDB (stream-based)**: `Libraries/src/Amazon.Lambda.Annotations/DynamoDB/DynamoDBEventAttribute.cs`
26+
- **S3 (notification-based)**: `Libraries/src/Amazon.Lambda.Annotations/S3/S3EventAttribute.cs`
27+
28+
## Steps
29+
30+
### Step 1: Create the Event Attribute Class
31+
32+
**Create**: `Libraries/src/Amazon.Lambda.Annotations/{ServiceName}/{ServiceName}EventAttribute.cs`
33+
34+
Key patterns:
35+
- Add copyright header: `// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.` + `// SPDX-License-Identifier: Apache-2.0`
36+
- Inherit from `Attribute` with `[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]`
37+
- Constructor takes the primary resource identifier as a required `string` parameter
38+
- All optional properties use nullable backing fields with `Is<PropertyName>Set` internal properties
39+
- Include auto-derived `ResourceName` property (strips `@` prefix or extracts name from ARN)
40+
- Include `internal List<string> Validate()` method with all validation rules
41+
- Use `Regex("^[a-zA-Z0-9]+$")` for ResourceName validation
42+
43+
### Step 2: Register Type Full Names
44+
45+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs`
46+
47+
Add constants:
48+
```csharp
49+
public const string {ServiceName}EventAttribute = "Amazon.Lambda.Annotations.{ServiceName}.{ServiceName}EventAttribute";
50+
public const string {ServiceName}Event = "Amazon.Lambda.{ServiceName}Events.{ServiceName}Event";
51+
```
52+
53+
Also add to `EventType` enum if needed in `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventType.cs`.
54+
55+
### Step 3: Create the Attribute Builder
56+
57+
**Create**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/{ServiceName}EventAttributeBuilder.cs`
58+
59+
Extracts attribute data from Roslyn `AttributeData`. Use consistent `else if` chaining. Reference: `SNSEventAttributeBuilder.cs`.
60+
61+
### Step 4: Register in AttributeModelBuilder
62+
63+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs`
64+
65+
Add `else if` block for the new attribute type after the existing event attribute blocks.
66+
67+
### Step 5: Register in EventTypeBuilder
68+
69+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs`
70+
71+
Add `else if` block mapping the attribute to the `EventType` enum value.
72+
73+
### Step 6: Add DiagnosticDescriptor
74+
75+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs`
76+
77+
Add descriptor with the next available `AWSLambda0XXX` ID for invalid attribute validation errors.
78+
79+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md` — add the new diagnostic ID.
80+
81+
### Step 7: Add Validation in LambdaFunctionValidator
82+
83+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs`
84+
85+
1. Add `Validate{ServiceName}Events()` call in `ValidateFunction` method
86+
2. Create private `Validate{ServiceName}Events()` method that validates:
87+
- Attribute properties via `Validate()` method
88+
- Method parameters (first must be event type, optional second is `ILambdaContext`)
89+
- Return type (usually `void` or `Task`)
90+
91+
### Step 8: Add Dependency Check
92+
93+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs`
94+
95+
In `ValidateDependencies`, add check for `Amazon.Lambda.{ServiceName}Events` NuGet package.
96+
97+
### Step 9: Check SyntaxReceiver
98+
99+
**Check**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs`
100+
101+
Add the new attribute name if the SyntaxReceiver filters by attribute name strings.
102+
103+
### Step 10: Add CloudFormation Writer Logic
104+
105+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs`
106+
107+
1. Add `case AttributeModel<{ServiceName}EventAttribute>` in the event processing switch
108+
2. Create `Process{ServiceName}Attribute()` method that writes CF template properties
109+
- Event source mappings (SQS, DynamoDB, Kinesis): use `Fn::GetAtt` for `@` references
110+
- Subscription events (SNS): use `Ref` for `@` references
111+
- Track synced properties in metadata
112+
113+
### Step 11: Create Attribute Unit Tests
114+
115+
**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/{ServiceName}EventAttributeTests.cs`
116+
117+
Cover: constructor, defaults, property tracking, ResourceName derivation, all validation paths. Reference: `SQSEventAttributeTests.cs`, `DynamoDBEventAttributeTests.cs`, `SNSEventAttributeTests.cs`.
118+
119+
### Step 12: Create CloudFormation Writer Tests
120+
121+
**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/{ServiceName}EventsTests.cs`
122+
123+
This is a `partial class CloudFormationWriterTests`. Include tests for:
124+
1. `Verify{ServiceName}EventAttributes_AreCorrectlyApplied` — Theory with JSON/YAML and property combinations
125+
2. `Verify{ServiceName}EventProperties_AreSyncedCorrectly` — Synced properties update when attributes change
126+
3. `SwitchBetweenArnAndRef_For{Resource}` — ARN to `@` reference switching
127+
4. `Verify{Resource}CanBeSet_FromCloudFormationParameter` — CF Parameters handling
128+
5. `VerifyManuallySet{ServiceName}EventProperties_ArePreserved` — Hand-edited template preservation
129+
130+
Reference: `SQSEventsTests.cs`, `DynamoDBEventsTests.cs`, `SNSEventsTests.cs`.
131+
132+
### Step 13: Create Valid Event Examples + Source Generator Test
133+
134+
**Create**: `Libraries/test/TestServerlessApp/{ServiceName}EventExamples/Valid{ServiceName}Events.cs.txt`
135+
**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/{ServiceName}/` (generated handler snapshots)
136+
**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/{serviceName}Events.template`
137+
**Modify**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs` — add `VerifyValid{ServiceName}Events()` test
138+
139+
### Step 14: Create Invalid Event Examples + Source Generator Test
140+
141+
**Create**: `Libraries/test/TestServerlessApp/{ServiceName}EventExamples/Invalid{ServiceName}Events.cs.error`
142+
143+
Cover: invalid property values, invalid params, invalid return type, multiple events, invalid ARN, invalid resource name.
144+
145+
**Modify**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs` — add `VerifyInvalid{ServiceName}Events_ThrowsCompilationErrors()` test with diagnostic assertions including line spans.
146+
147+
### Step 15: Create Generated Code Snapshots
148+
149+
**Create**: `Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/{ServiceName}/`
150+
151+
Tip: Run the source generator once to get actual output, then use as snapshot.
152+
153+
### Step 16: Create Integration Test
154+
155+
**Create**: `Libraries/test/TestServerlessApp.IntegrationTests/{ServiceName}EventSourceMapping.cs`
156+
**Modify**: `Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs` — resource lookup
157+
**Modify**: `Libraries/test/TestServerlessApp.IntegrationTests/DeploymentScript.ps1` — if needed
158+
159+
### Step 17: Update AnalyzerReleases.Unshipped.md
160+
161+
**Modify**: `Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md`
162+
163+
## File Map Summary
164+
165+
| Action | File Path |
166+
|--------|-----------|
167+
| Create | `src/Amazon.Lambda.Annotations/{ServiceName}/{ServiceName}EventAttribute.cs` |
168+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs` |
169+
| Create | `src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/{ServiceName}EventAttributeBuilder.cs` |
170+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs` |
171+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs` |
172+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs` |
173+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs` |
174+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs` |
175+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs` |
176+
| Modify | `src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md` |
177+
| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/{ServiceName}EventAttributeTests.cs` |
178+
| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/{ServiceName}EventsTests.cs` |
179+
| Create | `test/TestServerlessApp/{ServiceName}EventExamples/Valid{ServiceName}Events.cs.txt` |
180+
| Create | `test/TestServerlessApp/{ServiceName}EventExamples/Invalid{ServiceName}Events.cs.error` |
181+
| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/{ServiceName}/` |
182+
| Create | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/{serviceName}Events.template` |
183+
| Modify | `test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs` |
184+
| Create | `test/TestServerlessApp.IntegrationTests/{ServiceName}EventSourceMapping.cs` |
185+
| Modify | `test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs` |
186+
187+
## Important Conventions
188+
189+
- **Copyright header** on every new `.cs` file
190+
- **Consistent `else if` chaining** in attribute builders (never `if` then `if` for the same loop)
191+
- **Both JSON and YAML** template formats must be tested in writer tests
192+
- **Invalid event test spans** must reference exact line numbers in the `.cs.error` file
193+
- **`.cs.txt` extension** for valid test files (prevents deployment)
194+
- **`.cs.error` extension** for invalid test files (prevents compilation)
195+
- **Use enums instead of strings** when you need to represent a fixed, known set of constants that do not change frequently (e.g., `StartingPosition`, `AuthType`, `HttpApiVersion`). Enums provide compile-time type safety, eliminate the need for manual string validation in the `Validate()` method, and prevent invalid values from being set. In attribute builders, enum values come through Roslyn's `AttributeData` as their underlying `int` value and must be cast accordingly (e.g., `(MyEnum)(int)pair.Value.Value`). When writing enum values to CloudFormation templates, use `.ToString()` to convert back to the string representation.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Amazon.Lambda.Annotations",
5+
"Type": "Minor",
6+
"ChangelogMessages": [
7+
"Added [DynamoDBEvent] annotation attribute for declaratively configuring DynamoDB stream-triggered Lambda functions with support for stream reference, batch size, starting position, batching window, filters, and enabled state."
8+
]
9+
}
10+
]
11+
}

.github/workflows/auto-update-Dockerfiles.yml

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ on:
1111
# Allows to run this workflow manually from the Actions tab for testing
1212
workflow_dispatch:
1313

14-
15-
1614
jobs:
1715
auto-update:
1816
runs-on: ubuntu-latest
@@ -27,8 +25,7 @@ jobs:
2725
NET_11_ARM64_Dockerfile: "LambdaRuntimeDockerfiles/Images/net11/arm64/Dockerfile"
2826

2927
steps:
30-
# Checks-out the repository under $GITHUB_WORKSPACE
31-
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 #v4.2.2
28+
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 # v4.2.2
3229
with:
3330
ref: 'dev'
3431

@@ -149,34 +146,49 @@ jobs:
149146
id: commit-push
150147
shell: pwsh
151148
run: |
149+
git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com"
150+
git config --global user.name "aws-sdk-dotnet-automation"
151+
152+
$remoteBranch = "chore/auto-update-Dockerfiles-daily"
153+
154+
# Try to fetch the remote branch (it may not exist yet)
155+
git fetch origin $remoteBranch 2>$null
156+
$remoteExists = ($LASTEXITCODE -eq 0)
157+
152158
# Check if there are any changes to commit
153159
if (git status --porcelain) {
154-
git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com"
155-
git config --global user.name "aws-sdk-dotnet-automation"
156-
160+
157161
# Generate timestamp for unique local branch name
158162
$timestamp = Get-Date -Format "yyyyMMddHHmmss"
159163
$localBranch = "chore/auto-update-Dockerfiles-daily-$timestamp"
160-
$remoteBranch = "chore/auto-update-Dockerfiles-daily"
161-
164+
162165
# Always create a new unique local branch
163166
git checkout -b $localBranch
164-
167+
165168
git add "**/*Dockerfile"
166169
git commit -m "chore: Daily ASP.NET Core version update in Dockerfiles"
167-
168-
# Always delete the remote branch before pushing to avoid stale branch errors
170+
171+
# If remote branch exists and there is no diff vs remote branch, skip pushing and PR creation
172+
if ($remoteExists) {
173+
git diff --quiet "origin/$remoteBranch...HEAD"
174+
if ($LASTEXITCODE -eq 0) {
175+
echo "No diff vs origin/$remoteBranch. Skipping push/PR creation."
176+
Add-Content -Path $env:GITHUB_OUTPUT -Value "CHANGES_MADE=false"
177+
exit 0
178+
}
179+
}
180+
181+
# Only now delete + push (because we know it's different)
169182
git push origin --delete $remoteBranch 2>$null
170-
171-
# Push local branch to remote branch (force push to consistent remote branch name)
172183
git push --force origin "${localBranch}:${remoteBranch}"
173-
184+
174185
# Write the remote branch name to GITHUB_OUTPUT for use in the PR step
175186
Add-Content -Path $env:GITHUB_OUTPUT -Value "BRANCH=$remoteBranch"
176187
Add-Content -Path $env:GITHUB_OUTPUT -Value "CHANGES_MADE=true"
177188
echo "Changes committed to local branch $localBranch and pushed to remote branch $remoteBranch"
178189
} else {
179190
echo "No changes detected in Dockerfiles, skipping PR creation"
191+
Add-Content -Path $env:GITHUB_OUTPUT -Value "CHANGES_MADE=false"
180192
}
181193
182194
# Create a Pull Request

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ AWSLambda0133 | AWSLambdaCSharpGenerator | Error | ALB Listener Reference Not Fo
2121
AWSLambda0134 | AWSLambdaCSharpGenerator | Error | FromRoute not supported on ALB functions
2222
AWSLambda0135 | AWSLambdaCSharpGenerator | Error | Unmapped parameter on ALB function
2323
AWSLambda0136 | AWSLambdaCSharpGenerator | Error | Invalid S3EventAttribute
24+
AWSLambda0137 | AWSLambdaCSharpGenerator | Error | Invalid DynamoDBEventAttribute
2425
AWSLambda0138 | AWSLambdaCSharpGenerator | Error | Invalid SNSEventAttribute

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,13 @@ public static class DiagnosticDescriptors
282282
DiagnosticSeverity.Error,
283283
isEnabledByDefault: true);
284284

285+
public static readonly DiagnosticDescriptor InvalidDynamoDBEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0137",
286+
title: "Invalid DynamoDBEventAttribute",
287+
messageFormat: "Invalid DynamoDBEventAttribute encountered: {0}",
288+
category: "AWSLambdaCSharpGenerator",
289+
DiagnosticSeverity.Error,
290+
isEnabledByDefault: true);
291+
285292
public static readonly DiagnosticDescriptor InvalidSnsEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0138",
286293
title: "Invalid SNSEventAttribute",
287294
messageFormat: "Invalid SNSEventAttribute encountered: {0}",

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using Amazon.Lambda.Annotations.ALB;
66
using Amazon.Lambda.Annotations.APIGateway;
7+
using Amazon.Lambda.Annotations.DynamoDB;
78
using Amazon.Lambda.Annotations.S3;
89
using Amazon.Lambda.Annotations.SQS;
910
using Microsoft.CodeAnalysis;
@@ -113,6 +114,15 @@ public static AttributeModel Build(AttributeData att, GeneratorExecutionContext
113114
Type = TypeModelBuilder.Build(att.AttributeClass, context)
114115
};
115116
}
117+
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.DynamoDBEventAttribute), SymbolEqualityComparer.Default))
118+
{
119+
var data = DynamoDBEventAttributeBuilder.Build(att);
120+
model = new AttributeModel<DynamoDBEventAttribute>
121+
{
122+
Data = data,
123+
Type = TypeModelBuilder.Build(att.AttributeClass, context)
124+
};
125+
}
116126
else if (att.AttributeClass.Equals(context.Compilation.GetTypeByMetadataName(TypeFullNames.SNSEventAttribute), SymbolEqualityComparer.Default))
117127
{
118128
var data = SNSEventAttributeBuilder.Build(att);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using Amazon.Lambda.Annotations.DynamoDB;
5+
using Microsoft.CodeAnalysis;
6+
using System;
7+
8+
namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
9+
{
10+
/// <summary>
11+
/// Builder for <see cref="DynamoDBEventAttribute"/>.
12+
/// </summary>
13+
public class DynamoDBEventAttributeBuilder
14+
{
15+
public static DynamoDBEventAttribute Build(AttributeData att)
16+
{
17+
if (att.ConstructorArguments.Length != 1)
18+
{
19+
throw new NotSupportedException($"{TypeFullNames.DynamoDBEventAttribute} must have constructor with 1 argument.");
20+
}
21+
var stream = att.ConstructorArguments[0].Value as string;
22+
var data = new DynamoDBEventAttribute(stream);
23+
24+
foreach (var pair in att.NamedArguments)
25+
{
26+
if (pair.Key == nameof(data.ResourceName) && pair.Value.Value is string resourceName)
27+
{
28+
data.ResourceName = resourceName;
29+
}
30+
else if (pair.Key == nameof(data.BatchSize) && pair.Value.Value is uint batchSize)
31+
{
32+
data.BatchSize = batchSize;
33+
}
34+
else if (pair.Key == nameof(data.StartingPosition) && pair.Value.Value is int startingPosition)
35+
{
36+
data.StartingPosition = (StartingPosition)startingPosition;
37+
}
38+
else if (pair.Key == nameof(data.Enabled) && pair.Value.Value is bool enabled)
39+
{
40+
data.Enabled = enabled;
41+
}
42+
else if (pair.Key == nameof(data.MaximumBatchingWindowInSeconds) && pair.Value.Value is uint maximumBatchingWindowInSeconds)
43+
{
44+
data.MaximumBatchingWindowInSeconds = maximumBatchingWindowInSeconds;
45+
}
46+
else if (pair.Key == nameof(data.Filters) && pair.Value.Value is string filters)
47+
{
48+
data.Filters = filters;
49+
}
50+
}
51+
52+
return data;
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)