🌱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 AdminUserassertIsAdmin(user: NormalUser | AdminUseruser: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser): asserts user: NormalUser | AdminUseruser is type AdminUser = User & {
role: "admin";
}
AdminUser {
if( "admin" !== user: NormalUser | AdminUseruser.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 AdminUserassertIsAdmin2 = (user: NormalUser | AdminUseruser: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser): asserts user: NormalUser | AdminUseruser is type AdminUser = User & {
role: "admin";
}
AdminUser => {
if( "admin" !== user: NormalUser | AdminUseruser.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): voidadminOnly(user: NormalUser | AdminUseruser: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser){
assertIsAdmin2(user: NormalUser | AdminUseruser);}
function function deleteOrganization(user: AdminUser, organizationId: string): voiddeleteOrganization(user: AdminUseruser: type AdminUser = User & {
role: "admin";
}
AdminUser, organizationId: stringorganizationId: string){
// ...
}
function function attemptDeleteOrganization(user: NormalUser | AdminUser, organizationId: string): voidattemptDeleteOrganization(user: NormalUser | AdminUseruser: type NormalUser = User & {
role: "user";
}
NormalUser | type AdminUser = User & {
role: "admin";
}
AdminUser, organizationId: stringorganizationId: string){
function assertIsAdmin(user: NormalUser | AdminUser): asserts user is AdminUserassertIsAdmin(user: NormalUser | AdminUseruser);
function deleteOrganization(user: AdminUser, organizationId: string): voiddeleteOrganization(user: AdminUseruser, organizationId: stringorganizationId);
} assertIsAdmin asserts that the user is an AdminUser. There are a couple things to note here:
- The
functionkeyword 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
assertIsAdminwill have the type ofusernarrowed toAdminUser.
In attemptDeleteOrganization, you can see that after the call to assertIsAdmin, the user’s type is narrowed to AdminUser.