@@ -212,6 +212,7 @@ import {
212212 type ConfigureDescriptionData ,
213213 type EntityData ,
214214 type EntityInterfaceSubgraphData ,
215+ type DirectiveDefinitionData ,
215216 type EnumDefinitionData ,
216217 type EnumValueData ,
217218 ExtensionType ,
@@ -378,6 +379,7 @@ import {
378379 type FieldSetParentResult ,
379380 type HandleCostDirectiveParams ,
380381 type HandleListSizeDirectiveParams ,
382+ type RecordDirectiveWeightOnFieldParams ,
381383 type HandleOverrideDirectiveParams ,
382384 type HandleRequiresScopesDirectiveParams ,
383385 type HandleSemanticNonNullDirectiveParams ,
@@ -732,7 +734,10 @@ export class NormalizationFactory {
732734 if ( isAuthenticated ) {
733735 this . handleAuthenticatedDirective ( data , parentTypeName ) ;
734736 }
735- if ( isSemanticNonNull && isField ) {
737+ if ( ! isField ) {
738+ return errorMessages ;
739+ }
740+ if ( isSemanticNonNull ) {
736741 // The default argument for levels is [0], so a non-null wrapper is invalid.
737742 if ( isTypeRequired ( data . type ) ) {
738743 errorMessages . push (
@@ -745,11 +750,14 @@ export class NormalizationFactory {
745750 data . nullLevelsBySubgraphName . set ( this . subgraphName , new Set < number > ( [ 0 ] ) ) ;
746751 }
747752 }
748- if ( isListSize && isField && ! isTypeNodeListType ( data . type ) ) {
753+ if ( isListSize && ! isTypeNodeListType ( data . type ) ) {
749754 errorMessages . push (
750755 listSizeFieldMustReturnListOrUseSizedFieldsErrorMessage ( directiveCoords , printTypeNode ( data . type ) ) ,
751756 ) ;
752757 }
758+ if ( ! isCost && ! isListSize ) {
759+ this . recordDirectiveWeightOnField ( { data : data as FieldData , definitionData, directiveName, directiveNode } ) ;
760+ }
753761 return errorMessages ;
754762 }
755763 const definedArgumentNames = new Set < string > ( ) ;
@@ -812,8 +820,12 @@ export class NormalizationFactory {
812820 }
813821 if ( isCost ) {
814822 this . handleCostDirective ( { data, directiveCoords, directiveNode, errorMessages } ) ;
815- } else if ( isListSize && isField ) {
816- this . handleListSizeDirective ( { data, directiveCoords, directiveNode, errorMessages } ) ;
823+ } else if ( isField ) {
824+ if ( isListSize ) {
825+ this . handleListSizeDirective ( { data, directiveCoords, directiveNode, errorMessages } ) ;
826+ } else {
827+ this . recordDirectiveWeightOnField ( { data : data as FieldData , definitionData, directiveName, directiveNode } ) ;
828+ }
817829 }
818830 if ( duplicateArgumentNames . size > 0 ) {
819831 errorMessages . push ( duplicateDirectiveArgumentDefinitionsErrorMessage ( [ ...duplicateArgumentNames ] ) ) ;
@@ -2472,6 +2484,20 @@ export class NormalizationFactory {
24722484 data . nullLevelsBySubgraphName . set ( this . subgraphName , levels ) ;
24732485 }
24742486
2487+ getOrCreateFieldWeight ( typeName : TypeName , fieldName : FieldName ) : FieldWeightConfiguration {
2488+ const fieldCoords = `${ typeName } .${ fieldName } ` ;
2489+ return getValueOrDefault (
2490+ this . costs . fieldWeights ,
2491+ fieldCoords ,
2492+ ( ) : FieldWeightConfiguration => ( {
2493+ typeName,
2494+ fieldName,
2495+ argumentWeights : new Map ( ) ,
2496+ directiveArgumentWeights : new Map ( ) ,
2497+ } ) ,
2498+ ) ;
2499+ }
2500+
24752501 handleCostDirective ( { data, directiveCoords, directiveNode, errorMessages } : HandleCostDirectiveParams ) {
24762502 const weightArg = directiveNode . arguments ?. find ( ( arg ) => arg . name . value === WEIGHT ) ;
24772503 if ( ! weightArg || weightArg . value . kind !== Kind . INT ) {
@@ -2495,16 +2521,7 @@ export class NormalizationFactory {
24952521 errorMessages . push ( costOnInterfaceFieldErrorMessage ( directiveCoords ) ) ;
24962522 break ;
24972523 }
2498- const fieldCoords = `${ typeName } .${ data . name } ` ;
2499- const fieldWeight = getValueOrDefault (
2500- this . costs . fieldWeights ,
2501- fieldCoords ,
2502- ( ) : FieldWeightConfiguration => ( {
2503- typeName,
2504- fieldName : data . name ,
2505- argumentWeights : new Map ( ) ,
2506- } ) ,
2507- ) ;
2524+ const fieldWeight = this . getOrCreateFieldWeight ( typeName , data . name ) ;
25082525 fieldWeight . weight = weightValue ;
25092526 break ;
25102527 }
@@ -2522,36 +2539,68 @@ export class NormalizationFactory {
25222539 errorMessages . push ( costOnInterfaceFieldErrorMessage ( directiveCoords ) ) ;
25232540 break ;
25242541 }
2525- const parentFieldCoords = `${ typeName } .${ ivData . fieldName } ` ;
2526- const fieldWeight = getValueOrDefault (
2527- this . costs . fieldWeights ,
2528- parentFieldCoords ,
2529- ( ) : FieldWeightConfiguration => ( {
2530- typeName,
2531- fieldName : ivData . fieldName ! ,
2532- argumentWeights : new Map ( ) ,
2533- } ) ,
2534- ) ;
2542+ const fieldWeight = this . getOrCreateFieldWeight ( typeName , ivData . fieldName ! ) ;
25352543 fieldWeight . argumentWeights . set ( ivData . name , weightValue ) ;
25362544 } else {
25372545 const typeName = ivData . renamedParentTypeName || ivData . originalParentTypeName ;
2538- const fieldCoords = `${ typeName } .${ ivData . name } ` ;
2539- const fieldWeight = getValueOrDefault (
2540- this . costs . fieldWeights ,
2541- fieldCoords ,
2542- ( ) : FieldWeightConfiguration => ( {
2543- typeName,
2544- fieldName : ivData . name ,
2545- argumentWeights : new Map ( ) ,
2546- } ) ,
2547- ) ;
2546+ const fieldWeight = this . getOrCreateFieldWeight ( typeName , ivData . name ) ;
25482547 fieldWeight . weight = weightValue ;
25492548 }
25502549 break ;
25512550 }
25522551 }
25532552 }
25542553
2554+ recordDirectiveWeightOnField ( {
2555+ data,
2556+ definitionData,
2557+ directiveName,
2558+ directiveNode,
2559+ } : RecordDirectiveWeightOnFieldParams ) {
2560+ // This method walks every argument defined on the directive and records a directive weight
2561+ // on the field only when all these conditions hold:
2562+ // 1. The argument of the directive has a cost weight assigned
2563+ // 2. The argument is non-null
2564+ // 3. The parent type is not an interface type.
2565+ const typeName = data . renamedParentTypeName || data . originalParentTypeName ;
2566+ const parentTypeData = this . parentDefinitionDataByTypeName . get ( typeName ) ;
2567+ // Directive argument weights should only be recorded for concrete type fields.
2568+ if ( ! parentTypeData || parentTypeData . kind === Kind . INTERFACE_TYPE_DEFINITION ) {
2569+ return ;
2570+ }
2571+
2572+ // Determine which arguments are non-null on this directive usage.
2573+ // Record the DirectiveArgument coords if its argument has an explicit non-null value or
2574+ // if it has a default value and was not explicitly set to null.
2575+ const suppliedArgNodeByName = new Map < string , ConstValueNode > ( ) ;
2576+ for ( const arg of directiveNode . arguments ?? [ ] ) {
2577+ suppliedArgNodeByName . set ( arg . name . value , arg . value ) ;
2578+ }
2579+
2580+ for ( const [ argName , argData ] of definitionData . argumentTypeNodeByName ) {
2581+ const coords = `${ directiveName } .${ argName } ` ;
2582+ const argWeight = this . costs . directiveArgumentWeights . get ( coords ) ;
2583+ // Bail if the argName argument does not have cost attached to it.
2584+ if ( argWeight === undefined ) {
2585+ continue ;
2586+ }
2587+ // Check if this argument is non-null at the usage site:
2588+ const argNode = suppliedArgNodeByName . get ( argName ) ;
2589+ if ( argNode ) {
2590+ if ( argNode . kind === Kind . NULL ) {
2591+ continue ;
2592+ }
2593+ } else if ( ! argData . defaultValue || argData . defaultValue . kind === Kind . NULL ) {
2594+ continue ;
2595+ }
2596+ const fieldWeight = this . getOrCreateFieldWeight ( typeName , data . name ) ;
2597+ // Accumulate across directive usages so that repeatable directives on the same
2598+ // field are charged once per usage.
2599+ const existingWeight = fieldWeight . directiveArgumentWeights . get ( coords ) ?? 0 ;
2600+ fieldWeight . directiveArgumentWeights . set ( coords , existingWeight + argWeight ) ;
2601+ }
2602+ }
2603+
25552604 handleListSizeDirective ( { data, directiveCoords, directiveNode, errorMessages } : HandleListSizeDirectiveParams ) {
25562605 const args = directiveNode . arguments ;
25572606 if ( ! args ) {
0 commit comments