-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #148 from supercharge/header-bag-refactoring
HeaderBag extends InputBag refactoring
- Loading branch information
Showing
13 changed files
with
286 additions
and
234 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
|
||
import { IncomingHttpHeaders } from 'http2' | ||
|
||
// copy every declared property from http.IncomingHttpHeaders | ||
// but remove index signatures | ||
|
||
/** | ||
* This type copies over all properties from the `IncomingHttpHeaders` type | ||
* except the index signature. The index signature is nice to use custom | ||
* HTTP headers, but it throws away IntelliSense which we want to keep. | ||
*/ | ||
export type HttpDefaultRequestHeaders = { | ||
[K in keyof IncomingHttpHeaders as string extends K | ||
? never | ||
: number extends K | ||
? never | ||
: K | ||
]: IncomingHttpHeaders[K]; | ||
} | ||
|
||
export type HttpDefaultRequestHeader = keyof HttpDefaultRequestHeaders | ||
|
||
/** | ||
* This `HttpRequestHeaders` interface can be used to extend the default | ||
* HTTP headers with custom header key-value pairs. The HTTP request | ||
* picks up the custom headers and keeps IntelliSense for the dev. | ||
* | ||
* You can extend this interface in your code like this: | ||
* | ||
* @example | ||
* | ||
* ```ts | ||
* declare module '@supercharge/contracts' { | ||
* export interface HttpRequestHeaders { | ||
* 'your-header-name': string | undefined | ||
* } | ||
* } | ||
* ``` | ||
*/ | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface HttpRequestHeaders extends HttpDefaultRequestHeaders { | ||
// | ||
} | ||
|
||
export type HttpRequestHeader = keyof HttpRequestHeaders |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,55 @@ | ||
|
||
import { tap } from '@supercharge/goodies' | ||
import { RouterContext } from '@koa/router' | ||
import { IncomingHttpHeaders } from 'node:http' | ||
import { Dict, RequestHeaderBag as RequestHeaderBagContract } from '@supercharge/contracts' | ||
|
||
export class RequestHeaderBag implements RequestHeaderBagContract { | ||
/** | ||
* Stores the request headers as an object. | ||
*/ | ||
private readonly ctx: RouterContext | ||
|
||
/** | ||
* Create a new instance. | ||
*/ | ||
constructor (ctx: RouterContext) { | ||
this.ctx = ctx | ||
} | ||
import { InputBag } from './input-bag.js' | ||
|
||
export class RequestHeaderBag<RequestHeaders> extends InputBag<RequestHeaders> { | ||
/** | ||
* Returns the lowercased string value for the given `name`. | ||
*/ | ||
private resolveName (name: keyof IncomingHttpHeaders): string { | ||
return String(name).toLowerCase() | ||
} | ||
|
||
/** | ||
* Returns an object with all `keys` existing in the input bag. | ||
*/ | ||
all<Key extends keyof IncomingHttpHeaders = string> (...keys: Key[] | Key[][]): { [Key in keyof IncomingHttpHeaders]: IncomingHttpHeaders[Key] } { | ||
if (keys.length === 0) { | ||
return this.ctx.headers | ||
} | ||
|
||
return ([] as Key[]) | ||
.concat(...keys) | ||
.map(name => this.resolveName(name)) | ||
.reduce((carry: Dict<IncomingHttpHeaders[Key]>, key) => { | ||
carry[key] = this.get(key) | ||
|
||
return carry | ||
}, {}) | ||
private lowercase<Key extends keyof RequestHeaders = any> (name: string | Key): Key { | ||
return String(name).toLowerCase() as Key | ||
} | ||
|
||
/** | ||
* Returns the input value for the given `name`. Returns `undefined` | ||
* if the given `name` does not exist in the input bag. | ||
*/ | ||
get<Header extends keyof IncomingHttpHeaders> (name: Header): IncomingHttpHeaders[Header] | ||
get<T, Header extends keyof IncomingHttpHeaders> (name: Header, defaultValue: T): IncomingHttpHeaders[Header] | T | ||
get<T, Header extends keyof IncomingHttpHeaders> (name: Header, defaultValue?: T): IncomingHttpHeaders[Header] | T { | ||
const key = this.resolveName(name) | ||
|
||
override get<Value = any, Key extends keyof RequestHeaders = any> (key: Key, defaultValue?: Value): RequestHeaders[Key] | Value | undefined { | ||
switch (key) { | ||
case 'referrer': | ||
case 'referer': | ||
return this.ctx.request.headers.referrer ?? this.ctx.request.headers.referer ?? defaultValue | ||
return super.get('referrer' as Key) ?? super.get('referer' as Key) ?? defaultValue | ||
|
||
default: | ||
return this.ctx.request.headers[key] ?? defaultValue | ||
return super.get(this.lowercase<Key>(key), defaultValue) | ||
} | ||
} | ||
|
||
/** | ||
* Set an input for the given `name` and assign the `value`. This | ||
* overrides a possibly existing input with the same `name`. | ||
*/ | ||
set (name: string, value: any): this { | ||
const key = this.resolveName(name) | ||
|
||
return tap(this, () => { | ||
this.ctx.request.headers[key] = value | ||
}) | ||
} | ||
|
||
/** | ||
* Removes the input with the given `name`. | ||
*/ | ||
remove (name: string): this { | ||
const key = this.resolveName(name) | ||
override set<Key extends keyof RequestHeaders> (key: Key | Partial<RequestHeaders>, value?: any): this { | ||
if (this.isObject(key)) { | ||
const values = Object.entries(key).reduce<Partial<RequestHeaders>>((carry, [key, value]) => { | ||
const id = this.lowercase<Key>(key) | ||
// @ts-expect-error | ||
carry[id] = value | ||
|
||
return tap(this, () => { | ||
const { [key]: _, ...rest } = this.ctx.request.headers | ||
return carry | ||
}, {}) | ||
|
||
this.ctx.request.headers = rest | ||
}) | ||
} | ||
return super.set(values) | ||
} | ||
|
||
/** | ||
* Determine whether the HTTP header for the given `name` exists. | ||
*/ | ||
has (name: keyof IncomingHttpHeaders): name is keyof IncomingHttpHeaders { | ||
return !!this.get(name) | ||
return super.set(this.lowercase(key), value) | ||
} | ||
|
||
/** | ||
* Returns an object containing all parameters. | ||
* Removes the input with the given `name`. | ||
*/ | ||
toJSON (): Partial<IncomingHttpHeaders> { | ||
return this.all() | ||
override remove<Key extends keyof RequestHeaders> (key: Key): this { | ||
return super.remove( | ||
this.lowercase(key) | ||
) | ||
} | ||
} |
Oops, something went wrong.