Skip to content

Commit 0797eff

Browse files
authored
Add @consumes annotation (#202)
* Body String * professionals have standards * fix bodyPublisher * rename to body * import * Update Body.java * Revert "rename to body" * fix other body types * input stream body * Update BodyString.java * Update ElementReader.java * now will use application/text fo string * work * byte array openAPI * Update SchemaDocBuilder.java
1 parent f3fd134 commit 0797eff

File tree

10 files changed

+111
-26
lines changed

10 files changed

+111
-26
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.avaje.http.api;
2+
3+
import static java.lang.annotation.ElementType.METHOD;
4+
import static java.lang.annotation.ElementType.TYPE;
5+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
6+
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Specify endpoint request media type for the generated OpenAPI json.
12+
*
13+
* <p>When not specified the default MediaType is application/json, so we specify this on
14+
* controllers or methods where the responses return a different media type.
15+
*
16+
* <pre>{@code
17+
* @Path("/customers")
18+
* @Consumes(MediaType.TEXT_PLAIN)
19+
* class CustomerController {
20+
* ...
21+
* }
22+
*
23+
* }</pre>
24+
*/
25+
@Target({TYPE, METHOD})
26+
@Retention(RUNTIME)
27+
public @interface Consumes {
28+
29+
/**
30+
* Specify request media type.
31+
*
32+
* <p>When not specified the default MediaType is application/json
33+
*/
34+
String value() default MediaType.APPLICATION_JSON;
35+
}

http-api/src/main/java/io/avaje/http/api/Produces.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
* Specify endpoint response media type.
1212
*
13-
* When not specified the default MediaType is APPLICATION_JSON
13+
* When not specified the default MediaType is application/json,
1414
* so we specify this on controllers or methods where the responses
1515
* return a different media type.
1616
*
@@ -24,14 +24,14 @@
2424
*
2525
* }</pre>
2626
*/
27-
@Target(value = {TYPE, METHOD})
28-
@Retention(value = RUNTIME)
27+
@Target({TYPE, METHOD})
28+
@Retention(RUNTIME)
2929
public @interface Produces {
3030

3131
/**
3232
* Specify response media type.
3333
*
34-
* <p>When not specified the default MediaType is APPLICATION_JSON
34+
* <p>When not specified the default MediaType is application/json
3535
*/
3636
String value() default MediaType.APPLICATION_JSON;
3737

http-generator-core/src/main/java/io/avaje/http/generator/core/MethodReader.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package io.avaje.http.generator.core;
22

3-
import static io.avaje.http.generator.core.ProcessingContext.*;
4-
import io.avaje.http.generator.core.javadoc.Javadoc;
5-
import io.avaje.http.generator.core.openapi.MethodDocBuilder;
3+
import static io.avaje.http.generator.core.ProcessingContext.doc;
4+
import static io.avaje.http.generator.core.ProcessingContext.docComment;
5+
import static io.avaje.http.generator.core.ProcessingContext.platform;
6+
import static io.avaje.http.generator.core.ProcessingContext.superMethods;
7+
68
import java.util.ArrayList;
79
import java.util.HashMap;
810
import java.util.List;
@@ -12,6 +14,7 @@
1214
import java.util.function.Predicate;
1315
import java.util.stream.Collectors;
1416
import java.util.stream.Stream;
17+
1518
import javax.lang.model.element.AnnotationMirror;
1619
import javax.lang.model.element.Element;
1720
import javax.lang.model.element.ExecutableElement;
@@ -20,6 +23,9 @@
2023
import javax.lang.model.type.TypeKind;
2124
import javax.lang.model.type.TypeMirror;
2225

26+
import io.avaje.http.generator.core.javadoc.Javadoc;
27+
import io.avaje.http.generator.core.openapi.MethodDocBuilder;
28+
2329
public class MethodReader {
2430

2531
private final ControllerReader bean;
@@ -32,6 +38,7 @@ public class MethodReader {
3238
*/
3339
private final List<String> methodRoles;
3440
private final Optional<ProducesPrism> producesAnnotation;
41+
private final Optional<ConsumesPrism> consumesAnnotation;
3542
private final List<SecurityRequirementPrism> securityRequirements;
3643
private final List<OpenAPIResponsePrism> apiResponses;
3744
private final ExecutableType actualExecutable;
@@ -52,7 +59,13 @@ public class MethodReader {
5259
this.actualParams = (actualExecutable == null) ? null : actualExecutable.getParameterTypes();
5360
this.isVoid = element.getReturnType().getKind() == TypeKind.VOID;
5461
this.methodRoles = Util.findRoles(element);
55-
this.producesAnnotation = findAnnotation(ProducesPrism::getOptionalOn);
62+
this.producesAnnotation =
63+
findAnnotation(ProducesPrism::getOptionalOn)
64+
.or(() -> ProducesPrism.getOptionalOn(bean.beanType()));
65+
this.consumesAnnotation =
66+
findAnnotation(ConsumesPrism::getOptionalOn)
67+
.or(() -> ConsumesPrism.getOptionalOn(bean.beanType()));
68+
5669
initWebMethodViaAnnotation();
5770

5871
this.superMethods =
@@ -303,6 +316,10 @@ public String produces() {
303316
return producesAnnotation.map(ProducesPrism::value).orElseGet(bean::produces);
304317
}
305318

319+
public Optional<ConsumesPrism> consumesAnnotation() {
320+
return consumesAnnotation;
321+
}
322+
306323
public List<SecurityRequirementPrism> securityRequirements() {
307324
return securityRequirements;
308325
}

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/DocContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ void addFormParam(Operation operation, String varName, Schema schema) {
102102
schemaBuilder.addFormParam(operation, varName, schema);
103103
}
104104

105-
void addRequestBody(Operation operation, Schema schema, boolean asForm, String description) {
106-
schemaBuilder.addRequestBody(operation, schema, asForm, description);
105+
void addRequestBody(Operation operation, Schema schema, String mediaType, String description) {
106+
schemaBuilder.addRequestBody(operation, schema, mediaType, description);
107107
}
108108

109109
/**

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/KnownTypes.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.http.generator.core.openapi;
22

33
import java.io.File;
4+
import java.io.InputStream;
45
import java.math.BigDecimal;
56
import java.math.BigInteger;
67
import java.net.URI;
@@ -46,6 +47,7 @@ interface KnownType {
4647
add(new IntegerType(), Integer.class);
4748
add(new PLongType(), long.class);
4849
add(new LongType(), Long.class);
50+
add(new BytesType(), byte[].class, InputStream.class);
4951

5052
add(new PNumberType(), double.class, float.class);
5153
add(new NumberType(), Double.class, Float.class, BigDecimal.class, BigInteger.class);
@@ -174,6 +176,12 @@ private URIType() {
174176
}
175177
}
176178

179+
private class BytesType extends StringBaseType {
180+
private BytesType() {
181+
super("byte");
182+
}
183+
}
184+
177185
private class StringBaseType implements KnownType {
178186
private final String format;
179187

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/MethodDocBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.avaje.http.generator.core.openapi;
22

3+
import java.util.Optional;
4+
5+
import io.avaje.http.generator.core.ConsumesPrism;
36
import io.avaje.http.generator.core.HiddenPrism;
47
import io.avaje.http.generator.core.MethodParam;
58
import io.avaje.http.generator.core.MethodReader;
@@ -21,11 +24,13 @@ public class MethodDocBuilder {
2124
private final DocContext ctx;
2225

2326
private final Operation operation = new Operation();
27+
private Optional<ConsumesPrism> consumeOp;
2428

2529
public MethodDocBuilder(MethodReader methodReader, DocContext ctx) {
2630
this.methodReader = methodReader;
2731
this.ctx = ctx;
2832
this.javadoc = methodReader.javadoc();
33+
this.consumeOp = methodReader.consumesAnnotation();
2934
}
3035

3136
public void build() {
@@ -129,6 +134,10 @@ Operation getOperation() {
129134
return operation;
130135
}
131136

137+
public Optional<ConsumesPrism> consumeOp() {
138+
return consumeOp;
139+
}
140+
132141
private boolean isEmpty(String value) {
133142
return value == null || value.isEmpty();
134143
}

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/MethodParamDocBuilder.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
package io.avaje.http.generator.core.openapi;
22

3+
import java.util.Optional;
4+
5+
import javax.lang.model.element.Element;
6+
7+
import io.avaje.http.generator.core.ConsumesPrism;
38
import io.avaje.http.generator.core.ElementReader;
49
import io.avaje.http.generator.core.ParamType;
510
import io.avaje.http.generator.core.javadoc.Javadoc;
611
import io.swagger.v3.oas.models.Operation;
712
import io.swagger.v3.oas.models.media.Schema;
13+
import io.swagger.v3.oas.models.media.StringSchema;
814
import io.swagger.v3.oas.models.parameters.Parameter;
915

10-
import javax.lang.model.element.Element;
11-
1216
/**
1317
* Build the OpenAPI for a method parameter.
1418
*/
1519
public class MethodParamDocBuilder {
16-
20+
private static final String APP_FORM = "application/x-www-form-urlencoded";
21+
private static final String APP_JSON = "application/json";
22+
private static final String APP_TXT = "application/text";
1723
private final DocContext ctx;
1824
private final Javadoc javadoc;
1925
private final Operation operation;
@@ -22,12 +28,14 @@ public class MethodParamDocBuilder {
2228
private final String rawType;
2329
private final ParamType paramType;
2430
private final Element element;
31+
private Optional<ConsumesPrism> consumeOp;
2532

2633
public MethodParamDocBuilder(MethodDocBuilder methodDoc, ElementReader param) {
2734

2835
this.ctx = methodDoc.getContext();
2936
this.javadoc = methodDoc.getJavadoc();
3037
this.operation = methodDoc.getOperation();
38+
this.consumeOp = methodDoc.consumeOp();
3139

3240
this.paramType = param.paramType();
3341
this.paramName = param.paramName();
@@ -65,9 +73,21 @@ private void addMetaRequestBody(DocContext ctx, Javadoc javadoc, Operation opera
6573

6674
Schema schema = ctx.toSchema(rawType, element);
6775
String description = javadoc.getParams().get(paramName);
68-
69-
boolean asForm = (paramType == ParamType.FORM);
70-
ctx.addRequestBody(operation, schema, asForm, description);
76+
var mediaType =
77+
consumeOp
78+
.map(ConsumesPrism::value)
79+
.orElseGet(
80+
() -> {
81+
boolean asForm = (paramType == ParamType.FORM);
82+
var mime = asForm ? APP_FORM : APP_JSON;
83+
84+
if (schema instanceof StringSchema) {
85+
mime = APP_TXT;
86+
}
87+
return mime;
88+
});
89+
90+
ctx.addRequestBody(operation, schema, mediaType, description);
7191
}
7292

7393
}

http-generator-core/src/main/java/io/avaje/http/generator/core/openapi/SchemaDocBuilder.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ class SchemaDocBuilder {
4545

4646
private static final String APP_FORM = "application/x-www-form-urlencoded";
4747
private static final String APP_JSON = "application/json";
48-
private static final String APP_TXT = "application/text";
4948

5049
private final Elements elements;
5150
private final Types types;
@@ -103,21 +102,15 @@ private Schema requestFormParamSchema(RequestBody body) {
103102
return schema;
104103
}
105104

106-
/**
107-
* Add as request body.
108-
*/
109-
void addRequestBody(Operation operation, Schema schema, boolean asForm, String description) {
105+
/** Add as request body. */
106+
void addRequestBody(Operation operation, Schema schema, String mediaType, String description) {
110107
RequestBody body = requestBody(operation);
111108
body.setDescription(description);
112109

113110
MediaType mt = new MediaType();
114111
mt.schema(schema);
115112

116-
String mime = asForm ? APP_FORM : APP_JSON;
117-
if (schema instanceof StringSchema) {
118-
mime = APP_TXT;
119-
}
120-
body.getContent().addMediaType(mime, mt);
113+
body.getContent().addMediaType(mediaType, mt);
121114
}
122115

123116
private RequestBody requestBody(Operation operation) {

http-generator-core/src/main/java/io/avaje/http/generator/core/package-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
@GeneratePrism(value = io.avaje.http.api.Path.class, publicAccess = true)
1818
@GeneratePrism(value = io.avaje.http.api.Post.class, publicAccess = true)
1919
@GeneratePrism(value = io.avaje.http.api.Produces.class, publicAccess = true)
20+
@GeneratePrism(value = io.avaje.http.api.Consumes.class, publicAccess = true)
2021
@GeneratePrism(value = io.avaje.http.api.Put.class, publicAccess = true)
2122
@GeneratePrism(value = io.swagger.v3.oas.annotations.OpenAPIDefinition.class, publicAccess = true)
2223
@GeneratePrism(value = io.swagger.v3.oas.annotations.tags.Tag.class, publicAccess = true)

tests/test-javalin-jsonb/src/main/java/org/example/myapp/web/test/TestController2.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import org.example.myapp.web.ServerType;
88

9+
import io.avaje.http.api.Consumes;
910
import io.avaje.http.api.BodyString;
1011
import io.avaje.http.api.Controller;
1112
import io.avaje.http.api.Default;
@@ -47,6 +48,7 @@ String mapTest(Map<String, List<String>> strings) {
4748
}
4849

4950
@Get("/inputStream")
51+
@Consumes("application/bson")
5052
String stream(InputStream stream) {
5153
return stream.toString();
5254
}

0 commit comments

Comments
 (0)