@@ -61,9 +61,11 @@ private void generateTraitsHeader(CppWriter writer, OperationData<PaginatedTrait
6161 // Includes - detect suffix like C2J renameShape logic
6262 String resultSuffix = getResultSuffix (opName );
6363 String capitalizedServiceName = ServiceNameUtil .getServiceNameUpperCamel (service );
64+ String requestFileName = ServiceNameUtil .getClientMethodName (opName , c2jServiceName ) + "Request" ;
65+ String resultFileName = ServiceNameUtil .getClientMethodName (opName , c2jServiceName ) + resultSuffix ;
6466 writer .writeInclude ("aws/" + c2jServiceName + "/" + capitalizedServiceName + "_EXPORTS.h" )
65- .writeInclude ("aws/" + c2jServiceName + "/model/" + opName + "Request .h" )
66- .writeInclude ("aws/" + c2jServiceName + "/model/" + opName + resultSuffix + ".h" )
67+ .writeInclude ("aws/" + c2jServiceName + "/model/" + requestFileName + ".h" )
68+ .writeInclude ("aws/" + c2jServiceName + "/model/" + resultFileName + ".h" )
6769 .writeInclude ("aws/" + c2jServiceName + "/" + capitalizedServiceName + "Client.h" )
6870 .write ("" );
6971
@@ -76,15 +78,16 @@ private void generateTraitsHeader(CppWriter writer, OperationData<PaginatedTrait
7678 // Struct definition
7779 writer .openBlock ("struct " + opName + "PaginationTraits\n {" , "};" , () -> {
7880 // Use detected suffix to match C2J renameShape logic
79- writer .write (" using RequestType = Model::$LRequest;" , opName )
80- .write (" using ResultType = Model::$L$L;" , opName , resultSuffix )
81- .write (" using OutcomeType = Model::$LOutcome;" , opName )
81+ String methodName = ServiceNameUtil .getClientMethodName (opName , c2jServiceName );
82+ writer .write (" using RequestType = Model::$LRequest;" , methodName )
83+ .write (" using ResultType = Model::$L$L;" , methodName , resultSuffix )
84+ .write (" using OutcomeType = Model::$LOutcome;" , methodName )
8285 .write (" using ClientType = $LClient;" , capitalizedServiceName )
8386 .write ("" );
8487
8588 // Invoke method
8689 writer .openBlock (" static OutcomeType Invoke(ClientType& client, const RequestType& request)\n {" , " }" , () -> {
87- writer .write (" return client.$L(request);" , opName );
90+ writer .write (" return client.$L(request);" , methodName );
8891 });
8992
9093 writer .write ("" );
@@ -93,7 +96,11 @@ private void generateTraitsHeader(CppWriter writer, OperationData<PaginatedTrait
9396 writer .openBlock (" static bool HasMoreResults(const ResultType& result)\n {" , " }" , () -> {
9497 if (trait .getOutputToken ().isPresent ()) {
9598 String outToken = trait .getOutputToken ().get ();
96- if (outToken .toLowerCase ().contains ("marker" ) || outToken .toLowerCase ().contains ("number" )) {
99+ String nestedListMember = getNestedListMember (op );
100+ String tokenName = extractTokenName (outToken );
101+ if (nestedListMember != null ) {
102+ writer .write (" return !result.Get$L().Get$L().empty();" , capitalize (nestedListMember ), capitalize (tokenName ));
103+ } else if (isNumericToken (op , outToken )) {
97104 writer .write (" return result.Get$L() != 0;" , capitalize (outToken ));
98105 } else {
99106 writer .write (" return !result.Get$L().empty();" , capitalize (outToken ));
@@ -142,7 +149,13 @@ private void generateTraitsHeader(CppWriter writer, OperationData<PaginatedTrait
142149 }
143150
144151 if (inToken != null && outToken != null ) {
145- writer .write (" request.Set$L(result.Get$L());" , capitalize (inToken ), capitalize (outToken ));
152+ String nestedListMember = getNestedListMember (op );
153+ String tokenName = extractTokenName (outToken );
154+ if (nestedListMember != null ) {
155+ writer .write (" request.Set$L(result.Get$L().Get$L());" , capitalize (inToken ), capitalize (nestedListMember ), capitalize (tokenName ));
156+ } else {
157+ writer .write (" request.Set$L(result.Get$L());" , capitalize (inToken ), capitalize (outToken ));
158+ }
146159 } else {
147160 // TODO: Check AWS SDK C++ standard for handling null pagination tokens
148161 // Should we throw an exception, log a warning, or silently ignore?
@@ -168,23 +181,42 @@ private String getResultSuffix(String opName) {
168181 return "Response" ;
169182 }
170183
171- // For now, use the simple approach that works:
172- // If the actual generated file exists, use Result; otherwise use SdkResult
184+ // For CloudFront, use the base operation name (without version suffix) for conflict detection
185+ final String baseOpName ;
186+ if ("cloudfront" .equals (c2jServiceName ) && opName .endsWith ("2020_05_31" )) {
187+ baseOpName = opName .substring (0 , opName .length () - "2020_05_31" .length ());
188+ } else {
189+ baseOpName = opName ;
190+ }
173191
174192 // Check for known SdkResult cases (where data model conflicts exist)
175193 Set <Shape > allShapes = context .getModel ().toSet ();
176194 boolean hasDataModelConflict = allShapes .stream ()
177195 .anyMatch (shape -> {
178196 String shapeName = shape .getId ().getName ();
179- if (shapeName .equals (opName + "Result" )) {
180- // Found a shape with the same name - check if it's a data model
197+ // Check if there's a conflicting shape with the base name + "Result"
198+ String candidateName = baseOpName + "Result" ;
199+ if (shapeName .equals (candidateName )) {
200+ // Found a shape with the same name - check if it's a data model (not an operation result)
181201 if (shape instanceof StructureShape ) {
182202 StructureShape structShape = (StructureShape ) shape ;
183- // If it doesn't have NextToken/nextToken, it's likely a data model
203+ // Check if this is an operation result by looking for pagination-related patterns
184204 Set <String > memberNames = structShape .getAllMembers ().keySet ();
185- // TODO: Sanitize member names for other edge cases (e.g., different casing, underscores, etc.)
186- boolean hasNextToken = memberNames .contains ("NextToken" ) || memberNames .contains ("nextToken" );
187- return !hasNextToken ;
205+
206+ // Direct pagination tokens
207+ boolean hasDirectPaginationTokens = memberNames .contains ("NextToken" ) || memberNames .contains ("nextToken" ) ||
208+ memberNames .contains ("Marker" ) || memberNames .contains ("marker" ) ||
209+ memberNames .contains ("NextMarker" ) || memberNames .contains ("nextMarker" ) ||
210+ memberNames .contains ("IsTruncated" ) || memberNames .contains ("isTruncated" );
211+
212+ // Check for nested list structures (common in AWS APIs)
213+ boolean hasNestedListStructure = memberNames .stream ()
214+ .anyMatch (memberName -> memberName .toLowerCase ().contains ("list" ));
215+
216+ // If it has direct pagination tokens or nested list structure, it's likely an operation result
217+ boolean isOperationResult = hasDirectPaginationTokens || hasNestedListStructure ;
218+
219+ return !isOperationResult ;
188220 }
189221 }
190222 return false ;
@@ -193,44 +225,6 @@ private String getResultSuffix(String opName) {
193225 return hasDataModelConflict ? "SdkResult" : "Result" ;
194226 }
195227
196- // TODO: Delete this method if it's not used - replaced by simpler conflict detection in getResultSuffix
197- // Replicate the precise conflict detection logic from C2jModelToGeneratorModelTransformer
198- private boolean hasNamingConflict (String candidateName , String opName ) {
199- // Get all shapes in the model to check for conflicts
200- Set <Shape > allShapes = context .getModel ().toSet ();
201-
202- for (Shape shape : allShapes ) {
203- String shapeName = shape .getId ().getName ();
204-
205- // Direct exact name conflict - this is the main case
206- if (candidateName .equals (shapeName )) {
207- // If this is a structure, check if it's already a suitable operation result
208- if (shape instanceof StructureShape ) {
209- StructureShape structShape = (StructureShape ) shape ;
210- // If the existing shape has pagination tokens, it's already an operation result - no conflict
211- // Check for various pagination token field names
212- boolean hasNextToken = structShape .getAllMembers ().keySet ().stream ()
213- .anyMatch (memberName -> memberName .toLowerCase ().contains ("nexttoken" ) ||
214- memberName .toLowerCase ().contains ("token" ));
215-
216- if (hasNextToken ) {
217- // This is already an operation result shape, no conflict
218- return false ;
219- }
220-
221- // If it doesn't have pagination tokens, it's a data model - conflict!
222- return true ;
223- }
224- return true ;
225- }
226- }
227-
228- return false ;
229- }
230-
231- // Remove the hardcoded method - no longer needed
232- // private boolean isKnownConflictingOperation(String opName) { ... }
233-
234228 private boolean isEc2Protocol () {
235229 // EC2 protocol services rename all Result shapes to Response
236230 // This matches the logic in Ec2CppClientGenerator.legacyPatchEc2Model
@@ -262,4 +256,53 @@ private String getServiceLevelOutputToken() {
262256 private String capitalize (String str ) {
263257 return str .substring (0 , 1 ).toUpperCase () + str .substring (1 );
264258 }
259+
260+ private String extractTokenName (String outToken ) {
261+ // Pagination token may be in format "MemberName.TokenName", extract just the token name
262+ return outToken .contains ("." ) ? outToken .substring (outToken .lastIndexOf ("." ) + 1 ) : outToken ;
263+ }
264+
265+ private String getNestedListMember (OperationShape op ) {
266+ // Check if the output has a nested list structure containing pagination tokens
267+ // This pattern is used by CloudFront and potentially other services
268+ String result = op .getOutput ()
269+ .flatMap (outputId -> context .getModel ().getShape (outputId ))
270+ .filter (shape -> shape instanceof StructureShape )
271+ .map (shape -> (StructureShape ) shape )
272+ .flatMap (outputShape -> {
273+ // Find a member that contains "list" in its name and has pagination tokens
274+ return outputShape .getAllMembers ().entrySet ().stream ()
275+ .filter (entry -> entry .getKey ().toLowerCase ().contains ("list" ))
276+ .filter (entry -> {
277+ // Check if this member's target shape has pagination tokens
278+ return context .getModel ().getShape (entry .getValue ().getTarget ())
279+ .filter (targetShape -> targetShape instanceof StructureShape )
280+ .map (targetShape -> (StructureShape ) targetShape )
281+ .map (targetStruct -> {
282+ Set <String > memberNames = targetStruct .getAllMembers ().keySet ();
283+ return memberNames .contains ("NextMarker" ) || memberNames .contains ("nextMarker" ) ||
284+ memberNames .contains ("Marker" ) || memberNames .contains ("marker" ) ||
285+ memberNames .contains ("IsTruncated" ) || memberNames .contains ("isTruncated" );
286+ })
287+ .orElse (false );
288+ })
289+ .map (entry -> entry .getKey ())
290+ .findFirst ();
291+ })
292+ .orElse (null );
293+
294+ return result ;
295+ }
296+
297+ private boolean isNumericToken (OperationShape op , String tokenName ) {
298+ // Check if the token is numeric by examining the shape type
299+ return op .getOutput ()
300+ .flatMap (outputId -> context .getModel ().getShape (outputId ))
301+ .filter (shape -> shape instanceof StructureShape )
302+ .map (shape -> (StructureShape ) shape )
303+ .flatMap (outputShape -> outputShape .getMember (tokenName ))
304+ .flatMap (member -> context .getModel ().getShape (member .getTarget ()))
305+ .map (targetShape -> targetShape instanceof IntegerShape || targetShape instanceof LongShape )
306+ .orElse (false );
307+ }
265308}
0 commit comments