mirror of
https://github.com/valitydev/openapi-generator.git
synced 2024-11-06 10:35:25 +00:00
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`
This commit is contained in:
parent
8689227b3e
commit
5926ee5f1f
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
|
||||
* Copyright 2018 SmartBear Software
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openapitools.codegen;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CodegenCallback {
|
||||
public String name;
|
||||
public boolean hasMore;
|
||||
public List<Url> urls = new ArrayList<>();
|
||||
public Map<String, Object> vendorExtensions = new HashMap<>();
|
||||
|
||||
public static class Url {
|
||||
public String expression;
|
||||
public boolean hasMore;
|
||||
public List<CodegenOperation> requests = new ArrayList<>();
|
||||
public Map<String, Object> vendorExtensions = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Url that = (Url) o;
|
||||
return Objects.equals(that.expression, expression) && Objects.equals(that.hasMore, hasMore) &&
|
||||
Objects.equals(that.requests, requests) && Objects.equals(that.vendorExtensions, vendorExtensions);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(expression, hasMore, requests, vendorExtensions);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CodegenCallback.Urls {\n");
|
||||
sb.append(" expression: ").append(expression).append("\n");
|
||||
requests.forEach(r -> sb.append(" ").append(r).append("\n"));
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CodegenCallback that = (CodegenCallback) o;
|
||||
return Objects.equals(that.name, name) && Objects.equals(that.hasMore, hasMore) &&
|
||||
Objects.equals(that.urls, urls) && Objects.equals(that.vendorExtensions, vendorExtensions);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, hasMore, urls, vendorExtensions);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CodegenCallback {\n");
|
||||
sb.append(" name: ").append(name).append("\n");
|
||||
urls.forEach(u -> sb.append(" ").append(u).append("\n"));
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ public class CodegenOperation {
|
||||
isListContainer, isMultipart, hasMore = true,
|
||||
isResponseBinary = false, isResponseFile = false, hasReference = false,
|
||||
isRestfulIndex, isRestfulShow, isRestfulCreate, isRestfulUpdate, isRestfulDestroy,
|
||||
isRestful, isDeprecated;
|
||||
isRestful, isDeprecated, isCallbackRequest;
|
||||
public String path, operationId, returnType, httpMethod, returnBaseType,
|
||||
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
|
||||
public CodegenDiscriminator discriminator;
|
||||
@ -54,6 +54,7 @@ public class CodegenOperation {
|
||||
public List<CodegenSecurity> authMethods;
|
||||
public List<Tag> tags;
|
||||
public List<CodegenResponse> responses = new ArrayList<CodegenResponse>();
|
||||
public List<CodegenCallback> callbacks = new ArrayList<>();
|
||||
public Set<String> imports = new HashSet<String>();
|
||||
public List<Map<String, String>> examples;
|
||||
public List<Map<String, String>> requestBodyExamples;
|
||||
@ -293,6 +294,8 @@ public class CodegenOperation {
|
||||
return false;
|
||||
if (isDeprecated != that.isDeprecated)
|
||||
return false;
|
||||
if (isCallbackRequest != that.isCallbackRequest)
|
||||
return false;
|
||||
if (path != null ? !path.equals(that.path) : that.path != null)
|
||||
return false;
|
||||
if (operationId != null ? !operationId.equals(that.operationId) : that.operationId != null)
|
||||
@ -347,6 +350,8 @@ public class CodegenOperation {
|
||||
return false;
|
||||
if (responses != null ? !responses.equals(that.responses) : that.responses != null)
|
||||
return false;
|
||||
if (callbacks != null ? !callbacks.equals(that.callbacks) : that.callbacks != null)
|
||||
return false;
|
||||
if (imports != null ? !imports.equals(that.imports) : that.imports != null)
|
||||
return false;
|
||||
if (examples != null ? !examples.equals(that.examples) : that.examples != null)
|
||||
@ -386,6 +391,7 @@ public class CodegenOperation {
|
||||
result = 31 * result + (isResponseFile ? 13:31);
|
||||
result = 31 * result + (hasReference ? 13:31);
|
||||
result = 31 * result + (isDeprecated ? 13:31);
|
||||
result = 31 * result + (isCallbackRequest ? 13:31);
|
||||
result = 31 * result + (path != null ? path.hashCode() : 0);
|
||||
result = 31 * result + (operationId != null ? operationId.hashCode() : 0);
|
||||
result = 31 * result + (returnType != null ? returnType.hashCode() : 0);
|
||||
@ -413,6 +419,7 @@ public class CodegenOperation {
|
||||
result = 31 * result + (authMethods != null ? authMethods.hashCode() : 0);
|
||||
result = 31 * result + (tags != null ? tags.hashCode() : 0);
|
||||
result = 31 * result + (responses != null ? responses.hashCode() : 0);
|
||||
result = 31 * result + (callbacks != null ? callbacks.hashCode() : 0);
|
||||
result = 31 * result + (imports != null ? imports.hashCode() : 0);
|
||||
result = 31 * result + (examples != null ? examples.hashCode() : 0);
|
||||
result = 31 * result + (externalDocs != null ? externalDocs.hashCode() : 0);
|
||||
|
@ -24,6 +24,7 @@ import com.samskivert.mustache.Mustache.Compiler;
|
||||
import io.swagger.v3.core.util.Json;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.callbacks.Callback;
|
||||
import io.swagger.v3.oas.models.examples.Example;
|
||||
import io.swagger.v3.oas.models.headers.Header;
|
||||
import io.swagger.v3.oas.models.media.ArraySchema;
|
||||
@ -50,6 +51,7 @@ import io.swagger.v3.parser.util.SchemaTypeUtil;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openapitools.codegen.CodegenDiscriminator.MappedModel;
|
||||
import org.openapitools.codegen.examples.ExampleGenerator;
|
||||
import org.openapitools.codegen.serializer.SerializerUtils;
|
||||
@ -75,6 +77,7 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DefaultCodegen implements CodegenConfig {
|
||||
@ -2232,14 +2235,17 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
Map<String, Schema> schemas,
|
||||
OpenAPI openAPI) {
|
||||
LOGGER.debug("fromOperation => operation: " + operation);
|
||||
if (operation == null)
|
||||
throw new RuntimeException("operation cannot be null in fromOperation");
|
||||
|
||||
CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
|
||||
Set<String> imports = new HashSet<String>();
|
||||
if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
|
||||
op.vendorExtensions.putAll(operation.getExtensions());
|
||||
}
|
||||
|
||||
if (operation == null)
|
||||
throw new RuntimeException("operation cannot be null in fromOperation");
|
||||
Object isCallbackRequest = op.vendorExtensions.remove("x-callback-request");
|
||||
op.isCallbackRequest = Boolean.TRUE.equals(isCallbackRequest);
|
||||
}
|
||||
|
||||
// store the original operationId for plug-in
|
||||
op.operationIdOriginal = operation.getOperationId();
|
||||
@ -2253,6 +2259,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
}
|
||||
operationId = removeNonNameElementToCamelCase(operationId);
|
||||
|
||||
op.path = path;
|
||||
op.operationId = toOperationId(operationId);
|
||||
op.summary = escapeText(operation.getSummary());
|
||||
@ -2344,6 +2351,15 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if (operation.getCallbacks() != null && !operation.getCallbacks().isEmpty()) {
|
||||
operation.getCallbacks().forEach((name, callback) -> {
|
||||
CodegenCallback c = fromCallback(name, callback, schemas, openAPI);
|
||||
c.hasMore = true;
|
||||
op.callbacks.add(c);
|
||||
});
|
||||
op.callbacks.get(op.callbacks.size() - 1).hasMore = false;
|
||||
}
|
||||
|
||||
List<Parameter> parameters = operation.getParameters();
|
||||
List<CodegenParameter> allParams = new ArrayList<CodegenParameter>();
|
||||
List<CodegenParameter> bodyParams = new ArrayList<CodegenParameter>();
|
||||
@ -2621,6 +2637,79 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert OAS Callback object to Codegen Callback object
|
||||
*
|
||||
* @param name callback name
|
||||
* @param callback OAS Callback object
|
||||
* @param schemas a map of OAS models
|
||||
* @param openAPI a OAS object representing the spec
|
||||
* @return Codegen Response object
|
||||
*/
|
||||
public CodegenCallback fromCallback(String name, Callback callback, Map<String, Schema> schemas, OpenAPI openAPI) {
|
||||
CodegenCallback c = new CodegenCallback();
|
||||
c.name = name;
|
||||
|
||||
if (callback.getExtensions() != null && !callback.getExtensions().isEmpty()) {
|
||||
c.vendorExtensions.putAll(callback.getExtensions());
|
||||
}
|
||||
|
||||
callback.forEach((expression, pi) -> {
|
||||
CodegenCallback.Url u = new CodegenCallback.Url();
|
||||
u.expression = expression;
|
||||
u.hasMore = true;
|
||||
|
||||
if (pi.getExtensions() != null && !pi.getExtensions().isEmpty()) {
|
||||
u.vendorExtensions.putAll(pi.getExtensions());
|
||||
}
|
||||
|
||||
Stream.of(
|
||||
Pair.of("get", pi.getGet()),
|
||||
Pair.of("head", pi.getHead()),
|
||||
Pair.of("put", pi.getPut()),
|
||||
Pair.of("post", pi.getPost()),
|
||||
Pair.of("delete", pi.getDelete()),
|
||||
Pair.of("patch", pi.getPatch()),
|
||||
Pair.of("options", pi.getOptions()))
|
||||
.filter(p -> p.getValue() != null)
|
||||
.forEach(p -> {
|
||||
String method = p.getKey();
|
||||
Operation op = p.getValue();
|
||||
|
||||
boolean genId = op.getOperationId() == null;
|
||||
if (genId) {
|
||||
op.setOperationId(getOrGenerateOperationId(op, c.name+"_"+expression.replaceAll("\\{\\$.*}", ""), method));
|
||||
}
|
||||
|
||||
if (op.getExtensions() == null) {
|
||||
op.setExtensions(new HashMap<>());
|
||||
}
|
||||
// This extension will be removed later by `fromOperation()` as it is only needed here to
|
||||
// distinguish between normal operations and callback requests
|
||||
op.getExtensions().put("x-callback-request", true);
|
||||
|
||||
CodegenOperation co = fromOperation(expression, method, op, schemas, openAPI);
|
||||
if (genId) {
|
||||
co.operationIdOriginal = null;
|
||||
// legacy (see `fromOperation()`)
|
||||
co.nickname = co.operationId;
|
||||
}
|
||||
u.requests.add(co);
|
||||
});
|
||||
|
||||
if (!u.requests.isEmpty()) {
|
||||
u.requests.get(u.requests.size() - 1).hasMore = false;
|
||||
}
|
||||
c.urls.add(u);
|
||||
});
|
||||
|
||||
if (!c.urls.isEmpty()) {
|
||||
c.urls.get(c.urls.size() - 1).hasMore = false;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert OAS Parameter object to Codegen Parameter object
|
||||
*
|
||||
|
@ -38,12 +38,7 @@ import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DefaultCodegenTest {
|
||||
@ -407,6 +402,60 @@ public class DefaultCodegenTest {
|
||||
verifyPersonDiscriminator(personModel.discriminator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbacks() {
|
||||
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/callbacks.yaml", null, new ParseOptions()).getOpenAPI();
|
||||
final CodegenConfig codegen = new DefaultCodegen();
|
||||
|
||||
final String path = "/streams";
|
||||
Operation subscriptionOperation = openAPI.getPaths().get("/streams").getPost();
|
||||
CodegenOperation op = codegen.fromOperation(path, "post", subscriptionOperation, openAPI.getComponents().getSchemas(), openAPI);
|
||||
|
||||
Assert.assertFalse(op.isCallbackRequest);
|
||||
Assert.assertNotNull(op.operationId);
|
||||
Assert.assertEquals(op.callbacks.size(), 2);
|
||||
|
||||
CodegenCallback cbB = op.callbacks.get(1);
|
||||
Assert.assertEquals(cbB.name, "dummy");
|
||||
Assert.assertFalse(cbB.hasMore);
|
||||
Assert.assertEquals(cbB.urls.size(), 0);
|
||||
|
||||
CodegenCallback cbA = op.callbacks.get(0);
|
||||
Assert.assertEquals(cbA.name, "onData");
|
||||
Assert.assertTrue(cbA.hasMore);
|
||||
|
||||
Assert.assertEquals(cbA.urls.size(), 2);
|
||||
|
||||
CodegenCallback.Url urlB = cbA.urls.get(1);
|
||||
Assert.assertEquals(urlB.expression, "{$request.query.callbackUrl}/test");
|
||||
Assert.assertFalse(urlB.hasMore);
|
||||
Assert.assertEquals(urlB.requests.size(), 0);
|
||||
|
||||
CodegenCallback.Url urlA = cbA.urls.get(0);
|
||||
Assert.assertEquals(urlA.expression, "{$request.query.callbackUrl}/data");
|
||||
Assert.assertTrue(urlA.hasMore);
|
||||
Assert.assertEquals(urlA.requests.size(), 2);
|
||||
|
||||
urlA.requests.forEach(req -> {
|
||||
Assert.assertTrue(req.isCallbackRequest);
|
||||
Assert.assertNotNull(req.bodyParam);
|
||||
Assert.assertEquals(req.responses.size(), 2);
|
||||
|
||||
switch (req.httpMethod.toLowerCase(Locale.getDefault())) {
|
||||
case "post":
|
||||
Assert.assertEquals(req.operationId, "onDataDataPost");
|
||||
Assert.assertEquals(req.bodyParam.dataType, "NewNotificationData");
|
||||
break;
|
||||
case "delete":
|
||||
Assert.assertEquals(req.operationId, "onDataDataDelete");
|
||||
Assert.assertEquals(req.bodyParam.dataType, "DeleteNotificationData");
|
||||
break;
|
||||
default:
|
||||
Assert.fail(String.format(Locale.getDefault(), "invalid callback request http method '%s'", req.httpMethod));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyPersonDiscriminator(CodegenDiscriminator discriminator) {
|
||||
CodegenDiscriminator test = new CodegenDiscriminator();
|
||||
test.setPropertyName("$_type");
|
||||
|
@ -0,0 +1,85 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Callback Example
|
||||
version: 1.0.0
|
||||
paths:
|
||||
/streams:
|
||||
post:
|
||||
description: subscribes a client to receive out-of-band data
|
||||
parameters:
|
||||
- name: callbackUrl
|
||||
in: query
|
||||
required: true
|
||||
description: |
|
||||
the location where data will be sent. Must be network accessible
|
||||
by the source server
|
||||
schema:
|
||||
type: string
|
||||
format: uri
|
||||
example: https://tonys-server.com
|
||||
responses:
|
||||
'201':
|
||||
description: subscription successfully created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: subscription information
|
||||
required:
|
||||
- subscriptionId
|
||||
properties:
|
||||
subscriptionId:
|
||||
description: this unique identifier allows management of the subscription
|
||||
type: string
|
||||
example: 2531329f-fb09-4ef7-887e-84e648214436
|
||||
callbacks:
|
||||
onData:
|
||||
'{$request.query.callbackUrl}/data':
|
||||
post:
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/NewNotificationData'
|
||||
responses:
|
||||
'202':
|
||||
description: |
|
||||
Your server implementation should return this HTTP status code
|
||||
if the data was received successfully
|
||||
'204':
|
||||
description: |
|
||||
Your server should return this HTTP status code if no longer interested
|
||||
in further updates
|
||||
delete:
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteNotificationData'
|
||||
responses:
|
||||
'202':
|
||||
description: |
|
||||
Your server implementation should return this HTTP status code
|
||||
if the data was received successfully
|
||||
'204':
|
||||
description: |
|
||||
Your server should return this HTTP status code if no longer interested
|
||||
in further updates
|
||||
'{$request.query.callbackUrl}/test': {}
|
||||
dummy: {}
|
||||
|
||||
components:
|
||||
schemas:
|
||||
NewNotificationData:
|
||||
description: subscription payload
|
||||
properties:
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
userData:
|
||||
type: string
|
||||
DeleteNotificationData:
|
||||
description: subscription payload
|
||||
properties:
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
Loading…
Reference in New Issue
Block a user