Added HttpError on server, to be used in operations.

This commit is contained in:
Martin Sosic 2020-09-03 17:28:29 +02:00 committed by Martin Šošić
parent 3b0b284bac
commit 69f45c40a7
6 changed files with 60 additions and 8 deletions

View File

@ -8,13 +8,24 @@ const {= queryFnName =} = async (args) => {
const response = await axios.post(config.apiUrl + '/{= queryRoute =}', args)
return response.data
} catch (error) {
// TODO: This is a really crude error handling for now, and we should look into improving it,
// once we figure out what we need. We should start from the server side probably.
const e = new Error(error.message)
if (error?.response?.data) {
e.data = error.response.data
if (error?.response) {
// If error came from HTTP response, we capture most informative message
// and also add .statusCode information to it.
// If error had JSON response, we assume it is of format { message, data } and
// add that info to the error.
// TODO: We might want to use HttpError here instead of just Error, since
// HttpError is also used on server to throw errors like these.
// That would require copying HttpError code to web-app also and using it here.
const responseJson = error.response?.data
const responseStatusCode = error.response.status
const e = new Error(responseJson?.message || error.message)
e.statusCode = responseStatusCode
e.data = responseJson?.data
throw e
} else {
// If any other error, we just propagate it.
throw error
}
throw e
}
}

View File

@ -3,6 +3,7 @@ import cookieParser from 'cookie-parser'
import logger from 'morgan'
import cors from 'cors'
import HttpError from './core/HttpError.js'
import indexRouter from './routes/index.js'
// TODO: Consider extracting most of this logic into createApp(routes, path) function so that
@ -18,4 +19,17 @@ app.use(cookieParser())
app.use('/', indexRouter)
// Custom error handler.
app.use((err, req, res, next) => {
// As by expressjs documentation, when the headers have already
// been sent to the client, we must delegate to the default error handler.
if (res.headersSent) { return next(err) }
if (err instanceof HttpError) {
return res.status(err.statusCode).json({ message: err.message, data: err.data })
}
return next(err)
})
export default app

View File

@ -0,0 +1,22 @@
class HttpError extends Error {
constructor (statusCode, message, data, ...params) {
super(message, ...params)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, HttpError)
}
this.name = this.constructor.name
if (!(Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600)) {
throw new Error('statusCode has to be integer in range [400, 600).')
}
this.statusCode = statusCode
if (data) {
this.data = data
}
}
}
export default HttpError

View File

@ -1,8 +1,10 @@
import HttpError from '@wasp/core/HttpError.js'
import state from './state.js'
export const createTask = (task, context) => {
if (Math.random() < 0.5) {
throw new Error('Failed to create task, random error!')
throw new HttpError(400, 'Failed to create task, random error!')
}
state.tasks = [...(state.tasks || []), task]
}

View File

@ -1,8 +1,10 @@
import HttpError from '@wasp/core/HttpError.js'
import state from './state.js'
export const getTasks = async (args, context) => {
if (Math.random() < 0.5) {
throw new Error('Random error: getting tasks failed.')
throw new HttpError(400, 'Random error: getting tasks failed.')
}
return state.tasks || []
}

View File

@ -58,6 +58,7 @@ genSrcDir wasp = concat
[ [C.copySrcTmplAsIs $ asTmplSrcFile [P.relfile|app.js|]]
, [C.copySrcTmplAsIs $ asTmplSrcFile [P.relfile|server.js|]]
, [C.copySrcTmplAsIs $ asTmplSrcFile [P.relfile|utils.js|]]
, [C.copySrcTmplAsIs $ asTmplSrcFile [P.relfile|core/HttpError.js|]]
, genRoutesDir wasp
, genQueries wasp
]