🌱Planted
February 16, 2024.
seedling 🌱
2 minutes read ⏱
When working with union types, I often want to narrow the type to something specific. One way to do this is using assertion functions which assert that after they’ve been invoked, I can be certain that the type is what was asserted. The following assertion function will assert that a user is an admin:
function function assertIsAdmin(user: NormalUser | AdminUser): asserts user is AdminUser
assertIsAdmin(user: NormalUser | AdminUser
user: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser): asserts user: NormalUser | AdminUser
user is type AdminUser = User & {
role: "admin";
}
AdminUser {
if( "admin" !== user: NormalUser | AdminUser
user.role: "user" | "admin"
role ) throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error("user is not an Admin");
}
// Notice the arrow function syntax here
const const assertIsAdmin2: (user: NormalUser | AdminUser) => asserts user is AdminUser
assertIsAdmin2 = (user: NormalUser | AdminUser
user: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser): asserts user: NormalUser | AdminUser
user is type AdminUser = User & {
role: "admin";
}
AdminUser => {
if( "admin" !== user: NormalUser | AdminUser
user.role: "user" | "admin"
role ) throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error("user is not an Admin");
}
function function adminOnly(user: NormalUser | AdminUser): void
adminOnly(user: NormalUser | AdminUser
user: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser){
assertIsAdmin2(user: NormalUser | AdminUser
user);}
function function deleteOrganization(user: AdminUser, organizationId: string): void
deleteOrganization(user: AdminUser
user: type AdminUser = User & {
role: "admin";
}
AdminUser, organizationId: string
organizationId: string){
// ...
}
function function attemptDeleteOrganization(user: NormalUser | AdminUser, organizationId: string): void
attemptDeleteOrganization(user: NormalUser | AdminUser
user: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser, organizationId: string
organizationId: string){
function assertIsAdmin(user: NormalUser | AdminUser): asserts user is AdminUser
assertIsAdmin(user: NormalUser | AdminUser
user);
function deleteOrganization(user: AdminUser, organizationId: string): void
deleteOrganization(user: AdminUser
user, organizationId: string
organizationId);
}
assertIsAdmin
asserts that the user
is an AdminUser
. There are a couple things to note here:
- The
function
keyword is required. Notice that usingassertIsAdmin2
, the version with an arrow function, causes an error. This is because of an implementation detail of the compiler; the control flow graph is computed before type inferencing. - Any code that sits below a call to
assertIsAdmin
will have the type ofuser
narrowed toAdminUser
.
In attemptDeleteOrganization
, you can see that after the call to assertIsAdmin
, the user
’s type is narrowed to AdminUser
.