@@ -272,118 +272,105 @@ func findRawShape(shapes map[string]interface{}, shapeName string) map[string]in
272272 return nil
273273}
274274
275- // extractShapeName extracts the simple name from a fully qualified shape reference
275+ // extractShapeName extracts the simple name from a fully qualified shape reference.
276276// e.g., "com.amazonaws.ecs#TagKey" -> "TagKey"
277277func extractShapeName (qualifiedName string ) string {
278- if parts := strings .Split (qualifiedName , "#" ); len ( parts ) == 2 {
279- return parts [ 1 ]
278+ if idx := strings .LastIndex (qualifiedName , "#" ); idx >= 0 {
279+ return qualifiedName [ idx + 1 : ]
280280 }
281281 return qualifiedName
282282}
283283
284- // traverseToTagConstraints traverses list/map/structure shapes to find tag key/value constraints.
285- // Returns nil, nil if the shape is not a tag-like structure or has no constraints.
284+ // tagStructureMembers defines the member names used to identify tag-like structures
285+ // in Smithy models. AWS services use varying capitalization for these members
286+ // (e.g., "key"/"Key", "value"/"Value"), so lookups are case-insensitive.
287+ var tagStructureMembers = struct {
288+ Key string
289+ Value string
290+ }{
291+ Key : "key" ,
292+ Value : "value" ,
293+ }
294+
295+ // getMemberCaseInsensitive looks up a member in a Smithy members map using
296+ // case-insensitive matching. Returns nil if not found.
297+ func getMemberCaseInsensitive (members map [string ]interface {}, name string ) map [string ]interface {} {
298+ for k , v := range members {
299+ if strings .EqualFold (k , name ) {
300+ if m , ok := v .(map [string ]interface {}); ok {
301+ return m
302+ }
303+ }
304+ }
305+ return nil
306+ }
307+
308+ // getTarget extracts the target shape name from a Smithy reference.
309+ func getTarget (ref map [string ]interface {}) string {
310+ if ref == nil {
311+ return ""
312+ }
313+ target , _ := ref ["target" ].(string )
314+ return extractShapeName (target )
315+ }
316+
317+ // traverseToTagConstraints traverses Smithy shapes to find key/value string constraints.
318+ // It handles three shape types:
319+ // - map: directly has key/value targets (e.g., TagMap)
320+ // - list: recurses into the member element (e.g., TagList -> Tag)
321+ // - structure: looks for key/value members (e.g., Tag with Key/Value fields)
322+ //
323+ // Returns nil, nil if the shape doesn't resolve to a string key/value pair.
286324func traverseToTagConstraints (shapes map [string ]interface {}, shapeName string ) (keyModel , valueModel map [string ]interface {}) {
287325 raw := findRawShape (shapes , shapeName )
288326 if raw == nil {
289327 return nil , nil
290328 }
291329
292- shapeType , ok := raw ["type" ].(string )
293- if ! ok {
294- return nil , nil
295- }
330+ shapeType , _ := raw ["type" ].(string )
296331
297332 switch shapeType {
298333 case "map" :
299- // TagMap: directly has key/value targets
300- keyRef , ok := raw ["key" ].(map [string ]interface {})
301- if ! ok {
302- return nil , nil
303- }
304- valueRef , ok := raw ["value" ].(map [string ]interface {})
305- if ! ok {
306- return nil , nil
307- }
308- keyTarget , ok := keyRef ["target" ].(string )
309- if ! ok {
310- return nil , nil
311- }
312- valueTarget , ok := valueRef ["target" ].(string )
313- if ! ok {
314- return nil , nil
315- }
316- keyModel := findShape (shapes , extractShapeName (keyTarget ))
317- valueModel := findShape (shapes , extractShapeName (valueTarget ))
318- // Only return if both resolve to string types (not lists, maps, etc.)
319- if keyModel == nil || valueModel == nil {
320- return nil , nil
321- }
322- if keyModel ["type" ] != "string" || valueModel ["type" ] != "string" {
323- return nil , nil
324- }
325- return keyModel , valueModel
334+ keyRef , _ := raw ["key" ].(map [string ]interface {})
335+ valueRef , _ := raw ["value" ].(map [string ]interface {})
336+ return resolveKeyValuePair (shapes , getTarget (keyRef ), getTarget (valueRef ))
326337
327338 case "list" :
328- // TagList: traverse to element type
329- memberRef , ok := raw ["member" ].(map [string ]interface {})
330- if ! ok {
331- return nil , nil
332- }
333- memberTarget , ok := memberRef ["target" ].(string )
334- if ! ok {
335- return nil , nil
339+ memberRef , _ := raw ["member" ].(map [string ]interface {})
340+ if target := getTarget (memberRef ); target != "" {
341+ return traverseToTagConstraints (shapes , target )
336342 }
337- return traverseToTagConstraints (shapes , extractShapeName (memberTarget ))
338343
339344 case "structure" :
340- // Tag: has key/value members (case varies: key/Key, value/Value)
341- members , ok := raw ["members" ].(map [string ]interface {})
342- if ! ok {
343- return nil , nil
344- }
345- keyMember := members ["key" ]
346- if keyMember == nil {
347- keyMember = members ["Key" ]
348- }
349- valueMember := members ["value" ]
350- if valueMember == nil {
351- valueMember = members ["Value" ]
352- }
353- if keyMember == nil || valueMember == nil {
345+ members , _ := raw ["members" ].(map [string ]interface {})
346+ if members == nil {
354347 return nil , nil
355348 }
356- keyMemberMap , ok := keyMember .(map [string ]interface {})
357- if ! ok {
358- return nil , nil
359- }
360- valueMemberMap , ok := valueMember .(map [string ]interface {})
361- if ! ok {
362- return nil , nil
363- }
364- keyTarget , ok := keyMemberMap ["target" ].(string )
365- if ! ok {
366- return nil , nil
367- }
368- valueTarget , ok := valueMemberMap ["target" ].(string )
369- if ! ok {
370- return nil , nil
371- }
372- keyModel := findShape (shapes , extractShapeName (keyTarget ))
373- valueModel := findShape (shapes , extractShapeName (valueTarget ))
374- // Only return if both resolve to string types (not lists, maps, etc.)
375- if keyModel == nil || valueModel == nil {
376- return nil , nil
377- }
378- if keyModel ["type" ] != "string" || valueModel ["type" ] != "string" {
379- return nil , nil
380- }
381- return keyModel , valueModel
349+ keyMember := getMemberCaseInsensitive (members , tagStructureMembers .Key )
350+ valueMember := getMemberCaseInsensitive (members , tagStructureMembers .Value )
351+ return resolveKeyValuePair (shapes , getTarget (keyMember ), getTarget (valueMember ))
382352 }
383353
384354 return nil , nil
385355}
386356
357+ // resolveKeyValuePair resolves key and value shape names to their models,
358+ // returning nil if either is missing or not a string type.
359+ func resolveKeyValuePair (shapes map [string ]interface {}, keyName , valueName string ) (map [string ]interface {}, map [string ]interface {}) {
360+ if keyName == "" || valueName == "" {
361+ return nil , nil
362+ }
363+ keyModel := findShape (shapes , keyName )
364+ valueModel := findShape (shapes , valueName )
365+ if keyModel == nil || valueModel == nil {
366+ return nil , nil
367+ }
368+ if keyModel ["type" ] != "string" || valueModel ["type" ] != "string" {
369+ return nil , nil
370+ }
371+ return keyModel , valueModel
372+ }
373+
387374// extractServiceNamespace extracts the namespace from the Smithy service definition
388375func extractServiceNamespace (shapes map [string ]interface {}) string {
389376 for shapeName , shape := range shapes {
0 commit comments