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

torand / jsonschema2java / 18399814123

10 Oct 2025 07:35AM UTC coverage: 78.214% (-0.9%) from 79.157%
18399814123

push

github

torand
fix: bean validation annotations on primitive subtypes of compound pojo property types now generated

287 of 413 branches covered (69.49%)

Branch coverage included in aggregate %.

440 of 533 new or added lines in 26 files covered. (82.55%)

1 existing line in 1 file now uncovered.

808 of 987 relevant lines covered (81.86%)

4.75 hits per line

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

82.21
/src/main/java/io/github/torand/jsonschema2java/collectors/TypeInfoCollector.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.jsonschema2java.collectors;
17

18
import io.github.torand.jsonschema2java.generators.Options;
19
import io.github.torand.jsonschema2java.model.AnnotationInfo;
20
import io.github.torand.jsonschema2java.model.TypeInfo;
21
import io.github.torand.jsonschema2java.utils.JsonSchemaDef;
22

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

28
import static io.github.torand.javacommons.lang.Exceptions.illegalStateException;
29
import static io.github.torand.javacommons.lang.StringHelper.nonBlank;
30
import static io.github.torand.jsonschema2java.collectors.Extensions.*;
31
import static io.github.torand.jsonschema2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NOT_NULLABLE;
32
import static io.github.torand.jsonschema2java.collectors.TypeInfoCollector.NullabilityResolution.FORCE_NULLABLE;
33
import static io.github.torand.jsonschema2java.utils.StringUtils.getClassNameFromFqn;
34
import static io.github.torand.jsonschema2java.utils.StringUtils.joinCsv;
35
import static java.lang.Boolean.TRUE;
36
import static java.util.Objects.nonNull;
37
import static java.util.function.Predicate.not;
38

39
/**
40
 * Collects information about a type from a schema.
41
 */
42
public class TypeInfoCollector extends BaseCollector {
43
    public enum NullabilityResolution {FROM_SCHEMA, FORCE_NULLABLE, FORCE_NOT_NULLABLE}
21✔
44

45
    private final SchemaResolver schemaResolver;
46

47
    public TypeInfoCollector(Options opts, SchemaResolver schemaResolver) {
48
        super(opts);
3✔
49
        this.schemaResolver = schemaResolver;
3✔
50
    }
1✔
51

52
    public TypeInfo getTypeInfo(JsonSchemaDef schema) {
53
        return getTypeInfo(schema, NullabilityResolution.FROM_SCHEMA);
5✔
54
    }
55

56
    public TypeInfo getTypeInfo(JsonSchemaDef schema, NullabilityResolution nullabilityResolution) {
57
        if (!schema.hasTypes()) {
3✔
58

59
            boolean nullable = isNullable(schema, nullabilityResolution);
5✔
60

61
            if (schema.hasAnyOf()) {
3!
62
                throw new IllegalStateException("Schema 'anyOf' not supported");
×
63
            }
64

65
            if (schema.hasOneOf()) {
3✔
66
                // Limited support for 'oneOf' in properties: use the first non-nullable subschema
67
                JsonSchemaDef subSchema = getNonNullableSubSchema(schema.oneOf().toList())
8✔
68
                    .orElseThrow(illegalStateException("Schema 'oneOf' must contain a non-nullable sub-schema"));
4✔
69

70
                return getTypeInfo(subSchema, nullable ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
8!
71
            }
72

73
            if (schema.allOf().count() == 1) {
6!
74
                // 'allOf' only supported if it contains single type
75
                return getTypeInfo(schema.allOf().findFirst().get(), nullable ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
×
76
            }
77

78
            URI ref = schema.ref();
3✔
79
            if (nonNull(ref)) {
3!
80
                TypeInfo typeInfo;
81

82
                if (schemaResolver.isPrimitiveType(ref)) {
5✔
83
                    JsonSchemaDef refSchema = schemaResolver.getOrThrow(ref);
5✔
84
                    typeInfo = getTypeInfo(refSchema, nullable ? FORCE_NULLABLE : FORCE_NOT_NULLABLE);
9✔
85
                } else {
1✔
86
                    typeInfo = new TypeInfo()
7✔
87
                        .withName(schemaResolver.getTypeName(ref) + opts.pojoNameSuffix())
7✔
88
                        .withNullable(nullable);
2✔
89

90
                    String modelSubpackage = schemaResolver.getModelSubpackage(ref).orElse(null);
8✔
91
                    typeInfo = typeInfo.withAddedNormalImport(opts.getModelPackage(modelSubpackage) + "." + typeInfo.name());
10✔
92

93
                    if (!schemaResolver.isEnumType(schema.ref())) {
6✔
94
                        AnnotationInfo validAnnotation = getValidAnnotation();
3✔
95
                        typeInfo = typeInfo.withAddedAnnotation(validAnnotation);
4✔
96
                    }
97
                    if (!nullable) {
2!
98
                        AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
99
                        typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
100
                    }
101
                }
102

103
                if (nonBlank(schema.description())) {
4✔
104
                    typeInfo = typeInfo.withDescription(schema.description());
5✔
105
                }
106

107
                return typeInfo;
2✔
108
            } else if (schema.hasAllOf()) {
×
109
                throw new IllegalStateException("No types, no $ref: %s".formatted(schema.toString()));
×
110
            }
111
        }
112

113
        return getJsonType(schema, nullabilityResolution);
5✔
114
    }
115

116
    public <T> Optional<JsonSchemaDef> getNonNullableSubSchema(List<JsonSchemaDef> subSchemas) {
117
        return subSchemas.stream()
5✔
118
            .filter(not(this::isNullable))
2✔
119
            .findFirst();
1✔
120
    }
121

122
    private TypeInfo getJsonType(JsonSchemaDef schema, NullabilityResolution nullabilityResolution) {
123
        TypeInfo typeInfo = new TypeInfo()
4✔
124
            .withDescription(schema.description())
3✔
125
            .withPrimitive(true)
4✔
126
            .withNullable(isNullable(schema, nullabilityResolution));
3✔
127

128
        String jsonType = schema.types()
4✔
129
            .filter(not("null"::equals))
2✔
130
            .findFirst()
7✔
131
            .orElseThrow(illegalStateException("Unexpected types: %s", schema.toString()));
6✔
132

133
        if ("string".equals(jsonType)) {
4✔
134
            typeInfo = populateJsonStringType(typeInfo, schema);
6✔
135
        } else if ("number".equals(jsonType)) {
4✔
136
            typeInfo = populateJsonNumberType(typeInfo, schema);
6✔
137
        } else if ("integer".equals(jsonType)) {
4✔
138
            typeInfo = populateJsonIntegerType(typeInfo, schema);
6✔
139
        } else if ("boolean".equals(jsonType)) {
4✔
140
            typeInfo = populateJsonBooleanType(typeInfo);
5✔
141
        } else if ("array".equals(jsonType)) {
4!
142
            typeInfo = populateJsonArrayType(typeInfo, schema);
6✔
143
        } else if ("object".equals(jsonType) && schema.additionalProperties() instanceof JsonSchemaDef) {
×
NEW
144
            typeInfo = populateJsonMapType(typeInfo, schema);
×
145
        } else {
146
            // Schema not expected to be defined "inline" using type 'object'
147
            throw new IllegalStateException("Unexpected schema: %s".formatted(schema.toString()));
×
148
        }
149

150
        Optional<String> maybeJsonSerializer = schema.extensions().getString(EXT_JSON_SERIALIZER);
5✔
151
        if (maybeJsonSerializer.isPresent()) {
3✔
152
            AnnotationInfo jsonSerializeAnnotation = getJsonSerializeAnnotation(maybeJsonSerializer.get());
6✔
153
            typeInfo = typeInfo.withAddedAnnotation(jsonSerializeAnnotation);
4✔
154
        }
155

156
        Optional<String> maybeValidationConstraint = schema.extensions().getString(EXT_VALIDATION_CONSTRAINT);
5✔
157
        if (maybeValidationConstraint.isPresent()) {
3✔
158
            AnnotationInfo validationConstraintAnnotation = new AnnotationInfo(
8✔
159
                "@%s".formatted(getClassNameFromFqn(maybeValidationConstraint.get())),
6✔
160
                maybeValidationConstraint.get()
4✔
161
            );
162
            typeInfo = typeInfo.withAddedAnnotation(validationConstraintAnnotation);
4✔
163
        }
164

165
        return typeInfo;
2✔
166
    }
167

168
    private TypeInfo populateJsonStringType(TypeInfo typeInfo, JsonSchemaDef schema) {
169
        if ("uri".equals(schema.format())) {
5✔
170
            typeInfo = typeInfo.withName("URI")
4✔
171
                .withSchemaFormat(schema.format())
3✔
172
                .withAddedNormalImport("java.net.URI");
2✔
173
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
174
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
175
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
176
            }
1✔
177
        } else if ("uuid".equals(schema.format())) {
5✔
178
            typeInfo = typeInfo.withName("UUID")
4✔
179
                .withSchemaFormat(schema.format())
3✔
180
                .withAddedNormalImport("java.util.UUID");
2✔
181
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
182
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
183
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
184
            }
1✔
185
        } else if ("duration".equals(schema.format())) {
5✔
186
            typeInfo = typeInfo.withName("Duration")
4✔
187
                .withSchemaFormat(schema.format())
3✔
188
                .withAddedNormalImport("java.time.Duration");
2✔
189
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
190
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
191
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
192
            }
1✔
193
        } else if ("date".equals(schema.format())) {
5✔
194
            typeInfo = typeInfo.withName("LocalDate")
4✔
195
                .withSchemaFormat(schema.format())
3✔
196
                .withAddedNormalImport("java.time.LocalDate");
2✔
197
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
198
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
199
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
200
            }
201
            AnnotationInfo jsonFormatAnnotation = getJsonFormatAnnotation("yyyy-MM-dd");
4✔
202
            typeInfo = typeInfo.withAddedAnnotation(jsonFormatAnnotation);
4✔
203
        } else if ("date-time".equals(schema.format())) {
6✔
204
            typeInfo = typeInfo.withName("LocalDateTime")
4✔
205
                .withSchemaFormat(schema.format())
3✔
206
                .withAddedNormalImport("java.time.LocalDateTime");
2✔
207
            if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
208
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
209
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
210
            }
211
            AnnotationInfo jsonFormatAnnotation = getJsonFormatAnnotation("yyyy-MM-dd'T'HH:mm:ss");
4✔
212
            typeInfo = typeInfo.withAddedAnnotation(jsonFormatAnnotation);
4✔
213
        } else if ("email".equals(schema.format())) {
6✔
214
            typeInfo = typeInfo.withName("String")
4✔
215
                .withSchemaFormat(schema.format());
3✔
216
            if (opts.addJakartaBeanValidationAnnotations()) {
4!
217
                if (!typeInfo.nullable()) {
3✔
218
                    AnnotationInfo notBlankAnnotation = getNotBlankAnnotation();
3✔
219
                    typeInfo = typeInfo.withAddedAnnotation(notBlankAnnotation);
4✔
220
                }
221
                AnnotationInfo emailAnnotation = getEmailAnnotation();
3✔
222
                typeInfo = typeInfo.withAddedAnnotation(emailAnnotation);
4✔
223
            }
1✔
224
        } else if ("binary".equals(schema.format())) {
5✔
225
            typeInfo = typeInfo.withName("byte[]")
4✔
226
                .withSchemaFormat(schema.format());
3✔
227
            if (opts.addJakartaBeanValidationAnnotations()) {
4!
228
                if (!typeInfo.nullable()) {
3!
229
                    AnnotationInfo notEmptyAnnotation = getNotEmptyAnnotation();
3✔
230
                    typeInfo = typeInfo.withAddedAnnotation(notEmptyAnnotation);
4✔
231
                }
232
                if (nonNull(schema.minItems()) || nonNull(schema.maxItems())) {
8!
NEW
233
                    AnnotationInfo sizeAnnotaion = getArraySizeAnnotation(schema);
×
NEW
234
                    typeInfo = typeInfo.withAddedAnnotation(sizeAnnotaion);
×
UNCOV
235
                }
×
236
            }
237
        } else {
238
            typeInfo = typeInfo.withName("String")
4✔
239
                .withSchemaFormat(schema.format());
3✔
240
            if (opts.addJakartaBeanValidationAnnotations()) {
4!
241
                if (!typeInfo.nullable()) {
3✔
242
                    AnnotationInfo notBlankAnnotation = getNotBlankAnnotation();
3✔
243
                    typeInfo = typeInfo.withAddedAnnotation(notBlankAnnotation);
4✔
244
                }
245
                if (nonBlank(schema.pattern())) {
4✔
246
                    typeInfo = typeInfo.withSchemaPattern(schema.pattern())
6✔
247
                        .withAddedAnnotation(getPatternAnnotation(schema));
3✔
248
                }
249
                if (nonNull(schema.minLength()) || nonNull(schema.maxLength())) {
8✔
250
                    AnnotationInfo sizeAnnotation = getStringSizeAnnotation(schema);
4✔
251
                    typeInfo = typeInfo.withAddedAnnotation(sizeAnnotation);
4✔
252
                }
253
            }
254
        }
255

256
        return typeInfo;
2✔
257
    }
258

259
    private TypeInfo populateJsonNumberType(TypeInfo typeInfo, JsonSchemaDef schema) {
260
        if ("double".equals(schema.format())) {
5✔
261
            typeInfo = typeInfo.withName("Double");
5✔
262
        } else if ("float".equals(schema.format())) {
5✔
263
            typeInfo = typeInfo.withName("Float");
5✔
264
        } else {
265
            typeInfo = typeInfo.withName("BigDecimal")
4✔
266
                .withAddedNormalImport("java.math.BigDecimal");
2✔
267
        }
268
        typeInfo = typeInfo.withSchemaFormat(schema.format());
5✔
269
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
270
            if (!typeInfo.nullable()) {
3✔
271
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
272
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
273
            }
274
            if ("BigDecimal".equals(typeInfo.name())) {
5✔
275
                if (nonNull(schema.minimum())) {
4✔
276
                    AnnotationInfo minAnnotation = getMinAnnotation(schema);
4✔
277
                    typeInfo = typeInfo.withAddedAnnotation(minAnnotation);
4✔
278
                }
279
                if (nonNull(schema.maximum())) {
4✔
280
                    AnnotationInfo maxAnnotation = getMaxAnnotation(schema);
4✔
281
                    typeInfo = typeInfo.withAddedAnnotation(maxAnnotation);
4✔
282
                }
283
            }
284
        }
285

286
        return typeInfo;
2✔
287
    }
288

289
    private TypeInfo populateJsonIntegerType(TypeInfo typeInfo, JsonSchemaDef schema) {
290
        typeInfo = typeInfo.withName("int64".equals(schema.format()) ? "Long" :"Integer")
11✔
291
            .withSchemaFormat(schema.format());
3✔
292

293
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
294
            if (!typeInfo.nullable()) {
3✔
295
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
296
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
297
            }
298
            if (nonNull(schema.minimum())) {
4✔
299
                AnnotationInfo minAnnotation = getMinAnnotation(schema);
4✔
300
                typeInfo = typeInfo.withAddedAnnotation(minAnnotation);
4✔
301
            }
302
            if (nonNull(schema.maximum())) {
4✔
303
                AnnotationInfo maxAnnotation = getMaxAnnotation(schema);
4✔
304
                typeInfo = typeInfo.withAddedAnnotation(maxAnnotation);
4✔
305
            }
306
        }
307

308
        return typeInfo;
2✔
309
    }
310

311
    private TypeInfo populateJsonBooleanType(TypeInfo typeInfo) {
312
        typeInfo = typeInfo.withName("Boolean");
4✔
313
        if (!typeInfo.nullable() && opts.addJakartaBeanValidationAnnotations()) {
7!
314
            AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
315
            typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
316
        }
317

318
        return typeInfo;
2✔
319
    }
320

321
    private TypeInfo populateJsonArrayType(TypeInfo typeInfo, JsonSchemaDef schema) {
322
        typeInfo = typeInfo.withPrimitive(false);
4✔
323
        if (TRUE.equals(schema.uniqueItems())) {
6✔
324
            typeInfo = typeInfo.withName("Set")
4✔
325
                .withAddedNormalImport("java.util.Set");
3✔
326
        } else {
327
            typeInfo = typeInfo.withName("List")
4✔
328
                .withAddedNormalImport("java.util.List");
2✔
329
        }
330

331
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
332
            AnnotationInfo validAnnotation = getValidAnnotation();
3✔
333
            typeInfo = typeInfo.withAddedAnnotation(validAnnotation);
4✔
334
        }
335

336
        TypeInfo itemType = getTypeInfo(schema.items());
5✔
337

338
        if (opts.addJakartaBeanValidationAnnotations()) {
4!
339
            if (!typeInfo.nullable()) {
3✔
340
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
3✔
341
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
4✔
342
            }
343
            if (nonNull(schema.minItems()) || nonNull(schema.maxItems())) {
8✔
344
                AnnotationInfo sizeAnnotation = getArraySizeAnnotation(schema);
4✔
345
                typeInfo = typeInfo.withAddedAnnotation(sizeAnnotation);
4✔
346
            }
347
        }
348

349
        return typeInfo.withItemType(itemType);
4✔
350
    }
351

352
    private TypeInfo populateJsonMapType(TypeInfo typeInfo, JsonSchemaDef schema) {
NEW
353
        typeInfo = typeInfo.withName("Map")
×
NEW
354
            .withAddedNormalImport("java.util.Map");
×
355

NEW
356
        if (opts.addJakartaBeanValidationAnnotations()) {
×
NEW
357
            AnnotationInfo validAnnotation = getValidAnnotation();
×
NEW
358
            typeInfo = typeInfo.withAddedAnnotation(validAnnotation);
×
359
        }
360

NEW
361
        TypeInfo keyTypeInfo = new TypeInfo().withName("String");
×
NEW
362
        if (opts.addJakartaBeanValidationAnnotations()) {
×
NEW
363
            AnnotationInfo notBlankAnnotation = getNotBlankAnnotation();
×
NEW
364
            keyTypeInfo = keyTypeInfo.withAddedAnnotation(notBlankAnnotation);
×
365
        }
366

NEW
367
        typeInfo = typeInfo.withKeyType(keyTypeInfo)
×
NEW
368
            .withItemType(getTypeInfo((JsonSchemaDef)schema.additionalProperties()));
×
369

NEW
370
        if (opts.addJakartaBeanValidationAnnotations()) {
×
NEW
371
            if (!typeInfo.nullable()) {
×
NEW
372
                AnnotationInfo notNullAnnotation = getNotNullAnnotation();
×
NEW
373
                typeInfo = typeInfo.withAddedAnnotation(notNullAnnotation);
×
374
            }
375
            if (nonNull(schema.minItems()) || nonNull(schema.maxItems())) {
×
NEW
376
                AnnotationInfo sizeAnnotation = getArraySizeAnnotation(schema);
×
NEW
377
                typeInfo = typeInfo.withAddedAnnotation(sizeAnnotation);
×
378
            }
379
        }
380

NEW
381
        return typeInfo;
×
382
    }
383

384
    private boolean isNullable(JsonSchemaDef schema, NullabilityResolution resolution) {
385
        return switch(resolution) {
4✔
386
            case FROM_SCHEMA -> isNullable(schema);
4✔
387
            case FORCE_NULLABLE -> true;
2✔
388
            case FORCE_NOT_NULLABLE -> false;
1✔
389
        };
390
    }
391

392
    public boolean isNullable(JsonSchemaDef schema) {
393
        if (!schema.hasTypes()) {
3✔
394
            if (schema.hasAllOf()) {
3!
395
                return schema.allOf().allMatch(this::isNullable);
×
396
            } else if (schema.hasOneOf()) {
3✔
397
                return schema.oneOf().anyMatch(this::isNullable);
6✔
398
            } else if (nonNull(schema.ref())) {
4!
399
                return isNullableByExtension(schema);
4✔
400
            } else {
401
                throw new IllegalStateException("No types, no $ref: %s".formatted(schema.toString()));
×
402
            }
403
        }
404

405
        return schema.hasType("null") || isNullableByExtension(schema);
12✔
406
    }
407

408
    private boolean isNullableByExtension(JsonSchemaDef schema) {
409
        return schema.extensions().getBoolean(EXT_NULLABLE).orElse(false);
10✔
410
    }
411

412
    private AnnotationInfo getJsonSerializeAnnotation(String jsonSerializer) {
413
        return new AnnotationInfo("@JsonSerialize(using = %s)".formatted(getJsonSerializerClass(jsonSerializer)))
15✔
414
            .withAddedNormalImport("com.fasterxml.jackson.databind.annotation.JsonSerialize")
2✔
415
            .withAddedNormalImport(jsonSerializer);
1✔
416
    }
417

418
    private AnnotationInfo getJsonFormatAnnotation(String pattern) {
419
        return new AnnotationInfo(
10✔
420
            "@JsonFormat(pattern = \"%s\")".formatted(pattern),
3✔
421
            "com.fasterxml.jackson.annotation.JsonFormat");
422
    }
423

424
    private AnnotationInfo getValidAnnotation() {
425
        return new AnnotationInfo(
6✔
426
            "@Valid",
427
            "jakarta.validation.Valid");
428
    }
429

430
    private AnnotationInfo getNotNullAnnotation() {
431
        return new AnnotationInfo(
6✔
432
            "@NotNull",
433
            "jakarta.validation.constraints.NotNull");
434
    }
435

436
    private AnnotationInfo getNotBlankAnnotation() {
437
        return new AnnotationInfo(
6✔
438
            "@NotBlank",
439
            "jakarta.validation.constraints.NotBlank");
440
    }
441

442
    private AnnotationInfo getNotEmptyAnnotation() {
443
        return new AnnotationInfo(
6✔
444
            "@NotEmpty",
445
            "jakarta.validation.constraints.NotEmpty");
446
    }
447

448
    private AnnotationInfo getMinAnnotation(JsonSchemaDef schema) {
449
        return new AnnotationInfo(
9✔
450
            "@Min(%d)".formatted(schema.minimum().longValue()),
7✔
451
            "jakarta.validation.constraints.Min");
452
    }
453

454
    private AnnotationInfo getMaxAnnotation(JsonSchemaDef schema) {
455
        return new AnnotationInfo(
9✔
456
            "@Max(%d)".formatted(schema.maximum().longValue()),
7✔
457
            "jakarta.validation.constraints.Max");
458
    }
459

460
    private AnnotationInfo getPatternAnnotation(JsonSchemaDef schema) {
461
        return new AnnotationInfo(
9✔
462
            "@Pattern(regexp = \"%s\")".formatted(schema.pattern()),
5✔
463
            "jakarta.validation.constraints.Pattern");
464
    }
465

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

472
    private AnnotationInfo getArraySizeAnnotation(JsonSchemaDef schema) {
473
        List<String> sizeParams = new ArrayList<>();
4✔
474
        if (nonNull(schema.minItems())) {
4✔
475
            sizeParams.add("min = %d".formatted(schema.minItems()));
12✔
476
        }
477
        if (nonNull(schema.maxItems())) {
4✔
478
            sizeParams.add("max = %d".formatted(schema.maxItems()));
12✔
479
        }
480
        return new AnnotationInfo(
9✔
481
            "@Size(%s)".formatted(joinCsv(sizeParams)),
5✔
482
            "jakarta.validation.constraints.Size");
483
    }
484

485
    private AnnotationInfo getStringSizeAnnotation(JsonSchemaDef schema) {
486
        List<String> sizeParams = new ArrayList<>();
4✔
487
        if (nonNull(schema.minLength())) {
4✔
488
            sizeParams.add("min = %d".formatted(schema.minLength()));
12✔
489
        }
490
        if (nonNull(schema.maxLength())) {
4✔
491
            sizeParams.add("max = %d".formatted(schema.maxLength()));
12✔
492
        }
493
        return new AnnotationInfo(
9✔
494
            "@Size(%s)".formatted(joinCsv(sizeParams)),
5✔
495
            "jakarta.validation.constraints.Size");
496
    }
497

498
    private String getJsonSerializerClass(String jsonSerializerFqn) {
499
        String className = getClassNameFromFqn(jsonSerializerFqn);
3✔
500
        return formatClassRef(className);
4✔
501
    }
502
}
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