package topdata import ( "fmt" "strings" ) type familyIdentity struct { Parent string Child string } type familyExpansionSource struct { Dataset string Column string Predicate string } type familyExpansionSpec struct { Family string FamilyKey string Template string ChildSource familyExpansionSource } func splitFamilyExpansionIdentity(text string) familyIdentity { text = strings.TrimSpace(text) if text == "" { return familyIdentity{} } if idx := strings.Index(text, "_"); idx > 0 && idx < len(text)-1 { return familyIdentity{ Parent: text[:idx], Child: text[idx+1:], } } return familyIdentity{Parent: text} } func parseFamilyExpansionSource(raw any) (familyExpansionSource, error) { if raw == nil { return familyExpansionSource{}, fmt.Errorf("child_source is required") } obj, ok := raw.(map[string]any) if !ok { return familyExpansionSource{}, fmt.Errorf("child_source must be an object") } dataset, ok := obj["dataset"].(string) if !ok || strings.TrimSpace(dataset) == "" { return familyExpansionSource{}, fmt.Errorf("child_source.dataset must be a non-empty string") } source := familyExpansionSource{ Dataset: strings.TrimSpace(dataset), } if column, ok := obj["column"].(string); ok && strings.TrimSpace(column) != "" { source.Column = strings.TrimSpace(column) } if predicate, ok := obj["predicate"].(string); ok && strings.TrimSpace(predicate) != "" { source.Predicate = strings.TrimSpace(predicate) } return source, nil } func parseFamilyExpansionSpec(path string, obj map[string]any) (familyExpansionSpec, error) { family, ok := obj["family"].(string) if !ok || strings.TrimSpace(family) == "" { return familyExpansionSpec{}, fmt.Errorf("generated file %s: family must be a non-empty string", path) } familyKey, ok := obj["family_key"].(string) if !ok || strings.TrimSpace(familyKey) == "" { return familyExpansionSpec{}, fmt.Errorf("generated file %s: family_key must be a non-empty string", path) } template, ok := obj["template"].(string) if !ok || strings.TrimSpace(template) == "" { return familyExpansionSpec{}, fmt.Errorf("generated file %s: template must be a non-empty string", path) } source, err := parseFamilyExpansionSource(obj["child_source"]) if err != nil { return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err) } return familyExpansionSpec{ Family: strings.TrimSpace(family), FamilyKey: strings.TrimSpace(familyKey), Template: strings.TrimSpace(template), ChildSource: source, }, nil } func familyMetadata(parent, child, source, template string) map[string]any { meta := map[string]any{ "parent": strings.TrimSpace(parent), } if strings.TrimSpace(child) != "" { meta["child"] = strings.TrimSpace(child) } if strings.TrimSpace(source) != "" { meta["source"] = strings.TrimSpace(source) } if strings.TrimSpace(template) != "" { meta["template"] = strings.TrimSpace(template) } return map[string]any{"family": meta} } func parseFamilyMetadata(raw any) (map[string]any, error) { obj, ok := raw.(map[string]any) if !ok { return nil, fmt.Errorf("must be an object") } parent, ok := obj["parent"].(string) if !ok || strings.TrimSpace(parent) == "" { return nil, fmt.Errorf("parent must be a non-empty string") } meta := map[string]any{ "parent": strings.TrimSpace(parent), } if child, ok := obj["child"].(string); ok && strings.TrimSpace(child) != "" { meta["child"] = strings.TrimSpace(child) } if source, ok := obj["source"].(string); ok && strings.TrimSpace(source) != "" { meta["source"] = strings.TrimSpace(source) } if template, ok := obj["template"].(string); ok && strings.TrimSpace(template) != "" { meta["template"] = strings.TrimSpace(template) } return meta, nil } func childTokenFromExpandedKey(key, parent string) string { key = strings.TrimSpace(key) key = strings.TrimPrefix(key, "feat:") if strings.HasPrefix(key, parent+"_") { return strings.TrimPrefix(key, parent+"_") } if strings.HasPrefix(key, parent) { return strings.TrimPrefix(key, parent) } return "" }