From ee88bf14de27989381dd1573c00fd6ab5407c70f Mon Sep 17 00:00:00 2001 From: Arthur Joppart <38424924+BelgianNoise@users.noreply.github.com> Date: Thu, 4 Mar 2021 08:43:53 +0100 Subject: [PATCH] feat: Added cloneRepresentation function to ResourceUtil * feat: added cloneRepresentation function to ResourceUtil * fix: adapted to review * fix: adapted to review Co-authored-by: Arne Vandoorslaer --- src/ldp/auxiliary/RdfValidator.ts | 30 +++++++++----------- src/util/ResourceUtil.ts | 23 +++++++++++++++ test/unit/ldp/auxiliary/RdfValidator.test.ts | 2 +- test/unit/util/ResourceUtil.test.ts | 27 ++++++++++++++++++ 4 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 test/unit/util/ResourceUtil.test.ts diff --git a/src/ldp/auxiliary/RdfValidator.ts b/src/ldp/auxiliary/RdfValidator.ts index 663e921e0..a9fc56eea 100644 --- a/src/ldp/auxiliary/RdfValidator.ts +++ b/src/ldp/auxiliary/RdfValidator.ts @@ -1,8 +1,7 @@ import arrayifyStream from 'arrayify-stream'; import type { RepresentationConverter } from '../../storage/conversion/RepresentationConverter'; import { INTERNAL_QUADS } from '../../util/ContentTypes'; -import { guardedStreamFrom } from '../../util/StreamUtil'; -import { BasicRepresentation } from '../representation/BasicRepresentation'; +import { cloneRepresentation } from '../../util/ResourceUtil'; import type { Representation } from '../representation/Representation'; import { Validator } from './Validator'; @@ -23,23 +22,22 @@ export class RdfValidator extends Validator { if (representation.metadata.contentType === INTERNAL_QUADS) { return; } - - // eslint-disable-next-line unicorn/expiring-todo-comments - // TODO: Everything below should be part of a utility cloneRepresentation function. - const identifier = { path: representation.metadata.identifier.value }; - - // Read data in memory first so it does not get lost - const data = await arrayifyStream(representation.data); const preferences = { type: { [INTERNAL_QUADS]: 1 }}; - - // Creating new representation since converter might edit metadata - const tempRepresentation = new BasicRepresentation(data, identifier, representation.metadata.contentType); - const result = await this.converter.handleSafe({ identifier, representation: tempRepresentation, preferences }); + let result; + try { + // Creating new representation since converter might edit metadata + const tempRepresentation = await cloneRepresentation(representation); + result = await this.converter.handleSafe({ + identifier, + representation: tempRepresentation, + preferences, + }); + } catch (error: unknown) { + representation.data.destroy(); + throw error; + } // Drain stream to make sure data was parsed correctly await arrayifyStream(result.data); - - // Stream has been drained so need to create new stream - representation.data = guardedStreamFrom(data); } } diff --git a/src/util/ResourceUtil.ts b/src/util/ResourceUtil.ts index 6c6482b67..b93769c71 100644 --- a/src/util/ResourceUtil.ts +++ b/src/util/ResourceUtil.ts @@ -1,7 +1,12 @@ +import arrayifyStream from 'arrayify-stream'; import { DataFactory } from 'n3'; import type { NamedNode, Quad } from 'rdf-js'; +import { BasicRepresentation } from '../ldp/representation/BasicRepresentation'; +import type { Representation } from '../ldp/representation/Representation'; import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata'; import { pushQuad } from './QuadUtil'; +import { guardedStreamFrom } from './StreamUtil'; + import { LDP, RDF } from './Vocabularies'; /** @@ -33,3 +38,21 @@ export function generateContainmentQuads(containerURI: NamedNode, childURIs: str return new RepresentationMetadata(containerURI, { [LDP.contains]: childURIs.map(DataFactory.namedNode) }).quads(); } + +/** + * Helper function to clone a representation, the original representation can still be used. + * This function loads the entire stream in memory. + * @param representation - The representation to clone. + * + * @returns The cloned representation. + */ +export async function cloneRepresentation(representation: Representation): Promise { + const data = await arrayifyStream(representation.data); + const result = new BasicRepresentation( + data, + new RepresentationMetadata(representation.metadata), + representation.binary, + ); + representation.data = guardedStreamFrom(data); + return result; +} diff --git a/test/unit/ldp/auxiliary/RdfValidator.test.ts b/test/unit/ldp/auxiliary/RdfValidator.test.ts index 8216e75a2..8745d1c8a 100644 --- a/test/unit/ldp/auxiliary/RdfValidator.test.ts +++ b/test/unit/ldp/auxiliary/RdfValidator.test.ts @@ -39,6 +39,6 @@ describe('An RdfValidator', (): void => { const representation = new BasicRepresentation('data', 'content-type'); await expect(validator.handle(representation)).rejects.toThrow('bad data!'); // Make sure the data on the readable has not been reset - expect(representation.data.readableEnded).toBe(true); + expect(representation.data.destroyed).toBe(true); }); }); diff --git a/test/unit/util/ResourceUtil.test.ts b/test/unit/util/ResourceUtil.test.ts new file mode 100644 index 000000000..67dfa6846 --- /dev/null +++ b/test/unit/util/ResourceUtil.test.ts @@ -0,0 +1,27 @@ +import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation'; +import type { Representation } from '../../../src/ldp/representation/Representation'; +import * as resourceUtils from '../../../src/util/ResourceUtil'; +import 'jest-rdf'; + +describe('ResourceUtil', (): void => { + let representation: Representation; + + beforeEach(async(): Promise => { + representation = new BasicRepresentation('data', 'metadata'); + }); + + describe('cloneRepresentation', (): void => { + it('returns a clone of the passed representation.', async(): Promise => { + const res = await resourceUtils.cloneRepresentation(representation); + expect(res.binary).toBe(representation.binary); + expect(res.metadata.identifier).toBe(representation.metadata.identifier); + expect(res.metadata.contentType).toBe(representation.metadata.contentType); + }); + + it('ensures that original representation does not update when the clone is updated.', async(): Promise => { + const res = await resourceUtils.cloneRepresentation(representation); + res.metadata.contentType = 'typetype'; + expect(representation.metadata.contentType).not.toBe(res.metadata.contentType); + }); + }); +});