package conf import ( "encoding/json" "errors" "fmt" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/hclsimple" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/pelletier/go-toml/v2" "gopkg.in/yaml.v3" ) type filetype int const ( typeInvalid filetype = iota typeJSON typeTOML typeHCL typeYAML ) // getType returns the type of the config file. func getType(filename string) filetype { switch { case strings.HasSuffix(filename, ".json"): return typeJSON case strings.HasSuffix(filename, ".toml"): return typeTOML case strings.HasSuffix(filename, ".hcl"): return typeHCL case strings.HasSuffix(filename, ".yml") || strings.HasSuffix(filename, ".yaml"): return typeYAML default: return typeInvalid } } // unmarshal unmarshals the given data to the given struct. func unmarshal(filepath string, data []byte, v any) error { ft := getType(filepath) switch ft { case typeJSON: return unmarshalJSON(data, v) case typeTOML: return unmarshalTOML(data, v) case typeHCL: return unmarshalHCL(filepath, data, v) case typeYAML: return unmarshalYAML(data, v) default: return ErrUnsupportedFileType } } // marshal marshals the given struct to bytes. func marshal(ft filetype, v any) ([]byte, error) { switch ft { case typeJSON: return marshalJSON(v) case typeTOML: return marshalTOML(v) case typeHCL: return marshalHCL(v) case typeYAML: return marshalYAML(v) default: return nil, ErrUnsupportedFileType } } // unmarshalJSON unmarshals the given data to the given struct. func unmarshalJSON(data []byte, v any) error { err := json.Unmarshal(data, v) if err != nil { return fmt.Errorf("cannot parse config file: %w", err) } return nil } // marshalJSON marshals the given struct to bytes. func marshalJSON(v any) ([]byte, error) { data, err := json.MarshalIndent(v, "", " ") if err != nil { return nil, fmt.Errorf("cannot generate config content: %w", err) } return data, nil } // unmarshalTOML unmarshals the given data to the given struct. func unmarshalTOML(data []byte, v any) error { err := toml.Unmarshal(data, v) if err != nil { return fmt.Errorf("cannot parse config file: %w", err) } return nil } // marshalTOML marshals the given struct to bytes. func marshalTOML(v any) ([]byte, error) { data, err := toml.Marshal(v) if err != nil { return nil, fmt.Errorf("cannot generate config content: %w", err) } return data, nil } // unmarshalHCL unmarshals the given data to the given struct. func unmarshalHCL(filepath string, data []byte, v any) error { err := hclsimple.Decode(filepath, data, nil, v) var diags hcl.Diagnostics errors.As(err, &diags) newDiags := hclFilterDiagnostics(diags) if len(newDiags) > 0 { return fmt.Errorf("cannot parse config file: %w", newDiags) } return nil } // marshalHCL marshals the given struct to bytes. func marshalHCL(v any) ([]byte, error) { f := hclwrite.NewEmptyFile() gohcl.EncodeIntoBody(v, f.Body()) return f.Bytes(), nil } func hclFilterDiagnostics(diags hcl.Diagnostics) hcl.Diagnostics { var newDiags hcl.Diagnostics for _, diag := range diags { if diag.Summary != "Unsupported argument" { newDiags = append(newDiags, diag) } } if len(newDiags) > 0 { return newDiags } return nil } // unmarshalYAML unmarshals the given data to the given struct. func unmarshalYAML(data []byte, v any) error { err := yaml.Unmarshal(data, v) if err != nil { return fmt.Errorf("cannot parse config file: %w", err) } return nil } // marshalYAML marshals the given struct to bytes. func marshalYAML(v any) ([]byte, error) { data, err := yaml.Marshal(v) if err != nil { return nil, fmt.Errorf("cannot generate config content: %w", err) } return data, nil }