I have a function where some of the input's properties depend on the value of one of its props. So I've defined a type as follow, and it works as expected:
enum OrganizationPermission {
UPDATE = 'organization:update',
INVITE = 'organization:invite',
}
enum WorkspacePermission {
UPDATE = 'workspace:update',
INVITE = 'workspace:invite',
}
enum OrganizationRole {
MANAGER = 'manager'
}
enum WorkspaceRole {
MANAGER = 'manager'
}
type RolePermission = {
[OrganizationPermission.UPDATE]: {
organizationRole: OrganizationRole,
},
[OrganizationPermission.INVITE]: {
organizationRole: OrganizationRole,
},
[WorkspacePermission.UPDATE]: {
workspaceRole: WorkspaceRole,
},
}
type Permissions = WorkspacePermission | OrganizationPermission;
type CheckPermissionsArgs<P extends Permissions> = {
perform: P,
sustain?: boolean,
} & (P extends keyof RolePermission ? RolePermission[P] : Record<string, never>);
function checkPermissions<P extends Permissions>(props: CheckPermissionsArgs<P>): void {
// omitted for brevity
}
The problem arises when I have to extend this props, like in the following example:
type CheckRolePermissionsArgs<P extends Permissions> = CheckPermissionsArgs<P> & {
role: string[],
}
function checkRolePermissions<P extends Permissions>({role, ...props}: CheckRolePermissionsArgs<P>): void {
// Typing error
checkPermissions(props);
}
Typescript complains that:
TS2345: Argument of type 'Pick<CheckRolePermissionsArgs
, "perform" | "sustain" | Exclude<keyof (P extends OrganizationPermission | WorkspacePermission.UPDATE? RolePermission[P]: Record<...>), "role">>' is not assignable to parameter of type 'CheckPermissionsArgs<CheckRolePermissionsArgs
["perform"]>'. Type 'Pick<CheckRolePermissionsArgs
, "perform" | "sustain" | Exclude<keyof (P extends OrganizationPermission | WorkspacePermission.UPDATE? RolePermission[P]: Record<...>), "role">>' is not assignable to type 'CheckRolePermissionsArgs
["perform"] extends OrganizationPermission | WorkspacePermission.UPDATE? RolePermission[CheckRolePermissionsArgs<...>["perform"]]: Record<...>'
How can I refactor these typings to avoid casting the props
argument in the second example?
I think this is because of contravariance , but it is only my guess. Feel free to criticize me.
See this answer.
Consider next example:
/**
* VARIANCE
*/
declare var y: Omit<CheckRolePermissionsArgs<WorkspacePermission.UPDATE>, 'role'>
let x: CheckPermissionsArgs<WorkspacePermission.UPDATE> = y // ok
y = x // ok
checkPermissions(x)
Since, checkRolePermissions
is higher order function:
function checkRolePermissions<P extends Permissions_>(props: CheckRolePermissionsArgs<P>): void {
// Typing error
const { role, ...rest } = props
type Keys = keyof typeof rest // "perform" | "sustain" | Exclude<keyof (P extends keyof RolePermission ? RolePermission[P] : Record<string, never>), "role">
checkPermissions(rest);
}
TS is unable to figure out rest
type because it is kinda dynamic type in this place. It is only known in runtime.
I think the best solution here to split CheckPermissionsArgs
type.
enum OrganizationPermission {
UPDATE = 'organization:update',
INVITE = 'organization:invite',
}
enum WorkspacePermission {
UPDATE = 'workspace:update',
INVITE = 'workspace:invite',
}
enum OrganizationRole {
MANAGER = 'manager'
}
enum WorkspaceRole {
MANAGER = 'manager'
}
type RolePermission = {
[OrganizationPermission.UPDATE]: {
organizationRole: OrganizationRole,
},
[OrganizationPermission.INVITE]: {
organizationRole: OrganizationRole,
},
[WorkspacePermission.UPDATE]: {
workspaceRole: WorkspaceRole,
}
}
type Permissions_ = WorkspacePermission | OrganizationPermission;
type CheckPermissionsArgs<P extends Permissions_> = {
perform: P,
sustain?: boolean,
};
type Union<P extends Permissions_> = P extends keyof RolePermission ? RolePermission[P] : Record<string, never>
function checkPermissions<P extends Permissions_>(props: CheckPermissionsArgs<P>, data: Union<P>) {
// this object has exactly the same type as you have in your question
const merged = { ...props, ...data }
}
type CheckRolePermissionsArgs<P extends Permissions_> = CheckPermissionsArgs<P> & {
role: string[],
}
function checkRolePermissions<P extends Permissions_>({ role, ...rest }: CheckRolePermissionsArgs<P>) {
// Now, TS is able to infer the type
return (data: Union<P>) => checkPermissions(rest, data);
}
checkRolePermissions({
perform: OrganizationPermission.UPDATE,
sustain: true,
role: ['sdf']
})({
organizationRole: OrganizationRole.MANAGER,
}) // ok
checkRolePermissions({
perform: WorkspacePermission.INVITE,
sustain: true,
role: ['sdf']
})({
organizationRole: OrganizationRole.MANAGER,
}) // expected error
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.