Skip to content

Commit 5926ee5

Browse files
devplayer0wing328
authored andcommitted
Add callback model (#861)
* Add callback model (#372) This adds a new `CodegenCallback` class, a list of which is now present in `CodegenOperation`. `CodegenOperation` now also includes a `isCallbackRequest` boolean since `fromCallback()` (the method added to `DefaultCodegen` to process operations which contain OpenAPI callbacks) uses CodegenOperation as the model for a callback request. A `CodegenOperation` which represents a callback request will have a `null` operation id. A test is included for this new model. * Generate callback request `operationId` * Add license to `CodegenCallback`
1 parent 8689227 commit 5926ee5

File tree

5 files changed

+320
-10
lines changed

5 files changed

+320
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
3+
* Copyright 2018 SmartBear Software
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.openapitools.codegen;
19+
20+
import java.util.*;
21+
22+
public class CodegenCallback {
23+
public String name;
24+
public boolean hasMore;
25+
public List<Url> urls = new ArrayList<>();
26+
public Map<String, Object> vendorExtensions = new HashMap<>();
27+
28+
public static class Url {
29+
public String expression;
30+
public boolean hasMore;
31+
public List<CodegenOperation> requests = new ArrayList<>();
32+
public Map<String, Object> vendorExtensions = new HashMap<>();
33+
34+
@Override
35+
public boolean equals(Object o) {
36+
if (this == o) return true;
37+
if (o == null || getClass() != o.getClass()) return false;
38+
39+
Url that = (Url) o;
40+
return Objects.equals(that.expression, expression) && Objects.equals(that.hasMore, hasMore) &&
41+
Objects.equals(that.requests, requests) && Objects.equals(that.vendorExtensions, vendorExtensions);
42+
}
43+
@Override
44+
public int hashCode() {
45+
return Objects.hash(expression, hasMore, requests, vendorExtensions);
46+
}
47+
@Override
48+
public String toString() {
49+
StringBuilder sb = new StringBuilder();
50+
sb.append("CodegenCallback.Urls {\n");
51+
sb.append(" expression: ").append(expression).append("\n");
52+
requests.forEach(r -> sb.append(" ").append(r).append("\n"));
53+
sb.append("}");
54+
return sb.toString();
55+
}
56+
}
57+
58+
@Override
59+
public boolean equals(Object o) {
60+
if (this == o) return true;
61+
if (o == null || getClass() != o.getClass()) return false;
62+
63+
CodegenCallback that = (CodegenCallback) o;
64+
return Objects.equals(that.name, name) && Objects.equals(that.hasMore, hasMore) &&
65+
Objects.equals(that.urls, urls) && Objects.equals(that.vendorExtensions, vendorExtensions);
66+
}
67+
@Override
68+
public int hashCode() {
69+
return Objects.hash(name, hasMore, urls, vendorExtensions);
70+
}
71+
@Override
72+
public String toString() {
73+
StringBuilder sb = new StringBuilder();
74+
sb.append("CodegenCallback {\n");
75+
sb.append(" name: ").append(name).append("\n");
76+
urls.forEach(u -> sb.append(" ").append(u).append("\n"));
77+
sb.append("}");
78+
return sb.toString();
79+
}
80+
}

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class CodegenOperation {
3636
isListContainer, isMultipart, hasMore = true,
3737
isResponseBinary = false, isResponseFile = false, hasReference = false,
3838
isRestfulIndex, isRestfulShow, isRestfulCreate, isRestfulUpdate, isRestfulDestroy,
39-
isRestful, isDeprecated;
39+
isRestful, isDeprecated, isCallbackRequest;
4040
public String path, operationId, returnType, httpMethod, returnBaseType,
4141
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
4242
public CodegenDiscriminator discriminator;
@@ -54,6 +54,7 @@ public class CodegenOperation {
5454
public List<CodegenSecurity> authMethods;
5555
public List<Tag> tags;
5656
public List<CodegenResponse> responses = new ArrayList<CodegenResponse>();
57+
public List<CodegenCallback> callbacks = new ArrayList<>();
5758
public Set<String> imports = new HashSet<String>();
5859
public List<Map<String, String>> examples;
5960
public List<Map<String, String>> requestBodyExamples;
@@ -293,6 +294,8 @@ public boolean equals(Object o) {
293294
return false;
294295
if (isDeprecated != that.isDeprecated)
295296
return false;
297+
if (isCallbackRequest != that.isCallbackRequest)
298+
return false;
296299
if (path != null ? !path.equals(that.path) : that.path != null)
297300
return false;
298301
if (operationId != null ? !operationId.equals(that.operationId) : that.operationId != null)
@@ -347,6 +350,8 @@ public boolean equals(Object o) {
347350
return false;
348351
if (responses != null ? !responses.equals(that.responses) : that.responses != null)
349352
return false;
353+
if (callbacks != null ? !callbacks.equals(that.callbacks) : that.callbacks != null)
354+
return false;
350355
if (imports != null ? !imports.equals(that.imports) : that.imports != null)
351356
return false;
352357
if (examples != null ? !examples.equals(that.examples) : that.examples != null)
@@ -386,6 +391,7 @@ public int hashCode() {
386391
result = 31 * result + (isResponseFile ? 13:31);
387392
result = 31 * result + (hasReference ? 13:31);
388393
result = 31 * result + (isDeprecated ? 13:31);
394+
result = 31 * result + (isCallbackRequest ? 13:31);
389395
result = 31 * result + (path != null ? path.hashCode() : 0);
390396
result = 31 * result + (operationId != null ? operationId.hashCode() : 0);
391397
result = 31 * result + (returnType != null ? returnType.hashCode() : 0);
@@ -413,6 +419,7 @@ public int hashCode() {
413419
result = 31 * result + (authMethods != null ? authMethods.hashCode() : 0);
414420
result = 31 * result + (tags != null ? tags.hashCode() : 0);
415421
result = 31 * result + (responses != null ? responses.hashCode() : 0);
422+
result = 31 * result + (callbacks != null ? callbacks.hashCode() : 0);
416423
result = 31 * result + (imports != null ? imports.hashCode() : 0);
417424
result = 31 * result + (examples != null ? examples.hashCode() : 0);
418425
result = 31 * result + (externalDocs != null ? externalDocs.hashCode() : 0);

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

+92-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.swagger.v3.core.util.Json;
2525
import io.swagger.v3.oas.models.OpenAPI;
2626
import io.swagger.v3.oas.models.Operation;
27+
import io.swagger.v3.oas.models.callbacks.Callback;
2728
import io.swagger.v3.oas.models.examples.Example;
2829
import io.swagger.v3.oas.models.headers.Header;
2930
import io.swagger.v3.oas.models.media.ArraySchema;
@@ -50,6 +51,7 @@
5051
import org.apache.commons.lang3.ObjectUtils;
5152
import org.apache.commons.lang3.StringEscapeUtils;
5253
import org.apache.commons.lang3.StringUtils;
54+
import org.apache.commons.lang3.tuple.Pair;
5355
import org.openapitools.codegen.CodegenDiscriminator.MappedModel;
5456
import org.openapitools.codegen.examples.ExampleGenerator;
5557
import org.openapitools.codegen.serializer.SerializerUtils;
@@ -75,6 +77,7 @@
7577
import java.util.TreeSet;
7678
import java.util.regex.Matcher;
7779
import java.util.regex.Pattern;
80+
import java.util.stream.Stream;
7881
import java.util.stream.Collectors;
7982

8083
public class DefaultCodegen implements CodegenConfig {
@@ -2232,14 +2235,17 @@ public CodegenOperation fromOperation(String path,
22322235
Map<String, Schema> schemas,
22332236
OpenAPI openAPI) {
22342237
LOGGER.debug("fromOperation => operation: " + operation);
2238+
if (operation == null)
2239+
throw new RuntimeException("operation cannot be null in fromOperation");
2240+
22352241
CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
22362242
Set<String> imports = new HashSet<String>();
22372243
if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
22382244
op.vendorExtensions.putAll(operation.getExtensions());
2239-
}
22402245

2241-
if (operation == null)
2242-
throw new RuntimeException("operation cannot be null in fromOperation");
2246+
Object isCallbackRequest = op.vendorExtensions.remove("x-callback-request");
2247+
op.isCallbackRequest = Boolean.TRUE.equals(isCallbackRequest);
2248+
}
22432249

22442250
// store the original operationId for plug-in
22452251
op.operationIdOriginal = operation.getOperationId();
@@ -2253,6 +2259,7 @@ public CodegenOperation fromOperation(String path,
22532259
}
22542260
}
22552261
operationId = removeNonNameElementToCamelCase(operationId);
2262+
22562263
op.path = path;
22572264
op.operationId = toOperationId(operationId);
22582265
op.summary = escapeText(operation.getSummary());
@@ -2344,6 +2351,15 @@ public CodegenOperation fromOperation(String path,
23442351
}
23452352
}
23462353

2354+
if (operation.getCallbacks() != null && !operation.getCallbacks().isEmpty()) {
2355+
operation.getCallbacks().forEach((name, callback) -> {
2356+
CodegenCallback c = fromCallback(name, callback, schemas, openAPI);
2357+
c.hasMore = true;
2358+
op.callbacks.add(c);
2359+
});
2360+
op.callbacks.get(op.callbacks.size() - 1).hasMore = false;
2361+
}
2362+
23472363
List<Parameter> parameters = operation.getParameters();
23482364
List<CodegenParameter> allParams = new ArrayList<CodegenParameter>();
23492365
List<CodegenParameter> bodyParams = new ArrayList<CodegenParameter>();
@@ -2621,6 +2637,79 @@ public CodegenResponse fromResponse(OpenAPI openAPI, String responseCode, ApiRes
26212637
return r;
26222638
}
26232639

2640+
/**
2641+
* Convert OAS Callback object to Codegen Callback object
2642+
*
2643+
* @param name callback name
2644+
* @param callback OAS Callback object
2645+
* @param schemas a map of OAS models
2646+
* @param openAPI a OAS object representing the spec
2647+
* @return Codegen Response object
2648+
*/
2649+
public CodegenCallback fromCallback(String name, Callback callback, Map<String, Schema> schemas, OpenAPI openAPI) {
2650+
CodegenCallback c = new CodegenCallback();
2651+
c.name = name;
2652+
2653+
if (callback.getExtensions() != null && !callback.getExtensions().isEmpty()) {
2654+
c.vendorExtensions.putAll(callback.getExtensions());
2655+
}
2656+
2657+
callback.forEach((expression, pi) -> {
2658+
CodegenCallback.Url u = new CodegenCallback.Url();
2659+
u.expression = expression;
2660+
u.hasMore = true;
2661+
2662+
if (pi.getExtensions() != null && !pi.getExtensions().isEmpty()) {
2663+
u.vendorExtensions.putAll(pi.getExtensions());
2664+
}
2665+
2666+
Stream.of(
2667+
Pair.of("get", pi.getGet()),
2668+
Pair.of("head", pi.getHead()),
2669+
Pair.of("put", pi.getPut()),
2670+
Pair.of("post", pi.getPost()),
2671+
Pair.of("delete", pi.getDelete()),
2672+
Pair.of("patch", pi.getPatch()),
2673+
Pair.of("options", pi.getOptions()))
2674+
.filter(p -> p.getValue() != null)
2675+
.forEach(p -> {
2676+
String method = p.getKey();
2677+
Operation op = p.getValue();
2678+
2679+
boolean genId = op.getOperationId() == null;
2680+
if (genId) {
2681+
op.setOperationId(getOrGenerateOperationId(op, c.name+"_"+expression.replaceAll("\\{\\$.*}", ""), method));
2682+
}
2683+
2684+
if (op.getExtensions() == null) {
2685+
op.setExtensions(new HashMap<>());
2686+
}
2687+
// This extension will be removed later by `fromOperation()` as it is only needed here to
2688+
// distinguish between normal operations and callback requests
2689+
op.getExtensions().put("x-callback-request", true);
2690+
2691+
CodegenOperation co = fromOperation(expression, method, op, schemas, openAPI);
2692+
if (genId) {
2693+
co.operationIdOriginal = null;
2694+
// legacy (see `fromOperation()`)
2695+
co.nickname = co.operationId;
2696+
}
2697+
u.requests.add(co);
2698+
});
2699+
2700+
if (!u.requests.isEmpty()) {
2701+
u.requests.get(u.requests.size() - 1).hasMore = false;
2702+
}
2703+
c.urls.add(u);
2704+
});
2705+
2706+
if (!c.urls.isEmpty()) {
2707+
c.urls.get(c.urls.size() - 1).hasMore = false;
2708+
}
2709+
2710+
return c;
2711+
}
2712+
26242713
/**
26252714
* Convert OAS Parameter object to Codegen Parameter object
26262715
*

modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java

+55-6
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,7 @@
3838
import org.testng.annotations.Test;
3939

4040
import java.lang.reflect.Method;
41-
import java.util.Collections;
42-
import java.util.HashMap;
43-
import java.util.HashSet;
44-
import java.util.List;
45-
import java.util.Map;
46-
import java.util.Set;
41+
import java.util.*;
4742
import java.util.stream.Collectors;
4843

4944
public class DefaultCodegenTest {
@@ -407,6 +402,60 @@ public void testDiscriminatorWithCustomMapping() {
407402
verifyPersonDiscriminator(personModel.discriminator);
408403
}
409404

405+
@Test
406+
public void testCallbacks() {
407+
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/callbacks.yaml", null, new ParseOptions()).getOpenAPI();
408+
final CodegenConfig codegen = new DefaultCodegen();
409+
410+
final String path = "/streams";
411+
Operation subscriptionOperation = openAPI.getPaths().get("/streams").getPost();
412+
CodegenOperation op = codegen.fromOperation(path, "post", subscriptionOperation, openAPI.getComponents().getSchemas(), openAPI);
413+
414+
Assert.assertFalse(op.isCallbackRequest);
415+
Assert.assertNotNull(op.operationId);
416+
Assert.assertEquals(op.callbacks.size(), 2);
417+
418+
CodegenCallback cbB = op.callbacks.get(1);
419+
Assert.assertEquals(cbB.name, "dummy");
420+
Assert.assertFalse(cbB.hasMore);
421+
Assert.assertEquals(cbB.urls.size(), 0);
422+
423+
CodegenCallback cbA = op.callbacks.get(0);
424+
Assert.assertEquals(cbA.name, "onData");
425+
Assert.assertTrue(cbA.hasMore);
426+
427+
Assert.assertEquals(cbA.urls.size(), 2);
428+
429+
CodegenCallback.Url urlB = cbA.urls.get(1);
430+
Assert.assertEquals(urlB.expression, "{$request.query.callbackUrl}/test");
431+
Assert.assertFalse(urlB.hasMore);
432+
Assert.assertEquals(urlB.requests.size(), 0);
433+
434+
CodegenCallback.Url urlA = cbA.urls.get(0);
435+
Assert.assertEquals(urlA.expression, "{$request.query.callbackUrl}/data");
436+
Assert.assertTrue(urlA.hasMore);
437+
Assert.assertEquals(urlA.requests.size(), 2);
438+
439+
urlA.requests.forEach(req -> {
440+
Assert.assertTrue(req.isCallbackRequest);
441+
Assert.assertNotNull(req.bodyParam);
442+
Assert.assertEquals(req.responses.size(), 2);
443+
444+
switch (req.httpMethod.toLowerCase(Locale.getDefault())) {
445+
case "post":
446+
Assert.assertEquals(req.operationId, "onDataDataPost");
447+
Assert.assertEquals(req.bodyParam.dataType, "NewNotificationData");
448+
break;
449+
case "delete":
450+
Assert.assertEquals(req.operationId, "onDataDataDelete");
451+
Assert.assertEquals(req.bodyParam.dataType, "DeleteNotificationData");
452+
break;
453+
default:
454+
Assert.fail(String.format(Locale.getDefault(), "invalid callback request http method '%s'", req.httpMethod));
455+
}
456+
});
457+
}
458+
410459
private void verifyPersonDiscriminator(CodegenDiscriminator discriminator) {
411460
CodegenDiscriminator test = new CodegenDiscriminator();
412461
test.setPropertyName("$_type");

0 commit comments

Comments
 (0)