Skip to content

Commit 9f81ade

Browse files
authored
Auto generate unknown fallback (#1561)
Resolve #1537. #1462 # Summary Previously, in the line-bot-sdk-java, the `Unknown` class was manually implemented and used as the `defaultImpl` (fallback) in `@JsonTypeInfo`. However, with the transition to generating code from OpenAPI definitions, generating the `Unknown` class directly from the OpenAPI spec became challenging. Consequently, the fallback option was disabled. This change created an issue when decoding webhooks. The type field in webhooks may receive new values as future features are developed. If an older SDK version encounters these new values, it could crash while parsing the webhook. This PR aims to re-enable the fallback option by adding code that automatically generates the `Unknown` class from the Java code produced by OpenAPI, ~even if the method is a bit rough or not entirely elegant~ . This ensures that we can maintain the fallback mechanism under OpenAPI management.
1 parent e0991b8 commit 9f81ade

File tree

11 files changed

+199
-6
lines changed

11 files changed

+199
-6
lines changed

clients/line-bot-messaging-api-client/.openapi-generator/FILES

+2
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ src/main/java/com/linecorp/bot/messaging/model/TextMessage.java
153153
src/main/java/com/linecorp/bot/messaging/model/TextMessageV2.java
154154
src/main/java/com/linecorp/bot/messaging/model/URIAction.java
155155
src/main/java/com/linecorp/bot/messaging/model/URIImagemapAction.java
156+
src/main/java/com/linecorp/bot/messaging/model/UnknownMentionTarget.java
157+
src/main/java/com/linecorp/bot/messaging/model/UnknownSubstitutionObject.java
156158
src/main/java/com/linecorp/bot/messaging/model/UpdateRichMenuAliasRequest.java
157159
src/main/java/com/linecorp/bot/messaging/model/UserMentionTarget.java
158160
src/main/java/com/linecorp/bot/messaging/model/UserProfileResponse.java

clients/line-bot-messaging-api-client/src/main/java/com/linecorp/bot/messaging/model/MentionTarget.java

+1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@
3434
use = JsonTypeInfo.Id.NAME,
3535
include = JsonTypeInfo.As.PROPERTY,
3636
property = "type",
37+
defaultImpl = UnknownMentionTarget.class,
3738
visible = true)
3839
public interface MentionTarget {}

clients/line-bot-messaging-api-client/src/main/java/com/linecorp/bot/messaging/model/SubstitutionObject.java

+1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@
3434
use = JsonTypeInfo.Id.NAME,
3535
include = JsonTypeInfo.As.PROPERTY,
3636
property = "type",
37+
defaultImpl = UnknownSubstitutionObject.class,
3738
visible = true)
3839
public interface SubstitutionObject {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2023 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
/**
18+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
19+
* https://openapi-generator.tech Do not edit the class manually.
20+
*/
21+
package com.linecorp.bot.messaging.model;
22+
23+
24+
25+
import com.fasterxml.jackson.annotation.JsonInclude;
26+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
27+
28+
/** UnknownMentionTarget */
29+
@JsonInclude(Include.NON_NULL)
30+
@javax.annotation.Generated(value = "")
31+
public record UnknownMentionTarget() implements MentionTarget {
32+
33+
public static class Builder {
34+
35+
public Builder() {}
36+
37+
public UnknownMentionTarget build() {
38+
return new UnknownMentionTarget();
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2023 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
/**
18+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
19+
* https://openapi-generator.tech Do not edit the class manually.
20+
*/
21+
package com.linecorp.bot.messaging.model;
22+
23+
24+
25+
import com.fasterxml.jackson.annotation.JsonInclude;
26+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
27+
28+
/** UnknownSubstitutionObject */
29+
@JsonInclude(Include.NON_NULL)
30+
@javax.annotation.Generated(value = "")
31+
public record UnknownSubstitutionObject() implements SubstitutionObject {
32+
33+
public static class Builder {
34+
35+
public Builder() {}
36+
37+
public UnknownSubstitutionObject build() {
38+
return new UnknownSubstitutionObject();
39+
}
40+
}
41+
}

generator/src/main/java/com/linecorp/bot/codegen/LineJavaCodegenGenerator.java

+68
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import java.io.InputStream;
2323
import java.nio.charset.StandardCharsets;
2424
import java.util.ArrayList;
25+
import java.util.HashMap;
2526
import java.util.List;
2627
import java.util.Map;
2728
import java.util.NoSuchElementException;
29+
import java.util.Set;
2830
import java.util.stream.Collectors;
2931
import java.util.stream.Stream;
3032

@@ -137,6 +139,72 @@ public ModelsMap postProcessModels(ModelsMap objs) {
137139
return modelsMap;
138140
}
139141

142+
@Override
143+
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
144+
super.postProcessAllModels(objs);
145+
146+
Map<String, ModelsMap> additionalModels = new HashMap<>();
147+
148+
// List of interfaces for which an Unknown class should not be generated for backward compatibility
149+
List<String> definedInterfaces = List.of(
150+
"Event", "MessageContent", "Message", "Action", "DemographicFilter",
151+
"FlexBoxBackground", "FlexComponent", "FlexContainer", "ImagemapAction",
152+
"Mentionee", "ModuleContent", "Recipient", "RichMenuBatchOperation",
153+
"Source", "Template", "ThingsContent"
154+
);
155+
156+
// Set additional unknown* class for jackson's defaultImpl to have unknwon* class as fallback
157+
for (ModelsMap modelsAttrs : objs.values()) {
158+
String packageName = modelsAttrs.containsKey("packageName")
159+
? modelsAttrs.get("packageName").toString()
160+
: null;
161+
String _package = modelsAttrs.containsKey("package")
162+
? modelsAttrs.get("package").toString()
163+
: packageName;
164+
165+
for (ModelMap mo : modelsAttrs.getModels()) {
166+
CodegenModel codegenModel = mo.getModel();
167+
if (codegenModel.discriminator != null) {
168+
String baseName = codegenModel.classname;
169+
170+
// skip for excluded interfaces
171+
if (definedInterfaces.contains(baseName)) {
172+
continue;
173+
}
174+
175+
String fallbackModelName = "Unknown" + baseName;
176+
CodegenModel fallbackModel = new CodegenModel();
177+
fallbackModel.name = fallbackModelName;
178+
fallbackModel.schemaName = fallbackModelName;
179+
fallbackModel.parent = baseName;
180+
fallbackModel.interfaces = List.of(baseName);
181+
fallbackModel.allOf = Set.of(baseName);
182+
fallbackModel.classname = fallbackModelName;
183+
fallbackModel.classVarName = camelize(fallbackModelName);
184+
fallbackModel.dataType = baseName;
185+
fallbackModel.classFilename = fallbackModelName;
186+
fallbackModel.imports = Set.of(baseName);
187+
fallbackModel.setIsAnyType(true);
188+
addImplements(fallbackModel, baseName);
189+
ModelMap fallbackModelMap = new ModelMap();
190+
fallbackModelMap.setModel(fallbackModel);
191+
192+
ModelsMap fallbackModelsMap = new ModelsMap();
193+
fallbackModelsMap.setModels(List.of(fallbackModelMap));
194+
fallbackModelsMap.setImports(List.of(Map.of("import", _package + "." + baseName)));
195+
fallbackModelsMap.put("packageName", packageName);
196+
fallbackModelsMap.put("package", _package);
197+
fallbackModelsMap.put("classname", fallbackModelName);
198+
199+
additionalModels.put(fallbackModelName, fallbackModelsMap);
200+
}
201+
}
202+
}
203+
objs.putAll(additionalModels);
204+
205+
return objs;
206+
}
207+
140208
private String readPartialBody(String path) {
141209
// fill src/main/resources/body/*.java to the body of the class.
142210
try (InputStream inputStream = getClass().getClassLoader()

generator/src/main/resources/line-java-codegen/model/interface.pebble

-5
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@
1414
use = JsonTypeInfo.Id.NAME,
1515
include = JsonTypeInfo.As.PROPERTY,
1616
property = "{{model.discriminator.propertyName}}",
17-
{# Unknown{{className}} is not auto-generated, so the defaultImpl for newly added classes needs to be disabled. #}
18-
{% if ["Event", "MessageContent", "Message", "Action", "DemographicFilter", "FlexBoxBackground", "FlexComponent",
19-
"FlexContainer", "ImagemapAction", "Mentionee", "ModuleContent", "Recipient", "RichMenuBatchOperation",
20-
"Source", "Template", "ThingsContent"] contains model.classname %}
2117
defaultImpl = Unknown{{model.classname}}.class,
22-
{% endif %}
2318
visible = true
2419
)
2520
public interface {{model.classname}} {

line-bot-webhook/.openapi-generator/FILES

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ src/main/java/com/linecorp/bot/webhook/model/TextMessageContent.java
5656
src/main/java/com/linecorp/bot/webhook/model/ThingsContent.java
5757
src/main/java/com/linecorp/bot/webhook/model/ThingsEvent.java
5858
src/main/java/com/linecorp/bot/webhook/model/UnfollowEvent.java
59+
src/main/java/com/linecorp/bot/webhook/model/UnknownMembershipContent.java
5960
src/main/java/com/linecorp/bot/webhook/model/UnlinkThingsContent.java
6061
src/main/java/com/linecorp/bot/webhook/model/UnsendDetail.java
6162
src/main/java/com/linecorp/bot/webhook/model/UnsendEvent.java

line-bot-webhook/src/main/java/com/linecorp/bot/webhook/model/MembershipContent.java

+1
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@
3535
use = JsonTypeInfo.Id.NAME,
3636
include = JsonTypeInfo.As.PROPERTY,
3737
property = "type",
38+
defaultImpl = UnknownMembershipContent.class,
3839
visible = true)
3940
public interface MembershipContent {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2023 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
/**
18+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
19+
* https://openapi-generator.tech Do not edit the class manually.
20+
*/
21+
package com.linecorp.bot.webhook.model;
22+
23+
24+
25+
import com.fasterxml.jackson.annotation.JsonInclude;
26+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
27+
28+
/** UnknownMembershipContent */
29+
@JsonInclude(Include.NON_NULL)
30+
@javax.annotation.Generated(value = "")
31+
public record UnknownMembershipContent() implements MembershipContent {
32+
33+
public static class Builder {
34+
35+
public Builder() {}
36+
37+
public UnknownMembershipContent build() {
38+
return new UnknownMembershipContent();
39+
}
40+
}
41+
}

line-bot-webhook/src/test/java/com/linecorp/bot/webhook/model/UnknownFallbackTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public static Stream<Arguments> targets() {
5050
arguments(MessageContent.class, UnknownMessageContent.class),
5151
arguments(ModuleContent.class, UnknownModuleContent.class),
5252
arguments(Source.class, UnknownSource.class),
53-
arguments(ThingsContent.class, UnknownThingsContent.class)
53+
arguments(ThingsContent.class, UnknownThingsContent.class),
54+
arguments(MembershipContent.class, UnknownMembershipContent.class)
5455
);
5556
}
5657
}

0 commit comments

Comments
 (0)