From 721d95d28fc2273a3b5b316a32200384e7336998 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Mon, 28 Nov 2022 18:22:36 -0300 Subject: [PATCH 1/2] chore: parse escaped double colon (\\:) example struct tag Replaces the regular string splitting by a ":" made during the "example" tag parsing so that it will not split escaped double colons, allowing string examples containing a double colon to be escaped using double backlashes. --- parser.go | 33 ++++++++++++++++++++++++++++++++- parser_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 041e5c1ac..bbb5c135f 100644 --- a/parser.go +++ b/parser.go @@ -15,6 +15,7 @@ import ( "os/exec" "path/filepath" "reflect" + "regexp" "sort" "strconv" "strings" @@ -1441,7 +1442,11 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ result := map[string]interface{}{} for _, value := range values { - mapData := strings.Split(value, ":") + mapData, err := splitByUnescapedDoubleColon(value) + + if err != nil { + return nil, err + } if len(mapData) == 2 { v, err := defineTypeOfExample(arrayType, "", mapData[1]) @@ -1590,6 +1595,32 @@ func walkWith(excludes map[string]struct{}, parseVendor bool) func(path string, } } +// SplitByUnescapedDoubleColon splits a string on every double colon (:) that is not escaped (\\) +func splitByUnescapedDoubleColon(value string) ([]string, error) { + // Since Golang regexp does not support negative lookaheads, we make two capture groups, + // one with what comes before the ":" and another one with everything after. We iterate + // on the string until there are no more ":" matches + re, _ := regexp.Compile("^([^\\\\]+?):(.*?)$") + mapData := make([]string, 0) + + splitString := value + + for { + slices := re.FindStringSubmatch(splitString) + + if slices == nil || len(slices) < 2 { + break + } + + mapData = append(mapData, slices[1]) + splitString = slices[2] + } + + mapData = append(mapData, strings.ReplaceAll(splitString, "\\:", ":")) + + return mapData, nil +} + // GetSwagger returns *spec.Swagger which is the root document object for the API specification. func (parser *Parser) GetSwagger() *spec.Swagger { return parser.swagger diff --git a/parser_test.go b/parser_test.go index c7e5bacb1..7255ccd6a 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3650,6 +3650,49 @@ func TestDefineTypeOfExample(t *testing.T) { }) } +func TestSplitByUnescapedDoubleColon(t *testing.T) { + t.Run("No double colon", func(t *testing.T) { + t.Parallel() + + example, err := splitByUnescapedDoubleColon("example") + assert.NoError(t, err) + assert.Len(t, example, 1) + assert.Equal(t, example[0], "example") + + example, err = splitByUnescapedDoubleColon("") + assert.NoError(t, err) + assert.Len(t, example, 1) + assert.Equal(t, example[0], "") + }) + + t.Run("Unescaped double colon", func(t *testing.T) { + t.Parallel() + + example, err := splitByUnescapedDoubleColon("key_one:one") + assert.NoError(t, err) + assert.Len(t, example, 2) + assert.Equal(t, example[0], "key_one") + assert.Equal(t, example[1], "one") + + example, err = splitByUnescapedDoubleColon("key_one:one:key_two:two") + assert.NoError(t, err) + assert.Len(t, example, 4) + assert.Equal(t, example[0], "key_one") + assert.Equal(t, example[1], "one") + assert.Equal(t, example[2], "key_two") + assert.Equal(t, example[3], "two") + }) + + t.Run("Escaped double colon", func(t *testing.T) { + t.Parallel() + + example, err := splitByUnescapedDoubleColon("key_one\\:one") + assert.NoError(t, err) + assert.Len(t, example, 1) + assert.Equal(t, example[0], "key_one:one") + }) +} + type mockFS struct { os.FileInfo FileName string From a2de9c2fc3766452017a396319b08ecf3893f780 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 29 Nov 2022 11:01:17 -0300 Subject: [PATCH 2/2] refactor: use strings.SplitN to split the string by the first double colon found --- parser.go | 33 +-------------------------------- parser_test.go | 43 ------------------------------------------- 2 files changed, 1 insertion(+), 75 deletions(-) diff --git a/parser.go b/parser.go index bbb5c135f..ea77311d5 100644 --- a/parser.go +++ b/parser.go @@ -15,7 +15,6 @@ import ( "os/exec" "path/filepath" "reflect" - "regexp" "sort" "strconv" "strings" @@ -1442,11 +1441,7 @@ func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{ result := map[string]interface{}{} for _, value := range values { - mapData, err := splitByUnescapedDoubleColon(value) - - if err != nil { - return nil, err - } + mapData := strings.SplitN(value, ":", 2) if len(mapData) == 2 { v, err := defineTypeOfExample(arrayType, "", mapData[1]) @@ -1595,32 +1590,6 @@ func walkWith(excludes map[string]struct{}, parseVendor bool) func(path string, } } -// SplitByUnescapedDoubleColon splits a string on every double colon (:) that is not escaped (\\) -func splitByUnescapedDoubleColon(value string) ([]string, error) { - // Since Golang regexp does not support negative lookaheads, we make two capture groups, - // one with what comes before the ":" and another one with everything after. We iterate - // on the string until there are no more ":" matches - re, _ := regexp.Compile("^([^\\\\]+?):(.*?)$") - mapData := make([]string, 0) - - splitString := value - - for { - slices := re.FindStringSubmatch(splitString) - - if slices == nil || len(slices) < 2 { - break - } - - mapData = append(mapData, slices[1]) - splitString = slices[2] - } - - mapData = append(mapData, strings.ReplaceAll(splitString, "\\:", ":")) - - return mapData, nil -} - // GetSwagger returns *spec.Swagger which is the root document object for the API specification. func (parser *Parser) GetSwagger() *spec.Swagger { return parser.swagger diff --git a/parser_test.go b/parser_test.go index 7255ccd6a..c7e5bacb1 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3650,49 +3650,6 @@ func TestDefineTypeOfExample(t *testing.T) { }) } -func TestSplitByUnescapedDoubleColon(t *testing.T) { - t.Run("No double colon", func(t *testing.T) { - t.Parallel() - - example, err := splitByUnescapedDoubleColon("example") - assert.NoError(t, err) - assert.Len(t, example, 1) - assert.Equal(t, example[0], "example") - - example, err = splitByUnescapedDoubleColon("") - assert.NoError(t, err) - assert.Len(t, example, 1) - assert.Equal(t, example[0], "") - }) - - t.Run("Unescaped double colon", func(t *testing.T) { - t.Parallel() - - example, err := splitByUnescapedDoubleColon("key_one:one") - assert.NoError(t, err) - assert.Len(t, example, 2) - assert.Equal(t, example[0], "key_one") - assert.Equal(t, example[1], "one") - - example, err = splitByUnescapedDoubleColon("key_one:one:key_two:two") - assert.NoError(t, err) - assert.Len(t, example, 4) - assert.Equal(t, example[0], "key_one") - assert.Equal(t, example[1], "one") - assert.Equal(t, example[2], "key_two") - assert.Equal(t, example[3], "two") - }) - - t.Run("Escaped double colon", func(t *testing.T) { - t.Parallel() - - example, err := splitByUnescapedDoubleColon("key_one\\:one") - assert.NoError(t, err) - assert.Len(t, example, 1) - assert.Equal(t, example[0], "key_one:one") - }) -} - type mockFS struct { os.FileInfo FileName string