• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

torand / openapi2java / 18309258705

07 Oct 2025 10:08AM UTC coverage: 81.939% (-0.01%) from 81.952%
18309258705

push

github

torand
fix: sonar qube issues

548 of 785 branches covered (69.81%)

Branch coverage included in aggregate %.

6 of 9 new or added lines in 3 files covered. (66.67%)

1 existing line in 1 file now uncovered.

1616 of 1856 relevant lines covered (87.07%)

5.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.25
/src/main/java/io/github/torand/openapi2java/collectors/MethodInfoCollector.java
1
/*
2
 * Copyright (c) 2024-2025 Tore Eide Andersen
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package io.github.torand.openapi2java.collectors;
17

18
import io.github.torand.openapi2java.generators.Options;
19
import io.github.torand.openapi2java.model.AnnotationInfo;
20
import io.github.torand.openapi2java.model.ConstantValue;
21
import io.github.torand.openapi2java.model.MethodInfo;
22
import io.github.torand.openapi2java.model.MethodParamInfo;
23
import io.github.torand.openapi2java.model.SecurityRequirementInfo;
24
import io.github.torand.openapi2java.model.TypeInfo;
25
import io.swagger.v3.oas.models.Operation;
26
import io.swagger.v3.oas.models.headers.Header;
27
import io.swagger.v3.oas.models.media.MediaType;
28
import io.swagger.v3.oas.models.media.Schema;
29
import io.swagger.v3.oas.models.parameters.Parameter;
30
import io.swagger.v3.oas.models.parameters.RequestBody;
31
import io.swagger.v3.oas.models.responses.ApiResponse;
32
import io.swagger.v3.oas.models.responses.ApiResponses;
33

34
import java.util.ArrayList;
35
import java.util.List;
36
import java.util.Map;
37
import java.util.Optional;
38

39
import static io.github.torand.javacommons.collection.CollectionHelper.nonEmpty;
40
import static io.github.torand.javacommons.lang.StringHelper.nonBlank;
41
import static io.github.torand.javacommons.lang.StringHelper.quote;
42
import static io.github.torand.javacommons.lang.StringHelper.stripTail;
43
import static io.github.torand.javacommons.lang.StringHelper.uncapitalize;
44
import static io.github.torand.javacommons.stream.StreamHelper.streamSafely;
45
import static io.github.torand.openapi2java.collectors.SchemaResolver.isObjectType;
46
import static io.github.torand.openapi2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NOT_NULLABLE;
47
import static io.github.torand.openapi2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NULLABLE;
48
import static io.github.torand.openapi2java.utils.StringUtils.joinCsv;
49
import static java.lang.Boolean.TRUE;
50
import static java.util.Objects.isNull;
51
import static java.util.Objects.nonNull;
52
import static java.util.Objects.requireNonNull;
53

54
/**
55
 * Collects information about a method from an operation.
56
 */
57
public class MethodInfoCollector extends BaseCollector {
58
    private static final String APPLICATION_JSON = "application/json";
59
    private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
60
    private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
61
    private static final String MULTIPART_FORM_DATA = "multipart/form-data";
62
    private static final String TEXT_PLAIN = "text/plain";
63

64
    private static final Map<String, String> standardContentTypes = Map.of(
13✔
65
        APPLICATION_JSON, "APPLICATION_JSON",
66
        APPLICATION_OCTET_STREAM, "APPLICATION_OCTET_STREAM",
67
        APPLICATION_FORM_URLENCODED, "APPLICATION_FORM_URLENCODED",
68
        MULTIPART_FORM_DATA, "MULTIPART_FORM_DATA",
69
        TEXT_PLAIN, "TEXT_PLAIN"
70
    );
71

72
    private static final String PARAM_IN_HEADER = "header";
73
    private static final String PARAM_IN_QUERY = "query";
74
    private static final String PARAM_IN_PATH = "path";
75
    private static final String PARAM_IN_COOKIE = "cookie";
76

77
    private final ComponentResolver componentResolver;
78
    private final TypeInfoCollector typeInfoCollector;
79
    private final SecurityRequirementCollector securityRequirementCollector;
80

81
    public MethodInfoCollector(ComponentResolver componentResolver, TypeInfoCollector typeInfoCollector, Options opts) {
82
        super(opts);
3✔
83
        this.componentResolver = componentResolver;
3✔
84
        this.typeInfoCollector = typeInfoCollector;
3✔
85
        this.securityRequirementCollector = new SecurityRequirementCollector(opts);
6✔
86
    }
1✔
87

88
    public MethodInfo getMethodInfo(String verb, String path, Operation operation) {
89
        MethodInfo methodInfo = new MethodInfo(operation.getOperationId())
7✔
90
            .withAddedAnnotation(getVerbAnnotation(verb))
4✔
91
            .withAddedAnnotation(getPathAnnotation(path));
3✔
92

93
        if (TRUE.equals(operation.getDeprecated())) {
5!
94
            methodInfo = methodInfo.withDeprecationMessage(formatDeprecationMessage(operation.getExtensions()));
×
95
        }
96

97
        if (nonNull(operation.getRequestBody())) {
4✔
98
            methodInfo = methodInfo.withAddedAnnotation(getConsumesAnnotation(operation.getRequestBody()));
7✔
99
        }
100

101
        if (nonNull(operation.getResponses())) {
4!
102
            methodInfo = methodInfo.withAddedAnnotation(getProducesAnnotation(operation.getResponses()));
7✔
103
        }
104

105
        if (nonEmpty(operation.getSecurity())) {
4!
106
            SecurityRequirementInfo secReqInfo = securityRequirementCollector.getSequrityRequirementInfo(operation.getSecurity());
×
107
            if (nonNull(secReqInfo.annotation())) {
×
108
                methodInfo = methodInfo.withAddedAnnotation(secReqInfo.annotation());
×
109
            }
110
        }
111

112
        if (opts.addMpOpenApiAnnotations()) {
4!
113
            methodInfo = methodInfo.withAddedAnnotation(getOperationAnnotation(operation));
6✔
114

115
            if (nonEmpty(operation.getParameters())) {
4✔
116
                List<AnnotationInfo> parameterAnnotations = new ArrayList<>();
4✔
117
                operation.getParameters().forEach(parameter ->
6✔
118
                    parameterAnnotations.add(getParameterAnnotation(parameter))
7✔
119
                );
120
                methodInfo = methodInfo.withAddedAnnotations(parameterAnnotations);
4✔
121
            }
122

123
            if (nonEmpty(operation.getResponses())) {
4!
124
                List<AnnotationInfo> apiResponseAnnotations = new ArrayList<>();
4✔
125
                operation.getResponses().forEach((code, response) ->
6✔
126
                    apiResponseAnnotations.add(getApiResponseAnnotation(response, code))
8✔
127
                );
128
                methodInfo = methodInfo.withAddedAnnotations(apiResponseAnnotations);
4✔
129

130
                if (opts.useResteasyResponse()) {
4✔
131
                    String code = operation.getResponses().keySet().iterator().next();
7✔
132
                    ApiResponse response = operation.getResponses().get(code);
6✔
133
                    methodInfo = methodInfo.withReturnType(getResponseType(code, response));
7✔
134
                }
135
            }
136
        }
137

138
        List<MethodParamInfo> methodParams = getMethodParams(operation);
4✔
139

140
        return methodInfo.withAddedParameters(methodParams);
4✔
141
    }
142

143
    List<MethodParamInfo> getMethodParams(Operation operation) {
144
        List<MethodParamInfo> methodParams = new ArrayList<>();
4✔
145

146
        // Regular parameters
147
        if (nonEmpty(operation.getParameters())) {
4✔
148
            operation.getParameters().forEach(param -> {
6✔
149
                Parameter realParam = param;
2✔
150
                if (nonNull(param.get$ref())) {
4!
151
                    realParam = componentResolver.parameters().getOrThrow(param.get$ref());
7✔
152
                }
153

154
                MethodParamInfo paramInfo = new MethodParamInfo()
5✔
155
                    .withNullable(!TRUE.equals(realParam.getRequired()))
9✔
156
                    .withAddedAnnotation(getMethodParameterAnnotation(realParam));
3✔
157

158
                Schema<?> realSchema = realParam.getSchema();
3✔
159
                if (isNull(realSchema)) {
3!
160
                    throw new IllegalStateException("No schema found for ApiParameter %s".formatted(realParam.getName()));
×
161
                }
162

163
                TypeInfo paramType = typeInfoCollector.getTypeInfo(realParam.getSchema(), paramInfo.nullable() ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
12✔
164
                paramInfo = paramInfo
2✔
165
                    .withType(paramType)
3✔
166
                    .withName(toParamName(realParam.getName()))
4✔
167
                    .withComment(paramType.description());
3✔
168

169
                if (TRUE.equals(realParam.getDeprecated())) {
5!
170
                    paramInfo = paramInfo.withDeprecationMessage(formatDeprecationMessage(realParam.getExtensions()));
×
171
                }
172

173
                methodParams.add(paramInfo);
4✔
174
            });
1✔
175
        }
176

177
        // Payload parameters
178
        if (nonNull(operation.getRequestBody()) && nonEmpty(operation.getRequestBody().getContent())) {
9!
179
            operation.getRequestBody().getContent().keySet().stream()
5✔
180
                .findFirst()
5✔
181
                .ifPresent(mtKey -> {
1✔
182
                    boolean isMultipart = MULTIPART_FORM_DATA.equals(mtKey);
4✔
183
                    MediaType mt = operation.getRequestBody().getContent().get(mtKey);
7✔
184
                    Schema<?> mtSchema = mt.getSchema();
3✔
185

186
                    if (nonNull(mtSchema)) {
3!
187
                        if (isMultipart) {
2✔
188
                            if (!isObjectType(mtSchema)) {
3!
189
                                throw new IllegalStateException("Multipart body should be of type 'object'");
×
190
                            }
191

192
                            if (mtSchema.getProperties().containsKey("file") && !mtSchema.getProperties().containsKey("filename")) {
10!
193
                                throw new IllegalStateException("A multipart property 'file' should be accompanied by a 'filename' property containing the filename, since the File object will reference a random temporary internal filename.");
×
194
                            }
195

196
                            mtSchema.getProperties().forEach((propName, propSchema) -> {
7✔
197
                                MethodParamInfo paramInfo = getMultipartPayloadMethodParameter(propName, propSchema);
5✔
198
                                methodParams.add(paramInfo);
4✔
199
                            });
1✔
200
                        } else {
201
                            MethodParamInfo paramInfo = getSingularPayloadMethodParameter(mtSchema);
4✔
202
                            methodParams.add(paramInfo);
4✔
203
                        }
204
                    }
205
                });
1✔
206
        }
207

208
        return methodParams;
2✔
209
    }
210

211
    private AnnotationInfo getVerbAnnotation(String verb) {
212
        return new AnnotationInfo("@%s".formatted(verb), "jakarta.ws.rs.%s".formatted(verb));
20✔
213
    }
214

215
    private AnnotationInfo getPathAnnotation(String path) {
216
        return new AnnotationInfo("@Path(\"%s\")".formatted(normalizePath(path)), "jakarta.ws.rs.Path");
15✔
217
    }
218

219
    private String getResponseType(String code, ApiResponse response) {
220
        String responseType = null;
2✔
221

222
        int numericCode = Integer.parseInt(code);
3✔
223
        if (isSuccessfulStatusCode(numericCode) && nonEmpty(response.getContent())) {
8!
224
            for (MediaType mediaType : response.getContent().values()) {
12✔
225
                Schema<?> schema = mediaType.getSchema();
3✔
226
                TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
227
                if (nonNull(bodyType)) {
3!
228
                    String fullName = bodyType.getFullName();
3✔
229
                    if (isNull(responseType)) {
3✔
230
                        // If no return type is set yet, the type of this media type is used...
231
                        responseType = fullName;
3✔
232
                    } else if (!fullName.equals(responseType)) {
4!
233
                        // ...but if a return type is already set, and this media type specifies
234
                        // a different type, we cannot safely infer one single return type, and
235
                        // give up type safety and allow anything
236
                        responseType = opts.useKotlinSyntax() ? "*" : "?";
×
237
                        break; // no need to look any further
×
238
                    }
239
                }
240
            }
1✔
241
        }
242

243
        return responseType;
2✔
244
    }
245

246
    private MethodParamInfo getSingularPayloadMethodParameter(Schema<?> schema) {
247
        TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema, FORCE_NOT_NULLABLE);
6✔
248

249
        return new MethodParamInfo(toParamName(bodyType.name()))
9✔
250
            .withNullable(false)
2✔
251
            .withType(bodyType)
2✔
252
            .withComment(bodyType.description());
2✔
253
    }
254

255
    private MethodParamInfo getMultipartPayloadMethodParameter(String name, Schema<?> schema) {
256
        TypeInfo bodyType;
257
        String partMediaType = null;
2✔
258

259
        if ("file".equals(name)) {
4✔
260
            bodyType = new TypeInfo()
4✔
261
                .withName("File")
2✔
262
                .withAddedNormalImport("java.io.File")
2✔
263
                .withNullable(false)
6✔
264
                .withAddedAnnotation(new AnnotationInfo("@NotNull", "jakarta.validation.constraints.NotNull"))
2✔
265
                .withDescription(schema.getDescription());
3✔
266

267
            partMediaType = APPLICATION_OCTET_STREAM;
3✔
268
        } else {
269
            bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
270

271
            if (isObjectType(schema)) {
3!
272
                throw new IllegalStateException("Multipart property of type 'object' not supported. Use $ref instead.");
×
273
            }
274

275
            partMediaType = APPLICATION_JSON;
2✔
276
            if (bodyType.primitive() || (bodyType.isArray() && bodyType.itemType().primitive())) {
3!
277
                partMediaType = TEXT_PLAIN;
2✔
278
            }
279
        }
280

281
        // OpenAPI 3.1.x only
282
        if (nonBlank(schema.getContentMediaType())) {
4✔
283
            partMediaType = schema.getContentMediaType();
3✔
284
        }
285

286
        ConstantValue partMediaTypeConstant = getMediaTypeConstant(partMediaType);
4✔
287

288
        return new MethodParamInfo(name)
6✔
289
            .withNullable(bodyType.nullable())
3✔
290
            .withType(bodyType)
2✔
291
            .withComment(bodyType.description())
11✔
292
            .withAddedAnnotation(new AnnotationInfo("@RestForm(\"%s\")".formatted(name), "org.jboss.resteasy.reactive.RestForm"))
12✔
293
            .withAddedAnnotation(new AnnotationInfo("@PartType(%s)".formatted(partMediaTypeConstant.value()), "org.jboss.resteasy.reactive.PartType"))
7✔
294
            .withAddedImports(partMediaTypeConstant);
1✔
295
    }
296

297
    private AnnotationInfo getConsumesAnnotation(RequestBody requestBody) {
298
        List<ConstantValue> mediaTypes = new ArrayList<>();
4✔
299
        if (nonEmpty(requestBody.getContent())) {
4!
300
            streamSafely(requestBody.getContent().keySet())
6✔
301
                .map(this::getMediaTypeConstant)
3✔
302
                .forEach(mediaTypes::add);
4✔
303
        }
304

305
        String mediaTypesString = formatAnnotationDefaultParam(mediaTypes.stream().map(ConstantValue::value).toList());
8✔
306

307
        return new AnnotationInfo("@Consumes(%s)".formatted(mediaTypesString))
13✔
308
            .withAddedNormalImport("jakarta.ws.rs.Consumes")
2✔
309
            .withAddedImports(mediaTypes);
1✔
310
    }
311

312
    private AnnotationInfo getProducesAnnotation(ApiResponses responses) {
313
        List<ConstantValue> mediaTypes = new ArrayList<>();
4✔
314
        mediaTypes.add(new ConstantValue("APPLICATION_JSON").withStaticImport("jakarta.ws.rs.core.MediaType.APPLICATION_JSON"));
9✔
315

316
        getSuccessResponse(responses).ifPresent(apiResponse -> {
7✔
317
            if (nonNull(apiResponse.getContent())) {
4✔
318
                apiResponse.getContent().keySet().stream()
5✔
319
                    .filter(mt -> !APPLICATION_JSON.equals(mt))
11✔
320
                    .map(this::getMediaTypeConstant)
3✔
321
                    .forEach(mediaTypes::add);
4✔
322
            }
323
        });
1✔
324

325
        String mediaTypesString = formatAnnotationDefaultParam(mediaTypes.stream().map(ConstantValue::value).toList());
8✔
326

327
        return new AnnotationInfo("@Produces(%s)".formatted(mediaTypesString))
13✔
328
            .withAddedNormalImport("jakarta.ws.rs.Produces")
2✔
329
            .withAddedImports(mediaTypes);
1✔
330
    }
331

332
    private AnnotationInfo getOperationAnnotation(Operation operation) {
333
        List<String> params = new ArrayList<>();
4✔
334
        params.add("operationId = \"%s\"".formatted(operation.getOperationId()));
12✔
335
        params.add("summary = \"%s\"".formatted(operation.getSummary()));
12✔
336

337
        if (TRUE.equals(operation.getDeprecated())) {
5!
338
            params.add("deprecated = true");
×
339
        }
340

341
        return new AnnotationInfo("@Operation(%s)".formatted(joinCsv(params)), "org.eclipse.microprofile.openapi.annotations.Operation");
14✔
342
    }
343

344
    private AnnotationInfo getParameterAnnotation(Parameter parameter) {
345
        Parameter realParameter = parameter;
2✔
346
        if (nonNull(parameter.get$ref())) {
4!
347
            realParameter = componentResolver.parameters().getOrThrow(parameter.get$ref());
7✔
348
        }
349

350
        AnnotationInfo parameterAnnotation = new AnnotationInfo();
4✔
351

352
        List<String> params = new ArrayList<>();
4✔
353

354
        ConstantValue inValue = getParameterInValue(realParameter);
4✔
355
        String inName = opts.useKotlinSyntax() ? "`in`" : "in";
8✔
356
        params.add("%s = %s".formatted(inName, inValue.value()));
16✔
357
        parameterAnnotation = parameterAnnotation.withAddedImports(inValue);
4✔
358

359
        if (inValue.value().equalsIgnoreCase(PARAM_IN_HEADER)) {
5✔
360
            ConstantValue headerNameConstant = getHeaderNameConstant(realParameter.getName());
5✔
361
            params.add("name = %s".formatted(headerNameConstant.value()));
12✔
362
            parameterAnnotation = parameterAnnotation.withAddedImports(headerNameConstant);
4✔
363
        } else {
1✔
364
            params.add("name = \"%s\"".formatted(realParameter.getName()));
12✔
365
        }
366

367
        params.add("description = \"%s\"".formatted(normalizeDescription(realParameter.getDescription())));
14✔
368

369
        if (TRUE.equals(realParameter.getRequired())) {
5✔
370
            params.add("required = true");
4✔
371
        }
372

373
        if (nonNull(realParameter.getSchema())) {
4!
374
            AnnotationInfo schemaAnnotation = getSchemaAnnotation(realParameter.getSchema());
5✔
375
            params.add("schema = %s".formatted(schemaAnnotation.annotation()));
12✔
376
            parameterAnnotation = parameterAnnotation.withAddedImports(schemaAnnotation);
4✔
377
        }
378

379
        if (nonEmpty(realParameter.getContent())) {
4!
380
            List<AnnotationInfo> contentAnnotations = new ArrayList<>();
×
381
            realParameter.getContent().forEach((contentType, mediaType) ->
×
382
                contentAnnotations.add(getContentAnnotation(contentType, mediaType))
×
383
            );
384

385
            params.add("content = %s".formatted(formatAnnotationNamedParam(contentAnnotations.stream().map(AnnotationInfo::annotation).toList())));
×
386
            parameterAnnotation = parameterAnnotation.withAddedImports(contentAnnotations);
×
387
        }
388

389
        if (nonNull(realParameter.getStyle())) {
4!
390
            Parameter.StyleEnum defaultStyle = getDefaultParameterStyle(realParameter);
4✔
391
            if (!realParameter.getStyle().equals(defaultStyle)) {
5✔
392
                ConstantValue parameterStyle = getParameterStyle(realParameter);
4✔
393
                params.add("style = %s".formatted(parameterStyle.value()));
12✔
394
                parameterAnnotation = parameterAnnotation.withAddedImports(parameterStyle);
4✔
395
            }
396
        }
397

398
        if (nonNull(realParameter.getExplode())) {
4!
399
            boolean defaultValue = Parameter.StyleEnum.FORM.equals(realParameter.getStyle());
5✔
400
            if (!realParameter.getExplode().equals(defaultValue)) {
6✔
401
                ConstantValue parameterExplode = getParameterExplode(realParameter);
4✔
402
                params.add("explode = %s".formatted(parameterExplode.value()));
12✔
403
                parameterAnnotation = parameterAnnotation.withAddedImports(parameterExplode);
4✔
404
            }
405
        }
406
        if (TRUE.equals(realParameter.getDeprecated())) {
5!
407
            params.add("deprecated = true");
×
408
        }
409

410
        return parameterAnnotation.withAnnotation("@Parameter(%s)".formatted(joinCsv(params)))
13✔
411
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.parameters.Parameter");
1✔
412
    }
413

414
    private Parameter.StyleEnum getDefaultParameterStyle(Parameter parameter) {
415
        return switch(parameter.getIn()) {
10!
416
            case PARAM_IN_HEADER -> Parameter.StyleEnum.SIMPLE;
2✔
417
            case PARAM_IN_QUERY -> Parameter.StyleEnum.FORM;
2✔
418
            case PARAM_IN_PATH -> Parameter.StyleEnum.SIMPLE;
2✔
NEW
419
            case PARAM_IN_COOKIE -> Parameter.StyleEnum.FORM;
×
UNCOV
420
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
×
421
        };
422
    }
423

424
    private ConstantValue getParameterStyle(Parameter parameter) {
425
        String style = switch (parameter.getStyle()) {
6!
426
            case MATRIX -> "MATRIX";
×
427
            case LABEL -> "LABEL";
×
428
            case FORM -> "FORM";
×
429
            case SIMPLE -> "SIMPLE";
2✔
430
            case SPACEDELIMITED -> "SPACEDELIMITED";
×
431
            case PIPEDELIMITED -> "PIPEDELIMITED";
×
432
            case DEEPOBJECT -> "DEEPOBJECT";
1✔
433
        };
434

435
        return new ConstantValue(style).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.ParameterStyle." + style);
8✔
436
    }
437

438
    private ConstantValue getParameterExplode(Parameter parameter) {
439
        String explode = TRUE.equals(parameter.getExplode()) ? "TRUE" : "FALSE";
8!
440
        return new ConstantValue(explode).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.Explode." + explode);
8✔
441
    }
442

443
    private ConstantValue getParameterInValue(Parameter parameter) {
444
        String inValue = switch (parameter.getIn().toLowerCase()) {
10!
445
            case "" -> "DEFAULT";
×
446
            case PARAM_IN_HEADER -> "HEADER";
2✔
447
            case PARAM_IN_QUERY -> "QUERY";
2✔
448
            case PARAM_IN_PATH -> "PATH";
2✔
449
            case PARAM_IN_COOKIE -> "COOKIE";
×
450
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
1✔
451
        };
452

453
        return new ConstantValue(inValue).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.ParameterIn." + inValue);
8✔
454
    }
455

456
    private AnnotationInfo getApiResponseAnnotation(ApiResponse response, String statusCode) {
457
        ApiResponse realResponse = response;
2✔
458
        if (nonNull(response.get$ref())) {
4✔
459
            realResponse = componentResolver.responses().getOrThrow(response.get$ref());
7✔
460
        }
461

462
        AnnotationInfo apiResponseAnnotation = new AnnotationInfo();
4✔
463

464
        List<String> params = new ArrayList<>();
4✔
465
        params.add("responseCode = \"%s\"".formatted(statusCode));
11✔
466
        params.add("description = \"%s\"".formatted(normalizeDescription(realResponse.getDescription())));
14✔
467

468
        if (nonEmpty(realResponse.getHeaders())) {
4✔
469
            List<AnnotationInfo> headerAnnotations = new ArrayList<>();
4✔
470
            realResponse.getHeaders().forEach((name, header) ->
6✔
471
                headerAnnotations.add(getHeaderAnnotation(name, header))
8✔
472
            );
473

474
            params.add("headers = %s".formatted(
11✔
475
                formatAnnotationNamedParam(headerAnnotations.stream().map(AnnotationInfo::annotation).toList()))
6✔
476
            );
477

478
            apiResponseAnnotation = apiResponseAnnotation.withAddedImports(headerAnnotations);
4✔
479
        }
480

481
        if (nonEmpty(realResponse.getContent())) {
4✔
482
            List<AnnotationInfo> contentAnnotations = new ArrayList<>();
4✔
483
            realResponse.getContent().forEach((contentType, mediaType) ->
6✔
484
                contentAnnotations.add(getContentAnnotation(contentType, mediaType))
8✔
485
            );
486

487
            params.add("content = %s".formatted(
11✔
488
                formatAnnotationNamedParam(contentAnnotations.stream().map(AnnotationInfo::annotation).toList()))
6✔
489
            );
490

491
            apiResponseAnnotation = apiResponseAnnotation.withAddedImports(contentAnnotations);
4✔
492
        }
493

494
        return apiResponseAnnotation
8✔
495
            .withAnnotation("@APIResponse(%s)".formatted(joinCsv(params)))
5✔
496
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.responses.APIResponse");
1✔
497
    }
498

499
    private AnnotationInfo getMethodParameterAnnotation(Parameter parameter) {
500
        String paramAnnotationName = switch (parameter.getIn().toLowerCase()) {
10!
501
            case PARAM_IN_HEADER -> "HeaderParam";
2✔
502
            case PARAM_IN_QUERY -> "QueryParam";
2✔
503
            case PARAM_IN_PATH -> "PathParam";
2✔
504
            case PARAM_IN_COOKIE -> "CookieParam";
×
505
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
1✔
506
        };
507

508
        final String annotationImport = "jakarta.ws.rs." + paramAnnotationName;
3✔
509

510
        if (paramAnnotationName.equals("HeaderParam")) {
4✔
511
            ConstantValue headerNameConstant = getHeaderNameConstant(parameter.getName());
5✔
512
            return new AnnotationInfo("@%s(%s)".formatted(paramAnnotationName, headerNameConstant.value()))
18✔
513
                .withAddedNormalImport(annotationImport)
2✔
514
                .withAddedImports(headerNameConstant);
1✔
515
        } else {
516
            return new AnnotationInfo("@%s(\"%s\")".formatted(paramAnnotationName, parameter.getName()))
18✔
517
                .withAddedNormalImport(annotationImport);
1✔
518
        }
519
    }
520

521
    private AnnotationInfo getContentAnnotation(String contentType, MediaType mediaType) {
522
        ConstantValue mediaTypeConstant = getMediaTypeConstant(contentType);
4✔
523
        AnnotationInfo schemaAnnotation = getSchemaAnnotation(mediaType.getSchema());
5✔
524

525
        return new AnnotationInfo(formatInnerAnnotation("Content(mediaType = %s, schema = %s)", mediaTypeConstant.value(), schemaAnnotation.annotation()))
20✔
526
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.media.Content")
2✔
527
            .withAddedImports(mediaTypeConstant)
2✔
528
            .withAddedImports(schemaAnnotation);
1✔
529
    }
530

531
    private AnnotationInfo getSchemaAnnotation(Schema<?> schema) {
532
        AnnotationInfo schemaAnnotation = new AnnotationInfo();
4✔
533

534
        List<String> params = new ArrayList<>();
4✔
535

536
        TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
537
        schemaAnnotation = schemaAnnotation.withAddedImports(bodyType.imports());
5✔
538

539
        if (nonNull(bodyType.itemType())) {
4✔
540
            schemaAnnotation = schemaAnnotation
2✔
541
                .withAddedStaticImport("org.eclipse.microprofile.openapi.annotations.enums.SchemaType.ARRAY")
2✔
542
                .withAddedImports(bodyType.itemType().imports());
4✔
543

544
            params.add("type = ARRAY");
4✔
545
            bodyType = bodyType.itemType();
3✔
546
        }
547

548
        params.add("implementation = %s".formatted(formatClassRef(bodyType.name())));
14✔
549
        if (nonNull(schema.getDefault())) {
4✔
550
            params.add("defaultValue = \"%s\"".formatted(schema.getDefault().toString()));
13✔
551
        }
552
        if (nonBlank(bodyType.schemaFormat())) {
4✔
553
            params.add("format = \"%s\"".formatted(bodyType.schemaFormat()));
12✔
554
        }
555
        if (nonBlank(bodyType.schemaPattern())) {
4!
556
            params.add("pattern = \"%s\"".formatted(bodyType.schemaPattern()));
×
557
        }
558

559
        return schemaAnnotation.withAnnotation(formatInnerAnnotation("Schema(%s)", joinCsv(params)))
14✔
560
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.media.Schema");
1✔
561
    }
562

563
    private AnnotationInfo getHeaderAnnotation(String name, Header header) {
564
        Header realHeader = header;
2✔
565
        if (nonNull(header.get$ref())) {
4!
566
            realHeader = componentResolver.headers().getOrThrow(header.get$ref());
7✔
567
        }
568

569
        AnnotationInfo schemaAnnotation = getSchemaAnnotation(realHeader.getSchema());
5✔
570
        return new AnnotationInfo(formatInnerAnnotation("Header(name = \"%s\", description = \"%s\", schema = %s)", name, normalizeDescription(realHeader.getDescription()), schemaAnnotation.annotation()))
26✔
571
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.headers.Header")
2✔
572
            .withAddedImports(schemaAnnotation);
1✔
573
    }
574

575
    private String toParamName(String paramName) {
576
        requireNonNull(paramName);
3✔
577
        if (nonBlank(opts.pojoNameSuffix()) && paramName.endsWith(opts.pojoNameSuffix())) {
11!
578
            paramName = stripTail(paramName, opts.pojoNameSuffix().length());
7✔
579
        }
580
        paramName = paramName.replace("-", "");
5✔
581

582
        // paramName may contain array-symbol, replace with plural "s"
583
        paramName = paramName.replace("[]", "s");
5✔
584
        return uncapitalize(paramName);
3✔
585
    }
586

587
    private Optional<ApiResponse> getSuccessResponse(ApiResponses responses) {
588
        requireNonNull(responses);
3✔
589
        return responses.keySet().stream()
5✔
590
            .filter(sc -> sc.startsWith("2"))
5✔
591
            .findFirst()
3✔
592
            .map(responses::get);
4✔
593
    }
594

595
    private ConstantValue getMediaTypeConstant(String contentType) {
596
        if (standardContentTypes.containsKey(contentType)) {
4✔
597
            contentType = standardContentTypes.get(contentType);
5✔
598
            return new ConstantValue(contentType).withStaticImport("jakarta.ws.rs.core.MediaType." + contentType);
8✔
599
        } else {
600
            return new ConstantValue(quote(contentType));
6✔
601
        }
602
    }
603

604
    private boolean isSuccessfulStatusCode(int statusCode) {
605
        return statusCode >= 200 && statusCode < 300;
9!
606
    }
607
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc