001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.control.user.graphql.server.strategy; 018 019import graphql.ExecutionResult; 020import graphql.annotations.strategies.EnhancedExecutionStrategy; 021import graphql.execution.ExecutionContext; 022import graphql.execution.ExecutionStrategyParameters; 023import graphql.execution.NonNullableFieldValidator; 024import graphql.schema.GraphQLObjectType; 025import java.util.Map; 026import java.util.concurrent.CompletableFuture; 027 028public class GraphQlExecutionStrategy 029 extends EnhancedExecutionStrategy { 030 031 private static final String CLIENT_MUTATION_ID = "clientMutationId"; 032 033 @Override 034 protected CompletableFuture<ExecutionResult> resolveField(ExecutionContext executionContext, 035 ExecutionStrategyParameters parameters) { 036 CompletableFuture<ExecutionResult> result; 037 var parentInfo = parameters.getExecutionStepInfo(); 038 var parentType = (GraphQLObjectType)parentInfo.getUnwrappedNonNullType(); 039 var fieldDefinition = getFieldDef(executionContext.getGraphQLSchema(), parentType, parameters.getField().getSingleField()); 040 041 if(fieldDefinition != null && CLIENT_MUTATION_ID.equals(fieldDefinition.getName())) { 042 // Use the current mutation's coerced arguments, not the first AST field. 043 // This handles variables, defaults, aliases, fragments, and omitted IDs. 044 var input = parentInfo.getArgument("input"); 045 var clientMutationId = input instanceof Map<?, ?> map ? map.get(CLIENT_MUTATION_ID) : null; 046 var fieldInfo = createExecutionStepInfo(executionContext, parameters, fieldDefinition, parentType); 047 var valueParameters = parameters.transform(builder -> builder 048 .executionStepInfo(fieldInfo) 049 .nonNullFieldValidator(new NonNullableFieldValidator(executionContext, fieldInfo)) 050 .source(clientMutationId)); 051 052 result = completeValue(executionContext, valueParameters).getFieldValue(); 053 } else { 054 result = super.resolveField(executionContext, parameters); 055 } 056 057 return result; 058 } 059 060}