Skip to content

Commit e8f25d2

Browse files
committed
Refactor tag constraint traversal for readability
- Extract tagStructureMembers config with godoc explaining case variations - Add getMemberCaseInsensitive for case-insensitive member lookup - Add getTarget helper to simplify shape reference extraction - Extract resolveKeyValuePair to deduplicate map/structure handling - Reduce traverseToTagConstraints from 80 to 30 lines
1 parent 8212ff6 commit e8f25d2

File tree

1 file changed

+72
-85
lines changed

1 file changed

+72
-85
lines changed

rules/models/generator/main.go

Lines changed: 72 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -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"
277277
func 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.
286324
func 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
388375
func extractServiceNamespace(shapes map[string]interface{}) string {
389376
for shapeName, shape := range shapes {

0 commit comments

Comments
 (0)