From fc1b2847b6a32993ceee681384f25f1ec19618a5 Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Mon, 30 Oct 2023 17:53:27 +0100 Subject: [PATCH 1/7] test(ckan): if the middleware is a function and if it can be mounted --- package-lock.json | 108 ++++++++++++++++++++++++++++++++ packages/ckan/package.json | 8 ++- packages/ckan/test/ckan.test.js | 29 +++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 packages/ckan/test/ckan.test.js diff --git a/package-lock.json b/package-lock.json index d4993f3d..b5399b67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16124,6 +16124,12 @@ "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", "dev": true }, + "node_modules/oxigraph": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/oxigraph/-/oxigraph-0.3.20.tgz", + "integrity": "sha512-l+JR+ELEnq2kxSWr/mquj4G8iBYGRW8mflUtufuddgvOEjTixdBc+p/AVl8RLEPpOFp6I/DkRcQ62aDs0bb2OQ==", + "dev": true + }, "node_modules/p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -20817,6 +20823,108 @@ "dotenv": "^16.3.1", "sparql-http-client": "^2.4.2", "xmlbuilder2": "^3.1.1" + }, + "devDependencies": { + "c8": "^8.0.1", + "express-as-promise": "^1.2.0", + "mocha": "^10.2.0", + "oxigraph": "^0.3.20" + } + }, + "packages/ckan/node_modules/c8": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.1.tgz", + "integrity": "sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=12" + } + }, + "packages/ckan/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/ckan/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/ckan/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/ckan/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/ckan/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" } }, "packages/core": { diff --git a/packages/ckan/package.json b/packages/ckan/package.json index 11e58aab..e54c96d9 100644 --- a/packages/ckan/package.json +++ b/packages/ckan/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "type": "module", "scripts": { - "test": "true" + "test": "c8 --reporter=lcov --reporter=text mocha" }, "repository": { "type": "git", @@ -25,5 +25,11 @@ "dotenv": "^16.3.1", "sparql-http-client": "^2.4.2", "xmlbuilder2": "^3.1.1" + }, + "devDependencies": { + "c8": "^8.0.1", + "express-as-promise": "^1.2.0", + "mocha": "^10.2.0", + "oxigraph": "^0.3.20" } } diff --git a/packages/ckan/test/ckan.test.js b/packages/ckan/test/ckan.test.js new file mode 100644 index 00000000..2afd4f24 --- /dev/null +++ b/packages/ckan/test/ckan.test.js @@ -0,0 +1,29 @@ +import assert from 'assert' +import withServer from 'express-as-promise/withServer.js' +import { describe, it } from 'mocha' +import trifidFactory from '../src/index.js' + +const createTrifidConfig = (config, server = {}) => { + const loggerSpy = [] + + return { + logger: (str) => loggerSpy.push(str), + server, + config, + } +} + +describe('@zazuko/trifid-plugin-ckan', () => { + describe('trifid factory', () => { + it('should be a factory', () => { + assert.strictEqual(typeof trifidFactory, 'function') + }) + + it('should create a middleware with factory and default options', async () => { + await withServer(async (server) => { + const trifid = createTrifidConfig({}, server.app) + trifidFactory(trifid) + }) + }) + }) +}) From 2f633b4a2b1b8dd976b87fef8af86b773a830a56 Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Mon, 30 Oct 2023 17:54:39 +0100 Subject: [PATCH 2/7] style: format code using default configuration --- packages/core/index.js | 62 ++-- packages/core/lib/config/default.js | 8 +- packages/core/lib/config/handler.js | 130 +++---- packages/core/lib/config/parser.js | 16 +- packages/core/lib/config/resolvers.js | 42 +-- packages/core/lib/config/schema.js | 64 ++-- packages/core/lib/middlewares/apply.js | 48 +-- packages/core/lib/middlewares/assembler.js | 20 +- packages/core/lib/middlewares/default.js | 38 +-- packages/core/lib/middlewares/loader.js | 40 +-- packages/core/lib/middlewares/sort.js | 6 +- packages/core/lib/middlewares/standardize.js | 88 ++--- packages/core/lib/resolvers.js | 54 +-- packages/core/lib/templateEngine.js | 82 ++--- packages/core/middlewares/errors.js | 14 +- packages/core/middlewares/express.js | 16 +- packages/core/middlewares/health.js | 14 +- packages/core/middlewares/iri.js | 52 +-- packages/core/middlewares/locals.js | 48 +-- packages/core/middlewares/notFound.js | 70 ++-- packages/core/middlewares/redirect.js | 16 +- packages/core/middlewares/rewrite.js | 20 +- packages/core/middlewares/static.js | 12 +- packages/core/middlewares/throw.js | 14 +- packages/core/middlewares/view.js | 22 +- packages/core/server.js | 26 +- packages/core/test/config.handler.test.js | 196 +++++------ packages/core/test/config.test.js | 208 ++++++------ packages/core/test/middlewares/errors.test.js | 66 ++-- packages/core/test/middlewares/health.test.js | 86 ++--- .../core/test/middlewares/redirect.test.js | 52 +-- packages/core/test/middlewares/static.test.js | 64 ++-- packages/core/test/resolvers.test.js | 296 ++++++++-------- packages/graph-explorer/index.js | 70 ++-- packages/graph-explorer/static/app.js | 58 ++-- packages/graph-explorer/test/test.js | 38 +-- packages/handler-fetch/index.js | 76 ++--- packages/handler-fetch/lib/Fetcher.js | 56 +-- .../lib/spread/boundedDescriptionGraph.js | 26 +- .../lib/spread/splitIntoGraphs.js | 36 +- packages/handler-fetch/test/Fetcher.js | 308 ++++++++--------- packages/handler-fetch/test/index.js | 320 +++++++++--------- .../test/spread/boundedDescriptionGraph.js | 54 +-- .../test/spread/splitIntoGraphs.js | 52 +-- packages/handler-sparql/index.js | 152 ++++----- .../test/support/createEndpoint.js | 44 +-- .../handler-sparql/test/support/setIri.js | 10 +- packages/handler-sparql/test/test.js | 296 ++++++++-------- packages/i18n/index.js | 62 ++-- packages/i18n/test/support/withServer.js | 18 +- packages/i18n/test/test.js | 222 ++++++------ packages/sparql-proxy/index.js | 32 +- packages/trifid/middlewares/morgan.js | 18 +- packages/trifid/server.js | 38 +-- packages/yasgui/index.js | 50 +-- packages/yasgui/plugins/map.js | 70 ++-- packages/yasgui/test/test.js | 124 +++---- 57 files changed, 2110 insertions(+), 2110 deletions(-) diff --git a/packages/core/index.js b/packages/core/index.js index a0825661..5efc7848 100644 --- a/packages/core/index.js +++ b/packages/core/index.js @@ -1,17 +1,17 @@ -import express from "express"; -import pino from "pino"; -import cors from "cors"; -import cookieParser from "cookie-parser"; +import express from 'express' +import pino from 'pino' +import cors from 'cors' +import cookieParser from 'cookie-parser' -import handler from "./lib/config/handler.js"; +import handler from './lib/config/handler.js' import { defaultHost, defaultLogLevel, defaultPort, -} from "./lib/config/default.js"; -import middlewaresAssembler from "./lib/middlewares/assembler.js"; -import applyMiddlewares from "./lib/middlewares/apply.js"; -import templateEngine from "./lib/templateEngine.js"; +} from './lib/config/default.js' +import middlewaresAssembler from './lib/middlewares/assembler.js' +import applyMiddlewares from './lib/middlewares/apply.js' +import templateEngine from './lib/templateEngine.js' /** * Create a new Trifid instance. @@ -71,9 +71,9 @@ import templateEngine from "./lib/templateEngine.js"; * >}} */ const trifid = async (config, additionalMiddlewares = {}) => { - const fullConfig = await handler(config); - const server = express(); - server.disable("x-powered-by"); + const fullConfig = await handler(config) + const server = express() + server.disable('x-powered-by') // add required middlewares server.use( @@ -81,8 +81,8 @@ const trifid = async (config, additionalMiddlewares = {}) => { credentials: true, origin: true, }), - ); - server.use(cookieParser()); + ) + server.use(cookieParser()) // configure Express server if (fullConfig?.server?.express) { @@ -90,52 +90,52 @@ const trifid = async (config, additionalMiddlewares = {}) => { server.set( expressSettingKey, fullConfig.server.express[expressSettingKey], - ); + ) } } // dynamic server configuration - const port = fullConfig?.server?.listener?.port || defaultPort; - const host = fullConfig?.server?.listener?.host || defaultHost; + const port = fullConfig?.server?.listener?.port || defaultPort + const host = fullConfig?.server?.listener?.host || defaultHost // logger configuration - const logLevel = fullConfig?.server?.logLevel || defaultLogLevel; + const logLevel = fullConfig?.server?.logLevel || defaultLogLevel // template configuration - const template = fullConfig?.template || {}; + const template = fullConfig?.template || {} const logger = pino({ - name: "trifid-core", + name: 'trifid-core', level: logLevel, transport: { - target: "pino-pretty", + target: 'pino-pretty', }, - }); + }) - const templateEngineInstance = await templateEngine(template); + const templateEngineInstance = await templateEngine(template) const middlewares = await middlewaresAssembler( fullConfig, additionalMiddlewares, - ); + ) await applyMiddlewares( server, fullConfig.globals, middlewares, logger, templateEngineInstance, - ); + ) const start = () => { server.listen(port, host, () => { - logger.info(`Trifid instance listening on: http://${host}:${port}/`); - }); - }; + logger.info(`Trifid instance listening on: http://${host}:${port}/`) + }) + } return { start, server, config: fullConfig, - }; -}; + } +} -export default trifid; +export default trifid diff --git a/packages/core/lib/config/default.js b/packages/core/lib/config/default.js index 78f7590a..ef010140 100644 --- a/packages/core/lib/config/default.js +++ b/packages/core/lib/config/default.js @@ -1,6 +1,6 @@ // some default configuration -export const maxDepth = 50; -export const defaultPort = 8080; -export const defaultHost = "0.0.0.0"; -export const defaultLogLevel = "info"; +export const maxDepth = 50 +export const defaultPort = 8080 +export const defaultHost = '0.0.0.0' +export const defaultLogLevel = 'info' diff --git a/packages/core/lib/config/handler.js b/packages/core/lib/config/handler.js index a272cfd5..88e62f6f 100644 --- a/packages/core/lib/config/handler.js +++ b/packages/core/lib/config/handler.js @@ -1,19 +1,19 @@ -import fs from "fs/promises"; -import { dirname } from "path"; -import merge from "lodash/merge.js"; -import JSON5 from "json5"; -import { parse } from "yaml"; -import cloneDeep from "lodash/cloneDeep.js"; -import { cwdCallback } from "../resolvers.js"; -import parser from "./parser.js"; +import fs from 'fs/promises' +import { dirname } from 'path' +import merge from 'lodash/merge.js' +import JSON5 from 'json5' +import { parse } from 'yaml' +import cloneDeep from 'lodash/cloneDeep.js' +import { cwdCallback } from '../resolvers.js' +import parser from './parser.js' import { extendsResolver, globalsResolver, middlewaresResolver, serverResolver, templateResolver, -} from "./resolvers.js"; -import { defaultPort, maxDepth } from "./default.js"; +} from './resolvers.js' +import { defaultPort, maxDepth } from './default.js' const resolveConfig = async ( rawConfig, @@ -22,75 +22,75 @@ const resolveConfig = async ( ) => { if (depth >= maxDepth) { throw new Error( - "reached max configuration depth, maybe you went in an infinite loop. Please check the extends values from your configuration file recursively", - ); + 'reached max configuration depth, maybe you went in an infinite loop. Please check the extends values from your configuration file recursively', + ) } if (fileFullPath === undefined) { - fileFullPath = process.cwd(); + fileFullPath = process.cwd() } - const config = parser(rawConfig); - addDefaultFields(config); + const config = parser(rawConfig) + addDefaultFields(config) - const context = dirname(fileFullPath); + const context = dirname(fileFullPath) // fetch all configuration files from which this one is extending - let configs = []; + let configs = [] if (Array.isArray(config.extends) && config.extends.length > 0) { - config.extends = extendsResolver(config.extends, context); + config.extends = extendsResolver(config.extends, context) configs = await Promise.all( config.extends.map((configPath) => resolveConfigFile(configPath, depth + 1), ), - ); + ) } // merge all fields - const middlewares = {}; + const middlewares = {} configs.forEach((c) => { // merge template, globals and server parts - config.globals = merge({}, c.globals, config.globals); - config.server = merge({}, c.server, config.server); - config.template = merge({}, c.template, config.template); + config.globals = merge({}, c.globals, config.globals) + config.server = merge({}, c.server, config.server) + config.template = merge({}, c.template, config.template) // merge middlewares Object.keys(c.middlewares).forEach((m) => { - middlewares[m] = c.middlewares[m]; - }); - }); + middlewares[m] = c.middlewares[m] + }) + }) Object.keys(config.middlewares).forEach((m) => { - middlewares[m] = config.middlewares[m]; - }); + middlewares[m] = config.middlewares[m] + }) // apply all resolvers - config.middlewares = middlewaresResolver(middlewares, context); - config.globals = globalsResolver(config.globals, context); - config.server = serverResolver(config.server, context); - config.template = templateResolver(config.template, context); + config.middlewares = middlewaresResolver(middlewares, context) + config.globals = globalsResolver(config.globals, context) + config.server = serverResolver(config.server, context) + config.template = templateResolver(config.template, context) // we don't need the extends field anymore - delete config.extends; + delete config.extends - return config; -}; + return config +} const resolveConfigFile = async (filePath, depth = 0) => { // read config file - const fileFullPath = cwdCallback(filePath); - const fileContent = await fs.readFile(fileFullPath); + const fileFullPath = cwdCallback(filePath) + const fileContent = await fs.readFile(fileFullPath) - let parsed; + let parsed - const fileExtension = `${fileFullPath.split(".").pop()}`.toLocaleLowerCase(); - if (["yaml", "yml"].includes(fileExtension)) { - parsed = parse(`${fileContent}`); + const fileExtension = `${fileFullPath.split('.').pop()}`.toLocaleLowerCase() + if (['yaml', 'yml'].includes(fileExtension)) { + parsed = parse(`${fileContent}`) } else { - parsed = JSON5.parse(fileContent); + parsed = JSON5.parse(fileContent) } - return await resolveConfig(parsed, fileFullPath, depth); -}; + return await resolveConfig(parsed, fileFullPath, depth) +} /** * Add default fields for a configuration. @@ -99,17 +99,17 @@ const resolveConfigFile = async (filePath, depth = 0) => { */ const addDefaultFields = (config) => { if (!config.server) { - config.server = {}; + config.server = {} } if (!config.globals) { - config.globals = {}; + config.globals = {} } if (!config.middlewares) { - config.middlewares = {}; + config.middlewares = {} } -}; +} /** * Add the default port for the server configuration. @@ -118,12 +118,12 @@ const addDefaultFields = (config) => { */ const addDefaultPort = (config) => { if (!config.server.listener) { - config.server.listener = {}; + config.server.listener = {} } if (config.server.listener.port === undefined) { - config.server.listener.port = defaultPort; + config.server.listener.port = defaultPort } -}; +} /** * Add some default Express settings for the server configuration. @@ -132,26 +132,26 @@ const addDefaultPort = (config) => { */ const addDefaultExpressSettings = (config) => { if (!config.server.express) { - config.server.express = {}; + config.server.express = {} } - if (!Object.hasOwnProperty.call(config.server.express, "trust proxy")) { - config.server.express["trust proxy"] = "loopback"; + if (!Object.hasOwnProperty.call(config.server.express, 'trust proxy')) { + config.server.express['trust proxy'] = 'loopback' } -}; +} const handler = async (configFile) => { - let config = {}; - if (typeof configFile === "string") { - config = await resolveConfigFile(configFile); + let config = {} + if (typeof configFile === 'string') { + config = await resolveConfigFile(configFile) } else { - config = await resolveConfig(cloneDeep(configFile)); + config = await resolveConfig(cloneDeep(configFile)) } - addDefaultFields(config); - addDefaultPort(config); - addDefaultExpressSettings(config); + addDefaultFields(config) + addDefaultPort(config) + addDefaultExpressSettings(config) - return config; -}; + return config +} -export default handler; +export default handler diff --git a/packages/core/lib/config/parser.js b/packages/core/lib/config/parser.js index ddcad06c..4cf3804f 100644 --- a/packages/core/lib/config/parser.js +++ b/packages/core/lib/config/parser.js @@ -1,17 +1,17 @@ -import Ajv from "ajv"; -import schema from "./schema.js"; +import Ajv from 'ajv' +import schema from './schema.js' -const ajv = new Ajv(); +const ajv = new Ajv() /** * Return the configuration object if it is valid or throw an error in other cases. */ const parser = (data = {}) => { - const valid = ajv.validate(schema, data); + const valid = ajv.validate(schema, data) if (!valid) { - throw new Error(ajv.errorsText()); + throw new Error(ajv.errorsText()) } - return data; -}; + return data +} -export default parser; +export default parser diff --git a/packages/core/lib/config/resolvers.js b/packages/core/lib/config/resolvers.js index 51a82620..47da258d 100644 --- a/packages/core/lib/config/resolvers.js +++ b/packages/core/lib/config/resolvers.js @@ -3,7 +3,7 @@ import { envResolver, fileCallback, fileResolver, -} from "../resolvers.js"; +} from '../resolvers.js' /** * @@ -13,39 +13,39 @@ import { */ export const extendsResolver = (value, context) => { return value.map((path) => { - return fileCallback(context)(fileResolver(cwdResolver(path), context)); - }); -}; + return fileCallback(context)(fileResolver(cwdResolver(path), context)) + }) +} export const applyResolvers = (value, context) => { - if (typeof value === "object") { + if (typeof value === 'object') { Object.keys(value).map((k) => { - value[k] = applyResolvers(value[k], context); - return value[k]; - }); + value[k] = applyResolvers(value[k], context) + return value[k] + }) - return value; + return value } - if (typeof value === "string") { - return fileResolver(cwdResolver(envResolver(value)), context); + if (typeof value === 'string') { + return fileResolver(cwdResolver(envResolver(value)), context) } - return value; -}; + return value +} export const templateResolver = (value, context) => { - return applyResolvers(value, context); -}; + return applyResolvers(value, context) +} export const serverResolver = (value, context) => { - return applyResolvers(value, context); -}; + return applyResolvers(value, context) +} export const globalsResolver = (value, context) => { - return applyResolvers(value, context); -}; + return applyResolvers(value, context) +} export const middlewaresResolver = (value, context) => { - return applyResolvers(value, context); -}; + return applyResolvers(value, context) +} diff --git a/packages/core/lib/config/schema.js b/packages/core/lib/config/schema.js index 53d79e30..26b488a7 100644 --- a/packages/core/lib/config/schema.js +++ b/packages/core/lib/config/schema.js @@ -3,92 +3,92 @@ */ const server = { - type: "object", + type: 'object', properties: { listener: { - type: "object", + type: 'object', properties: { port: { anyOf: [ - { type: "number", minimum: 0, maximum: 65535 }, - { type: "string", minLength: 1 }, + { type: 'number', minimum: 0, maximum: 65535 }, + { type: 'string', minLength: 1 }, ], }, - host: { type: "string", minLength: 1 }, + host: { type: 'string', minLength: 1 }, }, additionalProperties: false, }, logLevel: { - type: "string", - enum: ["fatal", "error", "warn", "info", "debug", "trace", "silent"], + type: 'string', + enum: ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'], }, express: { - type: "object", + type: 'object', additionalProperties: true, }, }, additionalProperties: false, -}; +} const globals = { - type: "object", + type: 'object', additionalProperties: true, -}; +} const middleware = { - type: "object", + type: 'object', properties: { - order: { type: "number", minimum: 0 }, - module: { type: "string", minLength: 1 }, + order: { type: 'number', minimum: 0 }, + module: { type: 'string', minLength: 1 }, paths: { anyOf: [ - { type: "string", minLength: 1 }, - { type: "array", items: { type: "string" } }, + { type: 'string', minLength: 1 }, + { type: 'array', items: { type: 'string' } }, ], }, methods: { anyOf: [ - { type: "string", minLength: 1 }, - { type: "array", items: { type: "string" } }, + { type: 'string', minLength: 1 }, + { type: 'array', items: { type: 'string' } }, ], }, hosts: { anyOf: [ - { type: "string", minLength: 1 }, - { type: "array", items: { type: "string" } }, + { type: 'string', minLength: 1 }, + { type: 'array', items: { type: 'string' } }, ], }, - config: { type: "object", additionalProperties: true }, + config: { type: 'object', additionalProperties: true }, }, - required: ["module"], + required: ['module'], additionalProperties: false, -}; +} const middlewares = { - type: "object", + type: 'object', patternProperties: { - ".*": middleware, + '.*': middleware, }, -}; +} const schema = { - type: "object", + type: 'object', properties: { extends: { - type: "array", + type: 'array', items: { - type: "string", + type: 'string', }, }, server, globals, middlewares, template: { - type: "object", + type: 'object', additionalProperties: true, }, }, additionalProperties: false, -}; +} -export default schema; +export default schema diff --git a/packages/core/lib/middlewares/apply.js b/packages/core/lib/middlewares/apply.js index 48b398c8..10ac1299 100644 --- a/packages/core/lib/middlewares/apply.js +++ b/packages/core/lib/middlewares/apply.js @@ -1,38 +1,38 @@ -import merge from "lodash/merge.js"; -import vhost from "vhost"; +import merge from 'lodash/merge.js' +import vhost from 'vhost' const apply = async (server, globals, middlewares, logger, templateEngine) => { for (const middleware of middlewares) { - const name = middleware[0]; - const m = middleware[1]; + const name = middleware[0] + const m = middleware[1] - const { paths, hosts, methods, module, config } = m; + const { paths, hosts, methods, module, config } = m - delete m.paths; - delete m.hosts; - delete m.methods; - delete m.order; - delete m.module; + delete m.paths + delete m.hosts + delete m.methods + delete m.order + delete m.module - const middlewareLogger = logger.child({ name }); + const middlewareLogger = logger.child({ name }) - const { render, registerHelper } = templateEngine; + const { render, registerHelper } = templateEngine const loadedMiddleware = await module({ config: merge({}, globals, config), server, logger: middlewareLogger, render, registerTemplateHelper: registerHelper, - }); + }) // default path is '/' (see: https://github.com/expressjs/express/blob/d854c43ea177d1faeea56189249fff8c24a764bd/lib/router/index.js#L425) if (paths.length === 0) { - paths.push("/"); + paths.push('/') } // if no methods are specified, use 'use' if (methods.length === 0) { - methods.push("use"); + methods.push('use') } // mount the middleware the way it should @@ -42,21 +42,21 @@ const apply = async (server, globals, middlewares, logger, templateEngine) => { methods.map((method) => { logger.debug( `mount '${name}' middleware (method=${method}, path=${path})`, - ); - return server[method](path, loadedMiddleware); - }); + ) + return server[method](path, loadedMiddleware) + }) } else { hosts.map((host) => { return methods.map((method) => { logger.debug( `mount '${name}' middleware (method=${method}, path=${path}, host=${host})`, - ); - return server[method](path, vhost(host, loadedMiddleware)); - }); - }); + ) + return server[method](path, vhost(host, loadedMiddleware)) + }) + }) } } } -}; +} -export default apply; +export default apply diff --git a/packages/core/lib/middlewares/assembler.js b/packages/core/lib/middlewares/assembler.js index 0f8a2a99..36748ba7 100644 --- a/packages/core/lib/middlewares/assembler.js +++ b/packages/core/lib/middlewares/assembler.js @@ -1,22 +1,22 @@ -import defaultMiddlewares from "./default.js"; -import load from "./loader.js"; -import sort from "./sort.js"; -import standardize from "./standardize.js"; +import defaultMiddlewares from './default.js' +import load from './loader.js' +import sort from './sort.js' +import standardize from './standardize.js' const assembler = async (config, additionalMiddlewares = {}) => { - const loadedMiddlewares = await load(config); + const loadedMiddlewares = await load(config) const middlewares = { ...defaultMiddlewares, ...additionalMiddlewares, ...loadedMiddlewares, - }; + } return sort( Object.entries(middlewares).map((m) => { - return [m[0], standardize(m[1])]; + return [m[0], standardize(m[1])] }), - ); -}; + ) +} -export default assembler; +export default assembler diff --git a/packages/core/lib/middlewares/default.js b/packages/core/lib/middlewares/default.js index 55d48919..7d0954f1 100644 --- a/packages/core/lib/middlewares/default.js +++ b/packages/core/lib/middlewares/default.js @@ -1,48 +1,48 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; +import { dirname } from 'path' +import { fileURLToPath } from 'url' -import healthMiddleware from "../../middlewares/health.js"; -import errorsMiddleware from "../../middlewares/errors.js"; -import notFoundMiddleware from "../../middlewares/notFound.js"; -import staticMiddleware from "../../middlewares/static.js"; -import iriMiddleware from "../../middlewares/iri.js"; -import localsMiddleware from "../../middlewares/locals.js"; +import healthMiddleware from '../../middlewares/health.js' +import errorsMiddleware from '../../middlewares/errors.js' +import notFoundMiddleware from '../../middlewares/notFound.js' +import staticMiddleware from '../../middlewares/static.js' +import iriMiddleware from '../../middlewares/iri.js' +import localsMiddleware from '../../middlewares/locals.js' -const currentDir = dirname(fileURLToPath(import.meta.url)); +const currentDir = dirname(fileURLToPath(import.meta.url)) const health = { - paths: "/health", - methods: "GET", + paths: '/health', + methods: 'GET', module: healthMiddleware, -}; +} const errors = { module: errorsMiddleware, order: 1200, -}; +} const notFound = { module: notFoundMiddleware, order: 1100, -}; +} const templateStaticFiles = { module: staticMiddleware, - paths: "/static/core", + paths: '/static/core', config: { directory: `${currentDir}/../../static`, }, -}; +} const iri = { module: iriMiddleware, order: 10, -}; +} const locals = { module: localsMiddleware, order: 11, -}; +} export default { health, @@ -51,4 +51,4 @@ export default { templateStaticFiles, iri, locals, -}; +} diff --git a/packages/core/lib/middlewares/loader.js b/packages/core/lib/middlewares/loader.js index 151c747d..0333c6e8 100644 --- a/packages/core/lib/middlewares/loader.js +++ b/packages/core/lib/middlewares/loader.js @@ -1,41 +1,41 @@ -import path from "path"; -import cloneDeep from "lodash/cloneDeep.js"; +import path from 'path' +import cloneDeep from 'lodash/cloneDeep.js' const resolvePath = (modulePath) => { - if ([".", "/"].includes(modulePath.slice(0, 1))) { - return path.resolve(modulePath); + if (['.', '/'].includes(modulePath.slice(0, 1))) { + return path.resolve(modulePath) } else { - return modulePath; + return modulePath } -}; +} export const loader = async (modulePath) => { - const middleware = await import(resolvePath(modulePath)); - return middleware.default; -}; + const middleware = await import(resolvePath(modulePath)) + return middleware.default +} const load = async (config) => { - let middlewares = {}; - if (config.middlewares && typeof config.middlewares === "object") { - middlewares = cloneDeep(config.middlewares); + let middlewares = {} + if (config.middlewares && typeof config.middlewares === 'object') { + middlewares = cloneDeep(config.middlewares) } await Promise.all( Object.keys(middlewares).map(async (m) => { if (middlewares[m] === null) { - delete middlewares[m]; - return; + delete middlewares[m] + return } if (!middlewares[m].module) { - throw new Error(`middleware '${m}' has no module configured`); + throw new Error(`middleware '${m}' has no module configured`) } - middlewares[m].module = await loader(middlewares[m].module); + middlewares[m].module = await loader(middlewares[m].module) }), - ); + ) - return middlewares; -}; + return middlewares +} -export default load; +export default load diff --git a/packages/core/lib/middlewares/sort.js b/packages/core/lib/middlewares/sort.js index 2e78055d..577868c1 100644 --- a/packages/core/lib/middlewares/sort.js +++ b/packages/core/lib/middlewares/sort.js @@ -5,7 +5,7 @@ */ const sort = (middlewares) => middlewares.sort((a, b) => { - return a[1].order - b[1].order; - }); + return a[1].order - b[1].order + }) -export default sort; +export default sort diff --git a/packages/core/lib/middlewares/standardize.js b/packages/core/lib/middlewares/standardize.js index e091cb99..eae01e55 100644 --- a/packages/core/lib/middlewares/standardize.js +++ b/packages/core/lib/middlewares/standardize.js @@ -1,78 +1,78 @@ -import cloneDeep from "lodash/cloneDeep.js"; +import cloneDeep from 'lodash/cloneDeep.js' // see: https://expressjs.com/fr/api.html#routing-methods (+all) const supportedMethods = [ - "all", - "checkout", - "copy", - "delete", - "get", - "head", - "lock", - "merge", - "mkactivity", - "mkcol", - "move", - "m-search", - "notify", - "options", - "patch", - "post", - "purge", - "put", - "report", - "search", - "subscribe", - "trace", - "unlock", - "unsubscribe", -]; + 'all', + 'checkout', + 'copy', + 'delete', + 'get', + 'head', + 'lock', + 'merge', + 'mkactivity', + 'mkcol', + 'move', + 'm-search', + 'notify', + 'options', + 'patch', + 'post', + 'purge', + 'put', + 'report', + 'search', + 'subscribe', + 'trace', + 'unlock', + 'unsubscribe', +] const standardize = (middleware) => { - const m = cloneDeep(middleware); + const m = cloneDeep(middleware) // make sure order is defined if (m.order === undefined) { - m.order = 100; + m.order = 100 } // make sure paths is defined and is an array if (m.paths === undefined) { - m.paths = []; + m.paths = [] } - if (typeof m.paths === "string") { - m.paths = [m.paths]; + if (typeof m.paths === 'string') { + m.paths = [m.paths] } // make sure methods is defined and is an array of valid supported methods if (m.methods === undefined) { - m.methods = []; + m.methods = [] } - if (typeof m.methods === "string") { - m.methods = [m.methods]; + if (typeof m.methods === 'string') { + m.methods = [m.methods] } m.methods = m.methods .map((method) => { - return method.toLocaleLowerCase(); + return method.toLocaleLowerCase() }) .filter((method) => { - return supportedMethods.includes(method); - }); + return supportedMethods.includes(method) + }) // make sure hosts is defined and is an array if (m.hosts === undefined) { - m.hosts = []; + m.hosts = [] } - if (typeof m.hosts === "string") { - m.hosts = [m.hosts]; + if (typeof m.hosts === 'string') { + m.hosts = [m.hosts] } // make sure config is defined if (m.config === undefined) { - m.config = {}; + m.config = {} } - return m; -}; + return m +} -export default standardize; +export default standardize diff --git a/packages/core/lib/resolvers.js b/packages/core/lib/resolvers.js index 438acc81..cb17aa76 100644 --- a/packages/core/lib/resolvers.js +++ b/packages/core/lib/resolvers.js @@ -1,4 +1,4 @@ -import { resolve, join } from "path"; +import { resolve, join } from 'path' /** * Register a resolver. @@ -7,27 +7,27 @@ import { resolve, join } from "path"; * @param {(name: string): string} callback */ export const registerResolver = (name, callback, value) => { - const split = value.split(":"); + const split = value.split(':') if (split.length === 1) { - return value; + return value } if (split[0] !== name) { - return value; + return value } - split.shift(); - return callback(split.join(":")); -}; + split.shift() + return callback(split.join(':')) +} export const pathResolver = (base, path) => { - const resolvedPath = resolve(path); + const resolvedPath = resolve(path) if (resolvedPath === path) { - return path; + return path } - return join(base, path); -}; + return join(base, path) +} /** * Environment resolver @@ -36,27 +36,27 @@ export const pathResolver = (base, path) => { export const envCallback = (name) => { if (!process.env[name]) { // eslint-disable-next-line no-console - console.warn(`WARNING: '${name}' environment variable is not set`); - return ""; + console.warn(`WARNING: '${name}' environment variable is not set`) + return '' } - return process.env[name]; -}; + return process.env[name] +} export const envResolver = (value) => { - return registerResolver("env", envCallback, value); -}; + return registerResolver('env', envCallback, value) +} /** * Current working directory resolver. */ export const cwdCallback = (name) => { - return pathResolver(process.cwd(), name); -}; + return pathResolver(process.cwd(), name) +} export const cwdResolver = (value) => { - return registerResolver("cwd", cwdCallback, value); -}; + return registerResolver('cwd', cwdCallback, value) +} /** * File resolver. @@ -65,12 +65,12 @@ export const cwdResolver = (value) => { export const fileCallback = (base = undefined) => { return (name) => { if (base === undefined) { - base = process.cwd(); + base = process.cwd() } - return pathResolver(base, name); - }; -}; + return pathResolver(base, name) + } +} export const fileResolver = (value, base = undefined) => { - return registerResolver("file", fileCallback(base), value); -}; + return registerResolver('file', fileCallback(base), value) +} diff --git a/packages/core/lib/templateEngine.js b/packages/core/lib/templateEngine.js index 92a1c813..ac93a39b 100644 --- a/packages/core/lib/templateEngine.js +++ b/packages/core/lib/templateEngine.js @@ -1,10 +1,10 @@ -import fs from "fs/promises"; -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import Handlebars from "handlebars"; -import merge from "lodash/merge.js"; +import fs from 'fs/promises' +import { dirname } from 'path' +import { fileURLToPath } from 'url' +import Handlebars from 'handlebars' +import merge from 'lodash/merge.js' -const currentDir = dirname(fileURLToPath(import.meta.url)); +const currentDir = dirname(fileURLToPath(import.meta.url)) const defaultConfig = { files: { @@ -13,13 +13,13 @@ const defaultConfig = { footer: `${currentDir}/../views/partials/footer.hbs`, }, partials: {}, - title: "Trifid", + title: 'Trifid', scripts: [], styles: [], - body: "", + body: '', disableHeader: false, disableFooter: false, -}; +} const templateEngine = async (defaultOptions = {}, forceRefresh = false) => { /** @@ -29,41 +29,41 @@ const templateEngine = async (defaultOptions = {}, forceRefresh = false) => { * @param {Function} fn Helper function. */ const registerHelper = (name, fn) => { - Handlebars.registerHelper(name, fn); - }; + Handlebars.registerHelper(name, fn) + } - registerHelper("ifEquals", (arg1, arg2, options) => { - return arg1 === arg2 ? options.fn(this) : options.inverse(this); - }); + registerHelper('ifEquals', (arg1, arg2, options) => { + return arg1 === arg2 ? options.fn(this) : options.inverse(this) + }) - const templateOptions = merge({}, defaultConfig, defaultOptions); + const templateOptions = merge({}, defaultConfig, defaultOptions) - const resolvedTemplates = new Map(); + const resolvedTemplates = new Map() const resolveTemplate = async (path) => { if (!resolvedTemplates.has(path) || forceRefresh) { - const template = await fs.readFile(path); - const compiled = Handlebars.compile(`${template}`); - resolvedTemplates.set(path, compiled); + const template = await fs.readFile(path) + const compiled = Handlebars.compile(`${template}`) + resolvedTemplates.set(path, compiled) } - return resolvedTemplates.get(path); - }; + return resolvedTemplates.get(path) + } if (!templateOptions?.files) { - throw new Error("no files defined"); + throw new Error('no files defined') } if (!templateOptions?.files?.main) { - throw new Error("no 'main' template was defined"); + throw new Error("no 'main' template was defined") } // register all partials Object.entries(templateOptions.partials).map(async (t) => { - const partialName = t[0]; - const partialPath = t[1]; - const resolvedPartial = await resolveTemplate(partialPath); - Handlebars.registerPartial(partialName, resolvedPartial); - }); + const partialName = t[0] + const partialPath = t[1] + const resolvedPartial = await resolveTemplate(partialPath) + Handlebars.registerPartial(partialName, resolvedPartial) + }) const templates = Object.fromEntries( await Promise.all( @@ -72,11 +72,11 @@ const templateEngine = async (defaultOptions = {}, forceRefresh = false) => { await resolveTemplate(t[1]), ]), ), - ); + ) const templatesWithoutMain = Object.fromEntries( - Object.entries(templates).filter((t) => t[0] !== "main"), - ); - const mainTemplate = templates.main; + Object.entries(templates).filter((t) => t[0] !== 'main'), + ) + const mainTemplate = templates.main /** * Render the full page. @@ -87,25 +87,25 @@ const templateEngine = async (defaultOptions = {}, forceRefresh = false) => { * @returns {string} The rendered view. */ const render = async (templatePath, context, options = {}) => { - const template = await resolveTemplate(templatePath); - const body = template(context); + const template = await resolveTemplate(templatePath) + const body = template(context) - const renderedOptions = merge({}, context, templateOptions, options); + const renderedOptions = merge({}, context, templateOptions, options) const renderedPartials = Object.fromEntries( Object.entries(templatesWithoutMain).map((t) => [ t[0], t[1](renderedOptions), ]), - ); + ) return mainTemplate({ ...renderedOptions, ...renderedPartials, body, - }); - }; + }) + } - return { render, registerHelper }; -}; + return { render, registerHelper } +} -export default templateEngine; +export default templateEngine diff --git a/packages/core/middlewares/errors.js b/packages/core/middlewares/errors.js index 8479742a..6ce2b243 100644 --- a/packages/core/middlewares/errors.js +++ b/packages/core/middlewares/errors.js @@ -1,12 +1,12 @@ const factory = (trifid) => { - const { logger } = trifid; + const { logger } = trifid return (err, _req, res, _next) => { - logger.error(err.stack); + logger.error(err.stack) - res.statusCode = err.statusCode || 500; - res.end(); - }; -}; + res.statusCode = err.statusCode || 500 + res.end() + } +} -export default factory; +export default factory diff --git a/packages/core/middlewares/express.js b/packages/core/middlewares/express.js index 88f1bd38..7ee98519 100644 --- a/packages/core/middlewares/express.js +++ b/packages/core/middlewares/express.js @@ -1,4 +1,4 @@ -import { loader } from "../lib/middlewares/loader.js"; +import { loader } from '../lib/middlewares/loader.js' /** * Import a plain Express middleware. @@ -11,17 +11,17 @@ import { loader } from "../lib/middlewares/loader.js"; * @returns Express middleware. */ const factory = async (trifid) => { - const { config } = trifid; - const { module, options } = config; + const { config } = trifid + const { module, options } = config if (!module) { throw new Error( "configuration requires 'module' field, specifying the Express middleware NPM module to load", - ); + ) } - const middleware = await loader(module); + const middleware = await loader(module) - return middleware(options); -}; + return middleware(options) +} -export default factory; +export default factory diff --git a/packages/core/middlewares/health.js b/packages/core/middlewares/health.js index 26f0c9b2..273e88ca 100644 --- a/packages/core/middlewares/health.js +++ b/packages/core/middlewares/health.js @@ -1,12 +1,12 @@ const factory = (trifid) => { - const { logger } = trifid; + const { logger } = trifid return (_req, res, _next) => { - logger.debug("reached health endpoint"); + logger.debug('reached health endpoint') - res.set("Content-Type", "text/plain"); - res.send("ok"); - }; -}; + res.set('Content-Type', 'text/plain') + res.send('ok') + } +} -export default factory; +export default factory diff --git a/packages/core/middlewares/iri.js b/packages/core/middlewares/iri.js index 69e30891..37ebc2d7 100644 --- a/packages/core/middlewares/iri.js +++ b/packages/core/middlewares/iri.js @@ -1,5 +1,5 @@ -import { URL } from "url"; -import absoluteUrl from "absolute-url"; +import { URL } from 'url' +import absoluteUrl from 'absolute-url' /** * Replacement for `url.format` which is deprecated. @@ -7,7 +7,7 @@ import absoluteUrl from "absolute-url"; * @param {URL} urlObject The URL object. * @returns {string} URL as a string. */ -const urlFrom = (urlObject) => urlObject.toString(); +const urlFrom = (urlObject) => urlObject.toString() /** * Remove the searchParams part of a URL. @@ -16,47 +16,47 @@ const urlFrom = (urlObject) => urlObject.toString(); * @returns {string} The URL without the searchParams part. */ const removeSearchParams = (originalUrl) => { - const url = new URL(originalUrl); - url.search = ""; - url.searchParams.forEach((_value, key) => url.searchParams.delete(key)); - return urlFrom(url); -}; + const url = new URL(originalUrl) + url.search = '' + url.searchParams.forEach((_value, key) => url.searchParams.delete(key)) + return urlFrom(url) +} const factory = (trifid) => { - const { config, logger } = trifid; - const { datasetBaseUrl } = config; + const { config, logger } = trifid + const { datasetBaseUrl } = config // check if `datasetBaseUrl` is a valid URL if present if (datasetBaseUrl) { try { - new URL(datasetBaseUrl); // eslint-disable-line no-new + new URL(datasetBaseUrl) // eslint-disable-line no-new } catch (_e) { throw new Error( `The current value you have for 'datasetBaseUrl' is '${datasetBaseUrl}', which is not a valid URL.`, - ); + ) } } return (req, res, next) => { - absoluteUrl.attach(req); - const url = req.absoluteUrl(); - req.iri = decodeURI(removeSearchParams(url)); + absoluteUrl.attach(req) + const url = req.absoluteUrl() + req.iri = decodeURI(removeSearchParams(url)) // set current path, so that middlewares can access it - res.locals.currentPath = req.path; + res.locals.currentPath = req.path // update `req.iri` if a value for `datasetBaseUrl` is provided if (datasetBaseUrl) { - const absoluteBaseUrl = new URL("/", url); - const currentBaseUrl = absoluteBaseUrl.toString(); - req.iri = req.iri.replace(currentBaseUrl, datasetBaseUrl); - logger.debug(`value for req.iri: ${req.iri} (rewritten)`); - return next(); + const absoluteBaseUrl = new URL('/', url) + const currentBaseUrl = absoluteBaseUrl.toString() + req.iri = req.iri.replace(currentBaseUrl, datasetBaseUrl) + logger.debug(`value for req.iri: ${req.iri} (rewritten)`) + return next() } - logger.debug(`value for req.iri: ${req.iri}`); - return next(); - }; -}; + logger.debug(`value for req.iri: ${req.iri}`) + return next() + } +} -export default factory; +export default factory diff --git a/packages/core/middlewares/locals.js b/packages/core/middlewares/locals.js index b53ef34a..2e508f57 100644 --- a/packages/core/middlewares/locals.js +++ b/packages/core/middlewares/locals.js @@ -1,45 +1,45 @@ -import url from "url"; -import absoluteUrl from "absolute-url"; +import url from 'url' +import absoluteUrl from 'absolute-url' const factory = (trifid) => { - const { logger } = trifid; + const { logger } = trifid - const defaultLanguage = "en"; - const supportedLanguages = ["en", "fr", "de", "it"]; + const defaultLanguage = 'en' + const supportedLanguages = ['en', 'fr', 'de', 'it'] - const oneMonthMilliseconds = 60 * 60 * 24 * 30 * 1000; + const oneMonthMilliseconds = 60 * 60 * 24 * 30 * 1000 return (req, res, next) => { - absoluteUrl.attach(req); + absoluteUrl.attach(req) // export language information for other middlewares - res.locals.defaultLanguage = defaultLanguage; - res.locals.currentLanguage = req?.cookies?.i18n || defaultLanguage; + res.locals.defaultLanguage = defaultLanguage + res.locals.currentLanguage = req?.cookies?.i18n || defaultLanguage // update langage by setting `lang` query parameter - const lang = req.query.lang; + const lang = req.query.lang if (lang && supportedLanguages.includes(lang)) { - logger.debug(`set default language to '${lang}'`); - res.cookie("i18n", lang, { maxAge: oneMonthMilliseconds }); - res.locals.currentLanguage = lang; + logger.debug(`set default language to '${lang}'`) + res.cookie('i18n', lang, { maxAge: oneMonthMilliseconds }) + res.locals.currentLanguage = lang } // requested resource - res.locals.iri = req.iri; + res.locals.iri = req.iri // requested resource parsed into URL object - res.locals.url = new url.URL(res.locals.iri); + res.locals.url = new url.URL(res.locals.iri) // dummy translation res.locals.t = res.locals.t || ((x) => { - const translation = x.substring(x.indexOf(":") + 1); - logger.debug(`translation value: ${translation}`); - return translation; - }); - next(); - }; -}; - -export default factory; + const translation = x.substring(x.indexOf(':') + 1) + logger.debug(`translation value: ${translation}`) + return translation + }) + next() + } +} + +export default factory diff --git a/packages/core/middlewares/notFound.js b/packages/core/middlewares/notFound.js index 3004e9a4..8fb18c44 100644 --- a/packages/core/middlewares/notFound.js +++ b/packages/core/middlewares/notFound.js @@ -1,46 +1,46 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; +import { dirname } from 'path' +import { fileURLToPath } from 'url' -const currentDir = dirname(fileURLToPath(import.meta.url)); +const currentDir = dirname(fileURLToPath(import.meta.url)) const factory = (trifid) => { - const { logger, render } = trifid; + const { logger, render } = trifid return async (req, res, _next) => { - logger.debug(`path '${req.url}' returned a 404 error (Not Found)`); + logger.debug(`path '${req.url}' returned a 404 error (Not Found)`) - res.status(404); + res.status(404) const accepts = req.accepts([ - "text/plain", - "json", - "html", - "application/n-quads", - ]); + 'text/plain', + 'json', + 'html', + 'application/n-quads', + ]) switch (accepts) { - case "json": - res.send({ success: false, message: "Not found", status: 404 }); - break; - - case "application/n-quads": - case "html": - res.send( - await render( - `${currentDir}/../views/404.hbs`, - { - url: req.url, - locals: res.locals, - }, - { title: "Not Found" }, - ), - ); - break; - - default: - res.send("Not Found\n"); - break; + case 'json': + res.send({ success: false, message: 'Not found', status: 404 }) + break + + case 'application/n-quads': + case 'html': + res.send( + await render( + `${currentDir}/../views/404.hbs`, + { + url: req.url, + locals: res.locals, + }, + { title: 'Not Found' }, + ), + ) + break + + default: + res.send('Not Found\n') + break } - }; -}; + } +} -export default factory; +export default factory diff --git a/packages/core/middlewares/redirect.js b/packages/core/middlewares/redirect.js index 61510964..6e4995d9 100644 --- a/packages/core/middlewares/redirect.js +++ b/packages/core/middlewares/redirect.js @@ -1,14 +1,14 @@ const factory = (trifid) => { - const { config, logger } = trifid; - const { target } = config; + const { config, logger } = trifid + const { target } = config if (!target) { - throw new Error("configuration is missing 'target' field"); + throw new Error("configuration is missing 'target' field") } return (_req, res, _next) => { - logger.debug(`redirect to: ${target}`); - res.redirect(target); - }; -}; + logger.debug(`redirect to: ${target}`) + res.redirect(target) + } +} -export default factory; +export default factory diff --git a/packages/core/middlewares/rewrite.js b/packages/core/middlewares/rewrite.js index d8f5465f..1cb2322b 100644 --- a/packages/core/middlewares/rewrite.js +++ b/packages/core/middlewares/rewrite.js @@ -1,4 +1,4 @@ -import camouflageRewrite from "camouflage-rewrite"; +import camouflageRewrite from 'camouflage-rewrite' /** * Rewrite the dataset base URL. @@ -13,26 +13,26 @@ import camouflageRewrite from "camouflage-rewrite"; * @returns Express middleware. */ const factory = (trifid) => { - const { config } = trifid; - const { rewriteContent, datasetBaseUrl } = config; + const { config } = trifid + const { rewriteContent, datasetBaseUrl } = config - let rewriteContentValue = true; + let rewriteContentValue = true if (rewriteContent !== undefined) { - rewriteContentValue = rewriteContent; + rewriteContentValue = rewriteContent } // skip rewriting if the `datasetBaseUrl` is empty if (!datasetBaseUrl) { return (_req, _res, next) => { - next(); - }; + next() + } } return camouflageRewrite({ ...config, url: datasetBaseUrl, rewriteContent: rewriteContentValue, - }); -}; + }) +} -export default factory; +export default factory diff --git a/packages/core/middlewares/static.js b/packages/core/middlewares/static.js index 0be3299f..0b762960 100644 --- a/packages/core/middlewares/static.js +++ b/packages/core/middlewares/static.js @@ -1,11 +1,11 @@ -import express from "express"; +import express from 'express' const factory = (trifid) => { - const { directory } = trifid.config; + const { directory } = trifid.config if (!directory) { - throw new Error("configuration is missing 'directory' field"); + throw new Error("configuration is missing 'directory' field") } - return express.static(directory); -}; + return express.static(directory) +} -export default factory; +export default factory diff --git a/packages/core/middlewares/throw.js b/packages/core/middlewares/throw.js index 770fbf29..f558d706 100644 --- a/packages/core/middlewares/throw.js +++ b/packages/core/middlewares/throw.js @@ -1,14 +1,14 @@ const factory = (trifid) => { - const { message } = trifid.config; + const { message } = trifid.config - let messageToThrow = "Oops, something went wrong :-("; + let messageToThrow = 'Oops, something went wrong :-(' if (message) { - messageToThrow = `${message}`; + messageToThrow = `${message}` } return (_req, _res, _next) => { - throw new Error(messageToThrow); - }; -}; + throw new Error(messageToThrow) + } +} -export default factory; +export default factory diff --git a/packages/core/middlewares/view.js b/packages/core/middlewares/view.js index 75640e9f..d8031ad9 100644 --- a/packages/core/middlewares/view.js +++ b/packages/core/middlewares/view.js @@ -10,26 +10,26 @@ * @returns Express middleware. */ const factory = async (trifid) => { - const { config, render } = trifid; - const { path } = config; - let { context, options } = config; + const { config, render } = trifid + const { path } = config + let { context, options } = config if (!path) { - throw new Error("configuration is missing 'path' field"); + throw new Error("configuration is missing 'path' field") } if (!context) { - context = {}; + context = {} } if (!options) { - options = {}; + options = {} } return async (_req, res, _next) => { - res.status(200); - res.send(await render(path, { ...context, locals: res.locals }, options)); - }; -}; + res.status(200) + res.send(await render(path, { ...context, locals: res.locals }, options)) + } +} -export default factory; +export default factory diff --git a/packages/core/server.js b/packages/core/server.js index 1db995ad..7068cea0 100755 --- a/packages/core/server.js +++ b/packages/core/server.js @@ -1,19 +1,19 @@ #!/usr/bin/env node -import { join } from "path"; -import { Command } from "commander"; +import { join } from 'path' +import { Command } from 'commander' -import trifid from "./index.js"; +import trifid from './index.js' -const program = new Command(); +const program = new Command() program - .option("-c, --config ", "configuration file", "config.yaml") - .option("-p, --port ", "listener port", parseInt) - .parse(process.argv); + .option('-c, --config ', 'configuration file', 'config.yaml') + .option('-p, --port ', 'listener port', parseInt) + .parse(process.argv) -const opts = program.opts(); -const configFile = join(process.cwd(), opts.config); +const opts = program.opts() +const configFile = join(process.cwd(), opts.config) // create a minimal configuration that extends the specified one const config = { @@ -21,13 +21,13 @@ const config = { server: { listener: {}, }, -}; +} // add optional arguments to the configuration if (opts.port) { - config.server.listener.port = opts.port; + config.server.listener.port = opts.port } // load the configuration and start the server -const instance = await trifid(config); -instance.start(); +const instance = await trifid(config) +instance.start() diff --git a/packages/core/test/config.handler.test.js b/packages/core/test/config.handler.test.js index a455fbc0..78262633 100644 --- a/packages/core/test/config.handler.test.js +++ b/packages/core/test/config.handler.test.js @@ -1,132 +1,132 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { describe, expect, test } from "@jest/globals"; +import { dirname } from 'path' +import { fileURLToPath } from 'url' +import { describe, expect, test } from '@jest/globals' -import handler from "../lib/config/handler.js"; -import { fileCallback } from "../lib/resolvers.js"; +import handler from '../lib/config/handler.js' +import { fileCallback } from '../lib/resolvers.js' -describe("config handler", () => { - test("should not throw when loading an empty configuration file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); +describe('config handler', () => { + test('should not throw when loading an empty configuration file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/empty.json")), - ).resolves.not.toThrow(); - }); + handler(fileCallback(currentDir)('./support/empty.json')), + ).resolves.not.toThrow() + }) - test("should not throw when loading an empty configuration", async () => { - await expect(handler({})).resolves.not.toThrow(); - }); + test('should not throw when loading an empty configuration', async () => { + await expect(handler({})).resolves.not.toThrow() + }) - test("should not throw when loading a configuration that extends an existing one", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should not throw when loading a configuration that extends an existing one', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( handler({ extends: [`${currentDir}/support/empty.json`], }), - ).resolves.not.toThrow(); - }); + ).resolves.not.toThrow() + }) - test("should throw when loading a configuration that extends a non-existant one", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should throw when loading a configuration that extends a non-existant one', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( handler({ extends: [`${currentDir}/support/non-existant.json`], }), - ).rejects.toThrow(); - }); + ).rejects.toThrow() + }) - test("should not throw when loading a basic configuration file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should not throw when loading a basic configuration file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/basic.json")), - ).resolves.not.toThrow(); - }); + handler(fileCallback(currentDir)('./support/basic.json')), + ).resolves.not.toThrow() + }) - test("should not throw when loading a basic YAML configuration file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should not throw when loading a basic YAML configuration file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/basic.yaml")), - ).resolves.not.toThrow(); - }); + handler(fileCallback(currentDir)('./support/basic.yaml')), + ).resolves.not.toThrow() + }) - test("should throw when trying to load a non-existant configuration file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should throw when trying to load a non-existant configuration file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/non-existant.json")), - ).rejects.toThrow(); - }); + handler(fileCallback(currentDir)('./support/non-existant.json')), + ).rejects.toThrow() + }) - test("should throw when trying to read an invalid configuration file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should throw when trying to read an invalid configuration file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/invalid.json")), - ).rejects.toThrow(); - }); + handler(fileCallback(currentDir)('./support/invalid.json')), + ).rejects.toThrow() + }) - test("should throw when trying to read an invalid JSON file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should throw when trying to read an invalid JSON file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/invalid-json.json")), - ).rejects.toThrow(); - }); + handler(fileCallback(currentDir)('./support/invalid-json.json')), + ).rejects.toThrow() + }) - test("should support comments in JSON configuration file", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should support comments in JSON configuration file', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/basic-commented.json")), - ).resolves.not.toThrow(); - }); + handler(fileCallback(currentDir)('./support/basic-commented.json')), + ).resolves.not.toThrow() + }) - test("simple chain should work", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('simple chain should work', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/chain/chain1.json")), - ).resolves.not.toThrow(); - }); + handler(fileCallback(currentDir)('./support/chain/chain1.json')), + ).resolves.not.toThrow() + }) - test("check if expected values are here on extended config", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('check if expected values are here on extended config', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) const config = await handler( - fileCallback(currentDir)("./support/chain/chain1.json"), - ); - expect(config.globals).toBeDefined(); - expect(config.globals.value3).toBeDefined(); - expect(config.globals.value3).toEqual("chain3"); - expect(config.globals.value2).toBeDefined(); - expect(config.globals.value2).toEqual("chain2"); - expect(config.globals.value1).toBeDefined(); - expect(config.globals.value1).toEqual("chain1"); - expect(config.globals.value).toBeDefined(); - expect(config.globals.value).toEqual("chain1"); - }); - - test("simple check using the file resolver should work", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + fileCallback(currentDir)('./support/chain/chain1.json'), + ) + expect(config.globals).toBeDefined() + expect(config.globals.value3).toBeDefined() + expect(config.globals.value3).toEqual('chain3') + expect(config.globals.value2).toBeDefined() + expect(config.globals.value2).toEqual('chain2') + expect(config.globals.value1).toBeDefined() + expect(config.globals.value1).toEqual('chain1') + expect(config.globals.value).toBeDefined() + expect(config.globals.value).toEqual('chain1') + }) + + test('simple check using the file resolver should work', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/chain-file/chain1.json")), - ).resolves.not.toThrow(); - }); + handler(fileCallback(currentDir)('./support/chain-file/chain1.json')), + ).resolves.not.toThrow() + }) - test("check if expected values are here on extended config with file prefix", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('check if expected values are here on extended config with file prefix', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) const config = await handler( - fileCallback(currentDir)("./support/chain-file/chain1.json"), - ); - expect(config.globals).toBeDefined(); - expect(config.globals.value3).toBeDefined(); - expect(config.globals.value3).toEqual("chain3"); - expect(config.globals.value2).toBeDefined(); - expect(config.globals.value2).toEqual("chain2"); - expect(config.globals.value1).toBeDefined(); - expect(config.globals.value1).toEqual("chain1"); - expect(config.globals.value).toBeDefined(); - expect(config.globals.value).toEqual("chain1"); - }); - - test("should throw in case of infinite loop", async () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + fileCallback(currentDir)('./support/chain-file/chain1.json'), + ) + expect(config.globals).toBeDefined() + expect(config.globals.value3).toBeDefined() + expect(config.globals.value3).toEqual('chain3') + expect(config.globals.value2).toBeDefined() + expect(config.globals.value2).toEqual('chain2') + expect(config.globals.value1).toBeDefined() + expect(config.globals.value1).toEqual('chain1') + expect(config.globals.value).toBeDefined() + expect(config.globals.value).toEqual('chain1') + }) + + test('should throw in case of infinite loop', async () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) await expect( - handler(fileCallback(currentDir)("./support/infinite-loop/chain1.json")), - ).rejects.toThrow(); - }); -}); + handler(fileCallback(currentDir)('./support/infinite-loop/chain1.json')), + ).rejects.toThrow() + }) +}) diff --git a/packages/core/test/config.test.js b/packages/core/test/config.test.js index 15606de8..86a5b4f7 100644 --- a/packages/core/test/config.test.js +++ b/packages/core/test/config.test.js @@ -1,23 +1,23 @@ -import { describe, expect, test } from "@jest/globals"; +import { describe, expect, test } from '@jest/globals' -import schema from "../lib/config/schema.js"; -import parser from "../lib/config/parser.js"; +import schema from '../lib/config/schema.js' +import parser from '../lib/config/parser.js' -describe("config", () => { - test("should be an object", () => { - expect(typeof schema).toEqual("object"); - }); +describe('config', () => { + test('should be an object', () => { + expect(typeof schema).toEqual('object') + }) - test("should not throw if the configuration is empty", () => { - expect(() => parser()).not.toThrow(); - expect(() => parser({})).not.toThrow(); - }); + test('should not throw if the configuration is empty', () => { + expect(() => parser()).not.toThrow() + expect(() => parser({})).not.toThrow() + }) - test("sould throw if we add some non-supported fields", () => { - expect(() => parser({ thisFieldIsNotSupported: true })).toThrow(); - }); + test('sould throw if we add some non-supported fields', () => { + expect(() => parser({ thisFieldIsNotSupported: true })).toThrow() + }) - test("should not throw if supported properties are empty", () => { + test('should not throw if supported properties are empty', () => { expect(() => parser({ extends: [], @@ -25,51 +25,51 @@ describe("config", () => { server: {}, middlewares: {}, }), - ).not.toThrow(); - }); + ).not.toThrow() + }) - test("should not throw on valid values for extends", () => { + test('should not throw on valid values for extends', () => { expect(() => parser({ extends: [], }), - ).not.toThrow(); + ).not.toThrow() expect(() => parser({ - extends: ["path"], + extends: ['path'], }), - ).not.toThrow(); + ).not.toThrow() expect(() => parser({ - extends: ["path1", "path2", "path3"], + extends: ['path1', 'path2', 'path3'], }), - ).not.toThrow(); - }); + ).not.toThrow() + }) - test("should throw on invalid values for extends", () => { + test('should throw on invalid values for extends', () => { // this is a string instead of an array of strings expect(() => { parser({ - extends: "this is a string instead of an array", - }); - }).toThrow(); + extends: 'this is a string instead of an array', + }) + }).toThrow() // this is not an array of strings, but an array of integers expect(() => { parser({ extends: [1, 2, 3], - }); - }).toThrow(); - }); + }) + }).toThrow() + }) - test("should not throw on valid values for server", () => { + test('should not throw on valid values for server', () => { expect(() => { parser({ server: {}, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ @@ -77,8 +77,8 @@ describe("config", () => { listener: {}, express: {}, }, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ @@ -86,8 +86,8 @@ describe("config", () => { listener: {}, express: {}, }, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ @@ -97,8 +97,8 @@ describe("config", () => { }, express: {}, }, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ @@ -107,20 +107,20 @@ describe("config", () => { port: 8080, }, express: { - foo: "bar", + foo: 'bar', }, }, - }); - }).not.toThrow(); - }); + }) + }).not.toThrow() + }) - test("should throw on invalid values for server", () => { + test('should throw on invalid values for server', () => { // this is a string instead of an object expect(() => { parser({ - server: "this is a string instead of an object", - }); - }).toThrow(); + server: 'this is a string instead of an object', + }) + }).toThrow() // unsupported field expect(() => { @@ -130,8 +130,8 @@ describe("config", () => { express: {}, unsupportedField: true, }, - }); - }).toThrow(); + }) + }).toThrow() // invalid port number expect(() => { @@ -142,8 +142,8 @@ describe("config", () => { }, express: {}, }, - }); - }).toThrow(); + }) + }).toThrow() // unsupported listener property expect(() => { @@ -155,101 +155,101 @@ describe("config", () => { }, express: {}, }, - }); - }).toThrow(); - }); + }) + }).toThrow() + }) - test("should not throw on valid values for globals", () => { + test('should not throw on valid values for globals', () => { expect(() => { parser({ globals: {}, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ globals: { - foo: "bar", + foo: 'bar', }, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ globals: { - foo: "bar", - jon: "doe", + foo: 'bar', + jon: 'doe', }, - }); - }).not.toThrow(); + }) + }).not.toThrow() // multi-level globals expect(() => { parser({ globals: { foo: { - bar: "baz", + bar: 'baz', }, }, - }); - }).not.toThrow(); - }); + }) + }).not.toThrow() + }) - test("should throw on invalid values for globals", () => { + test('should throw on invalid values for globals', () => { // this is a string instead of an object expect(() => { parser({ - globals: "this is a string instead of an object", - }); - }).toThrow(); - }); + globals: 'this is a string instead of an object', + }) + }).toThrow() + }) - test("should not throw on valid values for middlewares", () => { + test('should not throw on valid values for middlewares', () => { expect(() => { parser({ middlewares: {}, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ middlewares: { module: { order: 42, - module: "module", + module: 'module', }, }, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ middlewares: { module: { order: 42, - module: "module", + module: 'module', config: { - foo: "bar", + foo: 'bar', }, }, }, - }); - }).not.toThrow(); + }) + }).not.toThrow() expect(() => { parser({ middlewares: { module: { order: 42, - module: "module", + module: 'module', config: { - foo: "bar", + foo: 'bar', baz: null, }, }, }, - }); - }).not.toThrow(); + }) + }).not.toThrow() // allow complex config object expect(() => { @@ -257,35 +257,35 @@ describe("config", () => { middlewares: { module: { order: 42, - module: "module", + module: 'module', config: { foo: { - bar: "baz", + bar: 'baz', }, }, }, }, - }); - }).not.toThrow(); - }); + }) + }).not.toThrow() + }) - test("should throw on invalid values for middlewares", () => { + test('should throw on invalid values for middlewares', () => { // this is a string instead of an object expect(() => { parser({ - middlewares: "this is a string instead of an object", - }); - }).toThrow(); + middlewares: 'this is a string instead of an object', + }) + }).toThrow() // not scoped into an object per middleware expect(() => { parser({ middlewares: { order: 42, - name: "module", + name: 'module', }, - }); - }).toThrow(); + }) + }).toThrow() // missing "module" property expect(() => { @@ -295,7 +295,7 @@ describe("config", () => { order: 42, }, }, - }); - }).toThrow(); - }); -}); + }) + }).toThrow() + }) +}) diff --git a/packages/core/test/middlewares/errors.test.js b/packages/core/test/middlewares/errors.test.js index 0b4969f3..b2d8e050 100644 --- a/packages/core/test/middlewares/errors.test.js +++ b/packages/core/test/middlewares/errors.test.js @@ -1,68 +1,68 @@ -import express from "express"; -import request from "supertest"; -import { describe, expect, test } from "@jest/globals"; +import express from 'express' +import request from 'supertest' +import { describe, expect, test } from '@jest/globals' -import errorsMiddleware from "../../middlewares/errors.js"; +import errorsMiddleware from '../../middlewares/errors.js' -describe("errors middleware", () => { - test("should be a function", () => { - expect(typeof errorsMiddleware).toEqual("function"); - }); +describe('errors middleware', () => { + test('should be a function', () => { + expect(typeof errorsMiddleware).toEqual('function') + }) - test("should return a 500 status code", async () => { - const app = express(); + test('should return a 500 status code', async () => { + const app = express() const throwingMiddleware = (_req, _res, _next) => { - throw new Error("Oops, something went wrong…"); - }; + throw new Error('Oops, something went wrong…') + } - app.use(throwingMiddleware); + app.use(throwingMiddleware) app.use( errorsMiddleware({ logger: { error: (_msg) => {}, }, }), - ); + ) - return request(app).get("/").expect(500); - }); + return request(app).get('/').expect(500) + }) - test("should forward status code", async () => { - const app = express(); + test('should forward status code', async () => { + const app = express() const throwingMiddleware = (_req, res, _next) => { - res.status(502).send("Something went wrong :-("); - }; + res.status(502).send('Something went wrong :-(') + } - app.use(throwingMiddleware); + app.use(throwingMiddleware) app.use( errorsMiddleware({ logger: { error: (_msg) => {}, }, }), - ); + ) - return request(app).get("/").expect(502); - }); + return request(app).get('/').expect(502) + }) - test("should return an empty body", async () => { - const app = express(); + test('should return an empty body', async () => { + const app = express() const throwingMiddleware = (_req, _res, _next) => { - throw new Error("Oops, something went wrong…"); - }; + throw new Error('Oops, something went wrong…') + } - app.use(throwingMiddleware); + app.use(throwingMiddleware) app.use( errorsMiddleware({ logger: { error: (_msg) => {}, }, }), - ); + ) - return request(app).get("/").expect(""); - }); -}); + return request(app).get('/').expect('') + }) +}) diff --git a/packages/core/test/middlewares/health.test.js b/packages/core/test/middlewares/health.test.js index 72dc6a39..ecd054d4 100644 --- a/packages/core/test/middlewares/health.test.js +++ b/packages/core/test/middlewares/health.test.js @@ -1,92 +1,92 @@ -import express from "express"; -import request from "supertest"; -import { describe, expect, test } from "@jest/globals"; +import express from 'express' +import request from 'supertest' +import { describe, expect, test } from '@jest/globals' -import healthMiddleware from "../../middlewares/health.js"; +import healthMiddleware from '../../middlewares/health.js' -describe("health middleware", () => { - test("should be a function", () => { - expect(typeof healthMiddleware).toEqual("function"); - }); +describe('health middleware', () => { + test('should be a function', () => { + expect(typeof healthMiddleware).toEqual('function') + }) - test("should return expected content-type", async () => { - const app = express(); + test('should return expected content-type', async () => { + const app = express() app.use( - "/health", + '/health', healthMiddleware({ logger: { debug: (_msg) => {}, }, }), - ); + ) return request(app) - .get("/health") - .expect("Content-Type", /text\/plain/); - }); + .get('/health') + .expect('Content-Type', /text\/plain/) + }) - test("should return expected body", async () => { - const app = express(); + test('should return expected body', async () => { + const app = express() app.use( - "/health", + '/health', healthMiddleware({ logger: { debug: (_msg) => {}, }, }), - ); + ) - return request(app).get("/health").expect("ok"); - }); + return request(app).get('/health').expect('ok') + }) - test("should return expected status code", async () => { - const app = express(); + test('should return expected status code', async () => { + const app = express() app.use( - "/health", + '/health', healthMiddleware({ logger: { debug: (_msg) => {}, }, }), - ); + ) - return request(app).get("/health").expect(200); - }); + return request(app).get('/health').expect(200) + }) - test("should call health request with valid response", async () => { - const app = express(); + test('should call health request with valid response', async () => { + const app = express() app.use( - "/health", + '/health', healthMiddleware({ logger: { debug: (_msg) => {}, }, }), - ); + ) return request(app) - .get("/health") - .expect("Content-Type", /text\/plain/) - .expect("ok") - .expect(200); - }); + .get('/health') + .expect('Content-Type', /text\/plain/) + .expect('ok') + .expect(200) + }) - test("should not call health request", async () => { - const app = express(); + test('should not call health request', async () => { + const app = express() app.use( - "/health", + '/health', healthMiddleware({ logger: { debug: (_msg) => {}, }, }), - ); + ) - return request(app).get("/non-existant-route").expect(404); - }); -}); + return request(app).get('/non-existant-route').expect(404) + }) +}) diff --git a/packages/core/test/middlewares/redirect.test.js b/packages/core/test/middlewares/redirect.test.js index 54be94c2..2c956448 100644 --- a/packages/core/test/middlewares/redirect.test.js +++ b/packages/core/test/middlewares/redirect.test.js @@ -1,51 +1,51 @@ -import express from "express"; -import request from "supertest"; -import { describe, expect, test } from "@jest/globals"; +import express from 'express' +import request from 'supertest' +import { describe, expect, test } from '@jest/globals' -import redirectMiddleware from "../../middlewares/redirect.js"; +import redirectMiddleware from '../../middlewares/redirect.js' -describe("redirect middleware", () => { - test("should be a function", () => { - expect(typeof redirectMiddleware).toEqual("function"); - }); +describe('redirect middleware', () => { + test('should be a function', () => { + expect(typeof redirectMiddleware).toEqual('function') + }) - test("should throw if the target parameter is not set", () => { - expect(() => redirectMiddleware({ config: {} })).toThrow(); - }); + test('should throw if the target parameter is not set', () => { + expect(() => redirectMiddleware({ config: {} })).toThrow() + }) - test("should redirect request", async () => { - const app = express(); + test('should redirect request', async () => { + const app = express() app.use( - "/redirect", + '/redirect', redirectMiddleware({ config: { - target: "/", + target: '/', }, logger: { debug: (_) => {}, }, }), - ); + ) - return request(app).get("/redirect").expect(302); - }); + return request(app).get('/redirect').expect(302) + }) - test("should not redirect request", async () => { - const app = express(); + test('should not redirect request', async () => { + const app = express() app.use( - "/redirect", + '/redirect', redirectMiddleware({ config: { - target: "/", + target: '/', }, logger: { debug: (_) => {}, }, }), - ); + ) - return request(app).get("/non-existant-route").expect(404); - }); -}); + return request(app).get('/non-existant-route').expect(404) + }) +}) diff --git a/packages/core/test/middlewares/static.test.js b/packages/core/test/middlewares/static.test.js index 756cff2b..af7ac810 100644 --- a/packages/core/test/middlewares/static.test.js +++ b/packages/core/test/middlewares/static.test.js @@ -1,34 +1,34 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import express from "express"; -import request from "supertest"; -import { describe, expect, test } from "@jest/globals"; +import { dirname } from 'path' +import { fileURLToPath } from 'url' +import express from 'express' +import request from 'supertest' +import { describe, expect, test } from '@jest/globals' -import staticMiddleware from "../../middlewares/static.js"; +import staticMiddleware from '../../middlewares/static.js' -describe("static middleware", () => { - test("should be a function", () => { - expect(typeof staticMiddleware).toEqual("function"); - }); +describe('static middleware', () => { + test('should be a function', () => { + expect(typeof staticMiddleware).toEqual('function') + }) - test("should throw if the directory parameter is not set", () => { - expect(() => staticMiddleware({ config: {} })).toThrow(); - }); + test('should throw if the directory parameter is not set', () => { + expect(() => staticMiddleware({ config: {} })).toThrow() + }) - test("should not throw if the directory parameter is set", () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); + test('should not throw if the directory parameter is set', () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) expect(() => staticMiddleware({ config: { directory: `${currentDir}/../support/`, }, }), - ).not.toThrow(); - }); + ).not.toThrow() + }) - test("should serve the specified resource", () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); - const app = express(); + test('should serve the specified resource', () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) + const app = express() app.use( staticMiddleware({ @@ -36,18 +36,18 @@ describe("static middleware", () => { directory: `${currentDir}/../support`, }, }), - ); + ) return request(app) - .get("/test.txt") + .get('/test.txt') .expect(200) - .expect("Content-Type", /text\/plain/) - .expect(/some text/); - }); + .expect('Content-Type', /text\/plain/) + .expect(/some text/) + }) - test("should return a 404 on non-existant resources", () => { - const currentDir = dirname(fileURLToPath(import.meta.url)); - const app = express(); + test('should return a 404 on non-existant resources', () => { + const currentDir = dirname(fileURLToPath(import.meta.url)) + const app = express() app.use( staticMiddleware({ @@ -55,8 +55,8 @@ describe("static middleware", () => { directory: `${currentDir}/../support/`, }, }), - ); + ) - return request(app).get("/test-not-exist.txt").expect(404); - }); -}); + return request(app).get('/test-not-exist.txt').expect(404) + }) +}) diff --git a/packages/core/test/resolvers.test.js b/packages/core/test/resolvers.test.js index 502498c5..ff5d4973 100644 --- a/packages/core/test/resolvers.test.js +++ b/packages/core/test/resolvers.test.js @@ -1,4 +1,4 @@ -import { describe, test, expect } from "@jest/globals"; +import { describe, test, expect } from '@jest/globals' import { cwdCallback, @@ -7,176 +7,176 @@ import { envResolver, fileCallback, fileResolver, -} from "../lib/resolvers.js"; +} from '../lib/resolvers.js' -describe("resolvers", () => { +describe('resolvers', () => { // Environment variables resolver - test("should be able to resolve an environment variable", () => { - process.env.TEST_VARIABLE = "test"; - expect(envCallback("TEST_VARIABLE")).toEqual("test"); - delete process.env.TEST_VARIABLE; - }); - - test("should return an empty string on non-existant environment variables", () => { - delete process.env.TEST_VARIABLE; - expect(envCallback("TEST_VARIABLE")).toEqual(""); - }); - - test("env should not resolve to anything if it is another prefix", () => { - expect(envResolver("something:TEST_VARIABLE")).toEqual( - "something:TEST_VARIABLE", - ); - }); - - test("env should resolve with the right prefix", () => { - process.env.TEST_VARIABLE = "test"; - expect(envResolver("env:TEST_VARIABLE")).toEqual("test"); - delete process.env.TEST_VARIABLE; - }); - - test("env should resolve to empty string for non-existant variable with the right prefix", () => { - delete process.env.TEST_VARIABLE; - expect(envResolver("env:TEST_VARIABLE")).toEqual(""); - }); + test('should be able to resolve an environment variable', () => { + process.env.TEST_VARIABLE = 'test' + expect(envCallback('TEST_VARIABLE')).toEqual('test') + delete process.env.TEST_VARIABLE + }) + + test('should return an empty string on non-existant environment variables', () => { + delete process.env.TEST_VARIABLE + expect(envCallback('TEST_VARIABLE')).toEqual('') + }) + + test('env should not resolve to anything if it is another prefix', () => { + expect(envResolver('something:TEST_VARIABLE')).toEqual( + 'something:TEST_VARIABLE', + ) + }) + + test('env should resolve with the right prefix', () => { + process.env.TEST_VARIABLE = 'test' + expect(envResolver('env:TEST_VARIABLE')).toEqual('test') + delete process.env.TEST_VARIABLE + }) + + test('env should resolve to empty string for non-existant variable with the right prefix', () => { + delete process.env.TEST_VARIABLE + expect(envResolver('env:TEST_VARIABLE')).toEqual('') + }) // Current working directory resolver - test("should return the current working directory", () => { - expect(cwdCallback(".")).toEqual(process.cwd()); - }); - - test("cwd should be able to resolve paths", () => { - expect(cwdCallback("./test.js")).toEqual(`${process.cwd()}/test.js`); - expect(cwdCallback("test.js")).toEqual(`${process.cwd()}/test.js`); - expect(cwdCallback("././././test.js")).toEqual(`${process.cwd()}/test.js`); - expect(cwdCallback("./a/.././test.js")).toEqual(`${process.cwd()}/test.js`); - expect(cwdCallback("/test.js")).toEqual("/test.js"); - expect(cwdCallback("/a/b/c/test.js")).toEqual("/a/b/c/test.js"); - }); - - test("cwd resolver should not resolve on other prefix", () => { - expect(cwdResolver("something:test.js")).toEqual("something:test.js"); - }); - - test("cwd resolver should resolve on the cwd prefix", () => { - expect(cwdResolver("cwd:test.js")).toEqual(`${process.cwd()}/test.js`); - }); - - test("cwd resolver should give the same results than the callback", () => { - expect(cwdResolver("cwd:.")).toEqual(process.cwd()); - expect(cwdResolver("cwd:./test.js")).toEqual(`${process.cwd()}/test.js`); - expect(cwdResolver("cwd:test.js")).toEqual(`${process.cwd()}/test.js`); - expect(cwdResolver("cwd:././././test.js")).toEqual( + test('should return the current working directory', () => { + expect(cwdCallback('.')).toEqual(process.cwd()) + }) + + test('cwd should be able to resolve paths', () => { + expect(cwdCallback('./test.js')).toEqual(`${process.cwd()}/test.js`) + expect(cwdCallback('test.js')).toEqual(`${process.cwd()}/test.js`) + expect(cwdCallback('././././test.js')).toEqual(`${process.cwd()}/test.js`) + expect(cwdCallback('./a/.././test.js')).toEqual(`${process.cwd()}/test.js`) + expect(cwdCallback('/test.js')).toEqual('/test.js') + expect(cwdCallback('/a/b/c/test.js')).toEqual('/a/b/c/test.js') + }) + + test('cwd resolver should not resolve on other prefix', () => { + expect(cwdResolver('something:test.js')).toEqual('something:test.js') + }) + + test('cwd resolver should resolve on the cwd prefix', () => { + expect(cwdResolver('cwd:test.js')).toEqual(`${process.cwd()}/test.js`) + }) + + test('cwd resolver should give the same results than the callback', () => { + expect(cwdResolver('cwd:.')).toEqual(process.cwd()) + expect(cwdResolver('cwd:./test.js')).toEqual(`${process.cwd()}/test.js`) + expect(cwdResolver('cwd:test.js')).toEqual(`${process.cwd()}/test.js`) + expect(cwdResolver('cwd:././././test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(cwdResolver("cwd:./a/.././test.js")).toEqual( + ) + expect(cwdResolver('cwd:./a/.././test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(cwdResolver("cwd:/test.js")).toEqual("/test.js"); - expect(cwdResolver("cwd:/a/b/c/test.js")).toEqual("/a/b/c/test.js"); - }); + ) + expect(cwdResolver('cwd:/test.js')).toEqual('/test.js') + expect(cwdResolver('cwd:/a/b/c/test.js')).toEqual('/a/b/c/test.js') + }) // File resolver - test("file callback should behave the same as cwd if no base is defined", () => { - expect(fileCallback()(".")).toEqual(process.cwd()); - expect(fileCallback()("./test.js")).toEqual(`${process.cwd()}/test.js`); - expect(fileCallback()("test.js")).toEqual(`${process.cwd()}/test.js`); - expect(fileCallback()("././././test.js")).toEqual( + test('file callback should behave the same as cwd if no base is defined', () => { + expect(fileCallback()('.')).toEqual(process.cwd()) + expect(fileCallback()('./test.js')).toEqual(`${process.cwd()}/test.js`) + expect(fileCallback()('test.js')).toEqual(`${process.cwd()}/test.js`) + expect(fileCallback()('././././test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileCallback()("./a/.././test.js")).toEqual( + ) + expect(fileCallback()('./a/.././test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileCallback()("/test.js")).toEqual("/test.js"); - expect(fileCallback()("/a/b/c/test.js")).toEqual("/a/b/c/test.js"); + ) + expect(fileCallback()('/test.js')).toEqual('/test.js') + expect(fileCallback()('/a/b/c/test.js')).toEqual('/a/b/c/test.js') // test with explicit 'undefined' base - expect(fileCallback(undefined)(".")).toEqual(process.cwd()); - expect(fileCallback(undefined)("./test.js")).toEqual( + expect(fileCallback(undefined)('.')).toEqual(process.cwd()) + expect(fileCallback(undefined)('./test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileCallback(undefined)("test.js")).toEqual( + ) + expect(fileCallback(undefined)('test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileCallback(undefined)("././././test.js")).toEqual( + ) + expect(fileCallback(undefined)('././././test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileCallback(undefined)("./a/.././test.js")).toEqual( + ) + expect(fileCallback(undefined)('./a/.././test.js')).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileCallback(undefined)("/test.js")).toEqual("/test.js"); - expect(fileCallback(undefined)("/a/b/c/test.js")).toEqual("/a/b/c/test.js"); - }); + ) + expect(fileCallback(undefined)('/test.js')).toEqual('/test.js') + expect(fileCallback(undefined)('/a/b/c/test.js')).toEqual('/a/b/c/test.js') + }) - test("file callback should resolve as expected with the specified base", () => { - expect(fileCallback("/path/test")(".")).toEqual("/path/test"); - expect(fileCallback("/path/test")("..")).toEqual("/path"); + test('file callback should resolve as expected with the specified base', () => { + expect(fileCallback('/path/test')('.')).toEqual('/path/test') + expect(fileCallback('/path/test')('..')).toEqual('/path') // note the '/' at the end - expect(fileCallback("/path/test")("../")).toEqual("/path/"); - - expect(fileCallback("/path/test")("../..")).toEqual("/"); - expect(fileCallback("/path/test")("../../")).toEqual("/"); - expect(fileCallback("/path/test")("../../..")).toEqual("/"); - expect(fileCallback("/path/test")("../../../")).toEqual("/"); - expect(fileCallback("/path/test")("./test.js")).toEqual( - "/path/test/test.js", - ); - expect(fileCallback("/path/test")("test.js")).toEqual("/path/test/test.js"); - expect(fileCallback("/path/test")("././././test.js")).toEqual( - "/path/test/test.js", - ); - expect(fileCallback("/path/test")("./a/.././test.js")).toEqual( - "/path/test/test.js", - ); - expect(fileCallback("/path/test")("/test.js")).toEqual("/test.js"); - expect(fileCallback("/path/test")("/a/b/c/test.js")).toEqual( - "/a/b/c/test.js", - ); - }); - - test("file resolver should not resolve on other prefix", () => { - expect(fileResolver("something:test.js")).toEqual("something:test.js"); - }); - - test("file resolver should resolve on the file prefix", () => { - expect(fileResolver("file:test.js")).toEqual(`${process.cwd()}/test.js`); - expect(fileResolver("file:test.js", undefined)).toEqual( + expect(fileCallback('/path/test')('../')).toEqual('/path/') + + expect(fileCallback('/path/test')('../..')).toEqual('/') + expect(fileCallback('/path/test')('../../')).toEqual('/') + expect(fileCallback('/path/test')('../../..')).toEqual('/') + expect(fileCallback('/path/test')('../../../')).toEqual('/') + expect(fileCallback('/path/test')('./test.js')).toEqual( + '/path/test/test.js', + ) + expect(fileCallback('/path/test')('test.js')).toEqual('/path/test/test.js') + expect(fileCallback('/path/test')('././././test.js')).toEqual( + '/path/test/test.js', + ) + expect(fileCallback('/path/test')('./a/.././test.js')).toEqual( + '/path/test/test.js', + ) + expect(fileCallback('/path/test')('/test.js')).toEqual('/test.js') + expect(fileCallback('/path/test')('/a/b/c/test.js')).toEqual( + '/a/b/c/test.js', + ) + }) + + test('file resolver should not resolve on other prefix', () => { + expect(fileResolver('something:test.js')).toEqual('something:test.js') + }) + + test('file resolver should resolve on the file prefix', () => { + expect(fileResolver('file:test.js')).toEqual(`${process.cwd()}/test.js`) + expect(fileResolver('file:test.js', undefined)).toEqual( `${process.cwd()}/test.js`, - ); - expect(fileResolver("file:test.js", "/path/test")).toEqual( - "/path/test/test.js", - ); - }); + ) + expect(fileResolver('file:test.js', '/path/test')).toEqual( + '/path/test/test.js', + ) + }) - test("file resolver should behave the same as the file callback", () => { - expect(fileResolver("file:.", "/path/test")).toEqual("/path/test"); - expect(fileResolver("file:..", "/path/test")).toEqual("/path"); + test('file resolver should behave the same as the file callback', () => { + expect(fileResolver('file:.', '/path/test')).toEqual('/path/test') + expect(fileResolver('file:..', '/path/test')).toEqual('/path') // note the '/' at the end - expect(fileResolver("file:../", "/path/test")).toEqual("/path/"); - - expect(fileResolver("file:../..", "/path/test")).toEqual("/"); - expect(fileResolver("file:../../", "/path/test")).toEqual("/"); - expect(fileResolver("file:../../..", "/path/test")).toEqual("/"); - expect(fileResolver("file:../../../", "/path/test")).toEqual("/"); - expect(fileResolver("file:./test.js", "/path/test")).toEqual( - "/path/test/test.js", - ); - expect(fileResolver("file:test.js", "/path/test")).toEqual( - "/path/test/test.js", - ); - expect(fileResolver("file:././././test.js", "/path/test")).toEqual( - "/path/test/test.js", - ); - expect(fileResolver("file:./a/.././test.js", "/path/test")).toEqual( - "/path/test/test.js", - ); - expect(fileResolver("file:/test.js", "/path/test")).toEqual("/test.js"); - expect(fileResolver("file:/a/b/c/test.js", "/path/test")).toEqual( - "/a/b/c/test.js", - ); - }); -}); + expect(fileResolver('file:../', '/path/test')).toEqual('/path/') + + expect(fileResolver('file:../..', '/path/test')).toEqual('/') + expect(fileResolver('file:../../', '/path/test')).toEqual('/') + expect(fileResolver('file:../../..', '/path/test')).toEqual('/') + expect(fileResolver('file:../../../', '/path/test')).toEqual('/') + expect(fileResolver('file:./test.js', '/path/test')).toEqual( + '/path/test/test.js', + ) + expect(fileResolver('file:test.js', '/path/test')).toEqual( + '/path/test/test.js', + ) + expect(fileResolver('file:././././test.js', '/path/test')).toEqual( + '/path/test/test.js', + ) + expect(fileResolver('file:./a/.././test.js', '/path/test')).toEqual( + '/path/test/test.js', + ) + expect(fileResolver('file:/test.js', '/path/test')).toEqual('/test.js') + expect(fileResolver('file:/a/b/c/test.js', '/path/test')).toEqual( + '/a/b/c/test.js', + ) + }) +}) diff --git a/packages/graph-explorer/index.js b/packages/graph-explorer/index.js index dd00bb4c..bcf54def 100644 --- a/packages/graph-explorer/index.js +++ b/packages/graph-explorer/index.js @@ -1,13 +1,13 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import absoluteUrl from "absolute-url"; -import { resolve } from "import-meta-resolve"; -import express from "express"; +import { dirname } from 'path' +import { fileURLToPath } from 'url' +import absoluteUrl from 'absolute-url' +import { resolve } from 'import-meta-resolve' +import express from 'express' -const currentDir = dirname(fileURLToPath(import.meta.url)); +const currentDir = dirname(fileURLToPath(import.meta.url)) const factory = async (trifid) => { - const { config, server, render } = trifid; + const { config, server, render } = trifid const { template, endpointUrl, @@ -16,41 +16,41 @@ const factory = async (trifid) => { schemaLabelProperty: schemaLabelPropertyConfig, language: languageConfig, languages: languagesConfig, - } = config; + } = config - const view = !template ? `${currentDir}/views/graph-explorer.hbs` : template; + const view = !template ? `${currentDir}/views/graph-explorer.hbs` : template // serve static files for graph-explorer - const distPath = await resolve("graph-explorer/dist/", import.meta.url); + const distPath = await resolve('graph-explorer/dist/', import.meta.url) server.use( - "/graph-explorer-assets/", - express.static(distPath.replace(/^file:\/\//, "")), - ); + '/graph-explorer-assets/', + express.static(distPath.replace(/^file:\/\//, '')), + ) server.use( - "/graph-explorer-static/", + '/graph-explorer-static/', express.static(`${currentDir}/static/`), - ); + ) - const endpoint = endpointUrl || "/query"; - const acceptBlankNodes = !!acceptBlankNodesConfig; - const dataLabelProperty = dataLabelPropertyConfig || "rdfs:label"; - const schemaLabelProperty = schemaLabelPropertyConfig || "rdfs:label"; - const language = languageConfig || "en"; + const endpoint = endpointUrl || '/query' + const acceptBlankNodes = !!acceptBlankNodesConfig + const dataLabelProperty = dataLabelPropertyConfig || 'rdfs:label' + const schemaLabelProperty = schemaLabelPropertyConfig || 'rdfs:label' + const language = languageConfig || 'en' const languages = languagesConfig || [ - { code: "en", label: "English" }, - { code: "de", label: "German" }, - { code: "fr", label: "French" }, - { code: "it", label: "Italian" }, - ]; + { code: 'en', label: 'English' }, + { code: 'de', label: 'German' }, + { code: 'fr', label: 'French' }, + { code: 'it', label: 'Italian' }, + ] return async (req, res, _next) => { - absoluteUrl.attach(req); + absoluteUrl.attach(req) - const urlPathname = new URL(req.originalUrl, req.absoluteUrl()).pathname; + const urlPathname = new URL(req.originalUrl, req.absoluteUrl()).pathname // redirect to trailing slash URL - if (urlPathname.slice(-1) !== "/") { - return res.redirect(`${urlPathname}/`); + if (urlPathname.slice(-1) !== '/') { + return res.redirect(`${urlPathname}/`) } const content = await render( @@ -72,11 +72,11 @@ const factory = async (trifid) => { // good practice: forward locals to templates locals: res.locals, }, - { title: "Graph Explorer" }, - ); + { title: 'Graph Explorer' }, + ) - res.send(content); - }; -}; + res.send(content) + } +} -export default factory; +export default factory diff --git a/packages/graph-explorer/static/app.js b/packages/graph-explorer/static/app.js index aed17e8a..ab318f5e 100644 --- a/packages/graph-explorer/static/app.js +++ b/packages/graph-explorer/static/app.js @@ -1,66 +1,66 @@ /* global ReactDOM, React, GraphExplorer, graphExplorerConfig */ -const SparqlDialect = GraphExplorer.OWLStatsSettings; -SparqlDialect.dataLabelProperty = graphExplorerConfig.dataLabelProperty; -SparqlDialect.schemaLabelProperty = graphExplorerConfig.schemaLabelProperty; +const SparqlDialect = GraphExplorer.OWLStatsSettings +SparqlDialect.dataLabelProperty = graphExplorerConfig.dataLabelProperty +SparqlDialect.schemaLabelProperty = graphExplorerConfig.schemaLabelProperty function onWorkspaceMounted(workspace) { if (!workspace) { - return; + return } - const model = workspace.getModel(); + const model = workspace.getModel() model.importLayout({ dataProvider: new GraphExplorer.SparqlDataProvider( { endpointUrl: graphExplorerConfig.endpointUrl, acceptBlankNodes: graphExplorerConfig.acceptBlankNodes, - imagePropertyUris: ["http://xmlns.com/foaf/0.1/img"], + imagePropertyUris: ['http://xmlns.com/foaf/0.1/img'], queryMethod: GraphExplorer.SparqlQueryMethod.GET, }, SparqlDialect, ), - }); + }) /** * get the '?resources' search param and load those resources */ - const url = new URL(window.location.href); - const resources = url.searchParams.get("resources"); + const url = new URL(window.location.href) + const resources = url.searchParams.get('resources') if (resources) { const elm = model.dataProvider.elementInfo({ - elementIds: resources.split(";"), - }); + elementIds: resources.split(';'), + }) elm .then(function (arg) { - const elmIds = []; - resources.split(";").forEach(function (item) { - const node = model.createElement(arg[item]); - elmIds[item] = node.id; - workspace.forceLayout(); - }); - return elmIds; + const elmIds = [] + resources.split(';').forEach(function (item) { + const node = model.createElement(arg[item]) + elmIds[item] = node.id + workspace.forceLayout() + }) + return elmIds }) .then(function (elmIds) { /* now that we have the resources, add the links */ const lnk = model.dataProvider.linksInfo({ - elementIds: resources.split(";"), - }); + elementIds: resources.split(';'), + }) lnk.then(function (arg) { arg.forEach(function (link) { const newLink = new GraphExplorer.Link({ typeId: link.linkTypeId, sourceId: elmIds[link.sourceId], targetId: elmIds[link.targetId], - }); - model.addLink(newLink); - workspace.forceLayout(); - }); - }); - }); + }) + model.addLink(newLink) + workspace.forceLayout() + }) + }) + }) } } @@ -68,9 +68,9 @@ const props = { ref: onWorkspaceMounted, languages: graphExplorerConfig.languages, language: graphExplorerConfig.language, -}; +} ReactDOM.render( React.createElement(GraphExplorer.Workspace, props), - document.getElementById("trifid-plugin-graph-explorer"), -); + document.getElementById('trifid-plugin-graph-explorer'), +) diff --git a/packages/graph-explorer/test/test.js b/packages/graph-explorer/test/test.js index 8f018cb2..5f64071a 100644 --- a/packages/graph-explorer/test/test.js +++ b/packages/graph-explorer/test/test.js @@ -1,29 +1,29 @@ -import assert from "assert"; -import withServer from "express-as-promise/withServer.js"; -import { describe, it } from "mocha"; -import trifidFactory from "../index.js"; +import assert from 'assert' +import withServer from 'express-as-promise/withServer.js' +import { describe, it } from 'mocha' +import trifidFactory from '../index.js' const createTrifidConfig = (config, server = {}) => { - const loggerSpy = []; + const loggerSpy = [] return { logger: (str) => loggerSpy.push(str), server, config, - }; -}; + } +} -describe("trifid-plugin-graph-explorer", () => { - describe("trifid factory", () => { - it("should be a factory", () => { - assert.strictEqual(typeof trifidFactory, "function"); - }); +describe('trifid-plugin-graph-explorer', () => { + describe('trifid factory', () => { + it('should be a factory', () => { + assert.strictEqual(typeof trifidFactory, 'function') + }) - it("should create a middleware with factory and default options", async () => { + it('should create a middleware with factory and default options', async () => { await withServer(async (server) => { - const trifid = createTrifidConfig({}, server.app); - trifidFactory(trifid); - }); - }); - }); -}); + const trifid = createTrifidConfig({}, server.app) + trifidFactory(trifid) + }) + }) + }) +}) diff --git a/packages/handler-fetch/index.js b/packages/handler-fetch/index.js index bab60a7c..01bad469 100644 --- a/packages/handler-fetch/index.js +++ b/packages/handler-fetch/index.js @@ -1,56 +1,56 @@ -import path from "path"; -import url from "url"; -import formats from "@rdfjs/formats-common/index.js"; -import rdf from "rdf-ext"; -import rdfHandler from "@rdfjs/express-handler"; +import path from 'path' +import url from 'url' +import formats from '@rdfjs/formats-common/index.js' +import rdf from 'rdf-ext' +import rdfHandler from '@rdfjs/express-handler' -import SerializerJsonld from "@rdfjs/serializer-jsonld-ext"; -import Fetcher from "./lib/Fetcher.js"; +import SerializerJsonld from '@rdfjs/serializer-jsonld-ext' +import Fetcher from './lib/Fetcher.js' // @TODO discuss what are the best serialization options. const jsonLdSerializer = new SerializerJsonld({ - encoding: "string", + encoding: 'string', // compact: true, // flatten: true -}); +}) -formats.serializers.set("application/json", jsonLdSerializer); -formats.serializers.set("application/ld+json", jsonLdSerializer); +formats.serializers.set('application/json', jsonLdSerializer) +formats.serializers.set('application/ld+json', jsonLdSerializer) const guessProtocol = (candidate) => { try { - return new url.URL(candidate).protocol; + return new url.URL(candidate).protocol } catch (error) { - return undefined; + return undefined } -}; +} export class FetchHandler { constructor(options) { - this.dataset = rdf.dataset(); - this.url = options.url; - this.cache = options.cache; - this.contentType = options.contentType; - this.options = options.options || {}; - this.resource = options.resource; - this.split = options.split; + this.dataset = rdf.dataset() + this.url = options.url + this.cache = options.cache + this.contentType = options.contentType + this.options = options.options || {} + this.resource = options.resource + this.split = options.split // add file:// and resolve with cwd if no protocol was given if (this.url && !guessProtocol(this.url)) { - this.url = "file://" + path.resolve(this.url); + this.url = 'file://' + path.resolve(this.url) } - this.handle = this._handle.bind(this); + this.handle = this._handle.bind(this) // legacy interface - this.get = this._get.bind(this); + this.get = this._get.bind(this) } _handle(req, res, next) { rdfHandler .attach(req, res, { formats }) .then(() => { - return Fetcher.load(this.dataset, this); + return Fetcher.load(this.dataset, this) }) .then(async () => { const dataset = this.dataset.match( @@ -58,34 +58,34 @@ export class FetchHandler { null, null, rdf.namedNode(req.iri), - ); + ) if (dataset.size === 0) { - next(); - return null; + next() + return null } - await res.dataset(dataset); + await res.dataset(dataset) }) - .catch(next); + .catch(next) } // legacy interface _get(req, res, next, iri) { - req.iri = iri; + req.iri = iri - this.handle(req, res, next); + this.handle(req, res, next) } } const factory = (trifid) => { - const { config } = trifid; + const { config } = trifid - const handler = new FetchHandler(config); + const handler = new FetchHandler(config) return (req, res, next) => { - handler.handle(req, res, next); - }; -}; + handler.handle(req, res, next) + } +} -export default factory; +export default factory diff --git a/packages/handler-fetch/lib/Fetcher.js b/packages/handler-fetch/lib/Fetcher.js index 311dc86e..662acb60 100644 --- a/packages/handler-fetch/lib/Fetcher.js +++ b/packages/handler-fetch/lib/Fetcher.js @@ -1,77 +1,77 @@ -import fileFetch from "file-fetch"; -import protoFetch from "proto-fetch"; -import rdf from "rdf-ext"; -import rdfFetch from "@rdfjs/fetch"; -import splitIntoGraphs from "./spread/splitIntoGraphs.js"; +import fileFetch from 'file-fetch' +import protoFetch from 'proto-fetch' +import rdf from 'rdf-ext' +import rdfFetch from '@rdfjs/fetch' +import splitIntoGraphs from './spread/splitIntoGraphs.js' const fetch = protoFetch({ file: fileFetch, http: rdf.fetch, https: rdf.fetch, -}); +}) class Fetcher { static isCached(options) { - return options.cache && options.fetched; + return options.cache && options.fetched } static clearDataset(dataset, options) { if (!options.resources) { - return; + return } options.resources.forEach((resource) => { - dataset.deleteMatches(null, null, null, rdf.namedNode(resource)); - }); + dataset.deleteMatches(null, null, null, rdf.namedNode(resource)) + }) } static async fetchDataset(options) { - options.options = options.options || {}; - options.options.fetch = fetch; + options.options = options.options || {} + options.options.fetch = fetch const res = await rdfFetch(options.url, { ...options.options, factory: rdf, - }); + }) if (options.contentType) { - res.headers.set("content-type", options.contentType); + res.headers.set('content-type', options.contentType) } - options.fetched = new Date(); - return res.dataset(); + options.fetched = new Date() + return res.dataset() } static spreadDataset(inputDataset, outputDataset, options) { if (options.resource) { outputDataset.addAll( rdf.dataset(inputDataset, rdf.namedNode(options.resource)), - ); + ) } else if (options.split) { - outputDataset.addAll(splitIntoGraphs(inputDataset)); + outputDataset.addAll(splitIntoGraphs(inputDataset)) } else { - outputDataset.addAll(inputDataset); + outputDataset.addAll(inputDataset) } options.resources = Object.keys( [...outputDataset].reduce((resources, quad) => { - resources[quad.graph.value] = true; + resources[quad.graph.value] = true - return resources; + return resources }, {}), - ); + ) - return outputDataset; + return outputDataset } static async load(dataset, options) { if (Fetcher.isCached(options)) { - return Promise.resolve(); + return Promise.resolve() } - Fetcher.clearDataset(dataset, options); + Fetcher.clearDataset(dataset, options) - const input = await Fetcher.fetchDataset(options); - return Fetcher.spreadDataset(input, dataset, options); + const input = await Fetcher.fetchDataset(options) + return Fetcher.spreadDataset(input, dataset, options) } } -export default Fetcher; +export default Fetcher diff --git a/packages/handler-fetch/lib/spread/boundedDescriptionGraph.js b/packages/handler-fetch/lib/spread/boundedDescriptionGraph.js index 0e36bb89..02795abe 100644 --- a/packages/handler-fetch/lib/spread/boundedDescriptionGraph.js +++ b/packages/handler-fetch/lib/spread/boundedDescriptionGraph.js @@ -1,27 +1,27 @@ -import rdf from "rdf-ext"; +import rdf from 'rdf-ext' function boundedDescriptionGraph(inputDataset, subject) { - const input = inputDataset.clone(); + const input = inputDataset.clone() - const siblings = rdf.termSet(); + const siblings = rdf.termSet() input.forEach((quad) => { - if (quad.subject.value.split("#")[0] === subject.value.split("#")[0]) { - siblings.add(quad.subject); + if (quad.subject.value.split('#')[0] === subject.value.split('#')[0]) { + siblings.add(quad.subject) } - }); + }) const descriptionWithBlankNodes = rdf.traverser( ({ dataset, level, quad }) => - level === 0 || quad.subject.termType === "BlankNode", - ); + level === 0 || quad.subject.termType === 'BlankNode', + ) - const result = rdf.dataset(); + const result = rdf.dataset() siblings.forEach((subject) => { result.addAll( descriptionWithBlankNodes.match({ term: subject, dataset: input }), - ); - }); - return result; + ) + }) + return result } -export default boundedDescriptionGraph; +export default boundedDescriptionGraph diff --git a/packages/handler-fetch/lib/spread/splitIntoGraphs.js b/packages/handler-fetch/lib/spread/splitIntoGraphs.js index c26b77c0..5fb013bd 100644 --- a/packages/handler-fetch/lib/spread/splitIntoGraphs.js +++ b/packages/handler-fetch/lib/spread/splitIntoGraphs.js @@ -1,37 +1,37 @@ -import rdf from "rdf-ext"; -import boundedDescriptionGraph from "./boundedDescriptionGraph.js"; +import rdf from 'rdf-ext' +import boundedDescriptionGraph from './boundedDescriptionGraph.js' function splitIntoGraphs(inputDataset) { - const input = inputDataset.clone(); + const input = inputDataset.clone() - const result = rdf.dataset(); + const result = rdf.dataset() const allIRIs = [...input].reduce((iriSet, quad) => { - if (quad.subject.termType !== "NamedNode") { - return iriSet; + if (quad.subject.termType !== 'NamedNode') { + return iriSet } - iriSet.add(quad.subject.value.split("#")[0]); - return iriSet; - }, new Set()); + iriSet.add(quad.subject.value.split('#')[0]) + return iriSet + }, new Set()) allIRIs.forEach((resourceIRI) => { - const resourceNode = rdf.namedNode(resourceIRI); - const resourceTriples = boundedDescriptionGraph(input, resourceNode); + const resourceNode = rdf.namedNode(resourceIRI) + const resourceTriples = boundedDescriptionGraph(input, resourceNode) resourceTriples.forEach((triple) => { - if (triple.subject.termType !== "BlankNode") { - input.delete(triple); + if (triple.subject.termType !== 'BlankNode') { + input.delete(triple) } - }); + }) result.addAll( resourceTriples.map((quad) => rdf.quad(quad.subject, quad.predicate, quad.object, resourceNode), ), - ); - }); + ) + }) - return result; + return result } -export default splitIntoGraphs; +export default splitIntoGraphs diff --git a/packages/handler-fetch/test/Fetcher.js b/packages/handler-fetch/test/Fetcher.js index 7cfc884d..6bb57046 100644 --- a/packages/handler-fetch/test/Fetcher.js +++ b/packages/handler-fetch/test/Fetcher.js @@ -1,240 +1,240 @@ /* global describe, it */ -import assert from "assert"; -import fs from "fs"; -import { createRequire } from "module"; -import nock from "nock"; -import rdf from "rdf-ext"; -import Fetcher from "../lib/Fetcher.js"; +import assert from 'assert' +import fs from 'fs' +import { createRequire } from 'module' +import nock from 'nock' +import rdf from 'rdf-ext' +import Fetcher from '../lib/Fetcher.js' -const require = createRequire(import.meta.url); +const require = createRequire(import.meta.url) -describe("Fetcher", () => { - const fileUrlDataset = `file://${require.resolve("tbbt-ld/dist/tbbt.nq")}`; +describe('Fetcher', () => { + const fileUrlDataset = `file://${require.resolve('tbbt-ld/dist/tbbt.nq')}` - describe(".isCached", () => { - it("should be a method", () => { - assert.equal(typeof Fetcher.isCached, "function"); - }); + describe('.isCached', () => { + it('should be a method', () => { + assert.equal(typeof Fetcher.isCached, 'function') + }) - it("should return false if caching is not enabled", () => { - assert(!Fetcher.isCached({})); - }); + it('should return false if caching is not enabled', () => { + assert(!Fetcher.isCached({})) + }) - it("should return false if caching is enabled but fetched date is not set", () => { - assert(!Fetcher.isCached({ cache: true })); - }); + it('should return false if caching is enabled but fetched date is not set', () => { + assert(!Fetcher.isCached({ cache: true })) + }) - it("should return true if caching is enabled and fetched date is set", () => { + it('should return true if caching is enabled and fetched date is set', () => { assert( Fetcher.isCached({ cache: true, fetched: new Date(), }), - ); - }); - }); + ) + }) + }) - describe(".fetchDataset", () => { - it("should be a method", () => { - assert.equal(typeof Fetcher.fetchDataset, "function"); - }); + describe('.fetchDataset', () => { + it('should be a method', () => { + assert.equal(typeof Fetcher.fetchDataset, 'function') + }) - it("should load a dataset from a file URL", async () => { + it('should load a dataset from a file URL', async () => { const options = { url: fileUrlDataset, options: { contentTypeLookup: () => { - return "application/n-quads"; + return 'application/n-quads' }, }, - }; + } - const dataset = await Fetcher.fetchDataset(options); + const dataset = await Fetcher.fetchDataset(options) const graphs = Array.from(dataset).reduce((graph, quad) => { - graph[quad.graph.value] = true; - return graph; - }, {}); - assert(graphs["http://localhost:8080/data/person/amy-farrah-fowler"]); - assert(graphs["http://localhost:8080/data/person/sheldon-cooper"]); - }); + graph[quad.graph.value] = true + return graph + }, {}) + assert(graphs['http://localhost:8080/data/person/amy-farrah-fowler']) + assert(graphs['http://localhost:8080/data/person/sheldon-cooper']) + }) - it("should load a dataset from a http URL", async () => { - const content = fs.readFileSync(new URL(fileUrlDataset)); + it('should load a dataset from a http URL', async () => { + const content = fs.readFileSync(new URL(fileUrlDataset)) - nock("http://example.org").get("/dataset").reply(200, content, { - "content-type": "application/n-quads", - }); + nock('http://example.org').get('/dataset').reply(200, content, { + 'content-type': 'application/n-quads', + }) const options = { - url: "http://example.org/dataset", - }; + url: 'http://example.org/dataset', + } - const dataset = await Fetcher.fetchDataset(options); + const dataset = await Fetcher.fetchDataset(options) const graphs = Array.from(dataset).reduce((graph, quad) => { - graph[quad.graph.value] = true; - return graph; - }, {}); - assert(graphs["http://localhost:8080/data/person/amy-farrah-fowler"]); - assert(graphs["http://localhost:8080/data/person/sheldon-cooper"]); - }); - - it("should load a dataset from a http URL and use the given content type to parse it", async () => { - const content = fs.readFileSync(new URL(fileUrlDataset)); - - nock("http://example.org") - .get("/dataset-content-type") + graph[quad.graph.value] = true + return graph + }, {}) + assert(graphs['http://localhost:8080/data/person/amy-farrah-fowler']) + assert(graphs['http://localhost:8080/data/person/sheldon-cooper']) + }) + + it('should load a dataset from a http URL and use the given content type to parse it', async () => { + const content = fs.readFileSync(new URL(fileUrlDataset)) + + nock('http://example.org') + .get('/dataset-content-type') .reply(200, content, { - "content-type": "text/turtle", - }); + 'content-type': 'text/turtle', + }) const options = { - url: "http://example.org/dataset-content-type", - contentType: "application/n-quads", - }; + url: 'http://example.org/dataset-content-type', + contentType: 'application/n-quads', + } - const dataset = await Fetcher.fetchDataset(options); + const dataset = await Fetcher.fetchDataset(options) const graphs = Array.from(dataset).reduce((graph, quad) => { - graph[quad.graph.value] = true; - return graph; - }, {}); - assert(graphs["http://localhost:8080/data/person/amy-farrah-fowler"]); - assert(graphs["http://localhost:8080/data/person/sheldon-cooper"]); - }); - }); - - describe(".spreadDataset", () => { - it("should be a method", () => { - assert.equal(typeof Fetcher.spreadDataset, "function"); - }); - - it("should forward the dataset if no options are given", () => { + graph[quad.graph.value] = true + return graph + }, {}) + assert(graphs['http://localhost:8080/data/person/amy-farrah-fowler']) + assert(graphs['http://localhost:8080/data/person/sheldon-cooper']) + }) + }) + + describe('.spreadDataset', () => { + it('should be a method', () => { + assert.equal(typeof Fetcher.spreadDataset, 'function') + }) + + it('should forward the dataset if no options are given', () => { const input = rdf.dataset([ rdf.quad( - rdf.namedNode("http://example.org/subject1"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph"), + rdf.namedNode('http://example.org/subject1'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph'), ), rdf.quad( - rdf.namedNode("http://example.org/subject2"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph"), + rdf.namedNode('http://example.org/subject2'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph'), ), - ]); + ]) - const output = rdf.dataset(); + const output = rdf.dataset() - Fetcher.spreadDataset(input, output, {}); + Fetcher.spreadDataset(input, output, {}) - assert.equal(output.toCanonical(), input.toCanonical()); - }); + assert.equal(output.toCanonical(), input.toCanonical()) + }) - it("should load the triples into the given named node if resource is set", () => { - const resource = "http://example.org/resource"; + it('should load the triples into the given named node if resource is set', () => { + const resource = 'http://example.org/resource' const input = rdf.dataset([ rdf.quad( - rdf.namedNode("http://example.org/subject1"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph"), + rdf.namedNode('http://example.org/subject1'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph'), ), rdf.quad( - rdf.namedNode("http://example.org/subject2"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph"), + rdf.namedNode('http://example.org/subject2'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph'), ), - ]); + ]) - const output = rdf.dataset(); + const output = rdf.dataset() const expected = rdf.dataset([ rdf.quad( - rdf.namedNode("http://example.org/subject1"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), + rdf.namedNode('http://example.org/subject1'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), rdf.namedNode(resource), ), rdf.quad( - rdf.namedNode("http://example.org/subject2"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), + rdf.namedNode('http://example.org/subject2'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), rdf.namedNode(resource), ), - ]); + ]) - Fetcher.spreadDataset(input, output, { resource }); + Fetcher.spreadDataset(input, output, { resource }) - assert.equal(output.toCanonical(), expected.toCanonical()); - }); + assert.equal(output.toCanonical(), expected.toCanonical()) + }) - it("should split the dataset if split option is true", () => { + it('should split the dataset if split option is true', () => { const input = rdf.dataset([ rdf.quad( - rdf.namedNode("http://example.org/subject1"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph"), + rdf.namedNode('http://example.org/subject1'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph'), ), rdf.quad( - rdf.namedNode("http://example.org/subject2"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph"), + rdf.namedNode('http://example.org/subject2'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph'), ), - ]); + ]) - const output = rdf.dataset(); + const output = rdf.dataset() const expected = rdf.dataset([ rdf.quad( - rdf.namedNode("http://example.org/subject1"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/subject1"), + rdf.namedNode('http://example.org/subject1'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/subject1'), ), rdf.quad( - rdf.namedNode("http://example.org/subject2"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/subject2"), + rdf.namedNode('http://example.org/subject2'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/subject2'), ), - ]); + ]) - Fetcher.spreadDataset(input, output, { split: true }); + Fetcher.spreadDataset(input, output, { split: true }) - assert.equal(output.toCanonical(), expected.toCanonical()); - }); + assert.equal(output.toCanonical(), expected.toCanonical()) + }) - it("should assign an array of all resources to the options object", () => { + it('should assign an array of all resources to the options object', () => { const input = rdf.dataset([ rdf.quad( - rdf.namedNode("http://example.org/subject1"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph1"), + rdf.namedNode('http://example.org/subject1'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph1'), ), rdf.quad( - rdf.namedNode("http://example.org/subject2"), - rdf.namedNode("http://example.org/predicate"), - rdf.literal("object"), - rdf.namedNode("http://example.org/graph2"), + rdf.namedNode('http://example.org/subject2'), + rdf.namedNode('http://example.org/predicate'), + rdf.literal('object'), + rdf.namedNode('http://example.org/graph2'), ), - ]); + ]) - const output = rdf.dataset(); + const output = rdf.dataset() - const options = {}; + const options = {} - Fetcher.spreadDataset(input, output, options); + Fetcher.spreadDataset(input, output, options) assert.deepEqual(options.resources, [ - "http://example.org/graph1", - "http://example.org/graph2", - ]); - }); - }); -}); + 'http://example.org/graph1', + 'http://example.org/graph2', + ]) + }) + }) +}) diff --git a/packages/handler-fetch/test/index.js b/packages/handler-fetch/test/index.js index 33793728..3b93c4f5 100644 --- a/packages/handler-fetch/test/index.js +++ b/packages/handler-fetch/test/index.js @@ -1,283 +1,283 @@ /* global describe, it */ -import assert from "assert"; -import fs from "fs"; -import path, { dirname } from "path"; -import { fileURLToPath } from "url"; -import { createRequire } from "module"; -import Promise from "bluebird"; +import assert from 'assert' +import fs from 'fs' +import path, { dirname } from 'path' +import { fileURLToPath } from 'url' +import { createRequire } from 'module' +import Promise from 'bluebird' -import request from "supertest"; -import express from "express"; -import { FetchHandler as Handler } from "../index.js"; +import request from 'supertest' +import express from 'express' +import { FetchHandler as Handler } from '../index.js' -const require = createRequire(import.meta.url); -const __dirname = dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url) +const __dirname = dirname(fileURLToPath(import.meta.url)) -describe("trifid-handler-fetch", () => { - const fileUrlDataset = "file://" + require.resolve("tbbt-ld/dist/tbbt.nq"); +describe('trifid-handler-fetch', () => { + const fileUrlDataset = 'file://' + require.resolve('tbbt-ld/dist/tbbt.nq') const attachIri = (req, res, next) => { - req.iri = new URL(decodeURI(req.url), "http://localhost:8080/").href; + req.iri = new URL(decodeURI(req.url), 'http://localhost:8080/').href - next(); - }; + next() + } - it("should be a constructor", () => { - assert.equal(typeof Handler, "function"); - }); + it('should be a constructor', () => { + assert.equal(typeof Handler, 'function') + }) - it("should assign url option", () => { - const iri = "http://example.org/dataset"; + it('should assign url option', () => { + const iri = 'http://example.org/dataset' - const handler = new Handler({ url: iri }); + const handler = new Handler({ url: iri }) - assert.equal(handler.url, iri); - }); + assert.equal(handler.url, iri) + }) - it("should use file:// and resolve to cwd if no protocol was given", () => { - const handler = new Handler({ url: "test" }); + it('should use file:// and resolve to cwd if no protocol was given', () => { + const handler = new Handler({ url: 'test' }) - assert.equal(handler.url, "file://" + path.resolve("test")); - }); + assert.equal(handler.url, 'file://' + path.resolve('test')) + }) - it("should assign cache option", () => { - const handler = new Handler({ cache: "test" }); + it('should assign cache option', () => { + const handler = new Handler({ cache: 'test' }) - assert.equal(handler.cache, "test"); - }); + assert.equal(handler.cache, 'test') + }) - it("should assign contentType option", () => { - const handler = new Handler({ contentType: "test" }); + it('should assign contentType option', () => { + const handler = new Handler({ contentType: 'test' }) - assert.equal(handler.contentType, "test"); - }); + assert.equal(handler.contentType, 'test') + }) - it("should assign options option", () => { - const handler = new Handler({ options: "test" }); + it('should assign options option', () => { + const handler = new Handler({ options: 'test' }) - assert.equal(handler.options, "test"); - }); + assert.equal(handler.options, 'test') + }) - it("should assign resource option", () => { - const handler = new Handler({ resource: "test" }); + it('should assign resource option', () => { + const handler = new Handler({ resource: 'test' }) - assert.equal(handler.resource, "test"); - }); + assert.equal(handler.resource, 'test') + }) - it("should assign split option", () => { - const handler = new Handler({ split: "test" }); + it('should assign split option', () => { + const handler = new Handler({ split: 'test' }) - assert.equal(handler.split, "test"); - }); + assert.equal(handler.split, 'test') + }) - it("should implement the handler interface", () => { - const handler = new Handler({ url: fileUrlDataset }); + it('should implement the handler interface', () => { + const handler = new Handler({ url: fileUrlDataset }) - assert.equal(typeof handler.handle, "function"); - }); + assert.equal(typeof handler.handle, 'function') + }) - it("should implement the legacy handler interface", () => { - const handler = new Handler({ url: fileUrlDataset }); + it('should implement the legacy handler interface', () => { + const handler = new Handler({ url: fileUrlDataset }) - assert.equal(typeof handler.get, "function"); - }); + assert.equal(typeof handler.get, 'function') + }) - it("should send a response", async () => { + it('should send a response', async () => { const includeNt = - ""; + '' const excludeNt = - ""; + '' - const app = express(); + const app = express() const handler = new Handler({ url: fileUrlDataset, options: { contentTypeLookup: () => { - return "application/n-quads"; + return 'application/n-quads' }, }, - }); + }) - app.use(attachIri); - app.use(handler.handle); + app.use(attachIri) + app.use(handler.handle) const res = await request(app) - .get("/data/person/amy-farrah-fowler") - .set("accept", "text/turtle"); - const text = res.text.split(" ").join(""); - assert.equal(text.indexOf(includeNt) >= 0, true); - assert.equal(text.indexOf(excludeNt) >= 0, false); - }); + .get('/data/person/amy-farrah-fowler') + .set('accept', 'text/turtle') + const text = res.text.split(' ').join('') + assert.equal(text.indexOf(includeNt) >= 0, true) + assert.equal(text.indexOf(excludeNt) >= 0, false) + }) - it("should not process next middleware after sending content", async () => { - const app = express(); + it('should not process next middleware after sending content', async () => { + const app = express() const handler = new Handler({ url: fileUrlDataset, options: { - contentType: () => "application/n-quads", + contentType: () => 'application/n-quads', }, - }); + }) - let touched = false; + let touched = false - app.use(attachIri); - app.use(handler.handle); + app.use(attachIri) + app.use(handler.handle) app.use(() => { - touched = true; - }); + touched = true + }) await request(app) - .get("/data/person/amy-farrah-fowler") - .set("accept", "text/turtle"); - await Promise.delay(500); - assert(!touched); - }); + .get('/data/person/amy-farrah-fowler') + .set('accept', 'text/turtle') + await Promise.delay(500) + assert(!touched) + }) - it("retrieves JSON-LD responses", async () => { - const app = express(); + it('retrieves JSON-LD responses', async () => { + const app = express() const handler = new Handler({ url: fileUrlDataset, options: { contentTypeLookup: () => { - return "application/n-quads"; + return 'application/n-quads' }, }, - }); + }) - app.use(attachIri); - app.use(handler.handle); + app.use(attachIri) + app.use(handler.handle) const res = await request(app) - .get("/data/person/amy-farrah-fowler") - .set("accept", "application/ld+json"); - const jsonld = JSON.parse(res.text); - assert(Array.isArray(jsonld)); - assert(jsonld.length > 0); + .get('/data/person/amy-farrah-fowler') + .set('accept', 'application/ld+json') + const jsonld = JSON.parse(res.text) + assert(Array.isArray(jsonld)) + assert(jsonld.length > 0) assert.equal( - jsonld[0]["@id"], - "http://localhost:8080/data/person/amy-farrah-fowler", - ); - }); + jsonld[0]['@id'], + 'http://localhost:8080/data/person/amy-farrah-fowler', + ) + }) - it("should send a 404 response for unknown resources", () => { - const app = express(); + it('should send a 404 response for unknown resources', () => { + const app = express() const handler = new Handler({ url: fileUrlDataset, options: { contentTypeLookup: () => { - return "application/n-quads"; + return 'application/n-quads' }, }, - }); + }) - app.use(attachIri); - app.use(handler.handle); + app.use(attachIri) + app.use(handler.handle) return request(app) - .get("/data/person/dr-who") - .set("accept", "text/turtle") - .expect(404); - }); + .get('/data/person/dr-who') + .set('accept', 'text/turtle') + .expect(404) + }) - it("should cache the dataset if cache option is true", async () => { - const base = "http://localhost:8080"; - const fileUrl = `file://${path.join(__dirname, "test.nt")}`; + it('should cache the dataset if cache option is true', async () => { + const base = 'http://localhost:8080' + const fileUrl = `file://${path.join(__dirname, 'test.nt')}` - const datasetBefore = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject1> <${base}/predicate> "object1" .\n`; + const datasetBefore = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject1> <${base}/predicate> "object1" .\n` - const datasetAfter = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject0> <${base}/predicate> "object1" .\n`; + const datasetAfter = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject0> <${base}/predicate> "object1" .\n` - const app = express(); + const app = express() const handler = new Handler({ url: fileUrl, options: { contentTypeLookup: () => { - return "application/n-triples"; + return 'application/n-triples' }, }, cache: true, split: true, - }); + }) - app.use(attachIri); - app.use(handler.handle); + app.use(attachIri) + app.use(handler.handle) - fs.writeFileSync(new URL(fileUrl), datasetBefore); + fs.writeFileSync(new URL(fileUrl), datasetBefore) await request(app) - .get("/subject1") - .set("accept", "text/turtle") - .expect(200); - fs.writeFileSync(new URL(fileUrl), datasetAfter); + .get('/subject1') + .set('accept', 'text/turtle') + .expect(200) + fs.writeFileSync(new URL(fileUrl), datasetAfter) return await request(app) - .get("/subject1") - .set("accept", "text/turtle") - .expect(200); - }); + .get('/subject1') + .set('accept', 'text/turtle') + .expect(200) + }) - it("should not cache the dataset if cache options is not true", async () => { - const base = "http://localhost:8080"; - const fileUrl = `file://${path.join(__dirname, "test.nt")}`; + it('should not cache the dataset if cache options is not true', async () => { + const base = 'http://localhost:8080' + const fileUrl = `file://${path.join(__dirname, 'test.nt')}` - const datasetBefore = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject1> <${base}/predicate> "object1" .\n`; + const datasetBefore = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject1> <${base}/predicate> "object1" .\n` - const datasetAfter = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject0> <${base}/predicate> "object1" .\n`; + const datasetAfter = `<${base}/subject0> <${base}/predicate> "object0" .\n<${base}/subject0> <${base}/predicate> "object1" .\n` - const app = express(); + const app = express() const handler = new Handler({ url: fileUrl, options: { contentTypeLookup: () => { - return "application/n-triples"; + return 'application/n-triples' }, }, split: true, - }); + }) - app.use(attachIri); - app.use(handler.handle); + app.use(attachIri) + app.use(handler.handle) - fs.writeFileSync(new URL(fileUrl), datasetBefore); + fs.writeFileSync(new URL(fileUrl), datasetBefore) await request(app) - .get("/subject1") - .set("accept", "text/turtle") - .expect(200); - fs.writeFileSync(new URL(fileUrl), datasetAfter); + .get('/subject1') + .set('accept', 'text/turtle') + .expect(200) + fs.writeFileSync(new URL(fileUrl), datasetAfter) await request(app) - .get("/subject1") - .set("accept", "text/turtle") - .expect(404); - fs.unlinkSync(new URL(fileUrl)); - }); + .get('/subject1') + .set('accept', 'text/turtle') + .expect(404) + fs.unlinkSync(new URL(fileUrl)) + }) - it("should implement the legacy interface", () => { - const app = express(); + it('should implement the legacy interface', () => { + const app = express() const handler = new Handler({ url: fileUrlDataset, options: { contentTypeLookup: () => { - return "application/n-quads"; + return 'application/n-quads' }, }, - }); + }) - app.use(attachIri); + app.use(attachIri) app.use((req, res, next) => { - handler.get(req, res, next, req.iri); - }); + handler.get(req, res, next, req.iri) + }) return request(app) - .get("/data/person/amy-farrah-fowler") - .set("accept", "text/turtle") - .expect(200); - }); -}); + .get('/data/person/amy-farrah-fowler') + .set('accept', 'text/turtle') + .expect(200) + }) +}) diff --git a/packages/handler-fetch/test/spread/boundedDescriptionGraph.js b/packages/handler-fetch/test/spread/boundedDescriptionGraph.js index be58f922..da33732e 100644 --- a/packages/handler-fetch/test/spread/boundedDescriptionGraph.js +++ b/packages/handler-fetch/test/spread/boundedDescriptionGraph.js @@ -1,59 +1,59 @@ /* global describe, it */ -import assert from "assert"; -import rdf from "rdf-ext"; -import boundedDescriptionGraph from "../../lib/spread/boundedDescriptionGraph.js"; +import assert from 'assert' +import rdf from 'rdf-ext' +import boundedDescriptionGraph from '../../lib/spread/boundedDescriptionGraph.js' -const ex = rdf.namespace("http://example.org/"); +const ex = rdf.namespace('http://example.org/') -describe("resource", () => { - it("should create sub graph for a resource", () => { - const blankNode0 = rdf.blankNode(); - const blankNode1 = rdf.blankNode(); +describe('resource', () => { + it('should create sub graph for a resource', () => { + const blankNode0 = rdf.blankNode() + const blankNode1 = rdf.blankNode() const input = rdf.dataset([ rdf.quad(ex.node0, ex.predicate, blankNode0), rdf.quad(blankNode0, ex.predicate, ex.node1), rdf.quad(ex.node1, ex.predicate, blankNode1), - ]); + ]) - const output = boundedDescriptionGraph(input, ex.node0); + const output = boundedDescriptionGraph(input, ex.node0) const expected = rdf.dataset([ rdf.quad(ex.node0, ex.predicate, blankNode0), rdf.quad(blankNode0, ex.predicate, ex.node1), - ]); + ]) - assert.equal(output.toCanonical(), expected.toCanonical()); - }); + assert.equal(output.toCanonical(), expected.toCanonical()) + }) - it("should handle circular links", () => { - const blankNode = rdf.blankNode(); + it('should handle circular links', () => { + const blankNode = rdf.blankNode() const input = rdf.dataset([ rdf.quad(ex.node, ex.predicate, blankNode), rdf.quad(blankNode, ex.predicate, ex.node), - ]); + ]) - const output = boundedDescriptionGraph(input, ex.node); + const output = boundedDescriptionGraph(input, ex.node) - assert.equal(output.toCanonical(), input.toCanonical()); - }); + assert.equal(output.toCanonical(), input.toCanonical()) + }) - it("should ignore the fragment part of the subject", () => { - const blankNode = rdf.blankNode(); + it('should ignore the fragment part of the subject', () => { + const blankNode = rdf.blankNode() const input = rdf.dataset([ rdf.quad(ex.node, ex.predicate, blankNode), rdf.quad( - rdf.namedNode("http://example.org/node#fragment"), + rdf.namedNode('http://example.org/node#fragment'), ex.predicate, blankNode, ), - ]); + ]) - const output = boundedDescriptionGraph(input, ex.node); + const output = boundedDescriptionGraph(input, ex.node) - assert.equal(output.toCanonical(), input.toCanonical()); - }); -}); + assert.equal(output.toCanonical(), input.toCanonical()) + }) +}) diff --git a/packages/handler-fetch/test/spread/splitIntoGraphs.js b/packages/handler-fetch/test/spread/splitIntoGraphs.js index 116b4907..7d96287a 100644 --- a/packages/handler-fetch/test/spread/splitIntoGraphs.js +++ b/packages/handler-fetch/test/spread/splitIntoGraphs.js @@ -1,55 +1,55 @@ /* global describe, it */ -import assert from "assert"; -import rdf from "rdf-ext"; -import splitIntoGraphs from "../../lib/spread/splitIntoGraphs.js"; +import assert from 'assert' +import rdf from 'rdf-ext' +import splitIntoGraphs from '../../lib/spread/splitIntoGraphs.js' -const ex = rdf.namespace("http://example.org/"); +const ex = rdf.namespace('http://example.org/') -describe("resourcesToGraph", () => { - it("should split resources in separate graphs", () => { - const namedNode0 = rdf.namedNode("http://example.org/node0"); - const namedNode1 = rdf.namedNode("http://example.org/node1"); - const blankNode0 = rdf.blankNode(); - const blankNode1 = rdf.blankNode(); +describe('resourcesToGraph', () => { + it('should split resources in separate graphs', () => { + const namedNode0 = rdf.namedNode('http://example.org/node0') + const namedNode1 = rdf.namedNode('http://example.org/node1') + const blankNode0 = rdf.blankNode() + const blankNode1 = rdf.blankNode() const input = rdf.dataset([ rdf.quad(namedNode0, ex.predicate, blankNode0), rdf.quad(blankNode0, ex.predicate, namedNode1), rdf.quad(namedNode1, ex.predicate, blankNode1), - ]); + ]) - const output = splitIntoGraphs(input); + const output = splitIntoGraphs(input) const expected = rdf.dataset([ rdf.quad(namedNode0, ex.predicate, blankNode0, namedNode0), rdf.quad(blankNode0, ex.predicate, namedNode1, namedNode0), rdf.quad(namedNode1, ex.predicate, blankNode1, namedNode1), - ]); + ]) - assert.equal(output.toCanonical(), expected.toCanonical()); - }); + assert.equal(output.toCanonical(), expected.toCanonical()) + }) - it("should ignore the fragment part of the subject", () => { - const namedNode0 = rdf.namedNode("http://example.org/node"); - const namedNode1 = rdf.namedNode("http://example.org/node#fragment"); - const blankNode0 = rdf.blankNode(); - const blankNode1 = rdf.blankNode(); + it('should ignore the fragment part of the subject', () => { + const namedNode0 = rdf.namedNode('http://example.org/node') + const namedNode1 = rdf.namedNode('http://example.org/node#fragment') + const blankNode0 = rdf.blankNode() + const blankNode1 = rdf.blankNode() const input = rdf.dataset([ rdf.quad(namedNode0, ex.predicate, blankNode0), rdf.quad(blankNode0, ex.predicate, namedNode1), rdf.quad(namedNode1, ex.predicate, blankNode1), - ]); + ]) - const output = splitIntoGraphs(input); + const output = splitIntoGraphs(input) const expected = rdf.dataset([ rdf.quad(namedNode0, ex.predicate, blankNode0, namedNode0), rdf.quad(blankNode0, ex.predicate, namedNode1, namedNode0), rdf.quad(namedNode1, ex.predicate, blankNode1, namedNode0), - ]); + ]) - assert.equal(output.toCanonical(), expected.toCanonical()); - }); -}); + assert.equal(output.toCanonical(), expected.toCanonical()) + }) +}) diff --git a/packages/handler-sparql/index.js b/packages/handler-sparql/index.js index d2385767..5f106b23 100644 --- a/packages/handler-sparql/index.js +++ b/packages/handler-sparql/index.js @@ -1,39 +1,39 @@ -import debugLib from "debug"; -import ParsingClient from "sparql-http-client/ParsingClient.js"; -import SimpleClient from "sparql-http-client/SimpleClient.js"; +import debugLib from 'debug' +import ParsingClient from 'sparql-http-client/ParsingClient.js' +import SimpleClient from 'sparql-http-client/SimpleClient.js' -const debug = debugLib("trifid:handler-sparql"); +const debug = debugLib('trifid:handler-sparql') const defaults = { authentication: false, resourceNoSlash: true, - resourceExistsQuery: "ASK { <${iri}> ?p ?o }", // eslint-disable-line no-template-curly-in-string - resourceGraphQuery: "DESCRIBE <${iri}>", // eslint-disable-line no-template-curly-in-string + resourceExistsQuery: 'ASK { <${iri}> ?p ?o }', // eslint-disable-line no-template-curly-in-string + resourceGraphQuery: 'DESCRIBE <${iri}>', // eslint-disable-line no-template-curly-in-string containerExistsQuery: 'ASK { ?s a ?o. FILTER REGEX(STR(?s), "^${iri}") }', // eslint-disable-line no-template-curly-in-string containerGraphQuery: 'CONSTRUCT { ?s a ?o. } WHERE { ?s a ?o. FILTER REGEX(STR(?s), "^${iri}") }', // eslint-disable-line no-template-curly-in-string -}; +} const authBasicHeader = (user, password) => { - return "Basic " + Buffer.from(user + ":" + password).toString("base64"); -}; + return 'Basic ' + Buffer.from(user + ':' + password).toString('base64') +} export class SparqlHandler { constructor(options) { // eslint-disable-line - this.authentication = options.authentication; - this.resourceNoSlash = options.resourceNoSlash; - this.resourceExistsQuery = options.resourceExistsQuery; - this.resourceGraphQuery = options.resourceGraphQuery; - this.containerExistsQuery = options.containerExistsQuery; - this.containerGraphQuery = options.containerGraphQuery; + this.authentication = options.authentication + this.resourceNoSlash = options.resourceNoSlash + this.resourceExistsQuery = options.resourceExistsQuery + this.resourceGraphQuery = options.resourceGraphQuery + this.containerExistsQuery = options.containerExistsQuery + this.containerGraphQuery = options.containerGraphQuery this.parsingClient = new ParsingClient({ endpointUrl: options.endpointUrl, - }); - this.simpleClient = new SimpleClient({ endpointUrl: options.endpointUrl }); + }) + this.simpleClient = new SimpleClient({ endpointUrl: options.endpointUrl }) } buildQueryOptions() { - const queryOptions = {}; + const queryOptions = {} if ( this.authentication && @@ -45,153 +45,153 @@ export class SparqlHandler { this.authentication.user, this.authentication.password, ), - }; + } } - return queryOptions; + return queryOptions } buildResourceExistsQuery(iri) { - return this.resourceExistsQuery.split("${iri}").join(iri); // eslint-disable-line no-template-curly-in-string + return this.resourceExistsQuery.split('${iri}').join(iri) // eslint-disable-line no-template-curly-in-string } buildResourceGraphQuery(iri) { - return this.resourceGraphQuery.split("${iri}").join(iri); // eslint-disable-line no-template-curly-in-string + return this.resourceGraphQuery.split('${iri}').join(iri) // eslint-disable-line no-template-curly-in-string } buildContainerExistsQuery(iri) { - return this.containerExistsQuery.split("${iri}").join(iri); // eslint-disable-line no-template-curly-in-string + return this.containerExistsQuery.split('${iri}').join(iri) // eslint-disable-line no-template-curly-in-string } buildContainerGraphQuery(iri) { - return this.containerGraphQuery.split("${iri}").join(iri); // eslint-disable-line no-template-curly-in-string + return this.containerGraphQuery.split('${iri}').join(iri) // eslint-disable-line no-template-curly-in-string } async exists(iri, query) { - debug("SPARQL exists query for IRI <" + iri + "> : " + query); + debug('SPARQL exists query for IRI <' + iri + '> : ' + query) try { const exists = await this.parsingClient.query.ask( query, this.buildQueryOptions(), - ); - return { exists, status: 200 }; + ) + return { exists, status: 200 } } catch (error) { - return { status: error.status }; + return { status: error.status } } } async graphStream(iri, query, accept) { - debug("SPARQL query for IRI <" + iri + "> : " + query); + debug('SPARQL query for IRI <' + iri + '> : ' + query) - const headers = this.buildQueryOptions(); - headers.accept = accept; + const headers = this.buildQueryOptions() + headers.accept = accept - const res = await this.simpleClient.query.construct(query, { headers }); + const res = await this.simpleClient.query.construct(query, { headers }) res.headers.forEach((value, name) => { // stream will be decoded by the client -> remove content-encoding header - if (name === "content-encoding") { - return; + if (name === 'content-encoding') { + return } - headers[name] = value; - }); + headers[name] = value + }) return { status: res.status, headers: headers, stream: res.body, - }; + } } handle(req, res, next) { switch (req.method) { - case "HEAD": - return this.head(req, res, next, req.iri); - case "GET": - return this.get(req, res, next, req.iri); + case 'HEAD': + return this.head(req, res, next, req.iri) + case 'GET': + return this.get(req, res, next, req.iri) } - return next(); + return next() } async head(_req, res, next, iri) { - iri = encodeURI(iri); + iri = encodeURI(iri) - debug("handle HEAD request for IRI <" + iri + ">"); + debug('handle HEAD request for IRI <' + iri + '>') - const isContainer = this.resourceNoSlash && iri.endsWith("/"); + const isContainer = this.resourceNoSlash && iri.endsWith('/') const queryExist = isContainer ? this.buildContainerExistsQuery(iri) - : this.buildResourceExistsQuery(iri); + : this.buildResourceExistsQuery(iri) - const { status, exists } = await this.exists(iri, queryExist); + const { status, exists } = await this.exists(iri, queryExist) if (status !== 200) { - res.sendStatus(status); - return next(); + res.sendStatus(status) + return next() } else if (!exists) { - return next(); + return next() } - res.sendStatus(status); + res.sendStatus(status) } async get(req, res, next, iri) { - iri = encodeURI(iri); + iri = encodeURI(iri) - debug("handle GET request for IRI <" + iri + ">"); + debug('handle GET request for IRI <' + iri + '>') - const isContainer = this.resourceNoSlash && iri.endsWith("/"); + const isContainer = this.resourceNoSlash && iri.endsWith('/') const queryExist = isContainer ? this.buildContainerExistsQuery(iri) - : this.buildResourceExistsQuery(iri); + : this.buildResourceExistsQuery(iri) - const { status, exists } = await this.exists(iri, queryExist); + const { status, exists } = await this.exists(iri, queryExist) if (status !== 200) { - res.sendStatus(status); - return next(); + res.sendStatus(status) + return next() } else if (!exists) { - return next(); + return next() } else { const query = isContainer ? this.buildContainerGraphQuery(iri) - : this.buildResourceGraphQuery(iri); + : this.buildResourceGraphQuery(iri) const { status, headers, stream } = await this.graphStream( iri, query, req.headers.accept, - ); + ) if (!stream) { - return next(); + return next() } - res.status(status); + res.status(status) Object.keys(headers).forEach((name) => { - res.setHeader(name, headers[name]); - }); - stream.pipe(res); + res.setHeader(name, headers[name]) + }) + stream.pipe(res) } } } export const factory = (trifid) => { - const { config } = trifid; - const { endpointUrl } = config; + const { config } = trifid + const { endpointUrl } = config - const endpoint = endpointUrl || "/query"; + const endpoint = endpointUrl || '/query' return (req, res, next) => { const absoluteUrl = - res.locals.camouflageRewriteOriginalUrl || req.absoluteUrl(); - const endpointUrl = new URL(endpoint, absoluteUrl); + res.locals.camouflageRewriteOriginalUrl || req.absoluteUrl() + const endpointUrl = new URL(endpoint, absoluteUrl) const handler = new SparqlHandler({ ...defaults, ...config, endpointUrl: endpointUrl.toString(), - }); - handler.handle(req, res, next); - }; -}; + }) + handler.handle(req, res, next) + } +} -export default factory; +export default factory diff --git a/packages/handler-sparql/test/support/createEndpoint.js b/packages/handler-sparql/test/support/createEndpoint.js index dc21f26b..67fd48be 100644 --- a/packages/handler-sparql/test/support/createEndpoint.js +++ b/packages/handler-sparql/test/support/createEndpoint.js @@ -1,41 +1,41 @@ -import ExpressAsPromise from "express-as-promise"; +import ExpressAsPromise from 'express-as-promise' const createEndpoint = async (status = 200) => { - const server = new ExpressAsPromise(); + const server = new ExpressAsPromise() - server.requestHeaders = []; - server.queries = []; + server.requestHeaders = [] + server.queries = [] server.app.use((req, res, next) => { - const query = req.query.query; + const query = req.query.query - server.requestHeaders.push(req.headers); - server.queries.push(query); + server.requestHeaders.push(req.headers) + server.queries.push(query) - if (query.startsWith("ASK")) { + if (query.startsWith('ASK')) { return res .status(status) - .set("content-type", "application/sparql-results+json") - .json({ boolean: true }); + .set('content-type', 'application/sparql-results+json') + .json({ boolean: true }) } - if (query.startsWith("DESCRIBE") || query.startsWith("CONSTRUCT")) { - const body = "hello"; + if (query.startsWith('DESCRIBE') || query.startsWith('CONSTRUCT')) { + const body = 'hello' return res .writeHead(status, { - "Content-Length": Buffer.byteLength(body), - "Content-Type": req.headers.accept, - "content-encoding": "some encoding", + 'Content-Length': Buffer.byteLength(body), + 'Content-Type': req.headers.accept, + 'content-encoding': 'some encoding', }) - .end(body); + .end(body) } - next(); - }); + next() + }) - await server.listen(); + await server.listen() - return server; -}; + return server +} -export { createEndpoint }; +export { createEndpoint } diff --git a/packages/handler-sparql/test/support/setIri.js b/packages/handler-sparql/test/support/setIri.js index 7756a2de..7e7dc595 100644 --- a/packages/handler-sparql/test/support/setIri.js +++ b/packages/handler-sparql/test/support/setIri.js @@ -1,8 +1,8 @@ const setIri = (iri) => { return (req, _res, next) => { - req.iri = iri; - next(); - }; -}; + req.iri = iri + next() + } +} -export default setIri; +export default setIri diff --git a/packages/handler-sparql/test/test.js b/packages/handler-sparql/test/test.js index d3adf34e..f7e373cf 100644 --- a/packages/handler-sparql/test/test.js +++ b/packages/handler-sparql/test/test.js @@ -1,292 +1,292 @@ -import { strictEqual } from "assert"; -import withServer from "express-as-promise/withServer.js"; -import { describe, it } from "mocha"; -import { SparqlHandler } from "../index.js"; -import { createEndpoint } from "./support/createEndpoint.js"; -import setIri from "./support/setIri.js"; +import { strictEqual } from 'assert' +import withServer from 'express-as-promise/withServer.js' +import { describe, it } from 'mocha' +import { SparqlHandler } from '../index.js' +import { createEndpoint } from './support/createEndpoint.js' +import setIri from './support/setIri.js' /* eslint-disable no-template-curly-in-string */ const defaults = { - resourceExistsQuery: "ASK { <${iri}> ?p ?o }", - resourceGraphQuery: "DESCRIBE <${iri}>", + resourceExistsQuery: 'ASK { <${iri}> ?p ?o }', + resourceGraphQuery: 'DESCRIBE <${iri}>', containerExistsQuery: 'ASK { ?s a ?o. FILTER REGEX(STR(?s), "^${iri}") }', containerGraphQuery: 'CONSTRUCT { ?s a ?o. } WHERE { ?s a ?o. FILTER REGEX(STR(?s), "^${iri}") }', -}; +} /* eslint-enable no-template-curly-in-string */ -describe("trifid-handler-sparql", () => { - it("should be a constructor", () => { - strictEqual(typeof SparqlHandler, "function"); - }); +describe('trifid-handler-sparql', () => { + it('should be a constructor', () => { + strictEqual(typeof SparqlHandler, 'function') + }) - describe("uses the resourceExistsQuery to check if the requested IRI exists", async () => { + describe('uses the resourceExistsQuery to check if the requested IRI exists', async () => { [ - { iri: "http://localhost/test", resourceNoSlash: undefined }, - { iri: "http://localhost/test/", resourceNoSlash: undefined }, - { iri: "http://localhost/test", resourceNoSlash: true }, + { iri: 'http://localhost/test', resourceNoSlash: undefined }, + { iri: 'http://localhost/test/', resourceNoSlash: undefined }, + { iri: 'http://localhost/test', resourceNoSlash: true }, ].forEach((input) => { it(`for IRI ${input.iri}, resourceNoSlash:${input.resourceNoSlash}`, async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, resourceNoSlash: input.resourceNoSlash, - }); + }) - server.app.use(setIri(input.iri)); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri(input.iri)) + server.app.use(handler.handle.bind(handler)) - await server.fetch("/test"); - await endpoint.stop(); + await server.fetch('/test') + await endpoint.stop() - strictEqual(endpoint.queries[0], `ASK { <${input.iri}> ?p ?o }`); - }); - }); - }); - }); + strictEqual(endpoint.queries[0], `ASK { <${input.iri}> ?p ?o }`) + }) + }) + }) + }) - describe("uses the resourceGraphQuery to query the full resource", async () => { + describe('uses the resourceGraphQuery to query the full resource', async () => { [ - { iri: "http://localhost/test", resourceNoSlash: undefined }, - { iri: "http://localhost/test/", resourceNoSlash: undefined }, - { iri: "http://localhost/test", resourceNoSlash: true }, + { iri: 'http://localhost/test', resourceNoSlash: undefined }, + { iri: 'http://localhost/test/', resourceNoSlash: undefined }, + { iri: 'http://localhost/test', resourceNoSlash: true }, ].forEach((input) => { it(`for IRI ${input.iri}, resourceNoSlash:${input.resourceNoSlash}`, async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, resourceNoSlash: input.resourceNoSlash, - }); + }) - server.app.use(setIri(input.iri)); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri(input.iri)) + server.app.use(handler.handle.bind(handler)) - await server.fetch("/test"); - await endpoint.stop(); + await server.fetch('/test') + await endpoint.stop() - strictEqual(endpoint.queries[1], `DESCRIBE <${input.iri}>`); - }); - }); - }); - }); + strictEqual(endpoint.queries[1], `DESCRIBE <${input.iri}>`) + }) + }) + }) + }) - describe("uses containerExistsQuery and containerGraphQuery", async () => { - [{ iri: "http://localhost/test/", resourceNoSlash: true }].forEach( + describe('uses containerExistsQuery and containerGraphQuery', async () => { + [{ iri: 'http://localhost/test/', resourceNoSlash: true }].forEach( (input) => { it(`for IRI ${input.iri}, resourceNoSlash:${input.resourceNoSlash}`, async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, resourceNoSlash: input.resourceNoSlash, - }); + }) - server.app.use(setIri(input.iri)); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri(input.iri)) + server.app.use(handler.handle.bind(handler)) - await server.fetch("/test"); - await endpoint.stop(); + await server.fetch('/test') + await endpoint.stop() strictEqual( endpoint.queries[0], `ASK { ?s a ?o. FILTER REGEX(STR(?s), "^${input.iri}") }`, - ); + ) strictEqual( endpoint.queries[1], `CONSTRUCT { ?s a ?o. } WHERE { ?s a ?o. FILTER REGEX(STR(?s), "^${input.iri}") }`, - ); - }); - }); + ) + }) + }) }, - ); - }); + ) + }) - it("should escape the IRI in resourceExistsQuery", async () => { + it('should escape the IRI in resourceExistsQuery', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, - }); + }) - server.app.use(setIri("http://localhost/test ?p ?o }", - ); - }); - }); + 'ASK { ?p ?o }', + ) + }) + }) - it("should escape the IRI in resourceGraphQuery", async () => { + it('should escape the IRI in resourceGraphQuery', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, - }); + }) - server.app.use(setIri("http://localhost/test", - ); - }); - }); + 'DESCRIBE ', + ) + }) + }) - it("should escape the IRI in containerExistsQuery", async () => { + it('should escape the IRI in containerExistsQuery', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, resourceNoSlash: true, - }); + }) - server.app.use(setIri("http://localhost/test { + it('should escape the IRI in containerGraphQuery', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, resourceNoSlash: true, - }); + }) - server.app.use(setIri("http://localhost/test { + it('should add auth headers to request', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const authentication = { - user: "bob", - password: "password", - }; + user: 'bob', + password: 'password', + } const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, authentication: authentication, - }); + }) - server.app.use(setIri("http://localhost/test")); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri('http://localhost/test')) + server.app.use(handler.handle.bind(handler)) - await server.fetch("/test"); - await endpoint.stop(); + await server.fetch('/test') + await endpoint.stop() strictEqual( endpoint.requestHeaders[0].authorization, - "Basic Ym9iOnBhc3N3b3Jk", - ); - }); - }); + 'Basic Ym9iOnBhc3N3b3Jk', + ) + }) + }) - it("should use accept header", async () => { + it('should use accept header', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, - }); + }) - server.app.use(setIri("http://localhost/test")); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri('http://localhost/test')) + server.app.use(handler.handle.bind(handler)) - await server.fetch("/test", { headers: { accept: "format" } }); - await endpoint.stop(); + await server.fetch('/test', { headers: { accept: 'format' } }) + await endpoint.stop() - strictEqual(endpoint.requestHeaders[1].accept, "format"); - }); - }); + strictEqual(endpoint.requestHeaders[1].accept, 'format') + }) + }) - it("Returns a stream and clears content encoding", async () => { + it('Returns a stream and clears content encoding', async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(); + const endpoint = await createEndpoint() const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, - }); + }) - server.app.use(setIri("http://localhost/test")); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri('http://localhost/test')) + server.app.use(handler.handle.bind(handler)) - const res = await server.fetch("/test", { - headers: { accept: "format" }, - }); - await endpoint.stop(); - strictEqual(res.headers["content-encoding"], undefined); - }); - }); + const res = await server.fetch('/test', { + headers: { accept: 'format' }, + }) + await endpoint.stop() + strictEqual(res.headers['content-encoding'], undefined) + }) + }) - describe("Endpoint status codes forwarded", async () => { + describe('Endpoint status codes forwarded', async () => { [400, 401, 403, 405, 415, 444, 500, 501, 502, 503, 511].forEach( (status) => { it(`for status ${status}`, async () => { await withServer(async (server) => { - const endpoint = await createEndpoint(status); + const endpoint = await createEndpoint(status) const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, - }); + }) - server.app.use(setIri("http://localhost/test")); - server.app.use(handler.handle.bind(handler)); + server.app.use(setIri('http://localhost/test')) + server.app.use(handler.handle.bind(handler)) - const res = await server.fetch("/test"); - await endpoint.stop(); - strictEqual(res.status, status); - }); - }); + const res = await server.fetch('/test') + await endpoint.stop() + strictEqual(res.status, status) + }) + }) }, - ); - }); -}); + ) + }) +}) diff --git a/packages/i18n/index.js b/packages/i18n/index.js index 9abc658d..3205dab2 100644 --- a/packages/i18n/index.js +++ b/packages/i18n/index.js @@ -1,65 +1,65 @@ -import cookieParser from "cookie-parser"; -import i18n from "i18n"; -import express from "express"; +import cookieParser from 'cookie-parser' +import i18n from 'i18n' +import express from 'express' -const { configure: i18nConfigure, init: i18nInit } = i18n; +const { configure: i18nConfigure, init: i18nInit } = i18n const defaults = { - cookie: "i18n", - queryParameter: "lang", - directory: "locales", + cookie: 'i18n', + queryParameter: 'lang', + directory: 'locales', api: { - __: "t", - __n: "tn", + __: 't', + __n: 'tn', }, cookieMaxAge: 30 * 24 * 60 * 60 * 1000, -}; +} export const middleware = (config) => { - config = { ...defaults, ...config }; + config = { ...defaults, ...config } - const middlewareRouter = express.Router(); + const middlewareRouter = express.Router() - i18nConfigure(config); + i18nConfigure(config) middlewareRouter.use(cookieParser(), i18nInit, (req, res, next) => { if (req.cookies.i18n !== res.locals.locale) { res.cookie(config.cookie, res.locals.locale, { maxAge: config.cookieMaxAge, - }); + }) } - next(); - }); + next() + }) - return middlewareRouter; -}; + return middlewareRouter +} const factory = (trifid) => { - const { config, registerTemplateHelper } = trifid; + const { config, registerTemplateHelper } = trifid // Force user to define the `directory` parameter - if (!config.directory || typeof config.directory !== "string") { + if (!config.directory || typeof config.directory !== 'string') { throw new Error( "The 'directory' configuration field should be a non-empty string.", - ); + ) } // Use the middleware - trifid.server.use(middleware(config)); + trifid.server.use(middleware(config)) // Register the 'i18n' helper for the template engine return (_req, res, next) => { - registerTemplateHelper("i18n", (value) => { - if (!res.locals.t || typeof res.locals.t !== "function") { - return value; + registerTemplateHelper('i18n', (value) => { + if (!res.locals.t || typeof res.locals.t !== 'function') { + return value } - return res.locals.t(value); - }); + return res.locals.t(value) + }) - next(); - }; -}; + next() + } +} -export default factory; +export default factory diff --git a/packages/i18n/test/support/withServer.js b/packages/i18n/test/support/withServer.js index 80a088b4..0449e20a 100644 --- a/packages/i18n/test/support/withServer.js +++ b/packages/i18n/test/support/withServer.js @@ -1,22 +1,22 @@ -import ExpressAsPromise from "express-as-promise"; +import ExpressAsPromise from 'express-as-promise' const withServer = async (callback) => { - let error = null; - const server = new ExpressAsPromise(); + let error = null + const server = new ExpressAsPromise() try { - await callback(server); + await callback(server) } catch (err) { - error = err; + error = err } if (server.server) { - await server.stop(); + await server.stop() } if (error) { - throw error; + throw error } -}; +} -export default withServer; +export default withServer diff --git a/packages/i18n/test/test.js b/packages/i18n/test/test.js index 262d6c73..d455be50 100644 --- a/packages/i18n/test/test.js +++ b/packages/i18n/test/test.js @@ -1,166 +1,166 @@ -import { strictEqual, throws } from "assert"; -import { dirname, resolve } from "path"; -import { fileURLToPath, URL } from "url"; -import fetch from "nodeify-fetch"; -import { describe, it } from "mocha"; -import factory, { middleware as trifidPluginI18n } from "../index.js"; -import withServer from "./support/withServer.js"; - -const currentDir = dirname(fileURLToPath(import.meta.url)); - -describe("trifid-plugin-i18n", () => { - it("should be a function", () => { - strictEqual(typeof trifidPluginI18n, "function"); - }); - - it("should add the .t method to to res to translate a string", async () => { +import { strictEqual, throws } from 'assert' +import { dirname, resolve } from 'path' +import { fileURLToPath, URL } from 'url' +import fetch from 'nodeify-fetch' +import { describe, it } from 'mocha' +import factory, { middleware as trifidPluginI18n } from '../index.js' +import withServer from './support/withServer.js' + +const currentDir = dirname(fileURLToPath(import.meta.url)) + +describe('trifid-plugin-i18n', () => { + it('should be a function', () => { + strictEqual(typeof trifidPluginI18n, 'function') + }) + + it('should add the .t method to to res to translate a string', async () => { await withServer(async (server) => { const middleware = trifidPluginI18n({ - locales: ["en", "de"], - defaultLocale: "en", - directory: resolve(currentDir, "support/locales"), - }); - server.app.use(middleware); + locales: ['en', 'de'], + defaultLocale: 'en', + directory: resolve(currentDir, 'support/locales'), + }) + server.app.use(middleware) - let t = null; + let t = null - server.app.get("/", (_req, res, next) => { - t = res.t; + server.app.get('/', (_req, res, next) => { + t = res.t - next(); - }); + next() + }) - const baseUrl = await server.listen(); - await (await fetch(baseUrl)).text(); + const baseUrl = await server.listen() + await (await fetch(baseUrl)).text() - strictEqual(typeof t, "function"); - }); - }); + strictEqual(typeof t, 'function') + }) + }) - it("should translate the string in the default language", async () => { + it('should translate the string in the default language', async () => { await withServer(async (server) => { const middleware = trifidPluginI18n({ - locales: ["en", "de"], - defaultLocale: "en", - directory: resolve(currentDir, "support/locales"), - }); - server.app.use(middleware); + locales: ['en', 'de'], + defaultLocale: 'en', + directory: resolve(currentDir, 'support/locales'), + }) + server.app.use(middleware) - server.app.get("/", (_req, res) => { - res.end(`${res.t("test")}`); - }); + server.app.get('/', (_req, res) => { + res.end(`${res.t('test')}`) + }) - const baseUrl = await server.listen(); - const content = await (await fetch(baseUrl)).text(); + const baseUrl = await server.listen() + const content = await (await fetch(baseUrl)).text() - strictEqual(content, "test-en"); - }); - }); + strictEqual(content, 'test-en') + }) + }) - it("should translate the string in the language given as query parameter", async () => { + it('should translate the string in the language given as query parameter', async () => { await withServer(async (server) => { const middleware = trifidPluginI18n({ - locales: ["en", "de"], - defaultLocale: "en", - directory: resolve(currentDir, "support/locales"), - }); - server.app.use(middleware); + locales: ['en', 'de'], + defaultLocale: 'en', + directory: resolve(currentDir, 'support/locales'), + }) + server.app.use(middleware) - server.app.get("/", (_req, res) => { - res.end(`${res.t("test")}`); - }); + server.app.get('/', (_req, res) => { + res.end(`${res.t('test')}`) + }) - const baseUrl = new URL(await server.listen()); - baseUrl.searchParams.append("lang", "de"); + const baseUrl = new URL(await server.listen()) + baseUrl.searchParams.append('lang', 'de') - const content = await (await fetch(baseUrl)).text(); + const content = await (await fetch(baseUrl)).text() - strictEqual(content, "test-de"); - }); - }); + strictEqual(content, 'test-de') + }) + }) - it("should translate the string in the language given as cookie", async () => { + it('should translate the string in the language given as cookie', async () => { await withServer(async (server) => { const middleware = trifidPluginI18n({ - locales: ["en", "de"], - defaultLocale: "en", - directory: resolve(currentDir, "support/locales"), - }); - server.app.use(middleware); + locales: ['en', 'de'], + defaultLocale: 'en', + directory: resolve(currentDir, 'support/locales'), + }) + server.app.use(middleware) - server.app.get("/", (_req, res) => { - res.end(`${res.t("test")}`); - }); + server.app.get('/', (_req, res) => { + res.end(`${res.t('test')}`) + }) - const baseUrl = await server.listen(); + const baseUrl = await server.listen() const content = await ( await fetch(baseUrl, { headers: { - cookie: "i18n=de", + cookie: 'i18n=de', }, }) - ).text(); + ).text() - strictEqual(content, "test-de"); - }); - }); + strictEqual(content, 'test-de') + }) + }) - it("should send a cookie if the language changed", async () => { + it('should send a cookie if the language changed', async () => { await withServer(async (server) => { const middleware = trifidPluginI18n({ - locales: ["en", "de"], - defaultLocale: "en", - directory: resolve(currentDir, "support/locales"), - }); - server.app.use(middleware); + locales: ['en', 'de'], + defaultLocale: 'en', + directory: resolve(currentDir, 'support/locales'), + }) + server.app.use(middleware) - const baseUrl = new URL(await server.listen()); - baseUrl.searchParams.append("lang", "de"); + const baseUrl = new URL(await server.listen()) + baseUrl.searchParams.append('lang', 'de') - const res = await fetch(baseUrl); + const res = await fetch(baseUrl) - strictEqual(res.headers.get("set-cookie").startsWith("i18n=de"), true); - }); - }); -}); + strictEqual(res.headers.get('set-cookie').startsWith('i18n=de'), true) + }) + }) +}) -describe("Trifid factory", () => { - it("should be a function", () => { - strictEqual(typeof factory, "function"); - }); +describe('Trifid factory', () => { + it('should be a function', () => { + strictEqual(typeof factory, 'function') + }) - it("should throw if no directory is defined", async () => { + it('should throw if no directory is defined', async () => { await withServer(async (server) => { throws(() => factory({ config: { - locales: ["en", "de"], - defaultLocale: "en", + locales: ['en', 'de'], + defaultLocale: 'en', }, }), - ); - }); - }); + ) + }) + }) - it("should work as expected", async () => { + it('should work as expected', async () => { await withServer(async (server) => { const middleware = factory({ registerTemplateHelper: (_name, _fn) => {}, server: server.app, config: { - locales: ["en", "de"], - defaultLocale: "en", - directory: resolve(currentDir, "support/locales"), + locales: ['en', 'de'], + defaultLocale: 'en', + directory: resolve(currentDir, 'support/locales'), }, - }); - server.app.use(middleware); + }) + server.app.use(middleware) - const baseUrl = new URL(await server.listen()); - baseUrl.searchParams.append("lang", "de"); + const baseUrl = new URL(await server.listen()) + baseUrl.searchParams.append('lang', 'de') - const res = await fetch(baseUrl); + const res = await fetch(baseUrl) - strictEqual(res.headers.get("set-cookie").startsWith("i18n=de"), true); - }); - }); -}); + strictEqual(res.headers.get('set-cookie').startsWith('i18n=de'), true) + }) + }) +}) diff --git a/packages/sparql-proxy/index.js b/packages/sparql-proxy/index.js index 3e7f54a0..f60133d2 100644 --- a/packages/sparql-proxy/index.js +++ b/packages/sparql-proxy/index.js @@ -1,8 +1,8 @@ -import sparqlProxy from "@zazuko/sparql-proxy"; -import { ProxyAgent } from "proxy-agent"; +import sparqlProxy from '@zazuko/sparql-proxy' +import { ProxyAgent } from 'proxy-agent' const factory = (trifid) => { - const { config } = trifid; + const { config } = trifid const { endpointUrl: _e, // ignore this field @@ -10,33 +10,33 @@ const factory = (trifid) => { enableProxy, // enable/disable the support for `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables sparqlEndpoint, // get the configuration about the endpoint ...proxyConfig // rest of the configuration - } = config; + } = config if (sparqlEndpoint) { - if (Object.hasOwnProperty.call(sparqlEndpoint, "url")) { - proxyConfig.endpointUrl = sparqlEndpoint.url; + if (Object.hasOwnProperty.call(sparqlEndpoint, 'url')) { + proxyConfig.endpointUrl = sparqlEndpoint.url } const hasProperties = - Object.hasOwnProperty.call(sparqlEndpoint, "username") && - Object.hasOwnProperty.call(sparqlEndpoint, "password"); + Object.hasOwnProperty.call(sparqlEndpoint, 'username') && + Object.hasOwnProperty.call(sparqlEndpoint, 'password') if ( hasProperties && - sparqlEndpoint.username !== "" && - sparqlEndpoint.password !== "" + sparqlEndpoint.username !== '' && + sparqlEndpoint.password !== '' ) { proxyConfig.authentication = { user: sparqlEndpoint.username, password: sparqlEndpoint.password, - }; + } } } - if (enableProxy && enableProxy !== "false") { - proxyConfig.agent = new ProxyAgent(); + if (enableProxy && enableProxy !== 'false') { + proxyConfig.agent = new ProxyAgent() } - return sparqlProxy(proxyConfig); -}; + return sparqlProxy(proxyConfig) +} -export default factory; +export default factory diff --git a/packages/trifid/middlewares/morgan.js b/packages/trifid/middlewares/morgan.js index ee76d4a9..873b99a8 100644 --- a/packages/trifid/middlewares/morgan.js +++ b/packages/trifid/middlewares/morgan.js @@ -1,4 +1,4 @@ -import morgan from "morgan"; +import morgan from 'morgan' /** * Log requests. @@ -12,17 +12,17 @@ import morgan from "morgan"; * @returns Express middleware. */ const factory = (trifid) => { - const { config } = trifid; - const format = config.format ?? "combined"; - const disabled = `${config.disabled}` === "true"; + const { config } = trifid + const format = config.format ?? 'combined' + const disabled = `${config.disabled}` === 'true' if (disabled) { return (_req, _res, next) => { - next(); - }; + next() + } } - return morgan(format); -}; + return morgan(format) +} -export default factory; +export default factory diff --git a/packages/trifid/server.js b/packages/trifid/server.js index 1842c85f..f8d110b4 100755 --- a/packages/trifid/server.js +++ b/packages/trifid/server.js @@ -1,26 +1,26 @@ #!/usr/bin/env node -import { join } from "path"; -import { Command } from "commander"; +import { join } from 'path' +import { Command } from 'commander' -import trifid from "trifid-core"; +import trifid from 'trifid-core' -const program = new Command(); +const program = new Command() -const defaultConfigurationFile = process.env.TRIFID_CONFIG ?? "config.yaml"; +const defaultConfigurationFile = process.env.TRIFID_CONFIG ?? 'config.yaml' program - .option("-c, --config ", "configuration file", defaultConfigurationFile) - .option("-p, --port ", "listener port", parseInt) - .option("--sparql-endpoint-url ", "SPARQL endpoint URL") + .option('-c, --config ', 'configuration file', defaultConfigurationFile) + .option('-p, --port ', 'listener port', parseInt) + .option('--sparql-endpoint-url ', 'SPARQL endpoint URL') .option( - "--dataset-base-url ", - "the base URL of the dataset to enable rewriting", + '--dataset-base-url ', + 'the base URL of the dataset to enable rewriting', ) - .parse(process.argv); + .parse(process.argv) -const opts = program.opts(); -const configFile = join(process.cwd(), opts.config); +const opts = program.opts() +const configFile = join(process.cwd(), opts.config) // create a minimal configuration that extends the specified one const config = { @@ -29,21 +29,21 @@ const config = { server: { listener: {}, }, -}; +} // add optional arguments to the configuration if (opts.port) { - config.server.listener.port = opts.port; + config.server.listener.port = opts.port } if (opts.sparqlEndpointUrl) { config.globals.sparqlEndpoint = { url: opts.sparqlEndpointUrl, - }; + } } if (opts.datasetBaseUrl) { - config.globals.datasetBaseUrl = opts.datasetBaseUrl; + config.globals.datasetBaseUrl = opts.datasetBaseUrl } // load the configuration and start the server -const instance = await trifid(config); -instance.start(); +const instance = await trifid(config) +instance.start() diff --git a/packages/yasgui/index.js b/packages/yasgui/index.js index 2ee44817..360df87a 100644 --- a/packages/yasgui/index.js +++ b/packages/yasgui/index.js @@ -1,35 +1,35 @@ -import url, { fileURLToPath } from "url"; -import { dirname } from "path"; -import express from "express"; -import { resolve } from "import-meta-resolve"; +import url, { fileURLToPath } from 'url' +import { dirname } from 'path' +import express from 'express' +import { resolve } from 'import-meta-resolve' -const currentDir = dirname(fileURLToPath(import.meta.url)); +const currentDir = dirname(fileURLToPath(import.meta.url)) const trifidFactory = async (trifid) => { - const { config, logger, render, server } = trifid; - const { template, endpointUrl, urlShortener } = config; + const { config, logger, render, server } = trifid + const { template, endpointUrl, urlShortener } = config - const endpoint = endpointUrl || "/query"; - const view = !template ? `${currentDir}/views/yasgui.hbs` : template; + const endpoint = endpointUrl || '/query' + const view = !template ? `${currentDir}/views/yasgui.hbs` : template // serve static files for YASGUI - const yasguiPath = resolve("@zazuko/yasgui/build/", import.meta.url); + const yasguiPath = resolve('@zazuko/yasgui/build/', import.meta.url) server.use( - "/yasgui-dist/", - express.static(yasguiPath.replace(/^file:\/\//, "")), - ); + '/yasgui-dist/', + express.static(yasguiPath.replace(/^file:\/\//, '')), + ) // serve static files for openlayers (maps) - const olPath = resolve("@openlayers-elements/bundle/dist/", import.meta.url); - server.use("/yasgui-ol/", express.static(olPath.replace(/^file:\/\//, ""))); + const olPath = resolve('@openlayers-elements/bundle/dist/', import.meta.url) + server.use('/yasgui-ol/', express.static(olPath.replace(/^file:\/\//, ''))) // serve static files for custom plugins - const pluginsUrl = new URL("plugins/", import.meta.url); - const pluginsPath = fileURLToPath(pluginsUrl); - server.use("/yasgui-plugins/", express.static(pluginsPath)); + const pluginsUrl = new URL('plugins/', import.meta.url) + const pluginsPath = fileURLToPath(pluginsUrl) + server.use('/yasgui-plugins/', express.static(pluginsPath)) return async (req, res, _next) => { - logger.debug("Yasgui plugin was called"); + logger.debug('Yasgui plugin was called') const content = await render( view, @@ -39,11 +39,11 @@ const trifidFactory = async (trifid) => { urlShortener, locals: res.locals, }, - { title: "YASGUI" }, - ); + { title: 'YASGUI' }, + ) - res.send(content); - }; -}; + res.send(content) + } +} -export default trifidFactory; +export default trifidFactory diff --git a/packages/yasgui/plugins/map.js b/packages/yasgui/plugins/map.js index d2edee63..fec83b57 100644 --- a/packages/yasgui/plugins/map.js +++ b/packages/yasgui/plugins/map.js @@ -1,13 +1,13 @@ /* global Yasr */ class YasguiMap { - priority = 10; + priority = 10 - hideFromSelection = false; + hideFromSelection = false // eslint-disable-next-line space-before-function-paren constructor(yasr) { - this.yasr = yasr; + this.yasr = yasr } getResults() { @@ -18,78 +18,78 @@ class YasguiMap { !this.yasr.results.json.results || !this.yasr.results.json.results.bindings ) { - return []; + return [] } - const results = this.yasr.results.json.results.bindings; + const results = this.yasr.results.json.results.bindings if (results.length < 1) { - return []; + return [] } - const wktData = []; + const wktData = [] // eslint-disable-next-line array-callback-return results.map((result) => { if (!result) { - return null; + return null } // eslint-disable-next-line array-callback-return Object.entries(result).map((entry) => { if (!entry[1]) { - return null; + return null } - const value = entry[1]; - if (!value.type || value.type !== "literal") { - return null; + const value = entry[1] + if (!value.type || value.type !== 'literal') { + return null } if ( !value.datatype || - value.datatype !== "http://www.opengis.net/ont/geosparql#wktLiteral" + value.datatype !== 'http://www.opengis.net/ont/geosparql#wktLiteral' ) { - return null; + return null } if (!value.value) { - return null; + return null } wktData.push({ id: `results-map-wkt-${entry[0]}`, wkt: value.value, - }); - }); - }); + }) + }) + }) - return wktData; + return wktData } draw() { - const results = this.getResults(); - const el = document.createElement("ol-map"); - const osm = document.createElement("ol-layer-openstreetmap"); - const wkt = document.createElement("ol-layer-wkt"); - osm.appendChild(wkt); - el.appendChild(osm); - this.yasr.resultsEl.appendChild(el); + const results = this.getResults() + const el = document.createElement('ol-map') + const osm = document.createElement('ol-layer-openstreetmap') + const wkt = document.createElement('ol-layer-wkt') + osm.appendChild(wkt) + el.appendChild(osm) + this.yasr.resultsEl.appendChild(el) - wkt.featureData = results; + wkt.featureData = results setTimeout(() => { - wkt.fit(); - }, 200); + wkt.fit() + }, 200) } canHandleResults() { - const results = this.getResults(); - return results.length > 0; + const results = this.getResults() + return results.length > 0 } getIcon() { - const textIcon = document.createElement("div"); - textIcon.innerText = "🌐"; - return textIcon; + const textIcon = document.createElement('div') + textIcon.innerText = '🌐' + return textIcon } } -Yasr.registerPlugin("Map", YasguiMap); +Yasr.registerPlugin('Map', YasguiMap) diff --git a/packages/yasgui/test/test.js b/packages/yasgui/test/test.js index 134d9da8..d78d02f6 100644 --- a/packages/yasgui/test/test.js +++ b/packages/yasgui/test/test.js @@ -1,13 +1,13 @@ -import assert from "assert"; -import request from "supertest"; -import { describe, it } from "mocha"; -import express from "express"; -import absoluteUrl from "absolute-url"; -import trifidFactory from "../index.js"; +import assert from 'assert' +import request from 'supertest' +import { describe, it } from 'mocha' +import express from 'express' +import absoluteUrl from 'absolute-url' +import trifidFactory from '../index.js' const createTrifidConfig = (app, config) => { - const server = app; - const logger = console; + const server = app + const logger = console const render = (filePath, context, options) => { return JSON.stringify( { @@ -17,93 +17,93 @@ const createTrifidConfig = (app, config) => { }, null, 2, - ); - }; + ) + } return { config, server, logger, render, - }; -}; + } +} -describe("trifid-plugin-yasgui", () => { - describe("trifid factory", () => { - it("should be a factory", () => { - assert.strictEqual(typeof trifidFactory, "function"); - }); +describe('trifid-plugin-yasgui', () => { + describe('trifid factory', () => { + it('should be a factory', () => { + assert.strictEqual(typeof trifidFactory, 'function') + }) - it("should create a middleware with factory and default options", async () => { - const app = express(); - const trifid = createTrifidConfig(app, {}); - const middleware = await trifidFactory(trifid); + it('should create a middleware with factory and default options', async () => { + const app = express() + const trifid = createTrifidConfig(app, {}) + const middleware = await trifidFactory(trifid) - assert.strictEqual(typeof middleware, "function"); - }); - }); + assert.strictEqual(typeof middleware, 'function') + }) + }) - describe("middleware", () => { - it("can execute", (done) => { - const app = express(); - app.use(absoluteUrl()); + describe('middleware', () => { + it('can execute', (done) => { + const app = express() + app.use(absoluteUrl()) - const trifidConfig = createTrifidConfig(app, {}); + const trifidConfig = createTrifidConfig(app, {}) trifidFactory(trifidConfig).then((middleware) => { - app.use("/sparql", middleware); + app.use('/sparql', middleware) request(app) - .get("/sparql") + .get('/sparql') .expect(200) .end((err, _res) => { if (err) { - done(err); + done(err) } else { - done(); + done() } - }); - }); - }); - }); + }) + }) + }) + }) - describe("YASGUI dist", () => { - it("can serve static CSS style", (done) => { - const app = express(); - app.use(absoluteUrl()); + describe('YASGUI dist', () => { + it('can serve static CSS style', (done) => { + const app = express() + app.use(absoluteUrl()) - const trifidConfig = createTrifidConfig(app, {}); + const trifidConfig = createTrifidConfig(app, {}) trifidFactory(trifidConfig).then((middleware) => { - app.use("/sparql", middleware); + app.use('/sparql', middleware) request(app) - .get("/yasgui-dist/yasgui.min.css") + .get('/yasgui-dist/yasgui.min.css') .expect(200) .end((err, _res) => { if (err) { - done(err); + done(err) } else { - done(); + done() } - }); - }); - }); + }) + }) + }) - it("can serve static JavaScript script", (done) => { - const app = express(); - app.use(absoluteUrl()); + it('can serve static JavaScript script', (done) => { + const app = express() + app.use(absoluteUrl()) - const trifidConfig = createTrifidConfig(app, {}); + const trifidConfig = createTrifidConfig(app, {}) trifidFactory(trifidConfig).then((middleware) => { - app.use("/sparql", middleware); + app.use('/sparql', middleware) request(app) - .get("/yasgui-dist/yasgui.min.js") + .get('/yasgui-dist/yasgui.min.js') .expect(200) .end((err, _res) => { if (err) { - done(err); + done(err) } else { - done(); + done() } - }); - }); - }); - }); -}); + }) + }) + }) + }) +}) From a94543cdb02312692c16568b8a51430bdb487ace Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Tue, 31 Oct 2023 16:32:25 +0100 Subject: [PATCH 3/7] core: improve types and `trifid.start` now returns a promise --- .changeset/honest-cows-sniff.md | 8 +++ .eslintrc.json | 3 +- packages/core/.gitignore | 1 + packages/core/index.js | 70 ++++++++------------------- packages/core/lib/config/default.js | 2 + packages/core/lib/config/handler.js | 37 ++++++++++---- packages/core/lib/config/parser.js | 8 ++- packages/core/middlewares/errors.js | 9 +++- packages/core/middlewares/express.js | 5 +- packages/core/middlewares/health.js | 3 ++ packages/core/middlewares/iri.js | 3 ++ packages/core/middlewares/locals.js | 2 + packages/core/middlewares/notFound.js | 45 +++++++++-------- packages/core/middlewares/redirect.js | 3 ++ packages/core/middlewares/rewrite.js | 4 +- packages/core/middlewares/static.js | 2 + packages/core/middlewares/throw.js | 3 ++ packages/core/middlewares/view.js | 4 +- packages/core/package.json | 2 + packages/core/tsconfig.json | 7 ++- packages/core/types/index.d.ts | 64 ++++++++++++++++++++++++ 21 files changed, 194 insertions(+), 91 deletions(-) create mode 100644 .changeset/honest-cows-sniff.md create mode 100644 packages/core/types/index.d.ts diff --git a/.changeset/honest-cows-sniff.md b/.changeset/honest-cows-sniff.md new file mode 100644 index 00000000..6c6400b1 --- /dev/null +++ b/.changeset/honest-cows-sniff.md @@ -0,0 +1,8 @@ +--- +"trifid-core": minor +--- + +Improve types in general. + +`trifid.start` now returns a `Promise` instead of `void`. +This allows to wait for the server to be ready before doing anything else. diff --git a/.eslintrc.json b/.eslintrc.json index 92f1daf1..79f0db86 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,6 +5,7 @@ }, "ignorePatterns": ["**/*.ts", "**/*.d.ts", "**/*.d.ts.map"], "rules": { - "space-before-function-paren": "off" + "space-before-function-paren": "off", + "indent": ["error", 2, { "SwitchCase": 1 }] } } diff --git a/packages/core/.gitignore b/packages/core/.gitignore index 792e11b4..181e2baf 100644 --- a/packages/core/.gitignore +++ b/packages/core/.gitignore @@ -4,4 +4,5 @@ node_modules npm-debug.log *.tgz *.d.ts +!types/*.d.ts *.d.ts.map diff --git a/packages/core/index.js b/packages/core/index.js index 5efc7848..d98e385b 100644 --- a/packages/core/index.js +++ b/packages/core/index.js @@ -1,5 +1,6 @@ +// @ts-check import express from 'express' -import pino from 'pino' +import { pino } from 'pino' import cors from 'cors' import cookieParser from 'cookie-parser' @@ -16,59 +17,20 @@ import templateEngine from './lib/templateEngine.js' /** * Create a new Trifid instance. * - * @param {{ - * extends?: string[]; - * server?: { - * listener: { - * host?: string; - * port?: number | string; - * }; - * logLevel?: "fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent"; - * express?: Record; - * }; - * globals?: Record; - * template?: Record; - * middlewares?: Record; - * }>; - * }}?} config Trifid configuration. + * @param {import('./types/index.js').TrifidConfigWithExtends?} config Trifid configuration. * @param {Record}) => Promise<()> | (); + * module: import('./types/index.d.ts').TrifidMiddleware, * paths?: string | string[]; * methods?: string | string[]; * hosts?: string | string[]; * config?: Record; - * }?} additionalMiddlewares Add additional middlewares. + * }>?} additionalMiddlewares Add additional middlewares. * @returns {Promise<{ - * start: () => void; - * server: unknown; - * config: {{ - * server?: { - * listener: { - * host?: string; - * port?: number | string; - * }; - * logLevel?: "fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent"; - * express?: Record; - * }; - * globals?: Record; - * template?: Record; - * middlewares?: Record; - * }>; - * }} - * >}} + * start: () => Promise; + * server: import('express').Express; + * config: import('./types/index.js').TrifidConfig + * }>} Trifid instance. */ const trifid = async (config, additionalMiddlewares = {}) => { const fullConfig = await handler(config) @@ -97,6 +59,7 @@ const trifid = async (config, additionalMiddlewares = {}) => { // dynamic server configuration const port = fullConfig?.server?.listener?.port || defaultPort const host = fullConfig?.server?.listener?.host || defaultHost + const portNumber = typeof port === 'string' ? parseInt(port, 10) : port // logger configuration const logLevel = fullConfig?.server?.logLevel || defaultLogLevel @@ -125,9 +88,16 @@ const trifid = async (config, additionalMiddlewares = {}) => { templateEngineInstance, ) - const start = () => { - server.listen(port, host, () => { - logger.info(`Trifid instance listening on: http://${host}:${port}/`) + const start = async () => { + return await new Promise((resolve, reject) => { + const listener = server.listen(portNumber, host, (err) => { + if (err) { + return reject(err) + } + + logger.info(`Trifid instance listening on: http://${host}:${portNumber}/`) + resolve(listener) + }) }) } diff --git a/packages/core/lib/config/default.js b/packages/core/lib/config/default.js index ef010140..48cec751 100644 --- a/packages/core/lib/config/default.js +++ b/packages/core/lib/config/default.js @@ -1,3 +1,5 @@ +// @ts-check + // some default configuration export const maxDepth = 50 diff --git a/packages/core/lib/config/handler.js b/packages/core/lib/config/handler.js index 88e62f6f..4b453282 100644 --- a/packages/core/lib/config/handler.js +++ b/packages/core/lib/config/handler.js @@ -1,3 +1,4 @@ +// @ts-check import fs from 'fs/promises' import { dirname } from 'path' import merge from 'lodash/merge.js' @@ -39,11 +40,13 @@ const resolveConfig = async ( let configs = [] if (Array.isArray(config.extends) && config.extends.length > 0) { config.extends = extendsResolver(config.extends, context) - configs = await Promise.all( - config.extends.map((configPath) => - resolveConfigFile(configPath, depth + 1), - ), - ) + if (Array.isArray(config.extends)) { + configs = await Promise.all( + config.extends.map((configPath) => + resolveConfigFile(configPath, depth + 1), + ), + ) + } } // merge all fields @@ -78,7 +81,7 @@ const resolveConfig = async ( const resolveConfigFile = async (filePath, depth = 0) => { // read config file const fileFullPath = cwdCallback(filePath) - const fileContent = await fs.readFile(fileFullPath) + const fileContent = await fs.readFile(fileFullPath, 'utf-8') let parsed @@ -94,12 +97,16 @@ const resolveConfigFile = async (filePath, depth = 0) => { /** * Add default fields for a configuration. + * Warning: this function mutates the config object. * - * @param {*} config + * @param {import('../../types/index.js').TrifidConfig} config Trifid configuration. + * @return {void} */ const addDefaultFields = (config) => { if (!config.server) { - config.server = {} + config.server = { + listener: {}, + } } if (!config.globals) { @@ -113,8 +120,10 @@ const addDefaultFields = (config) => { /** * Add the default port for the server configuration. + * Warning: this function mutates the config object. * - * @param {*} config + * @param {import('../../types/index.js').TrifidConfig} config Trifid configuration. + * @return {void} */ const addDefaultPort = (config) => { if (!config.server.listener) { @@ -127,8 +136,10 @@ const addDefaultPort = (config) => { /** * Add some default Express settings for the server configuration. + * Warning: this function mutates the config object. * - * @param {*} config + * @param {import('../../types/index.js').TrifidConfig} config Trifid configuration. + * @return {void} */ const addDefaultExpressSettings = (config) => { if (!config.server.express) { @@ -140,6 +151,12 @@ const addDefaultExpressSettings = (config) => { } } +/** + * Expand configuration and add default fields. + * + * @param {string | import('../../types/index.js').TrifidConfigWithExtends} configFile + * @returns {Promise} + */ const handler = async (configFile) => { let config = {} if (typeof configFile === 'string') { diff --git a/packages/core/lib/config/parser.js b/packages/core/lib/config/parser.js index 4cf3804f..93f0f44c 100644 --- a/packages/core/lib/config/parser.js +++ b/packages/core/lib/config/parser.js @@ -1,12 +1,18 @@ +// @ts-check import Ajv from 'ajv' import schema from './schema.js' +// @ts-ignore const ajv = new Ajv() /** * Return the configuration object if it is valid or throw an error in other cases. + * + * @param {import('../../types/index.js').TrifidConfigWithExtends} config Configuration to validate. + * @returns {import('../../types/index.js').TrifidConfigWithExtends} Valid configuration. */ -const parser = (data = {}) => { +const parser = (config) => { + const data = !config ? {} : config const valid = ajv.validate(schema, data) if (!valid) { throw new Error(ajv.errorsText()) diff --git a/packages/core/middlewares/errors.js b/packages/core/middlewares/errors.js index 6ce2b243..5430e16d 100644 --- a/packages/core/middlewares/errors.js +++ b/packages/core/middlewares/errors.js @@ -1,10 +1,17 @@ +// @ts-check + +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { logger } = trifid return (err, _req, res, _next) => { logger.error(err.stack) - res.statusCode = err.statusCode || 500 + res.statusCode = res.statusCode || 500 + if (res.statusCode < 400) { + res.statusCode = 500 + } + res.end() } } diff --git a/packages/core/middlewares/express.js b/packages/core/middlewares/express.js index 7ee98519..93dfa908 100644 --- a/packages/core/middlewares/express.js +++ b/packages/core/middlewares/express.js @@ -1,3 +1,4 @@ +// @ts-check import { loader } from '../lib/middlewares/loader.js' /** @@ -7,8 +8,8 @@ import { loader } from '../lib/middlewares/loader.js' * - module (string, required): the name of the NPM module to load * - options (any, optional): some options to pass to the Express middleware * - * @param {*} trifid Trifid object containing the configuration, and other utility functions. - * @returns Express middleware. + * @param {import('../types/index.d.ts').TrifidMiddlewareArgument} trifid Trifid object containing the configuration, and other utility functions. + * @returns {Promise} Express middleware. */ const factory = async (trifid) => { const { config } = trifid diff --git a/packages/core/middlewares/health.js b/packages/core/middlewares/health.js index 273e88ca..26a4cf8d 100644 --- a/packages/core/middlewares/health.js +++ b/packages/core/middlewares/health.js @@ -1,3 +1,6 @@ +// @ts-check + +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { logger } = trifid diff --git a/packages/core/middlewares/iri.js b/packages/core/middlewares/iri.js index 37ebc2d7..3696763b 100644 --- a/packages/core/middlewares/iri.js +++ b/packages/core/middlewares/iri.js @@ -1,3 +1,5 @@ +// @ts-check + import { URL } from 'url' import absoluteUrl from 'absolute-url' @@ -22,6 +24,7 @@ const removeSearchParams = (originalUrl) => { return urlFrom(url) } +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { config, logger } = trifid const { datasetBaseUrl } = config diff --git a/packages/core/middlewares/locals.js b/packages/core/middlewares/locals.js index 2e508f57..3ea2e90b 100644 --- a/packages/core/middlewares/locals.js +++ b/packages/core/middlewares/locals.js @@ -1,6 +1,8 @@ +// @ts-check import url from 'url' import absoluteUrl from 'absolute-url' +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { logger } = trifid diff --git a/packages/core/middlewares/notFound.js b/packages/core/middlewares/notFound.js index 8fb18c44..64cf2cf7 100644 --- a/packages/core/middlewares/notFound.js +++ b/packages/core/middlewares/notFound.js @@ -1,8 +1,11 @@ +// @ts-check + import { dirname } from 'path' import { fileURLToPath } from 'url' const currentDir = dirname(fileURLToPath(import.meta.url)) +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { logger, render } = trifid @@ -18,27 +21,27 @@ const factory = (trifid) => { 'application/n-quads', ]) switch (accepts) { - case 'json': - res.send({ success: false, message: 'Not found', status: 404 }) - break - - case 'application/n-quads': - case 'html': - res.send( - await render( - `${currentDir}/../views/404.hbs`, - { - url: req.url, - locals: res.locals, - }, - { title: 'Not Found' }, - ), - ) - break - - default: - res.send('Not Found\n') - break + case 'json': + res.send({ success: false, message: 'Not found', status: 404 }) + break + + case 'application/n-quads': + case 'html': + res.send( + await render( + `${currentDir}/../views/404.hbs`, + { + url: req.url, + locals: res.locals, + }, + { title: 'Not Found' }, + ), + ) + break + + default: + res.send('Not Found\n') + break } } } diff --git a/packages/core/middlewares/redirect.js b/packages/core/middlewares/redirect.js index 6e4995d9..489a54d3 100644 --- a/packages/core/middlewares/redirect.js +++ b/packages/core/middlewares/redirect.js @@ -1,3 +1,6 @@ +// @ts-check + +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { config, logger } = trifid const { target } = config diff --git a/packages/core/middlewares/rewrite.js b/packages/core/middlewares/rewrite.js index 1cb2322b..12b57a74 100644 --- a/packages/core/middlewares/rewrite.js +++ b/packages/core/middlewares/rewrite.js @@ -9,8 +9,8 @@ import camouflageRewrite from 'camouflage-rewrite' * * Other available options are documented here: https://github.com/zazuko/camouflage-rewrite#usage * - * @param {*} trifid Trifid object containing the configuration, and other utility functions. - * @returns Express middleware. + * @param {import('../types/index.d.ts').TrifidMiddlewareArgument} trifid Trifid object containing the configuration, and other utility functions. + * @returns {Promise} Express middleware. */ const factory = (trifid) => { const { config } = trifid diff --git a/packages/core/middlewares/static.js b/packages/core/middlewares/static.js index 0b762960..774eec9a 100644 --- a/packages/core/middlewares/static.js +++ b/packages/core/middlewares/static.js @@ -1,5 +1,7 @@ +// @ts-check import express from 'express' +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { directory } = trifid.config if (!directory) { diff --git a/packages/core/middlewares/throw.js b/packages/core/middlewares/throw.js index f558d706..f94f6f43 100644 --- a/packages/core/middlewares/throw.js +++ b/packages/core/middlewares/throw.js @@ -1,3 +1,6 @@ +// @ts-check + +/** @type {import('../types/index.d.ts').TrifidMiddleware} */ const factory = (trifid) => { const { message } = trifid.config diff --git a/packages/core/middlewares/view.js b/packages/core/middlewares/view.js index d8031ad9..de689acd 100644 --- a/packages/core/middlewares/view.js +++ b/packages/core/middlewares/view.js @@ -1,3 +1,5 @@ +// @ts-check + /** * Render a specific template file. * @@ -6,7 +8,7 @@ * - context (object, optional): context to give to this specific template file (some variables) * - options (object, optional): options to pass to the Trifid render function (change the title of the page, …) * - * @param {*} trifid Trifid object containing the configuration, and other utility functions. + * @param {import('../types/index.d.ts').TrifidMiddlewareArgument} trifid Trifid object containing the configuration, and other utility functions. * @returns Express middleware. */ const factory = async (trifid) => { diff --git a/packages/core/package.json b/packages/core/package.json index e33e442d..3a3410c1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -51,6 +51,8 @@ "@babel/core": "^7.22.17", "@babel/preset-env": "^7.22.15", "@jest/globals": "^29.7.0", + "@types/express": "^4.17.20", + "@types/node": "^20.8.9", "babel-jest": "^29.7.0", "jest": "^29.7.0", "nodemon": "^3.0.1", diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 08f6ad5e..f4f54a83 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,6 +1,6 @@ { // Change this to match your project - "include": ["*.js", "lib/**/*.js", "middlewares/**/*.js"], + "include": ["*.js", "lib/**/*.js", "middlewares/**/*.js", "types/index.d.ts"], "exclude": ["coverage", "test", "node_modules"], "compilerOptions": { // Tells TypeScript to read JS files, as @@ -15,6 +15,9 @@ // "outDir": "dist", // go to js file when using IDE functions like // "Go to Definition" in VSCode - "declarationMap": true + "declarationMap": true, + "esModuleInterop": true, + "moduleResolution": "NodeNext", + "module": "NodeNext" } } diff --git a/packages/core/types/index.d.ts b/packages/core/types/index.d.ts new file mode 100644 index 00000000..ac5b4d9f --- /dev/null +++ b/packages/core/types/index.d.ts @@ -0,0 +1,64 @@ +import { Request, Response, NextFunction } from "express"; + +/** + * Trifid configuration + */ +export type TrifidConfig = { + server?: { + listener: { + host?: string; + port?: number | string; + }; + logLevel?: + | "fatal" + | "error" + | "warn" + | "info" + | "debug" + | "trace" + | "silent"; + express?: Record; + }; + globals?: Record; + template?: Record; + middlewares?: Record< + string, + { + order?: number; + module: string; + paths?: string | string[]; + methods?: string | string[]; + hosts?: string | string[]; + config?: Record; + } + >; +}; + +/** + * Trifid configuration with `extends` field + */ +export type TrifidConfigWithExtends = { + extends?: string[]; +} & TrifidConfig; + +/** Express middleware */ +export type ExpressMiddleware = + | ((req: Request, res: Response, next: NextFunction) => void) + | ((error: Error, req: Request, res: Response, next: NextFunction) => void); + +/** Trifid Middleware Argument */ +export type TrifidMiddlewareArgument = { + logger: any; + server: import("express").Express; + config: Record; + render: ( + templatePath: string, + context: Record, + options: Record + ) => Promise; +}; + +/** Trifid Middleware */ +export type TrifidMiddleware = ( + trifid: TrifidMiddlewareArgument +) => Promise | ExpressMiddleware; From e873b2f16b48074544b847b1e6efdb2269bef746 Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Tue, 31 Oct 2023 16:37:09 +0100 Subject: [PATCH 4/7] ckan: added tests + blank line at the end --- .changeset/four-rabbits-invite.md | 7 + package-lock.json | 106 ++++++++++- packages/ckan/package.json | 1 - packages/ckan/src/xml.js | 2 +- packages/ckan/test/ckan.test.js | 89 ++++++++-- packages/ckan/test/support/basic-result.xml | 21 +++ packages/ckan/test/support/data.ttl | 36 ++++ packages/ckan/test/support/empty-result.xml | 4 + packages/ckan/test/support/store.js | 184 ++++++++++++++++++++ packages/ckan/test/support/utils.js | 20 +++ 10 files changed, 452 insertions(+), 18 deletions(-) create mode 100644 .changeset/four-rabbits-invite.md create mode 100644 packages/ckan/test/support/basic-result.xml create mode 100644 packages/ckan/test/support/data.ttl create mode 100644 packages/ckan/test/support/empty-result.xml create mode 100644 packages/ckan/test/support/store.js create mode 100644 packages/ckan/test/support/utils.js diff --git a/.changeset/four-rabbits-invite.md b/.changeset/four-rabbits-invite.md new file mode 100644 index 00000000..ea54b56e --- /dev/null +++ b/.changeset/four-rabbits-invite.md @@ -0,0 +1,7 @@ +--- +"@zazuko/trifid-plugin-ckan": patch +--- + +Generated XML documents have a blank line at the end. + +This Trifid plugin will be more robust, as we created a set of tests. diff --git a/package-lock.json b/package-lock.json index b5399b67..865bfae7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4357,6 +4357,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/body-parser": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/clownface": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/clownface/-/clownface-2.0.1.tgz", @@ -4366,6 +4376,39 @@ "rdf-js": "^4.0.2" } }, + "node_modules/@types/connect": { + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -4375,6 +4418,12 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "dev": true + }, "node_modules/@types/http-link-header": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/http-link-header/-/http-link-header-1.0.3.tgz", @@ -4448,6 +4497,12 @@ "@types/lodash": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -4474,6 +4529,18 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "dev": true + }, "node_modules/@types/rdf-dataset-ext": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/rdf-dataset-ext/-/rdf-dataset-ext-1.0.6.tgz", @@ -4628,6 +4695,27 @@ "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -20060,6 +20148,12 @@ "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -20826,7 +20920,6 @@ }, "devDependencies": { "c8": "^8.0.1", - "express-as-promise": "^1.2.0", "mocha": "^10.2.0", "oxigraph": "^0.3.20" } @@ -20954,6 +21047,8 @@ "@babel/core": "^7.22.17", "@babel/preset-env": "^7.22.15", "@jest/globals": "^29.7.0", + "@types/express": "^4.17.20", + "@types/node": "^20.8.9", "babel-jest": "^29.7.0", "jest": "^29.7.0", "nodemon": "^3.0.1", @@ -20967,6 +21062,15 @@ "npm": ">=8" } }, + "packages/core/node_modules/@types/node": { + "version": "20.8.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "packages/core/node_modules/ajv": { "version": "8.12.0", "license": "MIT", diff --git a/packages/ckan/package.json b/packages/ckan/package.json index e54c96d9..d549bb82 100644 --- a/packages/ckan/package.json +++ b/packages/ckan/package.json @@ -28,7 +28,6 @@ }, "devDependencies": { "c8": "^8.0.1", - "express-as-promise": "^1.2.0", "mocha": "^10.2.0", "oxigraph": "^0.3.20" } diff --git a/packages/ckan/src/xml.js b/packages/ckan/src/xml.js index 67cb8d3b..be7e608b 100644 --- a/packages/ckan/src/xml.js +++ b/packages/ckan/src/xml.js @@ -109,7 +109,7 @@ function toXML (dataset) { }).filter(Boolean), }, }, - }).doc().end({ prettyPrint: true }) + }).doc().end({ prettyPrint: true }).concat('\n') } function serializeTerm (pointer) { diff --git a/packages/ckan/test/ckan.test.js b/packages/ckan/test/ckan.test.js index 2afd4f24..eb9db2f8 100644 --- a/packages/ckan/test/ckan.test.js +++ b/packages/ckan/test/ckan.test.js @@ -1,29 +1,88 @@ -import assert from 'assert' -import withServer from 'express-as-promise/withServer.js' +// @ts-check +import { strictEqual } from 'assert' +import { readFile } from 'fs/promises' import { describe, it } from 'mocha' -import trifidFactory from '../src/index.js' -const createTrifidConfig = (config, server = {}) => { - const loggerSpy = [] +import trifidCore from '../../core/index.js' +import ckanTrifidPlugin from '../src/index.js' +import { storeMiddleware } from './support/store.js' +import { getListenerURL } from './support/utils.js' - return { - logger: (str) => loggerSpy.push(str), - server, - config, - } +const createTrifidInstance = async () => { + return await trifidCore({ + server: { + listener: { + port: 4242, + }, + logLevel: 'warn', + }, + }, { + store: { + module: storeMiddleware, + paths: ['/query'], + methods: ['GET', 'POST'], + }, + ckan: { + module: ckanTrifidPlugin, + paths: ['/ckan'], + methods: ['GET'], + config: { + endpointUrl: '/query', + }, + }, + }) } describe('@zazuko/trifid-plugin-ckan', () => { describe('trifid factory', () => { it('should be a factory', () => { - assert.strictEqual(typeof trifidFactory, 'function') + strictEqual(typeof ckanTrifidPlugin, 'function') }) it('should create a middleware with factory and default options', async () => { - await withServer(async (server) => { - const trifid = createTrifidConfig({}, server.app) - trifidFactory(trifid) - }) + const trifidInstance = await createTrifidInstance() + const trifidListener = await trifidInstance.start() + trifidListener.close() + }) + + it('should answer with a 400 status code if the organization parameter is missing', async () => { + const trifidInstance = await createTrifidInstance() + const trifidListener = await trifidInstance.start() + const ckanUrl = `${getListenerURL(trifidListener)}/ckan` + + const res = await fetch(ckanUrl) + strictEqual(res.status, 400) + trifidListener.close() + }) + + it('should get an empty result for an unknown organization', async () => { + const trifidInstance = await createTrifidInstance() + const trifidListener = await trifidInstance.start() + const ckanUrl = `${getListenerURL(trifidListener)}/ckan?organization=http://example.com/unkown-org` + + const res = await fetch(ckanUrl) + const body = await res.text() + const expectedResult = await readFile(new URL('./support/empty-result.xml', import.meta.url), 'utf8') + + strictEqual(res.status, 200) + strictEqual(body, expectedResult) + + trifidListener.close() + }) + + it('should get a basic result for a known organization', async () => { + const trifidInstance = await createTrifidInstance() + const trifidListener = await trifidInstance.start() + const ckanUrl = `${getListenerURL(trifidListener)}/ckan?organization=http://example.com/my-org` + + const res = await fetch(ckanUrl) + const body = await res.text() + const expectedResult = await readFile(new URL('./support/basic-result.xml', import.meta.url), 'utf8') + + strictEqual(res.status, 200) + strictEqual(body, expectedResult) + + trifidListener.close() }) }) }) diff --git a/packages/ckan/test/support/basic-result.xml b/packages/ckan/test/support/basic-result.xml new file mode 100644 index 00000000..fa33be57 --- /dev/null +++ b/packages/ckan/test/support/basic-result.xml @@ -0,0 +1,21 @@ + + + + + + dataset1@my-org + Dataset 1 - Titel + Dataset 1 - Title + Dataset 1 - Titre + Dataset 1 - Titolo + Dataset 1 - Description + Dataset 1 - Description + Dataset 1 - Description + Dataset 1 - Description + 2023-10-31 + 2023-10-31T:15:15.000Z + + + + + diff --git a/packages/ckan/test/support/data.ttl b/packages/ckan/test/support/data.ttl new file mode 100644 index 00000000..7e697f0c --- /dev/null +++ b/packages/ckan/test/support/data.ttl @@ -0,0 +1,36 @@ +@base . +@prefix schema: . +@prefix dcterms: . +@prefix skos: . +@prefix xsd: . +@prefix rdf: . + + + rdf:type schema:Dataset ; + rdf:type ; + rdf:type ; + rdf:type ; + dcterms:issued "2023-10-31"^^xsd:date ; + dcterms:modified "2023-10-31T:15:15.000Z"^^xsd:dateTime ; + dcterms:identifier "dataset1" ; + dcterms:creator ; + schema:workExample ; + schema:creativeWorkStatus ; + dcterms:title "Dataset 1 - Title"@en ; + dcterms:title "Dataset 1 - Titre"@fr ; + dcterms:title "Dataset 1 - Titolo"@it ; + dcterms:title "Dataset 1 - Titel"@de ; + schema:name "Dataset 1 - Title"@en ; + schema:name "Dataset 1 - Titre"@fr ; + schema:name "Dataset 1 - Titolo"@it ; + schema:name "Dataset 1 - Titel"@de ; + dcterms:description "Dataset 1 - Description"@en ; + dcterms:description "Dataset 1 - Description"@fr ; + dcterms:description "Dataset 1 - Description"@it ; + dcterms:description "Dataset 1 - Description"@de ; + schema:description "Dataset 1 - Description"@en ; + schema:description "Dataset 1 - Description"@fr ; + schema:description "Dataset 1 - Description"@it ; + schema:description "Dataset 1 - Description"@de ; + schema:version "1"^^xsd:integer ; + . diff --git a/packages/ckan/test/support/empty-result.xml b/packages/ckan/test/support/empty-result.xml new file mode 100644 index 00000000..ee2ef86b --- /dev/null +++ b/packages/ckan/test/support/empty-result.xml @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ckan/test/support/store.js b/packages/ckan/test/support/store.js new file mode 100644 index 00000000..bc39c1c2 --- /dev/null +++ b/packages/ckan/test/support/store.js @@ -0,0 +1,184 @@ +// @ts-check +import { readFile } from 'fs/promises' +import oxigraph from 'oxigraph' + +/** + * Convert Oxigraph termType to SPARQL termType. + * + * @param {string} termType Oxigraph termType value + * @returns {string} SPARQL termType value + */ +const convertTermType = (termType) => { + switch (termType) { + case 'Literal': + return 'literal' + case 'BlankNode': + return 'bnode' + case 'NamedNode': + return 'uri' + default: + return 'literal' + } +} + +/** + * Handle Oxigraph query results. + * + * @param {ReturnType} results + * @param {boolean} isConstructQuery + * @returns {Promise<{ + * raw: string | Record | string[]; + * response: string; + * contentType: 'application/sparql-results+json' | 'application/n-triples'; + * type: 'ASK' | 'SELECT' | 'CONSTRUCT'; + * }>} SPARQL response. + */ +const handleOxigraphResult = async (results, isConstructQuery = false) => { + let sparqlResponse = {} + + // Handle ASK queries + if (typeof results === 'boolean') { + sparqlResponse = { + head: {}, + boolean: results, + } + return { + raw: sparqlResponse, + response: JSON.stringify(sparqlResponse, null, 2), + contentType: 'application/sparql-results+json', + type: 'ASK', + } + } + + // Handle empty results + if (!results || !Array.isArray(results) || results.length === 0) { + if (isConstructQuery) { + return { + raw: '', + response: '', + contentType: 'application/n-triples', + type: 'CONSTRUCT', + } + } + + sparqlResponse = { + head: { + vars: [], + }, + results: { + bindings: [], + }, + } + return { + raw: sparqlResponse, + response: JSON.stringify(sparqlResponse, null, 2), + contentType: 'application/sparql-results+json', + type: 'SELECT', + } + } + + const headVariables = new Set() + const bindings = [] + let isOtherThanMap = false + + // Loop over each result, and build bindings and variables + // We assume that all results are `Map` objects for SELECT queries + // If we get something else than `Map` objects, we assume it's a CONSTRUCT query + for (const result of results) { + if (result instanceof Map) { + const binding = {} + for (const [key, value] of result) { + headVariables.add(key) + binding[key] = { + type: convertTermType(value.termType), + value: value.value, + } + if (value.language) { + binding[key]['xml:lang'] = value.language + } + if (value.datatype) { + binding[key].datatype = value.datatype.value + } + } + bindings.push(binding) + } else { + isOtherThanMap = true + break + } + } + + // We got something else than `Map` objects, so we assume it's a CONSTRUCT query + if (isOtherThanMap) { + const quads = results.map((quad) => quad.toString()) + const quadsOutput = `${quads.join(' . \n')} .` + return { + raw: quads, + response: quadsOutput, + contentType: 'application/n-triples', + type: 'CONSTRUCT', + } + } + + // Build the SPARQL response for the SELECT query + sparqlResponse = { + head: { + vars: Array.from(headVariables), + }, + results: { + bindings, + }, + } + + return { + raw: sparqlResponse, + response: JSON.stringify(sparqlResponse, null, 2), + contentType: 'application/sparql-results+json', + type: 'SELECT', + } +} + +/** + * Perform a SPARQL query using Oxigraph. + * + * @param {import('oxigraph').Store} store Oxigraph store + * @param {string} query The query to perform + * @returns {Promise<{ + * raw: string | Record | string[]; + * response: string; + * contentType: 'application/sparql-results+json' | 'application/n-triples'; + * type: 'ASK' | 'SELECT' | 'CONSTRUCT'; + * }>} SPARQL response. + */ +const performOxigraphQuery = async (store, query) => { + const results = await store.query(query) + const isConstructQuery = query.toUpperCase().includes('CONSTRUCT') + return await handleOxigraphResult(results, isConstructQuery) +} + +/** @type {import('../../../core/types/index.d.ts').TrifidMiddleware} */ +export const storeMiddleware = async (_trifid) => { + // read quads from file + const data = await readFile(new URL('./data.ttl', import.meta.url)) + const stringData = data.toString() + + // create a store and load the quads + const store = new oxigraph.Store() + store.load(stringData, 'text/turtle', 'http://example.com', oxigraph.namedNode('http://example.com/graph')) + + return async (req, res, _next) => { + let query + if (req.method === 'GET') { + query = req.query.query + } else if (req.method === 'POST') { + query = req.body.query || req.body + } + + if (!query) { + return res.status(400).send('Missing query parameter') + } + + const { response, contentType } = await performOxigraphQuery(store, query) + res.set('Content-Type', contentType) + return res.status(200).send(response) + } +} diff --git a/packages/ckan/test/support/utils.js b/packages/ckan/test/support/utils.js new file mode 100644 index 00000000..372e1cbb --- /dev/null +++ b/packages/ckan/test/support/utils.js @@ -0,0 +1,20 @@ +// @ts-check + +/** + * Get the URL of a listener. + * + * @param {import('http').Server} listener HTTP listener + * @returns {string} + */ +export const getListenerURL = (listener) => { + const address = listener.address() + if (!address) { + throw new Error('The listener is not listening') + } + if (typeof address === 'string') { + return address + } + + const { address: hostname, port } = address + return `http://${hostname}:${port}` +} From 29df7f8b765bd61df38182b5c9c0af5be5f2140d Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Mon, 13 Nov 2023 08:57:28 +0100 Subject: [PATCH 5/7] tests: remove useless tests --- packages/ckan/test/ckan.test.js | 6 +----- packages/core/test/config.test.js | 5 ----- packages/core/test/middlewares/errors.test.js | 12 ++++-------- packages/core/test/middlewares/health.test.js | 16 ++++++---------- packages/core/test/middlewares/redirect.test.js | 8 ++------ packages/core/test/middlewares/static.test.js | 4 ---- packages/graph-explorer/test/test.js | 5 ----- packages/handler-fetch/test/Fetcher.js | 12 ------------ packages/handler-fetch/test/index.js | 4 ---- packages/handler-sparql/test/test.js | 6 +----- packages/i18n/test/test.js | 10 +--------- packages/spex/test/test.js | 4 ---- packages/yasgui/test/test.js | 4 ---- 13 files changed, 15 insertions(+), 81 deletions(-) diff --git a/packages/ckan/test/ckan.test.js b/packages/ckan/test/ckan.test.js index eb9db2f8..d89ca445 100644 --- a/packages/ckan/test/ckan.test.js +++ b/packages/ckan/test/ckan.test.js @@ -34,11 +34,7 @@ const createTrifidInstance = async () => { } describe('@zazuko/trifid-plugin-ckan', () => { - describe('trifid factory', () => { - it('should be a factory', () => { - strictEqual(typeof ckanTrifidPlugin, 'function') - }) - + describe('basic tests', () => { it('should create a middleware with factory and default options', async () => { const trifidInstance = await createTrifidInstance() const trifidListener = await trifidInstance.start() diff --git a/packages/core/test/config.test.js b/packages/core/test/config.test.js index 86a5b4f7..ea8788fb 100644 --- a/packages/core/test/config.test.js +++ b/packages/core/test/config.test.js @@ -1,13 +1,8 @@ import { describe, expect, test } from '@jest/globals' -import schema from '../lib/config/schema.js' import parser from '../lib/config/parser.js' describe('config', () => { - test('should be an object', () => { - expect(typeof schema).toEqual('object') - }) - test('should not throw if the configuration is empty', () => { expect(() => parser()).not.toThrow() expect(() => parser({})).not.toThrow() diff --git a/packages/core/test/middlewares/errors.test.js b/packages/core/test/middlewares/errors.test.js index b2d8e050..3e498d9a 100644 --- a/packages/core/test/middlewares/errors.test.js +++ b/packages/core/test/middlewares/errors.test.js @@ -1,14 +1,10 @@ import express from 'express' import request from 'supertest' -import { describe, expect, test } from '@jest/globals' +import { describe, test } from '@jest/globals' import errorsMiddleware from '../../middlewares/errors.js' describe('errors middleware', () => { - test('should be a function', () => { - expect(typeof errorsMiddleware).toEqual('function') - }) - test('should return a 500 status code', async () => { const app = express() @@ -20,7 +16,7 @@ describe('errors middleware', () => { app.use( errorsMiddleware({ logger: { - error: (_msg) => {}, + error: (_msg) => { }, }, }), ) @@ -39,7 +35,7 @@ describe('errors middleware', () => { app.use( errorsMiddleware({ logger: { - error: (_msg) => {}, + error: (_msg) => { }, }, }), ) @@ -58,7 +54,7 @@ describe('errors middleware', () => { app.use( errorsMiddleware({ logger: { - error: (_msg) => {}, + error: (_msg) => { }, }, }), ) diff --git a/packages/core/test/middlewares/health.test.js b/packages/core/test/middlewares/health.test.js index ecd054d4..f083920d 100644 --- a/packages/core/test/middlewares/health.test.js +++ b/packages/core/test/middlewares/health.test.js @@ -1,14 +1,10 @@ import express from 'express' import request from 'supertest' -import { describe, expect, test } from '@jest/globals' +import { describe, test } from '@jest/globals' import healthMiddleware from '../../middlewares/health.js' describe('health middleware', () => { - test('should be a function', () => { - expect(typeof healthMiddleware).toEqual('function') - }) - test('should return expected content-type', async () => { const app = express() @@ -16,7 +12,7 @@ describe('health middleware', () => { '/health', healthMiddleware({ logger: { - debug: (_msg) => {}, + debug: (_msg) => { }, }, }), ) @@ -33,7 +29,7 @@ describe('health middleware', () => { '/health', healthMiddleware({ logger: { - debug: (_msg) => {}, + debug: (_msg) => { }, }, }), ) @@ -48,7 +44,7 @@ describe('health middleware', () => { '/health', healthMiddleware({ logger: { - debug: (_msg) => {}, + debug: (_msg) => { }, }, }), ) @@ -63,7 +59,7 @@ describe('health middleware', () => { '/health', healthMiddleware({ logger: { - debug: (_msg) => {}, + debug: (_msg) => { }, }, }), ) @@ -82,7 +78,7 @@ describe('health middleware', () => { '/health', healthMiddleware({ logger: { - debug: (_msg) => {}, + debug: (_msg) => { }, }, }), ) diff --git a/packages/core/test/middlewares/redirect.test.js b/packages/core/test/middlewares/redirect.test.js index 2c956448..c3ea7786 100644 --- a/packages/core/test/middlewares/redirect.test.js +++ b/packages/core/test/middlewares/redirect.test.js @@ -5,10 +5,6 @@ import { describe, expect, test } from '@jest/globals' import redirectMiddleware from '../../middlewares/redirect.js' describe('redirect middleware', () => { - test('should be a function', () => { - expect(typeof redirectMiddleware).toEqual('function') - }) - test('should throw if the target parameter is not set', () => { expect(() => redirectMiddleware({ config: {} })).toThrow() }) @@ -23,7 +19,7 @@ describe('redirect middleware', () => { target: '/', }, logger: { - debug: (_) => {}, + debug: (_) => { }, }, }), ) @@ -41,7 +37,7 @@ describe('redirect middleware', () => { target: '/', }, logger: { - debug: (_) => {}, + debug: (_) => { }, }, }), ) diff --git a/packages/core/test/middlewares/static.test.js b/packages/core/test/middlewares/static.test.js index af7ac810..96d6f6e4 100644 --- a/packages/core/test/middlewares/static.test.js +++ b/packages/core/test/middlewares/static.test.js @@ -7,10 +7,6 @@ import { describe, expect, test } from '@jest/globals' import staticMiddleware from '../../middlewares/static.js' describe('static middleware', () => { - test('should be a function', () => { - expect(typeof staticMiddleware).toEqual('function') - }) - test('should throw if the directory parameter is not set', () => { expect(() => staticMiddleware({ config: {} })).toThrow() }) diff --git a/packages/graph-explorer/test/test.js b/packages/graph-explorer/test/test.js index 5f64071a..2ff56bca 100644 --- a/packages/graph-explorer/test/test.js +++ b/packages/graph-explorer/test/test.js @@ -1,4 +1,3 @@ -import assert from 'assert' import withServer from 'express-as-promise/withServer.js' import { describe, it } from 'mocha' import trifidFactory from '../index.js' @@ -15,10 +14,6 @@ const createTrifidConfig = (config, server = {}) => { describe('trifid-plugin-graph-explorer', () => { describe('trifid factory', () => { - it('should be a factory', () => { - assert.strictEqual(typeof trifidFactory, 'function') - }) - it('should create a middleware with factory and default options', async () => { await withServer(async (server) => { const trifid = createTrifidConfig({}, server.app) diff --git a/packages/handler-fetch/test/Fetcher.js b/packages/handler-fetch/test/Fetcher.js index 6bb57046..9942e0e9 100644 --- a/packages/handler-fetch/test/Fetcher.js +++ b/packages/handler-fetch/test/Fetcher.js @@ -13,10 +13,6 @@ describe('Fetcher', () => { const fileUrlDataset = `file://${require.resolve('tbbt-ld/dist/tbbt.nq')}` describe('.isCached', () => { - it('should be a method', () => { - assert.equal(typeof Fetcher.isCached, 'function') - }) - it('should return false if caching is not enabled', () => { assert(!Fetcher.isCached({})) }) @@ -36,10 +32,6 @@ describe('Fetcher', () => { }) describe('.fetchDataset', () => { - it('should be a method', () => { - assert.equal(typeof Fetcher.fetchDataset, 'function') - }) - it('should load a dataset from a file URL', async () => { const options = { url: fileUrlDataset, @@ -104,10 +96,6 @@ describe('Fetcher', () => { }) describe('.spreadDataset', () => { - it('should be a method', () => { - assert.equal(typeof Fetcher.spreadDataset, 'function') - }) - it('should forward the dataset if no options are given', () => { const input = rdf.dataset([ rdf.quad( diff --git a/packages/handler-fetch/test/index.js b/packages/handler-fetch/test/index.js index 3b93c4f5..1db2924b 100644 --- a/packages/handler-fetch/test/index.js +++ b/packages/handler-fetch/test/index.js @@ -23,10 +23,6 @@ describe('trifid-handler-fetch', () => { next() } - it('should be a constructor', () => { - assert.equal(typeof Handler, 'function') - }) - it('should assign url option', () => { const iri = 'http://example.org/dataset' diff --git a/packages/handler-sparql/test/test.js b/packages/handler-sparql/test/test.js index f7e373cf..1ab5c8e5 100644 --- a/packages/handler-sparql/test/test.js +++ b/packages/handler-sparql/test/test.js @@ -16,10 +16,6 @@ const defaults = { /* eslint-enable no-template-curly-in-string */ describe('trifid-handler-sparql', () => { - it('should be a constructor', () => { - strictEqual(typeof SparqlHandler, 'function') - }) - describe('uses the resourceExistsQuery to check if the requested IRI exists', async () => { [ { iri: 'http://localhost/test', resourceNoSlash: undefined }, @@ -211,7 +207,7 @@ describe('trifid-handler-sparql', () => { const handler = new SparqlHandler({ ...defaults, endpointUrl: endpoint.url, - authentication: authentication, + authentication, }) server.app.use(setIri('http://localhost/test')) diff --git a/packages/i18n/test/test.js b/packages/i18n/test/test.js index d455be50..437f0357 100644 --- a/packages/i18n/test/test.js +++ b/packages/i18n/test/test.js @@ -9,10 +9,6 @@ import withServer from './support/withServer.js' const currentDir = dirname(fileURLToPath(import.meta.url)) describe('trifid-plugin-i18n', () => { - it('should be a function', () => { - strictEqual(typeof trifidPluginI18n, 'function') - }) - it('should add the .t method to to res to translate a string', async () => { await withServer(async (server) => { const middleware = trifidPluginI18n({ @@ -125,10 +121,6 @@ describe('trifid-plugin-i18n', () => { }) describe('Trifid factory', () => { - it('should be a function', () => { - strictEqual(typeof factory, 'function') - }) - it('should throw if no directory is defined', async () => { await withServer(async (server) => { throws(() => @@ -145,7 +137,7 @@ describe('Trifid factory', () => { it('should work as expected', async () => { await withServer(async (server) => { const middleware = factory({ - registerTemplateHelper: (_name, _fn) => {}, + registerTemplateHelper: (_name, _fn) => { }, server: server.app, config: { locales: ['en', 'de'], diff --git a/packages/spex/test/test.js b/packages/spex/test/test.js index 1cce5a25..bb05e9b9 100644 --- a/packages/spex/test/test.js +++ b/packages/spex/test/test.js @@ -15,10 +15,6 @@ const createTrifidConfig = (config) => { describe('trifid-plugin-spex', () => { describe('trifid factory', () => { - it('should be a factory', () => { - assert.strictEqual(typeof trifidFactory, 'function') - }) - it('should create a middleware with factory and default options', async () => { const trifid = createTrifidConfig({}) const middleware = await trifidFactory(trifid) diff --git a/packages/yasgui/test/test.js b/packages/yasgui/test/test.js index d78d02f6..afe0640c 100644 --- a/packages/yasgui/test/test.js +++ b/packages/yasgui/test/test.js @@ -30,10 +30,6 @@ const createTrifidConfig = (app, config) => { describe('trifid-plugin-yasgui', () => { describe('trifid factory', () => { - it('should be a factory', () => { - assert.strictEqual(typeof trifidFactory, 'function') - }) - it('should create a middleware with factory and default options', async () => { const app = express() const trifid = createTrifidConfig(app, {}) From 552ecf9e8d90150d569fa52e34f302bfa3693962 Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Mon, 13 Nov 2023 09:10:05 +0100 Subject: [PATCH 6/7] core/errors: return description of status code in the body --- .changeset/smooth-beans-lay.md | 5 +++++ packages/core/middlewares/errors.js | 9 +++++---- packages/core/test/middlewares/errors.test.js | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 .changeset/smooth-beans-lay.md diff --git a/.changeset/smooth-beans-lay.md b/.changeset/smooth-beans-lay.md new file mode 100644 index 00000000..d3249ef8 --- /dev/null +++ b/.changeset/smooth-beans-lay.md @@ -0,0 +1,5 @@ +--- +"trifid-core": minor +--- + +The errors middleware is now returning the description of the status code in the body. diff --git a/packages/core/middlewares/errors.js b/packages/core/middlewares/errors.js index 5430e16d..da028329 100644 --- a/packages/core/middlewares/errors.js +++ b/packages/core/middlewares/errors.js @@ -7,12 +7,13 @@ const factory = (trifid) => { return (err, _req, res, _next) => { logger.error(err.stack) - res.statusCode = res.statusCode || 500 - if (res.statusCode < 400) { - res.statusCode = 500 + let status = res.statusCode || 500 + // handle the case where there is an error, but no specific status code has been set + if (status < 400) { + status = 500 } - res.end() + res.sendStatus(status) } } diff --git a/packages/core/test/middlewares/errors.test.js b/packages/core/test/middlewares/errors.test.js index 3e498d9a..2d2d4ebe 100644 --- a/packages/core/test/middlewares/errors.test.js +++ b/packages/core/test/middlewares/errors.test.js @@ -43,7 +43,7 @@ describe('errors middleware', () => { return request(app).get('/').expect(502) }) - test('should return an empty body', async () => { + test('should return a body containing the description of the status code', async () => { const app = express() const throwingMiddleware = (_req, _res, _next) => { @@ -59,6 +59,6 @@ describe('errors middleware', () => { }), ) - return request(app).get('/').expect('') + return request(app).get('/').expect('Internal Server Error') }) }) From 9ea96533edcdfbc2581bffa587459ab758a8af0a Mon Sep 17 00:00:00 2001 From: Ludovic Muller Date: Mon, 13 Nov 2023 09:17:47 +0100 Subject: [PATCH 7/7] core/lib/handler: remove useless isArray check --- packages/core/lib/config/handler.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/lib/config/handler.js b/packages/core/lib/config/handler.js index 4b453282..82039a0d 100644 --- a/packages/core/lib/config/handler.js +++ b/packages/core/lib/config/handler.js @@ -40,13 +40,11 @@ const resolveConfig = async ( let configs = [] if (Array.isArray(config.extends) && config.extends.length > 0) { config.extends = extendsResolver(config.extends, context) - if (Array.isArray(config.extends)) { - configs = await Promise.all( - config.extends.map((configPath) => - resolveConfigFile(configPath, depth + 1), - ), - ) - } + configs = await Promise.all( + config.extends.map((configPath) => + resolveConfigFile(configPath, depth + 1), + ), + ) } // merge all fields