Skip to content

Commit 14390f9

Browse files
committed
fix(firestore-send-email): make attachment validation more lenient and more robust
1 parent 8276922 commit 14390f9

File tree

4 files changed

+63
-19
lines changed

4 files changed

+63
-19
lines changed

firestore-send-email/functions/__tests__/prepare-payload.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ describe("preparePayload Template Merging", () => {
398398
await expect(preparePayload(payload)).rejects.toThrow();
399399
});
400400

401-
it("should handle null attachments", async () => {
401+
it("should handle null attachments as no attachments", async () => {
402402
const payload = {
403403
to: "test@example.com",
404404
message: {
@@ -408,7 +408,24 @@ describe("preparePayload Template Merging", () => {
408408
},
409409
};
410410

411-
await expect(preparePayload(payload)).rejects.toThrow();
411+
const result = await preparePayload(payload);
412+
expect(result.message.attachments).toBeUndefined();
413+
});
414+
415+
it("should normalize single attachment object to array", async () => {
416+
const payload = {
417+
to: "test@example.com",
418+
message: {
419+
subject: "Test Subject",
420+
text: "Test text",
421+
attachments: { filename: "test.txt", content: "test content" },
422+
},
423+
};
424+
425+
const result = await preparePayload(payload);
426+
expect(result.message.attachments).toEqual([
427+
{ filename: "test.txt", content: "test content" },
428+
]);
412429
});
413430

414431
it("should handle undefined attachments", async () => {

firestore-send-email/functions/__tests__/validation.test.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -601,22 +601,16 @@ describe("validatePayload", () => {
601601
);
602602
});
603603

604-
it("should throw error for non-array attachments", () => {
605-
const invalidPayload = {
604+
it("should accept single attachment object and normalize to array", () => {
605+
const payload = {
606606
to: "test@example.com",
607607
message: {
608608
subject: "Test Subject",
609609
text: "Test message",
610-
attachments: {
611-
filename: "test.txt",
612-
content: "test",
613-
},
610+
attachments: { filename: "test.txt", content: "test" },
614611
},
615612
};
616-
expect(() => validatePayload(invalidPayload)).toThrow(ValidationError);
617-
expect(() => validatePayload(invalidPayload)).toThrow(
618-
"Invalid message configuration: Field 'message.attachments' must be an array"
619-
);
613+
expect(() => validatePayload(payload)).not.toThrow();
620614
});
621615
});
622616
});

firestore-send-email/functions/src/prepare-payload.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
validatePayload,
44
attachmentSchema,
55
attachmentsSchema,
6+
ValidationError,
67
} from "./validation";
78
import * as logs from "./logs";
89
import config from "./config";
@@ -41,11 +42,19 @@ export async function preparePayload(
4142
const templateRender = await templates.render(template.name, template.data);
4243
const mergeMessage = payload.message || {};
4344

44-
let attachments = attachmentsSchema.parse(
45-
templateRender.attachments
46-
? templateRender.attachments
47-
: mergeMessage.attachments
48-
);
45+
const attachmentsInput = templateRender.attachments
46+
? templateRender.attachments
47+
: mergeMessage.attachments;
48+
49+
const attachmentsResult = attachmentsSchema.safeParse(attachmentsInput);
50+
if (!attachmentsResult.success) {
51+
throw new ValidationError(
52+
`Invalid attachments: ${attachmentsResult.error.issues
53+
.map((i) => i.message)
54+
.join(", ")}`
55+
);
56+
}
57+
let attachments = attachmentsResult.data;
4958

5059
const handleTemplateValue = (value: any) => {
5160
if (value === null) {
@@ -75,6 +84,19 @@ export async function preparePayload(
7584
});
7685

7786
payload.message = Object.assign(mergeMessage, templateContent);
87+
} else if (payload.message?.attachments !== undefined) {
88+
// Normalize attachments for non-template messages
89+
const attachmentsResult = attachmentsSchema.safeParse(
90+
payload.message.attachments
91+
);
92+
if (!attachmentsResult.success) {
93+
throw new ValidationError(
94+
`Invalid attachments: ${attachmentsResult.error.issues
95+
.map((i) => i.message)
96+
.join(", ")}`
97+
);
98+
}
99+
payload.message.attachments = attachmentsResult.data;
78100
}
79101

80102
let to: string[] = [];

firestore-send-email/functions/src/validation.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,19 @@ export const attachmentSchema = z
5353
});
5454

5555
export const attachmentsSchema = z
56-
.array(attachmentSchema)
57-
.optional()
56+
.preprocess(
57+
// Normalize inputs before validation:
58+
// - null/undefined → undefined (no attachments)
59+
// - single object → wrap in array
60+
// - array → pass through
61+
(val) => {
62+
if (val === undefined || val === null) return undefined;
63+
if (Array.isArray(val)) return val;
64+
if (typeof val === "object") return [val];
65+
return val; // Let validation handle invalid types
66+
},
67+
z.array(attachmentSchema).optional()
68+
)
5869
.transform((attachments) =>
5970
attachments
6071
? attachments.filter((attachment) => Object.keys(attachment).length > 0)

0 commit comments

Comments
 (0)