Skip to content

Commit

Permalink
Properly handle reference types to string fields for enums (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
daogrady authored Jan 13, 2025
1 parent d32426a commit 4a1b2ca
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
### Deprecated
### Removed
### Fixed
- referencing another entity's property of type `cds.String` in an enum will not properly quote the generated values
### Security

## [0.31.0] - 2024-12-16
Expand Down
6 changes: 3 additions & 3 deletions lib/components/enum.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function printEnum(buffer, name, kvs, options = {}, doc=[]) {
* Converts a CSN type describing an enum into a list of kv-pairs.
* Values from CSN are unwrapped from their `.val` structure and
* will fall back to the key if no value is provided.
* @param {import('../typedefs').resolver.EnumCSN} enumCsn - the CSN type describing the enum
* @param {import('../typedefs').resolver.EnumCSN & { resolvedType?: string }} enumCsn - the CSN type describing the enum
* @param {{unwrapVals: boolean} | {}} options - if `unwrapVals` is passed,
* then the CSN structure `{val:x}` is flattened to just `x`.
* Retaining `val` is closer to the actual CSN structure and should be used where we want
Expand All @@ -81,10 +81,10 @@ function printEnum(buffer, name, kvs, options = {}, doc=[]) {
* csnToEnumPairs(csn, {unwrapVals: false}) // -> [['X', {val:'a'}], ['Y': {val:'b'}], ['Z':'Z']]
* ```
*/
const csnToEnumPairs = ({enum: enm, type}, options = {}) => {
const csnToEnumPairs = ({enum: enm, type, resolvedType}, options = {}) => {
const actualOptions = {...{unwrapVals: true}, ...options}
return Object.entries(enm).map(([k, v]) => {
const val = enumVal(k, v.val, type)
const val = enumVal(k, v.val, resolvedType ?? type) // if type is a ref, prefer the resolvedType to catch references to cds.Strings
return [k, (actualOptions.unwrapVals ? val : { val })]
})
}
Expand Down
22 changes: 21 additions & 1 deletion lib/csn.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,25 @@ const getProjectionAliases = entity => {
return { aliases, all }
}

/**
* Heuristic way of looking up a reference type.
* We currently only support up to two segments,
* the first referring to the entity, a possible second
* referring to an element of the entity.
* @param {CSN} csn - CSN
* @param {string[]} ref - reference
* @returns {EntityCSN}
*/
function lookUpRefType (csn, ref) {
if (ref.length > 2) throw new Error(`Unsupported reference type ${ref.join('.')} with ${ref.length} segments. Please report this error.`)
/** @type {EntityCSN | undefined} */
let result = csn.definitions[ref[0]] // entity
if (ref.length === 1) return result
result = result?.elements?.[ref[1]] // property
if (!result) throw new Error(`Failed to look up reference type ${ref.join('.')}`)
return result
}

module.exports = {
collectDraftEnabledEntities,
isView,
Expand All @@ -352,5 +371,6 @@ module.exports = {
getViewTarget,
propagateForeignKeys,
propagateInflectionAnnotations,
isCsnAny
isCsnAny,
lookUpRefType
}
3 changes: 2 additions & 1 deletion lib/typedefs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export module resolver {


export type EnumCSN = EntityCSN & {
enum: {[key:name]: string}
enum: {[key:name]: string},
resolvedType?: string // custom property! When .type points to a ref, the visitor will resolve the ref into this property
}

export type CSN = {
Expand Down
5 changes: 4 additions & 1 deletion lib/visitor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const { propagateForeignKeys, propagateInflectionAnnotations, collectDraftEnabledEntities, isDraftEnabled, isType, getMaxCardinality, isViewOrProjection, isEnum, isEntity } = require('./csn')
const { propagateForeignKeys, propagateInflectionAnnotations, collectDraftEnabledEntities, isDraftEnabled, isType, getMaxCardinality, isViewOrProjection, isEnum, isEntity, lookUpRefType } = require('./csn')
// eslint-disable-next-line no-unused-vars
const { SourceFile, FileRepository, Buffer, Path } = require('./file')
const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
Expand Down Expand Up @@ -272,6 +272,9 @@ class Visitor {
initialiser: propertyToInlineEnumName(clean, e.name),
isStatic: true,
}))
if (typeof e?.type !== 'string' && e?.type?.ref) {
e.resolvedType = /** @type {string} */(lookUpRefType(this.csn, e.type.ref)?.type)
}
file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}), buffer, eDoc)
}

Expand Down
4 changes: 4 additions & 0 deletions test/unit/files/enums/model.cds
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ entity ExternalEnums {
gender: Gender;
yesno: Truthy;
}

entity ReferingType {
gender: type of InlineEnums : gender
}

0 comments on commit 4a1b2ca

Please sign in to comment.