Files
calculate_negative_points/internal/config/secret.go
Eugene Howe b0957bfa49
Some checks failed
Docker Build and Publish / publish (push) Failing after 1m33s
webapp
2026-02-17 09:47:30 -05:00

86 lines
2.6 KiB
Go

package config
import (
"bytes"
"encoding/json"
"os"
"reflect"
"github.com/mitchellh/mapstructure"
)
// SecretValue defines what types of secrets this secret container supports
type SecretValue interface {
~string | ~[]string | ~map[string]string
}
// SecretFilePath is the source to the file containing the secret.
type SecretFilePath string
const SecretPlaceholder = "[SECRET]"
// Secret encapsulates a secret value, which is loaded from a file, or directly from configuration / environment variable
type Secret[T SecretValue] struct {
value T
source SecretFilePath
}
// MarshalJSON implements the json.Marshaler interface, used to hide the actual secret value in logs etc.
func (s Secret[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{"value": SecretPlaceholder, "source": string(s.source)})
}
// MarshalText implements the encoding.TextMarshaler interface, used to hide the actual secret value in logs etc.
func (s *Secret[T]) MarshalText() ([]byte, error) {
return []byte(SecretPlaceholder), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface, used to load the secret from config files and env. values
func (s *Secret[T]) UnmarshalText(text []byte) error {
var val T
switch any(val).(type) {
case string:
s.value = any(string(text)).(T)
return nil
default:
return json.Unmarshal(text, &s.value)
}
}
func (s Secret[T]) Value() T {
return s.value
}
func SecretFromSecretPath[T SecretValue](path SecretFilePath) (Secret[T], error) {
contentBytes, err := os.ReadFile(string(path))
if err != nil {
return Secret[T]{}, err
}
secret := Secret[T]{source: path}
err = secret.UnmarshalText(bytes.TrimSpace(contentBytes))
return secret, err
}
func SecretFromValue[T SecretValue](value T) Secret[T] {
return Secret[T]{value: value}
}
// SecretFilePathUnmarshalHookFunc is a mapstructure.DecodeHookFunc that will convert a SecretFilePath to a Secret
func SecretFilePathUnmarshalHookFunc() mapstructure.DecodeHookFuncType {
return func(from, to reflect.Type, data interface{}) (interface{}, error) {
if from != reflect.TypeOf(SecretFilePath("")) {
return data, nil
}
// Reflection does not work with generics as of 1.20, so we have to do this manually
if to == reflect.TypeOf(Secret[map[string]string]{}) {
return SecretFromSecretPath[map[string]string](data.(SecretFilePath))
} else if to == reflect.TypeOf(Secret[[]string]{}) {
return SecretFromSecretPath[[]string](data.(SecretFilePath))
} else if to == reflect.TypeOf(Secret[string]{}) {
return SecretFromSecretPath[string](data.(SecretFilePath))
} else {
return data, nil
}
}
}