Skip to content

Commit

Permalink
feat(crd-generator): Add support for size constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo42 committed Apr 3, 2024
1 parent ed1f351 commit 2fce7b0
Show file tree
Hide file tree
Showing 10 changed files with 510 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public abstract class AbstractJsonSchema<T, B> {
public static final String ANNOTATION_DEFAULT = "io.fabric8.generator.annotation.Default";
public static final String ANNOTATION_MIN = "io.fabric8.generator.annotation.Min";
public static final String ANNOTATION_MAX = "io.fabric8.generator.annotation.Max";
public static final String ANNOTATION_SIZE = "io.fabric8.generator.annotation.Size";
public static final String ANNOTATION_PATTERN = "io.fabric8.generator.annotation.Pattern";
public static final String ANNOTATION_NULLABLE = "io.fabric8.generator.annotation.Nullable";
public static final String ANNOTATION_REQUIRED = "io.fabric8.generator.annotation.Required";
Expand Down Expand Up @@ -151,32 +152,53 @@ public static String getSchemaTypeFor(TypeRef typeRef) {
}

protected static class SchemaPropsOptions {
final String defaultValue;
final Double min;
final Double max;
final String pattern;
final boolean nullable;
final boolean required;
final boolean preserveUnknownFields;
final List<KubernetesValidationRule> validationRules;
private final String defaultValue;
private final Double min;
private final Long minItems;
private final Long minProperties;
private final Long minLength;
private final Double max;
private final Long maxItems;
private final Long maxProperties;
private final Long maxLength;
private final String pattern;
private final boolean nullable;
private final boolean required;
private final boolean preserveUnknownFields;
private final List<KubernetesValidationRule> validationRules;

SchemaPropsOptions() {
defaultValue = null;
min = null;
minItems = null;
minProperties = null;
minLength = null;
max = null;
maxItems = null;
maxProperties = null;
maxLength = null;
pattern = null;
nullable = false;
required = false;
preserveUnknownFields = false;
validationRules = null;
}

public SchemaPropsOptions(String defaultValue, Double min, Double max, String pattern,
public SchemaPropsOptions(String defaultValue,
Double min, Long minItems, Long minProperties, Long minLength,
Double max, Long maxItems, Long maxProperties, Long maxLength,
String pattern,
List<KubernetesValidationRule> validationRules,
boolean nullable, boolean required, boolean preserveUnknownFields) {
this.defaultValue = defaultValue;
this.min = min;
this.minItems = minItems;
this.minProperties = minProperties;
this.minLength = minLength;
this.max = max;
this.maxItems = maxItems;
this.maxProperties = maxProperties;
this.maxLength = maxLength;
this.pattern = pattern;
this.nullable = nullable;
this.required = required;
Expand All @@ -192,10 +214,34 @@ public Optional<Double> getMin() {
return Optional.ofNullable(min);
}

public Optional<Long> getMinItems() {
return Optional.ofNullable(minItems);
}

public Optional<Long> getMinProperties() {
return Optional.ofNullable(minProperties);
}

public Optional<Long> getMinLength() {
return Optional.ofNullable(minLength);
}

public Optional<Double> getMax() {
return Optional.ofNullable(max);
}

public Optional<Long> getMaxItems() {
return Optional.ofNullable(maxItems);
}

public Optional<Long> getMaxProperties() {
return Optional.ofNullable(maxProperties);
}

public Optional<Long> getMaxLength() {
return Optional.ofNullable(maxLength);
}

public Optional<String> getPattern() {
return Optional.ofNullable(pattern);
}
Expand Down Expand Up @@ -354,10 +400,40 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, InternalSche
possiblyUpdatedSchema = addDescription(schema, description);
}

Long minItems = null;
Long minProperties = null;
Long minLength = null;
Long maxItems = null;
Long maxProperties = null;
Long maxLength = null;

if (facade.size != null) {
if (io.sundr.model.utils.Types.isArray(possiblyRenamedProperty.getTypeRef())
|| io.sundr.model.utils.Types.isList(possiblyRenamedProperty.getTypeRef())) {
minItems = facade.size.getMin().orElse(null);
maxItems = facade.size.getMax().orElse(null);
} else if (io.sundr.model.utils.Types.isMap(possiblyRenamedProperty.getTypeRef())) {
minProperties = facade.size.getMin().orElse(null);
maxProperties = facade.size.getMax().orElse(null);
} else if (possiblyRenamedProperty.getTypeRef().equals(STRING_REF)) {
minLength = facade.size.getMin().orElse(null);
maxLength = facade.size.getMax().orElse(null);
} else {
LOGGER.warn("@Size is used on an unsupported type ({}) in {}.{} and is therefore ignored",
possiblyRenamedProperty.getTypeRef(), definition.getFullyQualifiedName(), possiblyRenamedProperty.getName());
}
}

SchemaPropsOptions options = new SchemaPropsOptions(
facade.defaultValue,
facade.min,
minItems,
minProperties,
minLength,
facade.max,
maxItems,
maxProperties,
maxLength,
facade.pattern,
facade.validationRules,
facade.nullable,
Expand Down Expand Up @@ -396,6 +472,7 @@ private static class PropertyOrAccessor {
private String defaultValue;
private Double min;
private Double max;
private SizeInfo size;
private String pattern;
private List<KubernetesValidationRule> validationRules;
private boolean nullable;
Expand Down Expand Up @@ -438,6 +515,9 @@ public void process() {
case ANNOTATION_PATTERN:
pattern = (String) a.getParameters().get(VALUE);
break;
case ANNOTATION_SIZE:
size = SizeInfo.from(a);
break;
case ANNOTATION_REQUIRED:
required = true;
break;
Expand Down Expand Up @@ -496,6 +576,10 @@ public Optional<String> getPattern() {
return Optional.ofNullable(pattern);
}

public Optional<SizeInfo> getSize() {
return Optional.ofNullable(size);
}

public Optional<List<KubernetesValidationRule>> getValidationRules() {
return Optional.ofNullable(validationRules);
}
Expand Down Expand Up @@ -545,6 +629,7 @@ private static class PropertyFacade {
private String defaultValue;
private Double min;
private Double max;
private SizeInfo size;
private String pattern;
private boolean nullable;
private boolean required;
Expand Down Expand Up @@ -578,6 +663,7 @@ public PropertyFacade(Property property, Map<String, Method> potentialAccessors,
min = null;
max = null;
pattern = null;
size = null;
validationRules = new LinkedList<>();
}

Expand Down Expand Up @@ -608,6 +694,7 @@ public Property process() {
min = p.getMin().orElse(min);
max = p.getMax().orElse(max);
pattern = p.getPattern().orElse(pattern);
size = p.getSize().orElse(size);
p.getValidationRules().ifPresent(rules -> validationRules.addAll(rules));

if (p.isNullable()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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;

import io.sundr.model.AnnotationRef;

import java.util.Objects;
import java.util.Optional;

public class SizeInfo {
private final Long min;
private final Long max;

private SizeInfo(Long min, Long max) {
this.min = min;
this.max = max;
}

public Optional<Long> getMin() {
return Optional.ofNullable(min);
}

public Optional<Long> getMax() {
return Optional.ofNullable(max);
}

public static SizeInfo from(AnnotationRef annotationRef) {
final Long min = (Long) annotationRef.getParameters().get("min");
final Long max = (Long) annotationRef.getParameters().get("max");

assertGreaterOrEqual(min, 0L, "min must be greater than 0");
assertGreaterOrEqual(max, 0L, "max must be greater than 0");
assertGreaterOrEqual(max, min, String.format("max (%s) must be greater or equal to min (%s)", max, min));

return new SizeInfo(mapIfNotDefault(min, 0L), mapIfNotDefault(max, Long.MAX_VALUE));
}

private static void assertGreaterOrEqual(Long x, Long y, String message) {
if (x >= y)
return;
throw new IllegalArgumentException(message);
}

private static Long mapIfNotDefault(Long value, Long defaultValue) {
return Objects.equals(value, defaultValue) ? null : value;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ public void addProperty(Property property, JSONSchemaPropsBuilder builder,
}
});
options.getMin().ifPresent(schema::setMinimum);
options.getMinLength().ifPresent(schema::setMinLength);
options.getMinItems().ifPresent(schema::setMinItems);
options.getMinProperties().ifPresent(schema::setMinProperties);
options.getMax().ifPresent(schema::setMaximum);
options.getMaxLength().ifPresent(schema::setMaxLength);
options.getMaxItems().ifPresent(schema::setMaxItems);
options.getMaxProperties().ifPresent(schema::setMaxProperties);
options.getPattern().ifPresent(schema::setPattern);

List<ValidationRule> validationRulesFromProperty = options.getValidationRules().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ public void addProperty(Property property, JSONSchemaPropsBuilder builder,
}
});
options.getMin().ifPresent(schema::setMinimum);
options.getMinLength().ifPresent(schema::setMinLength);
options.getMinItems().ifPresent(schema::setMinItems);
options.getMinProperties().ifPresent(schema::setMinProperties);
options.getMax().ifPresent(schema::setMaximum);
options.getMaxLength().ifPresent(schema::setMaxLength);
options.getMaxItems().ifPresent(schema::setMaxItems);
options.getMaxProperties().ifPresent(schema::setMaxProperties);
options.getPattern().ifPresent(schema::setPattern);

List<ValidationRule> validationRulesFromProperty = options.getValidationRules().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.example.size;

import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;

@Group("sample.fabric8.io")
@Version("v1")
public class SizeExample extends CustomResource<SizeExampleSpec, Void> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.example.size;

import io.fabric8.generator.annotation.Size;

import java.util.List;
import java.util.Map;

public class SizeExampleSpec {
@Size(min = 1, max = 3)
private String stringWithLowerAndUpperLimits;

@Size(min = 1, max = 3)
private List<String> listWithLowerAndUpperLimits;

@Size(min = 1, max = 3)
private String[] arrayWithLowerAndUpperLimits;

@Size(min = 1, max = 3)
private Map<String, String> mapWithLowerAndUpperLimits;

@Size(min = 1)
private String stringWithLowerLimit;

@Size(min = 1)
private List<String> listWithLowerLimit;

@Size(min = 1)
private String[] arrayWithLowerLimit;

@Size(min = 1)
private Map<String, String> mapWithLowerLimit;

@Size(max = 3)
private String stringWithUpperLimit;

@Size(max = 3)
private List<String> listWithUpperLimit;

@Size(max = 3)
private String[] arrayWithUpperLimit;

@Size(max = 3)
private Map<String, String> mapWithUpperLimit;

@Size(min = 1, max = 3) // ignored, because int is not supported (use @Min and/or @Max)
private int integerWithIgnoredSizeLimits;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.fabric8.crd.example.k8svalidation.K8sValidation;
import io.fabric8.crd.example.multiple.v2.MultipleSpec;
import io.fabric8.crd.example.nonconversion.NoneConversionExample;
import io.fabric8.crd.example.size.SizeExample;
import io.fabric8.crd.example.webhookconversion.v1.WebhookConversionExample;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
Expand Down Expand Up @@ -65,6 +66,11 @@ void noneConversion() {
assertCRDOutputEquals(newCRDGenerator(), NoneConversionExample.class);
}

@Test
void size() {
assertCRDOutputEquals(newCRDGenerator(), SizeExample.class);
}

private CRDGenerator newCRDGenerator() {
return new CRDGenerator()
.withParallelGenerationEnabled(parallelCRDGeneration);
Expand Down
Loading

0 comments on commit 2fce7b0

Please sign in to comment.