Closed
Description
Expected Behavior
When using Parser with Lambda Function URL I should be able to parse requests that have a valid body that is not encoded as a JSON string. For example, when the body is a plain string or when it's a binary (& the isBase64Encoded
field is true
).
Current Behavior
Unless the body
field is a JSON string, the parsing fails.
Code snippet
import { Logger, LogLevel } from '@aws-lambda-powertools/logger';
import { parser } from '@aws-lambda-powertools/parser/middleware';
import { LambdaFunctionUrlEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import type { ParsedResult } from '@aws-lambda-powertools/parser/types';
import middy from '@middy/core';
import { z } from 'zod';
const logger = new Logger({ logLevel: LogLevel.DEBUG });
export const handler = middy(async (event: ParsedResult) => {
logger.logEventIfEnabled(event);
return {
statusCode: 200,
body: JSON.stringify('Hello, World!'),
};
}).use(
parser({
schema: z.string(),
envelope: LambdaFunctionUrlEnvelope,
safeParse: true,
})
);
Steps to Reproduce
For binary requests:
import {
type BinaryLike,
type KeyObject,
createHash,
createHmac,
} from 'node:crypto';
import { URL } from 'node:url';
import { HttpRequest } from '@smithy/protocol-http';
import { SignatureV4 } from '@smithy/signature-v4';
class Sha256 {
private readonly hash;
public constructor(secret?: unknown) {
this.hash = secret
? createHmac('sha256', secret as BinaryLike | KeyObject)
: createHash('sha256');
}
public digest(): Promise<Uint8Array> {
const buffer = this.hash.digest();
return Promise.resolve(new Uint8Array(buffer.buffer));
}
public update(array: Uint8Array): void {
this.hash.update(array);
}
}
const signer = new SignatureV4({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '',
sessionToken: process.env.AWS_SESSION_TOKEN ?? '',
},
service: 'lambda',
region: process.env.AWS_REGION ?? 'eu-west-1',
sha256: Sha256,
});
const buildHttpRequest = (apiUrl: string): HttpRequest => {
const url = new URL(apiUrl);
const buffer = Buffer.alloc(10);
return new HttpRequest({
hostname: url.hostname,
path: url.pathname,
body: buffer,
method: 'POST',
headers: {
host: url.hostname,
},
});
};
const sendAuthrequest = async (apiUrl: string): Promise<void> => {
// Build the HTTP request to be signed
const httpRequest = buildHttpRequest(apiUrl);
// Sign the request
const signedHttpRequest = await signer.sign(httpRequest);
try {
// Send the request
const result = await fetch(apiUrl, {
headers: new Headers(signedHttpRequest.headers),
body: signedHttpRequest.body,
method: signedHttpRequest.method,
});
if (!result.ok) throw new Error(result.statusText);
const body = await result.json();
console.log('Response:', body);
} catch (err) {
console.error(err as Error);
throw new Error('Failed to send request', { cause: err });
}
};
await sendAuthrequest(
'https://<api-id>.lambda-url.eu-west-1.on.aws/'
);
For raw string requests, use the same code as above, but change the body
in the buildHttpRequest
function to just "hello world"
or any string.
Possible Solution
No response
Powertools for AWS Lambda (TypeScript) version
latest
AWS Lambda function runtime
20.x
Packaging format used
npm
Execution logs
{
"success": false,
"error": {
"name": "ParseError",
"location": "file:///var/task/index.mjs:3",
"message": "Failed to parse Lambda function URL body. This error was caused by: Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON..",
"stack": "ParseError: Failed to parse Lambda function URL body. This error was caused by: Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON..\n at Object.safeParse (file:///var/task/index.mjs:3:82990)\n at _r (file:///var/task/index.mjs:3:9998)\n at before (file:///var/task/index.mjs:3:10351)\n at Vt (file:///var/task/index.mjs:3:85173)\n at co (file:///var/task/index.mjs:3:84503)\n at Runtime.i [as handler] (file:///var/task/index.mjs:3:83598)\n at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)",
"cause": {
"name": "ParseError",
"location": "file:///var/task/index.mjs:3",
"message": "Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON.",
"stack": "ParseError: Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON.\n at Object.safeParse (file:///var/task/index.mjs:3:70629)\n at Object.safeParse (file:///var/task/index.mjs:3:82928)\n at _r (file:///var/task/index.mjs:3:9998)\n at before (file:///var/task/index.mjs:3:10351)\n at Vt (file:///var/task/index.mjs:3:85173)\n at co (file:///var/task/index.mjs:3:84503)\n at Runtime.i [as handler] (file:///var/task/index.mjs:3:83598)\n at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)",
"cause": {
"name": "SyntaxError",
"location": "file:///var/task/index.mjs:3",
"message": "Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON",
"stack": "SyntaxError: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON\n at JSON.parse (<anonymous>)\n at Object.safeParse (file:///var/task/index.mjs:3:70457)\n at Object.safeParse (file:///var/task/index.mjs:3:82928)\n at _r (file:///var/task/index.mjs:3:9998)\n at before (file:///var/task/index.mjs:3:10351)\n at Vt (file:///var/task/index.mjs:3:85173)\n at co (file:///var/task/index.mjs:3:84503)\n at Runtime.i [as handler] (file:///var/task/index.mjs:3:83598)\n at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)"
}
}
},
"originalEvent": {
"version": "2.0",
"routeKey": "$default",
"rawPath": "/",
"rawQueryString": "",
"headers": {
"sec-fetch-mode": "cors",
"x-amz-content-sha256": "01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca",
"content-length": "10",
"x-amzn-tls-version": "TLSv1.3",
"accept-language": "*",
"x-amz-date": "20241014T100603Z",
"x-forwarded-proto": "https",
"x-forwarded-port": "443",
"x-forwarded-for": "92.177.95.206",
"x-amz-security-token": "....",
"accept": "*/*",
"x-amzn-tls-cipher-suite": "TLS_AES_128_GCM_SHA256",
"x-amzn-trace-id": "Root=1-670ced0b-7334ddc02966931874eeca3c",
"host": "api-id.lambda-url.eu-west-1.on.aws",
"accept-encoding": "br, gzip, deflate",
"user-agent": "node"
},
"requestContext": {
"accountId": "123456789023",
"apiId": "api-id",
"authorizer": {
"iam": {
"accessKey": "ASIA...............Z",
"accountId": "123456789023",
"callerId": "AROA...............Z:aamorosi-role",
"cognitoIdentity": null,
"principalOrgId": null,
"userArn": "arn:aws:sts::123456789023:assumed-role/Admin/aamorosi-role",
"userId": "AROA...............Z:aamorosi-role"
}
},
"domainName": "api-id.lambda-url.eu-west-1.on.aws",
"domainPrefix": "api-id",
"http": {
"method": "POST",
"path": "/",
"protocol": "HTTP/1.1",
"sourceIp": "92.177.95.206",
"userAgent": "node"
},
"requestId": "1a2de596-d389-469e-ba8f-6d0b080b8297",
"routeKey": "$default",
"stage": "$default",
"time": "14/Oct/2024:10:06:04 +0000",
"timeEpoch": 1728900364011
},
"body": "AAAAAAAAAAAAAA==",
"isBase64Encoded": true
}
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Shipped