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

torand / openapi2java / 27619408421

16 Jun 2026 01:01PM UTC coverage: 84.049% (-0.4%) from 84.496%
27619408421

push

github

torand
fix: transform id or name into valid Java/Kotlin identifier

590 of 831 branches covered (71.0%)

Branch coverage included in aggregate %.

44 of 51 new or added lines in 3 files covered. (86.27%)

1739 of 1940 relevant lines covered (89.64%)

5.33 hits per line

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

80.58
/src/main/java/io/github/torand/openapi2java/collectors/MethodInfoCollector.java
1
/*
2
 * Copyright (c) 2024-2026 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.github.torand.openapi2java.utils.OpenApi2JavaException;
26
import io.swagger.v3.oas.models.Operation;
27
import io.swagger.v3.oas.models.headers.Header;
28
import io.swagger.v3.oas.models.media.MediaType;
29
import io.swagger.v3.oas.models.media.Schema;
30
import io.swagger.v3.oas.models.parameters.Parameter;
31
import io.swagger.v3.oas.models.parameters.RequestBody;
32
import io.swagger.v3.oas.models.responses.ApiResponse;
33
import io.swagger.v3.oas.models.responses.ApiResponses;
34

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

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

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

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

75
    private static final String PARAM_IN_HEADER = "header";
76
    private static final String PARAM_IN_QUERY = "query";
77
    private static final String PARAM_IN_PATH = "path";
78
    private static final String PARAM_IN_COOKIE = "cookie";
79

80
    private final ComponentResolver componentResolver;
81
    private final TypeInfoCollector typeInfoCollector;
82
    private final SecurityRequirementCollector securityRequirementCollector;
83

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

91
    public MethodInfo getMethodInfo(String verb, String path, Operation operation) {
92
        MethodInfo methodInfo = new MethodInfo(toMethodName(operation.getOperationId()))
9✔
93
            .withAddedAnnotation(getVerbAnnotation(verb))
4✔
94
            .withAddedAnnotation(getPathAnnotation(path));
3✔
95

96
        if (TRUE.equals(operation.getDeprecated())) {
5!
97
            methodInfo = methodInfo.withDeprecationMessage(formatDeprecationMessage(operation.getExtensions()));
×
98
        }
99

100
        if (nonNull(operation.getRequestBody())) {
4✔
101
            methodInfo = methodInfo.withAddedAnnotation(getConsumesAnnotation(operation.getRequestBody()));
7✔
102
        }
103

104
        if (nonNull(operation.getResponses())) {
4!
105
            methodInfo = methodInfo.withAddedAnnotation(getProducesAnnotation(operation.getResponses()));
7✔
106
        }
107

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

115
        if (opts.addMpOpenApiAnnotations()) {
4!
116
            methodInfo = methodInfo.withAddedAnnotation(getOperationAnnotation(operation));
6✔
117

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

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

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

141
        List<MethodParamInfo> methodParams = getMethodParams(operation);
4✔
142

143
        return methodInfo.withAddedParameters(methodParams);
4✔
144
    }
145

146
    private String toMethodName(String operationId) {
147
        if (isBlank(operationId)) {
3!
NEW
148
            throw new OpenApi2JavaException("Blank operationId not allowed");
×
149
        }
150

151
        String methodName = toJavaIdentifier(operationId);
3✔
152
        if (isBlank(methodName)) {
3!
NEW
153
            throw new OpenApi2JavaException("Operation id '%s' can't be transformed into a valid %s method name".formatted(operationId, opts.useKotlinSyntax() ? "Kotlin" : "Java"));
×
154
        }
155

156
        return methodName;
2✔
157
    }
158

159
    private List<MethodParamInfo> getMethodParams(Operation operation) {
160
        List<MethodParamInfo> methodParams = new ArrayList<>();
4✔
161

162
        // Regular parameters
163
        if (nonEmpty(operation.getParameters())) {
4✔
164
            operation.getParameters().forEach(param -> {
6✔
165
                Parameter realParam = param;
2✔
166
                if (nonNull(param.get$ref())) {
4!
167
                    realParam = componentResolver.parameters().getOrThrow(param.get$ref());
7✔
168
                }
169

170
                MethodParamInfo paramInfo = new MethodParamInfo()
5✔
171
                    .withNullable(!TRUE.equals(realParam.getRequired()))
9✔
172
                    .withAddedAnnotation(getMethodParameterAnnotation(realParam));
3✔
173

174
                Schema<?> realSchema = realParam.getSchema();
3✔
175
                if (isNull(realSchema)) {
3!
176
                    throw new IllegalStateException("No schema found for ApiParameter %s".formatted(realParam.getName()));
×
177
                }
178

179
                TypeInfo paramType = typeInfoCollector.getTypeInfo(realParam.getSchema(), paramInfo.nullable() ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
12✔
180
                paramInfo = paramInfo
2✔
181
                    .withType(paramType)
3✔
182
                    .withName(toMethodParamName(realParam.getName()))
4✔
183
                    .withComment(paramType.description());
3✔
184

185
                if (TRUE.equals(realParam.getDeprecated())) {
5!
186
                    paramInfo = paramInfo.withDeprecationMessage(formatDeprecationMessage(realParam.getExtensions()));
×
187
                }
188

189
                methodParams.add(paramInfo);
4✔
190
            });
1✔
191
        }
192

193
        // Payload parameters
194
        if (nonNull(operation.getRequestBody()) && nonEmpty(operation.getRequestBody().getContent())) {
9!
195
            operation.getRequestBody().getContent().keySet().stream()
5✔
196
                .findFirst()
5✔
197
                .ifPresent(mtKey -> {
1✔
198
                    boolean isMultipart = MULTIPART_FORM_DATA.equals(mtKey);
4✔
199
                    MediaType mt = operation.getRequestBody().getContent().get(mtKey);
7✔
200
                    Schema<?> mtSchema = mt.getSchema();
3✔
201

202
                    if (nonNull(mtSchema)) {
3!
203
                        if (isMultipart) {
2✔
204
                            if (!isObjectType(mtSchema)) {
3!
205
                                throw new IllegalStateException("Multipart body should be of type 'object'");
×
206
                            }
207

208
                            if (mtSchema.getProperties().containsKey("file") && !mtSchema.getProperties().containsKey("filename")) {
10!
209
                                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.");
×
210
                            }
211

212
                            mtSchema.getProperties().forEach((propName, propSchema) -> {
7✔
213
                                MethodParamInfo paramInfo = getMultipartPayloadMethodParameter(propName, propSchema);
5✔
214
                                methodParams.add(paramInfo);
4✔
215
                            });
1✔
216
                        } else {
217
                            MethodParamInfo paramInfo = getSingularPayloadMethodParameter(mtSchema);
4✔
218
                            methodParams.add(paramInfo);
4✔
219
                        }
220
                    }
221
                });
1✔
222
        }
223

224
        return methodParams;
2✔
225
    }
226

227
    private AnnotationInfo getVerbAnnotation(String verb) {
228
        return new AnnotationInfo("@%s".formatted(verb), "jakarta.ws.rs.%s".formatted(verb));
20✔
229
    }
230

231
    private AnnotationInfo getPathAnnotation(String path) {
232
        return new AnnotationInfo("@Path(\"%s\")".formatted(normalizePath(path)), "jakarta.ws.rs.Path");
15✔
233
    }
234

235
    private String getResponseType(String code, ApiResponse response) {
236
        String responseType = null;
2✔
237

238
        int numericCode = Integer.parseInt(code);
3✔
239
        if (isSuccessfulStatusCode(numericCode) && nonEmpty(response.getContent())) {
8!
240
            for (MediaType mediaType : response.getContent().values()) {
12✔
241
                Schema<?> schema = mediaType.getSchema();
3✔
242
                TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
243
                if (nonNull(bodyType)) {
3!
244
                    String fullName = bodyType.getFullName();
3✔
245
                    if (isNull(responseType)) {
3✔
246
                        // If no return type is set yet, the type of this media type is used...
247
                        responseType = fullName;
3✔
248
                    } else if (!fullName.equals(responseType)) {
4!
249
                        // ...but if a return type is already set, and this media type specifies
250
                        // a different type, we cannot safely infer one single return type, and
251
                        // give up type safety and allow anything
252
                        responseType = opts.useKotlinSyntax() ? "*" : "?";
×
253
                        break; // no need to look any further
×
254
                    }
255
                }
256
            }
1✔
257
        }
258

259
        return responseType;
2✔
260
    }
261

262
    private MethodParamInfo getSingularPayloadMethodParameter(Schema<?> schema) {
263
        TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema, FORCE_NOT_NULLABLE);
6✔
264

265
        return new MethodParamInfo(toMethodParamName(bodyType.name()))
9✔
266
            .withNullable(false)
2✔
267
            .withType(bodyType)
2✔
268
            .withComment(bodyType.description());
2✔
269
    }
270

271
    private MethodParamInfo getMultipartPayloadMethodParameter(String name, Schema<?> schema) {
272
        TypeInfo bodyType;
273
        String partMediaType = null;
2✔
274

275
        if ("file".equals(name)) {
4✔
276
            bodyType = new TypeInfo()
4✔
277
                .withName("File")
2✔
278
                .withAddedNormalImport("java.io.File")
2✔
279
                .withNullable(false)
6✔
280
                .withAddedAnnotation(new AnnotationInfo("@NotNull", "jakarta.validation.constraints.NotNull"))
2✔
281
                .withDescription(schema.getDescription());
3✔
282

283
            partMediaType = APPLICATION_OCTET_STREAM;
3✔
284
        } else {
285
            bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
286

287
            if (isObjectType(schema)) {
3!
288
                throw new IllegalStateException("Multipart property of type 'object' not supported. Use $ref instead.");
×
289
            }
290

291
            partMediaType = APPLICATION_JSON;
2✔
292
            if (bodyType.primitive() || (bodyType.isArray() && bodyType.itemType().primitive())) {
3!
293
                partMediaType = TEXT_PLAIN;
2✔
294
            }
295
        }
296

297
        // OpenAPI 3.1.x only
298
        if (nonBlank(schema.getContentMediaType())) {
4✔
299
            partMediaType = schema.getContentMediaType();
3✔
300
        }
301

302
        ConstantValue partMediaTypeConstant = getMediaTypeConstant(partMediaType);
4✔
303

304
        return new MethodParamInfo(name)
6✔
305
            .withNullable(bodyType.nullable())
3✔
306
            .withType(bodyType)
2✔
307
            .withComment(bodyType.description())
11✔
308
            .withAddedAnnotation(new AnnotationInfo("@RestForm(\"%s\")".formatted(name), "org.jboss.resteasy.reactive.RestForm"))
12✔
309
            .withAddedAnnotation(new AnnotationInfo("@PartType(%s)".formatted(partMediaTypeConstant.value()), "org.jboss.resteasy.reactive.PartType"))
7✔
310
            .withAddedImports(partMediaTypeConstant);
1✔
311
    }
312

313
    private AnnotationInfo getConsumesAnnotation(RequestBody requestBody) {
314
        List<ConstantValue> mediaTypes = new ArrayList<>();
4✔
315
        if (nonEmpty(requestBody.getContent())) {
4!
316
            streamSafely(requestBody.getContent().keySet())
6✔
317
                .map(this::getMediaTypeConstant)
3✔
318
                .forEach(mediaTypes::add);
4✔
319
        }
320

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

323
        return new AnnotationInfo("@Consumes(%s)".formatted(mediaTypesString))
13✔
324
            .withAddedNormalImport("jakarta.ws.rs.Consumes")
2✔
325
            .withAddedImports(mediaTypes);
1✔
326
    }
327

328
    private AnnotationInfo getProducesAnnotation(ApiResponses responses) {
329
        List<ConstantValue> mediaTypes = new ArrayList<>();
4✔
330
        mediaTypes.add(new ConstantValue("APPLICATION_JSON").withStaticImport("jakarta.ws.rs.core.MediaType.APPLICATION_JSON"));
9✔
331

332
        getSuccessResponse(responses).ifPresent(apiResponse -> {
7✔
333
            if (nonNull(apiResponse.getContent())) {
4✔
334
                apiResponse.getContent().keySet().stream()
5✔
335
                    .filter(mt -> !APPLICATION_JSON.equals(mt))
11✔
336
                    .map(this::getMediaTypeConstant)
3✔
337
                    .forEach(mediaTypes::add);
4✔
338
            }
339
        });
1✔
340

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

343
        return new AnnotationInfo("@Produces(%s)".formatted(mediaTypesString))
13✔
344
            .withAddedNormalImport("jakarta.ws.rs.Produces")
2✔
345
            .withAddedImports(mediaTypes);
1✔
346
    }
347

348
    private AnnotationInfo getOperationAnnotation(Operation operation) {
349
        List<String> params = new ArrayList<>();
4✔
350
        params.add("operationId = \"%s\"".formatted(operation.getOperationId()));
12✔
351
        params.add("summary = \"%s\"".formatted(operation.getSummary()));
12✔
352

353
        if (TRUE.equals(operation.getDeprecated())) {
5!
354
            params.add("deprecated = true");
×
355
        }
356

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

360
    private AnnotationInfo getParameterAnnotation(Parameter parameter) {
361
        Parameter realParameter = parameter;
2✔
362
        if (nonNull(parameter.get$ref())) {
4!
363
            realParameter = componentResolver.parameters().getOrThrow(parameter.get$ref());
7✔
364
        }
365

366
        AnnotationInfo parameterAnnotation = new AnnotationInfo();
4✔
367

368
        List<String> params = new ArrayList<>();
4✔
369

370
        ConstantValue inValue = getParameterInValue(realParameter);
4✔
371
        String inName = opts.useKotlinSyntax() ? "`in`" : "in";
8✔
372
        params.add("%s = %s".formatted(inName, inValue.value()));
16✔
373
        parameterAnnotation = parameterAnnotation.withAddedImports(inValue);
4✔
374

375
        if (inValue.value().equalsIgnoreCase(PARAM_IN_HEADER)) {
5✔
376
            ConstantValue headerNameConstant = getHeaderNameConstant(realParameter.getName());
5✔
377
            params.add("name = %s".formatted(headerNameConstant.value()));
12✔
378
            parameterAnnotation = parameterAnnotation.withAddedImports(headerNameConstant);
4✔
379
        } else {
1✔
380
            params.add("name = \"%s\"".formatted(realParameter.getName()));
12✔
381
        }
382

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

385
        if (TRUE.equals(realParameter.getRequired())) {
5✔
386
            params.add("required = true");
4✔
387
        }
388

389
        if (nonNull(realParameter.getSchema())) {
4!
390
            AnnotationInfo schemaAnnotation = getSchemaAnnotation(realParameter.getSchema());
5✔
391
            params.add("schema = %s".formatted(schemaAnnotation.annotation()));
12✔
392
            parameterAnnotation = parameterAnnotation.withAddedImports(schemaAnnotation);
4✔
393
        }
394

395
        if (nonEmpty(realParameter.getContent())) {
4!
396
            List<AnnotationInfo> contentAnnotations = new ArrayList<>();
×
397
            realParameter.getContent().forEach((contentType, mediaType) ->
×
398
                contentAnnotations.add(getContentAnnotation(contentType, mediaType))
×
399
            );
400

401
            params.add("content = %s".formatted(formatAnnotationNamedParam(contentAnnotations.stream().map(AnnotationInfo::annotation).toList())));
×
402
            parameterAnnotation = parameterAnnotation.withAddedImports(contentAnnotations);
×
403
        }
404

405
        if (nonNull(realParameter.getStyle())) {
4!
406
            Parameter.StyleEnum defaultStyle = getDefaultParameterStyle(realParameter);
4✔
407
            if (!realParameter.getStyle().equals(defaultStyle)) {
5✔
408
                ConstantValue parameterStyle = getParameterStyle(realParameter);
4✔
409
                params.add("style = %s".formatted(parameterStyle.value()));
12✔
410
                parameterAnnotation = parameterAnnotation.withAddedImports(parameterStyle);
4✔
411
            }
412
        }
413

414
        if (nonNull(realParameter.getExplode())) {
4!
415
            boolean defaultValue = Parameter.StyleEnum.FORM.equals(realParameter.getStyle());
5✔
416
            if (!realParameter.getExplode().equals(defaultValue)) {
6✔
417
                ConstantValue parameterExplode = getParameterExplode(realParameter);
4✔
418
                params.add("explode = %s".formatted(parameterExplode.value()));
12✔
419
                parameterAnnotation = parameterAnnotation.withAddedImports(parameterExplode);
4✔
420
            }
421
        }
422
        if (TRUE.equals(realParameter.getDeprecated())) {
5!
423
            params.add("deprecated = true");
×
424
        }
425

426
        return parameterAnnotation.withAnnotation("@Parameter(%s)".formatted(joinCsv(params)))
13✔
427
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.parameters.Parameter");
1✔
428
    }
429

430
    private Parameter.StyleEnum getDefaultParameterStyle(Parameter parameter) {
431
        return switch(parameter.getIn()) {
10!
432
            case PARAM_IN_HEADER -> Parameter.StyleEnum.SIMPLE;
2✔
433
            case PARAM_IN_QUERY -> Parameter.StyleEnum.FORM;
2✔
434
            case PARAM_IN_PATH -> Parameter.StyleEnum.SIMPLE;
2✔
435
            case PARAM_IN_COOKIE -> Parameter.StyleEnum.FORM;
×
436
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
×
437
        };
438
    }
439

440
    private ConstantValue getParameterStyle(Parameter parameter) {
441
        String style = switch (parameter.getStyle()) {
6!
442
            case MATRIX -> "MATRIX";
×
443
            case LABEL -> "LABEL";
×
444
            case FORM -> "FORM";
×
445
            case SIMPLE -> "SIMPLE";
2✔
446
            case SPACEDELIMITED -> "SPACEDELIMITED";
×
447
            case PIPEDELIMITED -> "PIPEDELIMITED";
×
448
            case DEEPOBJECT -> "DEEPOBJECT";
1✔
449
        };
450

451
        return new ConstantValue(style).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.ParameterStyle." + style);
8✔
452
    }
453

454
    private ConstantValue getParameterExplode(Parameter parameter) {
455
        String explode = TRUE.equals(parameter.getExplode()) ? "TRUE" : "FALSE";
8!
456
        return new ConstantValue(explode).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.Explode." + explode);
8✔
457
    }
458

459
    private ConstantValue getParameterInValue(Parameter parameter) {
460
        String inValue = switch (parameter.getIn().toLowerCase()) {
10!
461
            case "" -> "DEFAULT";
×
462
            case PARAM_IN_HEADER -> "HEADER";
2✔
463
            case PARAM_IN_QUERY -> "QUERY";
2✔
464
            case PARAM_IN_PATH -> "PATH";
2✔
465
            case PARAM_IN_COOKIE -> "COOKIE";
×
466
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
1✔
467
        };
468

469
        return new ConstantValue(inValue).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.ParameterIn." + inValue);
8✔
470
    }
471

472
    private AnnotationInfo getApiResponseAnnotation(ApiResponse response, String statusCode) {
473
        ApiResponse realResponse = response;
2✔
474
        if (nonNull(response.get$ref())) {
4✔
475
            realResponse = componentResolver.responses().getOrThrow(response.get$ref());
7✔
476
        }
477

478
        AnnotationInfo apiResponseAnnotation = new AnnotationInfo();
4✔
479

480
        List<String> params = new ArrayList<>();
4✔
481
        params.add("responseCode = \"%s\"".formatted(statusCode));
11✔
482
        params.add("description = \"%s\"".formatted(normalizeDescription(realResponse.getDescription())));
14✔
483

484
        if (nonEmpty(realResponse.getHeaders())) {
4✔
485
            List<AnnotationInfo> headerAnnotations = new ArrayList<>();
4✔
486
            realResponse.getHeaders().forEach((name, header) ->
6✔
487
                headerAnnotations.add(getHeaderAnnotation(name, header))
8✔
488
            );
489

490
            params.add("headers = %s".formatted(
11✔
491
                formatAnnotationNamedParam(headerAnnotations.stream().map(AnnotationInfo::annotation).toList()))
6✔
492
            );
493

494
            apiResponseAnnotation = apiResponseAnnotation.withAddedImports(headerAnnotations);
4✔
495
        }
496

497
        if (nonEmpty(realResponse.getContent())) {
4✔
498
            List<AnnotationInfo> contentAnnotations = new ArrayList<>();
4✔
499
            realResponse.getContent().forEach((contentType, mediaType) ->
6✔
500
                contentAnnotations.add(getContentAnnotation(contentType, mediaType))
8✔
501
            );
502

503
            params.add("content = %s".formatted(
11✔
504
                formatAnnotationNamedParam(contentAnnotations.stream().map(AnnotationInfo::annotation).toList()))
6✔
505
            );
506

507
            apiResponseAnnotation = apiResponseAnnotation.withAddedImports(contentAnnotations);
4✔
508
        }
509

510
        return apiResponseAnnotation
8✔
511
            .withAnnotation("@APIResponse(%s)".formatted(joinCsv(params)))
5✔
512
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.responses.APIResponse");
1✔
513
    }
514

515
    private AnnotationInfo getMethodParameterAnnotation(Parameter parameter) {
516
        String paramAnnotationName = switch (parameter.getIn().toLowerCase()) {
10!
517
            case PARAM_IN_HEADER -> "HeaderParam";
2✔
518
            case PARAM_IN_QUERY -> "QueryParam";
2✔
519
            case PARAM_IN_PATH -> "PathParam";
2✔
520
            case PARAM_IN_COOKIE -> "CookieParam";
×
521
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
1✔
522
        };
523

524
        final String annotationImport = "jakarta.ws.rs." + paramAnnotationName;
3✔
525

526
        if (paramAnnotationName.equals("HeaderParam")) {
4✔
527
            ConstantValue headerNameConstant = getHeaderNameConstant(parameter.getName());
5✔
528
            return new AnnotationInfo("@%s(%s)".formatted(paramAnnotationName, headerNameConstant.value()))
18✔
529
                .withAddedNormalImport(annotationImport)
2✔
530
                .withAddedImports(headerNameConstant);
1✔
531
        } else {
532
            return new AnnotationInfo("@%s(\"%s\")".formatted(paramAnnotationName, parameter.getName()))
18✔
533
                .withAddedNormalImport(annotationImport);
1✔
534
        }
535
    }
536

537
    private AnnotationInfo getContentAnnotation(String contentType, MediaType mediaType) {
538
        ConstantValue mediaTypeConstant = getMediaTypeConstant(contentType);
4✔
539
        AnnotationInfo schemaAnnotation = getSchemaAnnotation(mediaType.getSchema());
5✔
540

541
        return new AnnotationInfo(formatInnerAnnotation("Content(mediaType = %s, schema = %s)", mediaTypeConstant.value(), schemaAnnotation.annotation()))
20✔
542
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.media.Content")
2✔
543
            .withAddedImports(mediaTypeConstant)
2✔
544
            .withAddedImports(schemaAnnotation);
1✔
545
    }
546

547
    private AnnotationInfo getSchemaAnnotation(Schema<?> schema) {
548
        AnnotationInfo schemaAnnotation = new AnnotationInfo();
4✔
549

550
        List<String> params = new ArrayList<>();
4✔
551

552
        TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
553
        schemaAnnotation = schemaAnnotation.withAddedImports(bodyType.imports());
5✔
554

555
        if (nonNull(bodyType.itemType())) {
4✔
556
            schemaAnnotation = schemaAnnotation
2✔
557
                .withAddedStaticImport("org.eclipse.microprofile.openapi.annotations.enums.SchemaType.ARRAY")
2✔
558
                .withAddedImports(bodyType.itemType().imports());
4✔
559

560
            params.add("type = ARRAY");
4✔
561
            bodyType = bodyType.itemType();
3✔
562
        }
563

564
        params.add("implementation = %s".formatted(formatClassRef(bodyType.name())));
14✔
565
        if (nonNull(schema.getDefault())) {
4✔
566
            params.add("defaultValue = \"%s\"".formatted(schema.getDefault().toString()));
13✔
567
        }
568
        if (nonBlank(bodyType.schemaFormat())) {
4✔
569
            params.add("format = \"%s\"".formatted(bodyType.schemaFormat()));
12✔
570
        }
571
        if (nonBlank(bodyType.schemaPattern())) {
4!
572
            params.add("pattern = \"%s\"".formatted(escape(bodyType.schemaPattern())));
×
573
        }
574

575
        return schemaAnnotation.withAnnotation(formatInnerAnnotation("Schema(%s)", joinCsv(params)))
14✔
576
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.media.Schema");
1✔
577
    }
578

579
    private AnnotationInfo getHeaderAnnotation(String name, Header header) {
580
        Header realHeader = header;
2✔
581
        if (nonNull(header.get$ref())) {
4!
582
            realHeader = componentResolver.headers().getOrThrow(header.get$ref());
7✔
583
        }
584

585
        AnnotationInfo schemaAnnotation = getSchemaAnnotation(realHeader.getSchema());
5✔
586
        return new AnnotationInfo(formatInnerAnnotation("Header(name = \"%s\", description = \"%s\", schema = %s)", name, normalizeDescription(realHeader.getDescription()), schemaAnnotation.annotation()))
26✔
587
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.headers.Header")
2✔
588
            .withAddedImports(schemaAnnotation);
1✔
589
    }
590

591
    private String toMethodParamName(String paramName) {
592
        if (isBlank(paramName)) {
3!
NEW
593
            throw new OpenApi2JavaException("Blank parameter name not allowed");
×
594
        }
595

596
        String methodParamName = paramName;
2✔
597
        if (nonBlank(opts.pojoNameSuffix()) && methodParamName.endsWith(opts.pojoNameSuffix())) {
11!
598
            methodParamName = stripTail(methodParamName, opts.pojoNameSuffix().length());
7✔
599
        }
600

601
        // paramName may contain array-symbol, replace with plural "s"
602
        methodParamName = methodParamName.replace("[]", "s");
5✔
603

604
        methodParamName = toJavaIdentifier(methodParamName);
3✔
605
        if (isBlank(methodParamName)) {
3!
NEW
606
            throw new OpenApi2JavaException("Parameter '%s' can't be transformed into a valid %s method parameter name".formatted(paramName, opts.useKotlinSyntax() ? "Kotlin" : "Java"));
×
607
        }
608

609
        return methodParamName;
2✔
610
    }
611

612
    private Optional<ApiResponse> getSuccessResponse(ApiResponses responses) {
613
        requireNonNull(responses);
3✔
614
        return responses.keySet().stream()
5✔
615
            .filter(sc -> sc.startsWith("2"))
5✔
616
            .findFirst()
3✔
617
            .map(responses::get);
4✔
618
    }
619

620
    private ConstantValue getMediaTypeConstant(String contentType) {
621
        if (standardContentTypes.containsKey(contentType)) {
4✔
622
            contentType = standardContentTypes.get(contentType);
5✔
623
            return new ConstantValue(contentType).withStaticImport("jakarta.ws.rs.core.MediaType." + contentType);
8✔
624
        } else {
625
            return new ConstantValue(quote(contentType));
6✔
626
        }
627
    }
628

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

© 2026 Coveralls, Inc