diff --git a/CHANGELOG b/CHANGELOG index 7addde3a4f..350bedc9a5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -207,6 +207,9 @@ devel * fixed internal #2215's failedleader timeout, when in todo +* Foxx manifest.json files can now contain a $schema key with the value + of "http://json.schemastore.org/foxx-manifest" to improve tooling support. + * added `@arangodb/locals` module to expose the Foxx service context as an alternative to using `module.context` directly. diff --git a/js/server/modules/@arangodb/foxx/manifest.js b/js/server/modules/@arangodb/foxx/manifest.js index 9d4067d964..68ee9b7254 100644 --- a/js/server/modules/@arangodb/foxx/manifest.js +++ b/js/server/modules/@arangodb/foxx/manifest.js @@ -10,6 +10,9 @@ const ArangoError = arangodb.ArangoError; const errors = arangodb.errors; const il = require('@arangodb/util').inline; +const CANONICAL_SCHEMA = 'http://json.schemastore.org/foxx-manifest'; +exports.schemaUrl = CANONICAL_SCHEMA; + // Regular expressions for joi patterns const RE_EMPTY = /^$/; const RE_NOT_EMPTY = /./; @@ -37,6 +40,7 @@ configTypes.int = configTypes.integer; configTypes.bool = configTypes.boolean; const manifestSchema = { + $schema: joi.forbidden().allow(CANONICAL_SCHEMA).default(CANONICAL_SCHEMA), // FoxxStore metadata name: joi.string().regex(/^[-_a-z][-_a-z0-9]*$/i).optional(), version: joi.string().optional(), @@ -167,11 +171,20 @@ function checkManifest (filename, inputManifest, mount, complainAboutVersionMism const value = inputManifest[key]; const result = joi.validate(value, schema); if (result.error) { - const error = result.error.message.replace(/^"value"/, 'Value'); - errors.push(il` - Service at "${mount}" specifies manifest field "${key}" - with invalid value "${util.format(value)}": ${error} - `); + if (key === '$schema') { + manifest[key] = CANONICAL_SCHEMA; + console.warnLines(il` + Service at "${mount}" specifies manifest field "$schema" + with invalid value "${util.format(value)}". + Did you mean "${CANONICAL_SCHEMA}"? + `); + } else { + const error = result.error.message.replace(/^"value"/, 'Value'); + errors.push(il` + Service at "${mount}" specifies manifest field "${key}" + with invalid value "${util.format(value)}": ${error} + `); + } } else { manifest[key] = result.value; } @@ -384,3 +397,4 @@ function validateManifestFile (filename, mount, complainAboutVersionMismatches) exports.configTypes = configTypes; exports.validate = validateManifestFile; +exports.validateJson = checkManifest; diff --git a/js/server/tests/shell/shell-foxx-manifest-spec.js b/js/server/tests/shell/shell-foxx-manifest-spec.js new file mode 100644 index 0000000000..487b8136ef --- /dev/null +++ b/js/server/tests/shell/shell-foxx-manifest-spec.js @@ -0,0 +1,28 @@ +/*global describe, it */ +'use strict'; +const expect = require('chai').expect; +const validateManifest = require('@arangodb/foxx/manifest').validateJson; +const CANONICAL_SCHEMA = require('@arangodb/foxx/manifest').schemaUrl; +const request = require('@arangodb/request'); +const db = require('@arangodb').db; + +describe('Foxx manifest $schema field', () => { + it(`defaults to "${CANONICAL_SCHEMA}"`, () => { + const manifest = validateManifest('fake', {}, '/fake'); + expect(manifest).to.have.property('$schema', CANONICAL_SCHEMA); + }); + it('warns (but does not fail validation) if invalid', () => { + const BAD_VALUE = 'http://badvalue.to/log'; + try { + validateManifest('fake', { $schema: BAD_VALUE }, '/fake'); + } catch (e) { + expect.fail(); + } + const logs = request.get(`/_db/${db._name()}/_admin/log`, {json: true}).json; + expect(logs.text.filter(text => text.includes(BAD_VALUE))).not.to.be.empty; + }); + it(`falls back to "${CANONICAL_SCHEMA}" if invalid`, () => { + const manifest = validateManifest('fake', { $schema: 'http://example.com' }, '/fake'); + expect(manifest).to.have.property('$schema', CANONICAL_SCHEMA); + }); +});