@@ -22,6 +22,7 @@ import {
2222 isLiteralExpr ,
2323 isModel ,
2424 isReferenceExpr ,
25+ isStringLiteral ,
2526 isTypeDef ,
2627} from '../generated/ast' ;
2728import {
@@ -103,10 +104,9 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
103104 }
104105 }
105106
106- if ( ! assignableToAttributeParam ( arg , paramDecl , attr ) ) {
107- accept ( 'error' , `Value is not assignable to parameter` , {
108- node : arg ,
109- } ) ;
107+ const argAssignable = assignableToAttributeParam ( arg , paramDecl , attr ) ;
108+ if ( ! argAssignable . result ) {
109+ accept ( 'error' , argAssignable . error , { node : arg } ) ;
110110 return ;
111111 }
112112
@@ -393,10 +393,21 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
393393 }
394394}
395395
396- function assignableToAttributeParam ( arg : AttributeArg , param : AttributeParam , attr : AttributeApplication ) : boolean {
396+ function assignableToAttributeParam (
397+ arg : AttributeArg ,
398+ param : AttributeParam ,
399+ attr : AttributeApplication ,
400+ ) :
401+ | {
402+ result : true ;
403+ }
404+ | { result : false ; error : string } {
405+ const genericError = { result : false , error : 'invalid argument type' } as const ;
406+ const success = { result : true } as const ;
407+
397408 const argResolvedType = arg . $resolvedType ;
398409 if ( ! argResolvedType ) {
399- return false ;
410+ return { result : false , error : 'unable to resolve argument type' } ;
400411 }
401412
402413 let dstType = param . type . type ;
@@ -405,10 +416,30 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
405416 if ( dstType === 'ContextType' ) {
406417 // ContextType is inferred from the attribute's container's type
407418 if ( isDataField ( attr . $container ) ) {
408- // If the field is Typed JSON, and the attribute is @default, the argument must be a string
409- const dstIsTypedJson = hasAttribute ( attr . $container , '@json' ) ;
410- if ( dstIsTypedJson && attr . decl . ref ?. name === '@default' ) {
411- return argResolvedType . decl === 'String' ;
419+ // If the field is JSON, and the attribute is @default, the argument must be a JSON string
420+ // (design inherited from Prisma)
421+ const dstIsJson = attr . $container . type . type === 'Json' || hasAttribute ( attr . $container , '@json' ) ;
422+ if ( dstIsJson && attr . decl . ref ?. name === '@default' ) {
423+ if ( attr . $container . type . array && attr . $container . type . type === 'Json' ) {
424+ // Json[] default value, must be array of JSON strings
425+ if ( isArrayExpr ( arg . value ) && arg . value . items . every ( ( item ) => isLiteralJsonString ( item ) ) ) {
426+ return success ;
427+ } else {
428+ return {
429+ result : false ,
430+ error : 'expected an array of JSON string literals' ,
431+ } ;
432+ }
433+ } else {
434+ if ( isLiteralJsonString ( arg . value ) ) {
435+ return success ;
436+ } else {
437+ return {
438+ result : false ,
439+ error : 'expected a JSON string literal' ,
440+ } ;
441+ }
442+ }
412443 }
413444 dstIsArray = attr . $container . type . array ;
414445 }
@@ -417,30 +448,45 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
417448 const dstRef = param . type . reference ;
418449
419450 if ( dstType === 'Any' && ! dstIsArray ) {
420- return true ;
451+ return success ;
421452 }
422453
423454 if ( argResolvedType . decl === 'Any' ) {
424455 // arg is any type
425456 if ( ! argResolvedType . array ) {
426457 // if it's not an array, it's assignable to any type
427- return true ;
458+ return success ;
428459 } else {
429460 // otherwise it's assignable to any array type
430- return argResolvedType . array === dstIsArray ;
461+ if ( argResolvedType . array === dstIsArray ) {
462+ return success ;
463+ } else {
464+ return {
465+ result : false ,
466+ error : `expected ${ dstIsArray ? 'array' : 'non-array' } ` ,
467+ } ;
468+ }
431469 }
432470 }
433471
434472 // destination is field reference or transitive field reference, check if
435473 // argument is reference or array or reference
436474 if ( dstType === 'FieldReference' || dstType === 'TransitiveFieldReference' ) {
437475 if ( dstIsArray ) {
438- return (
476+ if (
439477 isArrayExpr ( arg . value ) &&
440- ! arg . value . items . find ( ( item ) => ! isReferenceExpr ( item ) || ! isDataField ( item . target . ref ) )
441- ) ;
478+ ! arg . value . items . some ( ( item ) => ! isReferenceExpr ( item ) || ! isDataField ( item . target . ref ) )
479+ ) {
480+ return success ;
481+ } else {
482+ return { result : false , error : 'expected an array of field references' } ;
483+ }
442484 } else {
443- return isReferenceExpr ( arg . value ) && isDataField ( arg . value . target . ref ) ;
485+ if ( isReferenceExpr ( arg . value ) && isDataField ( arg . value . target . ref ) ) {
486+ return success ;
487+ } else {
488+ return { result : false , error : 'expected a field reference' } ;
489+ }
444490 }
445491 }
446492
@@ -454,21 +500,30 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
454500 attrArgDeclType = resolved ( attr . $container . type . reference ) ;
455501 dstIsArray = attr . $container . type . array ;
456502 }
457- return attrArgDeclType === argResolvedType . decl && dstIsArray === argResolvedType . array ;
503+
504+ if ( attrArgDeclType !== argResolvedType . decl ) {
505+ return genericError ;
506+ }
507+
508+ if ( dstIsArray !== argResolvedType . array ) {
509+ return { result : false , error : `expected ${ dstIsArray ? 'array' : 'non-array' } ` } ;
510+ }
511+
512+ return success ;
458513 } else if ( dstType ) {
459514 // scalar type
460515
461516 if ( typeof argResolvedType ?. decl !== 'string' ) {
462517 // destination type is not a reference, so argument type must be a plain expression
463- return false ;
518+ return genericError ;
464519 }
465520
466521 if ( dstType === 'ContextType' ) {
467522 // attribute parameter type is ContextType, need to infer type from
468523 // the attribute's container
469524 if ( isDataField ( attr . $container ) ) {
470525 if ( ! attr . $container ?. type ?. type ) {
471- return false ;
526+ return genericError ;
472527 }
473528 dstType = mapBuiltinTypeToExpressionType ( attr . $container . type . type ) ;
474529 dstIsArray = attr . $container . type . array ;
@@ -477,10 +532,18 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
477532 }
478533 }
479534
480- return typeAssignable ( dstType , argResolvedType . decl , arg . value ) && dstIsArray === argResolvedType . array ;
535+ if ( typeAssignable ( dstType , argResolvedType . decl , arg . value ) && dstIsArray === argResolvedType . array ) {
536+ return success ;
537+ } else {
538+ return genericError ;
539+ }
481540 } else {
482541 // reference type
483- return ( dstRef ?. ref === argResolvedType . decl || dstType === 'Any' ) && dstIsArray === argResolvedType . array ;
542+ if ( ( dstRef ?. ref === argResolvedType . decl || dstType === 'Any' ) && dstIsArray === argResolvedType . array ) {
543+ return success ;
544+ } else {
545+ return genericError ;
546+ }
484547 }
485548}
486549
@@ -552,3 +615,15 @@ export function validateAttributeApplication(
552615) {
553616 new AttributeApplicationValidator ( ) . validate ( attr , accept , contextDataModel ) ;
554617}
618+
619+ function isLiteralJsonString ( value : Expression ) {
620+ if ( ! isStringLiteral ( value ) ) {
621+ return false ;
622+ }
623+ try {
624+ JSON . parse ( value . value ) ;
625+ return true ;
626+ } catch {
627+ return false ;
628+ }
629+ }
0 commit comments