Skip to content
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

Error handling proposal #234

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion application/api/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"db": "readonly",
"bus": "readonly",
"domain": "readonly",
"metarhia": "readonly"
"metarhia": "readonly",
"DomainError": "readonly"
}
}
16 changes: 16 additions & 0 deletions application/api/auth.2/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
({
AuthError: class AuthError extends DomainError {
static ALREADY_EXISTS() {
return new AuthError(409, { message: 'User already exists' });
}
static INVALID_CREDENTIALS() {
Comment on lines +2 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not create separate AuthError classes instead?

class AuthError extends DomainError { code: 401 }
class AuthExistsError extends AuthError { code: 409 }
class AuthInvalidCredentialsError extends AuthError { code 400 }
...

it should be simpler to manage and use.

return new AuthError(400, { message: 'Incorrect logic or password' });
}
},
onError(error) {
const { AuthError } = api.auth.error;
if (error instanceof AuthError) return error;
// handle unexpected, non domain error and return based on error handling logic
return error;
},
});
4 changes: 3 additions & 1 deletion application/api/auth.2/register.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
({
access: 'public',
method: async ({ login, password, fullName }) => {
const { AuthError } = api.auth.error;
const hash = await metarhia.metautil.hashPassword(password);
await api.auth.provider.registerUser(login, hash, fullName);
const created = await api.auth.provider.registerUser(login, hash, fullName);
if (!created) return AuthError.ALREADY_EXISTS();
const token = await context.client.startSession();
return { status: 'success', token };
},
Expand Down
5 changes: 3 additions & 2 deletions application/api/auth.2/signin.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
({
access: 'public',
method: async ({ login, password }) => {
const { AuthError } = api.auth.error;
const user = await api.auth.provider.getUser(login);
if (!user) throw new Error('Incorrect login or password');
if (!user) return AuthError.INVALID_CREDENTIALS();
const { accountId, password: hash } = user;
const valid = await metarhia.metautil.validatePassword(password, hash);
if (!valid) throw new Error('Incorrect login or password');
if (!valid) return AuthError.INVALID_CREDENTIALS();
console.log(`Logged user: ${login}`);
const token = api.auth.provider.generateToken();
const data = { accountId: user.accountId };
Expand Down
11 changes: 11 additions & 0 deletions application/api/example.1/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,20 @@
},

method: async ({ a, b }) => {
const { ExampleError } = api.example.error;
if (typeof a !== 'number') return ExampleError.INVALID_ARG_A();
if (typeof b !== 'number') return ExampleError.INVALID_ARG_B();
const result = a + b;
return result;
},

returns: 'number',

onError(error) {
const { ExampleError } = api.example.error;
// in theory there's no need to trigger onError handler
if (error instanceof ExampleError) return error;
Comment on lines +17 to +20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO - this should be default behavior of server for DomainErrors with user-error-format.

// handle unexpected, non domain error and return based on error handling logic
return error;
},
});
2 changes: 2 additions & 0 deletions application/api/example.1/citiesByCountry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
async ({ countryId }) => {
const { ExampleError } = api.example.error;
if (!countryId) return ExampleError.MISSING_COUNTRY_ID();
const fields = ['cityId', 'name'];
const where = { countryId };
const data = await db.pg.select('City', fields, where);
Expand Down
14 changes: 13 additions & 1 deletion application/api/example.1/error.js
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
async () => new Error('Return error');
({
ExampleError: class ExampleError extends DomainError {
static INVALID_ARG_A() {
return new ExampleError(400, { message: 'Invalid argument: a' });
}
static INVALID_ARG_B() {
return new ExampleError(400, { message: 'Invalid argument: b' });
}
static MISSING_COUNTRY_ID() {
return new ExampleError(400, { message: 'Missing country id' });
}
},
});
24 changes: 24 additions & 0 deletions types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,27 @@ declare global {
const pg: Database;
}
}

export interface ErrorOptions {
code?: number | string;
cause?: Error;
}

export class Error extends global.Error {
constructor(message: string, options?: number | string | ErrorOptions);
message: string;
stack: string;
code?: number | string;
cause?: Error;
}

type Errors = Record<string, string>;

export class DomainError extends Error {
constructor(code?: string, options?: number | string | ErrorOptions);
message: string;
stack: string;
code?: number | string;
cause?: Error;
toError(errors: Errors): Error;
}