UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

111 lines (95 loc) 3.88 kB
const cds = require('../../lib') const LOG = cds.log('odata') const HttpAdapter = require('../../lib/srv/protocols/http') const HttpRequest = require('../http/HttpRequest') const bodyParser4 = require('../http/body-parser') class ODataRequest extends HttpRequest { get protocol() { return 'odata' } } const operation4 = require('./middleware/operation') const create4 = require('./middleware/create') const stream4 = require('./middleware/stream') const read4 = require('./middleware/read') const update4 = require('./middleware/update') const delete4 = require('./middleware/delete') const error4 = require('./middleware/error') const { isStream } = require('./utils') module.exports = class ODataAdapter extends HttpAdapter { request4(args) { return new ODataRequest(args) } get router() { function set_odata_version(_, res, next) { res.set('OData-Version', '4.0') next() } function validate_representation_headers(req, res, next) { if (req.method === 'PUT' && isStream(req._query)) { req.body = { value: req } return next() } if (req.method === 'POST' && req.headers['content-type']?.match(/multipart\/mixed/)) { return next() } const contentLength = req.headers['content-length'] const [type = '', suffix] = (req.headers['content-type'] || '').split(';') // header ending with semicolon is not allowed const isJson = type.match(/^application\/json$/) && suffix !== '' // POST with empty body is allowed if no content-type header is set if (req.method === 'POST' && (!contentLength || isJson)) return jsonBodyParser(req, res, next) if (req.method in { POST: 1, PUT: 1, PATCH: 1 }) { if (!isJson) { res.status(415).json({ error: { message: 'Unsupported Media Type', statusCode: 415, code: '415' } }) return } if (contentLength === '0') { res.status(400).json({ error: { message: 'Expected non-empty body', statusCode: 400, code: '400' } }) return } } return jsonBodyParser(req, res, next) } function validate_operation_http_method(req, _, next) { // operations must have been handled above (POST or GET) const { operation } = req._query.SELECT?.from.ref?.slice(-1)[0] || {} next(operation ? { code: 405 } : undefined) } const jsonBodyParser = bodyParser4(this) return ( super.router .use(set_odata_version) // service root .all('/', require('./middleware/service-document')(this)) .all('/\\$metadata', require('./middleware/metadata')(this)) // parse .use(require('./middleware/parse')(this)) .use(validate_representation_headers) // batch // .all is used deliberately instead of .use so that the matched path is not stripped from req properties .all('/\\$batch', require('./middleware/batch')(this)) // handle .head('*', (_, res) => res.sendStatus(405)) .post('*', operation4(this), create4(this)) .get('*', operation4(this), stream4(this), read4(this)) .use(validate_operation_http_method) .put('*', update4(this), create4(this, 'upsert')) .patch('*', update4(this), create4(this, 'upsert')) .delete('*', delete4(this)) // error .use(error4(this)) ) } // REVISIT: this impl recreates the behavior of the old adapter, but is not very clean log(req) { // req.__proto__.method is set in case of upsert if (req.__proto__.method in { PUT: 1, PATCH: 1 }) return // REVISIT: voodoo magic if (req._subrequest) { //> req._subrequest is set for batch subrequests LOG._info && LOG.info('>', req.method, req.path, Object.keys(req.query).length ? { ...req.query } : '') } else { super.log(req) } } }