Repository: spf13/viper (go)

Issue:
MergeInConfig does not use extension as config type
### Preflight Checklist

- [x] I have searched the [issue tracker](https://www.github.com/spf13/viper/issues) for an issue that matches the one I want to file, without success.
- [x] I am not looking for support or already pursued the available [support channels](https://github.com/spf13/viper/issues/new/choose) without success.
- [x] I have checked the [troubleshooting guide](https://github.com/spf13/viper/blob/master/TROUBLESHOOTING.md) for my problem, without success.

### Viper Version

1.20.0

### Go Version

1.23

### Config Source

Files

### Format

YAML

### Repl.it link

https://replit.com/@graffic/MergeInConfig-error

### Code reproducing the issue

```go
package main

import (
	"github.com/spf13/viper"
)

func main() {
	v := viper.New()
	v.SetConfigFile("config.yaml")

	err := v.MergeInConfig()
	if err != nil {
		panic(err)
	}
}
```

### Expected Behavior

I would expect that the configuration type detected in `MergeInConfig` be given to MergeConfig or at least set so the call works.

### Actual Behavior

`MergeInConfig` already validates the configuration file extension, but a further call to `MergeConfig` inside that method, checks for an already set configuration type and the call fails since there is none set.

### Steps To Reproduce

See repl.it link

### Additional Information

_No response_

Pull request:
fix(config): get config type from v.configType or config file ext
As described in the README, I expect viper to infer config type based on config file extension.
> viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name

Since `1.20` this behavior seems broken.

If this is intended (or not working the way I think it should) let me know.

Fixes #1994 

Patch:
diff --git a/viper.go b/viper.go
index f900e58..374fe74 100644
--- a/viper.go
+++ b/viper.go
@@ -1535,8 +1535,8 @@ func (v *Viper) MergeInConfig() error {
 func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
 
 func (v *Viper) ReadConfig(in io.Reader) error {
-	if v.configType == "" {
-		return errors.New("cannot decode configuration: config type is not set")
+	if v.getConfigType() == "" {
+		return errors.New("cannot decode configuration: unable to get config type from configType or file extension")
 	}
 
 	v.config = make(map[string]any)
@@ -1547,8 +1547,8 @@ func (v *Viper) ReadConfig(in io.Reader) error {
 func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
 
 func (v *Viper) MergeConfig(in io.Reader) error {
-	if v.configType == "" {
-		return errors.New("cannot decode configuration: config type is not set")
+	if v.getConfigType() == "" {
+		return errors.New("cannot decode configuration: unable to get config type from configType or file extension")
 	}
 
 	cfg := make(map[string]any)


Test patch:
diff --git a/viper_test.go b/viper_test.go
index c0c0e7c..f7e0787 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -1542,6 +1542,14 @@ func TestReadConfig(t *testing.T) {
 	})
 }
 
+func TestReadConfigWithSetConfigFile(t *testing.T) {
+	v := New()
+	v.SetConfigFile("config.yaml") // Dummy value to infer config type from file extension
+	err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc))
+	require.NoError(t, err)
+	assert.Equal(t, 45000, v.GetInt("hello.pop"))
+}
+
 func TestIsSet(t *testing.T) {
 	v := New()
 	v.SetConfigType("yaml")
@@ -2059,6 +2067,14 @@ func TestMergeConfig(t *testing.T) {
 	assert.Equal(t, "bar", v.GetString("fu"))
 }
 
+func TestMergeConfigWithSetConfigFile(t *testing.T) {
+	v := New()
+	v.SetConfigFile("config.yaml") // Dummy value to infer config type from file extension
+	err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc))
+	require.NoError(t, err)
+	assert.Equal(t, 45000, v.GetInt("hello.pop"))
+}
+
 func TestMergeConfigOverrideType(t *testing.T) {
 	v := New()
 	v.SetConfigType("json")