is there any way to know which criteria is not met on conditions? #993
-
I'm using current behavior: try{
// ...create abilities from { authorId: 1, count: { $lte: 2 } }
ForbiddenError.from(abilities).throwUnlessCan(
'create',
new Post({ authorId: 1, count: 3 }),
);
}
catch(e){
console.log(e) // just says it can't be created
} expected behavior: try{
// ...create abilities from { authorId: 1, count: { $lte: 2 } }
ForbiddenError.from(abilities).throwUnlessCan(
'create',
new Post({ authorId: 1, count: 3 }),
);
}
catch(e){
console.log(e) // can't create due to count being greater than allowed
} |
Beta Was this translation helpful? Give feedback.
Answered by
stalniy
Nov 27, 2024
Replies: 2 comments 1 reply
-
No there is no way to know this at the moment |
Beta Was this translation helpful? Give feedback.
1 reply
-
@stalniy I've just achieved that using the following code. I'm wondering if this is a bad idea. would you mind telling me what are your thoughts, once you've got enough time? import { Query } from 'mingo';
interface UnmetCondition {
field: string;
expected: Record<string, any>;
actual: any;
}
export function findUnmetConditions(
object: Record<string, any>,
conditions: Record<string, any>,
): UnmetCondition[] {
return Object.entries(conditions).flatMap(([field, condition]) => {
const actualValue = object[field];
// Check if the condition is an operator object
if (typeof condition === 'object' && !Array.isArray(condition)) {
return Object.entries(condition).flatMap(([operator, expectedValue]) => {
if (!operatorHandlers[operator]) {
throw new Error(`Unsupported operator: ${operator}`);
}
const isMatch = operatorHandlers[operator](actualValue, expectedValue);
if (!isMatch) {
return [
{
field,
expected: { [operator]: expectedValue },
actual: actualValue,
},
];
}
return [];
});
}
// Handle direct equality for simple fields
if (actualValue !== condition) {
return [{ field, expected: condition, actual: actualValue }];
}
return [];
});
}
export function translateUnmetConditionsFromFind(
unmetConditions: UnmetCondition[],
): PermissionErrorExplanation[] {
return unmetConditions.map(({ field, expected, actual }) => {
if (
typeof expected === 'object' &&
expected !== null &&
!Array.isArray(expected)
) {
// Handle operator-based conditions
const explanations = Object.entries(expected).map(
([operator, expectedValue]) => {
if (!operatorDescriptions[operator]) {
return `unsupported operator '${operator}' for field '${field}'`;
}
return operatorDescriptions[operator](field, expectedValue, actual);
},
);
return {
description: explanations.join(', '),
input: `${actual}`,
};
}
// Handle simple equality for direct values
return {
description: `expected ${field} to be '${expected}'`,
input: `${actual}`,
};
});
}
// Descriptions for operators
const operatorDescriptions: Record<
string,
(field: string, expected: any, actual: any) => string
> = {
$eq: (field, expected) => `expected ${field} to equal '${expected}'`,
$ne: (field, expected) => `expected ${field} to not equal '${expected}'`,
$lt: (field, expected) => `expected ${field} to be less than ${expected}`,
$lte: (field, expected) =>
`expected ${field} to be less or equal to ${expected}`,
$gt: (field, expected) => `expected ${field} to be greater than ${expected}`,
$gte: (field, expected) =>
`expected ${field} to be greater or equal to ${expected}`,
$in: (field, expected) =>
`expected ${field} to be one of [${expected.join(', ')}]`,
$nin: (field, expected) =>
`expected ${field} to not be one of [${expected.join(', ')}]`,
$all: (field, expected) =>
`expected ${field} to include all of [${expected.join(', ')}]`,
$size: (field, expected) => `expected ${field} to have size ${expected}`,
$regex: (field, expected) => `expected ${field} to match pattern ${expected}`,
$exists: (field, expected) =>
expected
? `expected ${field} to exist (got undefined)`
: `expected ${field} to not exist`,
$elemMatch: (field) =>
`expected ${field} to have at least one matching element`,
};
// Operator handlers for MongoDB-style operators
const operatorHandlers: Record<
string,
(actual: any, expected: any) => boolean
> = {
$eq: (actual, expected) => actual === expected,
$ne: (actual, expected) => actual !== expected,
$lt: (actual, expected) => actual < expected,
$lte: (actual, expected) => actual <= expected,
$gt: (actual, expected) => actual > expected,
$gte: (actual, expected) => actual >= expected,
$in: (actual, expected) =>
Array.isArray(expected) && expected.includes(actual),
$nin: (actual, expected) =>
Array.isArray(expected) && !expected.includes(actual),
$all: (actual, expected) =>
Array.isArray(actual) && expected.every((val: any) => actual.includes(val)),
$size: (actual, expected) =>
Array.isArray(actual) && actual.length === expected,
$regex: (actual, expected) =>
typeof actual === 'string' && new RegExp(expected).test(actual),
$exists: (actual, expected) =>
expected ? actual !== undefined : actual === undefined,
$elemMatch: (actual, expected) => {
if (!Array.isArray(actual)) return false;
const query = new Query(expected);
return actual.some((item) => query.test(item));
},
};
interface PermissionErrorExplanation {
description: string;
input: string;
}
export const parseErrorExplanations = <T>(claim: T, restrictions) => {
const unmetConditions = findUnmetConditions(claim, restrictions);
const explanations = translateUnmetConditionsFromFind(unmetConditions);
return explanations;
}; |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Currently you can get only failing rule with
ability.relevantRuleFor(action, subject, field)