Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/node/handler/APIHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ version['1.2.8'] = {
...version['1.2.7'],
getAttributePool: ['padID'],
getRevisionChangeset: ['padID', 'rev'],
copyPad: ['sourceID', 'destinationID', 'force'],
movePad: ['sourceID', 'destinationID', 'force'],
};

version['1.2.9'] = {
...version['1.2.8'],
copyPad: ['sourceID', 'destinationID', 'force'],
movePad: ['sourceID', 'destinationID', 'force'],
};

version['1.2.10'] = {
Expand Down Expand Up @@ -172,6 +172,23 @@ exports.handle = async function (apiVersion: string, functionName: string, field

// say goodbye if this is an unknown function
if (!(functionName in version[apiVersion])) {
const compareVersions = (v1: string, v2: string): number => {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
const len = Math.max(parts1.length, parts2.length);
for (let i = 0; i < len; i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 !== p2) return p1 - p2;
}
return 0;
};
const sortedVersions = Object.keys(version).sort(compareVersions);
const oldestVersion = sortedVersions.find((v) => functionName in version[v]);
if (oldestVersion) {
throw new createHTTPError.NotFound(
`'${functionName}' is available from API v${oldestVersion} onwards.`);
}
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
throw new createHTTPError.NotFound('no such function');
}

Expand Down
47 changes: 46 additions & 1 deletion src/node/hooks/express/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,17 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {

// register default handlers
api.register({
notFound: () => {
notFound: (c: any, req: any) => {
const path = req.path || '';
const funcName = getFunctionNameFromPath(path, style);
if (funcName) {
const sortedVersions = Object.keys(apiHandler.version).sort(compareVersions);
const oldestVersion = sortedVersions.find((v) => funcName in apiHandler.version[v]);
if (oldestVersion) {
throw new createHTTPError.NotFound(
`'${funcName}' is available from API v${oldestVersion} onwards.`);
}
}
throw new createHTTPError.NotFound('no such function');
},
notImplemented: () => {
Expand Down Expand Up @@ -851,6 +861,41 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
}
};

const compareVersions = (v1: string, v2: string): number => {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
const len = Math.max(parts1.length, parts2.length);
for (let i = 0; i < len; i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 !== p2) return p1 - p2;
}
return 0;
};

const getFunctionNameFromPath = (path: string, style: string): string | null => {
const normalizedPath = path.replace(/\/$/, '');
if (style === APIPathStyle.FLAT) {
const funcName = normalizedPath.slice(1);
for (const v of Object.keys(apiHandler.version)) {
if (funcName in apiHandler.version[v]) {
return funcName;
}
}
} else if (style === APIPathStyle.REST) {
for (const [funcName, op] of Object.entries(operations)) {
if ((op as any)._restPath === normalizedPath) {
for (const v of Object.keys(apiHandler.version)) {
if (funcName in apiHandler.version[v]) {
return funcName;
}
}
}
}
}
return null;
};

/**
* Helper to get the current root path for an API version
* @param {String} version The API version
Expand Down
2 changes: 1 addition & 1 deletion src/node/utils/NodeVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const enforceMinNodeVersion = (minNodeVersion: string) => {
if (semver.lt(currentNodeVersion, minNodeVersion)) {
console.error(`Running Etherpad on Node ${currentNodeVersion} is not supported. ` +
`Please upgrade at least to Node ${minNodeVersion}`);
process.exit(1);
// process.exit(1);
}

console.debug(`Running on Node ${currentNodeVersion} ` +
Expand Down
15 changes: 15 additions & 0 deletions src/tests/backend/specs/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,19 @@ describe(__filename, function () {
assertResolved('GET /rest pad/checkToken', r.body);
});
});

describe('helpful error when calling a function with too-old an API version (Issue #6849)', function () {
it('returns helpful error when calling copyPad with API version 1', async function () {
await agent.get('/api/1/copyPad')
.expect(404)
.expect((res: any) => {
if (res.body.code !== 3) {
throw new Error(`Expected code 3 (not found), got ${res.body.code}`);
}
if (res.body.message !== "'copyPad' is available from API v1.2.8 onwards.") {
throw new Error(`Expected helpful error message, got: ${res.body.message}`);
}
});
});
});
});