Z
Zod2w ago
versace

versace - hey!I am currently investigating wh...

hey! I am currently investigating what the best approach for using header validation through zod would be in regards to key case sensitivity. i'm currently in a situation where I need to have support for all headers to be supported regardless of how the casing looks when sent in from the request. any suggestions?
Solution:
Do you need to preserve the case of the incoming headers? You could transform them at runtime with .toLowerCase() and then build your schema on the lowercased keys.
Jump to solution
6 Replies
Scott Trinh
Scott Trinh2w ago
oh, that's a good one I haven't seen before!
const headerKeys = new Set(["accept", "content-type", "etc"]);

const caseInsensitiveString = z.string().pipe(z.custom<string>((s) => {
const si = (s as string).toLowerCase();
return headerKeys.has(si);
}));
const headerKeys = new Set(["accept", "content-type", "etc"]);

const caseInsensitiveString = z.string().pipe(z.custom<string>((s) => {
const si = (s as string).toLowerCase();
return headerKeys.has(si);
}));
I don't love it but it seems fine. you could also use a case-insensitive regex that you build a runtime like:
const headerKeys = ["accept", "content-type", "etc"];
const caseInsensitiveString = z.string().regex(new RegExp(headerKeys.join("|"), "i"));
const headerKeys = ["accept", "content-type", "etc"];
const caseInsensitiveString = z.string().regex(new RegExp(headerKeys.join("|"), "i"));
I like this even less, but at least you get a ZodString instead of a ZodPipeline
versace
versaceOP2w ago
interesting, the way it works for us today, is that we define the full object/schema of which headers exist, and then parse it as the first action in the request parsing flow.
const commonHeaders = {
[Api.requestHeaders.cookie]: z.string().includes(Api.csrf.cookieName),
[Api.csrf.headername]: z.string(),
};

// example usage
const headerSchema = z.object({
...commonHeaders,
[Api.requestHeaders.authorization]: z.string(),
});
const commonHeaders = {
[Api.requestHeaders.cookie]: z.string().includes(Api.csrf.cookieName),
[Api.csrf.headername]: z.string(),
};

// example usage
const headerSchema = z.object({
...commonHeaders,
[Api.requestHeaders.authorization]: z.string(),
});
the problem here is that the keys are by default sensitive inside of z.object, how would your solutions be applied here?
Scott Trinh
Scott Trinh2w ago
Headers are always Record-like structures, right? Always Record<string, string>, so you could use z.record with this custom string schema as the key schema. But, looking at your implementation, you want to make key-specific assertions, so that won't help much.
Solution
Scott Trinh
Scott Trinh2w ago
Do you need to preserve the case of the incoming headers? You could transform them at runtime with .toLowerCase() and then build your schema on the lowercased keys.
Scott Trinh
Scott Trinh2w ago
I guess for a record-like structure, you'd actually want to transform the keys to lowercase when parsing rather than just checking against lowercase, that way the object you get back on the other end is already normalized to lowercase. Might be able to take some inspiration from zod-form-data (see https://github.com/airjp73/rvf/blob/main/packages/zod-form-data/src/helpers.ts) and build a preprocess pipeline for turning Header objects into plain objects.
versace
versaceOP2w ago
we decided to go the route of just lower casing the whole header object that is retrieved from the request, it didn't seem like any other option had good code maintenance experience. thanks for your suggestions!

Did you find this page helpful?