feat: Add BasicRepresentation.

This commit is contained in:
Ruben Verborgh 2021-01-10 23:58:52 +01:00
parent be1af89b56
commit 66e636878f
20 changed files with 283 additions and 153 deletions

View File

@ -1,4 +1,5 @@
[
"BasicRepresentation",
"Error",
"EventEmitter",
"ValuePreferencesArg"

View File

@ -71,6 +71,7 @@ export * from './ldp/permissions/MethodPermissionsExtractor';
export * from './ldp/permissions/SparqlPatchPermissionsExtractor';
// LDP/Representation
export * from './ldp/representation/BasicRepresentation';
export * from './ldp/representation/Representation';
export * from './ldp/representation/RepresentationMetadata';
export * from './ldp/representation/RepresentationPreferences';

View File

@ -1,12 +1,11 @@
import type { AclManager } from '../authorization/AclManager';
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore';
import { TEXT_TURTLE } from '../util/ContentTypes';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { ensureTrailingSlash } from '../util/PathUtil';
import { guardedStreamFrom } from '../util/StreamUtil';
import { Initializer } from './Initializer';
/**
@ -66,15 +65,7 @@ export class AclInitializer extends Initializer {
acl:mode acl:Control;
acl:accessTo <${this.baseUrl}>;
acl:default <${this.baseUrl}>.`;
const metadata = new RepresentationMetadata(rootAcl, TEXT_TURTLE);
this.logger.debug(`Installing root ACL document at ${rootAcl.path}`);
await this.store.setRepresentation(
rootAcl,
{
binary: true,
data: guardedStreamFrom([ acl ]),
metadata,
},
);
await this.store.setRepresentation(rootAcl, new BasicRepresentation(acl, rootAcl, TEXT_TURTLE));
}
}

View File

@ -1,4 +1,5 @@
import { DataFactory } from 'n3';
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
@ -7,7 +8,6 @@ import { TEXT_TURTLE } from '../util/ContentTypes';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { ensureTrailingSlash } from '../util/PathUtil';
import { generateResourceQuads } from '../util/ResourceUtil';
import { guardedStreamFrom } from '../util/StreamUtil';
import { PIM, RDF } from '../util/Vocabularies';
import { Initializer } from './Initializer';
import namedNode = DataFactory.namedNode;
@ -54,20 +54,14 @@ export class RootContainerInitializer extends Initializer {
* Create a root container in a ResourceStore.
*/
protected async createRootContainer(): Promise<void> {
const metadata = new RepresentationMetadata(this.baseId);
const metadata = new RepresentationMetadata(this.baseId, TEXT_TURTLE);
metadata.addQuads(generateResourceQuads(namedNode(this.baseId.path), true));
// Make sure the root container is a pim:Storage
// This prevents deletion of the root container as storage root containers can not be deleted
metadata.add(RDF.type, PIM.terms.Storage);
metadata.contentType = TEXT_TURTLE;
this.logger.debug(`Creating root container at ${this.baseId.path}`);
await this.store.setRepresentation(this.baseId, {
binary: true,
data: guardedStreamFrom([]),
metadata,
});
await this.store.setRepresentation(this.baseId, new BasicRepresentation([], metadata));
}
}

View File

@ -1,5 +1,6 @@
import { getLoggerFor } from '../../logging/LogUtil';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { BasicRepresentation } from '../representation/BasicRepresentation';
import type { Representation } from '../representation/Representation';
import type { BodyParserArgs } from './BodyParser';
import { BodyParser } from './BodyParser';
@ -27,10 +28,6 @@ export class RawBodyParser extends BodyParser {
throw new BadRequestHttpError('HTTP request body was passed without Content-Type header');
}
return {
binary: true,
data: request,
metadata,
};
return new BasicRepresentation(request, metadata);
}
}

View File

@ -0,0 +1,113 @@
import type { Readable } from 'stream';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import type { Guarded } from '../../util/GuardedStream';
import { guardStream } from '../../util/GuardedStream';
import { guardedStreamFrom } from '../../util/StreamUtil';
import type { Representation } from './Representation';
import type { MetadataIdentifier, MetadataRecord } from './RepresentationMetadata';
import { RepresentationMetadata, isRepresentationMetadata } from './RepresentationMetadata';
/**
* Class with various constructors to facilitate creating a representation.
*
* A representation consists of 1) data, 2) metadata, and 3) a binary flag
* to indicate whether the data is a binary stream or an object stream.
*
* 1. The data can be given as a stream, array, or string.
* 2. The metadata can be specified as one or two parameters
* that will be passed to the {@link RepresentationMetadata} constructor.
* 3. The binary field is optional, and if not specified,
* is determined from the content type inside the metadata.
*/
export class BasicRepresentation implements Representation {
public readonly data: Guarded<Readable>;
public readonly metadata: RepresentationMetadata;
public readonly binary: boolean;
/**
* @param data - The representation data
* @param metadata - The representation metadata
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
metadata: RepresentationMetadata | MetadataRecord,
binary?: boolean,
);
/**
* @param data - The representation data
* @param metadata - The representation metadata
* @param contentType - The representation's content type
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
metadata: RepresentationMetadata | MetadataRecord,
contentType?: string,
binary?: boolean,
);
/**
* @param data - The representation data
* @param contentType - The representation's content type
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
contentType: string,
binary?: boolean,
);
/**
* @param data - The representation data
* @param identifier - The representation's identifier
* @param metadata - The representation metadata
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
identifier: MetadataIdentifier,
metadata?: MetadataRecord,
binary?: boolean,
);
/**
* @param data - The representation data
* @param identifier - The representation's identifier
* @param contentType - The representation's content type
* @param binary - Whether the representation is a binary or object stream
*/
public constructor(
data: Guarded<Readable> | Readable | any[] | string,
identifier: MetadataIdentifier,
contentType?: string,
binary?: boolean,
);
public constructor(
data: Readable | any[] | string,
metadata: RepresentationMetadata | MetadataRecord | MetadataIdentifier | string,
metadataRest?: MetadataRecord | string | boolean,
binary?: boolean,
) {
if (typeof data === 'string' || Array.isArray(data)) {
data = guardedStreamFrom(data);
}
this.data = guardStream(data);
if (typeof metadataRest === 'boolean') {
binary = metadataRest;
metadataRest = undefined;
}
if (!isRepresentationMetadata(metadata) || typeof metadataRest === 'string') {
metadata = new RepresentationMetadata(metadata as any, metadataRest as any);
}
this.metadata = metadata;
if (typeof binary !== 'boolean') {
binary = metadata.contentType !== INTERNAL_QUADS;
}
this.binary = binary;
}
}

View File

@ -1,5 +1,6 @@
import { promises as fsPromises } from 'fs';
import { Parser } from 'n3';
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import type {
@ -8,7 +9,6 @@ import type {
ResourceLink,
} from '../../storage/mapping/FileIdentifierMapper';
import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
import { guardedStreamFrom } from '../../util/StreamUtil';
import type { Resource, ResourcesGenerator } from './ResourcesGenerator';
import type { TemplateEngine } from './TemplateEngine';
import Dict = NodeJS.Dict;
@ -123,11 +123,7 @@ export class TemplatedResourcesGenerator implements ResourcesGenerator {
return {
identifier: link.identifier,
representation: {
binary: true,
data: guardedStreamFrom(data),
metadata,
},
representation: new BasicRepresentation(data, metadata),
};
}

View File

@ -2,8 +2,9 @@ import arrayifyStream from 'arrayify-stream';
import { DataFactory } from 'n3';
import type { Quad, Term } from 'rdf-js';
import { v4 as uuid } from 'uuid';
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
import type { Representation } from '../ldp/representation/Representation';
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
import type { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { INTERNAL_QUADS } from '../util/ContentTypes';
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
@ -21,7 +22,6 @@ import {
} from '../util/PathUtil';
import { parseQuads } from '../util/QuadUtil';
import { generateResourceQuads } from '../util/ResourceUtil';
import { guardedStreamFrom } from '../util/StreamUtil';
import { CONTENT_TYPE, HTTP, LDP, PIM, RDF } from '../util/Vocabularies';
import type { DataAccessor } from './accessors/DataAccessor';
import type { ResourceStore } from './ResourceStore';
@ -64,28 +64,9 @@ export class DataAccessorBasedStore implements ResourceStore {
// In the future we want to use getNormalizedMetadata and redirect in case the identifier differs
const metadata = await this.accessor.getMetadata(identifier);
let result: Representation;
// Create the representation of a container
if (this.isExistingContainer(metadata)) {
// Generate the data stream before setting the content-type to prevent unnecessary triples
const data = guardedStreamFrom(metadata.quads());
metadata.contentType = INTERNAL_QUADS;
result = {
binary: false,
data,
metadata,
};
// Obtain a representation of a document
} else {
result = {
binary: metadata.contentType !== INTERNAL_QUADS,
data: await this.accessor.getData(identifier),
metadata,
};
}
return result;
return this.isExistingContainer(metadata) ?
new BasicRepresentation(metadata.quads(), metadata, INTERNAL_QUADS) :
new BasicRepresentation(await this.accessor.getData(identifier), metadata);
}
public async addResource(container: ResourceIdentifier, representation: Representation): Promise<ResourceIdentifier> {
@ -374,22 +355,10 @@ export class DataAccessorBasedStore implements ResourceStore {
if (error instanceof NotFoundHttpError) {
// Make sure the parent exists first
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
await this.writeData(container, this.getEmptyContainerRepresentation(container), true);
await this.writeData(container, new BasicRepresentation([], container), true);
} else {
throw error;
}
}
}
/**
* Generates the minimal representation for an empty container.
* @param container - Identifier of this new container.
*/
protected getEmptyContainerRepresentation(container: ResourceIdentifier): Representation {
return {
binary: true,
data: guardedStreamFrom([]),
metadata: new RepresentationMetadata(container),
};
}
}

View File

@ -1,11 +1,10 @@
import type { Readable } from 'stream';
import { StreamWriter } from 'n3';
import rdfSerializer from 'rdf-serialize';
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
import type { Representation } from '../../ldp/representation/Representation';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { ValuePreferences } from '../../ldp/representation/RepresentationPreferences';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { guardStream } from '../../util/GuardedStream';
import { pipeSafely } from '../../util/StreamUtil';
import { PREFERRED_PREFIX_TERM } from '../../util/Vocabularies';
import { matchingMediaTypes } from './ConversionUtil';
@ -27,22 +26,18 @@ export class QuadToRdfConverter extends TypedRepresentationConverter {
public async handle({ representation: quads, preferences }: RepresentationConverterArgs): Promise<Representation> {
const contentType = matchingMediaTypes(preferences.type, await this.getOutputTypes())[0];
const metadata = new RepresentationMetadata(quads.metadata, contentType);
let data: Readable;
// Use prefixes if possible (see https://github.com/rubensworks/rdf-serialize.js/issues/1)
if (/(?:turtle|trig)$/u.test(contentType)) {
const prefixes = Object.fromEntries(metadata.quads(null, PREFERRED_PREFIX_TERM, null)
const prefixes = Object.fromEntries(quads.metadata.quads(null, PREFERRED_PREFIX_TERM, null)
.map(({ subject, object }): [string, string] => [ object.value, subject.value ]));
data = pipeSafely(quads.data, new StreamWriter({ format: contentType, prefixes }));
// Otherwise, write without prefixes
} else {
data = rdfSerializer.serialize(quads.data, { contentType }) as Readable;
}
return {
binary: true,
data: guardStream(data),
metadata,
};
return new BasicRepresentation(data, quads.metadata, contentType);
}
}

View File

@ -1,7 +1,7 @@
import { PassThrough } from 'stream';
import rdfParser from 'rdf-parse';
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
import type { Representation } from '../../ldp/representation/Representation';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { pipeSafely } from '../../util/StreamUtil';
@ -17,19 +17,13 @@ export class RdfToQuadConverter extends TypedRepresentationConverter {
}
public async handle({ representation, identifier }: RepresentationConverterArgs): Promise<Representation> {
const metadata = new RepresentationMetadata(representation.metadata, INTERNAL_QUADS);
const rawQuads = rdfParser.parse(representation.data, {
contentType: representation.metadata.contentType!,
baseIRI: identifier.path,
});
const pass = new PassThrough({ objectMode: true });
const data = pipeSafely(rawQuads, pass, (error): Error => new BadRequestHttpError(error.message));
return {
binary: false,
data,
metadata,
};
return new BasicRepresentation(data, representation.metadata, INTERNAL_QUADS);
}
}

View File

@ -5,14 +5,12 @@ import type { BaseQuad } from 'rdf-js';
import { someTerms } from 'rdf-terms';
import { Algebra } from 'sparqlalgebrajs';
import type { SparqlUpdatePatch } from '../../ldp/http/SparqlUpdatePatch';
import type { Representation } from '../../ldp/representation/Representation';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { guardStream } from '../../util/GuardedStream';
import type { ResourceLocker } from '../../util/locking/ResourceLocker';
import type { ResourceStore } from '../ResourceStore';
import { PatchHandler } from './PatchHandler';
@ -108,12 +106,6 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
this.logger.debug(`${store.size} quads will be stored to ${identifier.path}.`);
// Write the result
const metadata = new RepresentationMetadata(identifier, INTERNAL_QUADS);
const representation: Representation = {
binary: false,
data: guardStream(store.match() as Readable),
metadata,
};
await this.source.setRepresentation(identifier, representation);
await this.source.setRepresentation(identifier, new BasicRepresentation(store.match() as Readable, INTERNAL_QUADS));
}
}

View File

@ -25,7 +25,7 @@ export type Guarded<T extends NodeJS.EventEmitter = NodeJS.EventEmitter> = T & G
* Determines whether the stream is guarded from emitting errors.
*/
export function isGuarded<T extends NodeJS.EventEmitter>(stream: T): stream is Guarded<T> {
return guardedErrors in stream;
return typeof (stream as any)[guardedErrors] === 'object';
}
/**

View File

@ -97,10 +97,10 @@ export function transformSafely<T = any>(
}
/**
* Converts an iterable to a stream and applies an error guard so that it is {@link Guarded}.
* @param iterable - Data to stream.
* Converts a string or array to a stream and applies an error guard so that it is {@link Guarded}.
* @param contents - Data to stream.
* @param options - Options to pass to the Readable constructor. See {@link Readable.from}.
*/
export function guardedStreamFrom(iterable: Iterable<any>, options?: ReadableOptions): Guarded<Readable> {
return guardStream(Readable.from(iterable, options));
export function guardedStreamFrom(contents: string | Iterable<any>, options?: ReadableOptions): Guarded<Readable> {
return guardStream(Readable.from(typeof contents === 'string' ? [ contents ] : contents, options));
}

View File

@ -1,6 +1,6 @@
import { createReadStream } from 'fs';
import type { HttpHandler, Initializer, ResourceStore } from '../../src/';
import { LDP, RepresentationMetadata, guardStream, joinFilePath } from '../../src/';
import { LDP, BasicRepresentation, joinFilePath } from '../../src/';
import { AclHelper, ResourceHelper } from '../util/TestHelpers';
import { BASE, getTestFolder, createFolder, removeFolder, instantiateFromConfig } from './Config';
@ -53,11 +53,8 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeUrn, se
resourceHelper = new ResourceHelper(handler, BASE);
// Write test resource
await store.setRepresentation({ path: `${BASE}/permanent.txt` }, {
binary: true,
data: guardStream(createReadStream(joinFilePath(__dirname, '../assets/permanent.txt'))),
metadata: new RepresentationMetadata('text/plain'),
});
await store.setRepresentation({ path: `${BASE}/permanent.txt` },
new BasicRepresentation(createReadStream(joinFilePath(__dirname, '../assets/permanent.txt')), 'text/plain'));
});
afterAll(async(): Promise<void> => {

View File

@ -1,7 +1,7 @@
import streamifyArray from 'streamify-array';
import { RootContainerInitializer } from '../../src/init/RootContainerInitializer';
import { BasicRepresentation } from '../../src/ldp/representation/BasicRepresentation';
import type { Representation } from '../../src/ldp/representation/Representation';
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
import { InMemoryDataAccessor } from '../../src/storage/accessors/InMemoryDataAccessor';
import { DataAccessorBasedStore } from '../../src/storage/DataAccessorBasedStore';
import { LockingResourceStore } from '../../src/storage/LockingResourceStore';
@ -12,7 +12,6 @@ import type { ExpiringResourceLocker } from '../../src/util/locking/ExpiringReso
import type { ResourceLocker } from '../../src/util/locking/ResourceLocker';
import { SingleThreadedResourceLocker } from '../../src/util/locking/SingleThreadedResourceLocker';
import { WrappedExpiringResourceLocker } from '../../src/util/locking/WrappedExpiringResourceLocker';
import { guardedStreamFrom } from '../../src/util/StreamUtil';
import { BASE } from './Config';
describe('A LockingResourceStore', (): void => {
@ -39,9 +38,7 @@ describe('A LockingResourceStore', (): void => {
store = new LockingResourceStore(source, expiringLocker);
// Make sure something is in the store before we read from it in our tests.
const metadata = new RepresentationMetadata(APPLICATION_OCTET_STREAM);
const data = guardedStreamFrom([ 1, 2, 3 ]);
await store.setRepresentation({ path }, { metadata, data, binary: true });
await store.setRepresentation({ path }, new BasicRepresentation([ 1, 2, 3 ], APPLICATION_OCTET_STREAM));
});
it('destroys the stream when nothing is read after 1000ms.', async(): Promise<void> => {

View File

@ -1,9 +1,8 @@
import type { Representation } from '../../src/ldp/representation/Representation';
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
import { BasicRepresentation } from '../../src/ldp/representation/BasicRepresentation';
import { ChainedConverter } from '../../src/storage/conversion/ChainedConverter';
import { QuadToRdfConverter } from '../../src/storage/conversion/QuadToRdfConverter';
import { RdfToQuadConverter } from '../../src/storage/conversion/RdfToQuadConverter';
import { guardedStreamFrom, readableToString } from '../../src/util/StreamUtil';
import { readableToString } from '../../src/util/StreamUtil';
describe('A ChainedConverter', (): void => {
const converters = [
@ -13,14 +12,10 @@ describe('A ChainedConverter', (): void => {
const converter = new ChainedConverter(converters);
it('can convert from JSON-LD to turtle.', async(): Promise<void> => {
const metadata = new RepresentationMetadata('application/ld+json');
const representation: Representation = {
binary: true,
data: guardedStreamFrom(
[ '{"@id": "http://test.com/s", "http://test.com/p": { "@id": "http://test.com/o" }}' ],
),
metadata,
};
const representation = new BasicRepresentation(
'{"@id": "http://test.com/s", "http://test.com/p": { "@id": "http://test.com/o" }}',
'application/ld+json',
);
const result = await converter.handleSafe({
representation,
@ -33,12 +28,10 @@ describe('A ChainedConverter', (): void => {
});
it('can convert from turtle to JSON-LD.', async(): Promise<void> => {
const metadata = new RepresentationMetadata('text/turtle');
const representation: Representation = {
binary: true,
data: guardedStreamFrom([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]),
metadata,
};
const representation = new BasicRepresentation(
'<http://test.com/s> <http://test.com/p> <http://test.com/o>.',
'text/turtle',
);
const result = await converter.handleSafe({
representation,

View File

@ -0,0 +1,118 @@
import 'jest-rdf';
import { namedNode } from '@rdfjs/data-model';
import arrayifyStream from 'arrayify-stream';
import streamifyArray from 'streamify-array';
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import { INTERNAL_QUADS } from '../../../../src/util/ContentTypes';
import { guardedStreamFrom } from '../../../../src/util/StreamUtil';
import { CONTENT_TYPE } from '../../../../src/util/Vocabularies';
describe('BasicRepresentation', (): void => {
it('creates a representation with (data, metadata, binary).', (): void => {
const data = guardedStreamFrom([ '' ]);
const metadata = new RepresentationMetadata();
const representation = new BasicRepresentation(data, metadata, true);
expect(representation.data).toBe(data);
expect(representation.metadata).toBe(metadata);
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, metadata).', (): void => {
const data = guardedStreamFrom([ '' ]);
let metadata = new RepresentationMetadata();
let representation = new BasicRepresentation(data, metadata);
expect(representation.data).toBe(data);
expect(representation.metadata).toBe(metadata);
expect(representation.binary).toBe(true);
metadata = new RepresentationMetadata(INTERNAL_QUADS);
representation = new BasicRepresentation(data, metadata);
expect(representation.data).toBe(data);
expect(representation.metadata).toBe(metadata);
expect(representation.binary).toBe(false);
});
it('creates a representation with (unguarded data, metadata).', (): void => {
const data = streamifyArray([ '' ]);
const metadata = new RepresentationMetadata();
const representation = new BasicRepresentation(data, metadata);
expect(representation.data).toBe(data);
expect(representation.metadata).toBe(metadata);
expect(representation.binary).toBe(true);
});
it('creates a representation with (array data, metadata).', async(): Promise<void> => {
const data = [ 'my', 'data' ];
const metadata = new RepresentationMetadata();
const representation = new BasicRepresentation(data, metadata);
expect(await arrayifyStream(representation.data)).toEqual(data);
expect(representation.metadata).toBe(metadata);
expect(representation.binary).toBe(true);
});
it('creates a representation with (string data, metadata).', async(): Promise<void> => {
const data = 'my data';
const metadata = new RepresentationMetadata();
const representation = new BasicRepresentation(data, metadata);
expect(await arrayifyStream(representation.data)).toEqual([ data ]);
expect(representation.metadata).toBe(metadata);
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, metadata record).', (): void => {
const data = guardedStreamFrom([ '' ]);
const representation = new BasicRepresentation(data, { [CONTENT_TYPE]: 'text/custom' });
expect(representation.data).toBe(data);
expect(representation.metadata.contentType).toBe('text/custom');
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, content type).', (): void => {
const data = guardedStreamFrom([ '' ]);
const representation = new BasicRepresentation(data, 'text/custom');
expect(representation.data).toBe(data);
expect(representation.metadata.contentType).toBe('text/custom');
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, identifier, metadata record).', (): void => {
const identifier = { path: 'http://example.org/#' };
const data = guardedStreamFrom([ '' ]);
const representation = new BasicRepresentation(data, identifier, { [CONTENT_TYPE]: 'text/custom' });
expect(representation.data).toBe(data);
expect(representation.metadata.identifier).toEqualRdfTerm(namedNode(identifier.path));
expect(representation.metadata.contentType).toBe('text/custom');
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, identifier, content type).', (): void => {
const identifier = { path: 'http://example.org/#' };
const data = guardedStreamFrom([ '' ]);
const representation = new BasicRepresentation(data, identifier, 'text/custom');
expect(representation.data).toBe(data);
expect(representation.metadata.identifier).toEqualRdfTerm(namedNode(identifier.path));
expect(representation.metadata.contentType).toBe('text/custom');
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, identifier term, metadata record).', (): void => {
const identifier = namedNode('http://example.org/#');
const data = guardedStreamFrom([ '' ]);
const representation = new BasicRepresentation(data, identifier, { [CONTENT_TYPE]: 'text/custom' });
expect(representation.data).toBe(data);
expect(representation.metadata.identifier).toBe(identifier);
expect(representation.metadata.contentType).toBe('text/custom');
expect(representation.binary).toBe(true);
});
it('creates a representation with (data, identifier term, content type).', (): void => {
const identifier = namedNode('http://example.org/#');
const data = guardedStreamFrom([ '' ]);
const representation = new BasicRepresentation(data, identifier, 'text/custom');
expect(representation.data).toBe(data);
expect(representation.metadata.identifier).toBe(identifier);
expect(representation.metadata.contentType).toBe('text/custom');
expect(representation.binary).toBe(true);
});
});

View File

@ -1,5 +1,4 @@
import { Readable } from 'stream';
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
import type { Agent } from '../../../src/pods/agent/Agent';
import type { IdentifierGenerator } from '../../../src/pods/generate/IdentifierGenerator';
@ -46,11 +45,7 @@ describe('A GeneratedPodManager', (): void => {
});
it('throws an error if the generate identifier is not available.', async(): Promise<void> => {
(store.getRepresentation as jest.Mock).mockImplementationOnce((): any => ({
data: Readable.from([]),
metadata: new RepresentationMetadata(),
binary: true,
}));
(store.getRepresentation as jest.Mock).mockImplementationOnce((): any => new BasicRepresentation([], {}));
const result = manager.createPod(agent);
await expect(result).rejects.toThrow(`There already is a resource at ${base}user/`);
await expect(result).rejects.toThrow(ConflictHttpError);

View File

@ -1,3 +1,4 @@
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
import type { Representation } from '../../../../src/ldp/representation/Representation';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import { AgentJsonParser } from '../../../../src/pods/agent/AgentJsonParser';
@ -11,13 +12,8 @@ describe('An AgentJsonParser', (): void => {
const parser = new AgentJsonParser();
beforeEach(async(): Promise<void> => {
metadata = new RepresentationMetadata();
metadata.contentType = 'application/json';
representation = {
binary: true,
data: guardedStreamFrom([]),
metadata,
};
metadata = new RepresentationMetadata('application/json');
representation = new BasicRepresentation([], metadata);
});
it('only supports JSON data.', async(): Promise<void> => {

View File

@ -6,7 +6,7 @@ import * as url from 'url';
import type { MockResponse } from 'node-mocks-http';
import { createResponse } from 'node-mocks-http';
import type { ResourceStore, PermissionSet, HttpHandler, HttpRequest } from '../../src/';
import { guardedStreamFrom, RepresentationMetadata, joinFilePath, ensureTrailingSlash } from '../../src/';
import { BasicRepresentation, joinFilePath, ensureTrailingSlash } from '../../src/';
import { performRequest } from './Util';
/* eslint-disable jest/no-standalone-expect */
@ -45,16 +45,7 @@ export class AclHelper {
acl.push('.');
const representation = {
binary: true,
data: guardedStreamFrom(acl),
metadata: new RepresentationMetadata('text/turtle'),
};
return this.store.setRepresentation(
{ path: `${this.id}.acl` },
representation,
);
await this.store.setRepresentation({ path: `${this.id}.acl` }, new BasicRepresentation(acl, 'text/turtle'));
}
}