@sap/cds-compiler
Version:
CDS (Core Data Services) compiler and backends
129 lines (112 loc) • 5.47 kB
JavaScript
'use strict';
const { isBuiltinType } = require('../base/builtins');
// Only to be used with validator.js - a correct this value needs to be provided!
/**
* Check bound/unbound actions and functions. These checks are only meaningful for the OData backend.
*
* @param {CSN.Artifact} art Definition to be checked: Either the action artifact or an entity with actions.
* @param {string} artName name of the definition
* @param {string} prop Ignored property, always "definitions"
* @param {CSN.Path} path path to the definition
*/
function checkActionOrFunction( art, artName, prop, path ) {
if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
return;
// const isMultiSchema = this.options.odataFormat === 'structured' &&
// (this.options.odataProxies || this.options.odataXServiceRefs);
const serviceName = this.csnUtils.getServiceName(artName);
if (art.kind === 'entity') {
for (const [ actName, act ] of Object.entries(art.actions)) {
if (act.params) {
checkExplicitBindingParameter.call(this, act.params, path.concat([ 'actions', actName, 'params' ]));
const params = Object.entries(act.params);
for (const [ paramName, param ] of params)
checkActionOrFunctionParameter.call(this, param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind, params);
}
if (act.returns)
checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ]), act.kind);
}
}
else {
if (art.params) {
for (const [ paramName, param ] of Object.entries(art.params))
checkActionOrFunctionParameter.call(this, param, path.concat([ 'params', paramName ]), art.kind);
}
if (art.returns)
checkReturns.bind(this)(art.returns, path.concat('returns'), art.kind);
}
/**
*
* @param {object} params parameter dictionary
* @param {CSN.Path} currPath to the action parameter
*/
function checkExplicitBindingParameter( params, currPath ) {
Object.entries(params).forEach(([ pn, p ], i) => {
const type = p.items?.type || p.type;
if (type === '$self' && !this.csn.definitions.$self && i > 0) {
this.error('def-invalid-param', currPath.concat(pn),
'Binding parameter is expected to appear in first position only');
}
});
}
/**
* Check the parameters of an action
*
* @param {object} param parameter object
* @param {CSN.Path} currPath path to the parameter
* @param {string} actKind 'action' or 'function'
*/
function checkActionOrFunctionParameter( param, currPath, actKind, params ) {
if ((param.items || param)?.type === '$self' && param === params?.[0][1])
return; // binding parameter
const paramType = param.type ? this.csnUtils.getFinalTypeInfo(param.type) : param;
if (!paramType)
return; // no type could be resolved
if (param.type && this.csnUtils.isAssocOrComposition(param))
this.error('ref-unexpected-assoc-type', currPath, { '#': `${ actKind }-param` });
if (paramType.items?.type)
checkActionOrFunctionParameter.call(this, paramType.items, currPath.concat('items'), actKind);
// check if the structured & user-defined is from the current service
checkUserDefinedType.bind(this)(paramType, param.type, currPath);
}
/**
* Check the return statement of an action
*
* @param {object} returns returns object
* @param {CSN.Path} currPath path to the returns object
* @param {string} actKind 'action' or 'function'
*/
function checkReturns( returns, currPath, actKind ) {
const finalReturnType = returns.type ? this.csnUtils.getFinalTypeInfo(returns.type) : returns;
if (!finalReturnType)
return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
if (this.csnUtils.isAssocOrComposition(returns))
this.error('ref-unexpected-assoc-type', currPath, { '#': `${ actKind }-returns` });
if (finalReturnType.items) // check array return type
checkReturns.call(this, finalReturnType.items, currPath.concat('items'), actKind);
else // check if return type is user defined from the current service
checkUserDefinedType.call(this, finalReturnType, returns.type, currPath);
}
/**
* Check non-builtin used types in actions
*
* @param {CSN.Artifact} type The final type definition
* @param {string} typeName Name of the type definition
* @param {CSN.Path} currPath The current path
*/
function checkUserDefinedType( type, typeName, currPath ) {
if (type.kind && type.kind !== 'type' && type.type && !isBuiltinType(this.csnUtils.getFinalTypeInfo(type.type)?.type)) {
const serviceOfType = this.csnUtils.getServiceName(typeName);
if (serviceName && serviceName !== serviceOfType) {
// if (!(isMultiSchema && serviceOfType)) {
this.error('ref-expected-service-type', currPath, { type: typeName, kind: type.kind, service: serviceName }, {
std: 'Referenced $(KIND) $(TYPE) can\'t be used in service $(SERVICE) because it is defined outside the service',
entity: 'Referenced entity $(TYPE) can\'t be used in service $(SERVICE) because it is defined outside the service',
type: 'Referenced type $(TYPE) can\'t be used in service $(SERVICE) because it is defined outside the service',
});
// }
}
}
}
}
module.exports = { checkActionOrFunction };