package model import "strings" // SelectGatewayByPreference picks a gateway candidate using persisted affinity hints. // Matching order: // 1) gateway ID + instance ID // 2) invoke URI // 3) gateway ID // 4) instance ID // 5) first candidate fallback func SelectGatewayByPreference( candidates []*GatewayInstanceDescriptor, gatewayID string, instanceID string, invokeURI string, ) (*GatewayInstanceDescriptor, string) { if len(candidates) == 0 { return nil, "" } gatewayID = strings.TrimSpace(gatewayID) instanceID = strings.TrimSpace(instanceID) invokeURI = strings.TrimSpace(invokeURI) if gatewayID != "" && instanceID != "" { for _, entry := range candidates { if entry == nil { continue } if strings.EqualFold(strings.TrimSpace(entry.ID), gatewayID) && strings.EqualFold(strings.TrimSpace(entry.InstanceID), instanceID) { return entry, "exact" } } } if invokeURI != "" { for _, entry := range candidates { if entry == nil { continue } if strings.EqualFold(strings.TrimSpace(entry.InvokeURI), invokeURI) { return entry, "invoke_uri" } } } if gatewayID != "" { for _, entry := range candidates { if entry == nil { continue } if strings.EqualFold(strings.TrimSpace(entry.ID), gatewayID) { return entry, "gateway_id" } } } if instanceID != "" { for _, entry := range candidates { if entry == nil { continue } if strings.EqualFold(strings.TrimSpace(entry.InstanceID), instanceID) { return entry, "instance_id" } } } for _, entry := range candidates { if entry != nil { return entry, "rail_fallback" } } return nil, "" } // GatewayDescriptorIdentityKey returns a stable dedupe key for gateway entries. // The key is composed from logical gateway ID + instance identity; invoke URI is // used as a fallback identity when instance ID is missing. func GatewayDescriptorIdentityKey(entry *GatewayInstanceDescriptor) string { if entry == nil { return "" } return GatewayIdentityKey(entry.ID, entry.InstanceID, entry.InvokeURI) } // GatewayIdentityKey composes a stable identity key from gateway affinity fields. func GatewayIdentityKey(gatewayID string, instanceID string, invokeURI string) string { id := strings.ToLower(strings.TrimSpace(gatewayID)) if id == "" { return "" } instance := strings.ToLower(strings.TrimSpace(instanceID)) if instance == "" { instance = strings.ToLower(strings.TrimSpace(invokeURI)) } if instance == "" { return id } return id + "|" + instance } // LessGatewayDescriptor orders gateway descriptors deterministically. func LessGatewayDescriptor(left *GatewayInstanceDescriptor, right *GatewayInstanceDescriptor) bool { if left == nil { return right != nil } if right == nil { return false } if cmp := compareFolded(left.ID, right.ID); cmp != 0 { return cmp < 0 } if cmp := compareFolded(left.InstanceID, right.InstanceID); cmp != 0 { return cmp < 0 } if cmp := compareFolded(left.InvokeURI, right.InvokeURI); cmp != 0 { return cmp < 0 } if cmp := compareFolded(string(left.Rail), string(right.Rail)); cmp != 0 { return cmp < 0 } if cmp := compareFolded(left.Network, right.Network); cmp != 0 { return cmp < 0 } return compareFolded(left.Version, right.Version) < 0 } func compareFolded(left string, right string) int { l := strings.ToLower(strings.TrimSpace(left)) r := strings.ToLower(strings.TrimSpace(right)) switch { case l < r: return -1 case l > r: return 1 default: return 0 } }