Repository: caddyserver/caddy
File: caddy.go (Go)

Code before:
				Log().Error("unable to notify to service manager of reload error",
					zap.Error(notifyErr),
					zap.String("reload_err", err.Error()))
			}
			return
		}
		if err := notify.Ready(); err != nil {
			Log().Error("unable to notify to service manager of ready state", zap.Error(err))
		}
	}()

	err = changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload)
	if errors.Is(err, errSameConfig) {
		err = nil // not really an error
	}

	return err
}

// changeConfig changes the current config (rawCfg) according to the
// method, traversed via the given path, and uses the given input as
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
// If the resulting config is the same as the previous, no reload will
// occur unless forceReload is true. If the config is unchanged and not
// forcefully reloaded, then errConfigUnchanged is returned. This function is safe for
// concurrent use.
// The ifMatchHeader can optionally be given a string of the format:
//
//	"<path> <hash>"
//
// where <path> is the absolute path in the config and <hash> is the expected hash of
// the config at that path. If the hash in the ifMatchHeader doesn't match
// the hash of the config, then an APIError with status 412 will be returned.
func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error {
	switch method {
	case http.MethodGet,
		http.MethodHead,
		http.MethodOptions,
		http.MethodConnect,
		http.MethodTrace:
		return fmt.Errorf("method not allowed")
	}

	rawCfgMu.Lock()
	defer rawCfgMu.Unlock()

	if ifMatchHeader != "" {
		// expect the first and last character to be quotes
		if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' {
			return APIError{
				HTTPStatus: http.StatusBadRequest,

Review comment (suggestion):
```suggestion
// forcefully reloaded, then errConfigUnchanged is returned. This function
// is safe for concurrent use.
```

Code after:
				Log().Error("unable to notify to service manager of reload error",
					zap.Error(notifyErr),
					zap.String("reload_err", err.Error()))
			}
			return
		}
		if err := notify.Ready(); err != nil {
			Log().Error("unable to notify to service manager of ready state", zap.Error(err))
		}
	}()

	err = changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload)
	if errors.Is(err, errSameConfig) {
		err = nil // not really an error
	}

	return err
}

// changeConfig changes the current config (rawCfg) according to the
// method, traversed via the given path, and uses the given input as
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
// If the resulting config is the same as the previous, no reload will
// occur unless forceReload is true. If the config is unchanged and not
// forcefully reloaded, then errConfigUnchanged is returned. This function
// is safe for concurrent use.
// The ifMatchHeader can optionally be given a string of the format:
//
//	"<path> <hash>"
//
// where <path> is the absolute path in the config and <hash> is the expected hash of
// the config at that path. If the hash in the ifMatchHeader doesn't match
// the hash of the config, then an APIError with status 412 will be returned.
func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error {
	switch method {
	case http.MethodGet,
		http.MethodHead,
		http.MethodOptions,
		http.MethodConnect,
		http.MethodTrace:
		return fmt.Errorf("method not allowed")
	}

	rawCfgMu.Lock()
	defer rawCfgMu.Unlock()

	if ifMatchHeader != "" {
		// expect the first and last character to be quotes
		if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' {
			return APIError{
				HTTPStatus: http.StatusBadRequest,