Skip to content

Commit 6b4f5a5

Browse files
chore: handle integer maps in openapi (#694)
### Context For objects that are maps having key as string and value as integer, the open-api definition looks something like this: ``` schema_name: type: object additionalProperties: type: integer ``` This was not present in public specs till now. But now it is present in one of the spec files [here](https://code.hq.twilio.com/twilio/open-api/blob/f52384fce4c47d3c2d12ef70b39c6b860dd2d701/spec/insights/v2/schemas.yaml#L208). ### Error and its root cause When something like this is processed by our generator, it throws the following error: ``` java.util.regex.PatternSyntaxException: Illegal repetition { [key: string]: number; } occurs in NodeCodegenModelDataTypeResolver.java:66 ``` This arises because when the Twilio Node generator processes additionalProperties definitions as mentioned above, it generates TypeScript-like type definitions such as: ` { [key: string]: number; }` In NodeCodegenModelDataTypeResolver.java:66, this generated type string is incorrectly used as a regex pattern in the replaceFirst() method: ``` consumer.accept(datatypeWithEnum, dataType.replaceFirst(baseType, datatypeWithEnum)); ``` The problem is that { [key: string]: number; } contains curly braces {} which have special meaning in regex as quantifiers (e.g., {n} means "exactly n times"). However, the braces in `{ [key: string]: number; }` are not properly formatted as regex quantifiers, causing the "Illegal repetition" error. ### Solution The fix is to escape the baseType parameter before using it as a regex pattern with the help of Pattern.quote() method which treats the string as a literal pattern, escaping any regex special characters. ### Difference The change is relevant for fluent api languages like node, python and ruby. However, as mentioned above, since we didn't have any string-to-int map till now in our open-api specs, the change does not create any difference in the existing helpers. ### Checklist - [x] I acknowledge that all my contributions will be made under the project's license - [ ] Run `make test-docker` - [ ] Verify affected language: - [ ] Generate [twilio-go](https://github.com/twilio/twilio-go) from our [OpenAPI specification](https://github.com/twilio/twilio-oai) using the [build_twilio_go.py](./examples/build_twilio_go.py) using `python examples/build_twilio_go.py path/to/twilio-oai/spec/yaml path/to/twilio-go` and inspect the diff - [ ] Run `make test` in `twilio-go` - [ ] Create a pull request in `twilio-go` - [ ] Provide a link below to the pull request - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) - [ ] I have read the [Contribution Guidelines](https://github.com/twilio/twilio-oai-generator/blob/main/CONTRIBUTING.md) and my PR follows them - [ ] I have titled the PR appropriately - [ ] I have updated my branch with the main branch - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified If you have questions, please create a GitHub Issue in this repository.
1 parent f815343 commit 6b4f5a5

File tree

11 files changed

+41
-6
lines changed

11 files changed

+41
-6
lines changed

examples/csharp/src/Twilio/Rest/Versionless/DeployedDevices/FleetResource.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ public static string ToJson(object model)
202202
[JsonProperty("name")]
203203
public string Name { get; private set; }
204204

205+
///<summary> The test_int_map </summary>
206+
[JsonProperty("test_int_map")]
207+
public Dictionary<string, int> TestIntMap { get; private set; }
208+
205209
///<summary> A string that uniquely identifies this Fleet. </summary>
206210
[JsonProperty("sid")]
207211
public string Sid { get; private set; }

examples/java/src/main/java/com/twilio/rest/versionless/deployedDevices/Fleet.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public static Fleet fromJson(final InputStream json, final ObjectMapper objectMa
127127
}
128128

129129
private final String name;
130+
private final Map<String, Integer> testIntMap;
130131
private final String sid;
131132
private final String friendlyName;
132133

@@ -135,20 +136,27 @@ private Fleet(
135136
@JsonProperty("name")
136137
final String name,
137138

139+
@JsonProperty("test_int_map")
140+
final Map<String, Integer> testIntMap,
141+
138142
@JsonProperty("sid")
139143
final String sid,
140144

141145
@JsonProperty("friendly_name")
142146
final String friendlyName
143147
) {
144148
this.name = name;
149+
this.testIntMap = testIntMap;
145150
this.sid = sid;
146151
this.friendlyName = friendlyName;
147152
}
148153

149154
public final String getName() {
150155
return this.name;
151156
}
157+
public final Map<String, Integer> getTestIntMap() {
158+
return this.testIntMap;
159+
}
152160
public final String getSid() {
153161
return this.sid;
154162
}
@@ -168,12 +176,12 @@ public boolean equals(final Object o) {
168176

169177
Fleet other = (Fleet) o;
170178

171-
return Objects.equals(name, other.name) && Objects.equals(sid, other.sid) && Objects.equals(friendlyName, other.friendlyName) ;
179+
return Objects.equals(name, other.name) && Objects.equals(testIntMap, other.testIntMap) && Objects.equals(sid, other.sid) && Objects.equals(friendlyName, other.friendlyName) ;
172180
}
173181

174182
@Override
175183
public int hashCode() {
176-
return Objects.hash(name, sid, friendlyName);
184+
return Objects.hash(name, testIntMap, sid, friendlyName);
177185
}
178186

179187

examples/node/src/rest/versionless/deployed_devices/fleet.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ interface FleetPayload extends FleetResource {}
109109

110110
interface FleetResource {
111111
name: string;
112+
test_int_map: { [key: string]: number };
112113
sid: string;
113114
friendly_name: string;
114115
}
@@ -123,13 +124,15 @@ export class FleetInstance {
123124
sid?: string,
124125
) {
125126
this.name = payload.name;
127+
this.testIntMap = payload.test_int_map;
126128
this.sid = payload.sid;
127129
this.friendlyName = payload.friendly_name;
128130

129131
this._solution = { sid: sid || this.sid };
130132
}
131133

132134
name: string;
135+
testIntMap: { [key: string]: number };
133136
/**
134137
* A string that uniquely identifies this Fleet.
135138
*/
@@ -166,6 +169,7 @@ export class FleetInstance {
166169
toJSON() {
167170
return {
168171
name: this.name,
172+
testIntMap: this.testIntMap,
169173
sid: this.sid,
170174
friendlyName: this.friendlyName,
171175
};

examples/php/src/Twilio/Rest/Versionless/DeployedDevices/FleetInstance.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
/**
3434
* @property string $name
35+
* @property array<string,int> $testIntMap
3536
* @property string|null $sid
3637
* @property string|null $friendlyName
3738
*/
@@ -51,6 +52,7 @@ public function __construct(Version $version, array $payload, ?string $sid = nul
5152
// Marshaled Properties
5253
$this->properties = [
5354
'name' => Values::array_get($payload, 'name'),
55+
'testIntMap' => Values::array_get($payload, 'test_int_map'),
5456
'sid' => Values::array_get($payload, 'sid'),
5557
'friendlyName' => Values::array_get($payload, 'friendly_name'),
5658
];

examples/python/twilio/rest/versionless/deployed_devices/fleet.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class FleetInstance(InstanceResource):
2525

2626
"""
2727
:ivar name:
28+
:ivar test_int_map:
2829
:ivar sid: A string that uniquely identifies this Fleet.
2930
:ivar friendly_name: A human readable description for this Fleet.
3031
"""
@@ -35,6 +36,7 @@ def __init__(
3536
super().__init__(version)
3637

3738
self.name: Optional[str] = payload.get("name")
39+
self.test_int_map: Optional[dict[str, int]] = payload.get("test_int_map")
3840
self.sid: Optional[str] = payload.get("sid")
3941
self.friendly_name: Optional[str] = payload.get("friendly_name")
4042

examples/ruby/lib/twilio-ruby/rest/versionless/deployed_devices/fleet.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def initialize(version, payload, sid: nil)
142142
# Marshaled Properties
143143
@properties = {
144144
'name' => payload['name'],
145+
'test_int_map' => payload['test_int_map'],
145146
'sid' => payload['sid'],
146147
'friendly_name' => payload['friendly_name'],
147148
}
@@ -168,6 +169,12 @@ def name
168169
@properties['name']
169170
end
170171

172+
##
173+
# @return [Hash<String, Integer>]
174+
def test_int_map
175+
@properties['test_int_map']
176+
end
177+
171178
##
172179
# @return [String] A string that uniquely identifies this Fleet.
173180
def sid

examples/spec/twilio_versionless.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ components:
55
properties:
66
name:
77
type: string
8+
test_int_map:
9+
type: object
10+
additionalProperties:
11+
type: integer
812
versionless.fleet.instance:
913
type: object
1014
properties:

src/main/java/com/twilio/oai/api/FluentApiResourceBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.Objects;
1313
import java.util.TreeMap;
1414
import java.util.function.BiConsumer;
15+
import java.util.regex.Pattern;
1516
import java.util.stream.Collectors;
1617

1718
import org.openapitools.codegen.CodegenModel;
@@ -198,7 +199,7 @@ protected void updateDataType(final String baseType,
198199

199200
if (baseType != null) {
200201
final String datatypeWithEnum = getDataTypeName(baseType);
201-
consumer.accept(datatypeWithEnum, dataType.replaceFirst(baseType, datatypeWithEnum));
202+
consumer.accept(datatypeWithEnum, dataType.replaceFirst(Pattern.quote(baseType), datatypeWithEnum));
202203
}
203204
}
204205

src/main/java/com/twilio/oai/resolver/node/NodeCodegenModelDataTypeResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.util.Map;
1111
import java.util.function.BiConsumer;
12+
import java.util.regex.Pattern;
1213

1314
import com.twilio.oai.resolver.common.CodegenModelResolver;
1415
import org.openapitools.codegen.CodegenModel;
@@ -63,7 +64,7 @@ private void updateDataType(final String baseType,
6364

6465
if (baseType != null && dataType != null) {
6566
final String datatypeWithEnum = removeEnumName(baseType, apiResourceBuilder);
66-
consumer.accept(datatypeWithEnum, dataType.replaceFirst(baseType, datatypeWithEnum));
67+
consumer.accept(datatypeWithEnum, dataType.replaceFirst(Pattern.quote(baseType), datatypeWithEnum));
6768
}
6869
}
6970

src/main/java/com/twilio/oai/resolver/python/PythonCodegenModelDataTypeResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.util.Map;
1414
import java.util.function.BiConsumer;
15+
import java.util.regex.Pattern;
1516

1617
// Overriding default behavior and handling the enum
1718
public class PythonCodegenModelDataTypeResolver extends CodegenModelDataTypeResolver {
@@ -103,7 +104,7 @@ private void updateDataType(final String baseType,
103104

104105
if (baseType != null && dataType != null) {
105106
final String datatypeWithEnum = removeEnumName(baseType, apiResourceBuilder);
106-
consumer.accept(datatypeWithEnum, dataType.replaceFirst(baseType, datatypeWithEnum));
107+
consumer.accept(datatypeWithEnum, dataType.replaceFirst(Pattern.quote(baseType), datatypeWithEnum));
107108
}
108109
}
109110

0 commit comments

Comments
 (0)