package config import ( "encoding/json" "fmt" "path/filepath" "strings" "gopkg.in/yaml.v3" ) // Format represents a config file format. type Format int const ( FormatJSON Format = iota FormatYAML ) // DetectFormat returns the format based on file extension. func DetectFormat(path string) (Format, error) { ext := strings.ToLower(filepath.Ext(path)) switch ext { case ".json": return FormatJSON, nil case ".yaml", ".yml": return FormatYAML, nil default: return 0, fmt.Errorf("unsupported format: %s", ext) } } // Parse deserializes raw bytes into a map based on the given format. func Parse(data []byte, format Format) (map[string]any, error) { var result map[string]any switch format { case FormatJSON: if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("invalid JSON: %w", err) } case FormatYAML: if err := yaml.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("invalid YAML: %w", err) } default: return nil, fmt.Errorf("unknown format") } if result == nil { result = make(map[string]any) } return result, nil } // Serialize converts a map back to bytes in the given format. func Serialize(data map[string]any, format Format) ([]byte, error) { switch format { case FormatJSON: return json.MarshalIndent(data, "", " ") case FormatYAML: return yaml.Marshal(data) default: return nil, fmt.Errorf("unknown format") } } // DeepMerge merges src into dst recursively. Values in src override those in dst. // Nested maps are merged; all other types are replaced. func DeepMerge(dst, src map[string]any) map[string]any { out := make(map[string]any, len(dst)) for k, v := range dst { out[k] = v } for k, v := range src { if srcMap, ok := v.(map[string]any); ok { if dstMap, ok := out[k].(map[string]any); ok { out[k] = DeepMerge(dstMap, srcMap) continue } } out[k] = v } return out }