Skip to content

Commit b3a5fea

Browse files
authored
feat(crd-generator): add support for more validation constraints (6447)
Add support for exclusiveMinimum and exclusiveMaximum (#5868) --- Fix annotation description --- Add support for minLength and maxLength (#5836) --- Add support for minItems and maxItems (#5836) --- Add support for minProperties and maxProperties (#5836) --- Add tests for type annotations and cleanup --- Add type support for strings Lists and Maps using `@Size` --- Fix javadoc in Size annotation --- Fix approval test --- Rename group in approvaltest --- Add default value handling again --- Updated docs to describe inclusive/exclusive and `@Size` annotation --- Fix typo --- Cleanup SpecReplicasPathTest --- Add unit tests --- Add changelog entries
1 parent 5cab66c commit b3a5fea

File tree

13 files changed

+1190
-52
lines changed

13 files changed

+1190
-52
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#### Improvements
1313
* Fix #3069: (crd-generator) Add `@AdditionalPrinterColumn` to specify a printer column by JSON path.
1414
* Fix #6392: (crd-generator) Add `@AdditionalSelectableField` and `@SelectableField` to specify selectable fields.
15+
* Fix #5836: (crd-generator) Add `@Size` annotation to limit the size of strings, lists/arrays or maps
16+
* Fix #5868: (crd-generator) Add `exlusiveMinimum` / `exclusiveMaximum` support to `@Min` and `@Max`
1517
* Fix #5264: Remove deprecated `Config.errorMessages` field
1618
* Fix #6008: removing the optional dependency on bouncy castle
1719
* Fix #6407: sundrio builder-annotations is not available via bom import

Diff for: crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java

+140-21
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import com.fasterxml.jackson.databind.JsonNode;
2323
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
2424
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
25+
import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema;
2526
import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema.Items;
27+
import com.fasterxml.jackson.module.jsonSchema.types.IntegerSchema;
28+
import com.fasterxml.jackson.module.jsonSchema.types.NumberSchema;
2629
import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema;
2730
import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema.SchemaAdditionalProperties;
2831
import com.fasterxml.jackson.module.jsonSchema.types.ReferenceSchema;
@@ -43,6 +46,7 @@
4346
import io.fabric8.generator.annotation.Nullable;
4447
import io.fabric8.generator.annotation.Pattern;
4548
import io.fabric8.generator.annotation.Required;
49+
import io.fabric8.generator.annotation.Size;
4650
import io.fabric8.generator.annotation.ValidationRule;
4751
import io.fabric8.generator.annotation.ValidationRules;
4852
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
@@ -230,8 +234,16 @@ class PropertyMetadata {
230234
private final String description;
231235
private final Object defaultValue;
232236
private Double min;
237+
private Boolean exclusiveMinimum;
233238
private Double max;
239+
private Boolean exclusiveMaximum;
234240
private String pattern;
241+
private Long minLength;
242+
private Long maxLength;
243+
private Long minItems;
244+
private Long maxItems;
245+
private Long minProperties;
246+
private Long maxProperties;
235247
private boolean nullable;
236248
private String format;
237249
private List<V> validationRules = new ArrayList<>();
@@ -254,15 +266,55 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) {
254266
if (value.isStringSchema()) {
255267
StringSchema stringSchema = value.asStringSchema();
256268
// only set if ValidationSchemaFactoryWrapper is used
257-
this.pattern = stringSchema.getPattern();
258-
//this.maxLength = ofNullable(stringSchema.getMaxLength()).map(Integer::doubleValue).orElse(null);
259-
//this.minLength = ofNullable(stringSchema.getMinLength()).map(Integer::doubleValue).orElse(null);
260-
} else {
261-
// TODO: process the other schema types for validation values
269+
pattern = ofNullable(beanProperty.getAnnotation(Pattern.class)).map(Pattern::value)
270+
.or(() -> ofNullable(stringSchema.getPattern()))
271+
.orElse(null);
272+
minLength = findMinInSizeAnnotation(beanProperty)
273+
.or(() -> ofNullable(stringSchema.getMinLength()).map(Integer::longValue))
274+
.orElse(null);
275+
maxLength = findMaxInSizeAnnotation(beanProperty)
276+
.or(() -> ofNullable(stringSchema.getMaxLength()).map(Integer::longValue))
277+
.orElse(null);
278+
} else if (value.isIntegerSchema()) {
279+
// integerschema extends numberschema and must handled first
280+
IntegerSchema integerSchema = value.asIntegerSchema();
281+
setMinMax(beanProperty,
282+
integerSchema.getMinimum(),
283+
integerSchema.getExclusiveMinimum(),
284+
integerSchema.getMaximum(),
285+
integerSchema.getExclusiveMaximum());
286+
} else if (value.isNumberSchema()) {
287+
NumberSchema numberSchema = value.asNumberSchema();
288+
setMinMax(beanProperty,
289+
numberSchema.getMinimum(),
290+
numberSchema.getExclusiveMinimum(),
291+
numberSchema.getMaximum(),
292+
numberSchema.getExclusiveMaximum());
293+
} else if (value.isArraySchema()) {
294+
ArraySchema arraySchema = value.asArraySchema();
295+
minItems = findMinInSizeAnnotation(beanProperty)
296+
.or(() -> ofNullable(arraySchema.getMinItems()).map(Integer::longValue))
297+
.orElse(null);
298+
maxItems = findMaxInSizeAnnotation(beanProperty)
299+
.or(() -> ofNullable(arraySchema.getMaxItems()).map(Integer::longValue))
300+
.orElse(null);
301+
} else if (value.isObjectSchema()) {
302+
// TODO: Could be also applied only on Maps instead of "all the rest"
303+
minProperties = findMinInSizeAnnotation(beanProperty)
304+
.orElse(null);
305+
maxProperties = findMaxInSizeAnnotation(beanProperty)
306+
.orElse(null);
262307
}
263308

264309
collectValidationRules(beanProperty, validationRules);
265310

311+
// TODO: should probably move to a standard annotations
312+
// see ValidationSchemaFactoryWrapper
313+
nullable = beanProperty.getAnnotation(Nullable.class) != null;
314+
315+
// TODO: should the following be deprecated?
316+
required = beanProperty.getAnnotation(Required.class) != null;
317+
266318
if (beanProperty.getMetadata().getDefaultValue() != null) {
267319
defaultValue = toTargetType(beanProperty.getType(), beanProperty.getMetadata().getDefaultValue());
268320
} else if (ofNullable(beanProperty.getAnnotation(Default.class)).map(Default::value).isPresent()) {
@@ -271,16 +323,34 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) {
271323
} else {
272324
defaultValue = null;
273325
}
326+
}
274327

275-
// TODO: should probably move to a standard annotations
276-
// see ValidationSchemaFactoryWrapper
277-
nullable = beanProperty.getAnnotation(Nullable.class) != null;
278-
max = ofNullable(beanProperty.getAnnotation(Max.class)).map(Max::value).orElse(max);
279-
min = ofNullable(beanProperty.getAnnotation(Min.class)).map(Min::value).orElse(min);
280-
281-
// TODO: should the following be deprecated?
282-
required = beanProperty.getAnnotation(Required.class) != null;
283-
pattern = ofNullable(beanProperty.getAnnotation(Pattern.class)).map(Pattern::value).orElse(pattern);
328+
private void setMinMax(BeanProperty beanProperty,
329+
Double minimum, Boolean exclusiveMinimum, Double maximum, Boolean exclusiveMaximum) {
330+
ofNullable(minimum).ifPresent(v -> {
331+
this.min = v;
332+
if (Boolean.TRUE.equals(exclusiveMinimum)) {
333+
this.exclusiveMinimum = true;
334+
}
335+
});
336+
ofNullable(beanProperty.getAnnotation(Min.class)).ifPresent(a -> {
337+
min = a.value();
338+
if (!a.inclusive()) {
339+
this.exclusiveMinimum = true;
340+
}
341+
});
342+
ofNullable(maximum).ifPresent(v -> {
343+
this.max = v;
344+
if (Boolean.TRUE.equals(exclusiveMaximum)) {
345+
this.exclusiveMaximum = true;
346+
}
347+
});
348+
ofNullable(beanProperty.getAnnotation(Max.class)).ifPresent(a -> {
349+
this.max = a.value();
350+
if (!a.inclusive()) {
351+
this.exclusiveMaximum = true;
352+
}
353+
});
284354
}
285355

286356
public void updateSchema(T schema) {
@@ -294,10 +364,22 @@ public void updateSchema(T schema) {
294364
}
295365
}
296366
if (nullable) {
297-
schema.setNullable(nullable);
367+
schema.setNullable(true);
298368
}
299369
schema.setMaximum(max);
370+
schema.setExclusiveMaximum(exclusiveMaximum);
300371
schema.setMinimum(min);
372+
schema.setExclusiveMinimum(exclusiveMinimum);
373+
374+
schema.setMinLength(minLength);
375+
schema.setMaxLength(maxLength);
376+
377+
schema.setMinItems(minItems);
378+
schema.setMaxItems(maxItems);
379+
380+
schema.setMinProperties(minProperties);
381+
schema.setMaxProperties(maxProperties);
382+
301383
schema.setPattern(pattern);
302384
schema.setFormat(format);
303385
if (preserveUnknownFields) {
@@ -306,6 +388,18 @@ public void updateSchema(T schema) {
306388

307389
addToValidationRules(schema, validationRules);
308390
}
391+
392+
private Optional<Long> findMinInSizeAnnotation(BeanProperty beanProperty) {
393+
return ofNullable(beanProperty.getAnnotation(Size.class))
394+
.map(Size::min)
395+
.filter(v -> v > 0);
396+
}
397+
398+
private Optional<Long> findMaxInSizeAnnotation(BeanProperty beanProperty) {
399+
return ofNullable(beanProperty.getAnnotation(Size.class))
400+
.map(Size::max)
401+
.filter(v -> v < Long.MAX_VALUE);
402+
}
309403
}
310404

311405
private T resolveObject(LinkedHashMap<String, String> visited, InternalSchemaSwaps schemaSwaps, JsonSchema jacksonSchema,
@@ -514,24 +608,49 @@ private void handleTypeAnnotations(final T schema, BeanProperty beanProperty, Cl
514608

515609
AnnotatedElement member = beanProperty.getMember().getAnnotated();
516610
AnnotatedType fieldType = null;
517-
AnnotatedType type = null;
611+
AnnotatedType methodType = null;
518612
if (member instanceof Field) {
519613
fieldType = ((Field) member).getAnnotatedType();
520614
} else if (member instanceof Method) {
521615
fieldType = getFieldForMethod(beanProperty).map(Field::getAnnotatedType).orElse(null);
522-
type = ((Method) member).getAnnotatedReceiverType();
616+
methodType = ((Method) member).getAnnotatedReceiverType();
523617
}
524618

525-
Stream.of(fieldType, type)
619+
Stream.of(fieldType, methodType)
526620
.filter(o -> !Objects.isNull(o))
527621
.filter(AnnotatedParameterizedType.class::isInstance)
528622
.map(AnnotatedParameterizedType.class::cast)
529623
.map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments)
530624
.map(a -> a[typeIndex])
531625
.forEach(at -> {
532-
Optional.ofNullable(at.getAnnotation(Pattern.class)).ifPresent(a -> schema.setPattern(a.value()));
533-
Optional.ofNullable(at.getAnnotation(Min.class)).ifPresent(a -> schema.setMinimum(a.value()));
534-
Optional.ofNullable(at.getAnnotation(Max.class)).ifPresent(a -> schema.setMaximum(a.value()));
626+
if ("string".equals(schema.getType())) {
627+
ofNullable(at.getAnnotation(Pattern.class))
628+
.ifPresent(a -> schema.setPattern(a.value()));
629+
630+
ofNullable(at.getAnnotation(Size.class))
631+
.map(Size::min)
632+
.filter(v -> v > 0)
633+
.ifPresent(schema::setMinLength);
634+
635+
ofNullable(at.getAnnotation(Size.class))
636+
.map(Size::max)
637+
.filter(v -> v < Long.MAX_VALUE)
638+
.ifPresent(schema::setMaxLength);
639+
640+
} else if ("number".equals(schema.getType()) || "integer".equals(schema.getType())) {
641+
ofNullable(at.getAnnotation(Min.class)).ifPresent(a -> {
642+
schema.setMinimum(a.value());
643+
if (!a.inclusive()) {
644+
schema.setExclusiveMinimum(true);
645+
}
646+
});
647+
ofNullable(at.getAnnotation(Max.class)).ifPresent(a -> {
648+
schema.setMaximum(a.value());
649+
if (!a.inclusive()) {
650+
schema.setExclusiveMaximum(true);
651+
}
652+
});
653+
}
535654
});
536655
}
537656

Diff for: crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java

+16
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,24 @@ public interface KubernetesJSONSchemaProps {
3232

3333
void setMaximum(Double max);
3434

35+
void setExclusiveMaximum(Boolean b);
36+
3537
void setMinimum(Double min);
3638

39+
void setExclusiveMinimum(Boolean b);
40+
41+
void setMinLength(Long min);
42+
43+
void setMaxLength(Long max);
44+
45+
void setMinItems(Long min);
46+
47+
void setMaxItems(Long max);
48+
49+
void setMinProperties(Long min);
50+
51+
void setMaxProperties(Long max);
52+
3753
void setPattern(String pattern);
3854

3955
void setFormat(String format);

0 commit comments

Comments
 (0)