2020-03-29 20:25:43 +02:00
/ * *
* node / hooks / express / openapi . js
*
* This module generates OpenAPI definitions for each API version defined by
* APIHandler . js and hooks into express to route the API using openapi - backend .
*
* The openapi definition files are publicly available under :
*
* - / a p i / o p e n a p i . j s o n
* - / r e s t / o p e n a p i . j s o n
* - /api/ { version } / openapi . json
* - /rest/ { version } / openapi . json
* /
2020-03-29 16:25:52 +02:00
const OpenAPIBackend = require ( 'openapi-backend' ) . default ;
const formidable = require ( 'formidable' ) ;
const { promisify } = require ( 'util' ) ;
2020-03-29 20:25:43 +02:00
const cloneDeep = require ( 'lodash.clonedeep' ) ;
2020-04-01 18:18:02 +02:00
const createHTTPError = require ( 'http-errors' ) ;
2020-03-29 16:25:52 +02:00
const apiHandler = require ( '../../handler/APIHandler' ) ;
const settings = require ( '../../utils/Settings' ) ;
2020-03-29 20:56:49 +02:00
const isValidJSONPName = require ( './isValidJSONPName' ) ;
2020-03-29 16:25:52 +02:00
const log4js = require ( 'log4js' ) ;
const apiLogger = log4js . getLogger ( 'API' ) ;
// https://github.com/OAI/OpenAPI-Specification/tree/master/schemas/v3.0
const OPENAPI _VERSION = '3.0.2' ; // Swagger/OAS version
2020-03-29 18:47:42 +02:00
const info = {
title : 'Etherpad API' ,
description :
'Etherpad is a real-time collaborative editor scalable to thousands of simultaneous real time users. It provides full data export capabilities, and runs on your server, under your control.' ,
termsOfService : 'https://etherpad.org/' ,
contact : {
name : 'The Etherpad Foundation' ,
url : 'https://etherpad.org/' ,
email : 'support@example.com' ,
} ,
license : {
name : 'Apache 2.0' ,
url : 'https://www.apache.org/licenses/LICENSE-2.0.html' ,
} ,
version : apiHandler . latestApiVersion ,
} ;
2020-03-29 17:51:56 +02:00
const APIPathStyle = {
FLAT : 'api' , // flat paths e.g. /api/createGroup
REST : 'rest' , // restful paths e.g. /rest/group/create
} ;
2020-03-29 20:25:43 +02:00
// API resources - describe your API endpoints here
2020-03-29 18:47:42 +02:00
const resources = {
2020-03-29 16:39:45 +02:00
// Group
group : {
create : {
2020-03-29 20:25:43 +02:00
operationId : 'createGroup' ,
summary : 'creates a new group' ,
responseSchema : { groupID : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
createIfNotExistsFor : {
2020-03-29 20:25:43 +02:00
operationId : 'createGroupIfNotExistsFor' ,
summary : 'this functions helps you to map your application group ids to Etherpad group ids' ,
responseSchema : { groupID : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
delete : {
2020-03-29 20:25:43 +02:00
operationId : 'deleteGroup' ,
summary : 'deletes a group' ,
2020-03-29 16:39:45 +02:00
} ,
listPads : {
2020-03-29 20:25:43 +02:00
operationId : 'listPads' ,
summary : 'returns all pads of this group' ,
responseSchema : { padIDs : { type : 'array' , items : { type : 'string' } } } ,
2020-03-29 16:39:45 +02:00
} ,
createPad : {
2020-03-29 20:25:43 +02:00
operationId : 'createGroupPad' ,
summary : 'creates a new pad in this group' ,
2020-03-29 16:39:45 +02:00
} ,
listSessions : {
2020-03-29 20:25:43 +02:00
operationId : 'listSessionsOfGroup' ,
summary : '' ,
responseSchema : { sessions : { type : 'array' , items : { $ref : '#/components/schemas/SessionInfo' } } } ,
2020-03-29 16:39:45 +02:00
} ,
list : {
2020-03-29 20:25:43 +02:00
operationId : 'listAllGroups' ,
summary : '' ,
responseSchema : { groupIDs : { type : 'array' , items : { type : 'string' } } } ,
2020-03-29 16:39:45 +02:00
} ,
} ,
// Author
author : {
create : {
2020-03-29 20:25:43 +02:00
operationId : 'createAuthor' ,
summary : 'creates a new author' ,
responseSchema : { authorID : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
createIfNotExistsFor : {
2020-03-29 20:25:43 +02:00
operationId : 'createAuthorIfNotExistsFor' ,
summary : 'this functions helps you to map your application author ids to Etherpad author ids' ,
responseSchema : { authorID : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
listPads : {
2020-03-29 20:25:43 +02:00
operationId : 'listPadsOfAuthor' ,
summary : 'returns an array of all pads this author contributed to' ,
responseSchema : { padIDs : { type : 'array' , items : { type : 'string' } } } ,
2020-03-29 16:39:45 +02:00
} ,
listSessions : {
2020-03-29 20:25:43 +02:00
operationId : 'listSessionsOfAuthor' ,
summary : 'returns all sessions of an author' ,
responseSchema : { sessions : { type : 'array' , items : { $ref : '#/components/schemas/SessionInfo' } } } ,
2020-03-29 16:39:45 +02:00
} ,
// We need an operation that return a UserInfo so it can be picked up by the codegen :(
getName : {
2020-03-29 20:25:43 +02:00
operationId : 'getAuthorName' ,
summary : 'Returns the Author Name of the author' ,
responseSchema : { info : { $ref : '#/components/schemas/UserInfo' } } ,
2020-03-29 16:39:45 +02:00
} ,
} ,
// Session
session : {
create : {
2020-03-29 20:25:43 +02:00
operationId : 'createSession' ,
summary : 'creates a new session. validUntil is an unix timestamp in seconds' ,
responseSchema : { sessionID : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
delete : {
2020-03-29 20:25:43 +02:00
operationId : 'deleteSession' ,
summary : 'deletes a session' ,
2020-03-29 16:39:45 +02:00
} ,
// We need an operation that returns a SessionInfo so it can be picked up by the codegen :(
info : {
2020-03-29 20:25:43 +02:00
operationId : 'getSessionInfo' ,
summary : 'returns informations about a session' ,
responseSchema : { info : { $ref : '#/components/schemas/SessionInfo' } } ,
2020-03-29 16:39:45 +02:00
} ,
} ,
// Pad
pad : {
listAll : {
2020-03-29 20:25:43 +02:00
operationId : 'listAllPads' ,
summary : 'list all the pads' ,
responseSchema : { padIDs : { type : 'array' , items : { type : 'string' } } } ,
2020-03-29 16:39:45 +02:00
} ,
createDiffHTML : {
2020-03-29 20:25:43 +02:00
operationId : 'createDiffHTML' ,
summary : '' ,
responseSchema : { } ,
2020-03-29 16:39:45 +02:00
} ,
create : {
2020-03-29 20:25:43 +02:00
operationId : 'createPad' ,
2020-03-29 16:39:45 +02:00
description :
'creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad' ,
} ,
getText : {
2020-03-29 20:25:43 +02:00
operationId : 'getText' ,
summary : 'returns the text of a pad' ,
responseSchema : { text : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
setText : {
2020-03-29 20:25:43 +02:00
operationId : 'setText' ,
summary : 'sets the text of a pad' ,
2020-03-29 16:39:45 +02:00
} ,
getHTML : {
2020-03-29 20:25:43 +02:00
operationId : 'getHTML' ,
summary : 'returns the text of a pad formatted as HTML' ,
responseSchema : { html : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
setHTML : {
2020-03-29 20:25:43 +02:00
operationId : 'setHTML' ,
summary : 'sets the text of a pad with HTML' ,
2020-03-29 16:39:45 +02:00
} ,
getRevisionsCount : {
2020-03-29 20:25:43 +02:00
operationId : 'getRevisionsCount' ,
summary : 'returns the number of revisions of this pad' ,
responseSchema : { revisions : { type : 'integer' } } ,
2020-03-29 16:39:45 +02:00
} ,
getLastEdited : {
2020-03-29 20:25:43 +02:00
operationId : 'getLastEdited' ,
summary : 'returns the timestamp of the last revision of the pad' ,
responseSchema : { lastEdited : { type : 'integer' } } ,
2020-03-29 16:39:45 +02:00
} ,
delete : {
2020-03-29 20:25:43 +02:00
operationId : 'deletePad' ,
summary : 'deletes a pad' ,
2020-03-29 16:39:45 +02:00
} ,
getReadOnlyID : {
2020-03-29 20:25:43 +02:00
operationId : 'getReadOnlyID' ,
summary : 'returns the read only link of a pad' ,
responseSchema : { readOnlyID : { type : 'string' } } ,
2020-03-29 16:39:45 +02:00
} ,
setPublicStatus : {
2020-03-29 20:25:43 +02:00
operationId : 'setPublicStatus' ,
summary : 'sets a boolean for the public status of a pad' ,
2020-03-29 16:39:45 +02:00
} ,
getPublicStatus : {
2020-03-29 20:25:43 +02:00
operationId : 'getPublicStatus' ,
summary : 'return true of false' ,
responseSchema : { publicStatus : { type : 'boolean' } } ,
2020-03-29 16:39:45 +02:00
} ,
setPassword : {
2020-03-29 20:25:43 +02:00
operationId : 'setPassword' ,
summary : 'returns ok or a error message' ,
2020-03-29 16:39:45 +02:00
} ,
isPasswordProtected : {
2020-03-29 20:25:43 +02:00
operationId : 'isPasswordProtected' ,
summary : 'returns true or false' ,
responseSchema : { passwordProtection : { type : 'boolean' } } ,
2020-03-29 16:39:45 +02:00
} ,
authors : {
2020-03-29 20:25:43 +02:00
operationId : 'listAuthorsOfPad' ,
summary : 'returns an array of authors who contributed to this pad' ,
responseSchema : { authorIDs : { type : 'array' , items : { type : 'string' } } } ,
2020-03-29 16:39:45 +02:00
} ,
usersCount : {
2020-03-29 20:25:43 +02:00
operationId : 'padUsersCount' ,
summary : 'returns the number of user that are currently editing this pad' ,
responseSchema : { padUsersCount : { type : 'integer' } } ,
2020-03-29 16:39:45 +02:00
} ,
users : {
2020-03-29 20:25:43 +02:00
operationId : 'padUsers' ,
summary : 'returns the list of users that are currently editing this pad' ,
responseSchema : { padUsers : { type : 'array' , items : { $ref : '#/components/schemas/UserInfo' } } } ,
2020-03-29 16:39:45 +02:00
} ,
sendClientsMessage : {
2020-03-29 20:25:43 +02:00
operationId : 'sendClientsMessage' ,
summary : 'sends a custom message of type msg to the pad' ,
2020-03-29 16:39:45 +02:00
} ,
checkToken : {
2020-03-29 20:25:43 +02:00
operationId : 'checkToken' ,
summary : 'returns ok when the current api token is valid' ,
2020-03-29 16:39:45 +02:00
} ,
getChatHistory : {
2020-03-29 20:25:43 +02:00
operationId : 'getChatHistory' ,
summary : 'returns the chat history' ,
responseSchema : { messages : { type : 'array' , items : { $ref : '#/components/schemas/Message' } } } ,
2020-03-29 16:39:45 +02:00
} ,
// We need an operation that returns a Message so it can be picked up by the codegen :(
getChatHead : {
2020-03-29 20:25:43 +02:00
operationId : 'getChatHead' ,
summary : 'returns the chatHead (chat-message) of the pad' ,
responseSchema : { chatHead : { $ref : '#/components/schemas/Message' } } ,
2020-03-29 16:39:45 +02:00
} ,
appendChatMessage : {
2020-03-29 20:25:43 +02:00
operationId : 'appendChatMessage' ,
summary : 'appends a chat message' ,
2020-03-29 16:39:45 +02:00
} ,
} ,
} ;
2020-03-29 16:25:52 +02:00
const defaultResponses = {
2020-03-29 18:47:42 +02:00
Success : {
description : 'ok (code 0)' ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
code : {
type : 'integer' ,
example : 0 ,
} ,
message : {
type : 'string' ,
example : 'ok' ,
} ,
data : {
type : 'object' ,
example : null ,
} ,
} ,
} ,
} ,
} ,
} ,
ApiError : {
description : 'generic api error (code 1)' ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
code : {
type : 'integer' ,
example : 1 ,
} ,
message : {
type : 'string' ,
example : 'error message' ,
} ,
data : {
type : 'object' ,
example : null ,
} ,
} ,
} ,
} ,
} ,
} ,
InternalError : {
description : 'internal api error (code 2)' ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
code : {
type : 'integer' ,
example : 2 ,
} ,
message : {
type : 'string' ,
example : 'internal error' ,
} ,
data : {
type : 'object' ,
example : null ,
} ,
} ,
} ,
} ,
} ,
} ,
NotFound : {
description : 'no such function (code 4)' ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
code : {
type : 'integer' ,
example : 3 ,
} ,
message : {
type : 'string' ,
example : 'no such function' ,
} ,
data : {
type : 'object' ,
example : null ,
} ,
} ,
} ,
} ,
} ,
} ,
Unauthorized : {
description : 'no or wrong API key (code 4)' ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
code : {
type : 'integer' ,
example : 4 ,
} ,
message : {
type : 'string' ,
example : 'no or wrong API key' ,
} ,
data : {
type : 'object' ,
example : null ,
} ,
} ,
} ,
} ,
} ,
} ,
} ;
const defaultResponseRefs = {
2020-03-29 16:25:52 +02:00
200 : {
2020-03-29 18:47:42 +02:00
$ref : '#/components/responses/Success' ,
} ,
400 : {
$ref : '#/components/responses/ApiError' ,
} ,
401 : {
$ref : '#/components/responses/Unauthorized' ,
} ,
500 : {
$ref : '#/components/responses/InternalError' ,
2020-03-29 16:25:52 +02:00
} ,
} ;
2020-03-29 20:25:43 +02:00
// convert to a dictionary of operation objects
const operations = { } ;
2020-03-29 18:47:42 +02:00
for ( const resource in resources ) {
for ( const action in resources [ resource ] ) {
2020-03-29 20:25:43 +02:00
const { operationId , responseSchema , ... operation } = resources [ resource ] [ action ] ;
2020-03-29 18:47:42 +02:00
2020-03-29 20:25:43 +02:00
// add response objects
2020-03-29 18:47:42 +02:00
const responses = { ... defaultResponseRefs } ;
2020-03-29 20:25:43 +02:00
if ( responseSchema ) {
responses [ 200 ] = cloneDeep ( defaultResponses . Success ) ;
responses [ 200 ] . content [ 'application/json' ] . schema . properties . data = {
type : 'object' ,
properties : responseSchema ,
2020-03-29 18:47:42 +02:00
} ;
}
2020-03-29 20:25:43 +02:00
// add final operation object to dictionary
operations [ operationId ] = {
2020-03-29 16:25:52 +02:00
operationId ,
2020-03-29 20:25:43 +02:00
... operation ,
2020-03-29 18:47:42 +02:00
responses ,
2020-03-29 16:25:52 +02:00
tags : [ resource ] ,
_restPath : ` / ${ resource } / ${ action } ` ,
} ;
}
}
const generateDefinitionForVersion = ( version , style = APIPathStyle . FLAT ) => {
const definition = {
openapi : OPENAPI _VERSION ,
2020-03-29 18:47:42 +02:00
info ,
2020-03-29 16:25:52 +02:00
paths : { } ,
components : {
parameters : { } ,
schemas : {
SessionInfo : {
type : 'object' ,
properties : {
id : {
type : 'string' ,
} ,
authorID : {
type : 'string' ,
} ,
groupID : {
type : 'string' ,
} ,
validUntil : {
type : 'integer' ,
} ,
} ,
} ,
UserInfo : {
type : 'object' ,
properties : {
id : {
type : 'string' ,
} ,
colorId : {
type : 'string' ,
} ,
name : {
type : 'string' ,
} ,
timestamp : {
type : 'integer' ,
} ,
} ,
} ,
Message : {
type : 'object' ,
properties : {
text : {
type : 'string' ,
} ,
userId : {
type : 'string' ,
} ,
userName : {
type : 'string' ,
} ,
time : {
type : 'integer' ,
} ,
} ,
} ,
} ,
2020-03-29 18:47:42 +02:00
responses : {
... defaultResponses ,
} ,
2020-03-29 16:25:52 +02:00
securitySchemes : {
ApiKey : {
type : 'apiKey' ,
in : 'query' ,
name : 'apikey' ,
} ,
} ,
} ,
security : [ { ApiKey : [ ] } ] ,
} ;
// build operations
for ( const funcName in apiHandler . version [ version ] ) {
let operation = { } ;
if ( operations [ funcName ] ) {
operation = { ... operations [ funcName ] } ;
} else {
// console.warn(`No operation found for function: ${funcName}`);
operation = {
operationId : funcName ,
2020-03-29 18:47:42 +02:00
responses : defaultResponseRefs ,
2020-03-29 16:25:52 +02:00
} ;
}
// set parameters
operation . parameters = operation . parameters || [ ] ;
for ( const paramName of apiHandler . version [ version ] [ funcName ] ) {
operation . parameters . push ( { $ref : ` #/components/parameters/ ${ paramName } ` } ) ;
if ( ! definition . components . parameters [ paramName ] ) {
definition . components . parameters [ paramName ] = {
name : paramName ,
in : 'query' ,
schema : {
type : 'string' ,
} ,
} ;
}
}
// set path
let path = ` / ${ operation . operationId } ` ; // APIPathStyle.FLAT
if ( style === APIPathStyle . REST && operation . _restPath ) {
path = operation . _restPath ;
}
delete operation . _restPath ;
// add to definition
// NOTE: It may be confusing that every operation can be called with both GET and POST
definition . paths [ path ] = {
get : {
... operation ,
operationId : ` ${ operation . operationId } UsingGET ` ,
} ,
post : {
... operation ,
operationId : ` ${ operation . operationId } UsingPOST ` ,
} ,
} ;
}
return definition ;
} ;
2020-03-29 20:25:43 +02:00
exports . expressCreateServer = async ( _ , args ) => {
2020-03-29 16:25:52 +02:00
const { app } = args ;
2020-03-29 20:25:43 +02:00
// create openapi-backend handlers for each api version under /api/{version}/*
2020-03-29 16:25:52 +02:00
for ( const version in apiHandler . version ) {
2020-03-29 20:25:43 +02:00
// we support two different styles of api: flat + rest
// TODO: do we really want to support both?
2020-03-29 16:25:52 +02:00
for ( const style of [ APIPathStyle . FLAT , APIPathStyle . REST ] ) {
const apiRoot = getApiRootForVersion ( version , style ) ;
// generate openapi definition for this API version
const definition = generateDefinitionForVersion ( version , style ) ;
2020-03-29 20:25:43 +02:00
// serve version specific openapi definition
2020-03-29 16:25:52 +02:00
app . get ( ` ${ apiRoot } /openapi.json ` , ( req , res ) => {
2020-03-30 03:52:25 +02:00
// For openapi definitions, wide CORS is probably fine
2020-03-29 18:47:42 +02:00
res . header ( 'Access-Control-Allow-Origin' , '*' ) ;
2020-03-29 16:25:52 +02:00
res . json ( { ... definition , servers : [ generateServerForApiVersion ( apiRoot , req ) ] } ) ;
} ) ;
// serve latest openapi definition file under /api/openapi.json
2020-03-29 20:25:43 +02:00
const isLatestAPIVersion = version === apiHandler . latestApiVersion ;
if ( isLatestAPIVersion ) {
2020-03-29 16:25:52 +02:00
app . get ( ` / ${ style } /openapi.json ` , ( req , res ) => {
2020-03-29 18:47:42 +02:00
res . header ( 'Access-Control-Allow-Origin' , '*' ) ;
2020-03-29 16:25:52 +02:00
res . json ( { ... definition , servers : [ generateServerForApiVersion ( apiRoot , req ) ] } ) ;
} ) ;
}
// build openapi-backend instance for this api version
const api = new OpenAPIBackend ( {
2020-03-29 20:25:43 +02:00
apiRoot , // each api version has its own root
2020-03-29 16:25:52 +02:00
definition ,
2020-03-29 16:39:45 +02:00
validate : false ,
2020-03-29 20:25:43 +02:00
// for a small optimisation, we can run the quick startup for older
// API versions since they are subsets of the latest api definition
quick : ! isLatestAPIVersion ,
2020-03-29 16:25:52 +02:00
} ) ;
// register default handlers
api . register ( {
2020-04-01 18:18:02 +02:00
notFound : ( ) => {
throw new createHTTPError . notFound ( 'no such function' ) ;
2020-03-29 16:25:52 +02:00
} ,
2020-04-01 18:18:02 +02:00
notImplemented : ( ) => {
throw new createHTTPError . notImplemented ( 'function not implemented' ) ;
2020-03-29 16:25:52 +02:00
} ,
} ) ;
2020-04-01 18:18:02 +02:00
// register operation handlers
2020-03-29 16:25:52 +02:00
for ( const funcName in apiHandler . version [ version ] ) {
const handler = async ( c , req , res ) => {
// parse fields from request
const { header , params , query } = c . request ;
2020-03-29 20:25:43 +02:00
// read form data if method was POST
2020-03-29 16:25:52 +02:00
let formData = { } ;
if ( c . request . method === 'post' ) {
const form = new formidable . IncomingForm ( ) ;
const parseForm = promisify ( form . parse ) . bind ( form ) ;
formData = await parseForm ( req ) ;
}
const fields = Object . assign ( { } , header , params , query , formData ) ;
// log request
apiLogger . info ( ` REQUEST, v ${ version } : ${ funcName } , ${ JSON . stringify ( fields ) } ` ) ;
// pass to api handler
2020-04-01 18:18:02 +02:00
let data = await apiHandler . handle ( version , funcName , fields , req , res ) . catch ( ( err ) => {
// convert all errors to http errors
if ( err instanceof createHTTPError . HttpError ) {
// pass http errors thrown by handler forward
throw err ;
} else if ( err . name == 'apierror' ) {
// parameters were wrong and the api stopped execution, pass the error
// convert to http error
throw new createHTTPError . BadRequest ( err . message ) ;
} else {
// an unknown error happened
// log it and throw internal error
apiLogger . error ( err ) ;
throw new createHTTPError . InternalError ( 'internal error' ) ;
}
} ) ;
2020-03-29 19:17:36 +02:00
// return in common format
2020-04-01 18:18:02 +02:00
let response = { code : 0 , message : 'ok' , data : data || null } ;
2020-03-29 19:17:36 +02:00
// log response
apiLogger . info ( ` RESPONSE, ${ funcName } , ${ JSON . stringify ( response ) } ` ) ;
2020-04-01 18:18:02 +02:00
// return the response data
return response ;
2020-03-29 16:25:52 +02:00
} ;
// each operation can be called with either GET or POST
api . register ( ` ${ funcName } UsingGET ` , handler ) ;
api . register ( ` ${ funcName } UsingPOST ` , handler ) ;
}
// start and bind to express
api . init ( ) ;
2020-03-29 18:47:42 +02:00
app . use ( apiRoot , async ( req , res ) => {
2020-04-01 18:18:02 +02:00
let response = null ;
2020-03-29 19:17:36 +02:00
try {
2020-03-30 03:52:25 +02:00
if ( style === APIPathStyle . REST ) {
// @TODO: Don't allow CORS from everywhere
// This is purely to maintain compatibility with old swagger-node-express
res . header ( 'Access-Control-Allow-Origin' , '*' ) ;
}
2020-04-01 18:18:02 +02:00
// pass to openapi-backend handler
response = await api . handleRequest ( req , req , res ) ;
2020-03-29 19:17:36 +02:00
} catch ( err ) {
2020-04-01 18:18:02 +02:00
// handle http errors
res . statusCode = err . statusCode || 500 ;
// convert to our json response format
// https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
switch ( res . statusCode ) {
case 403 : // forbidden
response = { code : 4 , message : err . message , data : null } ;
break ;
case 401 : // unauthorized (no or wrong api key)
response = { code : 4 , message : err . message , data : null } ;
break ;
case 404 : // not found (no such function)
response = { code : 3 , message : err . message , data : null } ;
break ;
case 500 : // server error (internal error)
response = { code : 2 , message : err . message , data : null } ;
break ;
case 400 : // bad request (wrong parameters)
// respond with 200 OK to keep old behavior and pass tests
res . statusCode = 200 ; // @TODO: this is bad api design
response = { code : 1 , message : err . message , data : null } ;
break ;
default :
response = { code : 1 , message : err . message , data : null } ;
break ;
2020-03-29 19:17:36 +02:00
}
}
2020-04-01 18:18:02 +02:00
// support jsonp response format
if ( req . query . jsonp && isValidJSONPName . check ( req . query . jsonp ) ) {
res . header ( 'Content-Type' , 'application/javascript' ) ;
2020-06-27 09:47:16 +01:00
response = ` ${ req . query . jsonp } ( ${ JSON . stringify ( response ) } ) ` ;
2020-04-01 18:18:02 +02:00
}
// send response
return res . send ( response ) ;
2020-03-29 18:47:42 +02:00
} ) ;
2020-03-29 16:25:52 +02:00
}
}
} ;
2020-03-29 18:47:42 +02:00
// helper to get api root
const getApiRootForVersion = ( version , style = APIPathStyle . FLAT ) => ` / ${ style } / ${ version } ` ;
// helper to generate an OpenAPI server object when serving definitions
const generateServerForApiVersion = ( apiRoot , req ) => ( {
url : ` ${ settings . ssl ? 'https' : 'http' } :// ${ req . headers . host } ${ apiRoot } ` ,
} ) ;