feat: Add RFC error messages

Signed-off-by: Florian BOUILLON <f.bouillon@aptatio.com>
This commit is contained in:
Florian Bouillon 2023-04-05 14:46:59 +02:00
parent e39e3726ac
commit 19e3892099
Signed by: Florian Bouillon
GPG Key ID: E05B3A94178D3A7C
3 changed files with 103 additions and 17 deletions

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "3dprint-time-estimator",
"name": "fi3d-slicer-as-a-service",
"lockfileVersion": 2,
"requires": true,
"packages": {

54
src/HTTPError.ts Normal file
View File

@ -0,0 +1,54 @@
import { Response } from 'express'
/**
* An error as of the RFC7807
*
* you MUST send the content-type `application/problem+json`
*/
export default interface HTTPError extends Record<string, any> {
/**
* A URI reference [RFC3986] that identifies the
* problem type. This specification encourages that, when
* dereferenced, it provide human-readable documentation for the
* problem type (e.g., using HTML [W3C.REC-html5-20141028]). When
* this member is not present, its value is assumed to be
* "about:blank".
*/
title: string
/**
* A short, human-readable summary of the problem
* type. It SHOULD NOT change from occurrence to occurrence of the
* problem, except for purposes of localization (e.g., using
* proactive content negotiation; see [RFC7231], Section 3.4).
*/
type?: string
/**
* The HTTP status code ([RFC7231], Section 6)
* generated by the origin server for this occurrence of the problem.
*/
status?: number
/**
* A human-readable explanation specific to this
* occurrence of the problem.
*/
detail?: string
/**
* A URI reference that identifies the specific
* occurrence of the problem. It may or may not yield further
* information if dereferenced.
*/
instance?: string
}
export function createErrorResponse(res: Response, error: HTTPError) {
res.status(error.status ?? 500)
if (!error.type) {
error.type = 'about:blank'
}
res.setHeader('Content-Type', 'application/problem+json')
res.json(error)
}

View File

@ -1,10 +1,11 @@
import { objectMap, objectOmit } from '@dzeio/object-util'
import { exec as execAsync } from 'child_process'
import express from 'express'
import fs from 'fs/promises'
import { exec as execAsync } from 'child_process'
import { objectMap, objectOmit } from '@dzeio/object-util'
import { getParams } from './gcodeUtils'
import util from 'util'
import { evaluate } from 'mathjs'
import util from 'util'
import { createErrorResponse } from './HTTPError'
import { getParams } from './gcodeUtils'
const exec = util.promisify(execAsync)
const server = express()
@ -48,8 +49,12 @@ server.post('/', async (req, res) => {
let config = `${path}/configs/` + (req.query?.config ?? 'config') + '.ini'
if (!exists(config)) {
console.log('request finished in error :(', file)
res.status(403).send('Configuration file not existing')
return
return createErrorResponse(res, {
title: 'Configuration file does not exist',
status: 404,
detail: `The configuration file you want to use does not exist`,
config: req.query?.config ?? 'config'
})
}
const stlPath = `${path}/files/${file}.stl`
const gcodePath = `${path}/files/${file}.gcode`
@ -74,23 +79,50 @@ server.post('/', async (req, res) => {
console.error(e)
if (line.includes('Objects could not fit on the bed')) {
await fs.rm(stlPath)
res.status(403).send('Object is too large')
return
return createErrorResponse(res, {
title: 'Object is too large to fit on the bed',
status: 413,
detail: `The object you are trying to slice is too large to fit on the current bed`
})
} else if (line.includes('No such file')) {
await fs.rm(stlPath)
res.status(403).send('Configuration file not existing')
return
return createErrorResponse(res, {
title: 'Configuration file does not exist',
status: 404,
detail: `The configuration file you want to use does not exist`,
config: req.query?.config ?? 'config'
})
} else if (line.includes('Unknown option')) {
await fs.rm(stlPath)
res.status(403).send('an override does not exists, please contact an administrator or refer to the documentation')
return
return createErrorResponse(res, {
title: 'An override does exist',
status: 404,
detail: `One or more of your overrides are not available in the current slicer`,
overrides: overrides
})
} else if (line.includes('ETIMEDOUT') || line.includes('Command failed')) {
await fs.rm(stlPath)
res.status(403).send('the file is taking too long to process :(')
return
return createErrorResponse(res, {
title: 'File is taking too long to process',
status: 400,
detail: `The file you are trying to process is too large (${req.body.length}o) to be processed in less than 60 secs`,
fileId: file,
config: req.query?.config ?? 'config',
fileSize: req.body.length,
overrides: overrides
})
}
res.status(500).send(`Error while making the file:<br />${e.toString().replace(cmd, '<i>software</i>').replace(stlPath, `<i>file ${file}</i>`).replace(`${path}/configs/`, '').replace('.ini', '')}`)
return
return createErrorResponse(res, {
title: 'General I/O error',
status: 500,
detail: `A server error make it impossible to slice the file`,
fileId: file,
config: req.query?.config ?? 'config',
fileSize: req.body.length,
overrides: overrides,
serverMessage: e.toString().replace(cmd, '<i>software</i>').replace(stlPath, `<i>file ${file}</i>`).replace(`${path}/configs/`, '').replace('.ini', '')
})
}
const gcode = await fs.readFile(gcodePath, 'utf-8')
await fs.rm(stlPath)