66import copy
77from typing import Any , Dict , List , Mapping , Optional , Union
88
9+ from airbyte_cdk .sources .declarative .requesters .request_option import RequestOption , RequestOptionType
10+ from airbyte_cdk .sources .types import Config
911
1012def _merge_mappings (
1113 target : Dict [str , Any ],
@@ -33,13 +35,13 @@ def _merge_mappings(
3335 if isinstance (target_value , dict ) and isinstance (source_value , dict ):
3436 # Only body_json supports nested_structures
3537 if not allow_same_value_merge :
36- raise ValueError (f"Duplicate keys found : { '.' .join (current_path )} " )
38+ raise ValueError (f"Request body collision, duplicate keys detected at : { '.' .join (current_path )} . Please ensure that all keys in request are unique. " )
3739 # If both are dictionaries, recursively merge them
3840 _merge_mappings (target_value , source_value , current_path , allow_same_value_merge )
3941
4042 elif not allow_same_value_merge or target_value != source_value :
4143 # If same key has different values, that's a conflict
42- raise ValueError (f"Duplicate keys found : { '.' .join (current_path )} " )
44+ raise ValueError (f"Request body collision, duplicate keys detected at : { '.' .join (current_path )} . Please ensure that all keys in request are unique. " )
4345 else :
4446 # No conflict, just copy the value (using deepcopy for nested structures)
4547 target [key ] = copy .deepcopy (source_value )
@@ -102,3 +104,37 @@ def combine_mappings(
102104 _merge_mappings (result , mapping , allow_same_value_merge = allow_same_value_merge )
103105
104106 return result
107+
108+ def _validate_multiple_request_options (
109+ config : Config ,
110+ * request_options : Optional [RequestOption ]
111+ ) -> None :
112+ """
113+ Validates that a component with multiple request options does not have conflicting paths.
114+ Uses dummy values for validation since actual values might not be available at init time.
115+ """
116+ grouped_options : Dict [RequestOptionType , List [RequestOption ]] = {}
117+ for option in request_options :
118+ if option :
119+ grouped_options .setdefault (option .inject_into , []).append (option )
120+
121+ for inject_type , options in grouped_options .items ():
122+ if len (options ) <= 1 :
123+ continue
124+
125+ option_dicts : List [Optional [Union [Mapping [str , Any ], str ]]] = []
126+ for i , option in enumerate (options ):
127+ option_dict : Dict [str , Any ] = {}
128+ # Use indexed dummy values to ensure we catch conflicts
129+ option .inject_into_request (option_dict , f"dummy_value_{ i } " , config )
130+ option_dicts .append (option_dict )
131+
132+ try :
133+ combine_mappings (
134+ option_dicts ,
135+ allow_same_value_merge = (inject_type == RequestOptionType .body_json )
136+ )
137+ except ValueError as e :
138+ print (e )
139+ raise ValueError (f"Conflict mapping request options: { e } " ) from e
140+
0 commit comments