Skip to content

Comments

refactor: initialize a Viper instance to replace with the global singleton#1501

Merged
tobyxdd merged 4 commits intoapernet:masterfrom
shabbywu:refactor/app-client
Feb 22, 2026
Merged

refactor: initialize a Viper instance to replace with the global singleton#1501
tobyxdd merged 4 commits intoapernet:masterfrom
shabbywu:refactor/app-client

Conversation

@shabbywu
Copy link

@shabbywu shabbywu commented Jan 9, 2026

Should Viper be a global singleton or passed around?

The best practice is to initialize a Viper instance and pass that around when necessary.

Viper comes with a global instance (singleton) out of the box. Although it makes setting up configuration easy, using it is generally discouraged as it makes testing harder and can lead to unexpected behavior.

The global instance may be deprecated in the future. See spf13/viper#1855 for more details.

ref: https://github.com/spf13/viper?tab=readme-ov-file#should-viper-be-a-global-singleton-or-passed-around

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the CLI configuration handling to use an explicitly-initialized *viper.Viper instance (rather than relying on Viper’s global singleton), improving testability and reducing reliance on global state.

Changes:

  • Introduces a defaultViper instance initialized in initConfig() and passes it into command execution paths.
  • Splits Cobra Run handlers into run*Cmd wrappers and run* helpers that accept a *viper.Viper.
  • Renames several run* handlers for consistency (runClientCmd, runServerCmd, etc.).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
app/cmd/root.go Creates defaultViper and wires it into config initialization.
app/cmd/client.go Routes client execution through runClientCmdrunClient(*viper.Viper).
app/cmd/server.go Routes server execution through runServerCmdrunServer(*viper.Viper).
app/cmd/ping.go Updates ping command to use the injected Viper instance.
app/cmd/speedtest.go Updates speedtest command to use the injected Viper instance.
app/cmd/share.go Updates share command to use the injected Viper instance.
app/cmd/update.go Adds Viper dependency and routes update check through a helper function.
app/cmd/version.go Renames the version command handler for consistency.
Comments suppressed due to low confidence (1)

app/cmd/update.go:12

  • The imports are not gofmt-compliant: standard library imports (e.g., time) should come before third-party imports, with a blank line separating groups. Running gofmt (or reordering the import block) will fix this and avoid CI/lint failures if formatting is enforced.
import (
	"github.com/spf13/viper"
	"time"

	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/apernet/hysteria/app/v2/internal/utils"
	"github.com/apernet/hysteria/core/v2/client"
)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 37 to 41
runCheckUpdate(defaultViper)
}

func runCheckUpdate(viper *viper.Viper) {
checker := utils.NewServerUpdateChecker(appVersion, appPlatform, appArch, appType)
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runCheckUpdate accepts a *viper.Viper parameter but never uses it. This makes the function signature misleading and forces an otherwise unnecessary Viper dependency/import here. Consider removing the parameter (and passing no Viper from runCheckUpdateCmd), or actually using it if configuration is intended to influence update checks.

Copilot uses AI. Check for mistakes.
app/cmd/ping.go Outdated
Comment on lines 35 to 36
func runPing(viper *viper.Viper, addr string) {
if err := viper.ReadInConfig(); err != nil {
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name viper shadows the imported viper package inside this function body, which is easy to misread and prevents referring to the package name within the function. Consider renaming the parameter to something like vp, cfgViper, or cfg for clarity.

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 56
func runSpeedtest(viper *viper.Viper) {
if err := viper.ReadInConfig(); err != nil {
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name viper shadows the imported viper package inside this function body, which reduces readability. Consider renaming the parameter (e.g., vp/cfg) to avoid the shadowing.

Copilot uses AI. Check for mistakes.
app/cmd/share.go Outdated
Comment on lines 39 to 40
func runShare(viper *viper.Viper) {
if err := viper.ReadInConfig(); err != nil {
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name viper shadows the imported viper package inside this function body, which can be confusing when scanning code and makes it impossible to refer to the package name within the function. Consider renaming the parameter (e.g., vp/cfg).

Copilot uses AI. Check for mistakes.
Comment on lines 476 to 477
func runClient(viper *viper.Viper) {
if err := viper.ReadInConfig(); err != nil {
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name viper shadows the imported viper package inside this function body, which hurts readability and can make future edits awkward if package-level helpers are needed. Consider renaming the parameter (e.g., vp/cfgViper).

Copilot uses AI. Check for mistakes.
Comment on lines 940 to 942
func runServer(viper *viper.Viper) {
if err := viper.ReadInConfig(); err != nil {
logger.Fatal("failed to read server config", zap.Error(err))
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name viper shadows the imported viper package inside this function body. Renaming it (e.g., vp/cfg) would make the code easier to read and avoid shadowing surprises.

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +135
defaultViper = viper.New()
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
defaultViper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
defaultViper.SetConfigName("config")
defaultViper.SetConfigType("yaml")
viper.SupportedExts = append([]string{"yaml", "yml"}, viper.SupportedExts...)
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.hysteria")
viper.AddConfigPath("/etc/hysteria/")
defaultViper.AddConfigPath(".")
defaultViper.AddConfigPath("$HOME/.hysteria")
defaultViper.AddConfigPath("/etc/hysteria/")
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initConfig mutates the package-level viper.SupportedExts slice. That global mutation can accumulate duplicates if initConfig is invoked more than once (e.g., in tests) and partially undermines the goal of avoiding global Viper state. Consider avoiding this mutation (or guarding it with a sync.Once / dedup) and keeping all configuration on the defaultViper instance.

Copilot uses AI. Check for mistakes.
@tobyxdd tobyxdd merged commit ceee7f9 into apernet:master Feb 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants