diff --git a/arangod/Cluster/ClusterInfo.cpp b/arangod/Cluster/ClusterInfo.cpp index 232e2559a1..31555f7125 100644 --- a/arangod/Cluster/ClusterInfo.cpp +++ b/arangod/Cluster/ClusterInfo.cpp @@ -2004,8 +2004,8 @@ void ClusterInfo::loadServers() { } AgencyCommResult result = _agency.sendTransactionWithFailover( - AgencyReadTransaction({AgencyCommManager::path(prefixServers), - AgencyCommManager::path(mapUniqueToShortId)})); + AgencyReadTransaction(std::vector({AgencyCommManager::path(prefixServers), + AgencyCommManager::path(mapUniqueToShortId)}))); if (result.successful()) { diff --git a/js/apps/system/_api/foxx/APP/index.js b/js/apps/system/_api/foxx/APP/index.js index 87a6675b1b..eabe89187e 100644 --- a/js/apps/system/_api/foxx/APP/index.js +++ b/js/apps/system/_api/foxx/APP/index.js @@ -8,6 +8,7 @@ const semver = require('semver'); const actions = require('@arangodb/actions'); const ArangoError = require('@arangodb').ArangoError; const errors = require('@arangodb').errors; +const swaggerJson = require('@arangodb/foxx/legacy/swagger').swaggerJson; const fm = require('@arangodb/foxx/manager'); const fmu = require('@arangodb/foxx/manager-utils'); const createRouter = require('@arangodb/foxx/router'); @@ -400,3 +401,14 @@ instanceRouter.get('/readme', (req, res) => { .description(dd` Fetches the service's README or README.md file's contents if any. `); + +instanceRouter.get('/swagger', (req, res) => { + swaggerJson(req, res, { + mount: req.service.mount + }); +}) +.response(200, joi.object(), `Service Swagger description.`) +.summary(`Swagger description`) +.description(dd` + Fetches the Swagger API description for the service at the given mount path. +`); diff --git a/js/server/modules/@arangodb/foxx/router/router.js b/js/server/modules/@arangodb/foxx/router/router.js index 008f6cb4b0..518e40e823 100644 --- a/js/server/modules/@arangodb/foxx/router/router.js +++ b/js/server/modules/@arangodb/foxx/router/router.js @@ -106,7 +106,7 @@ const Router = module.exports = [['path', 'string'], ...repeat(Math.max(1, args.length - 2), ['handler', 'function']), ['name', 'string']], [['path', 'string'], ...repeat(Math.max(1, args.length - 1), ['handler', 'function'])], [...repeat(Math.max(1, args.length - 1), ['handler', 'function']), ['name', 'string']], - repeat(args.length, ['handler', 'function']) + repeat(Math.max(1, args.length - 1), ['handler', 'function']) ); const path = argv.path; const handler = argv.handler; @@ -130,7 +130,7 @@ ALL_METHODS.forEach(function (method) { [['path', 'string'], ...repeat(Math.max(1, args.length - 2), ['handler', 'function']), ['name', 'string']], [['path', 'string'], ...repeat(Math.max(1, args.length - 1), ['handler', 'function'])], [...repeat(Math.max(1, args.length - 1), ['handler', 'function']), ['name', 'string']], - repeat(args.length, ['handler', 'function']) + repeat(Math.max(1, args.length - 1), ['handler', 'function']) ); const path = argv.path; const handler = argv.handler; diff --git a/js/server/modules/@arangodb/foxx/router/swagger-context.js b/js/server/modules/@arangodb/foxx/router/swagger-context.js index 8144d0292d..79dd42b94c 100644 --- a/js/server/modules/@arangodb/foxx/router/swagger-context.js +++ b/js/server/modules/@arangodb/foxx/router/swagger-context.js @@ -49,6 +49,14 @@ const PARSED_JSON_MIME = (function (mime) { ])); }(MIME_JSON)); +const repeat = (times, value) => { + const arr = Array(times); + for (let i = 0; i < times; i++) { + arr[i] = value; + } + return arr; +}; + module.exports = exports = class SwaggerContext { constructor (path) { @@ -75,6 +83,7 @@ module.exports = exports = this._pathParams = new Map(); this._pathParamNames = []; this._pathTokens = tokenize(path, this); + this._tags = new Set(); } header (...args) { @@ -262,6 +271,18 @@ module.exports = exports = return this; } + tag (...tags) { + tags = check( + 'endpoint.tag', + tags, + [...repeat(Math.max(1, tags.length), ['tag', 'string'])] + ); + for (const tag of tags) { + this._tags.add(tag); + } + return this; + } + deprecated (...args) { const [flag] = check( 'endpoint.summary', @@ -284,6 +305,9 @@ module.exports = exports = for (const response of swaggerObj._responses.entries()) { this._responses.set(response[0], response[1]); } + for (const tag of swaggerObj._tags) { + this._tags.add(tag); + } if (!this._bodyParam && swaggerObj._bodyParam) { this._bodyParam = swaggerObj._bodyParam; } @@ -335,6 +359,9 @@ module.exports = exports = if (this._summary) { operation.summary = this._summary; } + if (this._tags) { + operation.tags = Array.from(this._tags); + } if (this._bodyParam) { operation.consumes = ( this._bodyParam.contentTypes diff --git a/js/server/modules/@arangodb/foxx/router/tree.js b/js/server/modules/@arangodb/foxx/router/tree.js index 6cd6a3815a..0d8cc6ac2d 100644 --- a/js/server/modules/@arangodb/foxx/router/tree.js +++ b/js/server/modules/@arangodb/foxx/router/tree.js @@ -36,6 +36,10 @@ const validation = require('@arangodb/foxx/router/validation'); const $_ROUTES = Symbol.for('@@routes'); // routes and child routers const $_MIDDLEWARE = Symbol.for('@@middleware'); // middleware +function ucFirst (str) { + return str[0].toUpperCase() + str.slice(1); +} + module.exports = class Tree { constructor (context, router) { @@ -135,11 +139,16 @@ module.exports = buildSwaggerPaths () { const paths = {}; + const ids = new Set(); for (const route of this.flatten()) { const parts = []; const swagger = new SwaggerContext(); let i = 0; + const names = []; for (const item of route) { + if (item.name) { + names.push(item.name); + } if (item.router) { swagger._merge(item, true); } else { @@ -164,10 +173,28 @@ module.exports = } const pathItem = paths[path]; const operation = swagger._buildOperation(); + if (names.length) { + operation.operationId = names + .map((name, i) => (i ? ucFirst(name) : name)) + .join(''); + } for (let method of swagger._methods) { method = method.toLowerCase(); if (!pathItem[method]) { - pathItem[method] = operation; + if (operation.operationId && swagger._methods.length > 1) { + const op = Object.assign({}, operation); + pathItem[method] = op; + if (ids.has(op.operationId)) { + let i = 2; + while (ids.has(op.operationId + i)) { + i++; + } + op.operationId += i; + } + ids.add(op.operationId); + } else { + pathItem[method] = operation; + } } } } diff --git a/js/server/modules/@arangodb/foxx/templates/dcRouter.js.tmpl b/js/server/modules/@arangodb/foxx/templates/dcRouter.js.tmpl index 1c3abd1f54..5e095d5de6 100644 --- a/js/server/modules/@arangodb/foxx/templates/dcRouter.js.tmpl +++ b/js/server/modules/@arangodb/foxx/templates/dcRouter.js.tmpl @@ -21,6 +21,9 @@ const router = createRouter(); module.exports = router; +router.tag('<%= document %>'); + + router.get(function (req, res) { res.send(<%= documents %>.all()); }, 'list') diff --git a/js/server/modules/@arangodb/foxx/templates/ecRouter.js.tmpl b/js/server/modules/@arangodb/foxx/templates/ecRouter.js.tmpl index 270101f539..37e485f390 100644 --- a/js/server/modules/@arangodb/foxx/templates/ecRouter.js.tmpl +++ b/js/server/modules/@arangodb/foxx/templates/ecRouter.js.tmpl @@ -20,6 +20,10 @@ const HTTP_CONFLICT = status('conflict'); const router = createRouter(); module.exports = router; + +router.tag('<%= document %>'); + + const New<%= model %> = Object.assign({}, <%= model %>, { schema: Object.assign({}, <%= model %>.schema, { _from: joi.string(),