New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom errors for rules and global fallback #60
Comments
@kolybasov this is great. I have nothing to add to second question, let’s make a PR! 🎉 Right now, there are two ways to solving the first question. You could use Hope this answers your question 🙂 |
@maticzav thanks for the fast response!
Let's imagine I have 2 rules: // isAuthenticated.js
import { shield, rule, and } from 'graphql-shield';
// User should be present in context
const isAuthenticated = rule()((parent, args, ctx) => Boolean(ctx.user));
// User role should be equal to 'admin'
const isAdmin = rule()((parent, args, ctx) => ctx.user.role === 'admin');
// I am combining this 2 rules together
const permissions = shield({
Query: {
getAllUsers: and(isAuthenticated, isAdmin)
}
}); My goal to have this:
If I understood correctly now we always will have
Thanks for helping with this! |
Hey 👋 Almost correct; logical operators, though, don't act like new rules (they should act like logical operators!). So, you can do something like this: // isAuthenticated.js
import { shield, rule, and, CustomError } from "graphql-shield";
// User should be present in context
const isAuthenticated = rule()((parent, args, ctx) => {
if (ctx.user) {
return true;
} else {
throw new CustomError(401);
}
});
// User role should be equal to 'admin'
const isAdmin = rule()((parent, args, ctx) => {
if (ctx.user.role === 'admin') {
return true;
} else {
throw new CustomError(403);
}
});
// I am combining this 2 rules together
const permissions = shield({
Query: {
getAllUsers: and(isAuthenticated, isAdmin)
}
}); As you can see, if one of the rules fails it will return desired error and vice versa. And yes, big time! I would love to see a PR for customizable default error. I hope this helps you out. 🙂 |
And in case of import { shield, rule, or, CustomError } from "graphql-shield";
// User should be present in context
const isAuthenticated = rule()((parent, args, ctx) => {
if (ctx.user) {
return true;
} else {
throw new CustomError(401);
}
});
// User role should be equal to 'admin'
const isAdmin = rule()((parent, args, ctx) => {
if (ctx.user.role === 'admin') {
return true;
} else {
throw new CustomError(403);
}
});
// I am combining this 2 rules together
const permissions = shield({
Query: {
getAllUsers: or(isAuthenticated, isAdmin)
}
}); So if I need only some of this rules then throwing custom error is not the case. Correct? |
The first question, indeed, it will work with all logical operators including Is this what you had in mind? |
Sorry for the confusion. For me it looks like when I need to check against few rules combined with My example don't plays really nice together(treat it as pseudocode) but if we will take both rules |
OK, I can see it now. I think we should find a way to combine the My thinking process looks like this;
Maybe the best option would be to return an error instead of throwing it since this also better aligns with code writing philosophy in my opinion. To conclude, I propose to change the functionality by adding Nevertheless, if anything throws an error, the rule should fail. I believe this is crucial for reducing unexpected errors. What do you think? |
Hey @maticzav! I am late a little bit :) Totally agree with this:
Seems the best option in my opinion!
Any help required with this? I see you already started to implement some parts. Thanks! |
Hey @kolybasov, I need some help. Could you give me your opinion on this; In a hypothetical situation, a user has defined a rule with a custom error. Unfortunately, they weren't precise and made a mistake in their resolver (not rule). Should the rule return |
@maticzav I imagine it this way: import { rule, and } from 'graphql-shield';
const isAuthenticated = rule()((parent, args, { user }) => {
if (user) return true;
return new Error('Not Authenticated');
});
const isAdmin = rule()((parent, args, { user }) => {
if (user.admin) return true;
return new Error('You have no permission to do this');
});
const canBanUser = and(isAuthenticated, isAdmin); type Mutation {
banUser(userId: ID!): Boolean
}
Hope it makes sense. Let me know if you have more questions :) And thank you very much for handling this! |
@maticzav so basically, graphql-shield rule can be in 4 states.
Personally, I do not use |
Ok, so my current implementation works like this; I used your first suggestion over mine because it seemed better in the meantime. const isAdmin = rule({
error: "You don't have permission to do this."
})(async (parent, args, ctx, info) => {
if (ctx.user) {
return true
} else {
return false
}
})
// no user -> error "You don't have permission to do this."
const isOwner = rule({
error: new ApolloError('Something')
})(async (parent, args, ctx, info) => {
if (ctx.user) {
return true
} else {
return false
}
})
// no user -> ApolloError "Something." Now that I think about it since having the ability to throw an error from inside the rule gives us the ability to access What do you think? |
@maticzav sorry for the initial confusion I was not sure about best solution also. const result = await rule.resolve(parent, args, ctx, info);
if (result instanceof Error) {
throw result;
} else if (result === false) {
throw new CustomError('Not Authorised!'); // Current default error, also make it replacebels via global config(IOptions interface)
} else {
// all is ok
return resolver(parent, args, ctx, info);
} |
I see... Should we make both cases possible? Can you imagine a scenario where the first idea would make more sense? |
@maticzav I believe they are just alternatives to each other and it is just matter of preference how you would like to specify custom error. In my opinion returning an error instead of passing it as param gives more flexibility because then rule may have many custom errors specified in case you need them for some complex one. And it is hard to imagine implemented with passing an error as param. rule()((parent, args, ctx, info) => {
if (args.something) {
return new Error('Something');
} else if (args.somethingElse) {
return new Error('Something else');
} else if (args.evenMore) {
return new Error('Even more!');
}
return true;
}); |
I agree, the new code already reflects that. @kolybasov let me know if you have a bit of spare time, I could really use a hand to write |
@maticzav sure! Just let me know what I can do and I will help! |
Perfect! Are you already in Prisma Slack? If not, could you join it here and find me at |
@maticzav I will join and ping you tomorrow then! |
@maticzav by some reason I can not receive invite to Prisma Slack. Any chance you are using Prisma Spectrum chat? https://spectrum.chat/prisma |
Just released the new version, going to close the issue now. |
I’m a few years late, but this seems to be a general problem with functions doing more than one thing. Rules should just test and return a boolean. That’s a separate concern from deciding which error to throw. |
Is there no way to just map error messages to rules? That seems like the way to go. |
Hi @maticzav! First of all thanks for the awesome library!
I have few questions/suggestions about CustomErrors:
Example:
I have isAuthenticated rule
I want to return my custom 401 error like this
Example:
I can implement this and looks like it is not a breaking change.
Just want to clarify if there is no way to do this in current version of the library?
Let me know if you have any questions. Thanks!
The text was updated successfully, but these errors were encountered: