diff --git a/.dockerignore b/.dockerignore index 285437ca..69e5e852 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,12 @@ +# Ignore all +* + # env file *.env # Exclude git folders .git* -!.github +.github # Ignore yml files *.yaml @@ -14,3 +17,12 @@ # Include data/ !data/* + +# Ignore source files +*.go +go.mod +go.sum + +# Include build +!app +!dist/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 09c3fa72..8639ca09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,4 +22,6 @@ COPY . . COPY dist/${TARGETOS}/${TARGETARCH}/app . +RUN rm dist/ -r + CMD ["./app"] diff --git a/go.mod b/go.mod index 2e84fe1e..d2af6d4d 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,23 @@ module github.com/codeshelldev/secured-signal-api -go 1.25.3 +go 1.25.4 -require go.uber.org/zap v1.27.0 +require github.com/codeshelldev/gotl v0.0.2 + +require go.uber.org/zap v1.27.0 // indirect require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/parsers/yaml v1.1.0 - github.com/knadh/koanf/providers/confmap v1.0.0 - github.com/knadh/koanf/providers/env/v2 v2.0.0 - github.com/knadh/koanf/providers/file v1.2.0 - github.com/knadh/koanf/v2 v2.3.0 + github.com/knadh/koanf/providers/confmap v1.0.0 // indirect + github.com/knadh/koanf/providers/env/v2 v2.0.0 // indirect + github.com/knadh/koanf/providers/file v1.2.0 // indirect + github.com/knadh/koanf/v2 v2.3.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.37.0 // indirect + golang.org/x/sys v0.38.0 // indirect ) diff --git a/go.sum b/go.sum index 5d68b7cf..a51f9a7d 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/codeshelldev/gotl v0.0.2 h1:PQbipYHut3DNvwcrdkQmIGo2p6X6a889Glmba/KIeFQ= +github.com/codeshelldev/gotl v0.0.2/go.mod h1:OzawxKcFw9QEgbeR5H2UXryhYeeLo8xSLme1r8viE+U= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -36,8 +38,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internals/config/loader.go b/internals/config/loader.go index a5340209..ff0f3fc1 100644 --- a/internals/config/loader.go +++ b/internals/config/loader.go @@ -7,9 +7,10 @@ import ( "strconv" "strings" + "github.com/codeshelldev/gotl/pkg/configutils" + log "github.com/codeshelldev/gotl/pkg/logger" + "github.com/codeshelldev/gotl/pkg/stringutils" "github.com/codeshelldev/secured-signal-api/internals/config/structure" - "github.com/codeshelldev/secured-signal-api/utils/configutils" - log "github.com/codeshelldev/secured-signal-api/utils/logger" "github.com/knadh/koanf/parsers/yaml" ) @@ -41,7 +42,7 @@ func Load() { LoadTokens() - userConf.LoadEnv() + userConf.LoadEnv(normalizeEnv) NormalizeConfig(defaultsConf) NormalizeConfig(userConf) @@ -110,18 +111,19 @@ func Normalize(config *configutils.Config, path string, structure any) { // Load temporary config back into paths config.Layer.Delete(path) - config.Load(tmpConf.Layer.Get("").(map[string]any), path) + config.Load(tmpConf.Layer.Raw(), path) } func InitReload() { - reload := func() { + reload := func(path string) { + log.Debug(path, " changed, reloading...") Load() Log() } - defaultsConf.OnLoad(reload) - userConf.OnLoad(reload) - tokenConf.OnLoad(reload) + defaultsConf.OnReload(reload) + userConf.OnReload(reload) + tokenConf.OnReload(reload) } func InitEnv() { @@ -139,6 +141,7 @@ func InitEnv() { } func LoadDefaults() { + log.Debug("Loading defaults ", ENV.DEFAULTS_PATH) _, err := defaultsConf.LoadFile(ENV.DEFAULTS_PATH, yaml.Parser()) if err != nil { @@ -147,6 +150,7 @@ func LoadDefaults() { } func LoadConfig() { + log.Debug("Loading Config ", ENV.CONFIG_PATH) _, err := userConf.LoadFile(ENV.CONFIG_PATH, yaml.Parser()) if err != nil { @@ -161,3 +165,11 @@ func LoadConfig() { log.Error("Could not Load Config ", ENV.CONFIG_PATH, ": ", err.Error()) } } + +func normalizeEnv(key string, value string) (string, any) { + key = strings.ToLower(key) + key = strings.ReplaceAll(key, "__", ".") + key = strings.ReplaceAll(key, "_", "") + + return key, stringutils.ToType(value) +} diff --git a/internals/config/tokens.go b/internals/config/tokens.go index ac6af7ee..3ef561f7 100644 --- a/internals/config/tokens.go +++ b/internals/config/tokens.go @@ -3,9 +3,9 @@ package config import ( "strconv" + "github.com/codeshelldev/gotl/pkg/configutils" + log "github.com/codeshelldev/gotl/pkg/logger" "github.com/codeshelldev/secured-signal-api/internals/config/structure" - "github.com/codeshelldev/secured-signal-api/utils/configutils" - log "github.com/codeshelldev/secured-signal-api/utils/logger" "github.com/knadh/koanf/parsers/yaml" ) @@ -31,17 +31,15 @@ func NormalizeTokens() { for _, config := range tokenConf.Layer.Slices("tokenconfigs") { tmpConf := configutils.New() - tmpConf.Load(config.Get("").(map[string]any), "") + tmpConf.Load(config.Raw(), "") Normalize(tmpConf, "overrides", &structure.SETTINGS{}) - data = append(data, tmpConf.Layer.Get("").(map[string]any)) + data = append(data, tmpConf.Layer.Raw()) } // Merge token configs together into new temporary config - tokenConf.Load(map[string]any{ - "tokenconfigs": data, - }, "") + tokenConf.Load(data, "tokenconfigs") } func InitTokens() { diff --git a/internals/proxy/middlewares/auth.go b/internals/proxy/middlewares/auth.go index 60afc459..f43daf8a 100644 --- a/internals/proxy/middlewares/auth.go +++ b/internals/proxy/middlewares/auth.go @@ -7,8 +7,8 @@ import ( "slices" "strings" + log "github.com/codeshelldev/gotl/pkg/logger" "github.com/codeshelldev/secured-signal-api/internals/config" - log "github.com/codeshelldev/secured-signal-api/utils/logger" ) var Auth Middleware = Middleware{ diff --git a/internals/proxy/middlewares/endpoints.go b/internals/proxy/middlewares/endpoints.go index 2a26d77b..57bba6d2 100644 --- a/internals/proxy/middlewares/endpoints.go +++ b/internals/proxy/middlewares/endpoints.go @@ -2,11 +2,11 @@ package middlewares import ( "net/http" + "path" "slices" "strings" - "path" - log "github.com/codeshelldev/secured-signal-api/utils/logger" + log "github.com/codeshelldev/gotl/pkg/logger" ) var Endpoints Middleware = Middleware{ diff --git a/internals/proxy/middlewares/log.go b/internals/proxy/middlewares/log.go index 384f12f5..c786535f 100644 --- a/internals/proxy/middlewares/log.go +++ b/internals/proxy/middlewares/log.go @@ -3,8 +3,8 @@ package middlewares import ( "net/http" - log "github.com/codeshelldev/secured-signal-api/utils/logger" - "github.com/codeshelldev/secured-signal-api/utils/request" + log "github.com/codeshelldev/gotl/pkg/logger" + "github.com/codeshelldev/gotl/pkg/request" ) var Logging Middleware = Middleware{ diff --git a/internals/proxy/middlewares/mapping.go b/internals/proxy/middlewares/mapping.go index 1dd055c0..432aef01 100644 --- a/internals/proxy/middlewares/mapping.go +++ b/internals/proxy/middlewares/mapping.go @@ -3,10 +3,10 @@ package middlewares import ( "net/http" + jsonutils "github.com/codeshelldev/gotl/pkg/jsonutils" + log "github.com/codeshelldev/gotl/pkg/logger" + request "github.com/codeshelldev/gotl/pkg/request" "github.com/codeshelldev/secured-signal-api/internals/config/structure" - jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils" - log "github.com/codeshelldev/secured-signal-api/utils/logger" - request "github.com/codeshelldev/secured-signal-api/utils/request" ) var Mapping Middleware = Middleware{ diff --git a/internals/proxy/middlewares/message.go b/internals/proxy/middlewares/message.go index 57491716..02c1d4c9 100644 --- a/internals/proxy/middlewares/message.go +++ b/internals/proxy/middlewares/message.go @@ -3,8 +3,8 @@ package middlewares import ( "net/http" - log "github.com/codeshelldev/secured-signal-api/utils/logger" - request "github.com/codeshelldev/secured-signal-api/utils/request" + log "github.com/codeshelldev/gotl/pkg/logger" + request "github.com/codeshelldev/gotl/pkg/request" ) var Message Middleware = Middleware{ diff --git a/internals/proxy/middlewares/middleware.go b/internals/proxy/middlewares/middleware.go index fe0af218..cc222e35 100644 --- a/internals/proxy/middlewares/middleware.go +++ b/internals/proxy/middlewares/middleware.go @@ -3,7 +3,7 @@ package middlewares import ( "net/http" - "github.com/codeshelldev/secured-signal-api/utils/logger" + "github.com/codeshelldev/gotl/pkg/logger" ) type Middleware struct { diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index 004f8a44..559ccced 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -5,10 +5,10 @@ import ( "net/http" "reflect" + log "github.com/codeshelldev/gotl/pkg/logger" + request "github.com/codeshelldev/gotl/pkg/request" "github.com/codeshelldev/secured-signal-api/internals/config/structure" - log "github.com/codeshelldev/secured-signal-api/utils/logger" - request "github.com/codeshelldev/secured-signal-api/utils/request" - "github.com/codeshelldev/secured-signal-api/utils/request/requestkeys" + "github.com/codeshelldev/secured-signal-api/utils/requestkeys" ) var Policy Middleware = Middleware{ diff --git a/internals/proxy/middlewares/template.go b/internals/proxy/middlewares/template.go index 9d8ae0fa..99e08ff6 100644 --- a/internals/proxy/middlewares/template.go +++ b/internals/proxy/middlewares/template.go @@ -7,12 +7,12 @@ import ( "regexp" "strings" - jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils" - log "github.com/codeshelldev/secured-signal-api/utils/logger" - query "github.com/codeshelldev/secured-signal-api/utils/query" - request "github.com/codeshelldev/secured-signal-api/utils/request" - "github.com/codeshelldev/secured-signal-api/utils/request/requestkeys" - templating "github.com/codeshelldev/secured-signal-api/utils/templating" + jsonutils "github.com/codeshelldev/gotl/pkg/jsonutils" + log "github.com/codeshelldev/gotl/pkg/logger" + query "github.com/codeshelldev/gotl/pkg/query" + request "github.com/codeshelldev/gotl/pkg/request" + templating "github.com/codeshelldev/gotl/pkg/templating" + "github.com/codeshelldev/secured-signal-api/utils/requestkeys" ) var Template Middleware = Middleware{ @@ -190,7 +190,7 @@ func TemplateBody(body map[string]any, headers map[string][]string, VARIABLES ma maps.Copy(variables, prefixedBody) maps.Copy(variables, prefixedHeaders) - templatedData, err := templating.RenderJSON("body", normalizedBody, variables) + templatedData, err := templating.RenderJSON(normalizedBody, variables) if err != nil { return body, false, err @@ -235,10 +235,16 @@ func TemplateQuery(reqUrl *url.URL, data map[string]any, VARIABLES any) (string, originalQueryData := reqUrl.Query() - addedData := query.ParseTypedQuery(templatedQuery, "@") + addedData, _ := query.ParseTypedQuery(templatedQuery) for key, val := range addedData { - data[key] = val + keyWithoutPrefix, match := strings.CutPrefix(key, "@") + + if !match { + continue + } + + data[keyWithoutPrefix] = val originalQueryData.Del(key) diff --git a/main.go b/main.go index 3ac13228..a15115bc 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,11 @@ import ( "net/http" "os" + log "github.com/codeshelldev/gotl/pkg/logger" config "github.com/codeshelldev/secured-signal-api/internals/config" "github.com/codeshelldev/secured-signal-api/internals/config/structure" reverseProxy "github.com/codeshelldev/secured-signal-api/internals/proxy" docker "github.com/codeshelldev/secured-signal-api/utils/docker" - log "github.com/codeshelldev/secured-signal-api/utils/logger" ) var proxy reverseProxy.Proxy diff --git a/tests/json_test.go b/tests/json_test.go deleted file mode 100644 index f738df3d..00000000 --- a/tests/json_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package tests - -import ( - "testing" - - jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils" - templating "github.com/codeshelldev/secured-signal-api/utils/templating" -) - -func TestJsonTemplating(t *testing.T) { - variables := map[string]any{ - "array": []string{ - "item0", - "item1", - }, - "key": "val", - "int": 4, - } - - json := ` - { - "multiple": "{{.key}}, {{.int}}", - "dict": { "key": "{{.key}}" }, - "dictArray": [ - { "key": "{{.key}}" }, - { "key": "{{.array}}" } - ], - "key1": "{{.array}}", - "key2": "{{.int}}" - }` - - data := jsonutils.GetJson[map[string]any](json) - - expected := map[string]any{ - "multiple": "val, 4", - "dict": map[string]any{ - "key": "val", - }, - "dictArray": []any{ - map[string]any{"key": "val"}, - map[string]any{"key": []any{"item0", "item1"}}, - }, - "key1": []any{"item0", "item1"}, - "key2": 4, - } - - got, err := templating.RenderDataKeyTemplateRecursive("", data, variables) - - if err != nil { - t.Error("Error Templating JSON:\n", err.Error()) - } - - expectedStr := jsonutils.ToJson(expected) - gotStr := jsonutils.ToJson(got) - - if expectedStr != gotStr { - t.Error("\nExpected: ", expectedStr, "\nGot: ", gotStr) - } -} - -func TestJsonPath(t *testing.T) { - json := ` - { - "dict": { "key": "value" }, - "dictArray": [ - { "key": "value0" }, - { "key": "value1" } - ], - "array": [ - "item0", - "item1" - ], - "key": "val" - }` - - data := jsonutils.GetJson[map[string]any](json) - - cases := []struct { - key string - expected string - }{ - { - key: "key", - expected: "val", - }, - { - key: "dict.key", - expected: "value", - }, - { - key: "dictArray[0].key", - expected: "value0", - }, - { - key: "dictArray[1].key", - expected: "value1", - }, - { - key: "array[0]", - expected: "item0", - }, - { - key: "array[1]", - expected: "item1", - }, - } - - for _, c := range cases { - key := c.key - expected := c.expected - - got, ok := jsonutils.GetByPath(key, data) - - if !ok || got.(string) != expected { - t.Error("Expected: ", key, " == ", expected, "; Got: ", got) - } - } -} diff --git a/tests/request_test.go b/tests/request_test.go deleted file mode 100644 index eca74f19..00000000 --- a/tests/request_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package tests - -import ( - "testing" - - jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils" - query "github.com/codeshelldev/secured-signal-api/utils/query" - templating "github.com/codeshelldev/secured-signal-api/utils/templating" -) - -func TestQueryTemplating(t *testing.T) { - variables := map[string]interface{}{ - "value": "helloworld", - "array": []string{ - "hello", - "world", - }, - } - - queryStr := "key={{.value}}&array={{.array}}" - - got, err := templating.RenderNormalizedTemplate("query", queryStr, variables) - - if err != nil { - t.Error("Error Templating Query: ", err.Error()) - } - - expected := "key=helloworld&array=[hello,world]" - - if got != expected { - t.Error("Expected: ", expected, "; Got: ", got) - } -} - -func TestTypedQuery(t *testing.T) { - queryStr := "key=helloworld&array=[hello,world]&int=1" - - got := query.ParseTypedQuery(queryStr, "") - - expected := map[string]interface{}{ - "key": "helloworld", - "int": 1, - "array": []string{ - "hello", "world", - }, - } - - expectedStr := jsonutils.ToJson(expected) - gotStr := jsonutils.ToJson(got) - - if expectedStr != gotStr { - t.Error("\nExpected: ", expectedStr, "\nGot: ", gotStr) - } -} diff --git a/tests/string_test.go b/tests/string_test.go deleted file mode 100644 index 075d31a7..00000000 --- a/tests/string_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package tests - -import ( - "reflect" - "testing" - - stringutils "github.com/codeshelldev/secured-signal-api/utils/stringutils" -) - -func TestStringEscaping(t *testing.T) { - str1 := `\-` - - res1 := stringutils.IsEscaped(str1, "-") - - if !res1 { - t.Error("Expected: ", str1, " == true", "; Got: ", str1, " == ", res1) - } - - str2 := "-" - - res2 := stringutils.IsEscaped(str2, "-") - - if res2 { - t.Error("Expected: ", str2, " == false", "; Got: ", str2, " == ", res2) - } - - str3 := `-\-` - - res3 := stringutils.Contains(str3, "-") - - if !res3 { - t.Error("Expected: ", str3, " == true", "; Got: ", str3, " == ", res3) - } -} - -func TestStringEnclosement(t *testing.T) { - str1 := "[enclosed]" - - res1 := stringutils.IsEnclosedBy(str1, `[`, `]`) - - if !res1 { - t.Error("Expected: ", str1, " == true", "; Got: ", str1, " == ", res1) - } - - str2 := `\[enclosed]` - - res2 := stringutils.IsEnclosedBy(str2, `[`, `]`) - - if res2 { - t.Error("Expected: ", str2, " == false", "; Got: ", str2, " == ", res2) - } -} - -func TestStringToType(t *testing.T) { - str1 := `[item1,item2]` - - res1 := stringutils.ToType(str1) - - if reflect.TypeOf(res1) != reflect.TypeFor[[]string]() { - t.Error("Expected: ", str1, " == []string", "; Got: ", str1, " == ", reflect.TypeOf(res1)) - } - - str2 := `1` - - res2 := stringutils.ToType(str2) - - if reflect.TypeOf(res2) != reflect.TypeFor[int]() { - t.Error("Expected: ", str2, " == int", "; Got: ", str2, " == ", reflect.TypeOf(res2)) - } - - str3 := `{ "key": "value" }` - - res3 := stringutils.ToType(str3) - - if reflect.TypeOf(res3) != reflect.TypeFor[map[string]any]() { - t.Error("Expected: ", str3, " == map[string]any", "; Got: ", str3, " == ", reflect.TypeOf(res3)) - } -} diff --git a/utils/configutils/configutils.go b/utils/configutils/configutils.go deleted file mode 100644 index b0686aa8..00000000 --- a/utils/configutils/configutils.go +++ /dev/null @@ -1,196 +0,0 @@ -package configutils - -import ( - "errors" - "os" - "path/filepath" - "strings" - "sync" - - log "github.com/codeshelldev/secured-signal-api/utils/logger" - stringutils "github.com/codeshelldev/secured-signal-api/utils/stringutils" - - "github.com/knadh/koanf/providers/confmap" - "github.com/knadh/koanf/providers/env/v2" - "github.com/knadh/koanf/providers/file" - "github.com/knadh/koanf/v2" -) - -var configLock sync.Mutex - -type Config struct { - Layer *koanf.Koanf - LoadFunc func() -} - -func New() *Config { - return &Config{ - Layer: koanf.New("."), - LoadFunc: func() {}, - } -} - -func (config *Config) OnLoad(onLoad func()) { - config.LoadFunc = onLoad -} - -func (config *Config) LoadFile(path string, parser koanf.Parser) (koanf.Provider, error) { - log.Debug("Loading Config File: ", path) - - f := file.Provider(path) - - err := config.Layer.Load(f, parser) - - if err != nil { - return nil, err - } - - WatchFile(path, f, config.LoadFunc) - - return f, err -} - -func WatchFile(path string, f *file.File, loadFunc func()) { - f.Watch(func(event any, err error) { - if err != nil { - return - } - - log.Info(path, " changed, Reloading...") - - configLock.Lock() - defer configLock.Unlock() - - f.Unwatch() - - loadFunc() - }) -} - -func getPath(str string) string { - if str == "." { - str = "" - } - - return str -} - -func (config *Config) Load(data map[string]any, path string) error { - parts := strings.Split(path, ".") - - res := map[string]any{} - - if path == "" { - res = data - } else { - for i, key := range parts { - if i == 0 { - res[key] = data - } else { - sub := map[string]any{} - - sub[key] = res - - res = sub - } - } - } - - return config.Layer.Load(confmap.Provider(res, "."), nil) -} - -func (config *Config) Delete(path string) (error) { - if !config.Layer.Exists(path) { - return errors.New("path not found") - } - - all := config.Layer.All() - - if all == nil { - return errors.New("empty config") - } - - for _, key := range config.Layer.Keys() { - if strings.HasPrefix(key, path + ".") || key == path { - config.Layer.Delete(key) - } - } - - return nil -} - -func (config *Config) LoadDir(path string, dir string, ext string, parser koanf.Parser) error { - files, err := filepath.Glob(filepath.Join(dir, "*" + ext)) - - if err != nil { - return nil - } - - var array []any - - for _, f := range files { - tmp := New() - - tmp.OnLoad(config.LoadFunc) - - _, err := tmp.LoadFile(f, parser) - - if err != nil { - return err - } - - array = append(array, tmp.Layer.Raw()) - } - - wrapper := map[string]any{ - path: array, - } - - return config.Load(wrapper, "") -} - -func (config *Config) LoadEnv() (koanf.Provider, error) { - e := env.Provider(".", env.Opt{ - TransformFunc: config.NormalizeEnv, - }) - - err := config.Layer.Load(e, nil) - - if err != nil { - log.Fatal("Error loading env: ", err.Error()) - } - - return e, err -} - -func (config *Config) TemplateConfig() { - data := config.Layer.All() - - for key, value := range data { - str, isStr := value.(string) - - if isStr { - templated := os.ExpandEnv(str) - - if templated != "" { - data[key] = templated - } - } - } - - config.Load(data, "") -} - -func (config *Config) MergeLayers(layers ...*koanf.Koanf) { - for _, layer := range layers { - config.Layer.Merge(layer) - } -} - -func (config *Config) NormalizeEnv(key string, value string) (string, any) { - key = strings.ToLower(key) - key = strings.ReplaceAll(key, "__", ".") - key = strings.ReplaceAll(key, "_", "") - - return key, stringutils.ToType(value) -} diff --git a/utils/configutils/transform.go b/utils/configutils/transform.go deleted file mode 100644 index 699a2235..00000000 --- a/utils/configutils/transform.go +++ /dev/null @@ -1,205 +0,0 @@ -package configutils - -import ( - "maps" - "reflect" - "strconv" - "strings" -) - -type TransformTarget struct { - Key string - Transform string - ChildTransform string - Value any -} - -func GetKeyToTransformMap(value any) map[string]TransformTarget { - data := map[string]TransformTarget{} - - if value == nil { - return data - } - - v := reflect.ValueOf(value) - t := reflect.TypeOf(value) - - if t.Kind() == reflect.Ptr { - v = v.Elem() - t = t.Elem() - } - - if t.Kind() != reflect.Struct { - return data - } - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - fieldValue := v.Field(i) - - key := field.Tag.Get("koanf") - if key == "" { - continue - } - - lower := strings.ToLower(key) - - transformTag := field.Tag.Get("transform") - childTransformTag := field.Tag.Get("childtransform") - - data[lower] = TransformTarget{ - Key: lower, - Transform: transformTag, - ChildTransform: childTransformTag, - Value: getValueSafe(fieldValue), - } - - // Recursively walk nested structs - if fieldValue.Kind() == reflect.Struct || (fieldValue.Kind() == reflect.Ptr && fieldValue.Elem().Kind() == reflect.Struct) { - - sub := GetKeyToTransformMap(fieldValue.Interface()) - - for subKey, subValue := range sub { - fullKey := lower + "." + strings.ToLower(subKey) - - data[fullKey] = subValue - } - } - } - - return data -} - -func getValueSafe(value reflect.Value) any { - if !value.IsValid() { - return nil - } - if value.Kind() == reflect.Ptr { - if value.IsNil() { - return nil - } - return getValueSafe(value.Elem()) - } - return value.Interface() -} - -func (config Config) ApplyTransformFuncs(structSchema any, path string, funcs map[string]func(string, any) (string, any)) { - path = getPath(path) - - transformTargets := GetKeyToTransformMap(structSchema) - - data := config.Layer.Get(path) - - _, res := applyTransform("", data, transformTargets, funcs) - - mapRes, ok := res.(map[string]any) - - if !ok { - return - } - - config.Layer.Delete("") - config.Load(mapRes, path) -} - -func applyTransform(key string, value any, transformTargets map[string]TransformTarget, funcs map[string]func(string, any) (string, any)) (string, any) { - lower := strings.ToLower(key) - target := transformTargets[lower] - - targets := map[string]TransformTarget{} - - maps.Copy(targets, transformTargets) - - newKey, _ := applyTransformToAny(lower, value, transformTargets, funcs) - - newKeyWithDot := newKey - - if newKey != "" { - newKeyWithDot = newKey + "." - } - - switch asserted := value.(type) { - case map[string]any: - res := map[string]any{} - - for k, v := range asserted { - fullKey := newKeyWithDot + k - - _, ok := targets[fullKey] - - if !ok { - childTarget := TransformTarget{ - Key: fullKey, - Transform: target.ChildTransform, - ChildTransform: target.ChildTransform, - } - - targets[fullKey] = childTarget - } - - childKey, childValue := applyTransform(fullKey, v, targets, funcs) - - keyParts := getKeyParts(childKey) - - res[keyParts[len(keyParts)-1]] = childValue - } - - keyParts := getKeyParts(newKey) - - return keyParts[len(keyParts)-1], res - case []any: - res := []any{} - - for i, child := range asserted { - fullKey := newKeyWithDot + strconv.Itoa(i) - - _, ok := targets[fullKey] - - if !ok { - childTarget := TransformTarget{ - Key: fullKey, - Transform: target.ChildTransform, - ChildTransform: target.ChildTransform, - } - - targets[fullKey] = childTarget - } - - _, childValue := applyTransform(fullKey, child, targets, funcs) - - res = append(res, childValue) - } - - keyParts := getKeyParts(newKey) - - return keyParts[len(keyParts)-1], res - default: - return applyTransformToAny(key, asserted, transformTargets, funcs) - } -} - -func applyTransformToAny(key string, value any, transformTargets map[string]TransformTarget, funcs map[string]func(string, any) (string, any)) (string, any) { - lower := strings.ToLower(key) - - transformTarget, ok := transformTargets[lower] - if !ok { - transformTarget.Transform = "default" - } - - fn, ok := funcs[transformTarget.Transform] - if !ok { - fn = funcs["default"] - } - - keyParts := getKeyParts(key) - - newKey, newValue := fn(keyParts[len(keyParts)-1], value) - - return newKey, newValue -} - -func getKeyParts(fullKey string) []string { - keyParts := strings.Split(fullKey, ".") - - return keyParts -} \ No newline at end of file diff --git a/utils/docker/docker.go b/utils/docker/docker.go index 0a8d593e..21b4f2ff 100644 --- a/utils/docker/docker.go +++ b/utils/docker/docker.go @@ -4,34 +4,24 @@ import ( "context" "net/http" "os" - "os/signal" - "syscall" "time" - log "github.com/codeshelldev/secured-signal-api/utils/logger" + "github.com/codeshelldev/gotl/pkg/docker" + log "github.com/codeshelldev/gotl/pkg/logger" ) -var stop chan os.Signal - func Init() { log.Info("Running ", os.Getenv("IMAGE_TAG"), " Image") } func Run(main func()) chan os.Signal { - stop = make(chan os.Signal, 1) - signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) - - go main() - - return stop + return docker.Run(main) } func Exit(code int) { log.Info("Exiting...") - os.Exit(code) - - stop <- syscall.SIGTERM + docker.Exit(code) } func Shutdown(server *http.Server) { diff --git a/utils/jsonutils/jsonutils.go b/utils/jsonutils/jsonutils.go deleted file mode 100644 index 7a461668..00000000 --- a/utils/jsonutils/jsonutils.go +++ /dev/null @@ -1,84 +0,0 @@ -package jsonutils - -import ( - "encoding/json" - "regexp" - "strconv" -) - -func GetByPath(path string, data any) (any, bool) { - // Split into parts by `.` and `[]` - re := regexp.MustCompile(`\.|\[|\]`) - - parts := re.Split(path, -1) - - cleaned := []string{} - - for _, part := range parts { - if part != "" { - cleaned = append(cleaned, part) - } - } - - current := data - - for _, key := range cleaned { - switch currentDataType := current.(type) { - // Case: Dictionary - case map[string]any: - value, ok := currentDataType[key] - if !ok { - return nil, false - } - current = value - - // Case: Array - case []any: - index, err := strconv.Atoi(key) - - if err != nil || index < 0 || index >= len(currentDataType) { - return nil, false - } - current = currentDataType[index] - - default: - return nil, false - } - } - - return current, true -} - -func GetJsonSafe[T any](jsonStr string) (T, error) { - var result T - - err := json.Unmarshal([]byte(jsonStr), &result) - - return result, err -} - -func GetJson[T any](jsonStr string) T { - var result T - - json.Unmarshal([]byte(jsonStr), &result) - - return result -} - -func ToJsonSafe[T any](obj T) (string, error) { - bytes, err := json.Marshal(obj) - - return string(bytes), err -} - -func ToJson[T any](obj T) string { - bytes, _ := json.Marshal(obj) - - return string(bytes) -} - -func Pretty[T any](obj T) string { - bytes, _ := json.MarshalIndent(obj, "", " ") - - return string(bytes) -} \ No newline at end of file diff --git a/utils/logger/levelhandler.go b/utils/logger/levelhandler.go deleted file mode 100644 index 8be48a80..00000000 --- a/utils/logger/levelhandler.go +++ /dev/null @@ -1,89 +0,0 @@ -package logger - -import ( - "image/color" - "strconv" - "strings" - - "go.uber.org/zap/zapcore" -) - -const DeveloperLevel zapcore.Level = -2 - -func ParseLevel(s string) zapcore.Level { - switch strings.ToLower(s) { - case "dev": - return DeveloperLevel - case "debug": - return zapcore.DebugLevel - case "info": - return zapcore.InfoLevel - case "warn": - return zapcore.WarnLevel - case "error": - return zapcore.ErrorLevel - case "fatal": - return zapcore.FatalLevel - default: - return zapcore.InfoLevel - } -} - -func ColorCode(str string, color color.RGBA) string { - return startColor(color) + str + endColor() -} - -func ColorToInt(color color.RGBA) (int, int, int, int) { - r, g, b, a := color.R, color.G, color.B, color.A - - red, green, blue, alpha := int(r), int(g), int(b), int(a) - - return red, green, blue, alpha -} - -func startColor(color color.RGBA) string { - red, green, blue, alpha := ColorToInt(color) - - mode := "38;2;" - - if alpha >= 255 { - mode = "48;2;" - } - - colorStr := strconv.Itoa(red) + ";" + strconv.Itoa(green) + ";" + strconv.Itoa(blue) - - return "\x1b[" + mode + colorStr + "m" -} - -func endColor() string { - return "\x1b[0m" -} - -func LevelString(l zapcore.Level) string { - switch l { - case DeveloperLevel: - return "dev" - default: - return l.CapitalString() - } -} - -func CapitalLevel(l zapcore.Level) string { - switch l { - case DeveloperLevel: - return ColorCode("DEV ", color.RGBA{ - R: 95, G: 175, B: 135, - }) - default: - return l.CapitalString() - } -} - -func CustomEncodeLevel(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { - switch l { - case DeveloperLevel: - enc.AppendString(CapitalLevel(l)) - default: - zapcore.CapitalColorLevelEncoder(l, enc) - } -} diff --git a/utils/logger/levels.go b/utils/logger/levels.go deleted file mode 100644 index bf2881e9..00000000 --- a/utils/logger/levels.go +++ /dev/null @@ -1,51 +0,0 @@ -package logger - -import "go.uber.org/zap/zapcore" - -func Dev(data ...any) { - ok := logger.Check(DeveloperLevel, Format(data...)) - - if ok != nil { - ok.Write() - } -} - -func Debug(data ...any) { - logger.Debug(Format(data...)) -} - -func Info(data ...any) { - logger.Info(Format(data...)) -} - -func Warn(data ...any) { - logger.Warn(Format(data...)) -} - -func Error(data ...any) { - logger.Error(Format(data...)) -} - -func Fatal(data ...any) { - logger.Fatal(Format(data...)) -} - -func IsDev() bool { - return logger.Level().Enabled(DeveloperLevel) -} - -func IsDebug() bool { - return logger.Level().Enabled(zapcore.DebugLevel) -} -func IsInfo() bool { - return logger.Level().Enabled(zapcore.InfoLevel) -} -func IsWarn() bool { - return logger.Level().Enabled(zapcore.WarnLevel) -} -func IsError() bool { - return logger.Level().Enabled(zapcore.ErrorLevel) -} -func IsFatal() bool { - return logger.Level().Enabled(zapcore.FatalLevel) -} \ No newline at end of file diff --git a/utils/logger/logger.go b/utils/logger/logger.go deleted file mode 100644 index 3d173f16..00000000 --- a/utils/logger/logger.go +++ /dev/null @@ -1,90 +0,0 @@ -package logger - -import ( - "fmt" - "image/color" - "strconv" - "strings" - - "github.com/codeshelldev/secured-signal-api/utils/jsonutils" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -var _logLevel = "" - -var logger *zap.Logger - -func Init(level string) { - _logLevel = strings.ToLower(level) - - logLevel := ParseLevel(_logLevel) - - cfg := zap.Config{ - Level: zap.NewAtomicLevelAt(logLevel), - Development: false, - Sampling: nil, - Encoding: "console", - EncoderConfig: zapcore.EncoderConfig{ - TimeKey: "time", - LevelKey: "level", - NameKey: "logger", - CallerKey: "caller", - MessageKey: "msg", - StacktraceKey: "stacktrace", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: CustomEncodeLevel, - EncodeTime: zapcore.TimeEncoderOfLayout("02.01 15:04"), - EncodeDuration: zapcore.StringDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - }, - OutputPaths: []string{"stdout"}, - ErrorOutputPaths: []string{"stderr"}, - } - - var err error - - logger, err = cfg.Build(zap.AddCaller(), zap.AddCallerSkip(1)) - - if err != nil { - fmt.Println("Encountered Error during Logger Init: ", err.Error()) - } -} - -func Format(data ...any) string { - res := "" - - for _, item := range data { - switch value := item.(type) { - case string: - res += value - case int: - res += strconv.Itoa(value) - case bool: - if value { - res += "true" - } else { - res += "false" - } - default: - lines := strings.Split(jsonutils.Pretty(value), "\n") - - lineStr := "" - - for _, line := range lines { - lineStr += "\n" + startColor(color.RGBA{ R: 0, G: 135, B: 95,}) + line + endColor() - } - res += lineStr - } - } - - return res -} - -func Level() string { - return LevelString(logger.Level()) -} - -func Sync() { - logger.Sync() -} diff --git a/utils/query/query.go b/utils/query/query.go deleted file mode 100644 index 8d49c82f..00000000 --- a/utils/query/query.go +++ /dev/null @@ -1,55 +0,0 @@ -package query - -import ( - "strings" - - stringutils "github.com/codeshelldev/secured-signal-api/utils/stringutils" -) - -func ParseRawQuery(raw string) map[string][]string { - result := make(map[string][]string) - pairs := strings.SplitSeq(raw, "&") - - for pair := range pairs { - if pair == "" { - continue - } - - parts := strings.SplitN(pair, "=", 2) - - key := parts[0] - val := "" - - if len(parts) == 2 { - val = parts[1] - } - - result[key] = append(result[key], val) - } - - return result -} - -func ParseTypedQueryValues(values []string) any { - raw := values[len(values)-1] - - return stringutils.ToType(raw) -} - -func ParseTypedQuery(query string, matchPrefix string) map[string]any { - addedData := map[string]any{} - - queryData := ParseRawQuery(query) - - for key, value := range queryData { - keyWithoutPrefix, match := strings.CutPrefix(key, matchPrefix) - - if match { - newValue := ParseTypedQueryValues(value) - - addedData[keyWithoutPrefix] = newValue - } - } - - return addedData -} diff --git a/utils/request/request.go b/utils/request/request.go deleted file mode 100644 index 8dd85141..00000000 --- a/utils/request/request.go +++ /dev/null @@ -1,200 +0,0 @@ -package request - -import ( - "bytes" - "encoding/json" - "errors" - "io" - "net/http" - "strconv" - "strings" - - "github.com/codeshelldev/secured-signal-api/utils/query" -) - -const ( - Json BodyType = "Json" - Form BodyType = "Form" - Unknown BodyType = "Unknown" -) - -type BodyType string - -type Body struct { - Data map[string]any - Raw []byte - Empty bool -} - -func (body Body) ToString() string { - return string(body.Raw) -} - -func (body *Body) Write(req *http.Request) error { - newBody, err := CreateBody(body.Data) - - if err != nil { - return err - } - - body = &newBody - - bodyLength := len(body.Raw) - - if req.ContentLength != int64(bodyLength) { - req.ContentLength = int64(bodyLength) - req.Header.Set("Content-Length", strconv.Itoa(bodyLength)) - } - - req.Body = io.NopCloser(bytes.NewReader(body.Raw)) - - return nil -} - -func CreateBody(data map[string]any) (Body, error) { - if len(data) <= 0 { - err := errors.New("empty data map") - - return Body{Empty: true}, err - } - - bytes, err := json.Marshal(data) - - if err != nil { - - return Body{Empty: true}, err - } - - isEmpty := len(data) <= 0 - - return Body{ - Data: data, - Raw: bytes, - Empty: isEmpty, - }, nil -} - -func GetJsonData(body []byte) (map[string]any, error) { - var data map[string]any - - err := json.Unmarshal(body, &data) - - if err != nil { - - return nil, err - } - - return data, nil -} - -func GetFormData(body []byte) (map[string]any, error) { - data := map[string]any{} - - queryData := query.ParseRawQuery(string(body)) - - if len(queryData) <= 0 { - err := errors.New("invalid form data") - - return nil, err - } - - for key, value := range queryData { - data[key] = query.ParseTypedQueryValues(value) - } - - return data, nil -} - -func GetBody(req *http.Request) ([]byte, error) { - bodyBytes, err := io.ReadAll(io.LimitReader(req.Body, 5<<20)) - - req.Body.Close() - - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - - if err != nil { - return nil, err - } - - return bodyBytes, nil -} - -func GetReqHeaders(req *http.Request) map[string][]string { - data := map[string][]string{} - - for key, value := range req.Header { - data[key] = value - } - - return data -} - -func ParseHeaders(headers map[string][]string) map[string]any { - generic := make(map[string]any, len(headers)) - - for i, header := range headers { - if len(header) == 1 { - generic[i] = header[0] - } else { - generic[i] = header - } - } - - return generic -} - -func GetReqBody(req *http.Request) (Body, error) { - bytes, err := GetBody(req) - - var isEmpty bool - - if err != nil { - return Body{Empty: true}, err - } - - if len(bytes) <= 0 { - return Body{Empty: true}, nil - } - - var data map[string]any - - switch GetBodyType(req) { - case Json: - data, err = GetJsonData(bytes) - - if err != nil { - return Body{Empty: true}, err - } - case Form: - data, err = GetFormData(bytes) - - if err != nil { - return Body{Empty: true}, err - } - } - - isEmpty = len(data) <= 0 - - return Body{ - Raw: bytes, - Data: data, - Empty: isEmpty, - }, nil -} - -func GetBodyType(req *http.Request) BodyType { - contentType := req.Header.Get("Content-Type") - - switch { - case strings.HasPrefix(contentType, "application/json"): - return Json - - case strings.HasPrefix(contentType, "multipart/form-data"): - return Form - - case strings.HasPrefix(contentType, "application/x-www-form-urlencoded"): - return Form - default: - return Unknown - } -} \ No newline at end of file diff --git a/utils/request/requestkeys/requestkeys.go b/utils/requestkeys/requestkeys.go similarity index 94% rename from utils/request/requestkeys/requestkeys.go rename to utils/requestkeys/requestkeys.go index 33b7f64b..b2644268 100644 --- a/utils/request/requestkeys/requestkeys.go +++ b/utils/requestkeys/requestkeys.go @@ -1,6 +1,6 @@ package requestkeys -import "github.com/codeshelldev/secured-signal-api/utils/request" +import "github.com/codeshelldev/gotl/pkg/request" type Field struct { Prefix string diff --git a/utils/stringutils/stringutils.go b/utils/stringutils/stringutils.go deleted file mode 100644 index 856cbf26..00000000 --- a/utils/stringutils/stringutils.go +++ /dev/null @@ -1,139 +0,0 @@ -package stringutils - -import ( - "regexp" - "strconv" - "strings" - - jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils" -) - -func ToType(str string) any { - cleaned := strings.TrimSpace(str) - - //* Try JSON - if IsEnclosedBy(cleaned, `[`, `]`) || IsEnclosedBy(cleaned, `{`, `}`) { - data, err := jsonutils.GetJsonSafe[any](str) - - if data != nil && err == nil { - return data - } - } - - //* Try String Slice - if IsEnclosedBy(cleaned, `[`, `]`) { - bracketsless := strings.ReplaceAll(str, "[", "") - bracketsless = strings.ReplaceAll(bracketsless, "]", "") - - var data []string - - if Contains(str, ",") { - data = ToArray(bracketsless) - } else { - data = []string{bracketsless} - } - - if data != nil { - if len(data) > 0 { - return data - } - } - } - - //* Try Number - if !strings.HasPrefix(cleaned, "+") { - intValue, intErr := strconv.Atoi(cleaned) - - if intErr == nil { - return intValue - } - } - - return str -} - -func Contains(str string, match string) bool { - return !IsEscaped(str, match) -} - -// Checks if a string is Enclosed by `char` and are not Escaped -func IsEnclosedBy(str string, charA, charB string) bool { - if NeedsEscapeForRegex(rune(charA[0])) { - charA = `\` + charA - } - - if NeedsEscapeForRegex(rune(charB[0])) { - charB = `\` + charB - } - - regexStr := `(^|[^\\])(\\\\)*(` + charA + `)(.*?)(^|[^\\])(\\\\)*(` + charB + ")" - - re := regexp.MustCompile(regexStr) - - matches := re.FindAllStringSubmatchIndex(str, -1) - - filtered := [][]int{} - - for _, match := range matches { - start := match[len(match)-2] - end := match[len(match)-1] - char := str[start:end] - - if char != `\` { - filtered = append(filtered, match) - } - } - - return len(filtered) > 0 -} - -// Checks if a string is completly Escaped with `\` -func IsEscaped(str string, char string) bool { - if NeedsEscapeForRegex(rune(char[0])) { - char = `\` + char - } - - regexStr := `(^|[^\\])(\\\\)*(` + char + ")" - - re := regexp.MustCompile(regexStr) - - matches := re.FindAllStringSubmatchIndex(str, -1) - - filtered := [][]int{} - - for _, match := range matches { - start := match[len(match)-2] - end := match[len(match)-1] - char := str[start:end] - - if char != `\` { - filtered = append(filtered, match) - } - } - - return len(filtered) == 0 -} - -func NeedsEscapeForRegex(char rune) bool { - special := `.+*?()|[]{}^$\\` - - return strings.ContainsRune(special, char) -} - -func ToArray(sliceStr string) []string { - if sliceStr == "" { - return nil - } - - rawItems := strings.Split(sliceStr, ",") - items := make([]string, 0, len(rawItems)) - - for _, item := range rawItems { - trimmed := strings.TrimSpace(item) - if trimmed != "" { - items = append(items, trimmed) - } - } - - return items -} diff --git a/utils/templating/templating.go b/utils/templating/templating.go deleted file mode 100644 index 9ed87507..00000000 --- a/utils/templating/templating.go +++ /dev/null @@ -1,204 +0,0 @@ -package templating - -import ( - "bytes" - "fmt" - "regexp" - "strings" - "text/template" - - "github.com/codeshelldev/secured-signal-api/utils/stringutils" -) - -func normalize(value any) string { - switch str := value.(type) { - case []string: - return "[" + strings.Join(str, ",") + "]" - case []any: - items := make([]string, len(str)) - - for i, item := range str { - items[i] = fmt.Sprintf("%v", item) - } - - return "[" + strings.Join(items, ",") + "]" - default: - return fmt.Sprintf("%v", value) - } -} - -func AddTemplateFunc(tmplStr string, funcName string) (string, error) { - return TransformTemplateKeys(tmplStr, `\.`, func(re *regexp.Regexp, match string) string { - reSimple, _ := regexp.Compile(`{{\s*\.[a-zA-Z0-9_.]+\s*}}`) - - if !reSimple.MatchString(match) { - return match - } - - return re.ReplaceAllStringFunc(match, func(varMatch string) string { - varName := re.ReplaceAllString(varMatch, ".$1") - - return strings.ReplaceAll(varMatch, varName, "("+funcName+" "+varName+")") - }) - }) -} - -func TransformTemplateKeys(tmplStr string, prefix string, transform func(varRegex *regexp.Regexp, m string) string) (string, error) { - re, err := regexp.Compile(`{{([^{}]+)}}`) - - if err != nil { - return tmplStr, err - } - - varRe, err := regexp.Compile(string(prefix) + `("?[a-zA-Z0-9_.]+"?)`) - - if err != nil { - return tmplStr, err - } - - tmplStr = re.ReplaceAllStringFunc(tmplStr, func(match string) string { - return transform(varRe, match) - }) - - return tmplStr, nil -} - -func ParseTemplate(templt *template.Template, tmplStr string, variables any) (string, error) { - tmpl, err := templt.Parse(tmplStr) - - if err != nil { - return "", err - } - var buf bytes.Buffer - - err = tmpl.Execute(&buf, variables) - - if err != nil { - return "", err - } - return buf.String(), nil -} - -func RenderTemplate(name string, tmplStr string, variables any) (string, error) { - templt := template.New(name) - - return ParseTemplate(templt, tmplStr, variables) -} - -func CreateTemplateWithFunc(name string, funcMap template.FuncMap) *template.Template { - return template.New(name).Funcs(funcMap) -} - -func RenderJSON(name string, data map[string]any, variables map[string]any) (map[string]any, error) { - data, err := RenderJSONTemplate(name, data, variables) - - if err != nil { - return data, err - } - - return data, nil -} - -func RenderDataKeyTemplateRecursive(key any, value any, variables map[string]any) (any, error) { - var err error - - strKey, isStr := key.(string) - - if !isStr { - strKey = "!string" - } - - switch typedValue := value.(type) { - case map[string]any: - data := map[string]any{} - - for mapKey, mapValue := range typedValue { - var templatedValue any - - templatedValue, err = RenderDataKeyTemplateRecursive(mapKey, mapValue, variables) - - if err != nil { - return mapValue, err - } - - data[mapKey] = templatedValue - } - - return data, err - - case []any: - data := []any{} - - for arrayIndex, arrayValue := range typedValue { - var templatedValue any - - templatedValue, err = RenderDataKeyTemplateRecursive(arrayIndex, arrayValue, variables) - - if err != nil { - return arrayValue, err - } - - data = append(data, templatedValue) - } - - return data, err - - case string: - templt := CreateTemplateWithFunc("json:"+strKey, template.FuncMap{ - "normalize": normalize, - }) - - tmplStr, _ := AddTemplateFunc(typedValue, "normalize") - - templatedValue, err := ParseTemplate(templt, tmplStr, variables) - - if err != nil { - return typedValue, err - } - - templateRe, err := regexp.Compile(`{{[^{}]+}}`) - - if err == nil { - nonWhitespaceRe, err := regexp.Compile(`(\S+)`) - - if err == nil { - filtered := templateRe.ReplaceAllString(tmplStr, "") - - if !nonWhitespaceRe.MatchString(filtered) { - return stringutils.ToType(templatedValue), err - } - } - } - - return templatedValue, err - - default: - return typedValue, err - } -} - -func RenderJSONTemplate(name string, data map[string]any, variables map[string]any) (map[string]any, error) { - res, err := RenderDataKeyTemplateRecursive("", data, variables) - - mapRes, ok := res.(map[string]any) - - if !ok { - return data, err - } - - return mapRes, err -} - -func RenderNormalizedTemplate(name string, tmplStr string, variables any) (string, error) { - tmplStr, err := AddTemplateFunc(tmplStr, "normalize") - - if err != nil { - return tmplStr, err - } - - templt := CreateTemplateWithFunc(name, template.FuncMap{ - "normalize": normalize, - }) - - return ParseTemplate(templt, tmplStr, variables) -}