[SER-279] Error Improvements#90
Conversation
e64a270 to
ab9340f
Compare
ab9340f to
6c46b53
Compare
4b048ea to
f7337fa
Compare
…ston-json-formatter
f7337fa to
04edc55
Compare
|
@mountHouli : Now that we're using the |
orndorffgrant
left a comment
There was a problem hiding this comment.
Looks really good! Just a couple small things. Also - should this (class and express middleware) be separated into its own package? We could call it amida-express-api-error and use it across all the microservices
| * @example if (err) { new APIError(err, 'Create user failed. User already exists.', 'CREATE_USER_FAILED', 409, { isPublic: false }) } | ||
| */ | ||
| /* eslint-enable max-len */ | ||
| constructor(arg1, arg2, arg3, arg4, arg5) { |
There was a problem hiding this comment.
I hear this is the normal way of getting around javascript's lack of constructor/function overloading.
Personally, I would prefer a constructor that looks like this:
constructor({ causalError, message, code, status, options }) { ... }and helper functions that would be used like this
APIError.fromCausalError(err, 'msg', 'CODE', 400)
APIError.new('msg', 'CODE', 400)which call new APIError( ... ) appropriately
This has the benefit of being easier to type check with something like Typescript or Flow because each function has exactly one type signature.
There was a problem hiding this comment.
Valid view, but not including change here.
Keeping open for reference when creating APIError lib.
Agreed, the typeof is canonical javascript, but is cumbersome.
My suspicion is the developer ergonomics of function overloading (same function; different type signatures) outweighs the cumbersome canonical implementation in javascript.
There was a problem hiding this comment.
Thanks for the feedback. Will revisit when pulling the code out into a lib.
- Fixed issues flagged by @orndorffgrant (thanks for the detailed/careful review) - Minor CHANGELOG style fix * Unpacked knapsack of reference to [SER-279] _in CHANGELOG_, because JIRA issues aren't public to non-Amida users. * Amida users will still be able to see SER-279 reference for PR and link to commits.
rmharrison
left a comment
There was a problem hiding this comment.
Addressed @orndorffgrant comments
I hate that I cannot use APIError without new (e.g. new APIError)
I'm assuming you ( @mountHouli ) spent many hours on this to no avail. 😢
| * @example if (err) { new APIError(err, 'Create user failed. User already exists.', 'CREATE_USER_FAILED', 409, { isPublic: false }) } | ||
| */ | ||
| /* eslint-enable max-len */ | ||
| constructor(arg1, arg2, arg3, arg4, arg5) { |
There was a problem hiding this comment.
Valid view, but not including change here.
Keeping open for reference when creating APIError lib.
Agreed, the typeof is canonical javascript, but is cumbersome.
My suspicion is the developer ergonomics of function overloading (same function; different type signatures) outweighs the cumbersome canonical implementation in javascript.
| // Read https://www.joyent.com/node-js/production/design/errors to understand the difference between | ||
| // "programmer errors" (i.e. bugs) and "operational errors" (i.e. not actual bugs). | ||
| // The APIError class assumes that if it was instantiated without a causal error, then it is being | ||
| // used to define an "operational error", and there is no actual "programmer error"/bug. |
There was a problem hiding this comment.
FFR: The definition of "greater than" was confusing. Had to lookup
winston.config.npm.levels
{ error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6 }
to understand the direction of greater, i.e. greater ==> more verbose.
There was a problem hiding this comment.
Mmm. Per our discussion I actually had it the other way at first, but I kept getting confused because the more verbose levels correspond to higher integers.
|
I did not at all attempt to make it usable without |
Related JIRA tickets:
https://jira.amida.com/browse/SER-279 (read it all, please!)
What's this PR do?
Read the comments in
APIError.js!winstonto^3.2.1andwinston-json-formatterto^0.10.0to get great new logging bug fixes and formatting features (see [SER-281] Log Errors winston-json-formatter#5).warnlevel and "programmer errors" at theerrorlevel.new APIError()to the new signature.How should this be manually tested?
NODE_ENV=developmentalwaysTest 1: Attempt Login With Bad Username
(Related to features 3, 5, 7, and 10)
For each case below, make this request:
POSTtoapi/v2/auth/loginwith invalid usernameTest 1.A: Set
ALWAYS_INCLUDE_ERROR_STACKS=falseHTTP response body:
{ "code": "INCORRECT_USERNAME_OR_PASSWORD", "status": "ERROR", "message": "Incorrect username or password" }Logged to stdout:
2019-04-23T00:04:44.547Z warn Incorrect username or password { "code": "INCORRECT_USERNAME_OR_PASSWORD", "status": 401, "options": {}, "isOperational": true, "isPublic": true }Test 1.B: Set
ALWAYS_INCLUDE_ERROR_STACKS=trueHTTP response body:
{ "code": "INCORRECT_USERNAME_OR_PASSWORD", "status": "ERROR", "message": "Incorrect username or password", "stack": "Error: Incorrect username or password\n at /Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/src/controllers/auth.controller.js:49:17\n at tryCatcher (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/util.js:16:23)\n at Promise._settlePromiseFromHandler (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:512:31)\n at Promise._settlePromise (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:569:18)\n at Promise._settlePromise0 (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:614:10)\n at Promise._settlePromises (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:694:18)\n at _drainQueueStep (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:138:12)\n at _drainQueue (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:131:9)\n at Async._drainQueues (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:147:5)\n at Immediate.Async.drainQueues (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:17:14)\n at runCallback (timers.js:810:20)\n at tryOnImmediate (timers.js:768:5)\n at processImmediate [as _immediateCallback] (timers.js:745:5)" }Logged to stdout:
2019-04-23T00:40:33.842Z warn Incorrect username or password Error: Incorrect username or password at /Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/src/controllers/auth.controller.js:49:17 at tryCatcher (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/util.js:16:23) at Promise._settlePromiseFromHandler (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:512:31) at Promise._settlePromise (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:569:18) at Promise._settlePromise0 (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:614:10) at Promise._settlePromises (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:694:18) at _drainQueueStep (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:138:12) at _drainQueue (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:131:9) at Async._drainQueues (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:147:5) at Immediate.Async.drainQueues (/Users/houli/docs/gitrepos/amida-tech/amida-auth-microservice/node_modules/sequelize/node_modules/bluebird/js/release/async.js:17:14) at runCallback (timers.js:810:20) at tryOnImmediate (timers.js:768:5) at processImmediate [as _immediateCallback] (timers.js:745:5) { "code": "INCORRECT_USERNAME_OR_PASSWORD", "status": 401, "options": {}, "isOperational": true, "isPublic": true }Test 2: Create User That Already Exists
(Related to features 1, 2, 7, 8)
Login with a user that has sufficient scope to create other users (an "admin" or "programAdmin"). Use the JWT in your authorization header for the request below.
For each case below, make this request:
POSTtoapi/v2/userwith a user that already exitsBody:
{ "username": "some_user_that_already_exists_in_the_db", "email": "some_user_that_already_exists_in_the_db", "password": "dont care", "scopes": ["clinician"] }Test 2.A: When
LOG_LEVEL=infoHTTP response body:
{ "code": "GENERIC_ERROR", "status": "ERROR", "message": "Sequelize Validation Error" }Logged to stdout:
2019-04-23T00:12:24.835Z warn Sequelize Validation Error { "code": "GENERIC_ERROR", "status": 409, "options": { "isOperational": true }, "isOperational": true, "isPublic": true }Test 2.B: When
LOG_LEVEL=debugHTTP response body:
{ "code": "GENERIC_ERROR", "status": "ERROR", "message": "Sequelize Validation Error: Validation error: username must be unique" }Logged to stdout:
2019-04-23T00:09:37.364Z warn Sequelize Validation Error: Validation error: username must be unique { "code": "GENERIC_ERROR", "status": 409, "options": { "isOperational": true }, "isOperational": true, "isPublic": true, "cause": { "name": "SequelizeUniqueConstraintError", "errors": [ { "message": "username must be unique", "type": "unique violation", "path": "username", "value": "aaron+errtest02@amida.com", "origin": "DB", "instance": { "uuid": "2fa94e85-558d-4d91-97d8-31e544a715c5", "scopes": [ "" ], "id": null, "username": "aaron+errtest02@amida.com", "email": "aaron+errtest02@amida.com", "password": "ae12f9d5a6bc41182a12acf13943ad21a0fbe8864be474cd9ed108cfbd491aa80c4924aaeb46b0c86d8534ea659441707d6d398d0cee6666c6d94ca65908ab2b82d00cc5487be61fe5be3af30597ae13a9606fe761db28dfadea55bb40214deddeae4f3dd4d39c261a22b607dbfb8135d5b456534755b623280114af1c3d15fe", "updatedAt": "2019-04-23T00:09:36.917Z", "createdAt": "2019-04-23T00:09:36.917Z", "salt": "0c549edf-5704-46cb-af6e-fed9eab3b90e" }, "validatorKey": "not_unique", "validatorName": null, "validatorArgs": [] } ], "fields": { "username": "aaron+errtest02@amida.com" }, "parent": { "name": "error", "length": 226, "severity": "ERROR", "code": "23505", "detail": "Key (username)=(aaron+errtest02@amida.com) already exists.", "schema": "public", "table": "Users", "constraint": "Users_username_key", "file": "nbtinsert.c", "line": "434", "routine": "_bt_check_unique", "sql": "INSERT INTO \"Users\" (\"uuid\",\"id\",\"username\",\"email\",\"password\",\"salt\",\"scopes\",\"createdAt\",\"updatedAt\") VALUES ('2fa94e85-558d-4d91-97d8-31e544a715c5',DEFAULT,'aaron+errtest02@amida.com','aaron+errtest02@amida.com','ae12f9d5a6bc41182a12acf13943ad21a0fbe8864be474cd9ed108cfbd491aa80c4924aaeb46b0c86d8534ea659441707d6d398d0cee6666c6d94ca65908ab2b82d00cc5487be61fe5be3af30597ae13a9606fe761db28dfadea55bb40214deddeae4f3dd4d39c261a22b607dbfb8135d5b456534755b623280114af1c3d15fe','0c549edf-5704-46cb-af6e-fed9eab3b90e',ARRAY['']::VARCHAR(255)[],'2019-04-23 00:09:36.917 +00:00','2019-04-23 00:09:36.917 +00:00') RETURNING *;" }, "original": { "name": "error", "length": 226, "severity": "ERROR", "code": "23505", "detail": "Key (username)=(aaron+errtest02@amida.com) already exists.", "schema": "public", "table": "Users", "constraint": "Users_username_key", "file": "nbtinsert.c", "line": "434", "routine": "_bt_check_unique", "sql": "INSERT INTO \"Users\" (\"uuid\",\"id\",\"username\",\"email\",\"password\",\"salt\",\"scopes\",\"createdAt\",\"updatedAt\") VALUES ('2fa94e85-558d-4d91-97d8-31e544a715c5',DEFAULT,'aaron+errtest02@amida.com','aaron+errtest02@amida.com','ae12f9d5a6bc41182a12acf13943ad21a0fbe8864be474cd9ed108cfbd491aa80c4924aaeb46b0c86d8534ea659441707d6d398d0cee6666c6d94ca65908ab2b82d00cc5487be61fe5be3af30597ae13a9606fe761db28dfadea55bb40214deddeae4f3dd4d39c261a22b607dbfb8135d5b456534755b623280114af1c3d15fe','0c549edf-5704-46cb-af6e-fed9eab3b90e',ARRAY['']::VARCHAR(255)[],'2019-04-23 00:09:36.917 +00:00','2019-04-23 00:09:36.917 +00:00') RETURNING *;" }, "sql": "INSERT INTO \"Users\" (\"uuid\",\"id\",\"username\",\"email\",\"password\",\"salt\",\"scopes\",\"createdAt\",\"updatedAt\") VALUES ('2fa94e85-558d-4d91-97d8-31e544a715c5',DEFAULT,'aaron+errtest02@amida.com','aaron+errtest02@amida.com','ae12f9d5a6bc41182a12acf13943ad21a0fbe8864be474cd9ed108cfbd491aa80c4924aaeb46b0c86d8534ea659441707d6d398d0cee6666c6d94ca65908ab2b82d00cc5487be61fe5be3af30597ae13a9606fe761db28dfadea55bb40214deddeae4f3dd4d39c261a22b607dbfb8135d5b456534755b623280114af1c3d15fe','0c549edf-5704-46cb-af6e-fed9eab3b90e',ARRAY['']::VARCHAR(255)[],'2019-04-23 00:09:36.917 +00:00','2019-04-23 00:09:36.917 +00:00') RETURNING *;" } }Test 3: Call endpoint that doesn't exist:
(Related to feature 7)
Make a request to an endpoint that does not exist.
HTTP response body:
{ "code": "UNKNOWN_ERROR", "status": "ERROR", "message": "Not Found" }Logged to stdout:
2019-04-23T00:32:32.772Z warn API not found { "code": "UNKNOWN_API", "status": 404, "options": { "isPublic": false }, "isOperational": true, "isPublic": false }