Skip to content

Commit 498b47e

Browse files
authored
Fix: required as default, and not required (#7)
* fix edgecase with required in the string * so much more complex than I was hoping
1 parent 07ee89d commit 498b47e

File tree

2 files changed

+102
-12
lines changed

2 files changed

+102
-12
lines changed

unmarshal.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,27 @@ func parseTag(tag string) tagOptions {
9292
keys := strings.Split(parts[0], "|")
9393
var fallbackValue string
9494
required := false
95+
9596
if len(parts) > 1 {
9697
extraParts := parts[1]
97-
if strings.Contains(extraParts, "default=[") || strings.Contains(extraParts, "fallback=[") {
98-
re := regexp.MustCompile(`(?:default|fallback)=\[(.*?)\]`)
99-
matches := re.FindStringSubmatch(extraParts)
100-
if len(matches) > 1 {
101-
fallbackValue = matches[1]
102-
}
103-
} else if strings.Contains(extraParts, "default=") || strings.Contains(extraParts, "fallback=") {
104-
re := regexp.MustCompile(`(?:default|fallback)=([^,]+)`)
105-
matches := re.FindStringSubmatch(extraParts)
106-
if len(matches) > 1 {
107-
fallbackValue = matches[1]
98+
inBrackets := false
99+
start := 0
100+
for i := 0; i < len(extraParts); i++ {
101+
switch extraParts[i] {
102+
case '[':
103+
inBrackets = true
104+
case ']':
105+
inBrackets = false
106+
case ',':
107+
if !inBrackets {
108+
part := extraParts[start:i]
109+
start = i + 1
110+
parsePart(part, &fallbackValue, &required)
111+
}
108112
}
109113
}
110-
required = strings.Contains(extraParts, "required")
114+
part := extraParts[start:]
115+
parsePart(part, &fallbackValue, &required)
111116
}
112117

113118
return tagOptions{
@@ -117,6 +122,24 @@ func parseTag(tag string) tagOptions {
117122
}
118123
}
119124

125+
func parsePart(part string, fallbackValue *string, required *bool) {
126+
if strings.Contains(part, "default=[") || strings.Contains(part, "fallback=[") {
127+
re := regexp.MustCompile(`(?:default|fallback)=\[(.*?)]`)
128+
matches := re.FindStringSubmatch(part)
129+
if len(matches) > 1 {
130+
*fallbackValue = matches[1]
131+
}
132+
} else if strings.Contains(part, "default=") || strings.Contains(part, "fallback=") {
133+
re := regexp.MustCompile(`(?:default|fallback)=([^,]+)`)
134+
matches := re.FindStringSubmatch(part)
135+
if len(matches) > 1 {
136+
*fallbackValue = matches[1]
137+
}
138+
} else if strings.TrimSpace(part) == "required" {
139+
*required = true
140+
}
141+
}
142+
120143
// setField sets the value of a struct field based on its type
121144
func setField(field reflect.Value, value string) error {
122145
switch field.Kind() {

unmarshal_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,70 @@ func TestUnmarshalSliceFloatError(t *testing.T) {
399399
err := Unmarshal(&cfg)
400400
assertError(t, err, "Unmarshal SliceFloatError")
401401
}
402+
403+
func TestParseTag(t *testing.T) {
404+
type TestCase struct {
405+
Tag string
406+
ExpectedOpts tagOptions
407+
}
408+
409+
testCases := []TestCase{
410+
{
411+
Tag: "NOT_REQUIRED,default=required",
412+
ExpectedOpts: tagOptions{
413+
keys: []string{"NOT_REQUIRED"},
414+
fallback: "required",
415+
required: false,
416+
},
417+
},
418+
{
419+
Tag: "REQUIRED,required",
420+
ExpectedOpts: tagOptions{
421+
keys: []string{"REQUIRED"},
422+
fallback: "",
423+
required: true,
424+
},
425+
},
426+
{
427+
Tag: "REQUIRED_WITH_DEFAULT,default=default,required",
428+
ExpectedOpts: tagOptions{
429+
keys: []string{"REQUIRED_WITH_DEFAULT"},
430+
fallback: "default",
431+
required: true,
432+
},
433+
},
434+
{
435+
Tag: "SINGLE_KEY,required,default=default",
436+
ExpectedOpts: tagOptions{
437+
keys: []string{"SINGLE_KEY"},
438+
fallback: "default",
439+
required: true,
440+
},
441+
},
442+
{
443+
Tag: "MULTI_KEY1|MULTI_KEY2|MULTI_KEY3,required,default=default",
444+
ExpectedOpts: tagOptions{
445+
keys: []string{"MULTI_KEY1", "MULTI_KEY2", "MULTI_KEY3"},
446+
fallback: "default",
447+
required: true,
448+
},
449+
},
450+
{
451+
Tag: "SQUARE_BRACKETS,default=[item1,item2,item3]",
452+
ExpectedOpts: tagOptions{
453+
keys: []string{"SQUARE_BRACKETS"},
454+
fallback: "item1,item2,item3",
455+
required: false,
456+
},
457+
},
458+
}
459+
460+
for _, tc := range testCases {
461+
t.Run(tc.Tag, func(t *testing.T) {
462+
opts := parseTag(tc.Tag)
463+
if !reflect.DeepEqual(opts, tc.ExpectedOpts) {
464+
t.Errorf("parseTag(%s) returned %+v, expected %+v", tc.Tag, opts, tc.ExpectedOpts)
465+
}
466+
})
467+
}
468+
}

0 commit comments

Comments
 (0)