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

torand / openapi2java / 22519240185

28 Feb 2026 10:47AM UTC coverage: 84.535% (+0.1%) from 84.425%
22519240185

push

github

torand
chore: prepare release

572 of 795 branches covered (71.95%)

Branch coverage included in aggregate %.

1702 of 1895 relevant lines covered (89.82%)

5.39 hits per line

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

89.08
/src/main/java/io/github/torand/openapi2java/collectors/TypeInfoCollector.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.TypeInfo;
21
import io.swagger.v3.oas.models.media.Schema;
22
import io.swagger.v3.oas.models.media.StringSchema;
23

24
import java.util.ArrayList;
25
import java.util.List;
26
import java.util.Optional;
27

28
import static io.github.torand.javacommons.collection.CollectionHelper.isEmpty;
29
import static io.github.torand.javacommons.collection.CollectionHelper.nonEmpty;
30
import static io.github.torand.javacommons.lang.Exceptions.illegalStateException;
31
import static io.github.torand.javacommons.lang.StringHelper.nonBlank;
32
import static io.github.torand.javacommons.stream.StreamHelper.streamSafely;
33
import static io.github.torand.openapi2java.collectors.Extensions.EXT_JSON_DESERIALIZER;
34
import static io.github.torand.openapi2java.collectors.Extensions.EXT_JSON_FORMAT;
35
import static io.github.torand.openapi2java.collectors.Extensions.EXT_JSON_SERIALIZER;
36
import static io.github.torand.openapi2java.collectors.Extensions.EXT_NULLABLE;
37
import static io.github.torand.openapi2java.collectors.Extensions.EXT_VALIDATION_CONSTRAINT;
38
import static io.github.torand.openapi2java.collectors.Extensions.extensions;
39
import static io.github.torand.openapi2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NOT_NULLABLE;
40
import static io.github.torand.openapi2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NULLABLE;
41
import static io.github.torand.openapi2java.utils.PackageUtils.getClassNameFromFqn;
42
import static io.github.torand.openapi2java.utils.StringUtils.joinCsv;
43
import static java.lang.Boolean.TRUE;
44
import static java.util.Objects.nonNull;
45
import static java.util.function.Predicate.not;
46

47
/**
48
 * Collects information about a type from a schema.
49
 */
50
public class TypeInfoCollector extends BaseCollector {
51
    public enum NullabilityResolution {FROM_SCHEMA, FORCE_NULLABLE, FORCE_NOT_NULLABLE}
21✔
52

53
    private final SchemaResolver schemaResolver;
54

55
    public TypeInfoCollector(SchemaResolver schemaResolver, Options opts) {
56
        super(opts);
3✔
57
        this.schemaResolver = schemaResolver;
3✔
58
    }
1✔
59

60
    public <T> TypeInfo getTypeInfo(Schema<T> schema) {
61
        return getTypeInfo(schema, NullabilityResolution.FROM_SCHEMA);
5✔
62
    }
63

64
    public TypeInfo getTypeInfo(Schema<?> schema, NullabilityResolution nullabilityResolution) {
65
        if (isEmpty(schema.getTypes())) {
4✔
66

67
            boolean nullable = isNullable(schema, nullabilityResolution);
5✔
68

69
            if (nonNull(schema.getAnyOf())) {
4!
70
                throw new IllegalStateException("Schema 'anyOf' not supported");
×
71
            }
72

73
            if (nonNull(schema.getOneOf())) {
4✔
74
                // Limited support for 'oneOf' in properties: use the first non-nullable subschema
75
                List<Schema<?>> oneOfSchemas = (List<Schema<?>>)(Object)schema.getOneOf();
4✔
76
                Schema<?> subSchema = getNonNullableSubSchema(oneOfSchemas)
6✔
77
                    .orElseThrow(illegalStateException("Schema 'oneOf' must contain a non-nullable sub-schema"));
4✔
78

79
                return getTypeInfo(subSchema, nullable ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
8!
80
            }
81

82
            if (nonNull(schema.getAllOf()) && schema.getAllOf().size() == 1) {
4!
83
                // 'allOf' only supported if it contains single type
84
                return getTypeInfo(schema.getAllOf().get(0), nullable ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
×
85
            }
86

87
            String ref = schema.get$ref();
3✔
88
            if (nonBlank(ref)) {
3!
89
                TypeInfo typeInfo;
90

91
                if (schemaResolver.isPrimitiveType(ref) || schemaResolver.isArrayType(ref)) {
10!
92
                    Schema<?> refSchema = schemaResolver.getOrThrow(ref);
5✔
93
                    typeInfo = getTypeInfo(refSchema, nullable ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
9✔
94
                } else {
1✔
95
                    typeInfo = new TypeInfo()
6✔
96
                        .withName(schemaResolver.getTypeName(ref) + opts.pojoNameSuffix())
7✔
97
                        .withNullable(nullable);
2✔
98

99
                    String modelSubpackage = schemaResolver.getModelSubpackage(ref).orElse(null);
8✔
100
                    typeInfo = typeInfo.withAddedNormalImport(opts.getModelPackage(modelSubpackage) + "." + typeInfo.name());
10✔
101
                    if (!schemaResolver.isEnumType(schema.get$ref())) {
6✔
102
                        AnnotationInfo validAnnotation = getValidAnnotation();
3✔
103
                        typeInfo = typeInfo.withAddedAnnotation(validAnnotation);
4✔
104
                    }
105
                    if (!nullable) {
2✔
106
                        AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
107
                        typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
108
                    }
109
                }
110

111
                if (nonBlank(schema.getDescription())) {
4✔
112
                    typeInfo = typeInfo.withDescription(schema.getDescription());
5✔
113
                }
114

115
                return typeInfo;
2✔
116
            } else if (isEmpty(schema.getAllOf())) {
×
117
                throw new IllegalStateException("No types, no ref: %s".formatted(schema.toString()));
×
118
            }
119
        }
120

121
        return getJsonType(schema, nullabilityResolution);
5✔
122
    }
123

124
    public Optional<Schema<?>> getNonNullableSubSchema(List<Schema<?>> subSchemas) {
125
        return subSchemas.stream()
5✔
126
            .filter(not(this::isNullable))
2✔
127
            .findFirst();
1✔
128
    }
129

130
    private TypeInfo getJsonType(Schema<?> schema, NullabilityResolution nullabilityResolution) {
131
        TypeInfo typeInfo = new TypeInfo()
4✔
132
            .withDescription(schema.getDescription())
3✔
133
            .withPrimitive(true)
4✔
134
            .withNullable(isNullable(schema, nullabilityResolution));
3✔
135

136
        String jsonType = streamSafely(schema.getTypes())
5✔
137
            .filter(not("null"::equals))
2✔
138
            .findFirst()
7✔
139
            .orElseThrow(illegalStateException("Unexpected types: %s", schema.toString()));
6✔
140

141
        if ("string".equals(jsonType)) {
4✔
142
            typeInfo = populateJsonStringType(typeInfo, schema);
6✔
143
        } else if ("number".equals(jsonType)) {
4✔
144
            typeInfo = populateJsonNumberType(typeInfo, schema);
6✔
145
        } else if ("integer".equals(jsonType)) {
4✔
146
            typeInfo = populateJsonIntegerType(typeInfo, schema);
6✔
147
        } else if ("boolean".equals(jsonType)) {
4✔
148
            typeInfo = populateJsonBooleanType(typeInfo);
5✔
149
        } else if ("array".equals(jsonType)) {
4✔
150
            typeInfo = populateJsonArrayType(typeInfo, schema);
6✔
151
        } else if ("object".equals(jsonType) && isEmpty(schema.getProperties()) && schema.getAdditionalProperties() instanceof Schema) {
12!
152
            typeInfo = populateJsonMapType(typeInfo, schema);
6✔
153
        } else {
154
            // Schema not expected to be defined "inline" using type 'object'
155
            throw new IllegalStateException("Unexpected schema: %s".formatted(schema.toString()));
×
156
        }
157

158
        Optional<String> maybeJsonSerializer = extensions(schema.getExtensions()).getString(EXT_JSON_SERIALIZER);
6✔
159
        if (maybeJsonSerializer.isPresent()) {
3✔
160
            AnnotationInfo jsonSerializeAnnotation = getJsonSerializeAnnotation(maybeJsonSerializer.get());
6✔
161
            typeInfo = typeInfo.withAddedAnnotation(jsonSerializeAnnotation);
4✔
162
        }
163

164
        Optional<String> maybeJsonDeserializer = extensions(schema.getExtensions()).getString(EXT_JSON_DESERIALIZER);
6✔
165
        if (maybeJsonDeserializer.isPresent()) {
3✔
166
            AnnotationInfo jsonDeserializeAnnotation = getJsonDeserializeAnnotation(maybeJsonDeserializer.get());
6✔
167
            typeInfo = typeInfo.withAddedAnnotation(jsonDeserializeAnnotation);
4✔
168
        }
169

170
        Optional<String> maybeValidationConstraint = extensions(schema.getExtensions()).getString(EXT_VALIDATION_CONSTRAINT);
6✔
171
        if (maybeValidationConstraint.isPresent()) {
3✔
172
            AnnotationInfo validationConstraintAnnotation = new AnnotationInfo(
8✔
173
                "@%s".formatted(getClassNameFromFqn(maybeValidationConstraint.get())),
6✔
174
                maybeValidationConstraint.get()
4✔
175
            );
176
            typeInfo = typeInfo.withAddedAnnotation(validationConstraintAnnotation);
4✔
177
        }
178

179
        return typeInfo;
2✔
180
    }
181

182
    private TypeInfo populateJsonStringType(TypeInfo typeInfo, Schema<?> schema) {
183
        if ("uri".equals(schema.getFormat())) {
5✔
184
            typeInfo = typeInfo.withName("URI")
4✔
185
                .withSchemaFormat(schema.getFormat())
3✔
186
                .withAddedNormalImport("java.net.URI");
2✔
187
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
188
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
189
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
190
            }
1✔
191
        } else if ("uuid".equals(schema.getFormat())) {
5✔
192
            typeInfo = typeInfo.withName("UUID")
4✔
193
                .withSchemaFormat(schema.getFormat())
3✔
194
                .withAddedNormalImport("java.util.UUID");
2✔
195
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
196
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
197
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
198
            }
1✔
199
        } else if ("duration".equals(schema.getFormat())) {
5✔
200
            typeInfo = typeInfo.withName(getClassNameFromFqn(opts.durationClassName()))
7✔
201
                .withSchemaFormat(schema.getFormat())
4✔
202
                .withAddedNormalImport(opts.durationClassName());
3✔
203
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
204
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
205
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
206
            }
1✔
207
        } else if ("date".equals(schema.getFormat())) {
5✔
208
            typeInfo = typeInfo.withName(getClassNameFromFqn(opts.dateClassName()))
7✔
209
                .withSchemaFormat(schema.getFormat())
4✔
210
                .withAddedNormalImport(opts.dateClassName());
3✔
211
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
212
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
213
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
214
            }
215
            Optional<String> maybeJsonFormat = extensions(schema.getExtensions()).getString(EXT_JSON_FORMAT);
6✔
216
            if (maybeJsonFormat.isPresent()) {
3✔
217
                AnnotationInfo jsonFormatAnnotation = getJsonFormatAnnotation(maybeJsonFormat.get());
6✔
218
                typeInfo = typeInfo.withAddedAnnotation(jsonFormatAnnotation);
4✔
219
            }
220
        } else if ("date-time".equals(schema.getFormat())) {
6✔
221
            typeInfo = typeInfo.withName(getClassNameFromFqn(opts.dateTimeClassName()))
7✔
222
                .withSchemaFormat(schema.getFormat())
4✔
223
                .withAddedNormalImport(opts.dateTimeClassName());
3✔
224
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
225
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
226
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
227
            }
228
            Optional<String> maybeJsonFormat = extensions(schema.getExtensions()).getString(EXT_JSON_FORMAT);
6✔
229
            if (maybeJsonFormat.isPresent()) {
3✔
230
                AnnotationInfo jsonFormatAnnotation = getJsonFormatAnnotation(maybeJsonFormat.get());
6✔
231
                typeInfo = typeInfo.withAddedAnnotation(jsonFormatAnnotation);
4✔
232
            }
233
        } else if ("email".equals(schema.getFormat())) {
6✔
234
            typeInfo = typeInfo.withName("String")
4✔
235
                .withSchemaFormat(schema.getFormat());
3✔
236
            if (opts.addJakartaBeanValidationAnnotations()) {
4!
237
                if (!typeInfo.nullable()) {
3✔
238
                    AnnotationInfo notBlankAnnotation = getNotBlankAnnotation();
3✔
239
                    typeInfo = typeInfo.withAddedAnnotation(notBlankAnnotation);
4✔
240
                }
241
                AnnotationInfo emailAnnotation = getEmailAnnotation();
3✔
242
                typeInfo = typeInfo.withAddedAnnotation(emailAnnotation);
4✔
243
            }
1✔
244
        } else if ("binary".equals(schema.getFormat())) {
5✔
245
            typeInfo = typeInfo.withName("byte[]")
4✔
246
                .withSchemaFormat(schema.getFormat());
3✔
247
            if (opts.addJakartaBeanValidationAnnotations()) {
4!
248
                if (!typeInfo.nullable()) {
3!
249
                    AnnotationInfo notEmptyAnnotation = getNotEmptyAnnotation();
3✔
250
                    typeInfo = typeInfo.withAddedAnnotation(notEmptyAnnotation);
4✔
251
                }
252
                if (nonNull(schema.getMinItems()) || nonNull(schema.getMaxItems())) {
8!
253
                    AnnotationInfo sizeAnnotaion = getArraySizeAnnotation(schema);
×
254
                    typeInfo = typeInfo.withAddedAnnotation(sizeAnnotaion);
×
255
                }
×
256
            }
257
        } else {
258
            typeInfo = typeInfo.withName("String")
4✔
259
                .withSchemaFormat(schema.getFormat());
3✔
260
            if (opts.addJakartaBeanValidationAnnotations()) {
4!
261
                if (!typeInfo.nullable()) {
3✔
262
                    AnnotationInfo notBlankAnnotation = getNotBlankAnnotation();
3✔
263
                    typeInfo = typeInfo.withAddedAnnotation(notBlankAnnotation);
4✔
264
                }
265
                if (nonBlank(schema.getPattern())) {
4✔
266
                    typeInfo = typeInfo.withSchemaPattern(schema.getPattern())
6✔
267
                        .withAddedAnnotation(getPatternAnnotation(schema));
3✔
268
                }
269
                if (nonNull(schema.getMinLength()) || nonNull(schema.getMaxLength())) {
8✔
270
                    typeInfo = typeInfo
2✔
271
                        .withSchemaMinLength(schema.getMinLength())
3✔
272
                        .withSchemaMaxLength(schema.getMaxLength());
3✔
273
                    AnnotationInfo sizeAnnotation = getStringSizeAnnotation(schema);
4✔
274
                    typeInfo = typeInfo.withAddedAnnotation(sizeAnnotation);
4✔
275
                }
276
            }
277
        }
278

279
        return typeInfo;
2✔
280
    }
281

282
    private TypeInfo populateJsonNumberType(TypeInfo typeInfo, Schema<?> schema) {
283
        if ("double".equals(schema.getFormat())) {
5✔
284
            typeInfo = typeInfo.withName("Double");
5✔
285
        } else if ("float".equals(schema.getFormat())) {
5✔
286
            typeInfo = typeInfo.withName("Float");
5✔
287
        } else {
288
            typeInfo = typeInfo.withName("BigDecimal")
4✔
289
                .withAddedNormalImport("java.math.BigDecimal");
2✔
290
        }
291
        typeInfo = typeInfo.withSchemaFormat(schema.getFormat());
5✔
292
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
293
            if (!typeInfo.nullable()) {
3✔
294
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
295
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
296
            }
297
            if ("BigDecimal".equals(typeInfo.name())) {
5✔
298
                if (nonNull(schema.getMinimum())) {
4✔
299
                    AnnotationInfo minAnnotation = getMinAnnotation(schema);
4✔
300
                    typeInfo = typeInfo.withAddedAnnotation(minAnnotation);
4✔
301
                }
302
                if (nonNull(schema.getMaximum())) {
4✔
303
                    AnnotationInfo maxAnnotation = getMaxAnnotation(schema);
4✔
304
                    typeInfo = typeInfo.withAddedAnnotation(maxAnnotation);
4✔
305
                }
306
            }
307
        }
308

309
        return typeInfo;
2✔
310
    }
311

312
    private TypeInfo populateJsonIntegerType(TypeInfo typeInfo, Schema<?> schema) {
313
        typeInfo = typeInfo.withName("int64".equals(schema.getFormat()) ? "Long" :"Integer")
11✔
314
            .withSchemaFormat(schema.getFormat());
3✔
315

316
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
317
            if (!typeInfo.nullable()) {
3✔
318
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
319
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
320
            }
321
            if (nonNull(schema.getMinimum())) {
4✔
322
                AnnotationInfo minAnnotation = getMinAnnotation(schema);
4✔
323
                typeInfo = typeInfo.withAddedAnnotation(minAnnotation);
4✔
324
            }
325
            if (nonNull(schema.getMaximum())) {
4✔
326
                AnnotationInfo maxAnnotation = getMaxAnnotation(schema);
4✔
327
                typeInfo = typeInfo.withAddedAnnotation(maxAnnotation);
4✔
328
            }
329
        }
330

331
        return typeInfo;
2✔
332
    }
333

334
    private TypeInfo populateJsonBooleanType(TypeInfo typeInfo) {
335
        typeInfo = typeInfo.withName("Boolean");
4✔
336
        if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
337
            AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
338
            typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
339
        }
340

341
        return typeInfo;
2✔
342
    }
343

344
    private TypeInfo populateJsonArrayType(TypeInfo typeInfo, Schema<?> schema) {
345
        typeInfo = typeInfo.withPrimitive(false);
4✔
346
        if (TRUE.equals(schema.getUniqueItems())) {
5✔
347
            typeInfo = typeInfo.withName("Set")
4✔
348
                .withAddedNormalImport("java.util.Set");
3✔
349
        } else {
350
            typeInfo = typeInfo.withName("List")
4✔
351
                .withAddedNormalImport("java.util.List");
2✔
352
        }
353

354
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
355
            AnnotationInfo validAnnotation = getValidAnnotation();
3✔
356
            typeInfo = typeInfo.withAddedAnnotation(validAnnotation);
4✔
357
        }
358

359
        TypeInfo itemType = getTypeInfo(schema.getItems());
5✔
360

361
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
362
            if (!typeInfo.nullable()) {
3✔
363
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
364
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
365
            }
366
            if (nonNull(schema.getMinItems()) || nonNull(schema.getMaxItems())) {
8✔
367
                AnnotationInfo sizeAnnotation = getArraySizeAnnotation(schema);
4✔
368
                typeInfo = typeInfo.withAddedAnnotation(sizeAnnotation);
4✔
369
            }
370
        }
371

372
        return typeInfo.withItemType(itemType);
4✔
373
    }
374

375
    private TypeInfo populateJsonMapType(TypeInfo typeInfo, Schema<?> schema) {
376
        typeInfo = typeInfo.withName("Map")
4✔
377
            .withAddedNormalImport("java.util.Map");
2✔
378

379
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
380
            AnnotationInfo validAnnotation = getValidAnnotation();
3✔
381
            typeInfo = typeInfo.withAddedAnnotation(validAnnotation);
4✔
382
        }
383

384
        typeInfo = typeInfo.withKeyType(getTypeInfo(new StringSchema()))
9✔
385
            .withItemType(getTypeInfo((Schema<?>)schema.getAdditionalProperties()));
5✔
386

387
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
388
            if (!typeInfo.nullable()) {
3!
389
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
390
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
391
            }
392
            if (nonNull(schema.getMinItems()) || nonNull(schema.getMaxItems())) {
4!
393
                AnnotationInfo sizeAnnotation = getArraySizeAnnotation(schema);
4✔
394
                typeInfo = typeInfo.withAddedAnnotation(sizeAnnotation);
4✔
395
            }
396
        }
397

398
        return typeInfo;
2✔
399
    }
400

401
    private boolean isNullable(Schema<?> schema, NullabilityResolution resolution) {
402
        return switch(resolution) {
4✔
403
            case FROM_SCHEMA -> isNullable(schema);
4✔
404
            case FORCE_NULLABLE -> true;
2✔
405
            case FORCE_NOT_NULLABLE -> false;
1✔
406
        };
407
    }
408

409
    public boolean isNullable(Schema<?> schema) {
410
        if (isEmpty(schema.getTypes())) {
4✔
411
            if (nonEmpty(schema.getAllOf())) {
4!
412
                return schema.getAllOf().stream().allMatch(this::isNullable);
×
413
            } else if (nonEmpty(schema.getOneOf())) {
4✔
414
                return schema.getOneOf().stream().anyMatch(this::isNullable);
7✔
415
            } else if (nonBlank(schema.get$ref())) {
4!
416
                return isNullableByExtension(schema);
4✔
417
            } else {
418
                throw new IllegalStateException("No types, no ref: %s".formatted(schema.toString()));
×
419
            }
420
        }
421

422
        // schema.getNullable() populated by OpenAPI 3.0.x only
423
        return schema.getTypes().contains("null") || TRUE.equals(schema.getNullable()) || isNullableByExtension(schema);
18✔
424
    }
425

426
    private boolean isNullableByExtension(Schema<?> schema) {
427
        return extensions(schema.getExtensions()).getBoolean(EXT_NULLABLE).orElse(false);
11✔
428
    }
429

430
    private AnnotationInfo getJsonSerializeAnnotation(String jsonSerializer) {
431
        return new AnnotationInfo("@JsonSerialize(using = %s)".formatted(getClassRefFromFullyQualifiedClassName(jsonSerializer)))
15✔
432
            .withAddedNormalImport("com.fasterxml.jackson.databind.annotation.JsonSerialize")
2✔
433
            .withAddedNormalImport(jsonSerializer);
1✔
434
    }
435

436
    private AnnotationInfo getJsonDeserializeAnnotation(String jsonDeserializer) {
437
        return new AnnotationInfo("@JsonDeserialize(using = %s)".formatted(getClassRefFromFullyQualifiedClassName(jsonDeserializer)))
15✔
438
            .withAddedNormalImport("com.fasterxml.jackson.databind.annotation.JsonDeserialize")
2✔
439
            .withAddedNormalImport(jsonDeserializer);
1✔
440
    }
441

442
    private AnnotationInfo getJsonFormatAnnotation(String pattern) {
443
        return new AnnotationInfo(
10✔
444
            "@JsonFormat(pattern = \"%s\")".formatted(pattern),
3✔
445
            "com.fasterxml.jackson.annotation.JsonFormat");
446
    }
447

448
    private AnnotationInfo getValidAnnotation() {
449
        return new AnnotationInfo(
6✔
450
            "@Valid",
451
            "jakarta.validation.Valid");
452
    }
453

454
    private AnnotationInfo getNotNullAnnotation() {
455
        return new AnnotationInfo(
6✔
456
            "@NotNull",
457
            "jakarta.validation.constraints.NotNull");
458
    }
459

460
    private AnnotationInfo getNotBlankAnnotation() {
461
        return new AnnotationInfo(
6✔
462
            "@NotBlank",
463
            "jakarta.validation.constraints.NotBlank");
464
    }
465

466
    private AnnotationInfo getNotEmptyAnnotation() {
467
        return new AnnotationInfo(
6✔
468
            "@NotEmpty",
469
            "jakarta.validation.constraints.NotEmpty");
470
    }
471

472
    private AnnotationInfo getMinAnnotation(Schema<?> schema) {
473
        return new AnnotationInfo(
9✔
474
            "@Min(%d)".formatted(schema.getMinimum().longValue()),
7✔
475
            "jakarta.validation.constraints.Min");
476
    }
477

478
    private AnnotationInfo getMaxAnnotation(Schema<?> schema) {
479
        return new AnnotationInfo(
9✔
480
            "@Max(%d)".formatted(schema.getMaximum().longValue()),
7✔
481
            "jakarta.validation.constraints.Max");
482
    }
483

484
    private AnnotationInfo getPatternAnnotation(Schema<?> schema) {
485
        return new AnnotationInfo(
9✔
486
            "@Pattern(regexp = \"%s\")".formatted(schema.getPattern()),
5✔
487
            "jakarta.validation.constraints.Pattern");
488
    }
489

490
    private AnnotationInfo getEmailAnnotation() {
491
        return new AnnotationInfo(
6✔
492
            "@Email",
493
            "jakarta.validation.constraints.Email");
494
    }
495

496
    private AnnotationInfo getArraySizeAnnotation(Schema<?> schema) {
497
        List<String> sizeParams = new ArrayList<>();
4✔
498
        if (nonNull(schema.getMinItems())) {
4✔
499
            sizeParams.add("min = %d".formatted(schema.getMinItems()));
12✔
500
        }
501
        if (nonNull(schema.getMaxItems())) {
4✔
502
            sizeParams.add("max = %d".formatted(schema.getMaxItems()));
12✔
503
        }
504
        return new AnnotationInfo(
9✔
505
            "@Size(%s)".formatted(joinCsv(sizeParams)),
5✔
506
            "jakarta.validation.constraints.Size");
507
    }
508

509
    private AnnotationInfo getStringSizeAnnotation(Schema<?> schema) {
510
        List<String> sizeParams = new ArrayList<>();
4✔
511
        if (nonNull(schema.getMinLength())) {
4✔
512
            sizeParams.add("min = %d".formatted(schema.getMinLength()));
12✔
513
        }
514
        if (nonNull(schema.getMaxLength())) {
4✔
515
            sizeParams.add("max = %d".formatted(schema.getMaxLength()));
12✔
516
        }
517
        return new AnnotationInfo(
9✔
518
            "@Size(%s)".formatted(joinCsv(sizeParams)),
5✔
519
            "jakarta.validation.constraints.Size");
520
    }
521

522
    private String getClassRefFromFullyQualifiedClassName(String fullyQualifiedClassName) {
523
        String className = getClassNameFromFqn(fullyQualifiedClassName);
3✔
524
        return formatClassRef(className);
4✔
525
    }
526
}
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