Skip to content

Commit 2b7cc7f

Browse files
committed
Fix Golang pattern validation with regex fails on commas #20079
1 parent ff0fe26 commit 2b7cc7f

File tree

30 files changed

+601
-28
lines changed

30 files changed

+601
-28
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,11 @@ public String toEnumVarName(String value, String datatype) {
917917
public boolean specVersionGreaterThanOrEqualTo310(OpenAPI openAPI) {
918918
String originalSpecVersion;
919919
String xOriginalSwaggerVersion = "x-original-swagger-version";
920+
921+
if (openAPI == null) {
922+
return false;
923+
}
924+
920925
if (openAPI.getExtensions() != null && !openAPI.getExtensions().isEmpty() && openAPI.getExtensions().containsValue(xOriginalSwaggerVersion)) {
921926
originalSpecVersion = (String) openAPI.getExtensions().get(xOriginalSwaggerVersion);
922927
} else {

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import java.io.File;
3131
import java.util.*;
32+
import java.util.regex.Matcher;
3233

3334
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
3435
import static org.openapitools.codegen.utils.StringUtils.camelize;
@@ -38,6 +39,7 @@ public abstract class AbstractGoCodegen extends DefaultCodegen implements Codege
3839

3940
private final Logger LOGGER = LoggerFactory.getLogger(AbstractGoCodegen.class);
4041
private static final String NUMERIC_ENUM_PREFIX = "_";
42+
private static final String X_GO_CUSTOM_TAG = "x-go-custom-tag";
4143

4244
@Setter
4345
protected boolean withGoCodegenComment = false;
@@ -785,9 +787,20 @@ public ModelsMap postProcessModels(ModelsMap objs) {
785787
}
786788

787789
if (cp.pattern != null) {
788-
cp.vendorExtensions.put("x-go-custom-tag", "validate:\"regexp=" +
789-
cp.pattern.replace("\\", "\\\\").replaceAll("^/|/$", "") +
790-
"\"");
790+
String regexp = String.format(Locale.getDefault(), "regexp=%s", cp.pattern);
791+
792+
// Replace backtick by \\x60, if found
793+
if (regexp.contains("`")) {
794+
regexp = regexp.replace("`", "\\x60");
795+
}
796+
797+
// Escape comma
798+
if (regexp.contains(",")) {
799+
regexp = regexp.replace(",", "\\\\,");
800+
}
801+
802+
String validate = String.format(Locale.getDefault(), "validate:\"%s\"", regexp);
803+
cp.vendorExtensions.put(X_GO_CUSTOM_TAG, validate);
791804
}
792805

793806
// construct data tag in the template: x-go-datatag
@@ -813,8 +826,8 @@ public ModelsMap postProcessModels(ModelsMap objs) {
813826
}
814827

815828
// {{#vendorExtensions.x-go-custom-tag}} {{{.}}}{{/vendorExtensions.x-go-custom-tag}}
816-
if (StringUtils.isNotEmpty(String.valueOf(cp.vendorExtensions.getOrDefault("x-go-custom-tag", "")))) {
817-
goDataTag += " " + cp.vendorExtensions.get("x-go-custom-tag");
829+
if (StringUtils.isNotEmpty(String.valueOf(cp.vendorExtensions.getOrDefault(X_GO_CUSTOM_TAG, "")))) {
830+
goDataTag += " " + cp.vendorExtensions.get(X_GO_CUSTOM_TAG);
818831
}
819832

820833
// if it contains backtick, wrap with " instead
@@ -874,6 +887,11 @@ public ModelsMap postProcessModels(ModelsMap objs) {
874887
return postProcessModelsEnum(objs);
875888
}
876889

890+
@Override
891+
public String addRegularExpressionDelimiter(String pattern) {
892+
return pattern;
893+
}
894+
877895
@Override
878896
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
879897
generateYAMLSpecFile(objs);

modules/openapi-generator/src/main/resources/go/model_oneof.mustache

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,16 @@ func (dst *{{classname}}) UnmarshalJSON(data []byte) error {
7979
} else if match == 1 {
8080
return nil // exactly one match
8181
} else { // no match
82-
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
82+
{{#oneOf}}
83+
if err != nil {
84+
return fmt.Errorf("data failed to match schemas in oneOf({{classname}}): %v", err)
85+
} else {
86+
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
87+
}
88+
{{/oneOf}}
89+
{{^oneOf}}
90+
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
91+
{{/oneOf}}
8392
}
8493
{{/discriminator}}
8594
{{/useOneOfDiscriminatorLookup}}
@@ -114,7 +123,16 @@ func (dst *{{classname}}) UnmarshalJSON(data []byte) error {
114123
} else if match == 1 {
115124
return nil // exactly one match
116125
} else { // no match
117-
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
126+
{{#oneOf}}
127+
if err != nil {
128+
return fmt.Errorf("data failed to match schemas in oneOf({{classname}}): %v", err)
129+
} else {
130+
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
131+
}
132+
{{/oneOf}}
133+
{{^oneOf}}
134+
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
135+
{{/oneOf}}
118136
}
119137
{{/useOneOfDiscriminatorLookup}}
120138
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package openapi
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"os"
10+
"testing"
11+
)
12+
13+
func Test_openapi_DefaultAPIService(t *testing.T) {
14+
15+
configuration := openapiclient.NewConfiguration()
16+
apiClient := openapiclient.NewAPIClient(configuration)
17+
18+
t.Run("Matching regex", func(t *testing.T) {
19+
filesNames := []string{"issue_20079_matching01.json", "issue_20079_matching02.json", "issue_20079_matching03.json", "issue_20079_matching04.json"}
20+
21+
for _, fileName := range filesNames {
22+
data, errLoad := os.ReadFile(fileName)
23+
24+
if errLoad != nil {
25+
t.Errorf("Cannot read test file resource %s: %s", fileName, errLoad.Error())
26+
}
27+
28+
var importCode *openapiclient.FooGet200ResponseCode
29+
30+
errParse := json.Unmarshal(data, &importCode)
31+
32+
if errParse != nil {
33+
t.Errorf("The resource file %s that was expected matching, doesn't: %s", fileName, errParse.Error())
34+
}
35+
}
36+
})
37+
38+
t.Run("Non-matching regex", func(t *testing.T) {
39+
filesNames := []string{"issue_20079_non_matching01.json", "issue_20079_non_matching02.json", "issue_20079_non_matching03.json", "issue_20079_non_matching04.json"}
40+
41+
for _, fileName := range filesNames {
42+
data, errLoad := os.ReadFile(fileName)
43+
44+
if errLoad != nil {
45+
t.Errorf("Cannot read test file resource %s: %s", fileName, errLoad.Error())
46+
}
47+
48+
var importCode *openapiclient.FooGet200ResponseCode
49+
50+
errParse := json.Unmarshal(data, &importCode)
51+
52+
if errParse == nil {
53+
t.Errorf("The resource file %s that was expected non-matching, does", fileName)
54+
}
55+
}
56+
})
57+
58+
t.Run("Test DefaultAPIService FooGet", func(t *testing.T) {
59+
60+
t.Skip("skip test") // remove to run test
61+
62+
resp, httpRes, err := apiClient.DefaultAPI.FooGet(context.Background()).Execute()
63+
64+
require.Nil(t, err)
65+
require.NotNil(t, resp)
66+
assert.Equal(t, 200, httpRes.StatusCode)
67+
68+
})
69+
70+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
openapi: '3.0.3'
2+
info:
3+
title: API Test
4+
version: '1.0'
5+
paths:
6+
/foo:
7+
get:
8+
responses:
9+
'200':
10+
description: OK
11+
content:
12+
application/json:
13+
schema:
14+
type: object
15+
properties:
16+
code:
17+
oneOf:
18+
- $ref: "#/components/schemas/importCode"
19+
20+
components:
21+
schemas:
22+
importCode:
23+
type: object
24+
properties:
25+
code:
26+
type: string
27+
pattern: "^[0-9]{2,}$"
28+
29+
creditCard:
30+
description: "Visa credit card\n
31+
matches: 4123 6453 2222 1746\n
32+
non-matches: 3124 5675 4400 4567, 4123-6453-2222-1746"
33+
type: string
34+
35+
pattern: "^4[0-9]{3}\\s[0-9]{4}\\s[0-9]{4}\\s[0-9]{4}$"
36+
# Original was: 4[0-9]{3}\s[0-9]{4}\s[0-9]{4}\s[0-9]{4}
37+
38+
date:
39+
description: "Some dates\n
40+
matches: 31/04/1999, 15/12/4567\n
41+
non-matches: 31/4/1999, 31/4/99, 1999/04/19, 42/67/25456"
42+
type: string
43+
pattern: "^([0-2][0-9]|30|31)/(0[1-9]|1[0-2])/[0-9]{4}$"
44+
# Original was: ([0-2][0-9]|30|31)/(0[1-9]|1[0-2])/[0-9]{4} : unchanged
45+
46+
windowsAbsolutePath:
47+
description: "Windows absolute path\n
48+
matches: \\\\server\\share\\file\n
49+
non-matches: \\directory\\directory2, /directory2"
50+
type: string
51+
52+
# This test case doesn't work due to a problem (?) in validator.v2 (?)
53+
# it issues an unexpected unknown tag or Bad Parameter.
54+
55+
# pattern: "^([A-Za-z]:|\\)\\[[:alnum:][:whitespace:]!\"#$%&'()+,-.;=@[]^_`{}~.]*$"
56+
# Original was: ([A-Za-z]:|\\)\\[[:alnum:][:whitespace:]!"#$%&'()+,-.\\;=@\[\]^_`{}~.]*
57+
58+
email1:
59+
description: "Email Address 1\n
60+
matches: abc.123@def456.com, _123@abc.ca\n
61+
non-matches: abc@dummy, ab*cd@efg.hijkl"
62+
type: string
63+
64+
pattern: "^[[:word:]\\-.]+@[[:word:]\\-.]+\\.[[:alpha:]]{2,3}$"
65+
# Original was: [[:word:]\-.]+@[[:word:]\-.]+\.[[:alpha:]]{2,3}
66+
67+
email2:
68+
description: "Email Address 2\n
69+
matches: *@qrstuv@wxyz.12345.com, __1234^%@@abc.def.ghijkl\n
70+
non-matches: abc.123.*&ca, ^%abcdefg123"
71+
type: string
72+
73+
pattern: "^.+@.+\\..+$"
74+
# Original was: .+@.+\..+
75+
76+
htmlHexadecimalColorCode1:
77+
description: "HTML Hexadecimal Color Code 1\n
78+
matches: AB1234, CCCCCC, 12AF3B\n
79+
non-matches: 123G45, 12-44-CC"
80+
type: string
81+
pattern: "^[A-F0-9]{6}$"
82+
# Original was: [A-F0-9]{6} : unchanged
83+
84+
htmlHexadecimalColorCode2:
85+
description: "HTML Hexadecimal Color Code 2\n
86+
matches: AB 11 00, CC 12 D3\n
87+
non-matches: SS AB CD, AA BB CC DD, 1223AB"
88+
type: string
89+
90+
pattern: "^[A-F0-9]{2}\\s[A-F0-9]{2}\\s[A-F0-9]{2}$"
91+
# Original was: [A-F0-9]{2}\s[A-F0-9]{2}\s[A-F0-9]{2}
92+
93+
ipAddress:
94+
description: "IP Address\n
95+
matches: 10.25.101.216\n
96+
non-matches: 0.0.0, 256.89.457.02"
97+
type: string
98+
99+
pattern: "^((2(5[0-5]|[0-4][0-9])|1([0-9][0-9])|([1-9][0-9])|[0-9])\\.){3}(2(5[0-5]|[0-4][0-9])|1([0-9][0-9])|([1-9][0-9])|[0-9])$"
100+
# Original was: ((2(5[0-5]|[0-4][0-9])|1([0-9][0-9])|([1-9][0-9])|[0-9])\.){3}(2(5[0-5]|[0-4][0-9])|1([0-9][0-9])|([1-9][0-9])|[0-9])
101+
102+
javaComments:
103+
description: "Java Comments\n
104+
matches: Matches Java comments that are between /* and */, or one line comments prefaced by //\n
105+
non-matches: a=1"
106+
type: string
107+
108+
# This test case doesn't work due to a problem (?) in validator.v2 (?)
109+
# org.yaml.snakeyaml.scanner declares \* being an invalid escape code at yaml checking step
110+
111+
# pattern: "^/\*.*\*/|//[^\\n]*$"
112+
# Original was: /\*.*\*/|//[^\n]*
113+
114+
money:
115+
description: "\n
116+
matches: $1.00, -$97.65
117+
non-matches: $1, 1.00$, $-75.17"
118+
type: string
119+
120+
pattern: "^(\\+|-)?\\$[0-9]*\\.[0-9]{2}$"
121+
# Original was: (\+|-)?\$[0-9]*\.[0-9]{2}
122+
123+
positiveNegativeDecimalValue:
124+
description: "Positive, negative numbers, and decimal values\n
125+
matches: +41, -412, 2, 7968412, 41, +41.1, -3.141592653
126+
non-matches: ++41, 41.1.19, -+97.14"
127+
type: string
128+
129+
pattern: "^(\\+|-)?[0-9]+(\\.[0-9]+)?$"
130+
# Original was: (\+|-)?[0-9]+(\.[0-9]+)?
131+
132+
password1:
133+
description: "Passwords 1\n
134+
matches: abcd, 1234, A1b2C3d4, 1a2B3\n
135+
non-matches: abc, *ab12, abcdefghijkl"
136+
type: string
137+
pattern: "^[[:alnum:]]{4,10}$"
138+
# Original was: [[:alnum:]]{4,10} : unchanged
139+
140+
password2:
141+
description: "Passwords 2\n
142+
matches: AB_cd, A1_b2c3, a123_\n
143+
non-matches: *&^g, abc, 1bcd"
144+
type: string
145+
146+
pattern: "^[a-zA-Z]\\w{3,7}$"
147+
# Original was: [a-zA-Z]\w{3,7} : unchanged
148+
149+
phoneNumber:
150+
description: "Phone Numbers\n
151+
matches: 519-883-6898, 519 888 6898\n
152+
non-matches: 888 6898, 5198886898, 519 883-6898"
153+
type: string
154+
155+
pattern: "^([2-9][0-9]{2}-[2-9][0-9]{2}-[0-9]{4})|([2-9][0-9]{2}\\s[2-9][0-9]{2}\\s[0-9]{4})$"
156+
# Original was: ([2-9][0-9]{2}-[2-9][0-9]{2}-[0-9]{4})|([2-9][0-9]{2}\s[2-9][0-9]{2}\s[0-9]{4})
157+
158+
sentence1:
159+
description: "Sentences 1\n
160+
matches: Hello, how are you?\n
161+
non-matches: i am fine"
162+
type: string
163+
164+
pattern: "^[A-Z0-9].*(\\.|\\?|!)$"
165+
# Original was: [A-Z0-9].*(\.|\?|!)
166+
167+
sentence2:
168+
description: "Sentences 2\n
169+
matches: Hello, how are you?n
170+
non-matches: i am fine"
171+
type: string
172+
pattern: "^[[:upper:]0-9].*[.?!]$"
173+
# Original was: [[:upper:]0-9].*[.?!] : unchanged
174+
175+
socialSecurityNumber:
176+
description: "Social Security Number\n
177+
matches: 123-45-6789\n
178+
non-matches: 123 45 6789, 123456789, 1234-56-7891"
179+
type: string
180+
pattern: "^[0-9]{3}-[0-9]{2}-[0-9]{4}$"
181+
# Original was: [0-9]{3}-[0-9]{2}-[0-9]{4} : unchanged
182+
183+
url:
184+
description: "URL\n
185+
matches: http://www.sample.com, www.sample.com\n
186+
non-matches: http://sample.com, http://www.sample.comm"
187+
type: string
188+
189+
# \. ==> \\.
190+
pattern: "^(http://)?www\\.[a-zA-Z0-9]+\\.[a-zA-Z]{2,3}$"
191+
# Original was: (http://)?www\.[a-zA-Z0-9]+\.[a-zA-Z]{2,3}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"code": "52",
3+
"creditCard": "4123 6453 2222 1746",
4+
"date": "31/04/1999",
5+
"windowsAbsolutePath": "\\\\server\\share\\file",
6+
7+
"email1": "abc.123@def456.com",
8+
"email2": "*@qrstuv@wxyz.12345.com",
9+
"htmlHexadecimalColorCode1": "AB1234",
10+
"htmlHexadecimalColorCode2": "AB 11 00",
11+
"ipAddress": "10.25.101.216",
12+
13+
"javaComments": "/* a comment */",
14+
"money": "$1.00",
15+
"positiveNegativeDecimalValue": "+41",
16+
"password1": "abcd",
17+
"password2": "AB_cd",
18+
19+
"phoneNumber": "519-883-6898",
20+
"sentence1": "Hello, how are you?",
21+
"sentence2": "Hello, how are you?",
22+
"socialSecurityNumber": "123-45-6789",
23+
"url": "http://www.sample.com"
24+
}

0 commit comments

Comments
 (0)