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 class CodegenParameter {
public Boolean isFormParam, isQueryParam, isPathParam, isHeaderParam, 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 baseName, paramName, dataType, collectionFormat, description, baseType, defaultValue;
public String jsonSchema; public String jsonSchema;
public boolean isEnum; public boolean isEnum;

View File

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

View File

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

View File

@ -30,6 +30,7 @@ import java.net.URLEncoder;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.io.DataInputStream;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -385,21 +386,12 @@ public class ApiClient {
} }
} }
/** 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 {
* Invoke API by sending HTTP request with the given options.
* if (body != null && binaryBody != null){
* @param path The sub-path of the HTTP URL throw new ApiException(500, "either body or binaryBody must be null");
* @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 {
updateParamsForAuth(authNames, queryParams, headerParams); updateParamsForAuth(authNames, queryParams, headerParams);
Client client = getClient(); Client client = getClient();
@ -446,7 +438,10 @@ public class ApiClient {
response = builder.type(contentType).post(ClientResponse.class, response = builder.type(contentType).post(ClientResponse.class,
encodedFormParams); encodedFormParams);
} else if (body == null) { } 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) { } else if(body instanceof FormDataMultiPart) {
response = builder.type(contentType).post(ClientResponse.class, body); response = builder.type(contentType).post(ClientResponse.class, body);
} }
@ -460,7 +455,10 @@ public class ApiClient {
response = builder.type(contentType).put(ClientResponse.class, response = builder.type(contentType).put(ClientResponse.class,
encodedFormParams); encodedFormParams);
} else if(body == null) { } 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 { } else {
response = builder.type(contentType).put(ClientResponse.class, serialize(body)); response = builder.type(contentType).put(ClientResponse.class, serialize(body));
} }
@ -472,7 +470,10 @@ public class ApiClient {
response = builder.type(contentType).delete(ClientResponse.class, response = builder.type(contentType).delete(ClientResponse.class,
encodedFormParams); encodedFormParams);
} else if(body == null) { } 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 { } else {
response = builder.type(contentType).delete(ClientResponse.class, serialize(body)); response = builder.type(contentType).delete(ClientResponse.class, serialize(body));
} }
@ -480,6 +481,27 @@ public class ApiClient {
else { else {
throw new ApiException(500, "unknown method type " + method); 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) { if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) {
return null; return null;
@ -511,6 +533,58 @@ public class ApiClient {
respBody); 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. * 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}} {{/allParams}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
*/ */
public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}} ({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) throws ApiException { 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}} {{#allParams}}{{#required}}
// verify the required parameter '{{paramName}}' is set // verify the required parameter '{{paramName}}' is set
if ({{paramName}} == null) { if ({{paramName}} == null) {
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}"); throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}");
} }
{{/required}}{{/allParams}} {{/required}}{{/allParams}}
// create path and map variables // 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}}; .replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}};
// query params // query params
@ -110,14 +110,29 @@ public class {{classname}} {
} }
try { try {
String[] {{localVariablePrefix}}authNames = new String[] { {{#authMethods}}"{{name}}"{{#hasMore}}, {{/hasMore}}{{/authMethods}} }; 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){ {{#responses}}{{#isDefault}}
return {{#returnType}}({{{returnType}}}) {{localVariablePrefix}}apiClient.deserialize({{localVariablePrefix}}response, "{{returnContainer}}", {{returnBaseType}}.class){{/returnType}}; {{#isBinary}}
} byte[] {{localVariablePrefix}}response = null;
else { {{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 {{#returnType}}null{{/returnType}}; 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) { } catch (ApiException ex) {
throw 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()) val op = codegen.fromOperation(path, "get", p, model.getDefinitions())
op.returnType should be("String") 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 val vars = cm.vars
cm.classname should be("WithDots") 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)
}
} }