Standardize error responses with consistent format and human-readable messages (#667)

This commit is contained in:
Thomas Durieux
2026-04-15 09:27:08 +02:00
committed by GitHub
parent f4209110c7
commit 8198a4b44a
10 changed files with 332 additions and 127 deletions
+101 -106
View File
@@ -70,7 +70,7 @@
"gulp-order": "^1.2.0",
"gulp-uglify": "^3.0.2",
"knip": "^5.1.0",
"mocha": "^10.4.0",
"mocha": "^10.8.2",
"nodemon": "^3.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.3",
@@ -8222,10 +8222,11 @@
}
},
"node_modules/ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
@@ -9584,10 +9585,11 @@
}
},
"node_modules/diff": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
"integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
@@ -12546,31 +12548,32 @@
}
},
"node_modules/mocha": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz",
"integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==",
"version": "10.8.2",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
"integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
"chokidar": "3.5.3",
"debug": "4.3.4",
"diff": "5.0.0",
"escape-string-regexp": "4.0.0",
"find-up": "5.0.0",
"glob": "8.1.0",
"he": "1.2.0",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "5.0.1",
"ms": "2.1.3",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
"workerpool": "6.2.1",
"yargs": "16.2.0",
"yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0"
"ansi-colors": "^4.1.3",
"browser-stdout": "^1.3.1",
"chokidar": "^3.5.3",
"debug": "^4.3.5",
"diff": "^5.2.0",
"escape-string-regexp": "^4.0.0",
"find-up": "^5.0.0",
"glob": "^8.1.0",
"he": "^1.2.0",
"js-yaml": "^4.1.0",
"log-symbols": "^4.1.0",
"minimatch": "^5.1.6",
"ms": "^2.1.3",
"serialize-javascript": "^6.0.2",
"strip-json-comments": "^3.1.1",
"supports-color": "^8.1.1",
"workerpool": "^6.5.1",
"yargs": "^16.2.0",
"yargs-parser": "^20.2.9",
"yargs-unparser": "^2.0.0"
},
"bin": {
"_mocha": "bin/_mocha",
@@ -12601,12 +12604,13 @@
}
},
"node_modules/mocha/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -12617,12 +12621,6 @@
}
}
},
"node_modules/mocha/node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/mocha/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -12655,10 +12653,11 @@
}
},
"node_modules/mocha/node_modules/minimatch": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -14004,6 +14003,7 @@
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.1.0"
}
@@ -14451,10 +14451,11 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serialize-javascript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"randombytes": "^2.1.0"
}
@@ -15710,10 +15711,11 @@
}
},
"node_modules/workerpool": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
"integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
@@ -15774,10 +15776,11 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yargs-parser": {
"version": "20.2.4",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
@@ -22210,9 +22213,9 @@
}
},
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"dev": true
},
"ansi-escapes": {
@@ -23226,9 +23229,9 @@
"dev": true
},
"diff": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
"integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
"dev": true
},
"dom-serializer": {
@@ -25398,31 +25401,31 @@
}
},
"mocha": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz",
"integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==",
"version": "10.8.2",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
"integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
"dev": true,
"requires": {
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
"chokidar": "3.5.3",
"debug": "4.3.4",
"diff": "5.0.0",
"escape-string-regexp": "4.0.0",
"find-up": "5.0.0",
"glob": "8.1.0",
"he": "1.2.0",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "5.0.1",
"ms": "2.1.3",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
"workerpool": "6.2.1",
"yargs": "16.2.0",
"yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0"
"ansi-colors": "^4.1.3",
"browser-stdout": "^1.3.1",
"chokidar": "^3.5.3",
"debug": "^4.3.5",
"diff": "^5.2.0",
"escape-string-regexp": "^4.0.0",
"find-up": "^5.0.0",
"glob": "^8.1.0",
"he": "^1.2.0",
"js-yaml": "^4.1.0",
"log-symbols": "^4.1.0",
"minimatch": "^5.1.6",
"ms": "^2.1.3",
"serialize-javascript": "^6.0.2",
"strip-json-comments": "^3.1.1",
"supports-color": "^8.1.1",
"workerpool": "^6.5.1",
"yargs": "^16.2.0",
"yargs-parser": "^20.2.9",
"yargs-unparser": "^2.0.0"
},
"dependencies": {
"brace-expansion": {
@@ -25446,20 +25449,12 @@
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
"ms": "^2.1.3"
}
},
"escape-string-regexp": {
@@ -25482,9 +25477,9 @@
}
},
"minimatch": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
@@ -26767,9 +26762,9 @@
}
},
"serialize-javascript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
@@ -27716,9 +27711,9 @@
"dev": true
},
"workerpool": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
"integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
"dev": true
},
"wrap-ansi": {
@@ -27764,9 +27759,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yargs-parser": {
"version": "20.2.4",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true
},
"yargs-unparser": {
+1 -1
View File
@@ -89,7 +89,7 @@
"gulp-order": "^1.2.0",
"gulp-uglify": "^3.0.2",
"knip": "^5.1.0",
"mocha": "^10.4.0",
"mocha": "^10.8.2",
"nodemon": "^3.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.3",
+54 -9
View File
@@ -3,37 +3,82 @@
"unknown_error": "Unknown error, contact the admin.",
"unreachable": "Anonymous GitHub is unreachable, contact the admin.",
"request_error": "Unable to download the file, check your connection or contact the admin.",
"not_found": "The requested resource could not be found.",
"not_connected": "You must be logged in to perform this action.",
"not_authorized": "You do not have permission to perform this action.",
"unable_to_connect_user": "Unable to connect your account. Please try again later.",
"user_not_found": "The requested user could not be found.",
"repo_access_limited": "Access to repository limited by org.",
"repo_not_found": "The repository is not found.",
"repo_not_accessible": "Anonymous GitHub is unable to or is forbidden to access the repository.",
"repository_expired": "The repository is expired",
"repository_expired": "The repository is expired.",
"repository_not_ready": "Anonymous GitHub is still processing the repository, it can take several minutes.",
"repository_not_accessible": "This repository is currently not accessible.",
"repo_is_updating": "Anonymous GitHub is still processing the repository, it can take several minutes.",
"repoUrl_not_defined": "The repository url needs to be defined.",
"repoId_already_used": "The repository id is already used.",
"invalid_repoId": "The format of the repository id is invalid.",
"invalid_repo": "The provided repository is not valid.",
"repoId_not_defined": "A repository ID must be provided.",
"repoUrl_not_defined": "The repository URL needs to be defined.",
"repoId_already_used": "The repository ID is already used.",
"invalid_repoId": "The format of the repository ID is invalid.",
"unsupported_source": "The repository source type is not supported.",
"branch_not_specified": "The branch is not specified.",
"branch_not_found": "The branch of the repository cannot be found.",
"commit_not_specified": "A commit must be specified.",
"invalid_commit_format": "The commit hash format is invalid. It must be a hexadecimal string.",
"pull_request_not_found": "The requested pull request could not be found.",
"pull_request_expired": "This pull request has expired and is no longer available.",
"pull_request_not_ready": "This pull request is still being prepared. Please try again shortly.",
"pull_request_not_available": "This pull request is currently not available.",
"invalid_pullRequestId": "The pull request ID is invalid.",
"pullRequestId_already_used": "This pull request ID is already in use. Please choose a different one.",
"repository_not_specified": "A repository must be specified for this pull request.",
"pullRequestId_not_specified": "A pull request ID must be specified.",
"pullRequestId_is_not_a_number": "The source pull request ID must be a number.",
"options_not_provided": "Anonymization options are mandatory.",
"terms_not_specified": "Anonymization terms must be specified.",
"invalid_terms_format": "Terms are in an invalid format.",
"unable_to_anonymize": "An error happened during the anonymization process. Please try later or report the issue.",
"non_supported_mode": "The selected anonymization mode is invalid, only download and stream are supported.",
"invalid_path": "The provided path is invalid or missing.",
"path_not_specified": "A file path must be specified.",
"path_not_defined": "The file path has not been resolved yet.",
"no_file_selected": "Please select a file.",
"file_not_found": "The requested file is not found.",
"file_not_accessible": "The requested file is not accessible.",
"file_not_supported": "The file type is not supported. Anonymous GitHub cannot handle it.",
"file_too_big": "The file size exceed the limit of Anonymous GitHub.",
"file_too_big": "The file size exceeds the limit of Anonymous GitHub.",
"is_folder": "The path points to a folder.",
"folder_not_supported": "The path points to a folder. Please select a file.",
"unable_to_write_file": "Unable to write file on disk.",
"download_not_enabled": "Repository downloads are not enabled on this server.",
"unable_to_download": "The repository could not be downloaded. Please try again later.",
"s3_config_not_provided": "Object storage has not been configured on this server.",
"stats_unsupported": "Statistics are only supported in download mode.",
"branches_not_found": "The requested branch is not found.",
"readme_not_available": "No readme for the repository is found.",
"user_not_found": "The current user is not found in the database",
"not_connected": "User is not connected",
"readme_not_available": "No README for the repository is found.",
"page_not_supported_on_different_branch": "Anonymized GitHub pages are only supported on the same branch.",
"page_not_activated": "Anonymized GitHub page is not enabled.",
"conf_not_activated": "The conference is not activated."
"is_removed": "This resource has been removed and is no longer available.",
"conf_name_missing": "A conference name is required.",
"conf_id_missing": "A conference ID is required.",
"conf_id_used": "This conference ID is already in use. Please choose a different one.",
"conf_start_date_missing": "A start date is required for the conference.",
"conf_end_date_missing": "An end date is required for the conference.",
"conf_start_date_invalid": "The start date must be before the end date.",
"conf_end_date_invalid": "The end date must be in the future.",
"invalid_plan": "The selected plan is not valid.",
"conference_not_found": "The requested conference could not be found.",
"conf_not_found": "The requested conference could not be found.",
"conf_not_activated": "The conference is not activated.",
"billing_missing": "Billing information is required for this plan.",
"billing_name_missing": "A billing name is required.",
"billing_email_missing": "A billing email is required.",
"billing_address_missing": "A billing address is required.",
"billing_city_missing": "A billing city is required.",
"billing_zip_missing": "A billing ZIP/postal code is required.",
"billing_country_missing": "A billing country is required.",
"queue_not_found": "The specified queue could not be found.",
"job_not_found": "The specified job could not be found in the queue.",
"error_retrying_job": "An error occurred while retrying the job."
}
}
+3 -2
View File
@@ -114,7 +114,7 @@ export default class PullRequest {
this.status == "removing" ||
this.status == "removed"
) {
throw new AnonymousError("pullRequest_expired", {
throw new AnonymousError("pull_request_expired", {
object: this,
httpStatus: 410,
});
@@ -126,8 +126,9 @@ export default class PullRequest {
this.status == "preparing" ||
(this.status == "download" && this._model.statusDate > fiveMinuteAgo)
) {
throw new AnonymousError("pullRequest_not_ready", {
throw new AnonymousError("pull_request_not_ready", {
object: this,
httpStatus: 503,
});
}
}
+2
View File
@@ -220,6 +220,7 @@ export default class Repository {
) {
throw new AnonymousError("repository_not_ready", {
object: this,
httpStatus: 503,
});
}
}
@@ -356,6 +357,7 @@ export default class Repository {
await this.resetSate();
throw new AnonymousError("branch_not_found", {
object: this,
httpStatus: 404,
});
}
this._model.anonymizeDate = new Date();
+1 -1
View File
@@ -127,7 +127,7 @@ export default class S3Storage extends StorageBase {
}
} catch {
try {
res.status(500);
res.status(500).json({ error: "file_not_found" });
} catch (err) {
console.error(`[ERROR] S3 send ${path}`, err);
}
+1 -1
View File
@@ -27,7 +27,7 @@ function indexResponse(req: express.Request, res: express.Response) {
req.path.startsWith("/favicon") ||
req.path.startsWith("/api")
) {
return res.status(404).send("Not found");
return res.status(404).json({ error: "not_found" });
}
if (
req.params.repoId &&
+1 -1
View File
@@ -59,7 +59,7 @@ router.post("/queue/:name/:repo_id", async (req, res) => {
}
res.send("ok");
} catch {
res.status(500).send("error_retrying_job");
res.status(500).json({ error: "error_retrying_job" });
}
}
});
+7 -6
View File
@@ -102,9 +102,9 @@ export function handleError(
req?: express.Request
) {
printError(error, req);
let message = error;
let errorCode = error;
if (error instanceof Error) {
message = error.message;
errorCode = error.message;
}
let status = 500;
if (error.httpStatus) {
@@ -112,15 +112,16 @@ export function handleError(
} else if (error.$metadata?.httpStatusCode) {
status = error.$metadata.httpStatusCode;
} else if (
message &&
(message.indexOf("not_found") > -1 || message.indexOf("(Not Found)") > -1)
errorCode &&
(errorCode.indexOf("not_found") > -1 ||
errorCode.indexOf("(Not Found)") > -1)
) {
status = 404;
} else if (message && message.indexOf("not_connected") > -1) {
} else if (errorCode && errorCode.indexOf("not_connected") > -1) {
status = 401;
}
if (res && !res.headersSent) {
res.status(status).send({ error: message });
res.status(status).json({ error: errorCode });
}
return;
}
+161
View File
@@ -0,0 +1,161 @@
const { expect } = require("chai");
const { readFileSync, readdirSync, statSync } = require("fs");
const { join } = require("path");
const LOCALE_PATH = join(__dirname, "..", "public", "i18n", "locale-en.json");
const SRC_DIR = join(__dirname, "..", "src");
/**
* Collect all .ts files under a directory recursively.
*/
function collectTsFiles(dir) {
const files = [];
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
if (statSync(full).isDirectory()) {
files.push(...collectTsFiles(full));
} else if (full.endsWith(".ts")) {
files.push(full);
}
}
return files;
}
/**
* Extract error codes from backend source files.
*
* Matches patterns such as:
* 1. new AnonymousError("error_code", ...) -- thrown errors
* 2. res.status(NNN).json({ error: "code" }) -- direct responses
*
* Only string literals that look like error codes (contain at least one
* underscore, indicating a snake_case identifier) are extracted.
*/
function extractBackendErrorCodes(files) {
const codes = new Map(); // code -> [{ file, line }]
const patterns = [
// new AnonymousError("code") -- including across ternary expressions
// e.g. new AnonymousError("repo_not_found",
// new AnonymousError(condition ? msg : "fallback_code",
/AnonymousError\([^)]*["']([a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+)["']/,
// { error: "code" } -- direct JSON responses
/\{\s*error:\s*["']([a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+)["']/,
];
for (const file of files) {
const content = readFileSync(file, "utf-8");
const lines = content.split("\n");
const relPath = file.replace(join(__dirname, "..") + "/", "");
// Per-line matching for simple cases
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/^\s*(import |\/\/|\/\*|\* )/.test(line)) continue;
for (const pattern of patterns) {
const match = line.match(pattern);
if (match) {
const code = match[1];
if (!codes.has(code)) {
codes.set(code, []);
}
codes.get(code).push({ file: relPath, line: i + 1 });
}
}
}
// Multi-line matching for AnonymousError calls that span lines
// (e.g. ternary expressions where the string is on the next line)
const multiLinePattern =
/AnonymousError\([\s\S]*?["']([a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+)["']/g;
let m;
while ((m = multiLinePattern.exec(content)) !== null) {
const code = m[1];
const lineNum =
content.substring(0, m.index + m[0].length).split("\n").length;
if (!codes.has(code)) {
codes.set(code, []);
}
// Avoid duplicate entries from the per-line pass
const existing = codes.get(code);
if (!existing.some((e) => e.file === relPath && e.line === lineNum)) {
existing.push({ file: relPath, line: lineNum });
}
}
}
return codes;
}
describe("Error code coverage", function () {
let localeErrors;
let backendCodes;
before(function () {
const locale = JSON.parse(readFileSync(LOCALE_PATH, "utf-8"));
localeErrors = locale.ERRORS || {};
backendCodes = extractBackendErrorCodes(collectTsFiles(SRC_DIR));
});
it("locale file is valid JSON with an ERRORS object", function () {
expect(localeErrors).to.be.an("object").that.is.not.empty;
});
it("every backend error code has a frontend translation", function () {
const missing = [];
for (const [code, locations] of backendCodes) {
if (!localeErrors[code]) {
const where = locations
.map((l) => ` ${l.file}:${l.line}`)
.join("\n");
missing.push(` "${code}" used in:\n${where}`);
}
}
expect(missing, `Missing translations:\n${missing.join("\n")}`).to.have
.length(0);
});
it("every frontend translation corresponds to a backend error code", function () {
const unused = [];
for (const code of Object.keys(localeErrors)) {
if (!backendCodes.has(code)) {
unused.push(` "${code}"`);
}
}
// This is a warning, not a hard failure -- some codes may only be used
// on the frontend itself (e.g. "unreachable", "request_error").
// We report them so developers can clean up stale entries.
if (unused.length > 0) {
console.log(
`${unused.length} locale key(s) not found in backend source (frontend-only or stale):\n${unused.join("\n")}`
);
}
});
it("locale error messages are non-empty strings", function () {
const empty = [];
for (const [code, message] of Object.entries(localeErrors)) {
if (typeof message !== "string" || message.trim() === "") {
empty.push(code);
}
}
expect(empty, `Empty/invalid messages for: ${empty.join(", ")}`).to.have
.length(0);
});
it("backend error codes use consistent snake_case format", function () {
const invalid = [];
// Allow snake_case with optional camelCase segments (e.g. pullRequestId_not_specified)
const validPattern = /^[a-zA-Z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$/;
for (const code of backendCodes.keys()) {
if (!validPattern.test(code)) {
invalid.push(code);
}
}
expect(
invalid,
`Invalid error code format: ${invalid.join(", ")}`
).to.have.length(0);
});
});