diff --git a/CHANGELOG.md b/CHANGELOG.md index d578f44fd13..2f47e3a7283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ #### Improvements * Fix #3069: (crd-generator) Add `@AdditionalPrinterColumn` to specify a printer column by JSON path. * Fix #6392: (crd-generator) Add `@AdditionalSelectableField` and `@SelectableField` to specify selectable fields. +* Fix #5836: (crd-generator) Add `@Size` annotation to limit the size of strings, lists/arrays or maps +* Fix #5868: (crd-generator) Add `exlusiveMinimum` / `exclusiveMaximum` support to `@Min` and `@Max` * Fix #5264: Remove deprecated `Config.errorMessages` field * Fix #6008: removing the optional dependency on bouncy castle * Fix #6407: sundrio builder-annotations is not available via bom import diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index c1f5109cb40..3b7202b0ebb 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -22,7 +22,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; +import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema; import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema.Items; +import com.fasterxml.jackson.module.jsonSchema.types.IntegerSchema; +import com.fasterxml.jackson.module.jsonSchema.types.NumberSchema; import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema.SchemaAdditionalProperties; import com.fasterxml.jackson.module.jsonSchema.types.ReferenceSchema; @@ -43,6 +46,7 @@ import io.fabric8.generator.annotation.Nullable; import io.fabric8.generator.annotation.Pattern; import io.fabric8.generator.annotation.Required; +import io.fabric8.generator.annotation.Size; import io.fabric8.generator.annotation.ValidationRule; import io.fabric8.generator.annotation.ValidationRules; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; @@ -230,8 +234,16 @@ class PropertyMetadata { private final String description; private final Object defaultValue; private Double min; + private Boolean exclusiveMinimum; private Double max; + private Boolean exclusiveMaximum; private String pattern; + private Long minLength; + private Long maxLength; + private Long minItems; + private Long maxItems; + private Long minProperties; + private Long maxProperties; private boolean nullable; private String format; private List validationRules = new ArrayList<>(); @@ -254,15 +266,55 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) { if (value.isStringSchema()) { StringSchema stringSchema = value.asStringSchema(); // only set if ValidationSchemaFactoryWrapper is used - this.pattern = stringSchema.getPattern(); - //this.maxLength = ofNullable(stringSchema.getMaxLength()).map(Integer::doubleValue).orElse(null); - //this.minLength = ofNullable(stringSchema.getMinLength()).map(Integer::doubleValue).orElse(null); - } else { - // TODO: process the other schema types for validation values + pattern = ofNullable(beanProperty.getAnnotation(Pattern.class)).map(Pattern::value) + .or(() -> ofNullable(stringSchema.getPattern())) + .orElse(null); + minLength = findMinInSizeAnnotation(beanProperty) + .or(() -> ofNullable(stringSchema.getMinLength()).map(Integer::longValue)) + .orElse(null); + maxLength = findMaxInSizeAnnotation(beanProperty) + .or(() -> ofNullable(stringSchema.getMaxLength()).map(Integer::longValue)) + .orElse(null); + } else if (value.isIntegerSchema()) { + // integerschema extends numberschema and must handled first + IntegerSchema integerSchema = value.asIntegerSchema(); + setMinMax(beanProperty, + integerSchema.getMinimum(), + integerSchema.getExclusiveMinimum(), + integerSchema.getMaximum(), + integerSchema.getExclusiveMaximum()); + } else if (value.isNumberSchema()) { + NumberSchema numberSchema = value.asNumberSchema(); + setMinMax(beanProperty, + numberSchema.getMinimum(), + numberSchema.getExclusiveMinimum(), + numberSchema.getMaximum(), + numberSchema.getExclusiveMaximum()); + } else if (value.isArraySchema()) { + ArraySchema arraySchema = value.asArraySchema(); + minItems = findMinInSizeAnnotation(beanProperty) + .or(() -> ofNullable(arraySchema.getMinItems()).map(Integer::longValue)) + .orElse(null); + maxItems = findMaxInSizeAnnotation(beanProperty) + .or(() -> ofNullable(arraySchema.getMaxItems()).map(Integer::longValue)) + .orElse(null); + } else if (value.isObjectSchema()) { + // TODO: Could be also applied only on Maps instead of "all the rest" + minProperties = findMinInSizeAnnotation(beanProperty) + .orElse(null); + maxProperties = findMaxInSizeAnnotation(beanProperty) + .orElse(null); } collectValidationRules(beanProperty, validationRules); + // TODO: should probably move to a standard annotations + // see ValidationSchemaFactoryWrapper + nullable = beanProperty.getAnnotation(Nullable.class) != null; + + // TODO: should the following be deprecated? + required = beanProperty.getAnnotation(Required.class) != null; + if (beanProperty.getMetadata().getDefaultValue() != null) { defaultValue = toTargetType(beanProperty.getType(), beanProperty.getMetadata().getDefaultValue()); } else if (ofNullable(beanProperty.getAnnotation(Default.class)).map(Default::value).isPresent()) { @@ -271,16 +323,34 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) { } else { defaultValue = null; } + } - // TODO: should probably move to a standard annotations - // see ValidationSchemaFactoryWrapper - nullable = beanProperty.getAnnotation(Nullable.class) != null; - max = ofNullable(beanProperty.getAnnotation(Max.class)).map(Max::value).orElse(max); - min = ofNullable(beanProperty.getAnnotation(Min.class)).map(Min::value).orElse(min); - - // TODO: should the following be deprecated? - required = beanProperty.getAnnotation(Required.class) != null; - pattern = ofNullable(beanProperty.getAnnotation(Pattern.class)).map(Pattern::value).orElse(pattern); + private void setMinMax(BeanProperty beanProperty, + Double minimum, Boolean exclusiveMinimum, Double maximum, Boolean exclusiveMaximum) { + ofNullable(minimum).ifPresent(v -> { + this.min = v; + if (Boolean.TRUE.equals(exclusiveMinimum)) { + this.exclusiveMinimum = true; + } + }); + ofNullable(beanProperty.getAnnotation(Min.class)).ifPresent(a -> { + min = a.value(); + if (!a.inclusive()) { + this.exclusiveMinimum = true; + } + }); + ofNullable(maximum).ifPresent(v -> { + this.max = v; + if (Boolean.TRUE.equals(exclusiveMaximum)) { + this.exclusiveMaximum = true; + } + }); + ofNullable(beanProperty.getAnnotation(Max.class)).ifPresent(a -> { + this.max = a.value(); + if (!a.inclusive()) { + this.exclusiveMaximum = true; + } + }); } public void updateSchema(T schema) { @@ -294,10 +364,22 @@ public void updateSchema(T schema) { } } if (nullable) { - schema.setNullable(nullable); + schema.setNullable(true); } schema.setMaximum(max); + schema.setExclusiveMaximum(exclusiveMaximum); schema.setMinimum(min); + schema.setExclusiveMinimum(exclusiveMinimum); + + schema.setMinLength(minLength); + schema.setMaxLength(maxLength); + + schema.setMinItems(minItems); + schema.setMaxItems(maxItems); + + schema.setMinProperties(minProperties); + schema.setMaxProperties(maxProperties); + schema.setPattern(pattern); schema.setFormat(format); if (preserveUnknownFields) { @@ -306,6 +388,18 @@ public void updateSchema(T schema) { addToValidationRules(schema, validationRules); } + + private Optional findMinInSizeAnnotation(BeanProperty beanProperty) { + return ofNullable(beanProperty.getAnnotation(Size.class)) + .map(Size::min) + .filter(v -> v > 0); + } + + private Optional findMaxInSizeAnnotation(BeanProperty beanProperty) { + return ofNullable(beanProperty.getAnnotation(Size.class)) + .map(Size::max) + .filter(v -> v < Long.MAX_VALUE); + } } private T resolveObject(LinkedHashMap visited, InternalSchemaSwaps schemaSwaps, JsonSchema jacksonSchema, @@ -514,24 +608,49 @@ private void handleTypeAnnotations(final T schema, BeanProperty beanProperty, Cl AnnotatedElement member = beanProperty.getMember().getAnnotated(); AnnotatedType fieldType = null; - AnnotatedType type = null; + AnnotatedType methodType = null; if (member instanceof Field) { fieldType = ((Field) member).getAnnotatedType(); } else if (member instanceof Method) { fieldType = getFieldForMethod(beanProperty).map(Field::getAnnotatedType).orElse(null); - type = ((Method) member).getAnnotatedReceiverType(); + methodType = ((Method) member).getAnnotatedReceiverType(); } - Stream.of(fieldType, type) + Stream.of(fieldType, methodType) .filter(o -> !Objects.isNull(o)) .filter(AnnotatedParameterizedType.class::isInstance) .map(AnnotatedParameterizedType.class::cast) .map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments) .map(a -> a[typeIndex]) .forEach(at -> { - Optional.ofNullable(at.getAnnotation(Pattern.class)).ifPresent(a -> schema.setPattern(a.value())); - Optional.ofNullable(at.getAnnotation(Min.class)).ifPresent(a -> schema.setMinimum(a.value())); - Optional.ofNullable(at.getAnnotation(Max.class)).ifPresent(a -> schema.setMaximum(a.value())); + if ("string".equals(schema.getType())) { + ofNullable(at.getAnnotation(Pattern.class)) + .ifPresent(a -> schema.setPattern(a.value())); + + ofNullable(at.getAnnotation(Size.class)) + .map(Size::min) + .filter(v -> v > 0) + .ifPresent(schema::setMinLength); + + ofNullable(at.getAnnotation(Size.class)) + .map(Size::max) + .filter(v -> v < Long.MAX_VALUE) + .ifPresent(schema::setMaxLength); + + } else if ("number".equals(schema.getType()) || "integer".equals(schema.getType())) { + ofNullable(at.getAnnotation(Min.class)).ifPresent(a -> { + schema.setMinimum(a.value()); + if (!a.inclusive()) { + schema.setExclusiveMinimum(true); + } + }); + ofNullable(at.getAnnotation(Max.class)).ifPresent(a -> { + schema.setMaximum(a.value()); + if (!a.inclusive()) { + schema.setExclusiveMaximum(true); + } + }); + } }); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java index e548f08b517..c2ca4708f93 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java @@ -32,8 +32,24 @@ public interface KubernetesJSONSchemaProps { void setMaximum(Double max); + void setExclusiveMaximum(Boolean b); + void setMinimum(Double min); + void setExclusiveMinimum(Boolean b); + + void setMinLength(Long min); + + void setMaxLength(Long max); + + void setMinItems(Long min); + + void setMaxItems(Long max); + + void setMinProperties(Long min); + + void setMaxProperties(Long max); + void setPattern(String pattern); void setFormat(String format); diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaValidationTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaValidationTest.java new file mode 100644 index 00000000000..0ffec7ac77b --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaValidationTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator.v1; + +import io.fabric8.generator.annotation.Max; +import io.fabric8.generator.annotation.Min; +import io.fabric8.generator.annotation.Size; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import lombok.Getter; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class JsonSchemaValidationTest { + + @Getter + private static final class ClassInTest { + @Min(1) + @Max(3) + private Integer integerMin1Max3; + + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private Integer integerMinExclusive1MaxExclusive3; + + @Size(min = 1, max = 3) + private String stringMin1Max3; + + @Size(min = 1, max = 3) + private List listMin1Max3; + + @Size(min = 1, max = 3) + private Map mapMin1Max3; + } + + @Test + @DisplayName("Min and Max can be combined") + void integerMin1Max3() { + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("integerMin1Max3")) + .extracting(JSONSchemaProps::getMinimum) + .isEqualTo(1.0); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("integerMin1Max3")) + .extracting(JSONSchemaProps::getMaximum) + .isEqualTo(3.0); + } + + @Test + @DisplayName("Exclusive Min and exclusive Max can be combined") + void integerMinExclusive1MaxExclusive3() { + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("integerMinExclusive1MaxExclusive3")) + .extracting(JSONSchemaProps::getMinimum) + .isEqualTo(1.0); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("integerMinExclusive1MaxExclusive3")) + .extracting(JSONSchemaProps::getExclusiveMinimum) + .isEqualTo(true); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("integerMinExclusive1MaxExclusive3")) + .extracting(JSONSchemaProps::getMaximum) + .isEqualTo(3.0); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("integerMinExclusive1MaxExclusive3")) + .extracting(JSONSchemaProps::getExclusiveMaximum) + .isEqualTo(true); + } + + @Test + @DisplayName("Strings can have size limits") + void stringMin1Max3() { + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("stringMin1Max3")) + .extracting(JSONSchemaProps::getMinLength) + .isEqualTo(1L); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("stringMin1Max3")) + .extracting(JSONSchemaProps::getMaxLength) + .isEqualTo(3L); + } + + @Test + @DisplayName("Lists can have size limits") + void listMin1Max3() { + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("listMin1Max3")) + .extracting(JSONSchemaProps::getMinItems) + .isEqualTo(1L); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("listMin1Max3")) + .extracting(JSONSchemaProps::getMaxItems) + .isEqualTo(3L); + } + + @Test + @DisplayName("Maps can have size limits") + void mapMin1Max3() { + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("mapMin1Max3")) + .extracting(JSONSchemaProps::getMinProperties) + .isEqualTo(1L); + + assertThat(JsonSchema.from(ClassInTest.class).getProperties()) + .extracting(props -> props.get("mapMin1Max3")) + .extracting(JSONSchemaProps::getMaxProperties) + .isEqualTo(3L); + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java index 411c97cb341..dcb5806bb52 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java @@ -29,7 +29,7 @@ class SpecReplicasPathTest { @Test - public void shoudDetectSpecReplicasPath() throws Exception { + void shouldDetectSpecReplicasPath() { JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithStatusProperty.class); Optional path = resolver.getSinglePath(SpecReplicas.class); assertTrue(path.isPresent()); @@ -37,7 +37,7 @@ public void shoudDetectSpecReplicasPath() throws Exception { } @Test - public void shoudDetectNestedSpecReplicasPath() throws Exception { + void shouldDetectNestedSpecReplicasPath() { JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithSpec.class); Optional path = resolver.getSinglePath(SpecReplicas.class); assertTrue(path.isPresent()); diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java index 5dc859a5935..144042e26cf 100644 --- a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java @@ -31,6 +31,7 @@ import io.fabric8.crd.generator.approvaltests.replica.Replica; import io.fabric8.crd.generator.approvaltests.required.Required; import io.fabric8.crd.generator.approvaltests.selectablefield.SelectableField; +import io.fabric8.crd.generator.approvaltests.validation.Validation; import io.fabric8.kubernetes.client.CustomResource; import io.sundr.utils.Strings; import org.approvaltests.Approvals; @@ -185,6 +186,7 @@ static Stream crdApprovalCasesApiV2(String crdVersion) { cases.add(new TestCase("printercolumns.sample.fabric8.io", crdVersion, parallel, PrinterColumn.class)); cases.add(new TestCase("requireds.samples.fabric8.io", crdVersion, parallel, Required.class)); cases.add(new TestCase("selectablefields.sample.fabric8.io", crdVersion, parallel, SelectableField.class)); + cases.add(new TestCase("validations.sample.fabric8.io", crdVersion, parallel, Validation.class)); } return cases.stream(); } diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/validation/Validation.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/validation/Validation.java new file mode 100644 index 00000000000..6a03c467811 --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/validation/Validation.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crd.generator.approvaltests.validation; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Version("v1alpha1") +@Group("sample.fabric8.io") +public class Validation extends CustomResource { +} diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/validation/ValidationSpec.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/validation/ValidationSpec.java new file mode 100644 index 00000000000..282abdaee43 --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/validation/ValidationSpec.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crd.generator.approvaltests.validation; + +import io.fabric8.generator.annotation.Max; +import io.fabric8.generator.annotation.Min; +import io.fabric8.generator.annotation.Pattern; +import io.fabric8.generator.annotation.Size; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class ValidationSpec { + + private ValidationOnInteger onInteger; + private ValidationOnIntegerPrim onIntegerPrim; + private ValidationOnLong onLong; + private ValidationOnLongPrim onLongPrim; + private ValidationOnFloat onFloat; + private ValidationOnFloatPrim onFloatPrim; + private ValidationOnDouble onDouble; + private ValidationOnDoublePrim onDoublePrim; + private ValidationOnString onString; + private ValidationOnList onList; + private ValidationOnMap onMap; + + @Data + static class ValidationOnInteger { + @Min(1) + private Integer minimum1; + @Min(value = 1, inclusive = false) + private Integer minimumExclusive1; + @Max(1) + private Integer maximum1; + @Max(value = 1, inclusive = false) + private Integer maximumExclusive1; + @Min(1) + @Max(3) + private Integer minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private Integer minimumExclusive1MaximumExclusive3; + + private List<@Min(1) Integer> listItemMin1; + private List<@Max(1) Integer> listItemMax1; + private List<@Min(1) @Max(3) Integer> listItemMin1Max3; + + private Map mapItemMin1; + private Map mapItemMax1; + private Map mapItemMin1Max3; + } + + @Data + static class ValidationOnIntegerPrim { + @Min(1) + private int minimum1; + @Min(value = 1, inclusive = false) + private int minimumExclusive1; + @Max(1) + private int maximum1; + @Max(value = 1, inclusive = false) + private int maximumExclusive1; + @Min(1) + @Max(3) + private int minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private int minimumExclusive1MaximumExclusive3; + } + + @Data + static class ValidationOnLong { + @Min(1) + private Long minimum1; + @Min(value = 1, inclusive = false) + private Long minimumExclusive1; + @Max(1) + private Long maximum1; + @Max(value = 1, inclusive = false) + private Long maximumExclusive1; + @Min(1) + @Max(3) + private Long minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private Long minimumExclusive1MaximumExclusive3; + + private List<@Min(1) Long> listItemMin1; + private List<@Max(1) Long> listItemMax1; + private List<@Min(1) @Max(3) Long> listItemMin1Max3; + + private Map mapItemMin1; + private Map mapItemMax1; + private Map mapItemMin1Max3; + } + + @Data + static class ValidationOnLongPrim { + @Min(1) + private long minimum1; + @Min(value = 1, inclusive = false) + private long minimumExclusive1; + @Max(1) + private long maximum1; + @Max(value = 1, inclusive = false) + private long maximumExclusive1; + @Min(1) + @Max(3) + private long minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private long minimumExclusive1MaximumExclusive3; + } + + @Data + static class ValidationOnFloat { + @Min(1) + private Float minimum1; + @Min(value = 1, inclusive = false) + private Float minimumExclusive1; + @Max(1) + private Float maximum1; + @Max(value = 1, inclusive = false) + private Float maximumExclusive1; + @Min(1) + @Max(3) + private Float minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private Float minimumExclusive1MaximumExclusive3; + + private List<@Min(1) Float> listItemMin1; + private List<@Max(1) Float> listItemMax1; + private List<@Min(1) @Max(3) Float> listItemMin1Max3; + + private Map mapItemMin1; + private Map mapItemMax1; + private Map mapItemMin1Max3; + } + + @Data + static class ValidationOnFloatPrim { + @Min(1) + private float minimum1; + @Min(value = 1, inclusive = false) + private float minimumExclusive1; + @Max(1) + private float maximum1; + @Max(value = 1, inclusive = false) + private float maximumExclusive1; + @Min(1) + @Max(3) + private float minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private float minimumExclusive1MaximumExclusive3; + } + + @Data + static class ValidationOnDouble { + @Min(1) + private Double minimum1; + @Min(value = 1, inclusive = false) + private Double minimumExclusive1; + @Max(1) + private Double maximum1; + @Max(value = 1, inclusive = false) + private Double maximumExclusive1; + @Min(1) + @Max(3) + private Double minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private Double minimumExclusive1MaximumExclusive3; + + private List<@Min(1) Double> listItemMin1; + private List<@Max(1) Double> listItemMax1; + private List<@Min(1) @Max(3) Double> listItemMin1Max3; + + private Map mapItemMin1; + private Map mapItemMax1; + private Map mapItemMin1Max3; + } + + @Data + static class ValidationOnDoublePrim { + @Min(1) + private double minimum1; + @Min(value = 1, inclusive = false) + private double minimumExclusive1; + @Max(1) + private double maximum1; + @Max(value = 1, inclusive = false) + private double maximumExclusive1; + @Min(1) + @Max(3) + private double minimum1Maximum3; + @Min(value = 1, inclusive = false) + @Max(value = 3, inclusive = false) + private double minimumExclusive1MaximumExclusive3; + } + + @Data + static class ValidationOnString { + @Pattern("(a|b)+") + private String pattern; + + private List<@Pattern("(a|b)+") String> listItemPattern; + private Map mapItemPattern; + + @Size(min = 1) + private String minLength1; + @Size(max = 1) + private String maxLength1; + @Size(min = 1, max = 3) + private String minLength1maxLength3; + + private List<@Size(min = 1, max = 3) String> listItemMinLength1MaxLength3; + private Map mapItemMinLength1MaxLength3; + } + + @Data + static class ValidationOnList { + @Size(min = 1) + private List minItems1; + @Size(max = 1) + private List maxItems1; + @Size(min = 1, max = 3) + private List minItems1maxItems3; + } + + @Data + static class ValidationOnMap { + @Size(min = 1) + private Map minItems1; + @Size(max = 1) + private Map maxItems1; + @Size(min = 1, max = 3) + private Map minItems1maxItems3; + } + +} diff --git a/crd-generator/test/src/test/resources/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.approvalTest.validations.sample.fabric8.io.v1.approved.yml b/crd-generator/test/src/test/resources/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.approvalTest.validations.sample.fabric8.io.v1.approved.yml new file mode 100644 index 00000000000..3c600e80ec6 --- /dev/null +++ b/crd-generator/test/src/test/resources/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.approvalTest.validations.sample.fabric8.io.v1.approved.yml @@ -0,0 +1,445 @@ +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" +metadata: + name: "validations.sample.fabric8.io" +spec: + group: "sample.fabric8.io" + names: + kind: "Validation" + plural: "validations" + singular: "validation" + scope: "Cluster" + versions: + - name: "v1alpha1" + schema: + openAPIV3Schema: + properties: + spec: + properties: + onDouble: + properties: + listItemMax1: + items: + maximum: 1.0 + type: "number" + type: "array" + listItemMin1: + items: + minimum: 1.0 + type: "number" + type: "array" + listItemMin1Max3: + items: + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "array" + mapItemMax1: + additionalProperties: + maximum: 1.0 + type: "number" + type: "object" + mapItemMin1: + additionalProperties: + minimum: 1.0 + type: "number" + type: "object" + mapItemMin1Max3: + additionalProperties: + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "object" + maximum1: + maximum: 1.0 + type: "number" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "number" + minimum1: + minimum: 1.0 + type: "number" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "number" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "number" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "object" + onDoublePrim: + properties: + maximum1: + maximum: 1.0 + type: "number" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "number" + minimum1: + minimum: 1.0 + type: "number" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "number" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "number" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "object" + onFloat: + properties: + listItemMax1: + items: + maximum: 1.0 + type: "number" + type: "array" + listItemMin1: + items: + minimum: 1.0 + type: "number" + type: "array" + listItemMin1Max3: + items: + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "array" + mapItemMax1: + additionalProperties: + maximum: 1.0 + type: "number" + type: "object" + mapItemMin1: + additionalProperties: + minimum: 1.0 + type: "number" + type: "object" + mapItemMin1Max3: + additionalProperties: + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "object" + maximum1: + maximum: 1.0 + type: "number" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "number" + minimum1: + minimum: 1.0 + type: "number" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "number" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "number" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "object" + onFloatPrim: + properties: + maximum1: + maximum: 1.0 + type: "number" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "number" + minimum1: + minimum: 1.0 + type: "number" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "number" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "number" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "number" + type: "object" + onInteger: + properties: + listItemMax1: + items: + maximum: 1.0 + type: "integer" + type: "array" + listItemMin1: + items: + minimum: 1.0 + type: "integer" + type: "array" + listItemMin1Max3: + items: + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "array" + mapItemMax1: + additionalProperties: + maximum: 1.0 + type: "integer" + type: "object" + mapItemMin1: + additionalProperties: + minimum: 1.0 + type: "integer" + type: "object" + mapItemMin1Max3: + additionalProperties: + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "object" + maximum1: + maximum: 1.0 + type: "integer" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "integer" + minimum1: + minimum: 1.0 + type: "integer" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "integer" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "integer" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "object" + onIntegerPrim: + properties: + maximum1: + maximum: 1.0 + type: "integer" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "integer" + minimum1: + minimum: 1.0 + type: "integer" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "integer" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "integer" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "object" + onList: + properties: + maxItems1: + items: + type: "string" + maxItems: 1 + type: "array" + minItems1: + items: + type: "string" + minItems: 1 + type: "array" + minItems1maxItems3: + items: + type: "string" + maxItems: 3 + minItems: 1 + type: "array" + type: "object" + onLong: + properties: + listItemMax1: + items: + maximum: 1.0 + type: "integer" + type: "array" + listItemMin1: + items: + minimum: 1.0 + type: "integer" + type: "array" + listItemMin1Max3: + items: + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "array" + mapItemMax1: + additionalProperties: + maximum: 1.0 + type: "integer" + type: "object" + mapItemMin1: + additionalProperties: + minimum: 1.0 + type: "integer" + type: "object" + mapItemMin1Max3: + additionalProperties: + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "object" + maximum1: + maximum: 1.0 + type: "integer" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "integer" + minimum1: + minimum: 1.0 + type: "integer" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "integer" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "integer" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "object" + onLongPrim: + properties: + maximum1: + maximum: 1.0 + type: "integer" + maximumExclusive1: + exclusiveMaximum: true + maximum: 1.0 + type: "integer" + minimum1: + minimum: 1.0 + type: "integer" + minimum1Maximum3: + maximum: 3.0 + minimum: 1.0 + type: "integer" + minimumExclusive1: + exclusiveMinimum: true + minimum: 1.0 + type: "integer" + minimumExclusive1MaximumExclusive3: + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 3.0 + minimum: 1.0 + type: "integer" + type: "object" + onMap: + properties: + maxItems1: + additionalProperties: + type: "string" + maxProperties: 1 + type: "object" + minItems1: + additionalProperties: + type: "string" + minProperties: 1 + type: "object" + minItems1maxItems3: + additionalProperties: + type: "string" + maxProperties: 3 + minProperties: 1 + type: "object" + type: "object" + onString: + properties: + listItemMinLength1MaxLength3: + items: + maxLength: 3 + minLength: 1 + type: "string" + type: "array" + listItemPattern: + items: + pattern: "(a|b)+" + type: "string" + type: "array" + mapItemMinLength1MaxLength3: + additionalProperties: + maxLength: 3 + minLength: 1 + type: "string" + type: "object" + mapItemPattern: + additionalProperties: + pattern: "(a|b)+" + type: "string" + type: "object" + maxLength1: + maxLength: 1 + type: "string" + minLength1: + minLength: 1 + type: "string" + minLength1maxLength3: + maxLength: 3 + minLength: 1 + type: "string" + pattern: + pattern: "(a|b)+" + type: "string" + type: "object" + type: "object" + status: + type: "object" + type: "object" + served: true + storage: true diff --git a/doc/CRD-generator.md b/doc/CRD-generator.md index b016c71ab07..307fd384156 100644 --- a/doc/CRD-generator.md +++ b/doc/CRD-generator.md @@ -217,8 +217,20 @@ The field will have the `minimum` property in the generated CRD, such as: someValue: minimum: -1.0 type: integer - required: - - someValue + type: object +``` + +By default, the minimum value is *inclusive*. + +If the value should be *exclusive* use `@Min(value = -1, inclusive = false)`: + +```yaml + spec: + properties: + someValue: + minimum: -1.0 + exclusiveMinimum: true + type: integer type: object ``` @@ -241,8 +253,60 @@ The field will have the `maximum` property in the generated CRD, such as: someValue: maximum: 1.0 type: integer - required: - - someValue + type: object +``` + +By default, the maximum value is *inclusive*. + +If the value should be *exclusive* use `@Max(value = 1, inclusive = false)`: + +```yaml + spec: + properties: + someValue: + maximum: 1.0 + exclusiveMaximum: true + type: integer + type: object +``` + +### io.fabric8.generator.annotation.Size + +If a field or one of its accessors is annotated with `io.fabric8.generator.annotation.Size` + +```java +public class ExampleSpec { + @Size(min = 1, max = 3) + String stringValue; + @Size(min = 1, max = 3) + List listValue; + @Size(min = 1, max = 3) + Map mapValue; +} +``` + +The field will have the `minLength`/`maxLength`, `minItems`/`maxItems`, `minProperties`/`maxProperties` properties +in the generated CRD depending on the type: + +```yaml + spec: + properties: + stringValue: + maxLength: 3 + minLength: 1 + type: string + listValue: + items: + type: "string" + maxItems: 3 + minItems: 1 + type: "array" + mapValue: + additionalProperties: + type: "string" + maxProperties: 3 + minProperties: 1 + type: "object" type: object ``` @@ -764,31 +828,32 @@ spec: ## Features cheatsheet -| Annotation | Description | -|-----------------------------------------------------------------|---------------------------------------------------------------------------------------------| -| `com.fasterxml.jackson.annotation.JsonProperty` | The field is named after the provided value instead of looking up the java field name | -| `com.fasterxml.jackson.annotation.JsonPropertyDescription` | The provided text is be embedded in the `description` of the field | -| `com.fasterxml.jackson.annotation.JsonIgnore` | The field is ignored | -| `io.fabric8.crd.generator.annotation.PreserveUnknownFields` | The field have `x-kubernetes-preserve-unknown-fields: true` defined | -| `com.fasterxml.jackson.annotation.JsonAnyGetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | -| `com.fasterxml.jackson.annotation.JsonAnySetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | -| `io.fabric8.generator.annotation.Min` | The field defines a validation `min` | -| `io.fabric8.generator.annotation.Max` | The field defines a validation `max` | -| `io.fabric8.generator.annotation.Pattern` | The field defines a validation `pattern` | -| `io.fabric8.generator.annotation.Nullable` | The field is marked as `nullable` | -| `io.fabric8.generator.annotation.Required` | The field is marked as `required` | -| `io.fabric8.generator.annotation.ValidationRule` | The field or object is validated by a CEL rule | -| `io.fabric8.crd.generator.annotation.SchemaFrom` | The field type for the generation is the one coming from the annotation | -| `io.fabric8.crd.generator.annotation.SchemaSwap` | Similar to SchemaFrom, but can be applied at any point in the class hierarchy | -| `io.fabric8.crd.generator.annotation.Annotations` | Additional `annotations` in `metadata` | -| `io.fabric8.crd.generator.annotation.Labels` | Additional `labels` in `metadata` | -| `io.fabric8.crd.generator.annotation.PrinterColumn` | Define the field as PrinterColumn, so that it will be shown by the `kubectl get` command. | -| `io.fabric8.crd.generator.annotation.AdditionalPrinterColumn` | Define a PrinterColumn by JSON path, so that it will be shown by the `kubectl get` command. | -| `io.fabric8.crd.generator.annotation.SelectableField` | Define the field as selectable, so that it can be used for filtering. | -| `io.fabric8.crd.generator.annotation.AdditionalSelectableField` | Define a SelectableField by JSON path, so that it can be used for filtering. | -| `io.fabric8.kubernetes.model.annotation.SpecReplicas` | The field is used in scale subresource as `specReplicaPath` | -| `io.fabric8.kubernetes.model.annotation.StatusReplicas` | The field is used in scale subresource as `statusReplicaPath` | -| `io.fabric8.kubernetes.model.annotation.LabelSelector` | The field is used in scale subresource as `labelSelectorPath` | +| Annotation | Description | +|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `com.fasterxml.jackson.annotation.JsonProperty` | The field is named after the provided value instead of looking up the java field name | +| `com.fasterxml.jackson.annotation.JsonPropertyDescription` | The provided text is be embedded in the `description` of the field | +| `com.fasterxml.jackson.annotation.JsonIgnore` | The field is ignored | +| `io.fabric8.crd.generator.annotation.PreserveUnknownFields` | The field have `x-kubernetes-preserve-unknown-fields: true` defined | +| `com.fasterxml.jackson.annotation.JsonAnyGetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | +| `com.fasterxml.jackson.annotation.JsonAnySetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | +| `io.fabric8.generator.annotation.Min` | The field's `minimum` value | +| `io.fabric8.generator.annotation.Max` | The field's `maximum` value | +| `io.fabric8.generator.annotation.Size` | The field (string, list/array, map) has size limits (`minLength`, `minItems`, `minProperties`, ...) | +| `io.fabric8.generator.annotation.Pattern` | The field defines a validation `pattern` | +| `io.fabric8.generator.annotation.Nullable` | The field is marked as `nullable` | +| `io.fabric8.generator.annotation.Required` | The field is marked as `required` | +| `io.fabric8.generator.annotation.ValidationRule` | The field or object is validated by a CEL rule | +| `io.fabric8.crd.generator.annotation.SchemaFrom` | The field type for the generation is the one coming from the annotation | +| `io.fabric8.crd.generator.annotation.SchemaSwap` | Similar to SchemaFrom, but can be applied at any point in the class hierarchy | +| `io.fabric8.crd.generator.annotation.Annotations` | Additional `annotations` in `metadata` | +| `io.fabric8.crd.generator.annotation.Labels` | Additional `labels` in `metadata` | +| `io.fabric8.crd.generator.annotation.PrinterColumn` | Define the field as PrinterColumn, so that it will be shown by the `kubectl get` command. | +| `io.fabric8.crd.generator.annotation.AdditionalPrinterColumn` | Define a PrinterColumn by JSON path, so that it will be shown by the `kubectl get` command. | +| `io.fabric8.crd.generator.annotation.SelectableField` | Define the field as selectable, so that it can be used for filtering. | +| `io.fabric8.crd.generator.annotation.AdditionalSelectableField` | Define a SelectableField by JSON path, so that it can be used for filtering. | +| `io.fabric8.kubernetes.model.annotation.SpecReplicas` | The field is used in scale subresource as `specReplicaPath` | +| `io.fabric8.kubernetes.model.annotation.StatusReplicas` | The field is used in scale subresource as `statusReplicaPath` | +| `io.fabric8.kubernetes.model.annotation.LabelSelector` | The field is used in scale subresource as `labelSelectorPath` | A field of type `com.fasterxml.jackson.databind.JsonNode` is encoded as an empty object with `x-kubernetes-preserve-unknown-fields: true` defined. diff --git a/generator-annotations/src/main/java/io/fabric8/generator/annotation/Max.java b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Max.java index 7489307428b..ebeddebfca5 100644 --- a/generator-annotations/src/main/java/io/fabric8/generator/annotation/Max.java +++ b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Max.java @@ -31,5 +31,17 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE }) @Retention(RetentionPolicy.RUNTIME) public @interface Max { + /** + * @return the value of the maximum constraint + */ double value(); + + /** + * Specifies whether the specified maximum is inclusive or exclusive. + * By default, it is inclusive. + * + * @return {@code true} if the value must be lower or equal to the specified maximum, + * {@code false} if the value must be lower + */ + boolean inclusive() default true; } diff --git a/generator-annotations/src/main/java/io/fabric8/generator/annotation/Min.java b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Min.java index 2a472ceedb4..6694d5d615f 100644 --- a/generator-annotations/src/main/java/io/fabric8/generator/annotation/Min.java +++ b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Min.java @@ -31,5 +31,17 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE }) @Retention(RetentionPolicy.RUNTIME) public @interface Min { + /** + * @return the value of the minimum constraint + */ double value(); + + /** + * Specifies whether the specified minimum is inclusive or exclusive. + * By default, it is inclusive. + * + * @return {@code true} if the value must be higher or equal to the specified minimum, + * {@code false} if the value must be higher + */ + boolean inclusive() default true; } diff --git a/generator-annotations/src/main/java/io/fabric8/generator/annotation/Size.java b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Size.java new file mode 100644 index 00000000000..e56550511d9 --- /dev/null +++ b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Size.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.generator.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Decorates the resulting property with size limits. + *
+ * The annotation can be used on strings, list/arrays and maps and will result in an appropriate JSON Schema constraint: + *
    + *
  • {@code minLength} and/or {@code maxLength} for a String
  • + *
  • {@code minItems} and/or {@code maxItems} for a list/array
  • + *
  • {@code minProperties} and/or {@code maxProperties} for a map
  • + *
+ * + * @see + * Kubernetes Docs - API Reference - CRD v1 - JSONSchemaProps + * + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Size { + /** + * @return the minimum value (inclusive) + */ + long min() default 0; + + /** + * @return the maximum value (inclusive) + */ + long max() default Long.MAX_VALUE; +}