@@ -20,6 +20,7 @@ import {
2020 isNullExpr ,
2121 isThisExpr ,
2222} from '@zenstackhq/language/ast' ;
23+ import { getContainerOfType } from 'langium' ;
2324import { P , match } from 'ts-pattern' ;
2425import { ExpressionContext } from './constants' ;
2526import { getEntityCheckerFunctionName } from './names' ;
@@ -40,6 +41,8 @@ type Options = {
4041 operationContext ?: 'read' | 'create' | 'update' | 'postUpdate' | 'delete' ;
4142} ;
4243
44+ type Casing = 'original' | 'upper' | 'lower' | 'capitalize' | 'uncapitalize' ;
45+
4346// a registry of function handlers marked with @func
4447const functionHandlers = new Map < string , PropertyDescriptor > ( ) ;
4548
@@ -150,7 +153,7 @@ export class TypeScriptExpressionTransformer {
150153 }
151154
152155 const args = expr . args . map ( ( arg ) => arg . value ) ;
153- return handler . value . call ( this , args , normalizeUndefined ) ;
156+ return handler . value . call ( this , expr , args , normalizeUndefined ) ;
154157 }
155158
156159 // #region function invocation handlers
@@ -168,7 +171,7 @@ export class TypeScriptExpressionTransformer {
168171 }
169172
170173 @func ( 'length' )
171- private _length ( args : Expression [ ] ) {
174+ private _length ( _invocation : InvocationExpr , args : Expression [ ] ) {
172175 const field = this . transform ( args [ 0 ] , false ) ;
173176 const min = getLiteral < number > ( args [ 1 ] ) ;
174177 const max = getLiteral < number > ( args [ 2 ] ) ;
@@ -188,7 +191,7 @@ export class TypeScriptExpressionTransformer {
188191 }
189192
190193 @func ( 'contains' )
191- private _contains ( args : Expression [ ] , normalizeUndefined : boolean ) {
194+ private _contains ( _invocation : InvocationExpr , args : Expression [ ] , normalizeUndefined : boolean ) {
192195 const field = this . transform ( args [ 0 ] , false ) ;
193196 const caseInsensitive = getLiteral < boolean > ( args [ 2 ] ) === true ;
194197 let result : string ;
@@ -201,34 +204,34 @@ export class TypeScriptExpressionTransformer {
201204 }
202205
203206 @func ( 'startsWith' )
204- private _startsWith ( args : Expression [ ] , normalizeUndefined : boolean ) {
207+ private _startsWith ( _invocation : InvocationExpr , args : Expression [ ] , normalizeUndefined : boolean ) {
205208 const field = this . transform ( args [ 0 ] , false ) ;
206209 const result = `${ field } ?.startsWith(${ this . transform ( args [ 1 ] , normalizeUndefined ) } )` ;
207210 return this . ensureBoolean ( result ) ;
208211 }
209212
210213 @func ( 'endsWith' )
211- private _endsWith ( args : Expression [ ] , normalizeUndefined : boolean ) {
214+ private _endsWith ( _invocation : InvocationExpr , args : Expression [ ] , normalizeUndefined : boolean ) {
212215 const field = this . transform ( args [ 0 ] , false ) ;
213216 const result = `${ field } ?.endsWith(${ this . transform ( args [ 1 ] , normalizeUndefined ) } )` ;
214217 return this . ensureBoolean ( result ) ;
215218 }
216219
217220 @func ( 'regex' )
218- private _regex ( args : Expression [ ] ) {
221+ private _regex ( _invocation : InvocationExpr , args : Expression [ ] ) {
219222 const field = this . transform ( args [ 0 ] , false ) ;
220223 const pattern = getLiteral < string > ( args [ 1 ] ) ;
221224 return this . ensureBooleanTernary ( args [ 0 ] , field , `new RegExp(${ JSON . stringify ( pattern ) } ).test(${ field } )` ) ;
222225 }
223226
224227 @func ( 'email' )
225- private _email ( args : Expression [ ] ) {
228+ private _email ( _invocation : InvocationExpr , args : Expression [ ] ) {
226229 const field = this . transform ( args [ 0 ] , false ) ;
227230 return this . ensureBooleanTernary ( args [ 0 ] , field , `z.string().email().safeParse(${ field } ).success` ) ;
228231 }
229232
230233 @func ( 'datetime' )
231- private _datetime ( args : Expression [ ] ) {
234+ private _datetime ( _invocation : InvocationExpr , args : Expression [ ] ) {
232235 const field = this . transform ( args [ 0 ] , false ) ;
233236 return this . ensureBooleanTernary (
234237 args [ 0 ] ,
@@ -238,20 +241,20 @@ export class TypeScriptExpressionTransformer {
238241 }
239242
240243 @func ( 'url' )
241- private _url ( args : Expression [ ] ) {
244+ private _url ( _invocation : InvocationExpr , args : Expression [ ] ) {
242245 const field = this . transform ( args [ 0 ] , false ) ;
243246 return this . ensureBooleanTernary ( args [ 0 ] , field , `z.string().url().safeParse(${ field } ).success` ) ;
244247 }
245248
246249 @func ( 'has' )
247- private _has ( args : Expression [ ] , normalizeUndefined : boolean ) {
250+ private _has ( _invocation : InvocationExpr , args : Expression [ ] , normalizeUndefined : boolean ) {
248251 const field = this . transform ( args [ 0 ] , false ) ;
249252 const result = `${ field } ?.includes(${ this . transform ( args [ 1 ] , normalizeUndefined ) } )` ;
250253 return this . ensureBoolean ( result ) ;
251254 }
252255
253256 @func ( 'hasEvery' )
254- private _hasEvery ( args : Expression [ ] , normalizeUndefined : boolean ) {
257+ private _hasEvery ( _invocation : InvocationExpr , args : Expression [ ] , normalizeUndefined : boolean ) {
255258 const field = this . transform ( args [ 0 ] , false ) ;
256259 return this . ensureBooleanTernary (
257260 args [ 0 ] ,
@@ -261,7 +264,7 @@ export class TypeScriptExpressionTransformer {
261264 }
262265
263266 @func ( 'hasSome' )
264- private _hasSome ( args : Expression [ ] , normalizeUndefined : boolean ) {
267+ private _hasSome ( _invocation : InvocationExpr , args : Expression [ ] , normalizeUndefined : boolean ) {
265268 const field = this . transform ( args [ 0 ] , false ) ;
266269 return this . ensureBooleanTernary (
267270 args [ 0 ] ,
@@ -271,13 +274,13 @@ export class TypeScriptExpressionTransformer {
271274 }
272275
273276 @func ( 'isEmpty' )
274- private _isEmpty ( args : Expression [ ] ) {
277+ private _isEmpty ( _invocation : InvocationExpr , args : Expression [ ] ) {
275278 const field = this . transform ( args [ 0 ] , false ) ;
276279 return `(!${ field } || ${ field } ?.length === 0)` ;
277280 }
278281
279282 @func ( 'check' )
280- private _check ( args : Expression [ ] ) {
283+ private _check ( _invocation : InvocationExpr , args : Expression [ ] ) {
281284 if ( ! isDataModelFieldReference ( args [ 0 ] ) ) {
282285 throw new TypeScriptExpressionTransformerError ( `First argument of check() must be a field` ) ;
283286 }
@@ -309,6 +312,52 @@ export class TypeScriptExpressionTransformer {
309312 return `${ entityCheckerFunc } (input.${ fieldRef . target . $refText } , context)` ;
310313 }
311314
315+ private toStringWithCaseChange ( value : string , casing : Casing ) {
316+ if ( ! value ) {
317+ return "''" ;
318+ }
319+ return match ( casing )
320+ . with ( 'original' , ( ) => `'${ value } '` )
321+ . with ( 'upper' , ( ) => `'${ value . toUpperCase ( ) } '` )
322+ . with ( 'lower' , ( ) => `'${ value . toLowerCase ( ) } '` )
323+ . with ( 'capitalize' , ( ) => `'${ value . charAt ( 0 ) . toUpperCase ( ) + value . slice ( 1 ) } '` )
324+ . with ( 'uncapitalize' , ( ) => `'${ value . charAt ( 0 ) . toLowerCase ( ) + value . slice ( 1 ) } '` )
325+ . exhaustive ( ) ;
326+ }
327+
328+ @func ( 'currentModel' )
329+ private _currentModel ( invocation : InvocationExpr , args : Expression [ ] ) {
330+ let casing : Casing = 'original' ;
331+ if ( args [ 0 ] ) {
332+ casing = getLiteral < string > ( args [ 0 ] ) as Casing ;
333+ }
334+
335+ const containingModel = getContainerOfType ( invocation , isDataModel ) ;
336+ if ( ! containingModel ) {
337+ throw new TypeScriptExpressionTransformerError ( 'currentModel() must be called inside a model' ) ;
338+ }
339+ return this . toStringWithCaseChange ( containingModel . name , casing ) ;
340+ }
341+
342+ @func ( 'currentOperation' )
343+ private _currentOperation ( _invocation : InvocationExpr , args : Expression [ ] ) {
344+ let casing : Casing = 'original' ;
345+ if ( args [ 0 ] ) {
346+ casing = getLiteral < string > ( args [ 0 ] ) as Casing ;
347+ }
348+
349+ if ( ! this . options . operationContext ) {
350+ throw new TypeScriptExpressionTransformerError (
351+ 'currentOperation() must be called inside an access policy rule'
352+ ) ;
353+ }
354+ let contextOperation = this . options . operationContext ;
355+ if ( contextOperation === 'postUpdate' ) {
356+ contextOperation = 'update' ;
357+ }
358+ return this . toStringWithCaseChange ( contextOperation , casing ) ;
359+ }
360+
312361 private ensureBoolean ( expr : string ) {
313362 if ( this . options . context === ExpressionContext . ValidationRule ) {
314363 // all fields are optional in a validation context, so we treat undefined
0 commit comments