[英]How to type a function argument with a function argument type in a nested object in TypeScript?
I'm trying to type a function where an argument type should be inferred from a value in a nested object. 我正在尝试键入一个函数,该函数应从嵌套对象中的值推断出参数类型。 How do I infer the function arguments type that is inside a deeply nested object?
如何推断深层嵌套对象内部的函数参数类型?
Example: 例:
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;
};
Given a specific role
and ability
, I want to type data
in can()
to be the function arguments type defined in ABILITIES
if it exists. 给定特定的
role
和ability
,我想在can()
键入data
,以将其作为ABILITIES
定义的函数参数类型(如果存在)。 If it doesn't exist, then I don't want data
to be required. 如果它不存在,那么我就不需要
data
。
I expect the type of data
to be a required type of { currentUserId: string; userId: string }
我希望
data
类型是{ currentUserId: string; userId: string }
{ currentUserId: string; userId: string }
, when role is Role.USER
and ability is 'users:edit'
. { currentUserId: string; userId: string }
,当角色是Role.USER
和能力是'users:edit'
。
You can do some conditional type magic to extract the appropriate parameter types provided ABILITIES
is typed with the actual type of the object literal (not just Abilities
). 您可以做一些条件类型魔术来提取适当的参数类型,前提是必须使用对象文字的实际类型(不仅仅是
Abilities
)来键入ABILITIES
。 We can use an extra function to help the compiler infer the correct type. 我们可以使用一个额外的功能来帮助编译器推断正确的类型。
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.