Browse Source

storage-node-v2: Update state API error codes.

Shamil Gadelshin 3 years ago
parent
commit
b5dc5696d8

+ 1 - 0
storage-node-v2/src/command-base/ExitCodes.ts

@@ -9,6 +9,7 @@ enum ExitCodes {
   DevelopmentModeOnly,
   FileError,
   InvalidWorkerId,
+  InvalidIntegerArray,
   ApiError = 200,
   UnsuccessfulRuntimeCall,
 }

+ 4 - 1
storage-node-v2/src/commands/leader/update-bag.ts

@@ -4,6 +4,7 @@ import ApiCommandBase from '../../command-base/ApiCommandBase'
 import logger from '../../services/logger'
 import ExitCodes from '../../command-base/ExitCodes'
 import _ from 'lodash'
+import { CLIError } from '@oclif/errors'
 
 // Custom 'integer array' oclif flag.
 const integerArrFlags = {
@@ -11,7 +12,9 @@ const integerArrFlags = {
     parse: (value: string) => {
       const arr: number[] = value.split(',').map((v) => {
         if (!/^-?\d+$/.test(v)) {
-          throw new Error(`Expected comma-separated integers, but received: ${value}`)
+          throw new CLIError(`Expected comma-separated integers, but received: ${value}`, {
+            exit: ExitCodes.InvalidIntegerArray,
+          })
         }
         return parseInt(v)
       })

+ 1 - 1
storage-node-v2/src/services/runtime/api.ts

@@ -211,7 +211,7 @@ export function getEvent<
   const event = result.findRecord(section, eventName)?.event as EventType | undefined
 
   if (!event) {
-    throw new Error(`Cannot find expected ${section}.${eventName} event in result: ${result.toHuman()}`)
+    throw new ExtrinsicFailedError(`Cannot find expected ${section}.${eventName} event in result: ${result.toHuman()}`)
   }
   return event as EventType
 }

+ 1 - 1
storage-node-v2/src/services/sync/tasks.ts

@@ -74,7 +74,7 @@ export class DownloadFileTask implements SyncTask {
       const timeoutMs = 30 * 60 * 1000 // 30 min for large files (~ 10 GB)
       // Casting because of:
       // https://stackoverflow.com/questions/38478034/pipe-superagent-response-to-express-response
-      const request = (superagent.get(this.url).timeout(timeoutMs) as unknown) as NodeJS.ReadableStream
+      const request = superagent.get(this.url).timeout(timeoutMs) as unknown as NodeJS.ReadableStream
 
       // We create tempfile first to mitigate partial downloads on app (or remote node) crash.
       // This partial downloads will be cleaned up during the next sync iteration.

+ 79 - 2
storage-node-v2/src/services/webApi/controllers/common.ts

@@ -1,5 +1,7 @@
 import * as express from 'express'
 import { CLIError } from '@oclif/errors'
+import { ExtrinsicFailedError } from '../../runtime/api'
+import { BagIdValidationError } from 'src/services/helpers/bagTypes'
 
 /**
  * Dedicated error for the web api requests.
@@ -50,7 +52,7 @@ export function getTempFileUploadingDir(res: express.Response): string {
     return res.locals.tempFileUploadingDir
   }
 
-  throw new Error('No temporary uploading directory path loaded.')
+  throw new ServerError('No temporary uploading directory path loaded.')
 }
 
 /**
@@ -80,5 +82,80 @@ export function getQueryNodeUrl(res: express.Response): string {
     return res.locals.queryNodeUrl
   }
 
-  throw new Error('No Query Node URL loaded.')
+  throw new ServerError('No Query Node URL loaded.')
+}
+
+/**
+ * Returns a command config.
+ *
+ * @remarks
+ * This is a helper function. It parses the response object for a variable and
+ * throws an error on failure.
+ */
+export function getCommandConfig(res: express.Response): {
+  version: string
+  userAgent: string
+} {
+  if (res.locals.config) {
+    return res.locals.config
+  }
+
+  throw new ServerError('Cannot load command config.')
+}
+
+/**
+ * Handles errors and sends a response.
+ *
+ * @param res - Response instance
+ * @param err - error
+ * @param errorType - defines request type
+ * @returns void promise.
+ */
+export function sendResponseWithError(res: express.Response, err: Error, errorType: string): void {
+  const message = isNofileError(err) ? `File not found.` : err.toString()
+
+  res.status(getHttpStatusCodeByError(err)).json({
+    type: errorType,
+    message,
+  })
+}
+
+/**
+ * Checks the error for 'no-file' error (ENOENT).
+ *
+ * @param err - error
+ * @returns true when error code contains 'ENOENT'.
+ */
+function isNofileError(err: Error): boolean {
+  return err.toString().includes('ENOENT')
+}
+
+/**
+ * Get the status code by error.
+ *
+ * @param err - error
+ * @returns HTTP status code
+ */
+export function getHttpStatusCodeByError(err: Error): number {
+  if (isNofileError(err)) {
+    return 404
+  }
+
+  if (err instanceof ExtrinsicFailedError) {
+    return 400
+  }
+
+  if (err instanceof WebApiError) {
+    return err.httpStatusCode
+  }
+
+  if (err instanceof CLIError) {
+    return 400
+  }
+
+  if (err instanceof BagIdValidationError) {
+    return 400
+  }
+
+  return 500
 }

+ 12 - 78
storage-node-v2/src/services/webApi/controllers/publicApi.ts

@@ -1,5 +1,4 @@
 import { acceptPendingDataObjects } from '../../runtime/extrinsics'
-import { ExtrinsicFailedError } from '../../runtime/api'
 import {
   RequestData,
   UploadTokenRequest,
@@ -18,11 +17,19 @@ import * as express from 'express'
 import fs from 'fs'
 import path from 'path'
 import send from 'send'
-import { CLIError } from '@oclif/errors'
 import { hexToString } from '@polkadot/util'
 import { parseBagId } from '../../helpers/bagTypes'
 import { timeout } from 'promise-timeout'
-import { getUploadsDir, getWorkerId, getQueryNodeUrl, WebApiError, ServerError } from './common'
+import {
+  getUploadsDir,
+  getWorkerId,
+  getQueryNodeUrl,
+  WebApiError,
+  ServerError,
+  getCommandConfig,
+  sendResponseWithError,
+  getHttpStatusCodeByError,
+} from './common'
 import { getStorageBucketIdsByWorkerId } from '../../../services/sync/storageObligations'
 import { Membership } from '@joystream/types/members'
 const fsPromises = fs.promises
@@ -260,7 +267,7 @@ async function validateTokenRequest(api: ApiPromise, tokenRequest: UploadTokenRe
   const membership = (await timeout(membershipPromise, 5000)) as Membership
 
   if (membership.controller_account.toString() !== tokenRequest.data.accountId) {
-    throw new Error(`Provided controller account and member id don't match.`)
+    throw new WebApiError(`Provided controller account and member id don't match.`, 401)
   }
 }
 
@@ -319,59 +326,6 @@ async function cleanupFileOnError(cleanupFileName: string, error: string): Promi
   }
 }
 
-/**
- * Handles errors and sends a response.
- *
- * @param res - Response instance
- * @param err - error
- * @param errorType - defines request type
- * @returns void promise.
- */
-function sendResponseWithError(res: express.Response, err: Error, errorType: string): void {
-  const message = isNofileError(err) ? `File not found.` : err.toString()
-
-  res.status(getHttpStatusCodeByError(err)).json({
-    type: errorType,
-    message,
-  })
-}
-
-/**
- * Checks the error for 'no-file' error (ENOENT).
- *
- * @param err - error
- * @returns true when error code contains 'ENOENT'.
- */
-function isNofileError(err: Error): boolean {
-  return err.toString().includes('ENOENT')
-}
-
-/**
- * Get the status code by error.
- *
- * @param err - error
- * @returns HTTP status code
- */
-function getHttpStatusCodeByError(err: Error): number {
-  if (isNofileError(err)) {
-    return 404
-  }
-
-  if (err instanceof ExtrinsicFailedError) {
-    return 400
-  }
-
-  if (err instanceof WebApiError) {
-    return err.httpStatusCode
-  }
-
-  if (err instanceof CLIError) {
-    return 400
-  }
-
-  return 500
-}
-
 /**
  * A public endpoint: return the server version.
  */
@@ -392,26 +346,6 @@ export async function getVersion(req: express.Request, res: express.Response): P
   }
 }
 
-/**
- * Returns a command config.
- *
- * @remarks
- * This is a helper function. It parses the response object for a variable and
- * throws an error on failure.
- */
-function getCommandConfig(
-  res: express.Response
-): {
-  version: string
-  userAgent: string
-} {
-  if (res.locals.config) {
-    return res.locals.config
-  }
-
-  throw new Error('No upload directory path loaded.')
-}
-
 /**
  * Validates the storage bucket ID obligations for the worker (storage provider).
  * It throws an error when storage bucket doesn't belong to the worker.
@@ -425,7 +359,7 @@ async function verifyBucketId(queryNodeUrl: string, workerId: number, bucketId:
   const bucketIds = await getStorageBucketIdsByWorkerId(queryNodeUrl, workerId)
 
   if (!bucketIds.includes(bucketId.toString())) {
-    throw new Error('Incorrect storage bucket ID.')
+    throw new WebApiError('Incorrect storage bucket ID.', 400)
   }
 }
 

+ 12 - 37
storage-node-v2/src/services/webApi/controllers/stateApi.ts

@@ -2,7 +2,14 @@ import { getLocalDataObjects } from '../../../services/sync/synchronizer'
 import * as express from 'express'
 import _ from 'lodash'
 import { getDataObjectIDsByBagId } from '../../sync/storageObligations'
-import { getUploadsDir, getTempFileUploadingDir, getQueryNodeUrl, WebApiError } from './common'
+import {
+  getUploadsDir,
+  getTempFileUploadingDir,
+  getQueryNodeUrl,
+  WebApiError,
+  getCommandConfig,
+  sendResponseWithError,
+} from './common'
 import fastFolderSize from 'fast-folder-size'
 import { promisify } from 'util'
 import fs from 'fs'
@@ -30,10 +37,7 @@ export async function getAllLocalDataObjects(req: express.Request, res: express.
 
     res.status(200).json(cids)
   } catch (err) {
-    res.status(500).json({
-      type: 'all_data_objects',
-      message: err.toString(),
-    })
+    sendResponseWithError(res, err, 'all_data_objects')
   }
 }
 
@@ -78,10 +82,7 @@ export async function getLocalDataStats(req: express.Request, res: express.Respo
       tempDirSize,
     })
   } catch (err) {
-    res.status(500).json({
-      type: 'local_data_stats',
-      message: err.toString(),
-    })
+    sendResponseWithError(res, err, 'local_data_stats')
   }
 }
 
@@ -104,10 +105,7 @@ export async function getLocalDataObjectsByBagId(req: express.Request, res: expr
 
     res.status(200).json(localDataForBag)
   } catch (err) {
-    res.status(500).json({
-      type: 'data_objects_by_bag',
-      message: err.toString(),
-    })
+    sendResponseWithError(res, err, 'data_objects_by_bag')
   }
 }
 
@@ -124,33 +122,10 @@ export async function getVersion(req: express.Request, res: express.Response): P
       userAgent: config.userAgent,
     })
   } catch (err) {
-    res.status(500).json({
-      type: 'version',
-      message: err.toString(),
-    })
+    sendResponseWithError(res, err, 'version')
   }
 }
 
-/**
- * Returns a command config.
- *
- * @remarks
- * This is a helper function. It parses the response object for a variable and
- * throws an error on failure.
- */
-function getCommandConfig(
-  res: express.Response
-): {
-  version: string
-  userAgent: string
-} {
-  if (res.locals.config) {
-    return res.locals.config
-  }
-
-  throw new Error('No upload directory path loaded.')
-}
-
 /**
  * Returns Bag ID from the request.
  *