Repository: solid/community-server
Pull request #76 (2020-08-27 13:03:12)

feat: Support link and slug headers in SimpleBodyParser
This is more of a hotfix than a feature, since I was planning on fully looking into the metadata parsing when #65 was merged. But since we currently have people working on the integration tests and having these two headers in the metadata is quite important for testing the FileResourceStore this would allow that work to continue there until an actual metadata parser was made.


Patch:
diff --git a/src/ldp/http/SimpleBodyParser.ts b/src/ldp/http/SimpleBodyParser.ts
index a62ad094d..8c7b2ad3c 100644
--- a/src/ldp/http/SimpleBodyParser.ts
+++ b/src/ldp/http/SimpleBodyParser.ts
@@ -1,5 +1,6 @@
 import { HttpRequest } from '../../server/HttpRequest';
 import { DATA_TYPE_BINARY } from '../../util/ContentTypes';
+import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
 import { BinaryRepresentation } from '../representation/BinaryRepresentation';
 import { RepresentationMetadata } from '../representation/RepresentationMetadata';
 import { BodyParser } from './BodyParser';
@@ -17,24 +18,53 @@ export class SimpleBodyParser extends BodyParser {
   // Note that the only reason this is a union is in case the body is empty.
   // If this check gets moved away from the BodyParsers this union could be removed
   public async handle(input: HttpRequest): Promise<BinaryRepresentation | undefined> {
-    const contentType = input.headers['content-type'];
-
-    if (!contentType) {
+    if (!input.headers['content-type']) {
       return;
     }
 
-    const mediaType = contentType.split(';')[0];
+    return {
+      dataType: DATA_TYPE_BINARY,
+      data: input,
+      metadata: this.parseMetadata(input),
+    };
+  }
+
+  private parseMetadata(input: HttpRequest): RepresentationMetadata {
+    const mediaType = input.headers['content-type']!.split(';')[0];
 
     const metadata: RepresentationMetadata = {
       raw: [],
-      profiles: [],
       contentType: mediaType,
     };
 
-    return {
-      dataType: DATA_TYPE_BINARY,
-      data: input,
-      metadata,
-    };
+    const { link, slug } = input.headers;
+
+    if (slug) {
+      if (Array.isArray(slug)) {
+        throw new UnsupportedHttpError('At most 1 slug header is allowed.');
+      }
+      metadata.slug = slug;
+    }
+
+    // There are similarities here to Accept header parsing so that library should become more generic probably
+    if (link) {
+      metadata.linkRel = {};
+      const linkArray = Array.isArray(link) ? link : [ link ];
+      const parsedLinks = linkArray.map((entry): { url: string; rel: string } => {
+        const [ , url, rest ] = /^<([^>]*)>(.*)$/u.exec(entry) ?? [];
+        const [ , rel ] = /^ *; *rel="(.*)"$/u.exec(rest) ?? [];
+        return { url, rel };
+      });
+      parsedLinks.forEach((entry): void => {
+        if (entry.rel) {
+          if (!metadata.linkRel![entry.rel]) {
+            metadata.linkRel![entry.rel] = new Set();
+          }
+          metadata.linkRel![entry.rel].add(entry.url);
+        }
+      });
+    }
+
+    return metadata;
   }
 }


Test patch:
diff --git a/test/integration/RequestParser.test.ts b/test/integration/RequestParser.test.ts
index 76fb6392d..86867adc1 100644
--- a/test/integration/RequestParser.test.ts
+++ b/test/integration/RequestParser.test.ts
@@ -38,7 +38,6 @@ describe('A SimpleRequestParser with simple input parsers', (): void => {
         dataType: DATA_TYPE_BINARY,
         metadata: {
           contentType: 'text/turtle',
-          profiles: [],
           raw: [],
         },
       },
diff --git a/test/unit/ldp/http/SimpleBodyParser.test.ts b/test/unit/ldp/http/SimpleBodyParser.test.ts
index 8f91f7ac7..e70ee6b23 100644
--- a/test/unit/ldp/http/SimpleBodyParser.test.ts
+++ b/test/unit/ldp/http/SimpleBodyParser.test.ts
@@ -1,9 +1,9 @@
-import { Readable } from 'stream';
 import arrayifyStream from 'arrayify-stream';
 import streamifyArray from 'streamify-array';
 import { SimpleBodyParser } from '../../../../src/ldp/http/SimpleBodyParser';
 import { HttpRequest } from '../../../../src/server/HttpRequest';
 import { DATA_TYPE_BINARY } from '../../../../src/util/ContentTypes';
+import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
 import 'jest-rdf';
 
 describe('A SimpleBodyparser', (): void => {
@@ -22,11 +22,10 @@ describe('A SimpleBodyparser', (): void => {
     input.headers = { 'content-type': 'text/turtle' };
     const result = (await bodyParser.handle(input))!;
     expect(result).toEqual({
-      data: expect.any(Readable),
+      data: input,
       dataType: DATA_TYPE_BINARY,
       metadata: {
         contentType: 'text/turtle',
-        profiles: [],
         raw: [],
       },
     });
@@ -34,4 +33,48 @@ describe('A SimpleBodyparser', (): void => {
       [ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ],
     );
   });
+
+  it('adds the slug header to the metadata.', async(): Promise<void> => {
+    const input = {} as HttpRequest;
+    input.headers = { 'content-type': 'text/turtle', slug: 'slugText' };
+    const result = (await bodyParser.handle(input))!;
+    expect(result.metadata).toEqual({
+      contentType: 'text/turtle',
+      raw: [],
+      slug: 'slugText',
+    });
+  });
+
+  it('errors if there are multiple slugs.', async(): Promise<void> => {
+    const input = {} as HttpRequest;
+    input.headers = { 'content-type': 'text/turtle', slug: [ 'slugTextA', 'slugTextB' ]};
+    await expect(bodyParser.handle(input)).rejects.toThrow(UnsupportedHttpError);
+  });
+
+  it('adds the link headers to the metadata.', async(): Promise<void> => {
+    const input = {} as HttpRequest;
+    input.headers = { 'content-type': 'text/turtle', link: '<http://www.w3.org/ns/ldp#Container>; rel="type"' };
+    const result = (await bodyParser.handle(input))!;
+    expect(result.metadata).toEqual({
+      contentType: 'text/turtle',
+      raw: [],
+      linkRel: { type: new Set([ 'http://www.w3.org/ns/ldp#Container' ]) },
+    });
+  });
+
+  it('supports multiple link headers.', async(): Promise<void> => {
+    const input = {} as HttpRequest;
+    input.headers = { 'content-type': 'text/turtle',
+      link: [ '<http://www.w3.org/ns/ldp#Container>; rel="type"',
+        '<http://www.w3.org/ns/ldp#Resource>; rel="type"',
+        '<unrelatedLink>',
+        'badLink',
+      ]};
+    const result = (await bodyParser.handle(input))!;
+    expect(result.metadata).toEqual({
+      contentType: 'text/turtle',
+      raw: [],
+      linkRel: { type: new Set([ 'http://www.w3.org/ns/ldp#Container', 'http://www.w3.org/ns/ldp#Resource' ]) },
+    });
+  });
 });