Skip to content

Commit

Permalink
fix: allow limited use of type annotations
Browse files Browse the repository at this point in the history
closes: fabric8io#6282

Signed-off-by: Steve Hawkins <[email protected]>
  • Loading branch information
shawkins committed Aug 30, 2024
1 parent 781441a commit 995f8c2
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Fix #6008: removing the optional dependency on bouncy castle
* Fix #6230: introduced Quantity.multiply(int) to allow for Quantity multiplication by an integer
* Fix #6281: use GitHub binary repo for Kube API Tests
* Fix #6282: Allow annotated types with Pattern, Min, and Max with Lists and Maps and CRD generation

#### Dependency Upgrade
* Fix #6052: Removed dependency on no longer maintained com.github.mifmif:generex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
Expand Down Expand Up @@ -143,7 +145,7 @@ private T resolveRoot(Class<?> definition) {
return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata");
}
return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null,
resolvingContext.objectMapper.getSerializationConfig().constructType(definition), schema);
resolvingContext.objectMapper.getSerializationConfig().constructType(definition), schema, null);
}

/**
Expand Down Expand Up @@ -225,8 +227,8 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) {
StringSchema stringSchema = value.asStringSchema();
// only set if ValidationSchemaFactoryWrapper is used
this.pattern = stringSchema.getPattern();
this.max = ofNullable(stringSchema.getMaxLength()).map(Integer::doubleValue).orElse(null);
this.min = ofNullable(stringSchema.getMinLength()).map(Integer::doubleValue).orElse(null);
//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
}
Expand Down Expand Up @@ -333,7 +335,7 @@ private T resolveObject(LinkedHashMap<String, String> visited, InternalSchemaSwa
type = resolvingContext.objectMapper.getSerializationConfig().constructType(propertyMetadata.schemaFrom);
}

T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema);
T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema, beanProperty);

propertyMetadata.updateSchema(schema);

Expand Down Expand Up @@ -378,15 +380,19 @@ static String toFQN(LinkedHashMap<String, String> visited, String name) {
}

private T resolveProperty(LinkedHashMap<String, String> visited, InternalSchemaSwaps schemaSwaps, String name,
JavaType type, JsonSchema jacksonSchema) {
JavaType type, JsonSchema jacksonSchema, BeanProperty beanProperty) {

if (jacksonSchema.isArraySchema()) {
Items items = jacksonSchema.asArraySchema().getItems();
if (items == null) { // raw collection
throw new IllegalStateException(String.format("Untyped collection %s", name));
}
if (items.isArrayItems()) {
throw new IllegalStateException("not yet supported");
}
JsonSchema arraySchema = jacksonSchema.asArraySchema().getItems().asSingleItems().getSchema();
final T schema = resolveProperty(visited, schemaSwaps, name, type.getContentType(), arraySchema);
final T schema = resolveProperty(visited, schemaSwaps, name, type.getContentType(), arraySchema, null);
handleTypeAnnotations(schema, beanProperty, List.class, 0);
return arrayLikeProperty(schema);
} else if (jacksonSchema.isIntegerSchema()) {
return singleProperty("integer");
Expand Down Expand Up @@ -440,7 +446,8 @@ private T resolveProperty(LinkedHashMap<String, String> visited, InternalSchemaS
final JavaType valueType = type.getContentType();
JsonSchema mapValueSchema = ((SchemaAdditionalProperties) ((ObjectSchema) jacksonSchema).getAdditionalProperties())
.getJsonSchema();
T component = resolveProperty(visited, schemaSwaps, name, valueType, mapValueSchema);
T component = resolveProperty(visited, schemaSwaps, name, valueType, mapValueSchema, null);
handleTypeAnnotations(component, beanProperty, Map.class, 1);
return mapLikeProperty(component);
}

Expand All @@ -464,8 +471,19 @@ private T resolveProperty(LinkedHashMap<String, String> visited, InternalSchemaS
return res;
}

private void handleTypeAnnotations(final T schema, BeanProperty beanProperty, Class<?> containerType, int typeIndex) {
Optional<AnnotatedType> annotatedType = Optional.of(beanProperty.getMember().getAnnotated())
.filter(Field.class::isInstance).map(Field.class::cast).filter(f -> f.getType().equals(containerType))
.map(Field::getAnnotatedType).filter(AnnotatedParameterizedType.class::isInstance)
.map(AnnotatedParameterizedType.class::cast)
.map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments).map(a -> a[typeIndex]);
annotatedType.map(at -> at.getAnnotation(Pattern.class)).ifPresent(a -> schema.setPattern(a.value()));
annotatedType.map(at -> at.getAnnotation(Min.class)).ifPresent(a -> schema.setMinimum(a.value()));
annotatedType.map(at -> at.getAnnotation(Max.class)).ifPresent(a -> schema.setMaximum(a.value()));
}

/**
* we've added support for ignoring an enum values, which complicates this processing
* we've added support for ignoring enum values, which complicates this processing
* as that is something not supported directly by jackson
*/
private Set<String> findIgnoredEnumConstants(JavaType type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
*/
package io.fabric8.crdv2.example.person;

import io.fabric8.generator.annotation.Max;
import io.fabric8.generator.annotation.Min;
import io.fabric8.generator.annotation.Pattern;

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

public class Person {
Expand All @@ -24,7 +29,8 @@ public class Person {
public Optional<String> middleName;
public String lastName;
public int birthYear;
public List<String> hobbies;
public List<@Pattern(".*ball.*") String> hobbies;
public Map<String, @Min(1) @Max(255) Integer> attributes;
public AddressList addresses;
public Type type;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder;
import io.fabric8.kubernetes.api.model.apiextensions.v1.ValidationRule;
import io.fabric8.kubernetes.api.model.coordination.v1.LeaseSpec;
import io.fabric8.kubernetes.client.utils.Serialization;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Node;

Expand Down Expand Up @@ -80,7 +81,7 @@ void shouldCreateAnyTypeWithoutProperties() {
void shouldCreateJsonSchemaFromClass() {
JSONSchemaProps schema = JsonSchema.from(Person.class);
assertNotNull(schema);
Map<String, JSONSchemaProps> properties = assertSchemaHasNumberOfProperties(schema, 7);
Map<String, JSONSchemaProps> properties = assertSchemaHasNumberOfProperties(schema, 8);
final List<String> personTypes = properties.get("type").getEnum().stream().map(JsonNode::asText)
.collect(Collectors.toList());
assertEquals(2, personTypes.size());
Expand All @@ -95,6 +96,10 @@ void shouldCreateJsonSchemaFromClass() {
assertEquals(2, addressTypes.size());
assertTrue(addressTypes.contains("home"));
assertTrue(addressTypes.contains("work"));
assertEquals(".*ball.*", properties.get("hobbies").getItems()
.getSchema().getPattern());
assertEquals(255, properties.get("attributes").getAdditionalProperties().getSchema().getMaximum());
assertEquals(1.0, properties.get("attributes").getAdditionalProperties().getSchema().getMinimum());

schema = JsonSchema.from(Basic.class);
assertNotNull(schema);
Expand All @@ -116,8 +121,10 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti
assertNotNull(schema);
Map<String, JSONSchemaProps> properties = assertSchemaHasNumberOfProperties(schema, 2);
final JSONSchemaProps specSchema = properties.get("spec");
Map<String, JSONSchemaProps> spec = assertSchemaHasNumberOfProperties(specSchema, 20);
Map<String, JSONSchemaProps> spec = assertSchemaHasNumberOfProperties(specSchema, 21);

System.out.println(Serialization.asYaml(spec.get("foos")));

// check descriptions are present
assertTrue(spec.containsKey("from-field"));
JSONSchemaProps prop = spec.get("from-field");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
*/
package io.fabric8.generator.annotation;

import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Java representation of the {@code maximum} field of JSONSchemaProps.
Expand All @@ -25,7 +28,7 @@
* Kubernetes Docs - API Reference - CRD v1 - JSONSchemaProps
* </a>
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Max {
double value();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
*/
package io.fabric8.generator.annotation;

import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Java representation of the {@code minimum} field of JSONSchemaProps.
Expand All @@ -25,7 +28,7 @@
* Kubernetes Docs - API Reference - CRD v1 - JSONSchemaProps
* </a>
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Min {
double value();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
*/
package io.fabric8.generator.annotation;

import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Java representation of the {@code pattern} field of JSONSchemaProps.
Expand All @@ -25,7 +28,7 @@
* Kubernetes Docs - API Reference - CRD v1 - JSONSchemaProps
* </a>
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Pattern {
String value();
Expand Down

0 comments on commit 995f8c2

Please sign in to comment.