Support binary input and output (for body parameters or responses with type "string" and format "binary". Implemented for Java.

This commit is contained in:
b_sapir 2015-08-24 14:33:15 +03:00
parent f2df26f6e6
commit d4b4fe4b47
9 changed files with 230 additions and 40 deletions

View File

@ -7,7 +7,7 @@ import java.util.List;
public class CodegenParameter {
public Boolean isFormParam, isQueryParam, isPathParam, isHeaderParam,
isCookieParam, isBodyParam, isFile, notFile, hasMore, isContainer, secondaryParam;
isCookieParam, isBodyParam, isFile, notFile, hasMore, isContainer, secondaryParam, isBinary;
public String baseName, paramName, dataType, collectionFormat, description, baseType, defaultValue;
public String jsonSchema;
public boolean isEnum;

View File

@ -15,6 +15,7 @@ public class CodegenResponse {
public Boolean primitiveType;
public Boolean isMapContainer;
public Boolean isListContainer;
public Boolean isBinary;
public Object schema;
public String jsonSchema;

View File

@ -27,6 +27,7 @@ import io.swagger.models.parameters.SerializableParameter;
import io.swagger.models.properties.AbstractNumericProperty;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.BooleanProperty;
import io.swagger.models.properties.ByteArrayProperty;
import io.swagger.models.properties.DateProperty;
import io.swagger.models.properties.DateTimeProperty;
import io.swagger.models.properties.DecimalProperty;
@ -308,6 +309,8 @@ public class DefaultCodegen {
typeMapping.put("double", "Double");
typeMapping.put("object", "Object");
typeMapping.put("integer", "Integer");
typeMapping.put("ByteArray", "byte[]");
instantiationTypes = new HashMap<String, String>();
@ -444,6 +447,8 @@ public class DefaultCodegen {
String datatype = null;
if (p instanceof StringProperty) {
datatype = "string";
} else if (p instanceof ByteArrayProperty) {
datatype = "ByteArray";
} else if (p instanceof BooleanProperty) {
datatype = "boolean";
} else if (p instanceof DateProperty) {
@ -965,6 +970,7 @@ public class DefaultCodegen {
}
}
r.dataType = cm.datatype;
r.isBinary = cm.datatype.equals("byte[]");
if (cm.isContainer != null) {
r.simpleType = false;
r.containerType = cm.containerType;
@ -1061,12 +1067,17 @@ public class DefaultCodegen {
p.dataType = getTypeDeclaration(cm.classname);
imports.add(p.dataType);
} else {
// TODO: missing format, so this will not always work
Property prop = PropertyBuilder.build(impl.getType(), null, null);
Property prop = PropertyBuilder.build(impl.getType(), impl.getFormat(), null);
prop.setRequired(bp.getRequired());
CodegenProperty cp = fromProperty("property", prop);
if (cp != null) {
p.dataType = cp.datatype;
if (p.dataType.equals("byte[]")) {
p.isBinary = true;
}
else {
p.isBinary = false;
}
}
}
} else if (model instanceof ArrayModel) {

View File

@ -64,7 +64,8 @@ public class JavaClientCodegen extends DefaultCodegen implements CodegenConfig {
"Integer",
"Long",
"Float",
"Object")
"Object",
"byte[]")
);
instantiationTypes.put("array", "ArrayList");
instantiationTypes.put("map", "HashMap");
@ -129,7 +130,7 @@ public class JavaClientCodegen extends DefaultCodegen implements CodegenConfig {
if (additionalProperties.containsKey("localVariablePrefix")) {
this.setLocalVariablePrefix((String) additionalProperties.get("localVariablePrefix"));
}
this.sanitizeConfig();
final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator);
@ -266,7 +267,7 @@ public class JavaClientCodegen extends DefaultCodegen implements CodegenConfig {
if (typeMapping.containsKey(swaggerType)) {
type = typeMapping.get(swaggerType);
if (languageSpecificPrimitives.contains(type)) {
return toModelName(type);
return type;
}
} else {
type = swaggerType;
@ -363,7 +364,7 @@ public class JavaClientCodegen extends DefaultCodegen implements CodegenConfig {
public void setLocalVariablePrefix(String localVariablePrefix) {
this.localVariablePrefix = localVariablePrefix;
}
private String sanitizePackageName(String packageName) {
packageName = packageName.trim();
packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_");

View File

@ -30,6 +30,7 @@ import java.net.URLEncoder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.DataInputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@ -385,21 +386,12 @@ public class ApiClient {
}
}
/**
* Invoke API by sending HTTP request with the given options.
*
* @param path The sub-path of the HTTP URL
* @param method The request method, one of "GET", "POST", "PUT", and "DELETE"
* @param queryParams The query parameters
* @param body The request body object
* @param headerParams The header parameters
* @param formParams The form parameters
* @param accept The request's Accept header
* @param contentType The request's Content-Type header
* @param authNames The authentications to apply
* @return The response body in type of string
*/
public String invokeAPI(String path, String method, List<Pair> queryParams, Object body, Map<String, String> headerParams, Map<String, String> formParams, String accept, String contentType, String[] authNames) throws ApiException {
private ClientResponse getAPIResponse(String path, String method, List<Pair> queryParams, Object body, byte[] binaryBody, Map<String, String> headerParams, Map<String, String> formParams, String accept, String contentType, String[] authNames) throws ApiException {
if (body != null && binaryBody != null){
throw new ApiException(500, "either body or binaryBody must be null");
}
updateParamsForAuth(authNames, queryParams, headerParams);
Client client = getClient();
@ -446,7 +438,10 @@ public class ApiClient {
response = builder.type(contentType).post(ClientResponse.class,
encodedFormParams);
} else if (body == null) {
response = builder.post(ClientResponse.class, null);
if(binaryBody == null)
response = builder.post(ClientResponse.class, null);
else
response = builder.type(contentType).post(ClientResponse.class, binaryBody);
} else if(body instanceof FormDataMultiPart) {
response = builder.type(contentType).post(ClientResponse.class, body);
}
@ -460,7 +455,10 @@ public class ApiClient {
response = builder.type(contentType).put(ClientResponse.class,
encodedFormParams);
} else if(body == null) {
response = builder.put(ClientResponse.class, serialize(body));
if(binaryBody == null)
response = builder.put(ClientResponse.class, null);
else
response = builder.type(contentType).put(ClientResponse.class, binaryBody);
} else {
response = builder.type(contentType).put(ClientResponse.class, serialize(body));
}
@ -472,7 +470,10 @@ public class ApiClient {
response = builder.type(contentType).delete(ClientResponse.class,
encodedFormParams);
} else if(body == null) {
response = builder.delete(ClientResponse.class);
if(binaryBody == null)
response = builder.delete(ClientResponse.class);
else
response = builder.type(contentType).delete(ClientResponse.class, binaryBody);
} else {
response = builder.type(contentType).delete(ClientResponse.class, serialize(body));
}
@ -480,6 +481,27 @@ public class ApiClient {
else {
throw new ApiException(500, "unknown method type " + method);
}
return response;
}
/**
* Invoke API by sending HTTP request with the given options.
*
* @param path The sub-path of the HTTP URL
* @param method The request method, one of "GET", "POST", "PUT", and "DELETE"
* @param queryParams The query parameters
* @param body The request body object - if it is not binary, otherwise null
* @param binaryBody The request body object - if it is binary, otherwise null
* @param headerParams The header parameters
* @param formParams The form parameters
* @param accept The request's Accept header
* @param contentType The request's Content-Type header
* @param authNames The authentications to apply
* @return The response body in type of string
*/
public String invokeAPI(String path, String method, List<Pair> queryParams, Object body, byte[] binaryBody, Map<String, String> headerParams, Map<String, String> formParams, String accept, String contentType, String[] authNames) throws ApiException {
ClientResponse response = getAPIResponse(path, method, queryParams, body, binaryBody, headerParams, formParams, accept, contentType, authNames);
if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) {
return null;
@ -511,6 +533,58 @@ public class ApiClient {
respBody);
}
}
/**
* Invoke API by sending HTTP request with the given options - return binary result
*
* @param path The sub-path of the HTTP URL
* @param method The request method, one of "GET", "POST", "PUT", and "DELETE"
* @param queryParams The query parameters
* @param body The request body object - if it is not binary, otherwise null
* @param binaryBody The request body object - if it is binary, otherwise null
* @param headerParams The header parameters
* @param formParams The form parameters
* @param accept The request's Accept header
* @param contentType The request's Content-Type header
* @param authNames The authentications to apply
* @return The response body in type of string
*/
public byte[] invokeBinaryAPI(String path, String method, List<Pair> queryParams, Object body, byte[] binaryBody, Map<String, String> headerParams, Map<String, String> formParams, String accept, String contentType, String[]authNames) throws ApiException {
ClientResponse response = getAPIResponse(path, method, queryParams, body, binaryBody, headerParams, formParams, accept, contentType, authNames);
if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) {
return null;
}
else if(response.getStatusInfo().getFamily() == Family.SUCCESSFUL) {
if(response.hasEntity()) {
DataInputStream stream = new DataInputStream(response.getEntityInputStream());
byte[] data = new byte[response.getLength()];
try {
stream.readFully(data);
} catch (IOException ex) {
throw new ApiException(500, "Error obtaining binary response data");
}
return data;
}
else {
return new byte[0];
}
}
else {
String message = "error";
if(response.hasEntity()) {
try{
message = String.valueOf(response.getEntity(String.class));
}
catch (RuntimeException e) {
// e.printStackTrace();
}
}
throw new ApiException(
response.getStatusInfo().getStatusCode(),
message);
}
}
/**
* Update query and header parameters based on authentication settings.

View File

@ -50,16 +50,16 @@ public class {{classname}} {
{{/allParams}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
*/
public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}} ({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) throws ApiException {
Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{^isBinary}}{{paramName}}{{/isBinary}}{{#isBinary}}null{{/isBinary}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
byte[] {{localVariablePrefix}}postBinaryBody = {{#bodyParam}}{{#isBinary}}{{paramName}}{{/isBinary}}{{^isBinary}}null{{/isBinary}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
{{#allParams}}{{#required}}
// verify the required parameter '{{paramName}}' is set
if ({{paramName}} == null) {
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}");
}
{{/required}}{{/allParams}}
// verify the required parameter '{{paramName}}' is set
if ({{paramName}} == null) {
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}");
}
{{/required}}{{/allParams}}
// create path and map variables
String {{localVariablePrefix}}path = "{{path}}".replaceAll("\\{format\\}","json"){{#pathParams}}
String {{localVariablePrefix}}path = "{{{path}}}".replaceAll("\\{format\\}","json"){{#pathParams}}
.replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}};
// query params
@ -110,14 +110,29 @@ public class {{classname}} {
}
try {
String[] {{localVariablePrefix}}authNames = new String[] { {{#authMethods}}"{{name}}"{{#hasMore}}, {{/hasMore}}{{/authMethods}} };
String {{localVariablePrefix}}response = {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames);
if({{localVariablePrefix}}response != null){
return {{#returnType}}({{{returnType}}}) {{localVariablePrefix}}apiClient.deserialize({{localVariablePrefix}}response, "{{returnContainer}}", {{returnBaseType}}.class){{/returnType}};
}
else {
return {{#returnType}}null{{/returnType}};
}
{{#responses}}{{#isDefault}}
{{#isBinary}}
byte[] {{localVariablePrefix}}response = null;
{{localVariablePrefix}}response = {{localVariablePrefix}}apiClient.invokeBinaryAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams,{{localVariablePrefix}} postBody, {{localVariablePrefix}}postBinaryBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames);
return {{localVariablePrefix}}response;
{{/isBinary}}
{{^isBinary}}
String {{localVariablePrefix}}response = {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}postBinaryBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames);
if({{localVariablePrefix}}response != null){
return {{#returnType}}({{{returnType}}}) {{localVariablePrefix}}apiClient.deserialize({{localVariablePrefix}}response, "{{returnContainer}}", {{returnBaseType}}.class){{/returnType}};
}
else {
return {{#returnType}}null{{/returnType}};
}
{{/isBinary}}
{{/isDefault}}
{{/responses}}
} catch (ApiException ex) {
throw ex;
}

View File

@ -0,0 +1,51 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server. You can find out more about Swagger at <a href=\"http://swagger.io\">http://swagger.io</a> or on irc.freenode.net, #swagger. For this sample, you can use the api key \"special-key\" to test the authorization filters",
"version": "1.0.0",
"title": "Swagger Petstore",
"termsOfService": "http://helloreverb.com/terms/",
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"basePath": "/v2",
"schemes": [
"http"
],
"paths": {
"/tests/binaryResponse": {
"post": {
"summary": "Echo test",
"operationId": "echotest",
"consumes": [
"application/octet-stream"
],
"produces": [
"application/octet-stream"
],
"parameters": [
{
"name": "InputBinaryData",
"in": "body",
"required": true,
"schema": {
"type": "string",
"format": "binary"
}
}
],
"responses": {
"200": {
"description": "OutputBinaryData",
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
}

View File

@ -139,4 +139,19 @@ class CodegenTest extends FlatSpec with Matchers {
val op = codegen.fromOperation(path, "get", p, model.getDefinitions())
op.returnType should be("String")
}
it should "return byte array when response format is byte" in {
val model = new SwaggerParser()
.read("src/test/resources/2_0/binaryDataTest.json")
System.err.println("model is " + model);
val codegen = new DefaultCodegen()
val path = "/tests/binaryResponse"
val p = model.getPaths().get(path).getPost()
val op = codegen.fromOperation(path, "post", p, model.getDefinitions())
op.returnType should be("byte[]")
op.bodyParam.dataType should be ("byte[]")
op.bodyParam.isBinary should equal (true);
op.responses.get(0).isBinary should equal(true);
}
}

View File

@ -365,6 +365,28 @@ class JavaModelTest extends FlatSpec with Matchers {
val vars = cm.vars
cm.classname should be("WithDots")
}
it should "convert a modelwith binary data" in {
val model = new ModelImpl()
.description("model with binary")
.property("inputBinaryData", new ByteArrayProperty());
val codegen = new JavaClientCodegen()
val cm = codegen.fromModel("sample", model)
val vars = cm.vars
vars.get(0).baseName should be ("inputBinaryData")
vars.get(0).getter should be ("getInputBinaryData")
vars.get(0).setter should be ("setInputBinaryData")
vars.get(0).datatype should be ("byte[]")
vars.get(0).name should be ("inputBinaryData")
vars.get(0).defaultValue should be ("null")
vars.get(0).baseType should be ("byte[]")
vars.get(0).hasMore should equal (null)
vars.get(0).required should equal (null)
vars.get(0).isNotContainer should equal (true)
}
}