Description
Describe the feature
smithy-typescript and aws-sdk-js-v3 generate clients with | string
for any enum type in the model. It breaks auto complete, which is annoying, and compile time type checks, which is unsafe.
Use Case
TypeScript simplifies the documented 1 type from ExecutionStatus | string
to string
in error messages, auto complete, and type hints so it's hard to discover the generated enum values from an editor or commandline.
The inclusion of string
in output types prevents me from writing exhaustiveness checks that would fail at compile time if my code did not handle all cases. For example handling step functions execution status1.
// According to aws-sdk docs[^1] the type is ExecutionStatus | string | undefined
// but according to typescript the type is `string | undefined`.
// ExecutionStatus | string is simplified to `string`.
const {status} = await sfn.describeExecution({executionArn})
switch (status) {
case ExecutionStatus.ABORTED:
case ExecutionStatus.FAILED:
case ExecutionStatus.TIMED_OUT:
return false;
case ExecutionStatus.RUNNING:
return undefined;
case ExecutionStatus.SUCCEEDED:
return true;
console.log(`some known status ${status}`)
case undefined:
// https://github.com/aws/aws-sdk-js-v3/issues/1613
throw Error('undefined')
default:
// This is a type error because status could be any string other than the enumerated values we checked above.
assertNever(status) ● Argument of type 'string' is not assignable to parameter of type 'never'.
}
The inclusion of string
in input types allows me to pass invalid values2
await sfn.createStateMachine({
// no type error here :(
type: 'nope',
...args
})
I understand that smithy requires3 clients to allow unmodeled enum values but it does not require the current implementation.
Proposed Solution
My proposal is to replace all | string
with a specific boxed value Unmodeled<string>
. This is inspired by rust aws-sdk4, but obviously typescript doesn't support parameterized enums like they do.
Instead of status: ExecutionStatus | string
(which is simplified to string
) we would have status: ExecutionStatus | Unmodeled<string>
. aws-sdk can ship a isModeled
typeguard which can narrow to only known values, or handle all unknown values.
// just one way to implement the box
const unmodeled = Symobol.for('unmodeled')
interface Unmodeled<T> { value: T, [unmodeled]: true }
function isModeled<T>(value: T | Unmodeled<unknown>) value is T {
return !(unmodeled in value);
}
The sdk would still handle unknown enum values in respondes, but any value not modeled at build time would be wrapped with Unmodeled. Callers could still provide unmodeled values as long as they wrap the value, providing type safty for the majority of uses.
await sfn.createStateMachine({
// no type error here :)
type: Unmodeled('nope'),
...args
})
I think this pattern could be used beyond enums and replace | undefined
and ?
added to inputs. I do not recommend using this pattern for optional outputs #1613, where I prefer optional chaining.
Acknowledgements
- I may be able to implement this feature request
- This feature might incur a breaking change
SDK version used
3.229.0
Environment details (OS name and version, etc.)
N/A
Footnotes
-
https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-sfn/interfaces/describeexecutioncommandoutput.html#status
https://github.com/aws/aws-sdk-js-v3/blob/main/codegen/sdk-codegen/aws-models/sfn.json#L3479 ↩ ↩2 -
https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-sfn/interfaces/createstatemachinecommandinput.html#type
https://github.com/aws/aws-sdk-js-v3/blob/main/codegen/sdk-codegen/aws-models/sfn.json#L5890 ↩ -
Enums are considered open, meaning it is a backward compatible change to add new members. Previously generated clients MUST NOT fail when they encounter an unknown enum value. Client implementations MUST provide the capability of sending and receiving unknown enum values.
https://smithy.io/2.0/spec/simple-types.html#enum-is-a-specialization-of-string -
https://docs.rs/aws-sdk-sfn/latest/aws_sdk_sfn/model/enum.ExecutionStatus.html ↩