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