From 07f370d6d2babcd58003ebaf9e5a60f4de3d43b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 26 Jan 2021 05:27:11 +0100 Subject: [PATCH] feat: better media types detection for JSON, XML and HTML lenses (#1438) Co-authored-by: Andrew Bastin Co-authored-by: Liyas Thomas --- helpers/lenses/__tests__/lenses.spec.js | 19 ++++++++++++++++-- helpers/lenses/htmlLens.js | 3 ++- helpers/lenses/imageLens.js | 11 ++-------- helpers/lenses/jsonLens.js | 4 +++- helpers/lenses/lenses.js | 21 ++++++-------------- helpers/lenses/rawLens.js | 2 +- helpers/lenses/xmlLens.js | 2 +- helpers/utils/__tests__/contenttypes.spec.js | 4 ++++ helpers/utils/contenttypes.js | 16 +-------------- 9 files changed, 37 insertions(+), 45 deletions(-) diff --git a/helpers/lenses/__tests__/lenses.spec.js b/helpers/lenses/__tests__/lenses.spec.js index 4db416574..cf536592a 100644 --- a/helpers/lenses/__tests__/lenses.spec.js +++ b/helpers/lenses/__tests__/lenses.spec.js @@ -20,11 +20,26 @@ describe("getSuitableLenses", () => { expect(undefinedResult).toContainEqual(rawLens) }) + const contentTypes = { + JSON: ["application/json", "application/ld+json", "application/hal+json; charset=utf8"], + Image: [ + "image/gif", + "image/jpeg; foo=bar", + "image/png", + "image/bmp", + "image/svg+xml", + "image/x-icon", + "image/vnd.microsoft.icon", + ], + HTML: ["text/html", "application/xhtml+xml", "text/html; charset=utf-8"], + XML: ["text/xml", "application/xml", "application/xhtml+xml; charset=utf-8"], + } + lenses .filter(({ lensName }) => lensName != rawLens.lensName) .forEach((el) => { test(`returns ${el.lensName} lens for its content-types`, () => { - el.supportedContentTypes.forEach((contentType) => { + contentTypes[el.lensName].forEach((contentType) => { expect( getSuitableLenses({ headers: { @@ -36,7 +51,7 @@ describe("getSuitableLenses", () => { }) test(`returns Raw Lens along with ${el.lensName} for the content types`, () => { - el.supportedContentTypes.forEach((contentType) => { + contentTypes[el.lensName].forEach((contentType) => { expect( getSuitableLenses({ headers: { diff --git a/helpers/lenses/htmlLens.js b/helpers/lenses/htmlLens.js index 5a8e3244d..bc428f040 100644 --- a/helpers/lenses/htmlLens.js +++ b/helpers/lenses/htmlLens.js @@ -1,6 +1,7 @@ const htmlLens = { lensName: "HTML", - supportedContentTypes: ["text/html"], + isSupportedContentType: (contentType) => + /\btext\/html|application\/xhtml\+xml\b/i.test(contentType), renderer: "htmlres", rendererImport: () => import("~/components/lenses/renderers/HTMLLensRenderer"), } diff --git a/helpers/lenses/imageLens.js b/helpers/lenses/imageLens.js index 1153822a4..9868db46b 100644 --- a/helpers/lenses/imageLens.js +++ b/helpers/lenses/imageLens.js @@ -1,14 +1,7 @@ const imageLens = { lensName: "Image", - supportedContentTypes: [ - "image/gif", - "image/jpeg", - "image/png", - "image/bmp", - "image/svg+xml", - "image/x-icon", - "image/vnd.microsoft.icon", - ], + isSupportedContentType: (contentType) => + /\bimage\/(?:gif|jpeg|png|bmp|svg\+xml|x-icon|vnd\.microsoft\.icon)\b/i.test(contentType), renderer: "imageres", rendererImport: () => import("~/components/lenses/renderers/ImageLensRenderer"), } diff --git a/helpers/lenses/jsonLens.js b/helpers/lenses/jsonLens.js index 5c337c1aa..440a2c5be 100644 --- a/helpers/lenses/jsonLens.js +++ b/helpers/lenses/jsonLens.js @@ -1,6 +1,8 @@ +import { isJSONContentType } from "../utils/contenttypes"; + const jsonLens = { lensName: "JSON", - supportedContentTypes: ["application/json", "application/hal+json", "application/vnd.api+json"], + isSupportedContentType: isJSONContentType, renderer: "json", rendererImport: () => import("~/components/lenses/renderers/JSONLensRenderer"), } diff --git a/helpers/lenses/lenses.js b/helpers/lenses/lenses.js index 6f2cc6d71..a6fcd3313 100644 --- a/helpers/lenses/lenses.js +++ b/helpers/lenses/lenses.js @@ -7,22 +7,13 @@ import xmlLens from "./xmlLens" export const lenses = [jsonLens, imageLens, htmlLens, xmlLens, rawLens] export function getSuitableLenses(response) { + if (!response || !response.headers || !response.headers["content-type"]) + return [rawLens] + const result = [] - - if (response && response.headers && response.headers["content-type"]) { - const properContentType = response.headers["content-type"].split(";")[0] - - for (const lens of lenses) { - if ( - lens.supportedContentTypes === null || - lens.supportedContentTypes.includes(properContentType) - ) { - result.push(lens) - } - } - } else { - // We don't know the content type, so lets just add rawLens - result.push(rawLens) + for (const lens of lenses) { + if (lens.isSupportedContentType(response.headers["content-type"])) + result.push(lens) } return result diff --git a/helpers/lenses/rawLens.js b/helpers/lenses/rawLens.js index 2bb9635d4..9a4c6e4f8 100644 --- a/helpers/lenses/rawLens.js +++ b/helpers/lenses/rawLens.js @@ -1,6 +1,6 @@ const rawLens = { lensName: "Raw", - supportedContentTypes: null, + isSupportedContentType: () => true, renderer: "raw", rendererImport: () => import("~/components/lenses/renderers/RawLensRenderer"), } diff --git a/helpers/lenses/xmlLens.js b/helpers/lenses/xmlLens.js index eb0b5a60b..5a7cce36a 100644 --- a/helpers/lenses/xmlLens.js +++ b/helpers/lenses/xmlLens.js @@ -1,6 +1,6 @@ const xmlLens = { lensName: "XML", - supportedContentTypes: ["application/xml", "image/svg+xml", "text/xml", "application/rss+xml"], + isSupportedContentType: (contentType) => /\bxml\b/i.test(contentType), renderer: "xmlres", rendererImport: () => import("~/components/lenses/renderers/XMLLensRenderer"), } diff --git a/helpers/utils/__tests__/contenttypes.spec.js b/helpers/utils/__tests__/contenttypes.spec.js index 48f9d486e..236f03077 100644 --- a/helpers/utils/__tests__/contenttypes.spec.js +++ b/helpers/utils/__tests__/contenttypes.spec.js @@ -5,24 +5,28 @@ describe("isJSONContentType", () => { expect(isJSONContentType("application/json")).toBe(true) expect(isJSONContentType("application/vnd.api+json")).toBe(true) expect(isJSONContentType("application/hal+json")).toBe(true) + expect(isJSONContentType("application/ld+json")).toBe(true) }) test("returns true for JSON types with charset specified", () => { expect(isJSONContentType("application/json; charset=utf-8")).toBe(true) expect(isJSONContentType("application/vnd.api+json; charset=utf-8")).toBe(true) expect(isJSONContentType("application/hal+json; charset=utf-8")).toBe(true) + expect(isJSONContentType("application/ld+json; charset=utf-8")).toBe(true) }) test("returns false for non-JSON content types", () => { expect(isJSONContentType("application/xml")).toBe(false) expect(isJSONContentType("text/html")).toBe(false) expect(isJSONContentType("application/x-www-form-urlencoded")).toBe(false) + expect(isJSONContentType("foo/jsoninword")).toBe(false) }) test("returns false for non-JSON content types with charset", () => { expect(isJSONContentType("application/xml; charset=utf-8")).toBe(false) expect(isJSONContentType("text/html; charset=utf-8")).toBe(false) expect(isJSONContentType("application/x-www-form-urlencoded; charset=utf-8")).toBe(false) + expect(isJSONContentType("foo/jsoninword; charset=utf-8")).toBe(false) }) test("returns false for null/undefined", () => { diff --git a/helpers/utils/contenttypes.js b/helpers/utils/contenttypes.js index e57347e05..3d1ce198a 100644 --- a/helpers/utils/contenttypes.js +++ b/helpers/utils/contenttypes.js @@ -10,19 +10,5 @@ export const knownContentTypes = [ ] export function isJSONContentType(contentType) { - if (contentType && contentType.includes(";")) { - const [justContentType] = contentType.split(";") - - return ( - justContentType === "application/json" || - justContentType === "application/vnd.api+json" || - justContentType === "application/hal+json" - ) - } else { - return ( - contentType === "application/json" || - contentType === "application/vnd.api+json" || - contentType === "application/hal+json" - ) - } + return /\bjson\b/i.test(contentType); }