Skip to content

Commit 0418829

Browse files
committed
fix(lambda): stop rejecting values AWS documents as valid
A comprehensive validation pass against the API reference found the previous commit's blanket "no empty optional strings" rule was wrong. Several Lambda parameters document an empty string as meaningful, and their patterns say so: KMSKeyArn, SourceKMSKeyArn, and DeadLetterConfig.TargetArn all carry `(arn:...)|()`, whose trailing alternative matches the empty string, and the on-success/on-failure destinations document `Minimum length of 0` with a pattern beginning `$|`. For each, empty is how the setting is cleared. The rule is now opt-in rather than opt-out: only the five fields feeding a truthiness-based cross-field check reject an empty value. That removes 47 constraints and leaves the ones that were actually reported. Also from the same pass: - Supplying an image URI no longer demands an explicit `packageType`. That subBlock is advanced with no default, so requiring it produced a 400 naming a control the user cannot see; the operation derives Image from the code source instead, and only an explicit Zip alongside an image is rejected. - `fileSystemConfigs` was the one projection without a null guard, so an omitted field vanished from the block output rather than reading null. - An absent function URL now maps to null instead of an empty string a workflow could build a request against. - GetFunction reports `tagsError`, so a partial tag-read failure is distinguishable from a function with no tags, and marks `configuration` nullable to match what the operation returns. - Event source mappings report `selfManagedKafkaBootstrapServers`, which could be set but never read back. - TagResource rejects an empty tag map instead of reporting "0 tags applied".
1 parent cc9821f commit 0418829

27 files changed

Lines changed: 211 additions & 90 deletions

apps/docs/content/docs/en/integrations/lambda.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Get a function's configuration, code location, tags, and reserved concurrency
115115
| Parameter | Type | Description |
116116
| --------- | ---- | ----------- |
117117
| `configuration` | json | The function's configuration \(ARN, runtime, handler, memory, state, layers, VPC, and logging settings\) |
118+
| `tagsError` | json | Why the tags could not be read, when a partial tag-read failure occurred |
118119
| `code` | json | Presigned download URL for the deployment package, or the container image URI |
119120
| `tags` | json | The function's tags |
120121
| `reservedConcurrentExecutions` | number | Concurrency reserved for this function, if any |

apps/sim/lib/api/contracts/tools/aws/lambda-add-permission.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ const AddPermissionSchema = z.object({
2929
'action must be a Lambda action such as lambda:InvokeFunction'
3030
),
3131
principal: z.string().min(1, 'principal is required'),
32-
sourceArn: z.string().min(1, 'sourceArn cannot be empty').optional(),
33-
sourceAccount: z.string().min(1, 'sourceAccount cannot be empty').optional(),
34-
principalOrgId: z.string().min(1, 'principalOrgId cannot be empty').optional(),
35-
eventSourceToken: z.string().min(1, 'eventSourceToken cannot be empty').optional(),
32+
sourceArn: z.string().optional(),
33+
sourceAccount: z.string().optional(),
34+
principalOrgId: z.string().optional(),
35+
eventSourceToken: z.string().optional(),
3636
functionUrlAuthType: z.enum(['NONE', 'AWS_IAM']).optional(),
3737
qualifier: z
3838
.string()
3939
.min(1, 'qualifier cannot be empty')
4040
.max(128, 'qualifier cannot exceed 128 characters')
4141
.optional(),
42-
revisionId: z.string().min(1, 'revisionId cannot be empty').optional(),
42+
revisionId: z.string().optional(),
4343
})
4444

4545
const AddPermissionResponseSchema = z.object({

apps/sim/lib/api/contracts/tools/aws/lambda-create-event-source-mapping.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ const CreateEventSourceMappingSchema = z
3636
queues: z.array(z.string()).optional(),
3737
functionResponseTypes: z.array(z.enum(['ReportBatchItemFailures'])).optional(),
3838
filterPatterns: z.array(z.string()).optional(),
39-
onSuccessDestination: z.string().min(1, 'onSuccessDestination cannot be empty').optional(),
40-
onFailureDestination: z.string().min(1, 'onFailureDestination cannot be empty').optional(),
41-
kmsKeyArn: z.string().min(1, 'kmsKeyArn cannot be empty').optional(),
39+
onSuccessDestination: z.string().optional(),
40+
onFailureDestination: z.string().optional(),
41+
kmsKeyArn: z.string().optional(),
4242
tags: z.record(z.string(), z.string()).optional(),
4343
sourceAccessConfigurations: z
4444
.array(
@@ -49,20 +49,11 @@ const CreateEventSourceMappingSchema = z
4949
)
5050
.max(22, 'sourceAccessConfigurations accepts at most 22 entries')
5151
.optional(),
52-
documentDbDatabaseName: z.string().min(1, 'documentDbDatabaseName cannot be empty').optional(),
53-
documentDbCollectionName: z
54-
.string()
55-
.min(1, 'documentDbCollectionName cannot be empty')
56-
.optional(),
52+
documentDbDatabaseName: z.string().optional(),
53+
documentDbCollectionName: z.string().optional(),
5754
documentDbFullDocument: z.enum(['UpdateLookup', 'Default']).optional(),
58-
amazonManagedKafkaConsumerGroupId: z
59-
.string()
60-
.min(1, 'amazonManagedKafkaConsumerGroupId cannot be empty')
61-
.optional(),
62-
selfManagedKafkaConsumerGroupId: z
63-
.string()
64-
.min(1, 'selfManagedKafkaConsumerGroupId cannot be empty')
65-
.optional(),
55+
amazonManagedKafkaConsumerGroupId: z.string().optional(),
56+
selfManagedKafkaConsumerGroupId: z.string().optional(),
6657
selfManagedKafkaBootstrapServers: z
6758
.array(z.string().min(1, 'a bootstrap server cannot be empty'))
6859
.optional(),

apps/sim/lib/api/contracts/tools/aws/lambda-create-function.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ const CreateFunctionSchema = z
1818
.min(1, 'functionName is required')
1919
.max(256, 'functionName cannot exceed 256 characters'),
2020
role: z.string().min(1, 'role is required'),
21-
runtime: z.string().min(1, 'runtime cannot be empty').optional(),
22-
handler: z.string().min(1, 'handler cannot be empty').optional(),
21+
runtime: z.string().optional(),
22+
handler: z.string().optional(),
2323
packageType: z.enum(['Zip', 'Image']).optional(),
2424
s3Bucket: z.string().min(1, 's3Bucket cannot be empty').optional(),
2525
s3Key: z.string().min(1, 's3Key cannot be empty').optional(),
2626
s3ObjectVersion: z.string().min(1, 's3ObjectVersion cannot be empty').optional(),
2727
imageUri: z.string().min(1, 'imageUri cannot be empty').optional(),
28-
sourceKmsKeyArn: z.string().min(1, 'sourceKmsKeyArn cannot be empty').optional(),
28+
sourceKmsKeyArn: z.string().optional(),
2929
description: z.string().max(256, 'description cannot exceed 256 characters').optional(),
3030
functionTimeout: z.number().int().min(1).max(900).optional(),
3131
memorySize: z.number().int().min(128).max(32768).optional(),
@@ -41,11 +41,11 @@ const CreateFunctionSchema = z
4141
vpcSubnetIds: z.array(z.string()).optional(),
4242
vpcSecurityGroupIds: z.array(z.string()).optional(),
4343
tracingMode: z.enum(['Active', 'PassThrough']).optional(),
44-
deadLetterTargetArn: z.string().min(1, 'deadLetterTargetArn cannot be empty').optional(),
45-
kmsKeyArn: z.string().min(1, 'kmsKeyArn cannot be empty').optional(),
44+
deadLetterTargetArn: z.string().optional(),
45+
kmsKeyArn: z.string().optional(),
4646
snapStartApplyOn: z.enum(['PublishedVersions', 'None']).optional(),
4747
logFormat: z.enum(['JSON', 'Text']).optional(),
48-
logGroup: z.string().min(1, 'logGroup cannot be empty').optional(),
48+
logGroup: z.string().optional(),
4949
})
5050
.superRefine((value, ctx) => {
5151
const hasAnyZipField = Boolean(
@@ -71,11 +71,11 @@ const CreateFunctionSchema = z
7171
})
7272
return
7373
}
74-
if (value.imageUri && value.packageType !== 'Image') {
74+
if (value.imageUri && value.packageType === 'Zip') {
7575
ctx.addIssue({
7676
code: 'custom',
7777
path: ['packageType'],
78-
message: 'packageType must be Image when imageUri is set',
78+
message: 'packageType Zip requires an S3 package, not imageUri',
7979
})
8080
}
8181
if (hasS3 && value.packageType === 'Image') {

apps/sim/lib/api/contracts/tools/aws/lambda-get-function.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ const GetFunctionResponseSchema = z.object({
2727
success: z.literal(true),
2828
output: z.object({
2929
configuration: lambdaFunctionConfigurationSchema.nullable(),
30+
tagsError: z
31+
.object({ errorCode: z.string().nullable(), message: z.string().nullable() })
32+
.nullable(),
3033
code: z
3134
.object({
3235
repositoryType: z.string().nullable(),

apps/sim/lib/api/contracts/tools/aws/lambda-invoke.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const InvokeSchema = z.object({
1818
logType: z.enum(['None', 'Tail']).optional(),
1919
clientContext: z
2020
.string()
21-
.min(1, 'clientContext cannot be empty')
2221
.max(3583, 'clientContext cannot exceed 3583 bytes of base64 data')
2322
.optional(),
2423
qualifier: z

apps/sim/lib/api/contracts/tools/aws/lambda-list-aliases.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const ListAliasesSchema = z.object({
1818
.string()
1919
.min(1, 'functionName is required')
2020
.max(256, 'functionName cannot exceed 256 characters'),
21-
aliasFunctionVersion: z.string().min(1, 'aliasFunctionVersion cannot be empty').optional(),
21+
aliasFunctionVersion: z.string().optional(),
2222
})
2323

2424
const ListAliasesResponseSchema = z.object({

apps/sim/lib/api/contracts/tools/aws/lambda-list-functions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const ListFunctionsSchema = z
1616
...lambdaConnectionFields,
1717
...lambdaPaginationFields,
1818
functionVersion: z.literal('ALL').optional(),
19-
masterRegion: z.string().min(1, 'masterRegion cannot be empty').optional(),
19+
masterRegion: z.string().optional(),
2020
})
2121
.superRefine((value, ctx) => {
2222
if (value.masterRegion && value.functionVersion !== 'ALL') {

apps/sim/lib/api/contracts/tools/aws/lambda-list-layer-versions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const ListLayerVersionsSchema = z.object({
2222
/^(arn:[a-zA-Z0-9-]+:lambda:[a-zA-Z0-9-]+:\d{12}:layer:[a-zA-Z0-9-_]+)$|^[a-zA-Z0-9-_]+$/,
2323
'layerName must be a layer name or a layer ARN'
2424
),
25-
compatibleRuntime: z.string().min(1, 'compatibleRuntime cannot be empty').optional(),
25+
compatibleRuntime: z.string().optional(),
2626
compatibleArchitecture: z.enum(['x86_64', 'arm64']).optional(),
2727
})
2828

apps/sim/lib/api/contracts/tools/aws/lambda-list-layers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
1414
const ListLayersSchema = z.object({
1515
...lambdaConnectionFields,
1616
...lambdaSmallPaginationFields,
17-
compatibleRuntime: z.string().min(1, 'compatibleRuntime cannot be empty').optional(),
17+
compatibleRuntime: z.string().optional(),
1818
compatibleArchitecture: z.enum(['x86_64', 'arm64']).optional(),
1919
})
2020

0 commit comments

Comments
 (0)