package directory import ( "sort" "github.com/netkingdom/flex-auth/pkg/api" ) // MergeResults combines resolver results while keeping source metadata. func MergeResults(subject api.SubjectRef, results ...ResolveResult) SubjectEnrichment { groups := map[string]struct{}{} roles := map[api.CanonicalRole]struct{}{} metadata := map[string]any{} enrichment := SubjectEnrichment{ Subject: subject, Metadata: metadata, } for _, result := range results { for _, group := range result.Groups { groups[group.ID] = struct{}{} if group.Descriptor != nil { enrichment.Descriptors = append(enrichment.Descriptors, *group.Descriptor) } } for _, role := range result.Roles { roles[role.Role] = struct{}{} } if !result.Freshness.RetrievedAt.IsZero() || result.Freshness.Source != "" { enrichment.Freshness = append(enrichment.Freshness, result.Freshness) } if result.Overage.Detected { enrichment.Overage = append(enrichment.Overage, result.Overage) } if result.Source != "" { metadata["source:"+string(result.Source)] = true } } enrichment.Groups = sortedStringKeys(groups) enrichment.Roles = sortedRoleKeys(roles) return enrichment } // ApplyToSubject returns a subject with resolved groups, roles, and metadata. func ApplyToSubject(subject api.Subject, enrichment SubjectEnrichment) api.Subject { out := subject out.Groups = mergeStrings(out.Groups, enrichment.Groups) out.Roles = mergeRoles(out.Roles, enrichment.Roles) if out.Metadata == nil { out.Metadata = map[string]any{} } out.Metadata["directory_freshness"] = enrichment.Freshness out.Metadata["directory_overage"] = enrichment.Overage return out } func mergeStrings(a, b []string) []string { items := map[string]struct{}{} for _, value := range a { items[value] = struct{}{} } for _, value := range b { items[value] = struct{}{} } return sortedStringKeys(items) } func mergeRoles(a, b []api.CanonicalRole) []api.CanonicalRole { items := map[api.CanonicalRole]struct{}{} for _, value := range a { items[value] = struct{}{} } for _, value := range b { items[value] = struct{}{} } return sortedRoleKeys(items) } func sortedStringKeys(items map[string]struct{}) []string { keys := make([]string, 0, len(items)) for key := range items { keys = append(keys, key) } sort.Strings(keys) return keys } func sortedRoleKeys(items map[api.CanonicalRole]struct{}) []api.CanonicalRole { keys := make([]api.CanonicalRole, 0, len(items)) for key := range items { keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) return keys }