From ded263a81df0f9d92c3d48b51b3b665a9d73b0d6 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Thu, 10 Jun 2021 16:09:53 +0200 Subject: [PATCH] fix: Support empty PATCH requests --- package-lock.json | 109 ++++++++++++++++-- package.json | 2 +- .../SparqlPatchPermissionsExtractor.ts | 12 +- src/storage/patch/SparqlUpdatePatchHandler.ts | 6 + .../SparqlPatchPermissionsExtractor.test.ts | 13 +++ .../patch/SparqlUpdatePatchHandler.test.ts | 7 ++ 6 files changed, 139 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6b676e9c..b558f7e0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1432,6 +1432,29 @@ "asynciterator": "^3.1.0", "immutable": "^3.8.2", "sparqlalgebrajs": "^2.4.0" + }, + "dependencies": { + "sparqlalgebrajs": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-2.5.5.tgz", + "integrity": "sha512-sG9XI5311mS+JPDaeZUwtwYaYDRiTZDzxtHVS1GSrnfcZ2aiK1fa1PX9z16l7dtS35X3z1j1qyHEElzZO5OM3A==", + "requires": { + "fast-deep-equal": "^3.1.3", + "minimist": "^1.2.5", + "rdf-data-factory": "^1.0.4", + "rdf-isomorphic": "^1.2.0", + "rdf-string": "^1.5.0", + "sparqljs": "^3.3.0" + } + }, + "sparqljs": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.4.2.tgz", + "integrity": "sha512-MmmZ6cMuvhf4Eh2FXX21dalgADUiZ9WN8XKMedwhTFg0r7W09/o8wvoZ8C4yA6FptnjjAjm+mGnxAEpkSRY3QQ==", + "requires": { + "rdf-data-factory": "^1.0.4" + } + } } }, "@dabh/diagnostics": { @@ -2368,6 +2391,14 @@ "resolved": "https://registry.npmjs.org/@rdfjs/to-ntriples/-/to-ntriples-1.0.2.tgz", "integrity": "sha512-ngw5XAaGHjgGiwWWBPGlfdCclHftonmbje5lMys4G2j4NvfExraPIuRZgjSnd5lg4dnulRVUll8tRbgKO+7EDA==" }, + "@rdfjs/types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", + "integrity": "sha512-YxVkH0XrCNG3MWeZxfg596GFe+oorTVusmNxRP6ZHTsGczZ8AGvG3UchRNkg3Fy4MyysI7vBAA5YZbESL+VmHQ==", + "requires": { + "@types/node": "*" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -6569,6 +6600,15 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", @@ -9628,6 +9668,11 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -10769,6 +10814,14 @@ "rdf-terms": "^1.6.2" } }, + "rdf-js": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rdf-js/-/rdf-js-4.0.2.tgz", + "integrity": "sha512-ApvlFa/WsQh8LpPK/6hctQwG06Z9ztQQGWVtrcrf9L6+sejHNXLPOqL+w7q3hF+iL0C4sv3AX1PUtGkLNzyZ0Q==", + "requires": { + "@rdfjs/types": "*" + } + }, "rdf-literal": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rdf-literal/-/rdf-literal-1.2.0.tgz", @@ -11505,22 +11558,62 @@ } }, "sparqlalgebrajs": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-2.5.1.tgz", - "integrity": "sha512-J682nUANGeaaJexN5gcjw7fS507Vxoiz78wIRgFZ2Zv0nu4ObPZQDPgzYK01V0/FXd7sboXkWiHyG8BUeXnimQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sparqlalgebrajs/-/sparqlalgebrajs-3.0.0.tgz", + "integrity": "sha512-3QbDE78l170TiyzzTNkW4ug0p9rCRpNHK5vTN67O9LO2yrwnqI3v3Hp+8RW7ZQZAswZCPQ3T3o8TFDR6s3AZYA==", "requires": { + "@types/minimist": "^1.2.1", + "@types/node": "^15.12.2", + "@types/rdf-js": "^4.0.2", + "@types/sparqljs": "^3.1.2", "fast-deep-equal": "^3.1.3", "minimist": "^1.2.5", "rdf-data-factory": "^1.0.4", - "rdf-isomorphic": "^1.2.0", + "rdf-isomorphic": "^1.2.1", "rdf-string": "^1.5.0", - "sparqljs": "^3.3.0" + "sparqljs": "^3.4.2" }, "dependencies": { + "@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==" + }, + "@types/node": { + "version": "15.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", + "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==" + }, + "@types/rdf-js": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/rdf-js/-/rdf-js-4.0.2.tgz", + "integrity": "sha512-soR/+RMogGiDU1lrpuQl5ZL55/L1eq/JlR2dWx052Uh/RYs9okh3XZHFlIJXHZqjqyjEn4WdbOMfBj7vvc2WVQ==", + "requires": { + "rdf-js": "*" + } + }, + "@types/sparqljs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/sparqljs/-/sparqljs-3.1.2.tgz", + "integrity": "sha512-tLfrnBuK37P2Bn8Fo7Qik95sBXYHw5D+gq3MMq1HVyoTpCWivwPnP0Mmd7Apamdc9eH3mLJwIZIETHCQ6HxMUw==", + "requires": { + "rdf-js": "^4.0.2" + } + }, + "rdf-isomorphic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.2.1.tgz", + "integrity": "sha512-kIKlQYoizNqp8zhbca1zV3mYngisoD/KNt/xBRjagp7R3F8niI3b1vxvqcWlSkNXgPD6MsXpP2E/uXZ8oGTIcA==", + "requires": { + "hash.js": "^1.1.7", + "rdf-string": "^1.5.0", + "rdf-terms": "^1.6.2" + } + }, "sparqljs": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.3.0.tgz", - "integrity": "sha512-2WnOX1b72pmf8yHyhoaPKnqqCBMn22qa59H/aiIruQ4lkv9ngaSYGOos5ZqW3o6AcUvCj/ObLQw4RnfQKXRdww==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/sparqljs/-/sparqljs-3.4.2.tgz", + "integrity": "sha512-MmmZ6cMuvhf4Eh2FXX21dalgADUiZ9WN8XKMedwhTFg0r7W09/o8wvoZ8C4yA6FptnjjAjm+mGnxAEpkSRY3QQ==", "requires": { "rdf-data-factory": "^1.0.4" } diff --git a/package.json b/package.json index 282c63916..c2ce4f113 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "rdf-terms": "^1.5.1", "redis": "^3.0.2", "redlock": "^4.2.0", - "sparqlalgebrajs": "^2.5.1", + "sparqlalgebrajs": "^3.0.0", "sparqljs": "^3.1.2", "streamify-array": "^1.0.1", "url-join": "^4.0.1", diff --git a/src/ldp/permissions/SparqlPatchPermissionsExtractor.ts b/src/ldp/permissions/SparqlPatchPermissionsExtractor.ts index 695978d1b..6a29e064f 100644 --- a/src/ldp/permissions/SparqlPatchPermissionsExtractor.ts +++ b/src/ldp/permissions/SparqlPatchPermissionsExtractor.ts @@ -44,7 +44,7 @@ export class SparqlPatchPermissionsExtractor extends PermissionsExtractor { } private isSupported(op: Algebra.Operation): boolean { - if (op.type === Algebra.types.DELETE_INSERT) { + if (this.isDeleteInsert(op) || this.isNop(op)) { return true; } if (op.type === Algebra.types.COMPOSITE_UPDATE) { @@ -57,7 +57,14 @@ export class SparqlPatchPermissionsExtractor extends PermissionsExtractor { return op.type === Algebra.types.DELETE_INSERT; } + private isNop(op: Algebra.Operation): op is Algebra.Nop { + return op.type === Algebra.types.NOP; + } + private needsAppend(update: Algebra.Operation): boolean { + if (this.isNop(update)) { + return false; + } if (this.isDeleteInsert(update)) { return Boolean(update.insert && update.insert.length > 0); } @@ -66,6 +73,9 @@ export class SparqlPatchPermissionsExtractor extends PermissionsExtractor { } private needsWrite(update: Algebra.Operation): boolean { + if (this.isNop(update)) { + return false; + } if (this.isDeleteInsert(update)) { return Boolean(update.delete && update.delete.length > 0); } diff --git a/src/storage/patch/SparqlUpdatePatchHandler.ts b/src/storage/patch/SparqlUpdatePatchHandler.ts index de07bcf5b..48d17ed80 100644 --- a/src/storage/patch/SparqlUpdatePatchHandler.ts +++ b/src/storage/patch/SparqlUpdatePatchHandler.ts @@ -47,6 +47,12 @@ export class SparqlUpdatePatchHandler extends PatchHandler { // Verify the patch const { source, identifier, patch } = input; const op = (patch as SparqlUpdatePatch).algebra; + + // In case of a NOP we can skip everything + if (op.type === Algebra.types.NOP) { + return []; + } + this.validateUpdate(op); return this.applyPatch(source, identifier, op); diff --git a/test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts b/test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts index 2605b0e13..6f9e8672d 100644 --- a/test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts +++ b/test/unit/ldp/permissions/SparqlPatchPermissionsExtractor.test.ts @@ -32,6 +32,19 @@ describe('A SparqlPatchPermissionsExtractor', (): void => { await expect(result).rejects.toThrow('Can only determine permissions of a PATCH with DELETE/INSERT operations.'); }); + it('requires nothing for NOP operations.', async(): Promise => { + const operation = { + method: 'PATCH', + body: { algebra: factory.createNop() }, + } as unknown as Operation; + await expect(extractor.handle(operation)).resolves.toEqual({ + read: false, + append: false, + write: false, + control: false, + }); + }); + it('requires append for INSERT operations.', async(): Promise => { const operation = { method: 'PATCH', diff --git a/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts b/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts index 69c62150c..03a9eb5cc 100644 --- a/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts +++ b/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts @@ -93,6 +93,13 @@ describe('A SparqlUpdatePatchHandler', (): void => { await expect(handler.canHandle(input)).rejects.toThrow(NotImplementedHttpError); }); + it('handles NOP operations by not doing anything.', async(): Promise => { + await handle(''); + expect(source.getRepresentation).toHaveBeenCalledTimes(0); + expect(converter.handleSafe).toHaveBeenCalledTimes(0); + expect(source.setRepresentation).toHaveBeenCalledTimes(0); + }); + it('handles INSERT DATA updates.', async(): Promise => { await handle(fullfilledDataInsert); expect(await basicChecks(startQuads.concat(