Repository: solid/community-server
Pull request #491 (2021-01-10 20:33:34)

Add new metadata and representation constructors
I noticed that `RepresentationMetadata` was often instantiated with just a content type, so I added a shortcut for this.

This allowed me to introduce `BasicRepresentation` as a convenience constructor.

RubenVerborgh: If I understand correctly, I think your question is independent of whether we use a constructor or a factory function.
Because there is no inherent difference between `new BasicRepresentation` and `Representation.from`.

So what you might really be asking is to have named parameters instead of a list of parameters?

> from a first look at where the constructors are used it's not always immediately clear what has to go in which numbered field

That should be clear however; the constructors are documented?

The logic at the moment is: `BasicRepresentation` is always `data, metadata, binary?`. The `data` argument is always one value (stream, string, array), the `metadata` value sometimes consists of two parts. And `binary` is always optional.
RubenVerborgh: … so perhaps all we need is an options map for the `metadata` argument? Since the other two are always one field.
joachimvh: Ah yea, constructor with a single named parameter would also do the same, no idea why my brain didn't go there 😄 .

It's documented yes, but in case there are many versions of a constructor I personally find it a bit harder to immediately grasp what is done with the values, especially if multiple different types can occur in the same position. It might also be because I'm not used to it yet, but then that would also be the situation for new developers.

> The logic at the moment is: `BasicRepresentation` is always `data, metadata, binary?`. The `data` argument is always one value (stream, string, array), the `metadata` value sometimes consists of two parts. And `binary` is always optional.

But from what I can see there is also a constructor with `data, identifier, metadata?, binary?` so it's not always binary boolean in third position for example.


RubenVerborgh: > so it's not always binary boolean in third position for example.

Yes, since:

> > the metadata value sometimes consists of two parts.
joachimvh: This is purely objective of course, but when I saw the amount of constructors in `BasicRepresentation` it felt like an indication that there was something wrong.

On the other hand, if we add too many named options then we just end up in the initial case of creating a Representation object I guess, so that's also not ideal.

Since I don't really have strong arguments either way I'll leave it up to you to decide on the best course since you felt the need for these functions when developing a new module.
RubenVerborgh: Yes, `BasicRepresentation` indeed has the combination of its own constructors times the options of `RepresentationMetadata`.

I will see if I can simplify any further. But yes, I'd be inclined to merge because it does make sense when you see usage:
- https://github.com/RubenVerborgh/solid-hue/commit/2bdf4e228d0a1efa77840d30229dbcc8e6f0a9a7#diff-31541e15402fa0d5b58a80acc368dac7894e6f9fa9a1cbba2950eedfaa99c4b1R46
- https://github.com/RubenVerborgh/solid-hue/commit/2bdf4e228d0a1efa77840d30229dbcc8e6f0a9a7#diff-7f00ccca0a5d684cf6667c08205326e57b649644720382cc86c92276fdf0dca4R48

And to avoid further complexity, I also had an idea like `QuadRepresentation`, which would then set options more specific to certain representations.

Patch:
diff --git a/.componentsignore b/.componentsignore
index 988877fa3..5bcc29553 100644
--- a/.componentsignore
+++ b/.componentsignore
@@ -1,4 +1,5 @@
 [
+  "BasicRepresentation",
   "Error",
   "EventEmitter",
   "ValuePreferencesArg"
diff --git a/src/index.ts b/src/index.ts
index c28cb1bd2..6c3af7e40 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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';
diff --git a/src/init/AclInitializer.ts b/src/init/AclInitializer.ts
index b7c401dbe..a9c2f7961 100644
--- a/src/init/AclInitializer.ts
+++ b/src/init/AclInitializer.ts
@@ -1,13 +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 { CONTENT_TYPE } from '../util/Vocabularies';
 import { Initializer } from './Initializer';
 
 /**
@@ -67,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, { [CONTENT_TYPE]: 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));
   }
 }
diff --git a/src/init/RootContainerInitializer.ts b/src/init/RootContainerInitializer.ts
index 2d4e6a719..ef83094cf 100644
--- a/src/init/RootContainerInitializer.ts
+++ b/src/init/RootContainerInitializer.ts
@@ -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));
   }
 }
diff --git a/src/ldp/http/RawBodyParser.ts b/src/ldp/http/RawBodyParser.ts
index a05bbeb71..57ac98d44 100644
--- a/src/ldp/http/RawBodyParser.ts
+++ b/src/ldp/http/RawBodyParser.ts
@@ -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);
   }
 }
diff --git a/src/ldp/representation/BasicRepresentation.ts b/src/ldp/representation/BasicRepresentation.ts
new file mode 100644
index 000000000..7c9a93f62
--- /dev/null
+++ b/src/ldp/representation/BasicRepresentation.ts
@@ -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;
+  }
+}
diff --git a/src/ldp/representation/RepresentationMetadata.ts b/src/ldp/representation/RepresentationMetadata.ts
index 4f9a5818e..35e53b179 100644
--- a/src/ldp/representation/RepresentationMetadata.ts
+++ b/src/ldp/representation/RepresentationMetadata.ts
@@ -2,12 +2,13 @@ import { DataFactory, Store } from 'n3';
 import type { BlankNode, Literal, NamedNode, Quad, Term } from 'rdf-js';
 import { getLoggerFor } from '../../logging/LogUtil';
 import { toSubjectTerm, toObjectTerm, toCachedNamedNode, isTerm } from '../../util/TermUtil';
-import { CONTENT_TYPE_TERM } from '../../util/Vocabularies';
+import { CONTENT_TYPE, CONTENT_TYPE_TERM } from '../../util/Vocabularies';
 import type { ResourceIdentifier } from './ResourceIdentifier';
 import { isResourceIdentifier } from './ResourceIdentifier';
 
 export type MetadataIdentifier = ResourceIdentifier | NamedNode | BlankNode;
-export type MetadataOverrideValue = NamedNode | Literal | string | (NamedNode | Literal | string)[];
+export type MetadataValue = NamedNode | Literal | string | (NamedNode | Literal | string)[];
+export type MetadataRecord = Record<string, MetadataValue>;
 
 /**
  * Determines whether the object is a `RepresentationMetadata`.
@@ -34,23 +35,40 @@ export class RepresentationMetadata {
    *
    * `@ignored` tag is necessary for Components-Generator.js
    */
-  public constructor(identifier?: MetadataIdentifier, overrides?: Record<string, MetadataOverrideValue>);
+  public constructor(identifier?: MetadataIdentifier, overrides?: MetadataRecord);
 
   /**
    * @param metadata - Starts as a copy of the input metadata.
    * @param overrides - Key/value map of extra values that need to be added to the metadata.
    *                    Will override values that were set by the input metadata.
    */
-  public constructor(metadata?: RepresentationMetadata, overrides?: Record<string, MetadataOverrideValue>);
+  public constructor(metadata?: RepresentationMetadata, overrides?: MetadataRecord);
 
   /**
-   * @param overrides - Key/value map of extra values that need to be added to the metadata.
+   * @param identifier - Identifier of the resource relevant to this metadata.
+   * @param contentType - Override for the content type of the representation.
    */
-  public constructor(overrides?: Record<string, MetadataOverrideValue>);
+  public constructor(identifier?: MetadataIdentifier, contentType?: string);
+
+  /**
+   * @param metadata - Starts as a copy of the input metadata.
+   * @param contentType - Override for the content type of the representation.
+   */
+  public constructor(metadata?: RepresentationMetadata, contentType?: string);
+
+  /**
+   * @param contentType - The content type of the representation.
+   */
+  public constructor(contentType?: string);
+
+  /**
+   * @param overrides - Metadata values (defaulting to content type if a string)
+   */
+  public constructor(metadata?: RepresentationMetadata | MetadataRecord | string);
 
   public constructor(
-    input?: MetadataIdentifier | RepresentationMetadata | Record<string, MetadataOverrideValue>,
-    overrides?: Record<string, MetadataOverrideValue>,
+    input?: MetadataIdentifier | RepresentationMetadata | MetadataRecord | string,
+    overrides?: MetadataRecord | string,
   ) {
     this.store = new Store();
     if (isResourceIdentifier(input)) {
@@ -66,11 +84,14 @@ export class RepresentationMetadata {
     }
 
     if (overrides) {
+      if (typeof overrides === 'string') {
+        overrides = { [CONTENT_TYPE]: overrides };
+      }
       this.setOverrides(overrides);
     }
   }
 
-  private setOverrides(overrides: Record<string, MetadataOverrideValue>): void {
+  private setOverrides(overrides: Record<string, MetadataValue>): void {
     for (const predicate of Object.keys(overrides)) {
       const namedPredicate = toCachedNamedNode(predicate);
       this.removeAll(namedPredicate);
diff --git a/src/pods/generate/TemplatedResourcesGenerator.ts b/src/pods/generate/TemplatedResourcesGenerator.ts
index f7982cf5c..9237679ad 100644
--- a/src/pods/generate/TemplatedResourcesGenerator.ts
+++ b/src/pods/generate/TemplatedResourcesGenerator.ts
@@ -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),
     };
   }
 
diff --git a/src/storage/DataAccessorBasedStore.ts b/src/storage/DataAccessorBasedStore.ts
index 30771f174..7618faee8 100644
--- a/src/storage/DataAccessorBasedStore.ts
+++ b/src/storage/DataAccessorBasedStore.ts
@@ -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),
-    };
-  }
 }
diff --git a/src/storage/conversion/ContentTypeReplacer.ts b/src/storage/conversion/ContentTypeReplacer.ts
index a92e684ec..1783bbaa1 100644
--- a/src/storage/conversion/ContentTypeReplacer.ts
+++ b/src/storage/conversion/ContentTypeReplacer.ts
@@ -2,7 +2,6 @@ import type { Representation } from '../../ldp/representation/Representation';
 import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
 import type { ValuePreferences } from '../../ldp/representation/RepresentationPreferences';
 import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
-import { CONTENT_TYPE } from '../../util/Vocabularies';
 import { matchesMediaType, matchingMediaTypes } from './ConversionUtil';
 import type { RepresentationConverterArgs } from './RepresentationConverter';
 import { RepresentationConverter } from './RepresentationConverter';
@@ -50,7 +49,7 @@ export class ContentTypeReplacer extends RepresentationConverter {
    */
   public async handle({ representation, preferences }: RepresentationConverterArgs): Promise<Representation> {
     const contentType = this.getReplacementType(representation.metadata.contentType, preferences.type);
-    const metadata = new RepresentationMetadata(representation.metadata, { [CONTENT_TYPE]: contentType });
+    const metadata = new RepresentationMetadata(representation.metadata, contentType);
     return { ...representation, metadata };
   }
 
diff --git a/src/storage/conversion/QuadToRdfConverter.ts b/src/storage/conversion/QuadToRdfConverter.ts
index 65f2dc221..a1ec3bd1e 100644
--- a/src/storage/conversion/QuadToRdfConverter.ts
+++ b/src/storage/conversion/QuadToRdfConverter.ts
@@ -1,13 +1,12 @@
 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 { CONTENT_TYPE, PREFERRED_PREFIX_TERM } from '../../util/Vocabularies';
+import { PREFERRED_PREFIX_TERM } from '../../util/Vocabularies';
 import { matchingMediaTypes } from './ConversionUtil';
 import type { RepresentationConverterArgs } from './RepresentationConverter';
 import { TypedRepresentationConverter } from './TypedRepresentationConverter';
@@ -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, { [CONTENT_TYPE]: 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);
   }
 }
diff --git a/src/storage/conversion/RdfToQuadConverter.ts b/src/storage/conversion/RdfToQuadConverter.ts
index c81f13261..ee6b2183c 100644
--- a/src/storage/conversion/RdfToQuadConverter.ts
+++ b/src/storage/conversion/RdfToQuadConverter.ts
@@ -1,11 +1,10 @@
 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';
-import { CONTENT_TYPE } from '../../util/Vocabularies';
 import type { RepresentationConverterArgs } from './RepresentationConverter';
 import { TypedRepresentationConverter } from './TypedRepresentationConverter';
 
@@ -18,19 +17,13 @@ export class RdfToQuadConverter extends TypedRepresentationConverter {
   }
 
   public async handle({ representation, identifier }: RepresentationConverterArgs): Promise<Representation> {
-    const metadata = new RepresentationMetadata(representation.metadata, { [CONTENT_TYPE]: 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);
   }
 }
diff --git a/src/storage/patch/SparqlUpdatePatchHandler.ts b/src/storage/patch/SparqlUpdatePatchHandler.ts
index aac91356a..beb563493 100644
--- a/src/storage/patch/SparqlUpdatePatchHandler.ts
+++ b/src/storage/patch/SparqlUpdatePatchHandler.ts
@@ -5,16 +5,13 @@ 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 { CONTENT_TYPE } from '../../util/Vocabularies';
 import type { ResourceStore } from '../ResourceStore';
 import { PatchHandler } from './PatchHandler';
 
@@ -109,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, { [CONTENT_TYPE]: 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));
   }
 }
diff --git a/src/util/GuardedStream.ts b/src/util/GuardedStream.ts
index 2fc95a78d..aacd48a43 100644
--- a/src/util/GuardedStream.ts
+++ b/src/util/GuardedStream.ts
@@ -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';
 }
 
 /**
diff --git a/src/util/StreamUtil.ts b/src/util/StreamUtil.ts
index dfcb0e9e7..555c27825 100644
--- a/src/util/StreamUtil.ts
+++ b/src/util/StreamUtil.ts
@@ -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));
 }


Test patch:
diff --git a/test/integration/LdpHandlerWithAuth.test.ts b/test/integration/LdpHandlerWithAuth.test.ts
index 2c716753b..0690e3ffc 100644
--- a/test/integration/LdpHandlerWithAuth.test.ts
+++ b/test/integration/LdpHandlerWithAuth.test.ts
@@ -1,9 +1,6 @@
 import { createReadStream } from 'fs';
 import type { HttpHandler, Initializer, ResourceStore } from '../../src/';
-import {
-  CONTENT_TYPE, 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';
 
@@ -56,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({ [CONTENT_TYPE]: 'text/plain' }),
-    });
+    await store.setRepresentation({ path: `${BASE}/permanent.txt` },
+      new BasicRepresentation(createReadStream(joinFilePath(__dirname, '../assets/permanent.txt')), 'text/plain'));
   });
 
   afterAll(async(): Promise<void> => {
diff --git a/test/integration/LockingResourceStore.test.ts b/test/integration/LockingResourceStore.test.ts
index eae571a1d..e87cb0265 100644
--- a/test/integration/LockingResourceStore.test.ts
+++ b/test/integration/LockingResourceStore.test.ts
@@ -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,8 +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 { CONTENT_TYPE } from '../../src/util/Vocabularies';
 import { BASE } from './Config';
 
 describe('A LockingResourceStore', (): void => {
@@ -40,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({ [CONTENT_TYPE]: 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> => {
diff --git a/test/integration/RepresentationConverter.test.ts b/test/integration/RepresentationConverter.test.ts
index cbf144ea5..ae2dfa8f7 100644
--- a/test/integration/RepresentationConverter.test.ts
+++ b/test/integration/RepresentationConverter.test.ts
@@ -1,10 +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 { CONTENT_TYPE } from '../../src/util/Vocabularies';
+import { readableToString } from '../../src/util/StreamUtil';
 
 describe('A ChainedConverter', (): void => {
   const converters = [
@@ -14,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({ [CONTENT_TYPE]: '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,
@@ -34,12 +28,10 @@ describe('A ChainedConverter', (): void => {
   });
 
   it('can convert from turtle to JSON-LD.', async(): Promise<void> => {
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: '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,
diff --git a/test/unit/ldp/http/BasicResponseWriter.test.ts b/test/unit/ldp/http/BasicResponseWriter.test.ts
index 944eba58f..4b83be61b 100644
--- a/test/unit/ldp/http/BasicResponseWriter.test.ts
+++ b/test/unit/ldp/http/BasicResponseWriter.test.ts
@@ -9,7 +9,6 @@ import { RepresentationMetadata } from '../../../../src/ldp/representation/Repre
 import { INTERNAL_QUADS } from '../../../../src/util/ContentTypes';
 import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
 import { guardedStreamFrom } from '../../../../src/util/StreamUtil';
-import { CONTENT_TYPE } from '../../../../src/util/Vocabularies';
 import { StaticAsyncHandler } from '../../../util/StaticAsyncHandler';
 
 describe('A BasicResponseWriter', (): void => {
@@ -28,7 +27,7 @@ describe('A BasicResponseWriter', (): void => {
   it('requires the input to be a binary ResponseDescription.', async(): Promise<void> => {
     await expect(writer.canHandle({ response, result: new Error('error') }))
       .rejects.toThrow(NotImplementedHttpError);
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: INTERNAL_QUADS });
+    const metadata = new RepresentationMetadata(INTERNAL_QUADS);
     await expect(writer.canHandle({ response, result: { statusCode: 201, metadata }}))
       .rejects.toThrow(NotImplementedHttpError);
     await expect(writer.canHandle({ response, result }))
diff --git a/test/unit/ldp/representation/BasicRepresentation.test.ts b/test/unit/ldp/representation/BasicRepresentation.test.ts
new file mode 100644
index 000000000..5f5f5454a
--- /dev/null
+++ b/test/unit/ldp/representation/BasicRepresentation.test.ts
@@ -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);
+  });
+});
diff --git a/test/unit/ldp/representation/RepresentationMetadata.test.ts b/test/unit/ldp/representation/RepresentationMetadata.test.ts
index a20d82d12..5f9c1e487 100644
--- a/test/unit/ldp/representation/RepresentationMetadata.test.ts
+++ b/test/unit/ldp/representation/RepresentationMetadata.test.ts
@@ -27,11 +27,22 @@ describe('A RepresentationMetadata', (): void => {
       expect(metadata.identifier).toEqualRdfTerm(namedNode('identifier'));
     });
 
-    it('converts identifier strings to named nodes.', async(): Promise<void> => {
+    it('converts identifiers to named nodes.', async(): Promise<void> => {
       metadata = new RepresentationMetadata({ path: 'identifier' });
       expect(metadata.identifier).toEqualRdfTerm(namedNode('identifier'));
     });
 
+    it('converts string to content type.', async(): Promise<void> => {
+      metadata = new RepresentationMetadata('text/turtle');
+      expect(metadata.contentType).toEqual('text/turtle');
+
+      metadata = new RepresentationMetadata({ path: 'identifier' }, 'text/turtle');
+      expect(metadata.contentType).toEqual('text/turtle');
+
+      metadata = new RepresentationMetadata(new RepresentationMetadata(), 'text/turtle');
+      expect(metadata.contentType).toEqual('text/turtle');
+    });
+
     it('copies an other metadata object.', async(): Promise<void> => {
       const other = new RepresentationMetadata({ path: 'otherId' }, { 'test:pred': 'objVal' });
       metadata = new RepresentationMetadata(other);
diff --git a/test/unit/pods/GeneratedPodManager.test.ts b/test/unit/pods/GeneratedPodManager.test.ts
index f76969dcc..6c66addca 100644
--- a/test/unit/pods/GeneratedPodManager.test.ts
+++ b/test/unit/pods/GeneratedPodManager.test.ts
@@ -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);
diff --git a/test/unit/pods/agent/AgentJsonParser.test.ts b/test/unit/pods/agent/AgentJsonParser.test.ts
index 642ec2ec4..6c5ff3290 100644
--- a/test/unit/pods/agent/AgentJsonParser.test.ts
+++ b/test/unit/pods/agent/AgentJsonParser.test.ts
@@ -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> => {
diff --git a/test/unit/storage/RepresentationConvertingStore.test.ts b/test/unit/storage/RepresentationConvertingStore.test.ts
index f1b55c0ea..7b023b407 100644
--- a/test/unit/storage/RepresentationConvertingStore.test.ts
+++ b/test/unit/storage/RepresentationConvertingStore.test.ts
@@ -14,7 +14,7 @@ describe('A RepresentationConvertingStore', (): void => {
   const convertedIn = { metadata: {}};
   const convertedOut = { metadata: {}};
   const inType = 'text/turtle';
-  const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
+  const metadata = new RepresentationMetadata('text/turtle');
   let representation: Representation;
 
   beforeEach(async(): Promise<void> => {
diff --git a/test/unit/storage/accessors/FileDataAccessor.test.ts b/test/unit/storage/accessors/FileDataAccessor.test.ts
index de5b44cc3..97899aa97 100644
--- a/test/unit/storage/accessors/FileDataAccessor.test.ts
+++ b/test/unit/storage/accessors/FileDataAccessor.test.ts
@@ -32,7 +32,7 @@ describe('A FileDataAccessor', (): void => {
     cache = mockFs(rootFilePath, now);
     accessor = new FileDataAccessor(new ExtensionBasedMapper(base, rootFilePath));
 
-    metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_OCTET_STREAM });
+    metadata = new RepresentationMetadata(APPLICATION_OCTET_STREAM);
 
     data = guardedStreamFrom([ 'data' ]);
   });
diff --git a/test/unit/storage/accessors/InMemoryDataAccessor.test.ts b/test/unit/storage/accessors/InMemoryDataAccessor.test.ts
index 706a8e916..8804f4c89 100644
--- a/test/unit/storage/accessors/InMemoryDataAccessor.test.ts
+++ b/test/unit/storage/accessors/InMemoryDataAccessor.test.ts
@@ -7,7 +7,7 @@ import { APPLICATION_OCTET_STREAM } from '../../../../src/util/ContentTypes';
 import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
 import type { Guarded } from '../../../../src/util/GuardedStream';
 import { guardedStreamFrom, readableToString } from '../../../../src/util/StreamUtil';
-import { CONTENT_TYPE, LDP, RDF } from '../../../../src/util/Vocabularies';
+import { LDP, RDF } from '../../../../src/util/Vocabularies';
 
 describe('An InMemoryDataAccessor', (): void => {
   const base = 'http://test.com/';
@@ -21,7 +21,7 @@ describe('An InMemoryDataAccessor', (): void => {
     // Create default root container
     await accessor.writeContainer({ path: `${base}` }, new RepresentationMetadata());
 
-    metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_OCTET_STREAM });
+    metadata = new RepresentationMetadata(APPLICATION_OCTET_STREAM);
 
     data = guardedStreamFrom([ 'data' ]);
   });
diff --git a/test/unit/storage/conversion/ChainedConverter.test.ts b/test/unit/storage/conversion/ChainedConverter.test.ts
index f86c10a7c..7fd766223 100644
--- a/test/unit/storage/conversion/ChainedConverter.test.ts
+++ b/test/unit/storage/conversion/ChainedConverter.test.ts
@@ -49,7 +49,7 @@ describe('A ChainedConverter', (): void => {
     ];
     converter = new ChainedConverter(converters);
 
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
+    const metadata = new RepresentationMetadata('text/turtle');
     representation = { metadata } as Representation;
     preferences = { type: { 'internal/quads': 1 }};
     args = { representation, preferences, identifier: { path: 'path' }};
diff --git a/test/unit/storage/conversion/QuadToRdfConverter.test.ts b/test/unit/storage/conversion/QuadToRdfConverter.test.ts
index a32a592e8..5a7bfe8c2 100644
--- a/test/unit/storage/conversion/QuadToRdfConverter.test.ts
+++ b/test/unit/storage/conversion/QuadToRdfConverter.test.ts
@@ -8,7 +8,7 @@ import type { RepresentationPreferences } from '../../../../src/ldp/representati
 import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
 import { QuadToRdfConverter } from '../../../../src/storage/conversion/QuadToRdfConverter';
 import { INTERNAL_QUADS } from '../../../../src/util/ContentTypes';
-import { CONTENT_TYPE, DC, PREFERRED_PREFIX_TERM } from '../../../../src/util/Vocabularies';
+import { DC, PREFERRED_PREFIX_TERM } from '../../../../src/util/Vocabularies';
 
 describe('A QuadToRdfConverter', (): void => {
   const converter = new QuadToRdfConverter();
@@ -16,7 +16,7 @@ describe('A QuadToRdfConverter', (): void => {
   let metadata: RepresentationMetadata;
 
   beforeEach((): void => {
-    metadata = new RepresentationMetadata({ [CONTENT_TYPE]: INTERNAL_QUADS });
+    metadata = new RepresentationMetadata(INTERNAL_QUADS);
   });
 
   it('supports parsing quads.', async(): Promise<void> => {
diff --git a/test/unit/storage/conversion/RdfToQuadConverter.test.ts b/test/unit/storage/conversion/RdfToQuadConverter.test.ts
index 54a1406b8..d1526a196 100644
--- a/test/unit/storage/conversion/RdfToQuadConverter.test.ts
+++ b/test/unit/storage/conversion/RdfToQuadConverter.test.ts
@@ -11,7 +11,6 @@ import type { ResourceIdentifier } from '../../../../src/ldp/representation/Reso
 import { RdfToQuadConverter } from '../../../../src/storage/conversion/RdfToQuadConverter';
 import { INTERNAL_QUADS } from '../../../../src/util/ContentTypes';
 import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
-import { CONTENT_TYPE } from '../../../../src/util/Vocabularies';
 
 describe('A RdfToQuadConverter', (): void => {
   const converter = new RdfToQuadConverter();
@@ -26,21 +25,21 @@ describe('A RdfToQuadConverter', (): void => {
   });
 
   it('can handle turtle to quad conversions.', async(): Promise<void> => {
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
+    const metadata = new RepresentationMetadata('text/turtle');
     const representation = { metadata } as Representation;
     const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
     await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined();
   });
 
   it('can handle JSON-LD to quad conversions.', async(): Promise<void> => {
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/ld+json' });
+    const metadata = new RepresentationMetadata('application/ld+json');
     const representation = { metadata } as Representation;
     const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
     await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined();
   });
 
   it('converts turtle to quads.', async(): Promise<void> => {
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
+    const metadata = new RepresentationMetadata('text/turtle');
     const representation = {
       data: streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]),
       metadata,
@@ -61,7 +60,7 @@ describe('A RdfToQuadConverter', (): void => {
   });
 
   it('converts JSON-LD to quads.', async(): Promise<void> => {
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/ld+json' });
+    const metadata = new RepresentationMetadata('application/ld+json');
     const representation = {
       data: streamifyArray([ '{"@id": "http://test.com/s", "http://test.com/p": { "@id": "http://test.com/o" }}' ]),
       metadata,
@@ -82,7 +81,7 @@ describe('A RdfToQuadConverter', (): void => {
   });
 
   it('throws an BadRequestHttpError on invalid triple data.', async(): Promise<void> => {
-    const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
+    const metadata = new RepresentationMetadata('text/turtle');
     const representation = {
       data: streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.co' ]),
       metadata,
diff --git a/test/util/TestHelpers.ts b/test/util/TestHelpers.ts
index f50838879..606e448a4 100644
--- a/test/util/TestHelpers.ts
+++ b/test/util/TestHelpers.ts
@@ -6,8 +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 { CONTENT_TYPE } from '../../src/util/Vocabularies';
+import { BasicRepresentation, joinFilePath, ensureTrailingSlash } from '../../src/';
 import { performRequest } from './Util';
 
 /* eslint-disable jest/no-standalone-expect */
@@ -46,16 +45,7 @@ export class AclHelper {
 
     acl.push('.');
 
-    const representation = {
-      binary: true,
-      data: guardedStreamFrom(acl),
-      metadata: new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' }),
-    };
-
-    return this.store.setRepresentation(
-      { path: `${this.id}.acl` },
-      representation,
-    );
+    await this.store.setRepresentation({ path: `${this.id}.acl` }, new BasicRepresentation(acl, 'text/turtle'));
   }
 }