Pārlūkot izejas kodu

Merge pull request #3074 from overleaf/msm-update-oerror-logger-v3

Update o-error to v3 and logger-sharelatex to v3

GitOrigin-RevId: 5e6386f65b7893949336aa4ff021212aebd98f4a
Miguel Serrano 6 gadi atpakaļ
vecāks
revīzija
d2845d9a38
27 mainītis faili ar 446 papildinājumiem un 719 dzēšanām
  1. 3 3
      services/web/app/src/Features/Authentication/AuthenticationErrors.js
  2. 1 4
      services/web/app/src/Features/Collaborators/CollaboratorsController.js
  3. 1 4
      services/web/app/src/Features/Collaborators/CollaboratorsGetter.js
  4. 9 11
      services/web/app/src/Features/Collaborators/CollaboratorsHandler.js
  5. 84 134
      services/web/app/src/Features/Compile/ClsiManager.js
  6. 5 8
      services/web/app/src/Features/Email/EmailSender.js
  7. 32 35
      services/web/app/src/Features/Errors/Errors.js
  8. 6 4
      services/web/app/src/Features/FileStore/FileStoreHandler.js
  9. 6 15
      services/web/app/src/Features/History/HistoryManager.js
  10. 27 24
      services/web/app/src/Features/Newsletter/NewsletterManager.js
  11. 1 1
      services/web/app/src/Features/Project/FolderStructureBuilder.js
  12. 1 4
      services/web/app/src/Features/Project/ProjectAuditLogHandler.js
  13. 5 8
      services/web/app/src/Features/Project/ProjectDuplicator.js
  14. 3 7
      services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js
  15. 18 27
      services/web/app/src/Features/Spelling/SpellingHandler.js
  16. 2 2
      services/web/app/src/Features/Subscription/Errors.js
  17. 9 12
      services/web/app/src/Features/TokenAccess/TokenAccessController.js
  18. 2 2
      services/web/app/src/Features/Uploads/ArchiveErrors.js
  19. 1 1
      services/web/app/src/Features/User/SAMLIdentityManager.js
  20. 1 4
      services/web/app/src/Features/User/UserAuditLogHandler.js
  21. 6 6
      services/web/app/src/Features/User/UserController.js
  22. 4 7
      services/web/app/src/infrastructure/SessionAutostartMiddleware.js
  23. 188 391
      services/web/package-lock.json
  24. 2 2
      services/web/package.json
  25. 6 2
      services/web/test/unit/src/FileStore/FileStoreHandlerTests.js
  26. 22 0
      services/web/test/unit/src/Newsletter/NewsletterManagerTests.js
  27. 1 1
      services/web/test/unit/src/User/UserControllerTests.js

+ 3 - 3
services/web/app/src/Features/Authentication/AuthenticationErrors.js

@@ -1,7 +1,7 @@
-const OError = require('@overleaf/o-error')
+const Errors = require('../Errors/Errors')
 
-class InvalidEmailError extends OError {}
-class InvalidPasswordError extends OError {}
+class InvalidEmailError extends Errors.BackwardCompatibleError {}
+class InvalidPasswordError extends Errors.BackwardCompatibleError {}
 
 module.exports = {
   InvalidEmailError,

+ 1 - 4
services/web/app/src/Features/Collaborators/CollaboratorsController.js

@@ -43,10 +43,7 @@ async function getAllMembers(req, res, next) {
   try {
     members = await CollaboratorsGetter.promises.getAllInvitedMembers(projectId)
   } catch (err) {
-    throw new OError({
-      message: 'error getting members for project',
-      info: { projectId }
-    }).withCause(err)
+    throw OError.tag(err, 'error getting members for project', { projectId })
   }
   res.json({ members })
 }

+ 1 - 4
services/web/app/src/Features/Collaborators/CollaboratorsGetter.js

@@ -170,10 +170,7 @@ async function getAllInvitedMembers(projectId) {
     )
     return members
   } catch (err) {
-    throw new OError({
-      message: 'error getting members for project',
-      info: { projectId }
-    }).withCause(err)
+    throw OError.tag(err, 'error getting members for project', { projectId })
   }
 }
 

+ 9 - 11
services/web/app/src/Features/Collaborators/CollaboratorsHandler.js

@@ -70,10 +70,10 @@ async function removeUserFromProject(projectId, userId) {
       )
     }
   } catch (err) {
-    throw new OError({
-      message: 'problem removing user from project collaborators',
-      info: { projectId, userId }
-    }).withCause(err)
+    throw OError.tag(err, 'problem removing user from project collaborators', {
+      projectId,
+      userId
+    })
   }
 }
 
@@ -225,9 +225,7 @@ async function setCollaboratorPrivilegeLevel(
       break
     }
     default: {
-      throw new OError({
-        message: `unknown privilege level: ${privilegeLevel}`
-      })
+      throw new OError(`unknown privilege level: ${privilegeLevel}`)
     }
   }
   const mongoResponse = await Project.updateOne(query, update).exec()
@@ -255,10 +253,10 @@ async function userIsTokenMember(userId, projectId) {
     )
     return project != null
   } catch (err) {
-    throw new OError({
-      message: 'problem while checking if user is token member',
-      info: { userId, projectId }
-    }).withCause(err)
+    throw OError.tag(err, 'problem while checking if user is token member', {
+      userId,
+      projectId
+    })
   }
 }
 

+ 84 - 134
services/web/app/src/Features/Compile/ClsiManager.js

@@ -70,10 +70,10 @@ const ClsiManager = {
           })
         } else {
           return callback(
-            new OError({
-              message: 'Could not build request to CLSI',
-              info: { projectId, options }
-            }).withCause(err)
+            OError.tag(err, 'Could not build request to CLSI', {
+              projectId,
+              options
+            })
           )
         }
       }
@@ -85,10 +85,7 @@ const ClsiManager = {
         (err, status, ...result) => {
           if (err != null) {
             return callback(
-              new OError({
-                message: 'CLSI compile failed',
-                info: { projectId, userId }
-              }).withCause(err)
+              OError.tag(err, 'CLSI compile failed', { projectId, userId })
             )
           }
           callback(null, status, ...result)
@@ -110,10 +107,11 @@ const ClsiManager = {
       (err, status, ...result) => {
         if (err != null) {
           return callback(
-            new OError({
-              message: 'CLSI compile failed',
-              info: { submissionId, clsiRequest, options }
-            }).withCause(err)
+            OError.tag(err, 'CLSI compile failed', {
+              submissionId,
+              clsiRequest,
+              options
+            })
           )
         }
         callback(null, status, ...result)
@@ -157,18 +155,16 @@ const ClsiManager = {
       DocumentUpdaterHandler.clearProjectState(projectId, docUpdaterErr => {
         if (clsiErr != null) {
           return callback(
-            new OError({
-              message: 'Failed to delete aux files',
-              info: { projectId }
-            }).withCause(clsiErr)
+            OError.tag(clsiErr, 'Failed to delete aux files', { projectId })
           )
         }
         if (docUpdaterErr != null) {
           return callback(
-            new OError({
-              message: 'Failed to clear project state in doc updater',
-              info: { projectId }
-            }).withCause(docUpdaterErr)
+            OError.tag(
+              docUpdaterErr,
+              'Failed to clear project state in doc updater',
+              { projectId }
+            )
           )
         }
         callback()
@@ -201,10 +197,10 @@ const ClsiManager = {
       (err, validationProblems) => {
         if (err != null) {
           return callback(
-            new OError({
-              message:
-                'could not check resources for potential problems before sending to clsi'
-            }).withCause(err)
+            OError.tag(
+              err,
+              'could not check resources for potential problems before sending to clsi'
+            )
           )
         }
         if (validationProblems != null) {
@@ -228,19 +224,16 @@ const ClsiManager = {
           (err, response) => {
             if (err != null) {
               return callback(
-                new OError({
-                  message: 'error sending request to clsi',
-                  info: { projectId, userId }
-                }).withCause(err)
+                OError.tag(err, 'error sending request to clsi', {
+                  projectId,
+                  userId
+                })
               )
             }
             ClsiCookieManager._getServerId(projectId, (err, clsiServerId) => {
               if (err != null) {
                 return callback(
-                  new OError({
-                    message: 'error getting server id',
-                    info: { projectId }
-                  }).withCause(err)
+                  OError.tag(err, 'error getting server id', { projectId })
                 )
               }
               const outputFiles = ClsiManager._parseOutputFiles(
@@ -268,10 +261,9 @@ const ClsiManager = {
           ClsiCookieManager.getCookieJar(projectId, (err, jar) => {
             if (err != null) {
               return callback(
-                new OError({
-                  message: 'error getting cookie jar for CLSI request',
-                  info: { projectId }
-                }).withCause(err)
+                OError.tag(err, 'error getting cookie jar for CLSI request', {
+                  projectId
+                })
               )
             }
             opts.jar = jar
@@ -279,10 +271,7 @@ const ClsiManager = {
             request(opts, (err, response, body) => {
               if (err != null) {
                 return callback(
-                  new OError({
-                    message: 'error making request to CLSI',
-                    info: { projectId }
-                  }).withCause(err)
+                  OError.tag(err, 'error making request to CLSI', { projectId })
                 )
               }
               timer.done()
@@ -292,10 +281,7 @@ const ClsiManager = {
               ClsiCookieManager.setServerId(projectId, response, err => {
                 if (err != null) {
                   callback(
-                    new OError({
-                      message: 'error setting server id',
-                      info: { projectId }
-                    }).withCause(err)
+                    OError.tag(err, 'error setting server id', { projectId })
                   )
                 } else {
                   // return as soon as the standard compile has returned
@@ -374,10 +360,9 @@ const ClsiManager = {
     NewBackendCloudClsiCookieManager.getCookieJar(projectId, (err, jar) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'error getting cookie jar for CLSI request',
-            info: { projectId }
-          }).withCause(err)
+          OError.tag(err, 'error getting cookie jar for CLSI request', {
+            projectId
+          })
         )
       }
       opts.jar = jar
@@ -386,10 +371,10 @@ const ClsiManager = {
         timer.done()
         if (err != null) {
           return callback(
-            new OError({
-              message: 'error making request to new CLSI',
-              info: { projectId, opts }
-            }).withCause(err)
+            OError.tag(err, 'error making request to new CLSI', {
+              projectId,
+              opts
+            })
           )
         }
         NewBackendCloudClsiCookieManager.setServerId(
@@ -398,10 +383,9 @@ const ClsiManager = {
           err => {
             if (err != null) {
               return callback(
-                new OError({
-                  message: 'error setting server id on new backend',
-                  info: { projectId }
-                }).withCause(err)
+                OError.tag(err, 'error setting server id on new backend', {
+                  projectId
+                })
               )
             }
             callback(null, response, body)
@@ -438,14 +422,11 @@ const ClsiManager = {
     ClsiManager._makeRequest(projectId, opts, (err, response, body) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'failed to make request to CLSI',
-            info: {
-              projectId,
-              userId,
-              compileOptions: req.compile.options,
-              rootResourcePath: req.compile.rootResourcePath
-            }
+          new OError('failed to make request to CLSI', {
+            projectId,
+            userId,
+            compileOptions: req.compile.options,
+            rootResourcePath: req.compile.rootResourcePath
           })
         )
       }
@@ -461,16 +442,13 @@ const ClsiManager = {
         callback(null, { compile: { status: 'unavailable' } })
       } else {
         callback(
-          new OError({
-            message: `CLSI returned non-success code: ${response.statusCode}`,
-            info: {
-              projectId,
-              userId,
-              compileOptions: req.compile.options,
-              rootResourcePath: req.compile.rootResourcePath,
-              clsiResponse: body,
-              statusCode: response.statusCode
-            }
+          new OError(`CLSI returned non-success code: ${response.statusCode}`, {
+            projectId,
+            userId,
+            compileOptions: req.compile.options,
+            rootResourcePath: req.compile.rootResourcePath,
+            clsiResponse: body,
+            statusCode: response.statusCode
           })
         )
       }
@@ -500,10 +478,7 @@ const ClsiManager = {
       (err, project) => {
         if (err != null) {
           return callback(
-            new OError({
-              message: 'failed to get project',
-              info: { projectId }
-            }).withCause(err)
+            OError.tag(err, 'failed to get project', { projectId })
           )
         }
         if (project == null) {
@@ -564,10 +539,9 @@ const ClsiManager = {
             timer.done()
             if (err != null) {
               return callback(
-                new OError({
-                  message: 'failed to get contents from Mongo',
-                  info: { projectId }
-                }).withCause(err)
+                OError.tag(err, 'failed to get contents from Mongo', {
+                  projectId
+                })
               )
             }
             ClsiManager._finaliseRequest(
@@ -588,10 +562,7 @@ const ClsiManager = {
     ClsiStateManager.computeHash(project, options, (err, projectStateHash) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'Failed to compute project state hash',
-            info: { projectId }
-          }).withCause(err)
+          OError.tag(err, 'Failed to compute project state hash', { projectId })
         )
       }
       DocumentUpdaterHandler.getProjectDocsIfMatch(
@@ -600,10 +571,10 @@ const ClsiManager = {
         (err, docs) => {
           if (err != null) {
             return callback(
-              new OError({
-                message: 'Failed to get project documents',
-                info: { projectId, projectStateHash }
-              }).withCause(err)
+              OError.tag(err, 'Failed to get project documents', {
+                projectId,
+                projectStateHash
+              })
             )
           }
           callback(null, projectStateHash, docs)
@@ -619,10 +590,12 @@ const ClsiManager = {
     ClsiCookieManager.getCookieJar(projectId, (err, jar) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'Failed to get cookie jar',
-            info: { projectId, userId, buildId, outputFilePath }
-          }).withCause(err)
+          OError.tag(err, 'Failed to get cookie jar', {
+            projectId,
+            userId,
+            buildId,
+            outputFilePath
+          })
         )
       }
       const options = { url, method: 'GET', timeout: 60 * 1000, jar }
@@ -642,10 +615,7 @@ const ClsiManager = {
     ProjectEntityHandler.getAllDocPathsFromProject(project, (err, docPath) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'Failed to get doc paths',
-            info: { projectId }
-          }).withCause(err)
+          OError.tag(err, 'Failed to get doc paths', { projectId })
         )
       }
       const docs = {}
@@ -690,10 +660,9 @@ const ClsiManager = {
     ClsiManager._getContentFromMongo(projectId, (err, docs, files) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'failed to get project contents from Mongo',
-            info: { projectId }
-          }).withCause(err)
+          OError.tag(err, 'failed to get project contents from Mongo', {
+            projectId
+          })
         )
       }
       options = {
@@ -716,28 +685,19 @@ const ClsiManager = {
     DocumentUpdaterHandler.flushProjectToMongo(projectId, err => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'failed to flush project to Mongo',
-            info: { projectId }
-          }).withCause(err)
+          OError.tag(err, 'failed to flush project to Mongo', { projectId })
         )
       }
       ProjectEntityHandler.getAllDocs(projectId, (err, docs) => {
         if (err != null) {
           return callback(
-            new OError({
-              message: 'failed to get project docs',
-              info: { projectId }
-            }).withCause(err)
+            OError.tag(err, 'failed to get project docs', { projectId })
           )
         }
         ProjectEntityHandler.getAllFiles(projectId, (err, files) => {
           if (err != null) {
             return callback(
-              new OError({
-                message: 'failed to get project files',
-                info: { projectId }
-              }).withCause(err)
+              OError.tag(err, 'failed to get project files', { projectId })
             )
           }
           if (files == null) {
@@ -797,12 +757,7 @@ const ClsiManager = {
           rootResourcePath = path.replace(/^\//, '')
         }
       } else {
-        return callback(
-          new OError({
-            message: 'no main file specified',
-            info: { projectId }
-          })
-        )
+        return callback(new OError('no main file specified', { projectId }))
       }
     }
 
@@ -840,10 +795,10 @@ const ClsiManager = {
     ClsiManager._buildRequest(projectId, options, (err, req) => {
       if (err != null) {
         return callback(
-          new OError({
-            message: 'Failed to build CLSI request',
-            info: { projectId, options }
-          }).withCause(err)
+          OError.tag(err, 'Failed to build CLSI request', {
+            projectId,
+            options
+          })
         )
       }
       const filename = file || req.compile.rootResourcePath
@@ -863,25 +818,20 @@ const ClsiManager = {
       }
       ClsiManager._makeRequest(projectId, opts, (err, response, body) => {
         if (err != null) {
-          return callback(
-            new OError({
-              message: 'CLSI request failed',
-              info: { projectId }
-            }).withCause(err)
-          )
+          return callback(OError.tag(err, 'CLSI request failed', { projectId }))
         }
         if (response.statusCode >= 200 && response.statusCode < 300) {
           callback(null, body)
         } else {
           callback(
-            new OError({
-              message: `CLSI returned non-success code: ${response.statusCode}`,
-              info: {
+            new OError(
+              `CLSI returned non-success code: ${response.statusCode}`,
+              {
                 projectId,
                 clsiResponse: body,
                 statusCode: response.statusCode
               }
-            })
+            )
           )
         }
       })

+ 5 - 8
services/web/app/src/Features/Email/EmailSender.js

@@ -28,10 +28,9 @@ function getClient() {
       logger.log('using aws ses for email')
       client = nodemailer.createTransport(sesTransport(emailParameters))
     } else if (emailParameters.sendgridApiKey) {
-      throw new OError({
-        message:
-          'sendgridApiKey configuration option is deprecated, use SMTP instead'
-      })
+      throw new OError(
+        'sendgridApiKey configuration option is deprecated, use SMTP instead'
+      )
     } else if (emailParameters.MandrillApiKey) {
       logger.log('using mandril for email')
       client = nodemailer.createTransport(
@@ -79,7 +78,7 @@ async function sendEmail(options) {
         },
         'rate limit hit for sending email, not sending'
       )
-      throw new OError({ message: 'rate limit hit sending email' })
+      throw new OError('rate limit hit sending email')
     }
     metrics.inc('email')
     let sendMailOptions = {
@@ -96,9 +95,7 @@ async function sendEmail(options) {
     }
     await client.sendMail(sendMailOptions)
   } catch (err) {
-    throw new OError({
-      message: 'error sending message'
-    }).withCause(err)
+    throw new OError('error sending message').withCause(err)
   }
 }
 

+ 32 - 35
services/web/app/src/Features/Errors/Errors.js

@@ -6,15 +6,27 @@ const settings = require('settings-sharelatex')
 // of object)
 class BackwardCompatibleError extends OError {
   constructor(messageOrOptions) {
-    let options
     if (typeof messageOrOptions === 'string') {
-      options = { message: messageOrOptions }
-    } else if (!messageOrOptions) {
-      options = {}
+      super(messageOrOptions)
+    } else if (messageOrOptions) {
+      const { message, info } = messageOrOptions
+      super(message, info)
     } else {
-      options = messageOrOptions
+      super()
+    }
+  }
+}
+
+// Error class that facilitates the migration to OError v3 by providing
+// a signature in which the 2nd argument can be an object containing
+// the `info` object.
+class OErrorV2CompatibleError extends OError {
+  constructor(message, options) {
+    if (options) {
+      super(message, options.info)
+    } else {
+      super(message)
     }
-    super(options)
   }
 }
 
@@ -42,12 +54,9 @@ class V1ConnectionError extends BackwardCompatibleError {}
 
 class UnconfirmedEmailError extends BackwardCompatibleError {}
 
-class EmailExistsError extends OError {
+class EmailExistsError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({
-      message: 'Email already exists',
-      ...options
-    })
+    super('Email already exists', options)
   }
 }
 
@@ -124,51 +133,39 @@ class ThirdPartyUserNotFoundError extends BackwardCompatibleError {
   }
 }
 
-class SubscriptionAdminDeletionError extends OError {
+class SubscriptionAdminDeletionError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({
-      message: 'subscription admins cannot be deleted',
-      ...options
-    })
+    super('subscription admins cannot be deleted', options)
   }
 }
 
-class ProjectNotFoundError extends OError {
+class ProjectNotFoundError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({
-      message: 'project not found',
-      ...options
-    })
+    super('project not found', options)
   }
 }
 
-class UserNotFoundError extends OError {
+class UserNotFoundError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({
-      message: 'user not found',
-      ...options
-    })
+    super('user not found', options)
   }
 }
 
-class UserNotCollaboratorError extends OError {
+class UserNotCollaboratorError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({
-      message: 'user not a collaborator',
-      ...options
-    })
+    super('user not a collaborator', options)
   }
 }
 
-class DocHasRangesError extends OError {
+class DocHasRangesError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({ message: 'document has ranges', ...options })
+    super('document has ranges', options)
   }
 }
 
-class InvalidQueryError extends OError {
+class InvalidQueryError extends OErrorV2CompatibleError {
   constructor(options) {
-    super({ message: 'invalid search query', ...options })
+    super('invalid search query', options)
   }
 }
 

+ 6 - 4
services/web/app/src/Features/FileStore/FileStoreHandler.js

@@ -7,6 +7,7 @@ const Async = require('async')
 const FileHashManager = require('./FileHashManager')
 const { File } = require('../../models/File')
 const Errors = require('../Errors/Errors')
+const OError = require('@overleaf/o-error')
 const { promisifyAll } = require('../../util/promises')
 
 const ONE_MIN_IN_MS = 60 * 1000
@@ -193,10 +194,11 @@ const FileStoreHandler = {
       err => {
         if (err) {
           return callback(
-            new Errors.OError({
-              message: 'something went wrong deleting a project in filestore',
-              info: { projectId }
-            }).withCause(err)
+            OError.tag(
+              err,
+              'something went wrong deleting a project in filestore',
+              { projectId }
+            )
           )
         }
         callback()

+ 6 - 15
services/web/app/src/Features/History/HistoryManager.js

@@ -39,9 +39,7 @@ async function initializeProject() {
     }
     return { overleaf_id: overleafId }
   } catch (err) {
-    throw new OError({
-      message: 'failed to initialize project history'
-    }).withCause(err)
+    throw OError.tag(err, 'failed to initialize project history')
   }
 }
 
@@ -51,10 +49,9 @@ async function flushProject(projectId) {
       url: `${settings.apis.project_history.url}/project/${projectId}/flush`
     })
   } catch (err) {
-    throw new OError({
-      message: 'failed to flush project to project history',
-      info: { projectId }
-    }).withCause(err)
+    throw OError.tag(err, 'failed to flush project to project history', {
+      projectId
+    })
   }
 }
 
@@ -64,10 +61,7 @@ async function resyncProject(projectId) {
       url: `${settings.apis.project_history.url}/project/${projectId}/resync`
     })
   } catch (err) {
-    throw new OError({
-      message: 'failed to resync project history',
-      info: { projectId }
-    })
+    throw new OError('failed to resync project history', { projectId })
   }
 }
 
@@ -77,10 +71,7 @@ async function deleteProject(projectId) {
       `${settings.apis.project_history.url}/project/${projectId}`
     )
   } catch (err) {
-    throw new OError({
-      message: 'failed to clear project history',
-      info: { projectId }
-    })
+    throw new OError('failed to clear project history', { projectId })
   }
 }
 

+ 27 - 24
services/web/app/src/Features/Newsletter/NewsletterManager.js

@@ -14,6 +14,12 @@ module.exports = {
   promises: provider
 }
 
+class NonFatalEmailUpdateError extends OError {
+  constructor(message, oldEmail, newEmail) {
+    super(message, { oldEmail, newEmail })
+  }
+}
+
 function getProvider() {
   if (mailchimpIsConfigured()) {
     logger.info('Using newsletter provider: mailchimp')
@@ -49,10 +55,9 @@ function makeMailchimpProvider() {
       })
       logger.info({ user }, 'finished subscribing user to newsletter')
     } catch (err) {
-      throw new OError({
-        message: 'error subscribing user to newsletter',
-        info: { userId: user._id }
-      }).withCause(err)
+      throw OError.tag(err, 'error subscribing user to newsletter', {
+        userId: user._id
+      })
     }
   }
 
@@ -85,10 +90,9 @@ function makeMailchimpProvider() {
         return
       }
 
-      throw new OError({
-        message: 'error unsubscribing user from newsletter',
-        info: { userId: user._id }
-      }).withCause(err)
+      throw OError.tag(err, 'error unsubscribing user from newsletter', {
+        userId: user._id
+      })
     }
   }
 
@@ -109,15 +113,14 @@ function makeMailchimpProvider() {
         await unsubscribe(user, { delete: true })
       } catch (unsubscribeError) {
         // something went wrong removing the user's address
-        throw new OError({
-          message:
-            'error unsubscribing old email in response to email change failure',
-          info: { oldEmail, newEmail, updateError }
-        }).withCause(unsubscribeError)
+        throw OError.tag(
+          unsubscribeError,
+          'error unsubscribing old email in response to email change failure',
+          { oldEmail, newEmail, updateError }
+        )
       }
 
-      // throw the error, unless it was an expected one that we can ignore
-      if (!updateError.info || !updateError.info.nonFatal) {
+      if (!(updateError instanceof NonFatalEmailUpdateError)) {
         throw updateError
       }
     }
@@ -155,19 +158,19 @@ function makeMailchimpProvider() {
 
           logger.info({ oldEmail, newEmail }, message)
 
-          // throw a non-fatal error
-          throw new OError({
-            message: message,
-            info: { oldEmail, newEmail, nonFatal: true }
-          }).withCause(err)
+          throw new NonFatalEmailUpdateError(
+            message,
+            oldEmail,
+            newEmail
+          ).withCause(err)
         }
       })
 
       // if we didn't find an expected error, generate something to throw
-      throw new OError({
-        message: 'error changing email in newsletter',
-        info: { oldEmail, newEmail }
-      }).withCause(err)
+      throw OError.tag(err, 'error changing email in newsletter', {
+        oldEmail,
+        newEmail
+      })
     }
   }
 

+ 1 - 1
services/web/app/src/Features/Project/FolderStructureBuilder.js

@@ -56,7 +56,7 @@ class FolderStructureBuilder {
 
   recordEntityPath(path) {
     if (this.entityPaths.has(path)) {
-      throw new OError({ message: 'entity already exists', info: { path } })
+      throw new OError('entity already exists', { path })
     }
     this.entityPaths.add(path)
   }

+ 1 - 4
services/web/app/src/Features/Project/ProjectAuditLogHandler.js

@@ -35,9 +35,6 @@ async function addEntry(projectId, operation, initiatorId, info = {}) {
     }
   ).exec()
   if (result.nModified === 0) {
-    throw new OError({
-      message: 'project not found',
-      info: { projectId }
-    })
+    throw new OError('project not found', { projectId })
   }
 }

+ 5 - 8
services/web/app/src/Features/Project/ProjectDuplicator.js

@@ -73,14 +73,11 @@ async function duplicate(owner, originalProjectId, newProjectName) {
     // Clean up broken clone on error.
     // Make sure we delete the new failed project, not the original one!
     await ProjectDeleter.promises.deleteProject(newProject._id)
-    throw new OError({
-      message: 'error cloning project, broken clone deleted',
-      info: {
-        originalProjectId,
-        newProjectName,
-        newProjectId: newProject._id
-      }
-    }).withCause(err)
+    throw OError.tag(err, 'error cloning project, broken clone deleted', {
+      originalProjectId,
+      newProjectName,
+      newProjectId: newProject._id
+    })
   }
   return newProject
 }

+ 3 - 7
services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js

@@ -694,17 +694,13 @@ async function createNewFolderStructure(projectId, docEntries, fileEntries) {
       }
     ).exec()
     if (project == null) {
-      throw new OError({
-        message: 'project not found or folder structure already exists',
-        info: { projectId }
+      throw new OError('project not found or folder structure already exists', {
+        projectId
       })
     }
     return project.version
   } catch (err) {
-    throw new OError({
-      message: 'failed to create folder structure',
-      info: { projectId }
-    }).withCause(err)
+    throw OError.tag(err, 'failed to create folder structure', { projectId })
   }
 }
 

+ 18 - 27
services/web/app/src/Features/Spelling/SpellingHandler.js

@@ -10,20 +10,16 @@ module.exports = {
     request.get({ url: url, timeout: TIMEOUT }, (error, response) => {
       if (error) {
         return callback(
-          new OError({
-            message: 'error getting user dictionary',
-            info: { error, userId }
-          }).withCause(error)
+          OError.tag(error, 'error getting user dictionary', { error, userId })
         )
       }
 
       if (response.statusCode < 200 || response.statusCode >= 300) {
         return callback(
-          new OError({
-            message:
-              'Non-success code from spelling API when getting user dictionary',
-            info: { userId, statusCode: response.statusCode }
-          })
+          new OError(
+            'Non-success code from spelling API when getting user dictionary',
+            { userId, statusCode: response.statusCode }
+          )
         )
       }
 
@@ -44,20 +40,19 @@ module.exports = {
       (error, response) => {
         if (error) {
           return callback(
-            new OError({
-              message: 'error deleting word from user dictionary',
-              info: { error, userId, word }
-            }).withCause(error)
+            OError.tag(error, 'error deleting word from user dictionary', {
+              userId,
+              word
+            })
           )
         }
 
         if (response.statusCode < 200 || response.statusCode >= 300) {
           return callback(
-            new OError({
-              message:
-                'Non-success code from spelling API when removing word from user dictionary',
-              info: { userId, word, statusCode: response.statusCode }
-            })
+            new OError(
+              'Non-success code from spelling API when removing word from user dictionary',
+              { userId, word, statusCode: response.statusCode }
+            )
           )
         }
 
@@ -71,20 +66,16 @@ module.exports = {
     request.delete({ url: url, timeout: TIMEOUT }, (error, response) => {
       if (error) {
         return callback(
-          new OError({
-            message: 'error deleting user dictionary',
-            info: { userId }
-          }).withCause(error)
+          OError.tag(error, 'error deleting user dictionary', { userId })
         )
       }
 
       if (response.statusCode < 200 || response.statusCode >= 300) {
         return callback(
-          new OError({
-            message:
-              'Non-success code from spelling API when removing user dictionary',
-            info: { userId, statusCode: response.statusCode }
-          })
+          new OError(
+            'Non-success code from spelling API when removing user dictionary',
+            { userId, statusCode: response.statusCode }
+          )
         )
       }
 

+ 2 - 2
services/web/app/src/Features/Subscription/Errors.js

@@ -1,6 +1,6 @@
-const OError = require('@overleaf/o-error')
+const Errors = require('../Errors/Errors')
 
-class RecurlyTransactionError extends OError {
+class RecurlyTransactionError extends Errors.BackwardCompatibleError {
   constructor(options) {
     super({
       message: 'Unknown transaction error',

+ 9 - 12
services/web/app/src/Features/TokenAccess/TokenAccessController.js

@@ -93,10 +93,7 @@ async function tokenAccessPage(req, res, next) {
     })
   } catch (err) {
     return next(
-      new OError({
-        message: 'error while rendering token access page',
-        info: { token }
-      }).withCause(err)
+      OError.tag(err, 'error while rendering token access page', { token })
     )
   }
 }
@@ -244,10 +241,11 @@ async function grantTokenAccessReadAndWrite(req, res, next) {
     })
   } catch (err) {
     return next(
-      new OError({
-        message: 'error while trying to grant read-and-write token access',
-        info: { token }
-      }).withCause(err)
+      OError.tag(
+        err,
+        'error while trying to grant read-and-write token access',
+        { token }
+      )
     )
   }
 }
@@ -290,10 +288,9 @@ async function grantTokenAccessReadOnly(req, res, next) {
     })
   } catch (err) {
     return next(
-      new OError({
-        message: 'error while trying to grant read-only token access',
-        info: { token }
-      }).withCause(err)
+      OError.tag(err, 'error while trying to grant read-only token access', {
+        token
+      })
     )
   }
 }

+ 2 - 2
services/web/app/src/Features/Uploads/ArchiveErrors.js

@@ -1,6 +1,6 @@
-const OError = require('@overleaf/o-error')
+const Errors = require('../Errors/Errors')
 
-class InvalidZipFileError extends OError {
+class InvalidZipFileError extends Errors.BackwardCompatibleError {
   constructor(options) {
     super({
       message: 'invalid_zip_file',

+ 1 - 1
services/web/app/src/Features/User/SAMLIdentityManager.js

@@ -60,7 +60,7 @@ async function _addIdentifier(
     if (err.code === 11000) {
       throw new Errors.SAMLIdentityExistsError()
     } else {
-      throw new OError(err)
+      throw OError.tag(err)
     }
   }
 }

+ 1 - 4
services/web/app/src/Features/User/UserAuditLogHandler.js

@@ -32,10 +32,7 @@ async function addEntry(userId, operation, initiatorId, ipAddress, info = {}) {
     }
   ).exec()
   if (result.nModified === 0) {
-    throw new OError({
-      message: 'user not found',
-      info: { userId }
-    })
+    throw new OError('user not found', { userId })
   }
 }
 

+ 6 - 6
services/web/app/src/Features/User/UserController.js

@@ -213,7 +213,7 @@ const UserController = {
                 errorData.info.public = {
                   error: 'SubscriptionAdminDeletionError'
                 }
-                logger.warn(new OError(errorData).withCause(err))
+                logger.warn(OError.tag(err, errorData.message, errorData.info))
                 return HttpErrorHandler.unprocessableEntity(
                   req,
                   res,
@@ -221,7 +221,7 @@ const UserController = {
                   errorData.info.public
                 )
               } else {
-                return next(new OError(errorData).withCause(err))
+                return next(OError.tag(err, errorData.message, errorData.info))
               }
             }
             const sessionId = req.sessionID
@@ -350,10 +350,10 @@ const UserController = {
                   req,
                   res,
                   req.i18n.translate('problem_changing_email_address'),
-                  new OError({
-                    message: 'problem_changing_email_address',
-                    info: { userId, newEmail }
-                  }).withCause(err)
+                  OError.tag(err, 'problem_changing_email_address', {
+                    userId,
+                    newEmail
+                  })
                 )
               }
             }

+ 4 - 7
services/web/app/src/infrastructure/SessionAutostartMiddleware.js

@@ -1,5 +1,5 @@
 const Settings = require('settings-sharelatex')
-const Errors = require('../Features/Errors/Errors')
+const OError = require('@overleaf/o-error')
 
 // SessionAutostartMiddleware provides a mechanism to force certain routes not
 // to get an automatic session where they don't have one already. This allows us
@@ -75,12 +75,9 @@ class SessionAutostartMiddleware {
   static genericPostGatewayMiddleware(req, res, next) {
     if (req.method !== 'POST') {
       return next(
-        new Errors.OError({
-          message: 'post gateway invoked for non-POST request',
-          info: {
-            path: req.path,
-            method: req.method
-          }
+        new OError('post gateway invoked for non-POST request', {
+          path: req.path,
+          method: req.method
         })
       )
     }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 188 - 391
services/web/package-lock.json


+ 2 - 2
services/web/package.json

@@ -40,7 +40,7 @@
     "@babel/core": "^7.9.0",
     "@babel/preset-env": "^7.9.5",
     "@babel/preset-react": "^7.9.4",
-    "@overleaf/o-error": "^2.1.0",
+    "@overleaf/o-error": "^3.0.0",
     "@pollyjs/adapter-node-http": "^4.2.1",
     "@pollyjs/core": "^4.2.1",
     "@pollyjs/persister-fs": "^4.2.1",
@@ -83,7 +83,7 @@
     "json2csv": "^4.3.3",
     "jsonwebtoken": "^8.5.1",
     "lodash": "^4.17.15",
-    "logger-sharelatex": "^1.9.1",
+    "logger-sharelatex": "^2.1.1",
     "mailchimp-api-v3": "^1.12.0",
     "marked": "^0.3.5",
     "method-override": "^2.3.3",

+ 6 - 2
services/web/test/unit/src/FileStore/FileStoreHandlerTests.js

@@ -3,6 +3,7 @@ const sinon = require('sinon')
 const { expect } = require('chai')
 const SandboxedModule = require('sandboxed-module')
 const Errors = require('../../../../app/src/Features/Errors/Errors')
+const OError = require('@overleaf/o-error')
 
 const MODULE_PATH = '../../../../app/src/Features/FileStore/FileStoreHandler.js'
 
@@ -283,10 +284,13 @@ describe('FileStoreHandler', function() {
     })
 
     it('should wrap the error if there is one', function(done) {
-      const error = 'my error'
+      const error = new Error('my error')
       this.request.callsArgWith(1, error)
       this.handler.deleteProject(this.projectId, err => {
-        assert.equal(err.cause, error)
+        expect(OError.getFullStack(err)).to.match(
+          /something went wrong deleting a project in filestore/
+        )
+        expect(OError.getFullStack(err)).to.match(/my error/)
         done()
       })
     })

+ 22 - 0
services/web/test/unit/src/Newsletter/NewsletterManagerTests.js

@@ -154,5 +154,27 @@ describe('NewsletterManager', function() {
         `/lists/list_id/members/${this.emailHash}`
       )
     })
+
+    it('does not reject on non-fatal error ', async function() {
+      const nonFatalError = new Error('merge fields were invalid')
+      this.mailchimp.patch.rejects(nonFatalError)
+      await expect(
+        this.NewsletterManager.changeEmail(
+          this.user,
+          'overleaf.squirrel@example.com'
+        )
+      ).to.be.fulfilled
+    })
+
+    it('rejects on any other error', async function() {
+      const fatalError = new Error('fatal error')
+      this.mailchimp.patch.rejects(fatalError)
+      await expect(
+        this.NewsletterManager.changeEmail(
+          this.user,
+          'overleaf.squirrel@example.com'
+        )
+      ).to.be.rejected
+    })
   })
 })

+ 1 - 1
services/web/test/unit/src/User/UserControllerTests.js

@@ -432,7 +432,7 @@ describe('UserController', function() {
     describe('when changeEmailAddress yields an error', function() {
       it('should pass on an error and not send a success status', function(done) {
         this.req.body.email = this.newEmail.toUpperCase()
-        this.UserUpdater.changeEmailAddress.callsArgWith(2, new Error())
+        this.UserUpdater.changeEmailAddress.callsArgWith(2, new OError())
         this.HttpErrorHandler.legacyInternal = sinon.spy(
           (req, res, message, error) => {
             expect(req).to.exist

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels