Skip to content

Commit 449eee6

Browse files
committed
open-api: default-value is not detected
- fix #3835
1 parent f807274 commit 449eee6

File tree

8 files changed

+304
-100
lines changed

8 files changed

+304
-100
lines changed

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/AnnotationParser.java

Lines changed: 107 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import static io.jooby.internal.openapi.AsmUtils.*;
99
import static io.jooby.internal.openapi.TypeFactory.*;
10-
import static java.util.Arrays.asList;
1110
import static java.util.Collections.singletonList;
1211

1312
import java.util.*;
@@ -22,6 +21,8 @@
2221

2322
import io.jooby.*;
2423
import io.jooby.annotation.*;
24+
import io.jooby.value.Value;
25+
import io.jooby.value.ValueFactory;
2526
import io.swagger.v3.oas.models.media.Content;
2627
import io.swagger.v3.oas.models.media.ObjectSchema;
2728
import io.swagger.v3.oas.models.media.Schema;
@@ -110,27 +111,52 @@ public boolean matches(String annotationType) {
110111

111112
public void setIn(Parameter parameter) {}
112113

114+
public Optional<String> getDefaultValue(List<AnnotationNode> annotations) {
115+
List<Class> names =
116+
Stream.of(annotations()).filter(it -> it.getName().startsWith("io.jooby")).toList();
117+
for (var a : annotations) {
118+
if (a.values != null) {
119+
var matches = names.stream().anyMatch(it -> Type.getDescriptor(it).equals(a.desc));
120+
if (matches) {
121+
for (int i = 0; i < a.values.size(); i++) {
122+
if (a.values.get(i).equals("value")) {
123+
Object value = a.values.get(i + 1);
124+
if (value != null && !value.toString().trim().isEmpty()) {
125+
return Optional.of(value.toString().trim());
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}
132+
return Optional.empty();
133+
}
134+
113135
public Optional<String> getHttpName(List<AnnotationNode> annotations) {
114-
List<Class> names = new ArrayList<>(asList(annotations()));
115-
names.add(Named.class);
116-
return annotations.stream()
117-
.filter(a -> names.stream().anyMatch(c -> Type.getDescriptor(c).equals(a.desc)))
118-
.map(
119-
a -> {
120-
if (a.values != null) {
121-
for (int i = 0; i < a.values.size(); i++) {
122-
if (a.values.get(i).equals("value")) {
123-
Object value = a.values.get(i + 1);
124-
if (value != null && value.toString().trim().length() > 0) {
125-
return value.toString().trim();
126-
}
127-
}
128-
}
136+
List<Map.Entry<Class, String>> names =
137+
Stream.concat(Stream.of(annotations()), Stream.of(Named.class))
138+
.map(it -> Map.entry(it, it.getName().startsWith("io.jooby") ? "name" : "value"))
139+
.toList();
140+
for (var a : annotations) {
141+
if (a.values != null) {
142+
var mapping =
143+
names.stream()
144+
.filter(it -> Type.getDescriptor(it.getKey()).equals(a.desc))
145+
.findFirst()
146+
.orElse(null);
147+
if (mapping != null) {
148+
for (int i = 0; i < a.values.size(); i++) {
149+
if (a.values.get(i).equals(mapping.getValue())) {
150+
Object value = a.values.get(i + 1);
151+
if (value != null && !value.toString().trim().isEmpty()) {
152+
return Optional.of(value.toString().trim());
129153
}
130-
return null;
131-
})
132-
.filter(Objects::nonNull)
133-
.findFirst();
154+
}
155+
}
156+
}
157+
}
158+
}
159+
return Optional.empty();
134160
}
135161

136162
public static ParamType find(List<AnnotationNode> annotations) {
@@ -383,9 +409,9 @@ private static List<ParameterExt> routerArguments(
383409
List<String> javaName;
384410
if ((parameter.name.equals("continuation") || parameter.name.equals("$completion"))
385411
&& i == method.parameters.size() - 1) {
386-
javaName = asList(parameter.name, "$continuation");
412+
javaName = List.of(parameter.name, "$continuation");
387413
} else {
388-
javaName = singletonList(parameter.name);
414+
javaName = List.of(parameter.name);
389415
}
390416
/* Java Type: */
391417
LocalVariableNode variable =
@@ -435,11 +461,7 @@ private static List<ParameterExt> routerArguments(
435461
body.setJavaType(javaType);
436462
requestBody.accept(body);
437463
} else if (paramType == ParamType.FORM) {
438-
String field = paramType.getHttpName(annotations).orElse(parameter.name);
439-
if (required) {
440-
requiredFormFields.add(field);
441-
}
442-
Schema schemaProperty = ctx.schema(javaType);
464+
var schemaProperty = ctx.schema(javaType);
443465
if (ctx.schemaRef(javaType).isPresent()) {
444466
// form bean (i.e. single body)
445467
RequestBodyExt body = new RequestBodyExt();
@@ -450,30 +472,45 @@ private static List<ParameterExt> routerArguments(
450472
body.setJavaType(javaType);
451473
requestBody.accept(body);
452474
} else {
475+
String field = paramType.getHttpName(annotations).orElse(parameter.name);
476+
paramType
477+
.getDefaultValue(annotations)
478+
.flatMap(value -> convertValue(ctx, javaType, value))
479+
.ifPresent(schemaProperty::setDefault);
480+
if (schemaProperty.getDefault() == null) {
481+
if (required) {
482+
requiredFormFields.add(field);
483+
}
484+
}
453485
// single property
454486
form.put(field, schemaProperty);
455487
}
456488
} else {
457489
ParameterExt argument = new ParameterExt();
458-
argument.setName(paramType.getHttpName(annotations).orElse(parameter.name));
459490
argument.setJavaType(javaType);
491+
argument.setName(paramType.getHttpName(annotations).orElse(parameter.name));
492+
paramType
493+
.getDefaultValue(annotations)
494+
.flatMap(value -> convertValue(ctx, javaType, value))
495+
.ifPresent(argument::setDefaultValue);
460496
paramType.setIn(argument);
461-
if (required) {
462-
argument.setRequired(true);
497+
if (argument.getDefaultValue() == null) {
498+
if (required) {
499+
argument.setRequired(true);
500+
}
463501
}
464502
result.add(argument);
465503
}
466504
}
467505
}
468-
if (form.size() > 0) {
469-
Schema schema = new ObjectSchema();
506+
if (!form.isEmpty()) {
507+
var schema = new ObjectSchema();
470508
schema.setProperties(form);
471-
if (requiredFormFields.size() > 0) {
509+
if (!requiredFormFields.isEmpty()) {
472510
schema.setRequired(requiredFormFields);
473511
}
474512

475-
io.swagger.v3.oas.models.media.MediaType mediaType =
476-
new io.swagger.v3.oas.models.media.MediaType();
513+
var mediaType = new io.swagger.v3.oas.models.media.MediaType();
477514
mediaType.setSchema(schema);
478515

479516
Content content = new Content();
@@ -487,6 +524,40 @@ private static List<ParameterExt> routerArguments(
487524
return result;
488525
}
489526

527+
private static Optional<?> convertValue(ParserContext ctx, String javaType, String value) {
528+
try {
529+
switch (javaType) {
530+
case "boolean":
531+
return Optional.of(Boolean.parseBoolean(value));
532+
case "int":
533+
return Optional.of(Integer.parseInt(value));
534+
case "long":
535+
return Optional.of(Long.parseLong(value));
536+
case "double":
537+
return Optional.of(Double.parseDouble(value));
538+
case "float":
539+
return Optional.of(Float.parseFloat(value));
540+
case "byte":
541+
return Optional.of(Byte.parseByte(value));
542+
case "short":
543+
return Optional.of(Short.parseShort(value));
544+
case "char":
545+
return Optional.of(value.charAt(0));
546+
case "java.lang.String":
547+
return Optional.of(value);
548+
default:
549+
{
550+
var realType = ctx.classLoader().loadClass(javaType);
551+
ValueFactory factory = new ValueFactory();
552+
return Optional.ofNullable(
553+
factory.convert(realType, Value.value(factory, "value", value)));
554+
}
555+
}
556+
} catch (Exception ignored) {
557+
return Optional.empty();
558+
}
559+
}
560+
490561
private static boolean isNullable(MethodNode method, int paramIndex) {
491562
if (paramIndex < method.invisibleAnnotableParameterCount) {
492563
List<AnnotationNode> annotations = method.invisibleParameterAnnotations[paramIndex];
@@ -604,7 +675,7 @@ private static boolean isRouter(MethodNode node) {
604675
List<String> annotationTypes =
605676
httpMethods().stream()
606677
.map(classname -> Type.getObjectType(classname.replace(".", "/")).getDescriptor())
607-
.collect(Collectors.toList());
678+
.toList();
608679

609680
return node.visibleAnnotations.stream().anyMatch(a -> annotationTypes.contains(a.desc));
610681
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/ParameterExt.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.fasterxml.jackson.annotation.JsonIgnore;
1111
import edu.umd.cs.findbugs.annotations.NonNull;
1212
import edu.umd.cs.findbugs.annotations.Nullable;
13+
import io.swagger.v3.oas.models.media.Schema;
1314
import io.swagger.v3.oas.models.media.StringSchema;
1415
import io.swagger.v3.oas.models.parameters.Parameter;
1516

@@ -37,6 +38,11 @@ public Object getDefaultValue() {
3738
return defaultValue;
3839
}
3940

41+
@Override
42+
public void setSchema(Schema schema) {
43+
super.setSchema(schema);
44+
}
45+
4046
public boolean isSingle() {
4147
return single;
4248
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/ParserContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ public Collection<Schema> schemas() {
161161
return schemas.values().stream().map(ref -> ref.schema).collect(Collectors.toList());
162162
}
163163

164+
public ClassLoader classLoader() {
165+
return source.getClassLoader();
166+
}
167+
164168
public JavaDocParser javadoc() {
165169
return javadocParser;
166170
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/RouteParser.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,12 @@ private List<Parameter> checkParameters(ParserContext ctx, List<Parameter> param
183183
for (Parameter parameter : parameters) {
184184
String javaType = ((ParameterExt) parameter).getJavaType();
185185
if (parameter.getSchema() == null) {
186-
Optional.ofNullable(ctx.schema(javaType)).ifPresent(parameter::setSchema);
186+
Optional.ofNullable(ctx.schema(javaType))
187+
.ifPresent(
188+
schema -> {
189+
schema.setDefault(((ParameterExt) parameter).getDefaultValue());
190+
parameter.setSchema(schema);
191+
});
187192
}
188193
if (parameter.getSchema() instanceof StringSchema && isPassword(parameter.getName())) {
189194
parameter.getSchema().setFormat("password");
@@ -214,7 +219,6 @@ private List<Parameter> checkParameters(ParserContext ctx, List<Parameter> param
214219
p.setDescription(propertyDoc);
215220
}
216221
}
217-
218222
params.add(p);
219223
}
220224
} else {
@@ -228,7 +232,9 @@ private List<Parameter> checkParameters(ParserContext ctx, List<Parameter> param
228232
}
229233

230234
private boolean isPassword(String name) {
231-
return "password".equalsIgnoreCase(name) || "pass".equalsIgnoreCase(name);
235+
return "password".equalsIgnoreCase(name)
236+
|| "pass".equalsIgnoreCase(name)
237+
|| "secret".equalsIgnoreCase(name);
232238
}
233239

234240
private void uniqueOperationId(List<OperationExt> operations) {

0 commit comments

Comments
 (0)