diff --git a/package-lock.json b/package-lock.json index e5decca..43572b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "3dprint-time-estimator", + "name": "fi3d-slicer-as-a-service", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/src/HTTPError.ts b/src/HTTPError.ts new file mode 100644 index 0000000..c9c121a --- /dev/null +++ b/src/HTTPError.ts @@ -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 { + /** + * 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) +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 9b73b02..d76308c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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:
${e.toString().replace(cmd, 'software').replace(stlPath, `file ${file}`).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, 'software').replace(stlPath, `file ${file}`).replace(`${path}/configs/`, '').replace('.ini', '') + }) } const gcode = await fs.readFile(gcodePath, 'utf-8') await fs.rm(stlPath)