-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: assertive client type helper (#1076)
- Loading branch information
Showing
5 changed files
with
225 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@smithy/types": minor | ||
--- | ||
|
||
add type helper for nullability in clients |
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 |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import type { Client } from "../client"; | ||
import type { HttpHandlerOptions } from "../http"; | ||
import type { MetadataBearer } from "../response"; | ||
import type { Exact } from "./exact"; | ||
import type { AssertiveClient, NoUndefined, UncheckedClient } from "./no-undefined"; | ||
|
||
type A = { | ||
a: string; | ||
b: number | string; | ||
c: boolean | number | string; | ||
required: string | undefined; | ||
optional?: string; | ||
nested: A; | ||
}; | ||
|
||
{ | ||
// it should remove undefined union from required fields. | ||
type T = NoUndefined<A>; | ||
|
||
const assert1: Exact<T["required"], string> = true as const; | ||
const assert2: Exact<T["nested"]["required"], string> = true as const; | ||
const assert3: Exact<T["nested"]["nested"]["required"], string> = true as const; | ||
} | ||
|
||
{ | ||
type MyInput = { | ||
a: string | undefined; | ||
b: number | undefined; | ||
c: string | number | undefined; | ||
optional?: string; | ||
}; | ||
|
||
type MyOutput = { | ||
a?: string; | ||
b?: number; | ||
c?: string | number; | ||
r?: MyOutput; | ||
} & MetadataBearer; | ||
|
||
type MyConfig = { | ||
version: number; | ||
}; | ||
|
||
interface MyClient extends Client<MyInput, MyOutput, MyConfig> { | ||
getObject(args: MyInput, options?: HttpHandlerOptions): Promise<MyOutput>; | ||
getObject(args: MyInput, cb: (err: any, data?: MyOutput) => void): void; | ||
getObject(args: MyInput, options: HttpHandlerOptions, cb: (err: any, data?: MyOutput) => void): void; | ||
|
||
putObject(args: MyInput, options?: HttpHandlerOptions): Promise<MyOutput>; | ||
putObject(args: MyInput, cb: (err: any, data?: MyOutput) => void): void; | ||
putObject(args: MyInput, options: HttpHandlerOptions, cb: (err: any, data?: MyOutput) => void): void; | ||
} | ||
|
||
{ | ||
// AssertiveClient should enforce union of undefined on inputs | ||
// but preserve undefined outputs. | ||
const c = (null as unknown) as AssertiveClient<MyClient>; | ||
const input = { | ||
a: "", | ||
b: 0, | ||
c: 0, | ||
}; | ||
const get = c.getObject(input); | ||
const output = (null as unknown) as Awaited<typeof get>; | ||
|
||
const assert1: Exact<typeof output.a, string | undefined> = true as const; | ||
const assert2: Exact<typeof output.b, number | undefined> = true as const; | ||
const assert3: Exact<typeof output.c, string | number | undefined> = true as const; | ||
if (output.r) { | ||
const assert4: Exact<typeof output.r.a, string | undefined> = true as const; | ||
const assert5: Exact<typeof output.r.b, number | undefined> = true as const; | ||
const assert6: Exact<typeof output.r.c, string | number | undefined> = true as const; | ||
} | ||
} | ||
|
||
{ | ||
// UncheckedClient both removes union-undefined from inputs | ||
// and the nullability of outputs. | ||
const c = (null as unknown) as UncheckedClient<MyClient>; | ||
const input = { | ||
a: "", | ||
b: 0, | ||
c: 0, | ||
}; | ||
const get = c.getObject(input); | ||
const output = (null as unknown) as Awaited<typeof get>; | ||
|
||
const assert1: Exact<typeof output.a, string> = true as const; | ||
const assert2: Exact<typeof output.b, number> = true as const; | ||
const assert3: Exact<typeof output.c, string | number> = true as const; | ||
const assert4: Exact<typeof output.r.a, string> = true as const; | ||
const assert5: Exact<typeof output.r.b, number> = true as const; | ||
const assert6: Exact<typeof output.r.c, string | number> = true as const; | ||
} | ||
} |
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,83 @@ | ||
import type { InvokeFunction, InvokeMethod } from "../client"; | ||
|
||
/** | ||
* @public | ||
* | ||
* This type is intended as a type helper for generated clients. | ||
* When initializing client, cast it to this type by passing | ||
* the client constructor type as the type parameter. | ||
* | ||
* It will then recursively remove "undefined" as a union type from all | ||
* input and output shapes' members. Note, this does not affect | ||
* any member that is optional (?) such as outputs with no required members. | ||
* | ||
* @example | ||
* ```ts | ||
* const client = new Client({}) as AssertiveClient<Client>; | ||
* ``` | ||
*/ | ||
export type AssertiveClient<Client extends object> = NarrowClientIOTypes<Client>; | ||
|
||
/** | ||
* @public | ||
* | ||
* This is similar to AssertiveClient but additionally changes all | ||
* output types to (recursive) Required<T> so as to bypass all output nullability guards. | ||
*/ | ||
export type UncheckedClient<Client extends object> = UncheckedClientOutputTypes<Client>; | ||
|
||
/** | ||
* @internal | ||
* | ||
* Excludes undefined recursively. | ||
*/ | ||
export type NoUndefined<T> = T extends Function | ||
? T | ||
: [T] extends [object] | ||
? { | ||
[key in keyof T]: NoUndefined<T[key]>; | ||
} | ||
: Exclude<T, undefined>; | ||
|
||
/** | ||
* @internal | ||
* | ||
* Excludes undefined and optional recursively. | ||
*/ | ||
export type RecursiveRequired<T> = T extends Function | ||
? T | ||
: [T] extends [object] | ||
? { | ||
[key in keyof T]-?: RecursiveRequired<T[key]>; | ||
} | ||
: Exclude<T, undefined>; | ||
|
||
/** | ||
* @internal | ||
* | ||
* Removes undefined from unions. | ||
*/ | ||
type NarrowClientIOTypes<ClientType extends object> = { | ||
[key in keyof ClientType]: [ClientType[key]] extends [ | ||
InvokeFunction<infer InputTypes, infer OutputTypes, infer ConfigType> | ||
] | ||
? InvokeFunction<NoUndefined<InputTypes>, NoUndefined<OutputTypes>, ConfigType> | ||
: [ClientType[key]] extends [InvokeMethod<infer FunctionInputTypes, infer FunctionOutputTypes>] | ||
? InvokeMethod<NoUndefined<FunctionInputTypes>, NoUndefined<FunctionOutputTypes>> | ||
: ClientType[key]; | ||
}; | ||
|
||
/** | ||
* @internal | ||
* | ||
* Removes undefined from unions and adds yolo output types. | ||
*/ | ||
type UncheckedClientOutputTypes<ClientType extends object> = { | ||
[key in keyof ClientType]: [ClientType[key]] extends [ | ||
InvokeFunction<infer InputTypes, infer OutputTypes, infer ConfigType> | ||
] | ||
? InvokeFunction<NoUndefined<InputTypes>, RecursiveRequired<OutputTypes>, ConfigType> | ||
: [ClientType[key]] extends [InvokeMethod<infer FunctionInputTypes, infer FunctionOutputTypes>] | ||
? InvokeMethod<NoUndefined<FunctionInputTypes>, RecursiveRequired<FunctionOutputTypes>> | ||
: ClientType[key]; | ||
}; |