mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
Merge branch 'main' into versions/3.0.0
This commit is contained in:
commit
11192ed4df
@ -44,6 +44,7 @@ module.exports = {
|
|||||||
// Problems with optional parameters
|
// Problems with optional parameters
|
||||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||||
'@typescript-eslint/prefer-optional-chain': 'error',
|
'@typescript-eslint/prefer-optional-chain': 'error',
|
||||||
|
'@typescript-eslint/promise-function-async': [ 'error', { checkArrowFunctions: false } ],
|
||||||
'@typescript-eslint/space-before-function-paren': [ 'error', 'never' ],
|
'@typescript-eslint/space-before-function-paren': [ 'error', 'never' ],
|
||||||
'@typescript-eslint/unbound-method': 'off',
|
'@typescript-eslint/unbound-method': 'off',
|
||||||
'@typescript-eslint/unified-signatures': 'off',
|
'@typescript-eslint/unified-signatures': 'off',
|
||||||
|
16
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F41B Bug report"
|
||||||
|
about: If something is not working as expected or crashes
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Environment
|
||||||
|
- Server version: *Output of `community-solid-server --version` for a global or `npx community-solid-server --version` for a local installation*
|
||||||
|
- Node.js version: *Output of `node -v`*
|
||||||
|
- npm version: *Output of `npm -v`*
|
||||||
|
|
||||||
|
#### Description
|
||||||
|
<!-- Please describe the exact problem as clearly as possible. Provide any error messages thrown. -->
|
13
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: "➕ Feature request"
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Feature description:
|
||||||
|
|
||||||
|
<!--In case you want to start a discussion about an idea, discussions are better suited for this https://github.com/solid/community-server/discussions -->
|
||||||
|
<!--A clear and concise description of what you want to happen.-->
|
13
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: "❓ Question"
|
||||||
|
about: A general question
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Question
|
||||||
|
|
||||||
|
<!-- Questions are usually better suited for discussions, which can be found at https://github.com/solid/community-server/discussions -->
|
||||||
|
<!-- In case you think this would better as an issue, please provide a clear and concisely formulated question.-->
|
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!--
|
||||||
|
Things to check before submitting a pull request:
|
||||||
|
* Label this PR with the correct semver label (if you have permission to do so).
|
||||||
|
- semver.patch: Backwards compatible bug fixes.
|
||||||
|
- semver.minor: Backwards compatible feature additions.
|
||||||
|
- semver.major: Breaking changes. This includes changing interfaces or configuration behaviour.
|
||||||
|
* Target the correct branch. Patch updates can target main, other changes should target the latest versions/* branch.
|
||||||
|
* Update the RELEASE_NOTES.md document in case of relevant feature or config changes.
|
||||||
|
-->
|
||||||
|
|
||||||
|
#### Related issues
|
||||||
|
<!-- Reference any relevant issues here. Closing keywords only have an effect when targeting the main branch. -->
|
||||||
|
|
||||||
|
#### Description
|
||||||
|
<!-- Describe the relevant changes in this PR. Also add notes that might be relevant for code reviewers. -->
|
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@ -46,7 +46,6 @@ jobs:
|
|||||||
- '14.x'
|
- '14.x'
|
||||||
- '16.0'
|
- '16.0'
|
||||||
- '16.x'
|
- '16.x'
|
||||||
- '17.0'
|
|
||||||
- '17.x'
|
- '17.x'
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
@ -74,11 +73,9 @@ jobs:
|
|||||||
run: npm run test:deploy
|
run: npm run test:deploy
|
||||||
|
|
||||||
test-integration:
|
test-integration:
|
||||||
runs-on: ${{ matrix.operating-system }}
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
operating-system:
|
|
||||||
- ubuntu-latest
|
|
||||||
node-version:
|
node-version:
|
||||||
- '12.x'
|
- '12.x'
|
||||||
- '14.x'
|
- '14.x'
|
||||||
@ -109,6 +106,29 @@ jobs:
|
|||||||
- name: Run integration tests
|
- name: Run integration tests
|
||||||
run: npm run test:integration
|
run: npm run test:integration
|
||||||
|
|
||||||
|
test-integration-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version:
|
||||||
|
- '12.x'
|
||||||
|
- '14.x'
|
||||||
|
- '16.x'
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Ensure line endings are consistent
|
||||||
|
run: git config --global core.autocrlf input
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install dependencies and run build scripts
|
||||||
|
run: npm ci
|
||||||
|
- name: Run integration tests
|
||||||
|
run: npm run test:integration
|
||||||
|
|
||||||
coveralls:
|
coveralls:
|
||||||
needs: test-unit
|
needs: test-unit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
2
.github/workflows/schedule.yml
vendored
2
.github/workflows/schedule.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
branch:
|
branch:
|
||||||
- 'main'
|
- 'main'
|
||||||
- 'versions/2.1.0'
|
- 'versions/3.0.0'
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Use Node.js 16.x
|
- name: Use Node.js 16.x
|
||||||
|
109
package-lock.json
generated
109
package-lock.json
generated
@ -30,7 +30,7 @@
|
|||||||
"@types/sparqljs": "^3.1.2",
|
"@types/sparqljs": "^3.1.2",
|
||||||
"@types/url-join": "^4.0.0",
|
"@types/url-join": "^4.0.0",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/ws": "^7.4.5",
|
"@types/ws": "^8.2.0",
|
||||||
"@types/yargs": "^17.0.0",
|
"@types/yargs": "^17.0.0",
|
||||||
"arrayify-stream": "^1.0.0",
|
"arrayify-stream": "^1.0.0",
|
||||||
"async-lock": "^1.3.0",
|
"async-lock": "^1.3.0",
|
||||||
@ -56,13 +56,13 @@
|
|||||||
"rdf-serialize": "^1.1.0",
|
"rdf-serialize": "^1.1.0",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"redlock": "^4.2.0",
|
"redlock": "^4.2.0",
|
||||||
"sparqlalgebrajs": "^3.0.0",
|
"sparqlalgebrajs": "^4.0.1",
|
||||||
"sparqljs": "^3.4.2",
|
"sparqljs": "^3.4.2",
|
||||||
"url-join": "^4.0.1",
|
"url-join": "^4.0.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.3.3",
|
"winston": "^3.3.3",
|
||||||
"winston-transport": "^4.4.0",
|
"winston-transport": "^4.4.0",
|
||||||
"ws": "^8.0.0",
|
"ws": "^8.2.3",
|
||||||
"yargs": "^17.0.1"
|
"yargs": "^17.0.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -3780,6 +3780,24 @@
|
|||||||
"sparqlalgebrajs": "^3.0.1"
|
"sparqlalgebrajs": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@comunica/types/node_modules/sparqlalgebrajs": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-XFNhsO55bprayrM35h/jY0kzzuGc3oZ1On3kc+s7Un0BFQBXa046aLcMZFp4MYSvn7GtMe9eZ08ONFnBH5kEsQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@rdfjs/types": "*",
|
||||||
|
"@types/sparqljs": "^3.1.2",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"minimist": "^1.2.5",
|
||||||
|
"rdf-data-factory": "^1.1.0",
|
||||||
|
"rdf-isomorphic": "^1.3.0",
|
||||||
|
"rdf-string": "^1.6.0",
|
||||||
|
"sparqljs": "^3.4.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sparqlalgebrajs": "bin/sparqlalgebrajs.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@comunica/utils-datasource": {
|
"node_modules/@comunica/utils-datasource": {
|
||||||
"version": "1.21.1",
|
"version": "1.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@comunica/utils-datasource/-/utils-datasource-1.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/@comunica/utils-datasource/-/utils-datasource-1.21.1.tgz",
|
||||||
@ -4886,9 +4904,9 @@
|
|||||||
"integrity": "sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ=="
|
"integrity": "sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/sparqljs": {
|
"node_modules/@types/sparqljs": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sparqljs/-/sparqljs-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sparqljs/-/sparqljs-3.1.3.tgz",
|
||||||
"integrity": "sha512-tLfrnBuK37P2Bn8Fo7Qik95sBXYHw5D+gq3MMq1HVyoTpCWivwPnP0Mmd7Apamdc9eH3mLJwIZIETHCQ6HxMUw==",
|
"integrity": "sha512-nmFgmR6ns4i8sg9fYu+293H+PMLKmDOZy34sgwgAeUEEiIqSs4guj5aCZRt3gq1g0yuKXkqrxLDq/684g7pGtQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rdf-js": "^4.0.2"
|
"rdf-js": "^4.0.2"
|
||||||
}
|
}
|
||||||
@ -4934,9 +4952,9 @@
|
|||||||
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "7.4.5",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz",
|
||||||
"integrity": "sha512-8mbDgtc8xpxDDem5Gwj76stBDJX35KQ3YBoayxlqUQcL5BZUthiqP/VQ4PQnLHqM4PmlbyO74t98eJpURO+gPA==",
|
"integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@ -14164,18 +14182,18 @@
|
|||||||
"integrity": "sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig=="
|
"integrity": "sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig=="
|
||||||
},
|
},
|
||||||
"node_modules/sparqlalgebrajs": {
|
"node_modules/sparqlalgebrajs": {
|
||||||
"version": "3.0.3",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-4.0.1.tgz",
|
||||||
"integrity": "sha512-XFNhsO55bprayrM35h/jY0kzzuGc3oZ1On3kc+s7Un0BFQBXa046aLcMZFp4MYSvn7GtMe9eZ08ONFnBH5kEsQ==",
|
"integrity": "sha512-/YOn4+5JEawI29h9eFYb9rScCtiLIK4TG+MrXpbJlHDjL8W34+V/RQoXqeau4SFXtA60Vzc7LRokefguh+I37Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rdfjs/types": "*",
|
"@rdfjs/types": "*",
|
||||||
"@types/sparqljs": "^3.1.2",
|
"@types/sparqljs": "^3.1.3",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"rdf-data-factory": "^1.1.0",
|
"rdf-data-factory": "^1.1.0",
|
||||||
"rdf-isomorphic": "^1.3.0",
|
"rdf-isomorphic": "^1.3.0",
|
||||||
"rdf-string": "^1.6.0",
|
"rdf-string": "^1.6.0",
|
||||||
"sparqljs": "^3.4.2"
|
"sparqljs": "^3.5.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"sparqlalgebrajs": "bin/sparqlalgebrajs.js"
|
"sparqlalgebrajs": "bin/sparqlalgebrajs.js"
|
||||||
@ -14220,9 +14238,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sparqljs": {
|
"node_modules/sparqljs": {
|
||||||
"version": "3.4.2",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.5.1.tgz",
|
||||||
"integrity": "sha512-MmmZ6cMuvhf4Eh2FXX21dalgADUiZ9WN8XKMedwhTFg0r7W09/o8wvoZ8C4yA6FptnjjAjm+mGnxAEpkSRY3QQ==",
|
"integrity": "sha512-sHc6z7hNF3ACvXurKe8hT1sD52Fc0fN3uPLS6SQnXRV9CJl33GNAS4w5Dd3X3GgykUt9SlnjhI1QRKhLzun4qQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rdf-data-factory": "^1.0.4"
|
"rdf-data-factory": "^1.0.4"
|
||||||
},
|
},
|
||||||
@ -15577,9 +15595,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.0.0",
|
"version": "8.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||||
"integrity": "sha512-6AcSIXpBlS0QvCVKk+3cWnWElLsA6SzC0lkQ43ciEglgXJXiCWK3/CGFEJ+Ybgp006CMibamAsqOlxE9s4AvYA==",
|
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@ -18574,6 +18592,23 @@
|
|||||||
"asynciterator": "^3.2.0",
|
"asynciterator": "^3.2.0",
|
||||||
"immutable": "^3.8.2",
|
"immutable": "^3.8.2",
|
||||||
"sparqlalgebrajs": "^3.0.1"
|
"sparqlalgebrajs": "^3.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"sparqlalgebrajs": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-XFNhsO55bprayrM35h/jY0kzzuGc3oZ1On3kc+s7Un0BFQBXa046aLcMZFp4MYSvn7GtMe9eZ08ONFnBH5kEsQ==",
|
||||||
|
"requires": {
|
||||||
|
"@rdfjs/types": "*",
|
||||||
|
"@types/sparqljs": "^3.1.2",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"minimist": "^1.2.5",
|
||||||
|
"rdf-data-factory": "^1.1.0",
|
||||||
|
"rdf-isomorphic": "^1.3.0",
|
||||||
|
"rdf-string": "^1.6.0",
|
||||||
|
"sparqljs": "^3.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@comunica/utils-datasource": {
|
"@comunica/utils-datasource": {
|
||||||
@ -19578,9 +19613,9 @@
|
|||||||
"integrity": "sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ=="
|
"integrity": "sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ=="
|
||||||
},
|
},
|
||||||
"@types/sparqljs": {
|
"@types/sparqljs": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sparqljs/-/sparqljs-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sparqljs/-/sparqljs-3.1.3.tgz",
|
||||||
"integrity": "sha512-tLfrnBuK37P2Bn8Fo7Qik95sBXYHw5D+gq3MMq1HVyoTpCWivwPnP0Mmd7Apamdc9eH3mLJwIZIETHCQ6HxMUw==",
|
"integrity": "sha512-nmFgmR6ns4i8sg9fYu+293H+PMLKmDOZy34sgwgAeUEEiIqSs4guj5aCZRt3gq1g0yuKXkqrxLDq/684g7pGtQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"rdf-js": "^4.0.2"
|
"rdf-js": "^4.0.2"
|
||||||
}
|
}
|
||||||
@ -19626,9 +19661,9 @@
|
|||||||
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
||||||
},
|
},
|
||||||
"@types/ws": {
|
"@types/ws": {
|
||||||
"version": "7.4.5",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz",
|
||||||
"integrity": "sha512-8mbDgtc8xpxDDem5Gwj76stBDJX35KQ3YBoayxlqUQcL5BZUthiqP/VQ4PQnLHqM4PmlbyO74t98eJpURO+gPA==",
|
"integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@ -26661,18 +26696,18 @@
|
|||||||
"integrity": "sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig=="
|
"integrity": "sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig=="
|
||||||
},
|
},
|
||||||
"sparqlalgebrajs": {
|
"sparqlalgebrajs": {
|
||||||
"version": "3.0.3",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-4.0.1.tgz",
|
||||||
"integrity": "sha512-XFNhsO55bprayrM35h/jY0kzzuGc3oZ1On3kc+s7Un0BFQBXa046aLcMZFp4MYSvn7GtMe9eZ08ONFnBH5kEsQ==",
|
"integrity": "sha512-/YOn4+5JEawI29h9eFYb9rScCtiLIK4TG+MrXpbJlHDjL8W34+V/RQoXqeau4SFXtA60Vzc7LRokefguh+I37Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@rdfjs/types": "*",
|
"@rdfjs/types": "*",
|
||||||
"@types/sparqljs": "^3.1.2",
|
"@types/sparqljs": "^3.1.3",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"rdf-data-factory": "^1.1.0",
|
"rdf-data-factory": "^1.1.0",
|
||||||
"rdf-isomorphic": "^1.3.0",
|
"rdf-isomorphic": "^1.3.0",
|
||||||
"rdf-string": "^1.6.0",
|
"rdf-string": "^1.6.0",
|
||||||
"sparqljs": "^3.4.2"
|
"sparqljs": "^3.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sparqlee": {
|
"sparqlee": {
|
||||||
@ -26710,9 +26745,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sparqljs": {
|
"sparqljs": {
|
||||||
"version": "3.4.2",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.5.1.tgz",
|
||||||
"integrity": "sha512-MmmZ6cMuvhf4Eh2FXX21dalgADUiZ9WN8XKMedwhTFg0r7W09/o8wvoZ8C4yA6FptnjjAjm+mGnxAEpkSRY3QQ==",
|
"integrity": "sha512-sHc6z7hNF3ACvXurKe8hT1sD52Fc0fN3uPLS6SQnXRV9CJl33GNAS4w5Dd3X3GgykUt9SlnjhI1QRKhLzun4qQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"rdf-data-factory": "^1.0.4"
|
"rdf-data-factory": "^1.0.4"
|
||||||
}
|
}
|
||||||
@ -27816,9 +27851,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "8.0.0",
|
"version": "8.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||||
"integrity": "sha512-6AcSIXpBlS0QvCVKk+3cWnWElLsA6SzC0lkQ43ciEglgXJXiCWK3/CGFEJ+Ybgp006CMibamAsqOlxE9s4AvYA==",
|
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"xdg-basedir": {
|
"xdg-basedir": {
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
"@types/sparqljs": "^3.1.2",
|
"@types/sparqljs": "^3.1.2",
|
||||||
"@types/url-join": "^4.0.0",
|
"@types/url-join": "^4.0.0",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/ws": "^7.4.5",
|
"@types/ws": "^8.2.0",
|
||||||
"@types/yargs": "^17.0.0",
|
"@types/yargs": "^17.0.0",
|
||||||
"arrayify-stream": "^1.0.0",
|
"arrayify-stream": "^1.0.0",
|
||||||
"async-lock": "^1.3.0",
|
"async-lock": "^1.3.0",
|
||||||
@ -122,13 +122,13 @@
|
|||||||
"rdf-serialize": "^1.1.0",
|
"rdf-serialize": "^1.1.0",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"redlock": "^4.2.0",
|
"redlock": "^4.2.0",
|
||||||
"sparqlalgebrajs": "^3.0.0",
|
"sparqlalgebrajs": "^4.0.1",
|
||||||
"sparqljs": "^3.4.2",
|
"sparqljs": "^3.4.2",
|
||||||
"url-join": "^4.0.1",
|
"url-join": "^4.0.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.3.3",
|
"winston": "^3.3.3",
|
||||||
"winston-transport": "^4.4.0",
|
"winston-transport": "^4.4.0",
|
||||||
"ws": "^8.0.0",
|
"ws": "^8.2.3",
|
||||||
"yargs": "^17.0.1"
|
"yargs": "^17.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -40,25 +40,25 @@ export class SparqlPatchModesExtractor extends ModesExtractor {
|
|||||||
return Boolean((data as SparqlUpdatePatch).algebra);
|
return Boolean((data as SparqlUpdatePatch).algebra);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isSupported(op: Algebra.Operation): boolean {
|
private isSupported(op: Algebra.Update): boolean {
|
||||||
if (this.isDeleteInsert(op) || this.isNop(op)) {
|
if (this.isDeleteInsert(op) || this.isNop(op)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (op.type === Algebra.types.COMPOSITE_UPDATE) {
|
if (op.type === Algebra.types.COMPOSITE_UPDATE) {
|
||||||
return (op as Algebra.CompositeUpdate).updates.every((update): boolean => this.isSupported(update));
|
return op.updates.every((update): boolean => this.isSupported(update));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDeleteInsert(op: Algebra.Operation): op is Algebra.DeleteInsert {
|
private isDeleteInsert(op: Algebra.Update): op is Algebra.DeleteInsert {
|
||||||
return op.type === Algebra.types.DELETE_INSERT;
|
return op.type === Algebra.types.DELETE_INSERT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isNop(op: Algebra.Operation): op is Algebra.Nop {
|
private isNop(op: Algebra.Update): op is Algebra.Nop {
|
||||||
return op.type === Algebra.types.NOP;
|
return op.type === Algebra.types.NOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
private needsAppend(update: Algebra.Operation): boolean {
|
private needsAppend(update: Algebra.Update): boolean {
|
||||||
if (this.isNop(update)) {
|
if (this.isNop(update)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ export class SparqlPatchModesExtractor extends ModesExtractor {
|
|||||||
return (update as Algebra.CompositeUpdate).updates.some((op): boolean => this.needsAppend(op));
|
return (update as Algebra.CompositeUpdate).updates.some((op): boolean => this.needsAppend(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
private needsWrite(update: Algebra.Operation): boolean {
|
private needsWrite(update: Algebra.Update): boolean {
|
||||||
if (this.isNop(update)) {
|
if (this.isNop(update)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import type WebSocket from 'ws';
|
import type { WebSocket } from 'ws';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import type { HttpRequest } from '../server/HttpRequest';
|
import type { HttpRequest } from '../server/HttpRequest';
|
||||||
import { WebSocketHandler } from '../server/WebSocketHandler';
|
import { WebSocketHandler } from '../server/WebSocketHandler';
|
||||||
|
@ -26,7 +26,7 @@ export class SparqlUpdateBodyParser extends BodyParser {
|
|||||||
const sparql = await readableToString(request);
|
const sparql = await readableToString(request);
|
||||||
let algebra: Algebra.Operation;
|
let algebra: Algebra.Operation;
|
||||||
try {
|
try {
|
||||||
algebra = translate(sparql, { quads: true, baseIRI: metadata.identifier.value });
|
algebra = translate(sparql, { quads: true, baseIRI: metadata.identifier.value }) as Algebra.Update;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.logger.warn('Could not translate SPARQL query to SPARQL algebra', { error });
|
this.logger.warn('Could not translate SPARQL query to SPARQL algebra', { error });
|
||||||
throw new BadRequestHttpError(createErrorMessage(error), { cause: error });
|
throw new BadRequestHttpError(createErrorMessage(error), { cause: error });
|
||||||
|
@ -3,7 +3,7 @@ import type {
|
|||||||
RepresentationConverterArgs,
|
RepresentationConverterArgs,
|
||||||
} from '../../../storage/conversion/RepresentationConverter';
|
} from '../../../storage/conversion/RepresentationConverter';
|
||||||
import { INTERNAL_ERROR } from '../../../util/ContentTypes';
|
import { INTERNAL_ERROR } from '../../../util/ContentTypes';
|
||||||
import { getStatusCode } from '../../../util/errors/ErrorUtil';
|
import { getStatusCode } from '../../../util/errors/HttpErrorUtil';
|
||||||
import { toLiteral } from '../../../util/TermUtil';
|
import { toLiteral } from '../../../util/TermUtil';
|
||||||
import { HTTP, XSD } from '../../../util/Vocabularies';
|
import { HTTP, XSD } from '../../../util/Vocabularies';
|
||||||
import { BasicRepresentation } from '../../representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../representation/BasicRepresentation';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { getLoggerFor } from '../../../logging/LogUtil';
|
import { getLoggerFor } from '../../../logging/LogUtil';
|
||||||
import { createErrorMessage, getStatusCode } from '../../../util/errors/ErrorUtil';
|
import { createErrorMessage } from '../../../util/errors/ErrorUtil';
|
||||||
|
import { getStatusCode } from '../../../util/errors/HttpErrorUtil';
|
||||||
import { guardedStreamFrom } from '../../../util/StreamUtil';
|
import { guardedStreamFrom } from '../../../util/StreamUtil';
|
||||||
import { toLiteral } from '../../../util/TermUtil';
|
import { toLiteral } from '../../../util/TermUtil';
|
||||||
import { HTTP, XSD } from '../../../util/Vocabularies';
|
import { HTTP, XSD } from '../../../util/Vocabularies';
|
||||||
|
@ -320,6 +320,7 @@ export * from './util/errors/ConflictHttpError';
|
|||||||
export * from './util/errors/ErrorUtil';
|
export * from './util/errors/ErrorUtil';
|
||||||
export * from './util/errors/ForbiddenHttpError';
|
export * from './util/errors/ForbiddenHttpError';
|
||||||
export * from './util/errors/HttpError';
|
export * from './util/errors/HttpError';
|
||||||
|
export * from './util/errors/HttpErrorUtil';
|
||||||
export * from './util/errors/InternalServerError';
|
export * from './util/errors/InternalServerError';
|
||||||
export * from './util/errors/MethodNotAllowedHttpError';
|
export * from './util/errors/MethodNotAllowedHttpError';
|
||||||
export * from './util/errors/NotFoundHttpError';
|
export * from './util/errors/NotFoundHttpError';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type WebSocket from 'ws';
|
import type { WebSocket } from 'ws';
|
||||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||||
import type { HttpRequest } from './HttpRequest';
|
import type { HttpRequest } from './HttpRequest';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Server } from 'http';
|
import type { Server } from 'http';
|
||||||
import type { Socket } from 'net';
|
import type { Socket } from 'net';
|
||||||
import type WebSocket from 'ws';
|
import type { WebSocket } from 'ws';
|
||||||
import { Server as WebSocketServer } from 'ws';
|
import { Server as WebSocketServer } from 'ws';
|
||||||
import type { HttpRequest } from './HttpRequest';
|
import type { HttpRequest } from './HttpRequest';
|
||||||
import type { HttpServerFactory } from './HttpServerFactory';
|
import type { HttpServerFactory } from './HttpServerFactory';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { createAggregateError } from './errors/HttpErrorUtil';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
const infinitePromise = new Promise<boolean>((): void => {});
|
function noop(): void {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that simulates the Array.some behaviour but on an array of Promises.
|
* A function that simulates the Array.some behaviour but on an array of Promises.
|
||||||
@ -14,17 +16,36 @@ const infinitePromise = new Promise<boolean>((): void => {});
|
|||||||
* 2. throwing an error should be logically equivalent to returning false.
|
* 2. throwing an error should be logically equivalent to returning false.
|
||||||
*/
|
*/
|
||||||
export async function promiseSome(predicates: Promise<boolean>[]): Promise<boolean> {
|
export async function promiseSome(predicates: Promise<boolean>[]): Promise<boolean> {
|
||||||
// These promises will only finish when their predicate returns true
|
return new Promise((resolve): void => {
|
||||||
const infinitePredicates = predicates.map(async(predicate): Promise<boolean> => predicate.then(
|
function resolveIfTrue(value: boolean): void {
|
||||||
async(value): Promise<boolean> => value ? true : infinitePromise,
|
if (value) {
|
||||||
async(): Promise<boolean> => infinitePromise,
|
resolve(true);
|
||||||
));
|
}
|
||||||
|
}
|
||||||
// Returns after all predicates are resolved
|
Promise.all(predicates.map((predicate): Promise<void> => predicate.then(resolveIfTrue, noop)))
|
||||||
const finalPromise = Promise.allSettled(predicates).then((results): boolean =>
|
.then((): void => resolve(false), noop);
|
||||||
results.some((result): boolean => result.status === 'fulfilled' && result.value));
|
});
|
||||||
|
}
|
||||||
// Either one of the infinitePredicates will return true,
|
|
||||||
// or finalPromise will return the result if none of them did or finalPromise was faster
|
/**
|
||||||
return Promise.race([ ...infinitePredicates, finalPromise ]);
|
* Obtains the values of all fulfilled promises.
|
||||||
|
* If there are rejections (and `ignoreErrors` is false), throws a combined error of all rejected promises.
|
||||||
|
*/
|
||||||
|
export async function allFulfilled<T>(promises: Promise<T> [], ignoreErrors = false): Promise<T[]> {
|
||||||
|
// Collect values and errors
|
||||||
|
const values: T[] = [];
|
||||||
|
const errors: Error[] = [];
|
||||||
|
for (const result of await Promise.allSettled(promises)) {
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
values.push(result.value);
|
||||||
|
} else if (!ignoreErrors) {
|
||||||
|
errors.push(result.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either throw or return
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw createAggregateError(errors);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { types } from 'util';
|
import { types } from 'util';
|
||||||
import { HttpError } from './HttpError';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the input is an {@link Error}.
|
* Checks if the input is an {@link Error}.
|
||||||
@ -25,10 +24,3 @@ export function assertError(error: unknown): asserts error is Error {
|
|||||||
export function createErrorMessage(error: unknown): string {
|
export function createErrorMessage(error: unknown): string {
|
||||||
return isError(error) ? error.message : `Unknown error: ${error}`;
|
return isError(error) ? error.message : `Unknown error: ${error}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTTP status code corresponding to the error.
|
|
||||||
*/
|
|
||||||
export function getStatusCode(error: Error): number {
|
|
||||||
return HttpError.isInstance(error) ? error.statusCode : 500;
|
|
||||||
}
|
|
||||||
|
38
src/util/errors/HttpErrorUtil.ts
Normal file
38
src/util/errors/HttpErrorUtil.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { BadRequestHttpError } from './BadRequestHttpError';
|
||||||
|
import { createErrorMessage } from './ErrorUtil';
|
||||||
|
import { HttpError } from './HttpError';
|
||||||
|
import { InternalServerError } from './InternalServerError';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTTP status code corresponding to the error.
|
||||||
|
*/
|
||||||
|
export function getStatusCode(error: Error): number {
|
||||||
|
return HttpError.isInstance(error) ? error.statusCode : 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines a list of errors into a single HttpErrors.
|
||||||
|
* Status code depends on the input errors. If they all share the same status code that code will be re-used.
|
||||||
|
* If they are all within the 4xx range, 400 will be used, otherwise 500.
|
||||||
|
*
|
||||||
|
* @param errors - Errors to combine.
|
||||||
|
* @param messagePrefix - Prefix for the aggregate error message. Will be followed with an array of all the messages.
|
||||||
|
*/
|
||||||
|
export function createAggregateError(errors: Error[], messagePrefix = 'No handler supports the given input:'):
|
||||||
|
HttpError {
|
||||||
|
const httpErrors = errors.map((error): HttpError =>
|
||||||
|
HttpError.isInstance(error) ? error : new InternalServerError(createErrorMessage(error)));
|
||||||
|
const joined = httpErrors.map((error: Error): string => error.message).join(', ');
|
||||||
|
const message = `${messagePrefix} [${joined}]`;
|
||||||
|
|
||||||
|
// Check if all errors have the same status code
|
||||||
|
if (httpErrors.length > 0 && httpErrors.every((error): boolean => error.statusCode === httpErrors[0].statusCode)) {
|
||||||
|
return new HttpError(httpErrors[0].statusCode, httpErrors[0].name, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the error range (4xx or 5xx)
|
||||||
|
if (httpErrors.some((error): boolean => error.statusCode >= 500)) {
|
||||||
|
return new InternalServerError(message);
|
||||||
|
}
|
||||||
|
return new BadRequestHttpError(message);
|
||||||
|
}
|
@ -1,36 +1,7 @@
|
|||||||
import { BadRequestHttpError } from '../errors/BadRequestHttpError';
|
|
||||||
import { createErrorMessage, isError } from '../errors/ErrorUtil';
|
import { createErrorMessage, isError } from '../errors/ErrorUtil';
|
||||||
import { HttpError } from '../errors/HttpError';
|
import { createAggregateError } from '../errors/HttpErrorUtil';
|
||||||
import { InternalServerError } from '../errors/InternalServerError';
|
|
||||||
import type { AsyncHandler } from './AsyncHandler';
|
import type { AsyncHandler } from './AsyncHandler';
|
||||||
|
|
||||||
/**
|
|
||||||
* Combines a list of errors into a single HttpErrors.
|
|
||||||
* Status code depends on the input errors. If they all share the same status code that code will be re-used.
|
|
||||||
* If they are all within the 4xx range, 400 will be used, otherwise 500.
|
|
||||||
*
|
|
||||||
* @param errors - Errors to combine.
|
|
||||||
* @param messagePrefix - Prefix for the aggregate error message. Will be followed with an array of all the messages.
|
|
||||||
*/
|
|
||||||
export function createAggregateError(errors: Error[], messagePrefix = 'No handler supports the given input:'):
|
|
||||||
HttpError {
|
|
||||||
const httpErrors = errors.map((error): HttpError =>
|
|
||||||
HttpError.isInstance(error) ? error : new InternalServerError(createErrorMessage(error)));
|
|
||||||
const joined = httpErrors.map((error: Error): string => error.message).join(', ');
|
|
||||||
const message = `${messagePrefix} [${joined}]`;
|
|
||||||
|
|
||||||
// Check if all errors have the same status code
|
|
||||||
if (httpErrors.length > 0 && httpErrors.every((error): boolean => error.statusCode === httpErrors[0].statusCode)) {
|
|
||||||
return new HttpError(httpErrors[0].statusCode, httpErrors[0].name, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the error range (4xx or 5xx)
|
|
||||||
if (httpErrors.some((error): boolean => error.statusCode >= 500)) {
|
|
||||||
return new InternalServerError(message);
|
|
||||||
}
|
|
||||||
return new BadRequestHttpError(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a handler that can handle the given input data.
|
* Finds a handler that can handle the given input data.
|
||||||
* Otherwise an error gets thrown.
|
* Otherwise an error gets thrown.
|
||||||
|
@ -12,12 +12,10 @@ export class ParallelHandler<TIn = void, TOut = void> extends AsyncHandler<TIn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle(input: TIn): Promise<void> {
|
public async canHandle(input: TIn): Promise<void> {
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
||||||
await Promise.all(this.handlers.map((handler): Promise<void> => handler.canHandle(input)));
|
await Promise.all(this.handlers.map((handler): Promise<void> => handler.canHandle(input)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(input: TIn): Promise<TOut[]> {
|
public async handle(input: TIn): Promise<TOut[]> {
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
||||||
return Promise.all(this.handlers.map((handler): Promise<TOut> => handler.handle(input)));
|
return Promise.all(this.handlers.map((handler): Promise<TOut> => handler.handle(input)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,41 @@
|
|||||||
|
import { allFulfilled } from '../PromiseUtil';
|
||||||
import { AsyncHandler } from './AsyncHandler';
|
import { AsyncHandler } from './AsyncHandler';
|
||||||
import { createAggregateError, filterHandlers, findHandler } from './HandlerUtil';
|
import { filterHandlers, findHandler } from './HandlerUtil';
|
||||||
|
|
||||||
// Helper types to make sure the UnionHandler has the same in/out types as the AsyncHandler type it wraps
|
// Helper types to make sure the UnionHandler has the same in/out types as the AsyncHandler type it wraps
|
||||||
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
|
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
|
||||||
type InType<T extends AsyncHandler<any, any>> = Parameters<T['handle']>[0];
|
type InType<T extends AsyncHandler<any, any>> = Parameters<T['handle']>[0];
|
||||||
type OutType<T extends AsyncHandler<any, any>> = ThenArg<ReturnType<T['handle']>>;
|
type OutType<T extends AsyncHandler<any, any>> = Awaited<ReturnType<T['handle']>>;
|
||||||
type HandlerType<T extends AsyncHandler> = AsyncHandler<InType<T>, OutType<T>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility handler that allows combining the results of multiple handlers into one.
|
* Utility handler that allows combining the results of multiple handlers into one.
|
||||||
* Will run all the handlers and then call the abstract `combine` function with the results,
|
* Will run the handlers and then call the abstract `combine` function with the results,
|
||||||
* which should return the output of the class.
|
* which then generates the handler's output.
|
||||||
*
|
|
||||||
* If `requireAll` is true, the handler will fail if any of the handlers do not support the input.
|
|
||||||
* If `requireAll` is false, only the handlers that support the input will be called,
|
|
||||||
* only if all handlers reject the input will this handler reject as well.
|
|
||||||
* With `requireAll` set to false, the length of the input array
|
|
||||||
* for the `combine` function is variable (but always at least 1).
|
|
||||||
*/
|
*/
|
||||||
export abstract class UnionHandler<T extends AsyncHandler<any, any>> extends AsyncHandler<InType<T>, OutType<T>> {
|
export abstract class UnionHandler<T extends AsyncHandler<any, any>> extends AsyncHandler<InType<T>, OutType<T>> {
|
||||||
protected readonly handlers: T[];
|
protected readonly handlers: T[];
|
||||||
private readonly requireAll: boolean;
|
private readonly requireAll: boolean;
|
||||||
|
private readonly ignoreErrors: boolean;
|
||||||
|
|
||||||
protected constructor(handlers: T[], requireAll = false) {
|
/**
|
||||||
|
* Creates a new `UnionHandler`.
|
||||||
|
*
|
||||||
|
* When `requireAll` is false or `ignoreErrors` is true,
|
||||||
|
* the length of the input to `combine` can vary;
|
||||||
|
* otherwise, it is exactly the number of handlers.
|
||||||
|
*
|
||||||
|
* @param handlers - The handlers whose output is to be combined.
|
||||||
|
* @param requireAll - If true, will fail if any of the handlers do not support the input.
|
||||||
|
If false, only the handlers that support the input will be called;
|
||||||
|
* will fail only if none of the handlers can handle the input.
|
||||||
|
* @param ignoreErrors - If true, ignores handlers that fail by omitting their output;
|
||||||
|
* if false, fails when any handlers fail.
|
||||||
|
*/
|
||||||
|
public constructor(handlers: T[], requireAll = false, ignoreErrors = !requireAll) {
|
||||||
super();
|
super();
|
||||||
this.handlers = handlers;
|
this.handlers = handlers;
|
||||||
this.requireAll = requireAll;
|
this.requireAll = requireAll;
|
||||||
|
this.ignoreErrors = ignoreErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle(input: InType<T>): Promise<void> {
|
public async canHandle(input: InType<T>): Promise<void> {
|
||||||
@ -38,57 +48,21 @@ export abstract class UnionHandler<T extends AsyncHandler<any, any>> extends Asy
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handle(input: InType<T>): Promise<OutType<T>> {
|
public async handle(input: InType<T>): Promise<OutType<T>> {
|
||||||
let handlers: HandlerType<T>[];
|
const handlers = this.requireAll ? this.handlers : await filterHandlers(this.handlers, input);
|
||||||
if (this.requireAll) {
|
const results = handlers.map((handler): Promise<OutType<T>> => handler.handle(input));
|
||||||
// Handlers were already checked in canHandle
|
return this.combine(await allFulfilled(results, this.ignoreErrors));
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
handlers = this.handlers;
|
|
||||||
} else {
|
|
||||||
handlers = await filterHandlers(this.handlers, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
|
||||||
handlers.map(async(handler): Promise<OutType<T>> => handler.handle(input)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.combine(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async handleSafe(input: InType<T>): Promise<OutType<T>> {
|
|
||||||
let handlers: HandlerType<T>[];
|
|
||||||
if (this.requireAll) {
|
|
||||||
await this.allCanHandle(input);
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
handlers = this.handlers;
|
|
||||||
} else {
|
|
||||||
// This will error if no handler supports the input
|
|
||||||
handlers = await filterHandlers(this.handlers, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
|
||||||
handlers.map(async(handler): Promise<OutType<T>> => handler.handle(input)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.combine(results);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if all handlers can handle the input.
|
* Checks if all handlers can handle the input.
|
||||||
* If not, throw an error based on the errors of the failed handlers.
|
* If not, throw an error based on the errors of the failed handlers.
|
||||||
*/
|
*/
|
||||||
private async allCanHandle(input: InType<T>): Promise<void> {
|
protected async allCanHandle(input: InType<T>): Promise<void> {
|
||||||
const results = await Promise.allSettled(this.handlers.map(async(handler): Promise<HandlerType<T>> => {
|
await allFulfilled(this.handlers.map((handler): Promise<void> => handler.canHandle(input)));
|
||||||
await handler.canHandle(input);
|
|
||||||
return handler;
|
|
||||||
}));
|
|
||||||
if (results.some(({ status }): boolean => status === 'rejected')) {
|
|
||||||
const errors = results.map((result): Error => (result as PromiseRejectedResult).reason);
|
|
||||||
throw createAggregateError(errors);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine the results of the handlers into a single output.
|
* Combines the results of the handlers into a single output.
|
||||||
*/
|
*/
|
||||||
protected abstract combine(results: OutType<T>[]): Promise<OutType<T>>;
|
protected abstract combine(results: OutType<T>[]): Promise<OutType<T>>;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ describe('A Solid server with setup', (): void => {
|
|||||||
|
|
||||||
// Root access disabled
|
// Root access disabled
|
||||||
res = await fetch(baseUrl);
|
res = await fetch(baseUrl);
|
||||||
expect(res.status).toBe(403);
|
expect(res.status).toBe(401);
|
||||||
|
|
||||||
// Registration still possible
|
// Registration still possible
|
||||||
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true };
|
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true };
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import WebSocket from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
import { getPort } from '../util/Util';
|
import { getPort } from '../util/Util';
|
||||||
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
|
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
|
||||||
|
@ -43,4 +43,11 @@ describe('A UnionCredentialsExtractor', (): void => {
|
|||||||
[CredentialGroup.public]: {},
|
[CredentialGroup.public]: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('skips erroring handlers.', async(): Promise<void> => {
|
||||||
|
extractors[0].handle.mockRejectedValueOnce(new Error('error'));
|
||||||
|
await expect(extractor.handle(request)).resolves.toEqual({
|
||||||
|
[CredentialGroup.public]: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Server } from 'http';
|
import type { Server } from 'http';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import WebSocket from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import { BaseHttpServerFactory } from '../../../src/server/BaseHttpServerFactory';
|
import { BaseHttpServerFactory } from '../../../src/server/BaseHttpServerFactory';
|
||||||
import type { HttpHandlerInput } from '../../../src/server/HttpHandler';
|
import type { HttpHandlerInput } from '../../../src/server/HttpHandler';
|
||||||
import { HttpHandler } from '../../../src/server/HttpHandler';
|
import { HttpHandler } from '../../../src/server/HttpHandler';
|
||||||
|
@ -2,6 +2,7 @@ import 'jest-rdf';
|
|||||||
import { namedNode, quad } from '@rdfjs/data-model';
|
import { namedNode, quad } from '@rdfjs/data-model';
|
||||||
import arrayifyStream from 'arrayify-stream';
|
import arrayifyStream from 'arrayify-stream';
|
||||||
import type { Quad } from 'rdf-js';
|
import type { Quad } from 'rdf-js';
|
||||||
|
import type { Algebra } from 'sparqlalgebrajs';
|
||||||
import { translate } from 'sparqlalgebrajs';
|
import { translate } from 'sparqlalgebrajs';
|
||||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||||
import type { Representation } from '../../../../src/http/representation/Representation';
|
import type { Representation } from '../../../../src/http/representation/Representation';
|
||||||
@ -15,7 +16,7 @@ import { guardedStreamFrom } from '../../../../src/util/StreamUtil';
|
|||||||
function getPatch(query: string): SparqlUpdatePatch {
|
function getPatch(query: string): SparqlUpdatePatch {
|
||||||
const prefixedQuery = `prefix : <http://test.com/>\n${query}`;
|
const prefixedQuery = `prefix : <http://test.com/>\n${query}`;
|
||||||
return {
|
return {
|
||||||
algebra: translate(prefixedQuery, { quads: true }),
|
algebra: translate(prefixedQuery, { quads: true }) as Algebra.Update,
|
||||||
data: guardedStreamFrom(prefixedQuery),
|
data: guardedStreamFrom(prefixedQuery),
|
||||||
metadata: new RepresentationMetadata(),
|
metadata: new RepresentationMetadata(),
|
||||||
binary: true,
|
binary: true,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { assertError, createErrorMessage, getStatusCode, isError } from '../../../../src/util/errors/ErrorUtil';
|
import { assertError, createErrorMessage, isError } from '../../../../src/util/errors/ErrorUtil';
|
||||||
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
|
|
||||||
|
|
||||||
describe('ErrorUtil', (): void => {
|
describe('ErrorUtil', (): void => {
|
||||||
describe('#isError', (): void => {
|
describe('#isError', (): void => {
|
||||||
@ -39,14 +38,4 @@ describe('ErrorUtil', (): void => {
|
|||||||
expect(createErrorMessage('apple')).toBe('Unknown error: apple');
|
expect(createErrorMessage('apple')).toBe('Unknown error: apple');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getStatusCode', (): void => {
|
|
||||||
it('returns the corresponding status code for HttpErrors.', async(): Promise<void> => {
|
|
||||||
expect(getStatusCode(new NotFoundHttpError())).toBe(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns 500 for other errors.', async(): Promise<void> => {
|
|
||||||
expect(getStatusCode(new Error('404'))).toBe(500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
50
test/unit/util/errors/HttpErrorUtil.test.ts
Normal file
50
test/unit/util/errors/HttpErrorUtil.test.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { HttpError } from '../../../../src/util/errors/HttpError';
|
||||||
|
import { createAggregateError, getStatusCode } from '../../../../src/util/errors/HttpErrorUtil';
|
||||||
|
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
|
||||||
|
|
||||||
|
describe('ErrorUtil', (): void => {
|
||||||
|
describe('createAggregateError', (): void => {
|
||||||
|
const error401 = new HttpError(401, 'UnauthorizedHttpError');
|
||||||
|
const error415 = new HttpError(415, 'UnsupportedMediaTypeHttpError');
|
||||||
|
const error501 = new HttpError(501, 'NotImplementedHttpError');
|
||||||
|
const error = new Error('noStatusCode');
|
||||||
|
|
||||||
|
it('throws an error with matching status code if all errors have the same.', async(): Promise<void> => {
|
||||||
|
expect(createAggregateError([ error401, error401 ])).toMatchObject({
|
||||||
|
statusCode: 401,
|
||||||
|
name: 'UnauthorizedHttpError',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an InternalServerError if one of the errors has status code 5xx.', async(): Promise<void> => {
|
||||||
|
expect(createAggregateError([ error401, error501 ])).toMatchObject({
|
||||||
|
statusCode: 500,
|
||||||
|
name: 'InternalServerError',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an BadRequestHttpError if all handlers have 4xx status codes.', async(): Promise<void> => {
|
||||||
|
expect(createAggregateError([ error401, error415 ])).toMatchObject({
|
||||||
|
statusCode: 400,
|
||||||
|
name: 'BadRequestHttpError',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('interprets non-HTTP errors as internal errors.', async(): Promise<void> => {
|
||||||
|
expect(createAggregateError([ error ])).toMatchObject({
|
||||||
|
statusCode: 500,
|
||||||
|
name: 'InternalServerError',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getStatusCode', (): void => {
|
||||||
|
it('returns the corresponding status code for HttpErrors.', async(): Promise<void> => {
|
||||||
|
expect(getStatusCode(new NotFoundHttpError())).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 500 for other errors.', async(): Promise<void> => {
|
||||||
|
expect(getStatusCode(new Error('404'))).toBe(500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,44 +1,8 @@
|
|||||||
import { HttpError } from '../../../../src/util/errors/HttpError';
|
|
||||||
import type { AsyncHandler } from '../../../../src/util/handlers/AsyncHandler';
|
import type { AsyncHandler } from '../../../../src/util/handlers/AsyncHandler';
|
||||||
import { createAggregateError, filterHandlers, findHandler } from '../../../../src/util/handlers/HandlerUtil';
|
import { filterHandlers, findHandler } from '../../../../src/util/handlers/HandlerUtil';
|
||||||
import { StaticAsyncHandler } from '../../../util/StaticAsyncHandler';
|
import { StaticAsyncHandler } from '../../../util/StaticAsyncHandler';
|
||||||
|
|
||||||
describe('HandlerUtil', (): void => {
|
describe('HandlerUtil', (): void => {
|
||||||
describe('createAggregateError', (): void => {
|
|
||||||
const error401 = new HttpError(401, 'UnauthorizedHttpError');
|
|
||||||
const error415 = new HttpError(415, 'UnsupportedMediaTypeHttpError');
|
|
||||||
const error501 = new HttpError(501, 'NotImplementedHttpError');
|
|
||||||
const error = new Error('noStatusCode');
|
|
||||||
|
|
||||||
it('throws an error with matching status code if all errors have the same.', async(): Promise<void> => {
|
|
||||||
expect(createAggregateError([ error401, error401 ])).toMatchObject({
|
|
||||||
statusCode: 401,
|
|
||||||
name: 'UnauthorizedHttpError',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws an InternalServerError if one of the errors has status code 5xx.', async(): Promise<void> => {
|
|
||||||
expect(createAggregateError([ error401, error501 ])).toMatchObject({
|
|
||||||
statusCode: 500,
|
|
||||||
name: 'InternalServerError',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws an BadRequestHttpError if all handlers have 4xx status codes.', async(): Promise<void> => {
|
|
||||||
expect(createAggregateError([ error401, error415 ])).toMatchObject({
|
|
||||||
statusCode: 400,
|
|
||||||
name: 'BadRequestHttpError',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('interprets non-HTTP errors as internal errors.', async(): Promise<void> => {
|
|
||||||
expect(createAggregateError([ error ])).toMatchObject({
|
|
||||||
statusCode: 500,
|
|
||||||
name: 'InternalServerError',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findHandler', (): void => {
|
describe('findHandler', (): void => {
|
||||||
let handlerTrue: AsyncHandler<any, any>;
|
let handlerTrue: AsyncHandler<any, any>;
|
||||||
let handlerFalse: AsyncHandler<any, any>;
|
let handlerFalse: AsyncHandler<any, any>;
|
||||||
|
@ -2,10 +2,6 @@ import type { AsyncHandler } from '../../../../src/util/handlers/AsyncHandler';
|
|||||||
import { UnionHandler } from '../../../../src/util/handlers/UnionHandler';
|
import { UnionHandler } from '../../../../src/util/handlers/UnionHandler';
|
||||||
|
|
||||||
class SimpleUnionHandler extends UnionHandler<AsyncHandler<any, string>> {
|
class SimpleUnionHandler extends UnionHandler<AsyncHandler<any, string>> {
|
||||||
public constructor(handlers: AsyncHandler<any, any>[], requireAll?: boolean) {
|
|
||||||
super(handlers, requireAll);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async combine(results: string[]): Promise<string> {
|
protected async combine(results: string[]): Promise<string> {
|
||||||
return results.join('');
|
return results.join('');
|
||||||
}
|
}
|
||||||
@ -61,4 +57,25 @@ describe('A UnionHandler', (): void => {
|
|||||||
handlers[0].canHandle.mockRejectedValue(new Error('bad request'));
|
handlers[0].canHandle.mockRejectedValue(new Error('bad request'));
|
||||||
await expect(handler.handle(input)).resolves.toBe('ab');
|
await expect(handler.handle(input)).resolves.toBe('ab');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('requires all handlers to succeed if requireAll is true.', async(): Promise<void> => {
|
||||||
|
handler = new SimpleUnionHandler(handlers, true);
|
||||||
|
|
||||||
|
handlers[0].handle.mockRejectedValue(new Error('bad request'));
|
||||||
|
await expect(handler.handleSafe(input)).rejects.toThrow('bad request');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not require all handlers to succeed if ignoreErrors is true.', async(): Promise<void> => {
|
||||||
|
handler = new SimpleUnionHandler(handlers, true, true);
|
||||||
|
|
||||||
|
handlers[0].handle.mockRejectedValueOnce(new Error('bad request'));
|
||||||
|
await expect(handler.handleSafe(input)).resolves.toBe('b');
|
||||||
|
|
||||||
|
handlers[1].handle.mockRejectedValueOnce(new Error('bad request'));
|
||||||
|
await expect(handler.handleSafe(input)).resolves.toBe('a');
|
||||||
|
|
||||||
|
handlers[0].handle.mockRejectedValueOnce(new Error('bad request'));
|
||||||
|
handlers[1].handle.mockRejectedValueOnce(new Error('bad request'));
|
||||||
|
await expect(handler.handleSafe(input)).resolves.toBe('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user