Skip to content

Commit 1cf9c8c

Browse files
b_sapirb_sapir
b_sapir
authored and
b_sapir
committed
Support binary input and output (for body parameters or responses with type "string" and format "binary". Implemented for Java.
1 parent f662699 commit 1cf9c8c

File tree

8 files changed

+178
-38
lines changed

8 files changed

+178
-38
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenParameter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
public class CodegenParameter {
99
public Boolean isFormParam, isQueryParam, isPathParam, isHeaderParam,
10-
isCookieParam, isBodyParam, isFile, notFile, hasMore, isContainer, secondaryParam;
10+
isCookieParam, isBodyParam, isFile, notFile, hasMore, isContainer, secondaryParam, isBinary;
1111
public String baseName, paramName, dataType, collectionFormat, description, baseType, defaultValue;
1212
public String jsonSchema;
1313
public boolean isEnum;

modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenResponse.java

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class CodegenResponse {
1515
public Boolean primitiveType;
1616
public Boolean isMapContainer;
1717
public Boolean isListContainer;
18+
public Boolean isBinary;
1819
public Object schema;
1920
public String jsonSchema;
2021

modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.swagger.models.properties.AbstractNumericProperty;
2828
import io.swagger.models.properties.ArrayProperty;
2929
import io.swagger.models.properties.BooleanProperty;
30+
import io.swagger.models.properties.ByteArrayProperty;
3031
import io.swagger.models.properties.DateProperty;
3132
import io.swagger.models.properties.DateTimeProperty;
3233
import io.swagger.models.properties.DecimalProperty;
@@ -308,6 +309,8 @@ public DefaultCodegen() {
308309
typeMapping.put("double", "Double");
309310
typeMapping.put("object", "Object");
310311
typeMapping.put("integer", "Integer");
312+
typeMapping.put("ByteArray", "byte[]");
313+
311314

312315
instantiationTypes = new HashMap<String, String>();
313316

@@ -444,6 +447,8 @@ public String getSwaggerType(Property p) {
444447
String datatype = null;
445448
if (p instanceof StringProperty) {
446449
datatype = "string";
450+
} else if (p instanceof ByteArrayProperty) {
451+
datatype = "ByteArray";
447452
} else if (p instanceof BooleanProperty) {
448453
datatype = "boolean";
449454
} else if (p instanceof DateProperty) {
@@ -525,6 +530,7 @@ public CodegenModel fromModel(String name, Model model, Map<String, Model> allDe
525530
m.externalDocs = model.getExternalDocs();
526531
if (model instanceof ArrayModel) {
527532
ArrayModel am = (ArrayModel) model;
533+
m.hasEnums = false; // otherwise causes NullPointerException in JavaClientCodegen.fromModel
528534
ArrayProperty arrayProperty = new ArrayProperty(am.getItems());
529535
addParentContainer(m, name, arrayProperty);
530536
} else if (model instanceof RefModel) {
@@ -965,6 +971,7 @@ public CodegenResponse fromResponse(String responseCode, Response response) {
965971
}
966972
}
967973
r.dataType = cm.datatype;
974+
r.isBinary = cm.datatype.equals("byte[]");
968975
if (cm.isContainer != null) {
969976
r.simpleType = false;
970977
r.containerType = cm.containerType;
@@ -1061,12 +1068,17 @@ public CodegenParameter fromParameter(Parameter param, Set<String> imports) {
10611068
p.dataType = getTypeDeclaration(cm.classname);
10621069
imports.add(p.dataType);
10631070
} else {
1064-
// TODO: missing format, so this will not always work
1065-
Property prop = PropertyBuilder.build(impl.getType(), null, null);
1071+
Property prop = PropertyBuilder.build(impl.getType(), impl.getFormat(), null);
10661072
prop.setRequired(bp.getRequired());
10671073
CodegenProperty cp = fromProperty("property", prop);
10681074
if (cp != null) {
10691075
p.dataType = cp.datatype;
1076+
if (p.dataType.equals("byte[]")) {
1077+
p.isBinary = true;
1078+
}
1079+
else {
1080+
p.isBinary = false;
1081+
}
10701082
}
10711083
}
10721084
} else if (model instanceof ArrayModel) {

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public JavaClientCodegen() {
6666
"Integer",
6767
"Long",
6868
"Float",
69-
"Object")
69+
"Object",
70+
"byte[]")
7071
);
7172
instantiationTypes.put("array", "ArrayList");
7273
instantiationTypes.put("map", "HashMap");
@@ -275,7 +276,7 @@ public String getSwaggerType(Property p) {
275276
if (typeMapping.containsKey(swaggerType)) {
276277
type = typeMapping.get(swaggerType);
277278
if (languageSpecificPrimitives.contains(type)) {
278-
return toModelName(type);
279+
return type;
279280
}
280281
} else {
281282
type = swaggerType;

modules/swagger-codegen/src/main/resources/Java/ApiClient.mustache

+92-18
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import java.net.URLEncoder;
3030

3131
import java.io.IOException;
3232
import java.io.UnsupportedEncodingException;
33+
import java.io.DataInputStream;
3334

3435
import java.text.DateFormat;
3536
import java.text.SimpleDateFormat;
@@ -385,21 +386,12 @@ public class ApiClient {
385386
}
386387
}
387388

388-
/**
389-
* Invoke API by sending HTTP request with the given options.
390-
*
391-
* @param path The sub-path of the HTTP URL
392-
* @param method The request method, one of "GET", "POST", "PUT", and "DELETE"
393-
* @param queryParams The query parameters
394-
* @param body The request body object
395-
* @param headerParams The header parameters
396-
* @param formParams The form parameters
397-
* @param accept The request's Accept header
398-
* @param contentType The request's Content-Type header
399-
* @param authNames The authentications to apply
400-
* @return The response body in type of string
401-
*/
402-
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 {
389+
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 {
390+
391+
if (body != null && binaryBody != null){
392+
throw new ApiException(500, "either body or binaryBody must be null");
393+
}
394+
403395
updateParamsForAuth(authNames, queryParams, headerParams);
404396

405397
Client client = getClient();
@@ -446,7 +438,10 @@ public class ApiClient {
446438
response = builder.type(contentType).post(ClientResponse.class,
447439
encodedFormParams);
448440
} else if (body == null) {
449-
response = builder.post(ClientResponse.class, null);
441+
if(binaryBody == null)
442+
response = builder.post(ClientResponse.class, null);
443+
else
444+
response = builder.type(contentType).post(ClientResponse.class, binaryBody);
450445
} else if(body instanceof FormDataMultiPart) {
451446
response = builder.type(contentType).post(ClientResponse.class, body);
452447
}
@@ -460,7 +455,10 @@ public class ApiClient {
460455
response = builder.type(contentType).put(ClientResponse.class,
461456
encodedFormParams);
462457
} else if(body == null) {
463-
response = builder.put(ClientResponse.class, serialize(body));
458+
if(binaryBody == null)
459+
response = builder.put(ClientResponse.class, null);
460+
else
461+
response = builder.type(contentType).put(ClientResponse.class, binaryBody);
464462
} else {
465463
response = builder.type(contentType).put(ClientResponse.class, serialize(body));
466464
}
@@ -472,14 +470,38 @@ public class ApiClient {
472470
response = builder.type(contentType).delete(ClientResponse.class,
473471
encodedFormParams);
474472
} else if(body == null) {
475-
response = builder.delete(ClientResponse.class);
473+
if(binaryBody == null)
474+
response = builder.delete(ClientResponse.class);
475+
else
476+
response = builder.type(contentType).delete(ClientResponse.class, binaryBody);
476477
} else {
477478
response = builder.type(contentType).delete(ClientResponse.class, serialize(body));
478479
}
479480
}
480481
else {
481482
throw new ApiException(500, "unknown method type " + method);
482483
}
484+
return response;
485+
}
486+
487+
/**
488+
* Invoke API by sending HTTP request with the given options.
489+
*
490+
* @param path The sub-path of the HTTP URL
491+
* @param method The request method, one of "GET", "POST", "PUT", and "DELETE"
492+
* @param queryParams The query parameters
493+
* @param body The request body object - if it is not binary, otherwise null
494+
* @param binaryBody The request body object - if it is binary, otherwise null
495+
* @param headerParams The header parameters
496+
* @param formParams The form parameters
497+
* @param accept The request's Accept header
498+
* @param contentType The request's Content-Type header
499+
* @param authNames The authentications to apply
500+
* @return The response body in type of string
501+
*/
502+
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 {
503+
504+
ClientResponse response = getAPIResponse(path, method, queryParams, body, binaryBody, headerParams, formParams, accept, contentType, authNames);
483505
484506
if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) {
485507
return null;
@@ -511,6 +533,58 @@ public class ApiClient {
511533
respBody);
512534
}
513535
}
536+
/**
537+
* Invoke API by sending HTTP request with the given options - return binary result
538+
*
539+
* @param path The sub-path of the HTTP URL
540+
* @param method The request method, one of "GET", "POST", "PUT", and "DELETE"
541+
* @param queryParams The query parameters
542+
* @param body The request body object - if it is not binary, otherwise null
543+
* @param binaryBody The request body object - if it is binary, otherwise null
544+
* @param headerParams The header parameters
545+
* @param formParams The form parameters
546+
* @param accept The request's Accept header
547+
* @param contentType The request's Content-Type header
548+
* @param authNames The authentications to apply
549+
* @return The response body in type of string
550+
*/
551+
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 {
552+
553+
ClientResponse response = getAPIResponse(path, method, queryParams, body, binaryBody, headerParams, formParams, accept, contentType, authNames);
554+
555+
if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) {
556+
return null;
557+
}
558+
else if(response.getStatusInfo().getFamily() == Family.SUCCESSFUL) {
559+
if(response.hasEntity()) {
560+
DataInputStream stream = new DataInputStream(response.getEntityInputStream());
561+
byte[] data = new byte[response.getLength()];
562+
try {
563+
stream.readFully(data);
564+
} catch (IOException ex) {
565+
throw new ApiException(500, "Error obtaining binary response data");
566+
}
567+
return data;
568+
}
569+
else {
570+
return new byte[0];
571+
}
572+
}
573+
else {
574+
String message = "error";
575+
if(response.hasEntity()) {
576+
try{
577+
message = String.valueOf(response.getEntity(String.class));
578+
}
579+
catch (RuntimeException e) {
580+
// e.printStackTrace();
581+
}
582+
}
583+
throw new ApiException(
584+
response.getStatusInfo().getStatusCode(),
585+
message);
586+
}
587+
}
514588

515589
/**
516590
* Update query and header parameters based on authentication settings.

modules/swagger-codegen/src/main/resources/Java/api.mustache

+30-15
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,16 @@ public class {{classname}} {
5050
{{/allParams}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
5151
*/
5252
public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}} ({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) throws ApiException {
53-
Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
53+
Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{^isBinary}}{{paramName}}{{/isBinary}}{{#isBinary}}null{{/isBinary}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
54+
byte[] {{localVariablePrefix}}postBinaryBody = {{#bodyParam}}{{#isBinary}}{{paramName}}{{/isBinary}}{{^isBinary}}null{{/isBinary}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
5455
{{#allParams}}{{#required}}
55-
// verify the required parameter '{{paramName}}' is set
56-
if ({{paramName}} == null) {
57-
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}");
58-
}
59-
{{/required}}{{/allParams}}
60-
56+
// verify the required parameter '{{paramName}}' is set
57+
if ({{paramName}} == null) {
58+
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}");
59+
}
60+
{{/required}}{{/allParams}}
6161
// create path and map variables
62-
String {{localVariablePrefix}}path = "{{path}}".replaceAll("\\{format\\}","json"){{#pathParams}}
62+
String {{localVariablePrefix}}path = "{{{path}}}".replaceAll("\\{format\\}","json"){{#pathParams}}
6363
.replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}};
6464

6565
// query params
@@ -110,14 +110,29 @@ public class {{classname}} {
110110
}
111111

112112
try {
113+
113114
String[] {{localVariablePrefix}}authNames = new String[] { {{#authMethods}}"{{name}}"{{#hasMore}}, {{/hasMore}}{{/authMethods}} };
114-
String {{localVariablePrefix}}response = {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames);
115-
if({{localVariablePrefix}}response != null){
116-
return {{#returnType}}({{{returnType}}}) {{localVariablePrefix}}apiClient.deserialize({{localVariablePrefix}}response, "{{returnContainer}}", {{returnBaseType}}.class){{/returnType}};
117-
}
118-
else {
119-
return {{#returnType}}null{{/returnType}};
120-
}
115+
116+
{{#responses}}{{#isDefault}}
117+
{{#isBinary}}
118+
byte[] {{localVariablePrefix}}response = null;
119+
{{localVariablePrefix}}response = {{localVariablePrefix}}apiClient.invokeBinaryAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams,{{localVariablePrefix}} postBody, {{localVariablePrefix}}postBinaryBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames);
120+
return {{localVariablePrefix}}response;
121+
122+
{{/isBinary}}
123+
{{^isBinary}}
124+
125+
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);
126+
if({{localVariablePrefix}}response != null){
127+
return {{#returnType}}({{{returnType}}}) {{localVariablePrefix}}apiClient.deserialize({{localVariablePrefix}}response, "{{returnContainer}}", {{returnBaseType}}.class){{/returnType}};
128+
}
129+
else {
130+
return {{#returnType}}null{{/returnType}};
131+
}
132+
{{/isBinary}}
133+
{{/isDefault}}
134+
{{/responses}}
135+
121136
} catch (ApiException ex) {
122137
throw ex;
123138
}

modules/swagger-codegen/src/test/scala/CodegenTest.scala

+15
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,19 @@ class CodegenTest extends FlatSpec with Matchers {
139139
val op = codegen.fromOperation(path, "get", p, model.getDefinitions())
140140
op.returnType should be("String")
141141
}
142+
143+
it should "return byte array when response format is byte" in {
144+
val model = new SwaggerParser()
145+
.read("src/test/resources/2_0/binaryDataTest.json")
146+
System.err.println("model is " + model);
147+
val codegen = new DefaultCodegen()
148+
149+
val path = "/tests/binaryResponse"
150+
val p = model.getPaths().get(path).getPost()
151+
val op = codegen.fromOperation(path, "post", p, model.getDefinitions())
152+
op.returnType should be("byte[]")
153+
op.bodyParam.dataType should be ("byte[]")
154+
op.bodyParam.isBinary should equal (true);
155+
op.responses.get(0).isBinary should equal(true);
156+
}
142157
}

modules/swagger-codegen/src/test/scala/Java/JavaModelTest.scala

+22
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,28 @@ class JavaModelTest extends FlatSpec with Matchers {
365365
val vars = cm.vars
366366
cm.classname should be("WithDots")
367367
}
368+
369+
it should "convert a modelwith binary data" in {
370+
val model = new ModelImpl()
371+
.description("model with binary")
372+
.property("inputBinaryData", new ByteArrayProperty());
373+
374+
val codegen = new JavaClientCodegen()
375+
val cm = codegen.fromModel("sample", model)
376+
val vars = cm.vars
377+
378+
vars.get(0).baseName should be ("inputBinaryData")
379+
vars.get(0).getter should be ("getInputBinaryData")
380+
vars.get(0).setter should be ("setInputBinaryData")
381+
vars.get(0).datatype should be ("byte[]")
382+
vars.get(0).name should be ("inputBinaryData")
383+
vars.get(0).defaultValue should be ("null")
384+
vars.get(0).baseType should be ("byte[]")
385+
vars.get(0).hasMore should equal (null)
386+
vars.get(0).required should equal (null)
387+
vars.get(0).isNotContainer should equal (true)
388+
389+
}
368390
}
369391

370392

0 commit comments

Comments
 (0)