diff --git a/binding.go b/binding.go index 777f4b1..e8f6513 100644 --- a/binding.go +++ b/binding.go @@ -27,6 +27,7 @@ type tagConfig struct { } var tagConfigCache sync.Map +var optionalTypePkgPath = reflect.TypeOf(Optional[int]{}).PkgPath() func cloneTagConfig(cfg tagConfig) tagConfig { if len(cfg.oneof) == 0 { @@ -653,6 +654,13 @@ func isOptionalType(t reflect.Type) bool { if t.Kind() != reflect.Struct { return false } + if t.PkgPath() != optionalTypePkgPath { + return false + } + name := t.Name() + if name != "Optional" && !strings.HasPrefix(name, "Optional[") { + return false + } if t.NumField() != 2 { return false } diff --git a/binding_bind_test.go b/binding_bind_test.go index ca8eaa1..24d0492 100644 --- a/binding_bind_test.go +++ b/binding_bind_test.go @@ -370,6 +370,45 @@ func TestBindStruct_OptionalField(t *testing.T) { }) } +func TestBindStruct_ValueSetLookalikeStructIsNotOptional(t *testing.T) { + type ValueSet struct { + Value int + Set bool + } + type Config struct { + Flag ValueSet + } + + data := map[string]mergedEntry{ + "flag.value": {value: "42", sourceName: "env"}, + "flag.set": {value: "true", sourceName: "env"}, + } + + var cfg Config + var provFields []FieldProvenance + errors := bindStruct(reflect.ValueOf(&cfg), data, &provFields, "", "") + + if len(errors) > 0 { + t.Fatalf("unexpected errors: %v", errors) + } + + if cfg.Flag.Value != 42 { + t.Errorf("Flag.Value = %d, want %d", cfg.Flag.Value, 42) + } + if !cfg.Flag.Set { + t.Errorf("Flag.Set = %v, want true", cfg.Flag.Set) + } + + valueProv := findProvenance(provFields, "Flag.Value") + if valueProv == nil { + t.Fatal("Flag.Value provenance not found") + } + setProv := findProvenance(provFields, "Flag.Set") + if setProv == nil { + t.Fatal("Flag.Set provenance not found") + } +} + func TestBindStruct_MultipleErrors(t *testing.T) { type Config struct { Host string `conf:"required"`