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

torand / openapi2java / 18202480060

02 Oct 2025 06:44PM UTC coverage: 81.561% (-0.8%) from 82.388%
18202480060

push

github

web-flow
Merge pull request #48 from torand/refactor-info-classes

Refactor info classes

507 of 736 branches covered (68.89%)

Branch coverage included in aggregate %.

825 of 934 new or added lines in 38 files covered. (88.33%)

11 existing lines in 7 files now uncovered.

1541 of 1775 relevant lines covered (86.82%)

5.09 hits per line

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

84.27
/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.*;
20
import io.swagger.v3.oas.models.Operation;
21
import io.swagger.v3.oas.models.headers.Header;
22
import io.swagger.v3.oas.models.media.MediaType;
23
import io.swagger.v3.oas.models.media.Schema;
24
import io.swagger.v3.oas.models.parameters.Parameter;
25
import io.swagger.v3.oas.models.parameters.RequestBody;
26
import io.swagger.v3.oas.models.responses.ApiResponse;
27
import io.swagger.v3.oas.models.responses.ApiResponses;
28

29
import java.util.ArrayList;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Optional;
33

34
import static io.github.torand.javacommons.collection.CollectionHelper.nonEmpty;
35
import static io.github.torand.javacommons.lang.StringHelper.*;
36
import static io.github.torand.javacommons.stream.StreamHelper.streamSafely;
37
import static io.github.torand.openapi2java.collectors.SchemaResolver.isObjectType;
38
import static io.github.torand.openapi2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NOT_NULLABLE;
39
import static io.github.torand.openapi2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NULLABLE;
40
import static io.github.torand.openapi2java.utils.StringUtils.joinCsv;
41
import static java.lang.Boolean.TRUE;
42
import static java.util.Objects.*;
43

44
/**
45
 * Collects information about a method from an operation.
46
 */
47
public class MethodInfoCollector extends BaseCollector {
48
    private static final String APPLICATION_JSON = "application/json";
49
    private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
50
    private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
51
    private static final String MULTIPART_FORM_DATA = "multipart/form-data";
52
    private static final String TEXT_PLAIN = "text/plain";
53

54
    private static final Map<String, String> standardContentTypes = Map.of(
13✔
55
        APPLICATION_JSON, "APPLICATION_JSON",
56
        APPLICATION_OCTET_STREAM, "APPLICATION_OCTET_STREAM",
57
        APPLICATION_FORM_URLENCODED, "APPLICATION_FORM_URLENCODED",
58
        MULTIPART_FORM_DATA, "MULTIPART_FORM_DATA",
59
        TEXT_PLAIN, "TEXT_PLAIN"
60
    );
61

62
    private final ComponentResolver componentResolver;
63
    private final TypeInfoCollector typeInfoCollector;
64
    private final SecurityRequirementCollector securityRequirementCollector;
65

66
    public MethodInfoCollector(ComponentResolver componentResolver, TypeInfoCollector typeInfoCollector, Options opts) {
67
        super(opts);
3✔
68
        this.componentResolver = componentResolver;
3✔
69
        this.typeInfoCollector = typeInfoCollector;
3✔
70
        this.securityRequirementCollector = new SecurityRequirementCollector(opts);
6✔
71
    }
1✔
72

73
    public MethodInfo getMethodInfo(String verb, String path, Operation operation) {
74
        MethodInfo methodInfo = new MethodInfo(operation.getOperationId())
7✔
75
            .withAddedAnnotation(getVerbAnnotation(verb))
4✔
76
            .withAddedAnnotation(getPathAnnotation(path));
3✔
77

78
        if (TRUE.equals(operation.getDeprecated())) {
5!
NEW
79
            methodInfo = methodInfo.withDeprecationMessage(formatDeprecationMessage(operation.getExtensions()));
×
80
        }
81

82
        if (nonNull(operation.getRequestBody())) {
4✔
83
            methodInfo = methodInfo.withAddedAnnotation(getConsumesAnnotation(operation.getRequestBody()));
7✔
84
        }
85

86
        if (nonNull(operation.getResponses())) {
4!
87
            methodInfo = methodInfo.withAddedAnnotation(getProducesAnnotation(operation.getResponses()));
7✔
88
        }
89

90
        if (nonEmpty(operation.getSecurity())) {
4!
91
            SecurityRequirementInfo secReqInfo = securityRequirementCollector.getSequrityRequirementInfo(operation.getSecurity());
×
NEW
92
            if (nonNull(secReqInfo.annotation())) {
×
NEW
93
                methodInfo = methodInfo.withAddedAnnotation(secReqInfo.annotation());
×
94
            }
95
        }
96

97
        if (opts.addMpOpenApiAnnotations()) {
4!
98
            methodInfo = methodInfo.withAddedAnnotation(getOperationAnnotation(operation));
6✔
99

100
            if (nonEmpty(operation.getParameters())) {
4✔
101
                List<AnnotationInfo> parameterAnnotations = new ArrayList<>();
4✔
102
                operation.getParameters().forEach(parameter ->
6✔
103
                    parameterAnnotations.add(getParameterAnnotation(parameter))
7✔
104
                );
105
                methodInfo = methodInfo.withAddedAnnotations(parameterAnnotations);
4✔
106
            }
107

108
            if (nonEmpty(operation.getResponses())) {
4!
109
                List<AnnotationInfo> apiResponseAnnotations = new ArrayList<>();
4✔
110
                operation.getResponses().forEach((code, response) ->
6✔
111
                    apiResponseAnnotations.add(getApiResponseAnnotation(response, code))
8✔
112
                );
113
                methodInfo = methodInfo.withAddedAnnotations(apiResponseAnnotations);
4✔
114

115
                if (opts.useResteasyResponse()) {
4✔
116
                    String code = operation.getResponses().keySet().iterator().next();
7✔
117
                    ApiResponse response = operation.getResponses().get(code);
6✔
118
                    methodInfo = methodInfo.withReturnType(getResponseType(code, response));
7✔
119
                }
120
            }
121
        }
122

123
        List<MethodParamInfo> methodParams = new ArrayList<>();
4✔
124

125
        if (nonEmpty(operation.getParameters())) {
4✔
126
            operation.getParameters().forEach(param -> {
6✔
127
                Parameter realParam = param;
2✔
128
                if (nonNull(param.get$ref())) {
4!
129
                    realParam = componentResolver.parameters().getOrThrow(param.get$ref());
7✔
130
                }
131

132
                MethodParamInfo paramInfo = new MethodParamInfo()
5✔
133
                    .withNullable(!TRUE.equals(realParam.getRequired()))
9✔
134
                    .withAddedAnnotation(getMethodParameterAnnotation(realParam));
3✔
135

136
                Schema<?> realSchema = realParam.getSchema();
3✔
137
                if (isNull(realSchema)) {
3!
138
                    throw new IllegalStateException("No schema found for ApiParameter %s".formatted(realParam.getName()));
×
139
                }
140

141
                TypeInfo paramType = typeInfoCollector.getTypeInfo(realParam.getSchema(), paramInfo.nullable() ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
12✔
142
                paramInfo = paramInfo
2✔
143
                    .withType(paramType)
3✔
144
                    .withName(toParamName(realParam.getName()))
4✔
145
                    .withComment(paramType.description())
3✔
146
                    .withAddedAnnotations(paramType.annotations());
3✔
147

148
                if (TRUE.equals(realParam.getDeprecated())) {
5!
NEW
149
                    paramInfo = paramInfo.withDeprecationMessage(formatDeprecationMessage(realParam.getExtensions()));
×
150
                }
151

152
                methodParams.add(paramInfo);
4✔
153
            });
1✔
154
        }
155

156
        // Payload parameters
157
        if (nonNull(operation.getRequestBody()) && nonEmpty(operation.getRequestBody().getContent())) {
9!
158
            operation.getRequestBody().getContent().keySet().stream()
5✔
159
                .findFirst()
5✔
160
                .ifPresent(mtKey -> {
1✔
161
                    boolean isMultipart = MULTIPART_FORM_DATA.equals(mtKey);
4✔
162
                    MediaType mt = operation.getRequestBody().getContent().get(mtKey);
7✔
163
                    Schema<?> mtSchema = mt.getSchema();
3✔
164

165
                    if (nonNull(mtSchema)) {
3!
166
                        if (isMultipart) {
2✔
167
                            if (!isObjectType(mtSchema)) {
3!
168
                                throw new IllegalStateException("Multipart body should be of type 'object'");
×
169
                            }
170

171
                            if (mtSchema.getProperties().containsKey("file") && !mtSchema.getProperties().containsKey("filename")) {
10!
172
                                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.");
×
173
                            }
174

175
                            mtSchema.getProperties().forEach((propName, propSchema) -> {
7✔
176
                                MethodParamInfo paramInfo = getMultipartPayloadMethodParameter(propName, propSchema);
5✔
177
                                methodParams.add(paramInfo);
4✔
178
                            });
1✔
179
                        } else {
180
                            MethodParamInfo paramInfo = getSingularPayloadMethodParameter(mtSchema);
4✔
181
                            methodParams.add(paramInfo);
4✔
182
                        }
183
                    }
184
                });
1✔
185
        }
186

187
        return methodInfo.withAddedParameters(methodParams);
4✔
188
    }
189

190
    private AnnotationInfo getVerbAnnotation(String verb) {
191
        return new AnnotationInfo("@%s".formatted(verb), "jakarta.ws.rs.%s".formatted(verb));
20✔
192
    }
193

194
    private AnnotationInfo getPathAnnotation(String path) {
195
        return new AnnotationInfo("@Path(\"%s\")".formatted(normalizePath(path)), "jakarta.ws.rs.Path");
15✔
196
    }
197

198
    private String getResponseType(String code, ApiResponse response) {
199
        String responseType = null;
2✔
200

201
        int numericCode = Integer.parseInt(code);
3✔
202
        if (numericCode >= 200 && numericCode <= 299) {
6!
203
            if (nonEmpty(response.getContent())) {
4✔
204
                for (MediaType mediaType : response.getContent().values()) {
12✔
205
                    Schema<?> schema = mediaType.getSchema();
3✔
206
                    TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
207
                    if (nonNull(bodyType)) {
3!
208
                        String fullName = bodyType.getFullName();
3✔
209
                        if (isNull(responseType)) {
3✔
210
                            // If no return type is set yet, the type of this media type is used...
211
                            responseType = fullName;
3✔
212
                        } else if (!fullName.equals(responseType)) {
4!
213
                            // ...but if a return type is already set, and this media type specifies
214
                            // a different type, we cannot safely infer one single return type, and
215
                            // give up type safety and allow anything
NEW
216
                            responseType = opts.useKotlinSyntax() ? "*" : "?";
×
UNCOV
217
                            break; // no need to look any further
×
218
                        }
219
                    }
220
                }
1✔
221
            }
222
        }
223
        return responseType;
2✔
224
    }
225

226
    private MethodParamInfo getSingularPayloadMethodParameter(Schema<?> schema) {
227
        TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema, FORCE_NOT_NULLABLE);
6✔
228

229
        MethodParamInfo paramInfo = new MethodParamInfo(toParamName(bodyType.name()))
8✔
230
            .withNullable(false)
2✔
231
            .withType(bodyType)
2✔
232
            .withComment(bodyType.description())
3✔
233
            .withAddedAnnotations(bodyType.annotations());
3✔
234

235
        return paramInfo;
2✔
236
    }
237

238
    private MethodParamInfo getMultipartPayloadMethodParameter(String name, Schema<?> schema) {
239
        TypeInfo bodyType;
240
        String partMediaType = null;
2✔
241

242
        if ("file".equals(name)) {
4✔
243
            bodyType = new TypeInfo()
4✔
244
                .withName("File")
2✔
245
                .withAddedNormalImport("java.io.File")
2✔
246
                .withNullable(false)
6✔
247
                .withAddedAnnotation(new AnnotationInfo("@NotNull", "jakarta.validation.constraints.NotNull"))
2✔
248
                .withDescription(schema.getDescription());
3✔
249

250
            partMediaType = APPLICATION_OCTET_STREAM;
3✔
251
        } else {
252
            bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
253

254
            if (isObjectType(schema)) {
3!
255
                throw new IllegalStateException("Multipart property of type 'object' not supported. Use $ref instead.");
×
256
            }
257

258
            partMediaType = APPLICATION_JSON;
2✔
259
            if (bodyType.primitive() || (bodyType.isArray() && bodyType.itemType().primitive())) {
3!
260
                partMediaType = TEXT_PLAIN;
2✔
261
            }
262
        }
263

264
        // OpenAPI 3.1.x only
265
        if (nonBlank(schema.getContentMediaType())) {
4✔
266
            partMediaType = schema.getContentMediaType();
3✔
267
        }
268

269
        ConstantValue partMediaTypeConstant = getMediaTypeConstant(partMediaType);
4✔
270

271
        MethodParamInfo paramInfo = new MethodParamInfo(name)
5✔
272
            .withNullable(bodyType.nullable())
3✔
273
            .withType(bodyType)
2✔
274
            .withComment(bodyType.description())
11✔
275
            .withAddedAnnotation(new AnnotationInfo("@RestForm(\"%s\")".formatted(name), "org.jboss.resteasy.reactive.RestForm"))
12✔
276
            .withAddedAnnotation(new AnnotationInfo("@PartType(%s)".formatted(partMediaTypeConstant.value()), "org.jboss.resteasy.reactive.PartType"))
7✔
277
            .withAddedAnnotations(bodyType.annotations())
3✔
278
            .withAddedImports(partMediaTypeConstant);
2✔
279

280
        return paramInfo;
2✔
281
    }
282

283
    private AnnotationInfo getConsumesAnnotation(RequestBody requestBody) {
284
        List<ConstantValue> mediaTypes = new ArrayList<>();
4✔
285
        if (nonEmpty(requestBody.getContent())) {
4!
286
            streamSafely(requestBody.getContent().keySet())
6✔
287
                .map(this::getMediaTypeConstant)
3✔
288
                .forEach(mediaTypes::add);
4✔
289
        }
290

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

293
        return new AnnotationInfo("@Consumes(%s)".formatted(mediaTypesString))
13✔
294
            .withAddedNormalImport("jakarta.ws.rs.Consumes")
2✔
295
            .withAddedImports(mediaTypes);
1✔
296
    }
297

298
    private AnnotationInfo getProducesAnnotation(ApiResponses responses) {
299
        // TODO: Bør denne alltid være med?
300
        List<ConstantValue> mediaTypes = new ArrayList<>();
4✔
301
        mediaTypes.add(new ConstantValue("APPLICATION_JSON").withStaticImport("jakarta.ws.rs.core.MediaType.APPLICATION_JSON"));
9✔
302

303
        getSuccessResponse(responses).ifPresent(apiResponse -> {
7✔
304
            if (nonNull(apiResponse.getContent())) {
4✔
305
                apiResponse.getContent().keySet().stream()
5✔
306
                    .filter(mt -> !APPLICATION_JSON.equals(mt))
11✔
307
                    .map(this::getMediaTypeConstant)
3✔
308
                    .forEach(mediaTypes::add);
4✔
309
            }
310
        });
1✔
311

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

314
        return new AnnotationInfo("@Produces(%s)".formatted(mediaTypesString))
13✔
315
            .withAddedNormalImport("jakarta.ws.rs.Produces")
2✔
316
            .withAddedImports(mediaTypes);
1✔
317
    }
318

319
    private AnnotationInfo getOperationAnnotation(Operation operation) {
320
        List<String> params = new ArrayList<>();
4✔
321
        params.add("operationId = \"%s\"".formatted(operation.getOperationId()));
12✔
322
        params.add("summary = \"%s\"".formatted(operation.getSummary()));
12✔
323

324
        if (TRUE.equals(operation.getDeprecated())) {
5!
NEW
325
            params.add("deprecated = true");
×
326
        }
327

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

331
    private AnnotationInfo getParameterAnnotation(Parameter parameter) {
332
        Parameter realParameter = parameter;
2✔
333
        if (nonNull(parameter.get$ref())) {
4!
334
            realParameter = componentResolver.parameters().getOrThrow(parameter.get$ref());
7✔
335
        }
336

337
        AnnotationInfo parameterAnnotation = new AnnotationInfo();
4✔
338

339
        List<String> params = new ArrayList<>();
4✔
340

341
        ConstantValue inValue = getParameterInValue(realParameter);
4✔
342
        String inName = opts.useKotlinSyntax() ? "`in`" : "in";
8✔
343
        params.add("%s = %s".formatted(inName, inValue.value()));
16✔
344
        parameterAnnotation = parameterAnnotation.withAddedImports(inValue);
4✔
345

346
        if (inValue.value().equalsIgnoreCase("header")) {
5✔
347
            ConstantValue headerNameConstant = getHeaderNameConstant(realParameter.getName());
5✔
348
            params.add("name = %s".formatted(headerNameConstant.value()));
12✔
349
            parameterAnnotation = parameterAnnotation.withAddedImports(headerNameConstant);
4✔
350
        } else {
1✔
351
            params.add("name = \"%s\"".formatted(realParameter.getName()));
12✔
352
        }
353

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

356
        if (TRUE.equals(realParameter.getRequired())) {
5✔
357
            params.add("required = true");
4✔
358
        }
359

360
        if (nonNull(realParameter.getSchema())) {
4!
361
            AnnotationInfo schemaAnnotation = getSchemaAnnotation(realParameter.getSchema());
5✔
362
            params.add("schema = %s".formatted(schemaAnnotation.annotation()));
12✔
363
            parameterAnnotation = parameterAnnotation.withAddedImports(schemaAnnotation);
4✔
364
        }
365

366
        if (nonEmpty(realParameter.getContent())) {
4!
NEW
367
            List<AnnotationInfo> contentAnnotations = new ArrayList<>();
×
NEW
368
            realParameter.getContent().forEach((contentType, mediaType) ->
×
NEW
369
                contentAnnotations.add(getContentAnnotation(contentType, mediaType))
×
370
            );
371

NEW
372
            params.add("content = %s".formatted(formatAnnotationNamedParam(contentAnnotations.stream().map(AnnotationInfo::annotation).toList())));
×
NEW
373
            parameterAnnotation = parameterAnnotation.withAddedImports(contentAnnotations);
×
374
        }
375

376
        if (TRUE.equals(realParameter.getDeprecated())) {
5!
NEW
377
            params.add("deprecated = true");
×
378
        }
379

380
        return parameterAnnotation.withAnnotation("@Parameter(%s)".formatted(joinCsv(params)))
13✔
381
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.parameters.Parameter");
1✔
382
    }
383

384
    private ConstantValue getParameterInValue(Parameter parameter) {
385
        String inValue = switch (parameter.getIn().toLowerCase()) {
10!
386
            case "" -> "DEFAULT";
×
387
            case "header" -> "HEADER";
2✔
388
            case "query" -> "QUERY";
2✔
389
            case "path" -> "PATH";
2✔
390
            case "cookie" -> "COOKIE";
×
391
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
1✔
392
        };
393

394
        return new ConstantValue(inValue).withStaticImport("org.eclipse.microprofile.openapi.annotations.enums.ParameterIn." + inValue);
8✔
395
    }
396

397
    private AnnotationInfo getApiResponseAnnotation(ApiResponse response, String statusCode) {
398
        ApiResponse realResponse = response;
2✔
399
        if (nonNull(response.get$ref())) {
4✔
400
            realResponse = componentResolver.responses().getOrThrow(response.get$ref());
7✔
401
        }
402

403
        AnnotationInfo apiResponseAnnotation = new AnnotationInfo();
4✔
404

405
        List<String> params = new ArrayList<>();
4✔
406
        params.add("responseCode = \"%s\"".formatted(statusCode));
11✔
407
        params.add("description = \"%s\"".formatted(normalizeDescription(realResponse.getDescription())));
14✔
408

409
        if (nonEmpty(realResponse.getHeaders())) {
4✔
410
            List<AnnotationInfo> headerAnnotations = new ArrayList<>();
4✔
411
            realResponse.getHeaders().forEach((name, header) ->
6✔
412
                headerAnnotations.add(getHeaderAnnotation(name, header))
8✔
413
            );
414

415
            params.add("headers = %s".formatted(
11✔
416
                formatAnnotationNamedParam(headerAnnotations.stream().map(AnnotationInfo::annotation).toList()))
6✔
417
            );
418

419
            apiResponseAnnotation = apiResponseAnnotation.withAddedImports(headerAnnotations);
4✔
420
        }
421

422
        if (nonEmpty(realResponse.getContent())) {
4✔
423
            List<AnnotationInfo> contentAnnotations = new ArrayList<>();
4✔
424
            realResponse.getContent().forEach((contentType, mediaType) ->
6✔
425
                contentAnnotations.add(getContentAnnotation(contentType, mediaType))
8✔
426
            );
427

428
            params.add("content = %s".formatted(
11✔
429
                formatAnnotationNamedParam(contentAnnotations.stream().map(AnnotationInfo::annotation).toList()))
6✔
430
            );
431

432
            apiResponseAnnotation = apiResponseAnnotation.withAddedImports(contentAnnotations);
4✔
433
        }
434

435
        return apiResponseAnnotation
8✔
436
            .withAnnotation("@APIResponse(%s)".formatted(joinCsv(params)))
5✔
437
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.responses.APIResponse");
1✔
438
    }
439

440
    private AnnotationInfo getMethodParameterAnnotation(Parameter parameter) {
441
        String paramAnnotationName = switch (parameter.getIn().toLowerCase()) {
10!
442
            case "header" -> "HeaderParam";
2✔
443
            case "query" -> "QueryParam";
2✔
444
            case "path" -> "PathParam";
2✔
445
            case "cookie" -> "CookieParam";
×
446
            default -> throw new IllegalStateException("Parameter in-value %s not supported".formatted(parameter.getIn()));
1✔
447
        };
448

449
        final String annotationImport = "jakarta.ws.rs." + paramAnnotationName;
3✔
450

451
        if (paramAnnotationName.equals("HeaderParam")) {
4✔
452
            ConstantValue headerNameConstant = getHeaderNameConstant(parameter.getName());
5✔
453
            return new AnnotationInfo("@%s(%s)".formatted(paramAnnotationName, headerNameConstant.value()))
18✔
454
                .withAddedNormalImport(annotationImport)
2✔
455
                .withAddedImports(headerNameConstant);
1✔
456
        } else {
457
            return new AnnotationInfo("@%s(\"%s\")".formatted(paramAnnotationName, parameter.getName()))
18✔
458
                .withAddedNormalImport(annotationImport);
1✔
459
        }
460
    }
461

462
    private ConstantValue getHeaderNameConstant(String name) {
463
        String standardHeaderConstant = switch (name.toUpperCase()) {
9!
464
            case "ACCEPT-LANGUAGE" -> "ACCEPT_LANGUAGE";
2✔
465
            case "CONTENT-LANGUAGE" -> "CONTENT_LANGUAGE";
×
466
            case "LOCATION" -> "LOCATION";
×
467
            default -> null;
2✔
468
        };
469

470
        if (nonNull(standardHeaderConstant)) {
3✔
471
            return new ConstantValue(standardHeaderConstant).withStaticImport("jakarta.ws.rs.core.HttpHeaders." + standardHeaderConstant);
8✔
472
        }
473

474
        return new ConstantValue(quote(name));
6✔
475
    }
476

477
    private AnnotationInfo getContentAnnotation(String contentType, MediaType mediaType) {
478
        ConstantValue mediaTypeConstant = getMediaTypeConstant(contentType);
4✔
479
        AnnotationInfo schemaAnnotation = getSchemaAnnotation(mediaType.getSchema());
5✔
480

481
        return new AnnotationInfo(formatInnerAnnotation("Content(mediaType = %s, schema = %s)", mediaTypeConstant.value(), schemaAnnotation.annotation()))
20✔
482
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.media.Content")
2✔
483
            .withAddedImports(mediaTypeConstant)
2✔
484
            .withAddedImports(schemaAnnotation);
1✔
485
    }
486

487
    private AnnotationInfo getSchemaAnnotation(Schema<?> schema) {
488
        AnnotationInfo schemaAnnotation = new AnnotationInfo();
4✔
489

490
        List<String> params = new ArrayList<>();
4✔
491

492
        TypeInfo bodyType = typeInfoCollector.getTypeInfo(schema);
5✔
493
        schemaAnnotation = schemaAnnotation.withAddedImports(bodyType.imports());
5✔
494

495
        if (nonNull(bodyType.itemType())) {
4✔
496
            schemaAnnotation = schemaAnnotation
2✔
497
                .withAddedStaticImport("org.eclipse.microprofile.openapi.annotations.enums.SchemaType.ARRAY")
2✔
498
                .withAddedImports(bodyType.itemType().imports());
4✔
499

500
            params.add("type = ARRAY");
4✔
501
            bodyType = bodyType.itemType();
3✔
502
        }
503

504
        params.add("implementation = %s".formatted(formatClassRef(bodyType.name())));
14✔
505
        if (nonNull(schema.getDefault())) {
4✔
506
            params.add("defaultValue = \"%s\"".formatted(schema.getDefault().toString()));
13✔
507
        }
508
        if (nonBlank(bodyType.schemaFormat())) {
4✔
509
            params.add("format = \"%s\"".formatted(bodyType.schemaFormat()));
12✔
510
        }
511
        if (nonBlank(bodyType.schemaPattern())) {
4!
NEW
512
            params.add("pattern = \"%s\"".formatted(bodyType.schemaPattern()));
×
513
        }
514

515
        return schemaAnnotation.withAnnotation(formatInnerAnnotation("Schema(%s)", joinCsv(params)))
14✔
516
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.media.Schema");
1✔
517
    }
518

519
    private AnnotationInfo getHeaderAnnotation(String name, Header header) {
520
        Header realHeader = header;
2✔
521
        if (nonNull(header.get$ref())) {
4!
522
            realHeader = componentResolver.headers().getOrThrow(header.get$ref());
7✔
523
        }
524

525
        AnnotationInfo schemaAnnotation = getSchemaAnnotation(realHeader.getSchema());
5✔
526
        return new AnnotationInfo(formatInnerAnnotation("Header(name = \"%s\", description = \"%s\", schema = %s)", name, normalizeDescription(realHeader.getDescription()), schemaAnnotation.annotation()))
26✔
527
            .withAddedNormalImport("org.eclipse.microprofile.openapi.annotations.headers.Header")
2✔
528
            .withAddedImports(schemaAnnotation);
1✔
529
    }
530

531
    private String toParamName(String paramName) {
532
        requireNonNull(paramName);
3✔
533
        if (nonBlank(opts.pojoNameSuffix()) && paramName.endsWith(opts.pojoNameSuffix())) {
11!
534
            paramName = stripTail(paramName, opts.pojoNameSuffix().length());
7✔
535
        }
536
        paramName = paramName.replace("-", "");
5✔
537

538
        // paramName may contain array-symbol, replace with plural "s"
539
        paramName = paramName.replace("[]", "s");
5✔
540
        return uncapitalize(paramName);
3✔
541
    }
542

543
    private Optional<ApiResponse> getSuccessResponse(ApiResponses responses) {
544
        requireNonNull(responses);
3✔
545
        return responses.keySet().stream()
5✔
546
            .filter(sc -> sc.startsWith("2"))
5✔
547
            .findFirst()
3✔
548
            .map(responses::get);
4✔
549
    }
550

551
    private ConstantValue getMediaTypeConstant(String contentType) {
552
        if (standardContentTypes.containsKey(contentType)) {
4✔
553
            contentType = standardContentTypes.get(contentType);
5✔
554
            return new ConstantValue(contentType).withStaticImport("jakarta.ws.rs.core.MediaType." + contentType);
8✔
555
        } else {
556
            return new ConstantValue(quote(contentType));
6✔
557
        }
558
    }
559
}
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