我正在尝试键入一个函数,该函数应从嵌套对象中的值推断出参数类型。如何推断深层嵌套对象内部的函数参数类型?
示例:
export enum Role {
USER = 'user',
ADMIN = 'admin',
OWNER = 'owner',
PRIMARY_OWNER = 'primaryOwner',
}
// Add as needed. Formatted as 'resource:action'?
export type Ability =
| 'users:create'
| 'users:edit'
| 'reports:view'
| 'settings:view';
type StaticAbilities = readonly Ability[];
type DynamicAbility = (data: any) => boolean;
type DynamicAbilities = { readonly [key in Ability]?: DynamicAbility };
export type Abilities = {
readonly [R in Role]?: {
readonly static?: StaticAbilities;
readonly dynamic?: DynamicAbilities;
}
};
/**
* A configuration object containing allowed abilities for specific roles.
*/
export const ABILITIES: Abilities = {
user: {
dynamic: {
// THIS IS AN EXAMPLE OF DYNAMIC RULES
'users:edit': ({
currentUserId,
userId,
}: {
/** Current users ID */
currentUserId: string;
/** User ID trying to be edited */
userId: string;
}) => {
if (!currentUserId || !userId) return false;
return currentUserId === userId;
},
},
},
admin: {
static: ['reports:view', 'settings:view'],
},
owner: {
static: ['reports:view', 'settings:view'],
},
primaryOwner: {
static: ['reports:view', 'settings:view'],
},
};
export const can = ({
role,
ability,
data,
}: {
role: Role;
ability: Ability;
data?: any;
}): boolean => {
const permissions = ABILITIES[role];
// Return false if role not present in rules.
if (!permissions) {
return false;
}
const staticPermissions = permissions.static;
// Return true if rule is in role's static permissions.
if (staticPermissions && staticPermissions.includes(ability)) {
return true;
}
const dynamicPermissions = permissions.dynamic;
if (dynamicPermissions) {
const permissionCondition = dynamicPermissions[ability];
// No rule was found in dynamic permissions.
if (!permissionCondition) {
return false;
}
return permissionCondition(data);
}
// Default to false.
return false;
};
给定特定的role
和ability
,我想在data
中键入can()
作为ABILITIES
中定义的函数参数类型(如果存在)。如果它不存在,那么我就不需要data
。
当角色为data
而能力为{ currentUserId: string; userId: string }
时,我希望Role.USER
的类型是'users:edit'
的必需类型。
答案 0 :(得分:2)
您可以做一些条件类型魔术来提取适当的参数类型,只要ABILITIES
是用对象文字的实际类型(不仅仅是Abilities
)键入的。我们可以使用额外的功能来帮助编译器推断正确的类型。
export enum Role {
USER = 'user',
ADMIN = 'admin',
OWNER = 'owner',
PRIMARY_OWNER = 'primaryOwner',
}
// Add as needed. Formatted as 'resource:action'?
export type Ability =
| 'users:create'
| 'users:edit'
| 'reports:view'
| 'settings:view';
type StaticAbilities = readonly Ability[];
type DynamicAbility = (data: any) => boolean;
type DynamicAbilities = { readonly [key in Ability]?: DynamicAbility };
export type Abilities = {
readonly [R in Role]?: {
readonly static?: StaticAbilities;
readonly dynamic?: DynamicAbilities;
}
};
function createAbilities<A extends Abilities>(a: A) {
return a;
}
export const ABILITIES = createAbilities({
user: {
dynamic: {
// THIS IS AN EXAMPLE OF DYNAMIC RULES
'users:edit': ({
currentUserId,
userId,
}: {
/** Current users ID */
currentUserId: string;
/** User ID trying to be edited */
userId: string;
}) => {
if (!currentUserId || !userId) return false;
return currentUserId === userId;
},
},
},
admin: {
static: ['reports:view', 'settings:view'],
},
owner: {
static: ['reports:view', 'settings:view'],
},
primaryOwner: {
static: ['reports:view', 'settings:view'],
},
});
type ExtractDynamicParameter<R extends Role, A extends Ability> = typeof ABILITIES[R] extends { dynamic: Record<A, (p: infer P) => boolean> } ? { data : P } : { data?: undefined}
export const can = <R extends Role, A extends Ability>({
role,
ability,
data,
}: {
role: R;
ability: A;
} & ExtractDynamicParameter<R, A>): boolean => {
const permissions = ABILITIES[role as Role] as Abilities[Role]; // Needed assertions
// Return false if role not present in rules.
if (!permissions) {
return false;
}
const staticPermissions = permissions.static;
// Return true if rule is in role's static permissions.
if (staticPermissions && staticPermissions.includes(ability)) {
return true;
}
const dynamicPermissions = permissions.dynamic;
if (dynamicPermissions) {
const permissionCondition = dynamicPermissions[ability];
// No rule was found in dynamic permissions.
if (!permissionCondition) {
return false;
}
return permissionCondition(data);
}
// Default to false.
return false;
};
can({ role: Role.USER, ability: "users:edit", data: { currentUserId: "", userId: "" } }) // ok
can({ role: Role.USER, ability: "users:edit", data: {} }) // err
can({ role: Role.USER, ability: "users:edit" }) // err