@@ -120,7 +120,8 @@ func TestReadValues(t *testing.T) {
120120 CustomJSONUnmarshaler2 : CustomJSONUnmarshaler {
121121 Value : "***custom3***" ,
122122 },
123- Duration : time .Hour ,
123+ CustomString : "***custom_text***" ,
124+ Duration : time .Hour ,
124125 SliceDuration : []time.Duration {
125126 time .Hour ,
126127 2 * time .Hour ,
@@ -318,26 +319,27 @@ type Config struct {
318319 NotPopulated string `env:"-"`
319320 unexported string
320321
321- String string `env:"A"`
322- Int int `env:"B"`
323- Int8 int8 `env:"C"`
324- Int16 int16 `env:"D"`
325- Int32 int32 `env:"E"`
326- Int64 int64 `env:"F"`
327- Uint uint `env:"G"`
328- Uint8 uint8 `env:"H"`
329- Uint16 uint16 `env:"I"`
330- Uint32 uint32 `env:"J"`
331- Uint64 uint64 `env:"K"`
332- Float32 float32 `env:"L"`
333- Float64 float64 `env:"M"`
334- Bool bool `env:"N"`
335- ArrString [2 ]string `env:"O"`
336- SliceString []string `env:"P"`
337- Map map [string ]string `env:"Q"`
338- PtrBool * bool `env:"R"`
339- NilPtr * string `env:"MISSING"`
340- NilPtrStruct * struct {
322+ String string `env:"A"`
323+ Int int `env:"B"`
324+ Int8 int8 `env:"C"`
325+ Int16 int16 `env:"D"`
326+ Int32 int32 `env:"E"`
327+ Int64 int64 `env:"F"`
328+ Uint uint `env:"G"`
329+ Uint8 uint8 `env:"H"`
330+ Uint16 uint16 `env:"I"`
331+ Uint32 uint32 `env:"J"`
332+ Uint64 uint64 `env:"K"`
333+ Float32 float32 `env:"L"`
334+ Float64 float64 `env:"M"`
335+ Bool bool `env:"N"`
336+ ArrString [2 ]string `env:"O"`
337+ SliceString []string `env:"P"`
338+ Map map [string ]string `env:"Q"`
339+ PtrBool * bool `env:"R"`
340+ NilPtrIgnored * int `env:"-"`
341+ NilPtr * string `env:"MISSING"`
342+ NilPtrStruct * struct {
341343 A string `env:"A"`
342344 B * string `env:"MISSING"`
343345 }
@@ -351,6 +353,8 @@ type Config struct {
351353 CustomBinaryUnmarshaler CustomBinaryUnmarshaler `env:"CUSTOM_BINARY"`
352354 CustomJSONUnmarshaler CustomJSONUnmarshaler `env:"CUSTOM_JSON"`
353355
356+ CustomString CustomString `env:"CUSTOM_TEXT"`
357+
354358 CustomTextUnmarshaler2 CustomTextUnmarshaler `env:"CUSTOM"`
355359 CustomBinaryUnmarshaler2 CustomBinaryUnmarshaler `env:"CUSTOM"`
356360 CustomJSONUnmarshaler2 CustomJSONUnmarshaler `env:"CUSTOM"`
@@ -398,6 +402,13 @@ func (c *CustomJSONUnmarshaler) UnmarshalJSON(text []byte) error {
398402 return nil
399403}
400404
405+ type CustomString string
406+
407+ func (c * CustomString ) UnmarshalJSON (text []byte ) error {
408+ * c = CustomString ("***" + string (text ) + "***" )
409+ return nil
410+ }
411+
401412func ptr [T any ](t T ) * T {
402413 return & t
403414}
@@ -823,6 +834,175 @@ func TestNilPointer(t *testing.T) {
823834 assertErr (t , err , "envconfig: nil holder" )
824835}
825836
837+ func TestPointerStructDeallocation (t * testing.T ) {
838+ type Sub struct {
839+ Host string `env:"HOST"`
840+ Port int `env:"PORT"`
841+ }
842+
843+ t .Run ("nil_when_no_env_vars_set" , func (t * testing.T ) {
844+ type Config struct {
845+ DB * Sub `envPrefix:"DB"`
846+ }
847+
848+ le := func (key string ) (string , bool ) {
849+ return "" , false
850+ }
851+
852+ var cfg Config
853+ if err := envconfig .Read (& cfg , le ); err != nil {
854+ t .Fatal (err )
855+ }
856+
857+ if cfg .DB != nil {
858+ t .Errorf ("expected DB to be nil, got %+v" , cfg .DB )
859+ }
860+ })
861+
862+ t .Run ("allocated_when_any_env_var_set" , func (t * testing.T ) {
863+ type Config struct {
864+ DB * Sub `envPrefix:"DB"`
865+ }
866+
867+ le := func (key string ) (string , bool ) {
868+ if key == "DB_HOST" {
869+ return "localhost" , true
870+ }
871+ return "" , false
872+ }
873+
874+ var cfg Config
875+ if err := envconfig .Read (& cfg , le ); err != nil {
876+ t .Fatal (err )
877+ }
878+
879+ if cfg .DB == nil {
880+ t .Fatal ("expected DB to be allocated" )
881+ }
882+ if cfg .DB .Host != "localhost" {
883+ t .Errorf ("expected Host=localhost, got %q" , cfg .DB .Host )
884+ }
885+ if cfg .DB .Port != 0 {
886+ t .Errorf ("expected Port=0, got %d" , cfg .DB .Port )
887+ }
888+ })
889+
890+ t .Run ("nested_pointer_struct_nil" , func (t * testing.T ) {
891+ type Inner struct {
892+ Cert string `env:"CERT"`
893+ }
894+ type Outer struct {
895+ Host string `env:"HOST"`
896+ TLS * Inner `envPrefix:"TLS"`
897+ }
898+ type Config struct {
899+ DB * Outer `envPrefix:"DB"`
900+ }
901+
902+ le := func (key string ) (string , bool ) {
903+ if key == "DB_HOST" {
904+ return "localhost" , true
905+ }
906+ return "" , false
907+ }
908+
909+ var cfg Config
910+ if err := envconfig .Read (& cfg , le ); err != nil {
911+ t .Fatal (err )
912+ }
913+
914+ if cfg .DB == nil {
915+ t .Fatal ("expected DB to be allocated" )
916+ }
917+ if cfg .DB .TLS != nil {
918+ t .Errorf ("expected TLS to be nil, got %+v" , cfg .DB .TLS )
919+ }
920+ })
921+ }
922+
923+ func TestUnmarshalerInCompositeTypes (t * testing.T ) {
924+ t .Run ("slice_of_text_unmarshaler" , func (t * testing.T ) {
925+ type Config struct {
926+ Values []CustomString `env:"VALUES"`
927+ }
928+
929+ le := func (key string ) (string , bool ) {
930+ if key == "VALUES" {
931+ return "hello,world" , true
932+ }
933+ return "" , false
934+ }
935+
936+ var cfg Config
937+ if err := envconfig .Read (& cfg , le ); err != nil {
938+ t .Fatal (err )
939+ }
940+
941+ if len (cfg .Values ) != 2 {
942+ t .Fatalf ("expected 2 elements, got %d" , len (cfg .Values ))
943+ }
944+ if cfg .Values [0 ] != "***hello***" {
945+ t .Errorf ("expected ***hello***, got %q" , cfg .Values [0 ])
946+ }
947+ if cfg .Values [1 ] != "***world***" {
948+ t .Errorf ("expected ***world***, got %q" , cfg .Values [1 ])
949+ }
950+ })
951+
952+ t .Run ("map_value_text_unmarshaler" , func (t * testing.T ) {
953+ type Config struct {
954+ Values map [string ]CustomString `env:"VALUES"`
955+ }
956+
957+ le := func (key string ) (string , bool ) {
958+ if key == "VALUES" {
959+ return "a=hello,b=world" , true
960+ }
961+ return "" , false
962+ }
963+
964+ var cfg Config
965+ if err := envconfig .Read (& cfg , le ); err != nil {
966+ t .Fatal (err )
967+ }
968+
969+ if len (cfg .Values ) != 2 {
970+ t .Fatalf ("expected 2 entries, got %d" , len (cfg .Values ))
971+ }
972+ if cfg .Values ["a" ] != "***hello***" {
973+ t .Errorf ("expected ***hello***, got %q" , cfg .Values ["a" ])
974+ }
975+ if cfg .Values ["b" ] != "***world***" {
976+ t .Errorf ("expected ***world***, got %q" , cfg .Values ["b" ])
977+ }
978+ })
979+
980+ t .Run ("array_of_text_unmarshaler" , func (t * testing.T ) {
981+ type Config struct {
982+ Values [2 ]CustomString `env:"VALUES"`
983+ }
984+
985+ le := func (key string ) (string , bool ) {
986+ if key == "VALUES" {
987+ return "hello,world" , true
988+ }
989+ return "" , false
990+ }
991+
992+ var cfg Config
993+ if err := envconfig .Read (& cfg , le ); err != nil {
994+ t .Fatal (err )
995+ }
996+
997+ if cfg .Values [0 ] != "***hello***" {
998+ t .Errorf ("expected ***hello***, got %q" , cfg .Values [0 ])
999+ }
1000+ if cfg .Values [1 ] != "***world***" {
1001+ t .Errorf ("expected ***world***, got %q" , cfg .Values [1 ])
1002+ }
1003+ })
1004+ }
1005+
8261006func assertErr (t * testing.T , err error , exp string ) {
8271007 t .Helper ()
8281008 if err == nil {
0 commit comments