From c8d0bbb5b54f5cec3ebb245f9a21d8a94b3bd944 Mon Sep 17 00:00:00 2001 From: Bobby <30593201+luciferreeves@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:53:17 +0530 Subject: Add initial project structure with Go Fiber framework and environment configuration --- utils/env/functions.go | 97 +++++++++++++++++++++++++++++++++++++++++++ utils/env/parser.go | 28 +++++++++++++ utils/env/setters.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ utils/env/validators.go | 15 +++++++ utils/urls/attach.go | 38 +++++++++++++++++ utils/urls/namespace.go | 7 ++++ utils/urls/path.go | 51 +++++++++++++++++++++++ utils/urls/registery.go | 28 +++++++++++++ 8 files changed, 371 insertions(+) create mode 100644 utils/env/functions.go create mode 100644 utils/env/parser.go create mode 100644 utils/env/setters.go create mode 100644 utils/env/validators.go create mode 100644 utils/urls/attach.go create mode 100644 utils/urls/namespace.go create mode 100644 utils/urls/path.go create mode 100644 utils/urls/registery.go (limited to 'utils') diff --git a/utils/env/functions.go b/utils/env/functions.go new file mode 100644 index 0000000..de03793 --- /dev/null +++ b/utils/env/functions.go @@ -0,0 +1,97 @@ +package env + +import ( + "os" + "reflect" + "strconv" + "strings" + "time" +) + +func getEnv(key, defaultVal string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultVal +} + +func getEnvBool(key string, defaultVal bool) bool { + if value := os.Getenv(key); value != "" { + if parsed, err := strconv.ParseBool(value); err == nil { + return parsed + } + } + return defaultVal +} + +func getEnvDuration(key string, defaultVal time.Duration) time.Duration { + if value := os.Getenv(key); value != "" { + if parsed, err := time.ParseDuration(value); err == nil { + return parsed + } + } + return defaultVal +} + +func getEnvInt(key string, defaultVal int64) int64 { + if value := os.Getenv(key); value != "" { + if parsed, err := strconv.ParseInt(value, 10, 64); err == nil { + return parsed + } + } + return defaultVal +} + +func getEnvFloat(key string, defaultVal float64) float64 { + if value := os.Getenv(key); value != "" { + if parsed, err := strconv.ParseFloat(value, 64); err == nil { + return parsed + } + } + return defaultVal +} + +func getEnvStringSlice(key string, defaultVal []string) []string { + if value := os.Getenv(key); value != "" { + parts := strings.Split(value, ",") + result := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + result = append(result, trimmed) + } + } + return result + } + return defaultVal +} + +func Defaults[T any](config *T) *T { + v := reflect.ValueOf(config) + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { + return config + } + + elem := v.Elem() + t := elem.Type() + newStruct := reflect.New(t) + newElem := newStruct.Elem() + + for i := range elem.NumField() { + field := newElem.Field(i) + fieldType := t.Field(i) + + if !field.CanSet() { + continue + } + + defaultVal := fieldType.Tag.Get("default") + if defaultVal == "" { + continue + } + + setFieldDefault(field, defaultVal) + } + + return newStruct.Interface().(*T) +} diff --git a/utils/env/parser.go b/utils/env/parser.go new file mode 100644 index 0000000..c1fdb53 --- /dev/null +++ b/utils/env/parser.go @@ -0,0 +1,28 @@ +package env + +func Parse(config any) error { + elem, t, err := validateConfigInput(config) + if err != nil { + return err + } + + for i := range elem.NumField() { + field := elem.Field(i) + fieldType := t.Field(i) + + if !field.CanSet() { + continue + } + + envKey := fieldType.Tag.Get("env") + defaultVal := fieldType.Tag.Get("default") + + if envKey == "" { + continue + } + + setFieldFromEnv(field, envKey, defaultVal) + } + + return nil +} diff --git a/utils/env/setters.go b/utils/env/setters.go new file mode 100644 index 0000000..7e42274 --- /dev/null +++ b/utils/env/setters.go @@ -0,0 +1,107 @@ +package env + +import ( + "os" + "reflect" + "strconv" + "strings" + "time" +) + +func setFieldFromEnv(field reflect.Value, envKey, defaultVal string) { + switch field.Kind() { + case reflect.String: + field.SetString(getEnv(envKey, defaultVal)) + case reflect.Bool: + defaultBool, _ := strconv.ParseBool(defaultVal) + field.SetBool(getEnvBool(envKey, defaultBool)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + defaultInt, _ := strconv.ParseInt(defaultVal, 10, 64) + field.SetInt(getEnvInt(envKey, defaultInt)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + defaultUint, _ := strconv.ParseUint(defaultVal, 10, 64) + setUintField(field, envKey, defaultUint) + case reflect.Float32, reflect.Float64: + defaultFloat, _ := strconv.ParseFloat(defaultVal, 64) + field.SetFloat(getEnvFloat(envKey, defaultFloat)) + case reflect.Slice: + setSliceField(field, envKey, defaultVal) + default: + setDurationField(field, envKey, defaultVal) + } +} + +func setUintField(field reflect.Value, envKey string, defaultVal uint64) { + if value := os.Getenv(envKey); value != "" { + if parsed, err := strconv.ParseUint(value, 10, 64); err == nil { + field.SetUint(parsed) + return + } + } + field.SetUint(defaultVal) +} + +func setDurationField(field reflect.Value, envKey, defaultVal string) { + if field.Type() == reflect.TypeFor[time.Duration]() { + defaultDuration, _ := time.ParseDuration(defaultVal) + field.Set(reflect.ValueOf(getEnvDuration(envKey, defaultDuration))) + } +} + +func setSliceField(field reflect.Value, envKey, defaultVal string) { + if field.Type().Elem().Kind() == reflect.String { + var defaultSlice []string + if defaultVal != "" { + parts := strings.Split(defaultVal, ",") + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + defaultSlice = append(defaultSlice, trimmed) + } + } + } + result := getEnvStringSlice(envKey, defaultSlice) + field.Set(reflect.ValueOf(result)) + } +} + +func setFieldDefault(field reflect.Value, defaultVal string) { + switch field.Kind() { + case reflect.String: + field.SetString(defaultVal) + case reflect.Bool: + if defaultBool, err := strconv.ParseBool(defaultVal); err == nil { + field.SetBool(defaultBool) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if defaultInt, err := strconv.ParseInt(defaultVal, 10, 64); err == nil { + field.SetInt(defaultInt) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if defaultUint, err := strconv.ParseUint(defaultVal, 10, 64); err == nil { + field.SetUint(defaultUint) + } + case reflect.Float32, reflect.Float64: + if defaultFloat, err := strconv.ParseFloat(defaultVal, 64); err == nil { + field.SetFloat(defaultFloat) + } + case reflect.Slice: + if field.Type().Elem().Kind() == reflect.String && defaultVal != "" { + parts := strings.Split(defaultVal, ",") + result := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + result = append(result, trimmed) + } + } + field.Set(reflect.ValueOf(result)) + } + default: + if field.Type() == reflect.TypeFor[time.Duration]() { + if defaultDuration, err := time.ParseDuration(defaultVal); err == nil { + field.Set(reflect.ValueOf(defaultDuration)) + } + } + } +} diff --git a/utils/env/validators.go b/utils/env/validators.go new file mode 100644 index 0000000..fa9b17f --- /dev/null +++ b/utils/env/validators.go @@ -0,0 +1,15 @@ +package env + +import ( + "fmt" + "reflect" +) + +func validateConfigInput(config any) (reflect.Value, reflect.Type, error) { + v := reflect.ValueOf(config) + if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct { + return reflect.Value{}, nil, fmt.Errorf("config must be a pointer to struct") + } + elem := v.Elem() + return elem, elem.Type(), nil +} diff --git a/utils/urls/attach.go b/utils/urls/attach.go new file mode 100644 index 0000000..7d5d732 --- /dev/null +++ b/utils/urls/attach.go @@ -0,0 +1,38 @@ +package urls + +import ( + "cafe/types" + "log" + + "github.com/gofiber/fiber/v2" +) + +var methodBinders = map[types.HTTPMethod]func(fiber.Router, string, fiber.Handler) fiber.Router{ + types.GET: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Get(path, h) }, + types.POST: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Post(path, h) }, + types.PUT: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Put(path, h) }, + types.PATCH: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Patch(path, h) }, + types.DELETE: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Delete(path, h) }, + types.OPTIONS: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Options(path, h) }, + types.HEAD: func(r fiber.Router, path string, h fiber.Handler) fiber.Router { return r.Head(path, h) }, +} + +func Attach(app *fiber.App) { + namespaceGroups := make(map[string]fiber.Router) + + for fullName, route := range registry.routes { + group, exists := namespaceGroups[route.namespace] + if !exists { + group = app.Group("/" + route.namespace) + namespaceGroups[route.namespace] = group + } + + binder, ok := methodBinders[route.method] + if !ok { + log.Fatalf("%s", "unsupported HTTP method: "+string(route.method)) + } + + fiberRoute := binder(group, route.path, route.handler) + fiberRoute.Name(fullName) + } +} diff --git a/utils/urls/namespace.go b/utils/urls/namespace.go new file mode 100644 index 0000000..7bb5311 --- /dev/null +++ b/utils/urls/namespace.go @@ -0,0 +1,7 @@ +package urls + +func SetNamespace(namespace string) { + registry.mutex.Lock() + defer registry.mutex.Unlock() + registry.currentNamespace = namespace +} diff --git a/utils/urls/path.go b/utils/urls/path.go new file mode 100644 index 0000000..e24ed58 --- /dev/null +++ b/utils/urls/path.go @@ -0,0 +1,51 @@ +package urls + +import ( + "cafe/types" + "strings" + + "github.com/gofiber/fiber/v2" +) + +func Path(method types.HTTPMethod, path string, handler fiber.Handler, name string) { + registry.mutex.Lock() + defer registry.mutex.Unlock() + + namespace := registry.currentNamespace + fullName := name + fullPath := path + + if namespace != "" { + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + fullName = namespace + "." + name + fullPath = "/" + namespace + path + } else { + if !strings.HasPrefix(fullPath, "/") { + fullPath = "/" + fullPath + } + } + + registry.routes[fullName] = registeredRoute{ + method: method, + path: path, + handler: handler, + namespace: namespace, + name: name, + fullPath: fullPath, + } +} + +func GetFullPath(routeName string) (string, bool) { + registry.mutex.Lock() + defer registry.mutex.Unlock() + + route, ok := registry.routes[routeName] + if !ok { + return "", false + } + + return route.fullPath, true +} diff --git a/utils/urls/registery.go b/utils/urls/registery.go new file mode 100644 index 0000000..b4e6a36 --- /dev/null +++ b/utils/urls/registery.go @@ -0,0 +1,28 @@ +package urls + +import ( + "sync" + + "cafe/types" + + "github.com/gofiber/fiber/v2" +) + +type registeredRoute struct { + method types.HTTPMethod + path string + handler fiber.Handler + namespace string + name string + fullPath string +} + +type routeRegistry struct { + mutex sync.Mutex + currentNamespace string + routes map[string]registeredRoute +} + +var registry = &routeRegistry{ + routes: make(map[string]registeredRoute), +} -- cgit v1.2.3