如何在TypeScript中的嵌套对象中键入具有函数参数类型的函数参数?

如何在TypeScript中的嵌套对象中键入具有函数参数类型的函数参数?,typescript,Typescript,我试图键入一个函数,其中参数类型应该从嵌套对象中的值推断出来。如何推断深度嵌套对象中的函数参数类型 例如: export enum Role { USER = 'user', ADMIN = 'admin', OWNER = 'owner', PRIMARY_OWNER = 'primaryOwner', } // Add as needed. Formatted as 'resource:action'? export type Ability = | 'users:cr

我试图键入一个函数,其中参数类型应该从嵌套对象中的值推断出来。如何推断深度嵌套对象中的函数参数类型

例如:

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;
};
给定一个特定的
角色
能力
,我想在
can()
中键入
数据
,作为
能力
中定义的函数参数类型(如果存在)。如果它不存在,那么我不希望需要
数据


我希望
数据的类型
{currentUserId:string;userId:string}
的必需类型,当角色为
role.USER
且能力为
'users:edit'
时,您可以执行一些条件类型魔术来提取适当的参数类型,前提是
能力
是使用对象文本的实际类型键入的(不仅仅是
能力
)。我们可以使用一个额外的函数来帮助编译器推断正确的类型

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
导出枚举角色{
用户='USER',
ADMIN='ADMIN',
所有者='所有者',
主要所有者='primaryOwner',
}
//根据需要添加。是否格式化为“资源:操作”?
出口型能力=
|“用户:创建”
|'用户:编辑'
|“报告:视图”
|'设置:查看';
类型StaticAbilities=只读能力[];
类型DynamicAbility=(数据:any)=>boolean;
类型DynamicAbility={readonly[输入能力]?:DynamicAbility};
导出类型能力={
只读[R在角色中]?:{
只读静态?:静态能力;
只读动态?:动态能力;
}
};
函数createAbilities(a:a){
返回a;
}
导出常量能力=创建能力({
用户:{
动态:{
//这是一个动态规则的示例
“用户:编辑”:({
当前用户ID,
用户ID,
}: {
/**当前用户ID*/
currentUserId:字符串;
/**试图编辑的用户ID*/
userId:string;
}) => {
如果(!currentUserId | |!userId)返回false;
return currentUserId==用户ID;
},
},
},
管理员:{
静态:[“报告:视图”,“设置:视图”],
},
所有者:{
静态:[“报告:视图”,“设置:视图”],
},
主要所有者:{
静态:[“报告:视图”,“设置:视图”],
},
});
type ExtractDynamicParameter=typeof能力[R]扩展了{dynamic:Record boolean>}?{data:P}:{data?:未定义}
导出常量can=({
角色
能力,,
数据,
}: {
角色:R;
能力:A;
}&ExtractDynamicParameter):布尔=>{
const permissions=ABILITIES[role as role]as ABILITIES[role];//需要的断言
//如果规则中不存在角色,则返回false。
如果(!权限){
返回false;
}
const staticPermissions=permissions.static;
//如果规则处于角色的静态权限中,则返回true。
if(staticPermissions&&staticPermissions.includes(能力)){
返回true;
}
const dynamicPermissions=permissions.dynamic;
if(动态许可证){
常量许可条件=动态许可[能力];
//在动态权限中找不到规则。
如果(!许可条件){
返回false;
}
返回许可条件(数据);
}
//默认为false。
返回false;
};
can({role:role.USER,能力:“users:edit”,数据:{currentUserId:,userId:}})//确定
can({role:role.USER,能力:“用户:编辑”,数据:{})//err
can({role:role.USER,ability:“users:edit”})//错误

我想让
可以({role:role.USER,ability:'users:edit'})因数据丢失而失败。“我想这样做的方法是做某种函数重载?”埃里克·泰勒改变了答案,使之与上面的请求一样工作。