diff --git a/controllers/crud.js b/controllers/crud.js index 8d88ef4..13bf51b 100644 --- a/controllers/crud.js +++ b/controllers/crud.js @@ -9,7 +9,7 @@ import { newID, isValidID, db } from '../database/client.js' import { configureWebAnnoHeadersFor, configureLDHeadersFor, configureLastModifiedHeader } from '../headers.js' import { configureRerumOptions } from '../versioning.js' import config from '../config/index.js' -import { _contextid, idNegotiation, generateSlugId, ObjectID, createExpressError, getAgentClaim, parseDocumentID } from './utils.js' +import { _contextid, idNegotiation, generateSlugId, ObjectID, createError, createExpressError, getAgentClaim, parseDocumentID } from './utils.js' /** * Create a new Linked Open Data object in RERUM v1. @@ -74,11 +74,7 @@ const query = async function (req, res, next) { const skip = parseInt(req.query.skip ?? 0) if (Object.keys(props).length === 0) { //Hey now, don't ask for everything...this can happen by accident. Don't allow it. - let err = { - message: "Detected empty JSON object. You must provide at least one property in the /query request body JSON.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "Detected empty JSON object. You must provide at least one property in the /query request body JSON."))) return } try { @@ -115,11 +111,7 @@ const id = async function (req, res, next) { res.json(match) return } - let err = { - "message": `No RERUM object with id '${id}'`, - "status": 404 - } - next(createExpressError(err)) + next(createExpressError(createError(404, `No RERUM object with id '${id}'`))) } catch (error) { next(createExpressError(error)) } diff --git a/controllers/delete.js b/controllers/delete.js index bdbde7c..0ed8b75 100644 --- a/controllers/delete.js +++ b/controllers/delete.js @@ -7,7 +7,7 @@ import { newID, isValidID, db } from '../database/client.js' import { isDeleted, isReleased, isGenerator } from '../predicates.js' import config from '../config/index.js' -import { createExpressError, getAgentClaim, parseDocumentID, getAllVersions, getAllDescendants } from './utils.js' +import { createError, createExpressError, getAgentClaim, parseDocumentID, getAllVersions, getAllDescendants } from './utils.js' /** * Mark an object as deleted in the database. @@ -23,7 +23,8 @@ import { createExpressError, getAgentClaim, parseDocumentID, getAllVersions, get * */ const deleteObj = async function(req, res, next) { let id - let err = { message: `` } + let errMessage = "" + let errStatus = 0 try { id = req.params["_id"] ?? parseDocumentID(JSON.parse(JSON.stringify(req.body))["@id"]) ?? parseDocumentID(JSON.parse(JSON.stringify(req.body))["id"]) } catch(error){ @@ -41,25 +42,19 @@ const deleteObj = async function(req, res, next) { if (null !== originalObject) { let safe_original = JSON.parse(JSON.stringify(originalObject)) if (isDeleted(safe_original)) { - err = Object.assign(err, { - message: `The object you are trying to delete is already deleted. ${err.message}`, - status: 403 - }) + errMessage = `The object you are trying to delete is already deleted. ${errMessage}` + errStatus = 403 } else if (isReleased(safe_original)) { - err = Object.assign(err, { - message: `The object you are trying to delete is released. Fork to make changes. ${err.message}`, - status: 403 - }) + errMessage = `The object you are trying to delete is released. Fork to make changes. ${errMessage}` + errStatus = 403 } else if (!isGenerator(safe_original, agentRequestingDelete)) { - err = Object.assign(err, { - message: `You are not the generating agent for this object and so are not authorized to delete it. ${err.message}`, - status: 401 - }) + errMessage = `You are not the generating agent for this object and so are not authorized to delete it. ${errMessage}` + errStatus = 401 } - if (err.status) { - next(createExpressError(err)) + if (errStatus) { + next(createExpressError(createError(errStatus, errMessage))) return } let preserveID = safe_original["@id"] @@ -82,9 +77,7 @@ const deleteObj = async function(req, res, next) { } if (result.modifiedCount === 0) { //result didn't error out, the action was not performed. Sometimes, this is a neutral thing. Sometimes it is indicative of an error. - err.message = "The original object was not replaced with the deleted object in the database." - err.status = 500 - next(createExpressError(err)) + next(createExpressError(createError(500, "The original object was not replaced with the deleted object in the database."))) return } //204 to say it is deleted and there is nothing in the body @@ -93,14 +86,10 @@ const deleteObj = async function(req, res, next) { return } //Not sure we can get here, as healHistoryTree might throw and error. - err.message = "The history tree for the object being deleted could not be mended." - err.status = 500 - next(createExpressError(err)) + next(createExpressError(createError(500, "The history tree for the object being deleted could not be mended."))) return } - err.message = "No object with this id could be found in RERUM. Cannot delete." - err.status = 404 - next(createExpressError(err)) + next(createExpressError(createError(404, "No object with this id could be found in RERUM. Cannot delete."))) } /** diff --git a/controllers/overwrite.js b/controllers/overwrite.js index dd30e41..770b35d 100644 --- a/controllers/overwrite.js +++ b/controllers/overwrite.js @@ -9,7 +9,7 @@ import { newID, isValidID, db } from '../database/client.js' import { isDeleted, isReleased, isGenerator } from '../predicates.js' import { configureWebAnnoHeadersFor } from '../headers.js' -import { _contextid, ObjectID, createExpressError, getAgentClaim, parseDocumentID, idNegotiation } from './utils.js' +import { _contextid, ObjectID, createError, createExpressError, getAgentClaim, parseDocumentID, idNegotiation } from './utils.js' /** * Replace some existing object in MongoDB with the JSON object in the request body. @@ -18,7 +18,8 @@ import { _contextid, ObjectID, createExpressError, getAgentClaim, parseDocumentI * Respond RESTfully * */ const overwrite = async function (req, res, next) { - let err = { message: `` } + let errMessage = "" + let errStatus = 0 res.set("Content-Type", "application/json; charset=utf-8") let objectReceived = JSON.parse(JSON.stringify(req.body)) let agentRequestingOverwrite = getAgentClaim(req, next) @@ -34,28 +35,20 @@ const overwrite = async function (req, res, next) { return } if (null === originalObject) { - err = Object.assign(err, { - message: `No object with this id could be found in RERUM. Cannot overwrite. ${err.message}`, - status: 404 - }) + errMessage = `No object with this id could be found in RERUM. Cannot overwrite. ${errMessage}` + errStatus = 404 } else if (isDeleted(originalObject)) { - err = Object.assign(err, { - message: `The object you are trying to overwrite is deleted. ${err.message}`, - status: 403 - }) + errMessage = `The object you are trying to overwrite is deleted. ${errMessage}` + errStatus = 403 } else if (isReleased(originalObject)) { - err = Object.assign(err, { - message: `The object you are trying to overwrite is released. Fork with /update to make changes. ${err.message}`, - status: 403 - }) + errMessage = `The object you are trying to overwrite is released. Fork with /update to make changes. ${errMessage}` + errStatus = 403 } else if (!isGenerator(originalObject, agentRequestingOverwrite)) { - err = Object.assign(err, { - message: `You are not the generating agent for this object. You cannot overwrite it. Fork with /update to make changes. ${err.message}`, - status: 401 - }) + errMessage = `You are not the generating agent for this object. You cannot overwrite it. Fork with /update to make changes. ${errMessage}` + errStatus = 401 } else { // Optimistic locking check - no expected version is a brutal overwrite @@ -105,12 +98,10 @@ const overwrite = async function (req, res, next) { } else { //This is a custom one, the http module will not detect this as a 400 on its own - err = Object.assign(err, { - message: `Object in request body must have the property '@id' or 'id'. ${err.message}`, - status: 400 - }) + errMessage = `Object in request body must have the property '@id' or 'id'. ${errMessage}` + errStatus = 400 } - next(createExpressError(err)) + next(createExpressError(createError(errStatus, errMessage))) } export { overwrite } diff --git a/controllers/release.js b/controllers/release.js index 957081f..ba23ad8 100644 --- a/controllers/release.js +++ b/controllers/release.js @@ -9,7 +9,7 @@ import { newID, isValidID, db } from '../database/client.js' import { isDeleted, isReleased, isGenerator } from '../predicates.js' import { configureWebAnnoHeadersFor } from '../headers.js' -import { _contextid, ObjectID, createExpressError, getAgentClaim, parseDocumentID, idNegotiation, generateSlugId, establishReleasesTree, healReleasesTree } from './utils.js' +import { _contextid, ObjectID, createError, createExpressError, getAgentClaim, parseDocumentID, idNegotiation, generateSlugId, establishReleasesTree, healReleasesTree } from './utils.js' /** * Public facing servlet to release an existing RERUM object. This will not @@ -25,7 +25,8 @@ const release = async function (req, res, next) { let agentRequestingRelease = getAgentClaim(req, next) let id = req.params["_id"] let slug - let err = {"message":""} + let errMessage = "" + let errStatus = 0 let treeHealed = false if(req.get("Slug")){ let slug_json = await generateSlugId(req.get("Slug"), next) @@ -51,25 +52,19 @@ const release = async function (req, res, next) { let nextReleases = safe_original.__rerum.releases.next if (isDeleted(safe_original)) { - err = Object.assign(err, { - message: `The object you are trying to release is deleted. ${err.message}`, - status: 403 - }) + errMessage = `The object you are trying to release is deleted. ${errMessage}` + errStatus = 403 } if (isReleased(safe_original)) { - err = Object.assign(err, { - message: `The object you are trying to release is already released. ${err.message}`, - status: 403 - }) + errMessage = `The object you are trying to release is already released. ${errMessage}` + errStatus = 403 } if (!isGenerator(safe_original, agentRequestingRelease)) { - err = Object.assign(err, { - message: `You are not the generating agent for this object. You cannot release it. ${err.message}`, - status: 401 - }) + errMessage = `You are not the generating agent for this object. You cannot release it. ${errMessage}` + errStatus = 401 } - if (err.status) { - next(createExpressError(err)) + if (errStatus) { + next(createExpressError(createError(errStatus, errMessage))) return } console.log("RELEASE") @@ -122,11 +117,7 @@ const release = async function (req, res, next) { } else{ //This was a bad request - err = { - message: "You must provide the id of an object to release. Use /release/id-here or release?_id=id-here.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "You must provide the id of an object to release. Use /release/id-here or release?_id=id-here."))) return } } diff --git a/controllers/search.js b/controllers/search.js index 2b4d1fb..6edd135 100644 --- a/controllers/search.js +++ b/controllers/search.js @@ -6,7 +6,7 @@ */ import { db } from '../database/client.js' import { configureLDHeadersFor } from '../headers.js' -import { idNegotiation, createExpressError } from './utils.js' +import { idNegotiation, createError, createExpressError } from './utils.js' /** * Merges and deduplicates results from multiple MongoDB Atlas Search index queries. @@ -265,11 +265,7 @@ const searchAsWords = async function (req, res, next) { let searchText = req.body?.searchText ?? req.body const searchOptions = req.body?.options ?? {} if (!searchText) { - let err = { - message: "You did not provide text to search for in the search request.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "You did not provide text to search for in the search request."))) return } const limit = parseInt(req.query.limit ?? 100) @@ -353,11 +349,7 @@ const searchAsPhrase = async function (req, res, next) { slop: 2 } if (!searchText) { - let err = { - message: "You did not provide text to search for in the search request.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "You did not provide text to search for in the search request."))) return } const limit = parseInt(req.query.limit ?? 100) @@ -433,11 +425,7 @@ const searchFuzzily = async function (req, res, next) { } } if (!searchText) { - let err = { - message: "You did not provide text to search for in the search request.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "You did not provide text to search for in the search request."))) return } const limit = parseInt(req.query.limit ?? 100) @@ -521,20 +509,12 @@ const searchWildly = async function (req, res, next) { allowAnalyzedField: true } if (!searchText) { - let err = { - message: "You did not provide text to search for in the search request.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "You did not provide text to search for in the search request."))) return } // Require wildcards in the search text if (!searchText.includes('*') && !searchText.includes('?')) { - let err = { - message: "Wildcards must be used in wildcard search. Use '*' to match any characters or '?' to match a single character.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "Wildcards must be used in wildcard search. Use '*' to match any characters or '?' to match a single character."))) return } const limit = parseInt(req.query.limit ?? 100) @@ -625,11 +605,7 @@ const searchAlikes = async function (req, res, next) { let likeDocument = req.body // Validate that a document was provided if (!likeDocument || (typeof likeDocument !== 'object') || Object.keys(likeDocument).length === 0) { - let err = { - message: "You must provide a JSON document in the request body to find similar documents.", - status: 400 - } - next(createExpressError(err)) + next(createExpressError(createError(400, "You must provide a JSON document in the request body to find similar documents."))) return } const limit = parseInt(req.query.limit ?? 100) diff --git a/controllers/utils.js b/controllers/utils.js index c643abd..a8c4a2b 100644 --- a/controllers/utils.js +++ b/controllers/utils.js @@ -99,6 +99,10 @@ const index = function (req, res, next) { }) } +export function createError(status, message, details = {}) { + return Object.assign(new Error(message), { status, ...details }) +} + function createExpressError(err) { let error = {} if (err.code) {