UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

102 lines (81 loc) 3.6 kB
const cds = require('../_runtime/cds') const HttpAdapter = require('../../lib/srv/protocols/http') const bodyParser4 = require('../http/body-parser') const parse4 = require('./middleware/parse') const create4 = require('./middleware/create') const read4 = require('./middleware/read') const update4 = require('./middleware/update') const upsert4 = require('./middleware/upsert') const delete4 = require('./middleware/delete') const operation = require('./middleware/operation') const { postProcessData } = require('./post-processing') class RestRequest extends cds.Request {} RestRequest.prototype.protocol = 'rest' // for batch subrequests where prototype is not properly set class RestAdapter extends HttpAdapter { request4(args) { return new RestRequest(args) } get router() { const srv = this.service const router = super.router.use(this.log_request) // service root router.head('/', (_, res) => res.json({})) const entities = Object.keys(srv.entities).map(e => ({ name: e, url: e })) router.get('/', (_, res) => res.json({ entities })) // validate headers router.use((req, res, next) => { if (req.method in { POST: 1, PUT: 1, PATCH: 1 } && req.headers['content-type']) { const parts = req.headers['content-type'].split(';') if (!parts[0].match(/^application\/json$/) || parts[1] === '') { throw cds.error('INVALID_CONTENT_TYPE_ONLY_JSON', { statusCode: 415, code: '415' }) // FIXME: better i18n + use res.status } } if (req.method in { PUT: 1, PATCH: 1 }) { if (req.headers['content-length'] === '0') { res.status(400).json({ error: { message: 'Malformed document', statusCode: 400, code: '400' } }) return } } return next() }) router.use(bodyParser4(this)) router.use(parse4(this)) // handlers const action_handler = operation(this) const crud_handlers = { HEAD: read4(this), GET: read4(this), PUT: cds.env.runtime.put_as_upsert ? upsert4(this) : update4(this), POST: create4(this), PATCH: cds.env.runtime.patch_as_upsert ? upsert4(this) : update4(this), DELETE: delete4(this) } router.use((req, res, next) => { const handle = req._operation ? action_handler : crud_handlers[req.method] handle(req, res).then(outcome => { // if authentication or something else within the processing of a cds.Request terminates the request, no need to continue if (res.headersSent) return next() const { result, status } = outcome // post process if (result) { let def = req._operation || cds.infer.target(req._query) const defs = srv.model.definitions if (typeof def === 'string') def = defs[def] || defs[def.split(':$:')[0]].actions[def.split(':$:')[1]] if (def) postProcessData(result, srv, def) } if (status && res.statusCode === 200) res.status(status) //> only set status if not yet modified if (req.method === 'HEAD') res.type('json').set('content-length', JSON.stringify(result).length).end() else res.send(typeof result === 'number' ? result.toString() : result) }, next) }) return router } normalized(err) { err = super.normalized(err) // Actually `code` is only required by OData, but we had that applied to // REST as well, so we keep it here for compatibility with legacy behavior. if (typeof err.code !== 'string') err.code = String(err.code || err.status || '') return err } } module.exports = RestAdapter