@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
111 lines (95 loc) • 3.88 kB
JavaScript
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)
}
}
}