mirror of
https://github.com/taigrr/jety.git
synced 2026-04-02 03:19:03 -07:00
Compare commits
2 Commits
cd/getters
...
cd/ci-upda
| Author | SHA1 | Date | |
|---|---|---|---|
| c8cbb72ed7 | |||
| 8b154b58ba |
21
.github/workflows/codeql-analysis.yml
vendored
21
.github/workflows/codeql-analysis.yml
vendored
@@ -36,31 +36,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
2
.github/workflows/govulncheck.yml
vendored
2
.github/workflows/govulncheck.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Run govulncheck
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- id: govulncheck
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
|
||||
21
README.md
21
README.md
@@ -68,16 +68,25 @@ timeout = "30s"
|
||||
client_id = "abc123"
|
||||
```
|
||||
|
||||
Access nested values using `GetStringMap` and type assertions:
|
||||
Use `Sub()` to get a scoped ConfigManager for a nested section:
|
||||
|
||||
```go
|
||||
cloud := jety.Sub("services")
|
||||
if cloud != nil {
|
||||
inner := cloud.Sub("cloud")
|
||||
if inner != nil {
|
||||
varValue := inner.GetString("var") // "xyz"
|
||||
timeout := inner.GetDuration("timeout") // 30s
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or access nested values directly with `GetStringMap` and type assertions:
|
||||
|
||||
```go
|
||||
services := jety.GetStringMap("services")
|
||||
cloud := services["cloud"].(map[string]any)
|
||||
varValue := cloud["var"].(string) // "xyz"
|
||||
|
||||
// For deeper nesting
|
||||
auth := cloud["auth"].(map[string]any)
|
||||
clientID := auth["client_id"].(string) // "abc123"
|
||||
```
|
||||
|
||||
### Environment Variable Overrides
|
||||
@@ -124,6 +133,8 @@ export MYAPP_SERVICES_CLOUD_VAR=override_value
|
||||
| ------------------------ | ------------------------ |
|
||||
| `Set(key, value)` | Set a value |
|
||||
| `SetDefault(key, value)` | Set a default value |
|
||||
| `Delete(key)` | Remove a key |
|
||||
| `Sub(key)` | Get scoped sub-config |
|
||||
| `Get(key)` | Get raw value |
|
||||
| `GetString(key)` | Get as string |
|
||||
| `GetInt(key)` | Get as int |
|
||||
|
||||
@@ -88,6 +88,14 @@ func SetString(key string, value string) {
|
||||
defaultConfigManager.SetString(key, value)
|
||||
}
|
||||
|
||||
func Delete(key string) {
|
||||
defaultConfigManager.Delete(key)
|
||||
}
|
||||
|
||||
func Sub(key string) *ConfigManager {
|
||||
return defaultConfigManager.Sub(key)
|
||||
}
|
||||
|
||||
func SetConfigDir(path string) {
|
||||
defaultConfigManager.SetConfigDir(path)
|
||||
}
|
||||
|
||||
25
jety.go
25
jety.go
@@ -308,6 +308,31 @@ func readFile(filename string, fileType configType) (map[string]any, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sub returns a new ConfigManager rooted at the given key. The key must
|
||||
// refer to a map value (e.g., from a nested TOML/YAML/JSON section).
|
||||
// The returned ConfigManager has the nested map loaded as its default
|
||||
// config, and does not inherit the parent's environment prefix, overrides,
|
||||
// or config file. Returns nil if the key does not exist or is not a map.
|
||||
func (c *ConfigManager) Sub(key string) *ConfigManager {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
v, ok := c.resolve(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
m, ok := v.Value.(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
sub := NewConfigManager()
|
||||
for k, val := range m {
|
||||
lower := strings.ToLower(k)
|
||||
sub.defaultConfig[lower] = ConfigMap{Key: k, Value: val}
|
||||
sub.combinedConfig[lower] = ConfigMap{Key: k, Value: val}
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
func (c *ConfigManager) SetConfigDir(path string) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
167
jety_test.go
167
jety_test.go
@@ -1554,3 +1554,170 @@ func TestPackageLevelGetInt64(t *testing.T) {
|
||||
t.Errorf("GetInt64(bignum) = %d, want 9223372036854775807", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
cm := NewConfigManager()
|
||||
cm.SetDefault("keep", "yes")
|
||||
cm.SetDefault("remove", "default")
|
||||
cm.Set("remove", "override")
|
||||
|
||||
if !cm.IsSet("remove") {
|
||||
t.Fatal("remove should be set before delete")
|
||||
}
|
||||
|
||||
cm.Delete("remove")
|
||||
|
||||
if cm.IsSet("remove") {
|
||||
t.Error("remove should not be set after delete")
|
||||
}
|
||||
if cm.Get("remove") != nil {
|
||||
t.Error("Get(remove) should return nil after delete")
|
||||
}
|
||||
if cm.GetString("keep") != "yes" {
|
||||
t.Error("keep should be unaffected by deleting remove")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteFromAllLayers(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
configFile := filepath.Join(dir, "config.toml")
|
||||
if err := os.WriteFile(configFile, []byte(`filekey = "fromfile"`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cm := NewConfigManager()
|
||||
cm.SetConfigFile(configFile)
|
||||
if err := cm.SetConfigType("toml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cm.SetDefault("filekey", "default")
|
||||
if err := cm.ReadInConfig(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cm.Set("filekey", "override")
|
||||
|
||||
if cm.GetString("filekey") != "override" {
|
||||
t.Fatal("expected override value before delete")
|
||||
}
|
||||
|
||||
cm.Delete("filekey")
|
||||
|
||||
if cm.IsSet("filekey") {
|
||||
t.Error("filekey should not be set after delete from all layers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCaseInsensitive(t *testing.T) {
|
||||
cm := NewConfigManager()
|
||||
cm.Set("MyKey", "value")
|
||||
|
||||
cm.Delete("MYKEY")
|
||||
|
||||
if cm.IsSet("mykey") {
|
||||
t.Error("delete should be case-insensitive")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageLevelDelete(t *testing.T) {
|
||||
defaultConfigManager = NewConfigManager()
|
||||
Set("temp", "value")
|
||||
if !IsSet("temp") {
|
||||
t.Fatal("temp should be set")
|
||||
}
|
||||
Delete("temp")
|
||||
if IsSet("temp") {
|
||||
t.Error("temp should not be set after package-level Delete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSub(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
configFile := filepath.Join(dir, "config.toml")
|
||||
if err := os.WriteFile(configFile, []byte(tomlConfig), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cm := NewConfigManager()
|
||||
cm.SetConfigFile(configFile)
|
||||
if err := cm.SetConfigType("toml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cm.ReadInConfig(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sub := cm.Sub("database")
|
||||
if sub == nil {
|
||||
t.Fatal("Sub(database) returned nil")
|
||||
}
|
||||
|
||||
host := sub.GetString("host")
|
||||
if host != "db.example.com" {
|
||||
t.Errorf("Sub(database).GetString(host) = %q, want %q", host, "db.example.com")
|
||||
}
|
||||
|
||||
port := sub.GetInt("port")
|
||||
if port != 5432 {
|
||||
t.Errorf("Sub(database).GetInt(port) = %d, want 5432", port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubNonExistentKey(t *testing.T) {
|
||||
cm := NewConfigManager()
|
||||
cm.SetDefault("simple", "value")
|
||||
|
||||
sub := cm.Sub("nonexistent")
|
||||
if sub != nil {
|
||||
t.Error("Sub(nonexistent) should return nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubNonMapKey(t *testing.T) {
|
||||
cm := NewConfigManager()
|
||||
cm.Set("name", "plain-string")
|
||||
|
||||
sub := cm.Sub("name")
|
||||
if sub != nil {
|
||||
t.Error("Sub on a non-map key should return nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubIsIndependent(t *testing.T) {
|
||||
cm := NewConfigManager()
|
||||
cm.Set("section", map[string]any{
|
||||
"key1": "val1",
|
||||
"key2": "val2",
|
||||
})
|
||||
|
||||
sub := cm.Sub("section")
|
||||
if sub == nil {
|
||||
t.Fatal("Sub(section) returned nil")
|
||||
}
|
||||
|
||||
// Modifying sub should not affect parent
|
||||
sub.Set("key1", "modified")
|
||||
if sub.GetString("key1") != "modified" {
|
||||
t.Error("sub should reflect the Set")
|
||||
}
|
||||
|
||||
parentSection := cm.Get("section").(map[string]any)
|
||||
if parentSection["key1"] != "val1" {
|
||||
t.Error("modifying sub should not affect parent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageLevelSub(t *testing.T) {
|
||||
defaultConfigManager = NewConfigManager()
|
||||
Set("db", map[string]any{"host": "localhost", "port": 5432})
|
||||
|
||||
sub := Sub("db")
|
||||
if sub == nil {
|
||||
t.Fatal("package-level Sub returned nil")
|
||||
}
|
||||
if sub.GetString("host") != "localhost" {
|
||||
t.Errorf("Sub(db).GetString(host) = %q, want %q", sub.GetString("host"), "localhost")
|
||||
}
|
||||
if sub.GetInt("port") != 5432 {
|
||||
t.Errorf("Sub(db).GetInt(port) = %d, want 5432", sub.GetInt("port"))
|
||||
}
|
||||
}
|
||||
|
||||
13
setters.go
13
setters.go
@@ -28,6 +28,19 @@ func (c *ConfigManager) Set(key string, value any) {
|
||||
c.combinedConfig[lower] = ConfigMap{Key: key, Value: value}
|
||||
}
|
||||
|
||||
// Delete removes a key from all configuration layers (overrides, file,
|
||||
// defaults) and rebuilds the combined configuration. Environment variables
|
||||
// are not affected since they are loaded from the process environment.
|
||||
func (c *ConfigManager) Delete(key string) {
|
||||
c.mutex.Lock()
|
||||
lower := strings.ToLower(key)
|
||||
delete(c.overrideConfig, lower)
|
||||
delete(c.fileConfig, lower)
|
||||
delete(c.defaultConfig, lower)
|
||||
delete(c.combinedConfig, lower)
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (c *ConfigManager) SetDefault(key string, value any) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user