diff --git a/internal/utils/env.go b/internal/utils/env.go new file mode 100644 index 00000000..4040b961 --- /dev/null +++ b/internal/utils/env.go @@ -0,0 +1,92 @@ +package utils + +import ( + "fmt" + "reflect" + "strconv" +) + +func MarshalEnv(instance interface{}) ([]string, error) { + v := reflect.ValueOf(instance) + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil, fmt.Errorf("instance is nil") + } + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("instance must be a struct or pointer to struct") + } + + t := v.Type() + var result []string + + for i := 0; i < v.NumField(); i++ { + field := t.Field(i) + fieldValue := v.Field(i) + + // Get the env tag + envTag := field.Tag.Get("env") + if envTag == "" || envTag == "-" { + continue + } + + // Skip unexported fields + if !fieldValue.CanInterface() { + continue + } + + var valueStr string + + // Handle different types + switch fieldValue.Kind() { + case reflect.Bool: + valueStr = strconv.FormatBool(fieldValue.Bool()) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + valueStr = strconv.FormatUint(fieldValue.Uint(), 10) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + valueStr = strconv.FormatInt(fieldValue.Int(), 10) + + case reflect.Float32, reflect.Float64: + valueStr = strconv.FormatFloat(fieldValue.Float(), 'f', -1, 64) + + case reflect.String: + valueStr = fieldValue.String() + + case reflect.Ptr: + if fieldValue.IsNil() { + continue // Skip nil pointers + } + elem := fieldValue.Elem() + // Handle *semver.Version and other pointer types + if elem.CanInterface() { + if stringer, ok := elem.Interface().(fmt.Stringer); ok { + valueStr = stringer.String() + } else { + valueStr = fmt.Sprintf("%v", elem.Interface()) + } + } else { + valueStr = fmt.Sprintf("%v", elem.Interface()) + } + + default: + // For other types, try to convert to string + if fieldValue.CanInterface() { + if stringer, ok := fieldValue.Interface().(fmt.Stringer); ok { + valueStr = stringer.String() + } else { + valueStr = fmt.Sprintf("%v", fieldValue.Interface()) + } + } else { + valueStr = fmt.Sprintf("%v", fieldValue.Interface()) + } + } + + result = append(result, fmt.Sprintf("%s=%s", envTag, valueStr)) + } + + return result, nil +} diff --git a/internal/utils/env_test.go b/internal/utils/env_test.go new file mode 100644 index 00000000..25313677 --- /dev/null +++ b/internal/utils/env_test.go @@ -0,0 +1,57 @@ +package utils + +import ( + "reflect" + "testing" + + "github.com/Masterminds/semver/v3" +) + +type nativeOptions struct { + Disable bool `env:"JETKVM_NATIVE_DISABLE"` + SystemVersion *semver.Version `env:"JETKVM_NATIVE_SYSTEM_VERSION"` + AppVersion *semver.Version `env:"JETKVM_NATIVE_APP_VERSION"` + DisplayRotation uint16 `env:"JETKVM_NATIVE_DISPLAY_ROTATION"` + DefaultQualityFactor float64 `env:"JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR"` +} + +func TestMarshalEnv(t *testing.T) { + tests := []struct { + name string + instance interface{} + want []string + wantErr bool + }{ + { + name: "basic struct", + instance: nativeOptions{ + Disable: false, + SystemVersion: semver.MustParse("1.1.0"), + AppVersion: semver.MustParse("1111.0.0"), + DisplayRotation: 1, + DefaultQualityFactor: 1.0, + }, + want: []string{ + "JETKVM_NATIVE_DISABLE=false", + "JETKVM_NATIVE_SYSTEM_VERSION=1.1.0", + "JETKVM_NATIVE_APP_VERSION=1111.0.0", + "JETKVM_NATIVE_DISPLAY_ROTATION=1", + "JETKVM_NATIVE_DEFAULT_QUALITY_FACTOR=1", + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MarshalEnv(tt.instance) + if (err != nil) != tt.wantErr { + t.Errorf("MarshalEnv() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalEnv() = %v, want %v", got, tt.want) + } + }) + } +}