Skip to content

Commit

Permalink
feat: support for JsonFormat shape in crd-generator and java-generator
Browse files Browse the repository at this point in the history
  • Loading branch information
matteriben authored Apr 24, 2024
1 parent b8eeca4 commit a8fba97
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 57 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#### Improvements
* Fix #5878: (java-generator) Add implements Editable for extraAnnotations
* Fix #5878: (java-generator) Update documentation to include dependencies
* Fix #5867: (crd-generator) Imply schemaFrom via JsonFormat shape (SchemaFrom takes precedence)
* Fix #5867: (java-generator) Add JsonFormat shape to date-time

#### Dependency Upgrade

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.fabric8.crd.generator;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
Expand Down Expand Up @@ -91,6 +92,8 @@ public abstract class AbstractJsonSchema<T, B> {
protected static final TypeDef DATE = TypeDef.forName(Date.class.getName());
protected static final TypeRef DATE_REF = DATE.toReference();

private static final String JSON_FORMAT_SHAPE = "shape";
private static final Map<JsonFormat.Shape, TypeRef> JSON_FORMAT_SHAPE_MAPPING = new HashMap<>();
private static final String VALUE = "value";

private static final String INT_OR_STRING_MARKER = "int_or_string";
Expand All @@ -107,6 +110,7 @@ public abstract class AbstractJsonSchema<T, B> {
.build();

private static final Map<TypeRef, String> COMMON_MAPPINGS = new HashMap<>();
public static final String ANNOTATION_JSON_FORMAT = "com.fasterxml.jackson.annotation.JsonFormat";
public static final String ANNOTATION_JSON_PROPERTY = "com.fasterxml.jackson.annotation.JsonProperty";
public static final String ANNOTATION_JSON_PROPERTY_DESCRIPTION = "com.fasterxml.jackson.annotation.JsonPropertyDescription";
public static final String ANNOTATION_JSON_IGNORE = "com.fasterxml.jackson.annotation.JsonIgnore";
Expand Down Expand Up @@ -151,6 +155,12 @@ public abstract class AbstractJsonSchema<T, B> {
// initialize with client defaults
new KubernetesSerialization(mapper, false);
GENERATOR = new JsonSchemaGenerator(mapper);

JSON_FORMAT_SHAPE_MAPPING.put(JsonFormat.Shape.BOOLEAN, Types.typeDefFrom(Boolean.class).toReference());
JSON_FORMAT_SHAPE_MAPPING.put(JsonFormat.Shape.NUMBER, Types.typeDefFrom(Double.class).toReference());
JSON_FORMAT_SHAPE_MAPPING.put(JsonFormat.Shape.NUMBER_FLOAT, Types.typeDefFrom(Double.class).toReference());
JSON_FORMAT_SHAPE_MAPPING.put(JsonFormat.Shape.NUMBER_INT, Types.typeDefFrom(Long.class).toReference());
JSON_FORMAT_SHAPE_MAPPING.put(JsonFormat.Shape.STRING, Types.typeDefFrom(String.class).toReference());
}

public static String getSchemaTypeFor(TypeRef typeRef) {
Expand Down Expand Up @@ -456,6 +466,11 @@ public void process() {
case ANNOTATION_REQUIRED:
required = true;
break;
case ANNOTATION_JSON_FORMAT:
if (schemaFrom == null) {
schemaFrom = JSON_FORMAT_SHAPE_MAPPING.get((JsonFormat.Shape) a.getParameters().get(JSON_FORMAT_SHAPE));
}
break;
case ANNOTATION_JSON_PROPERTY:
final String nameFromAnnotation = (String) a.getParameters().get(VALUE);
if (!Strings.isNullOrEmpty(nameFromAnnotation) && !propertyName.equals(nameFromAnnotation)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.fabric8.crd.example.annotated;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
Expand All @@ -27,6 +28,8 @@
import io.fabric8.generator.annotation.ValidationRule;
import lombok.Data;

import java.time.ZonedDateTime;

@Data
public class AnnotatedSpec {
@JsonProperty("from-field")
Expand All @@ -49,6 +52,11 @@ public class AnnotatedSpec {
private AnnotatedEnum anEnum;
@javax.validation.constraints.Min(0) // a non-string value attribute
private int sizedField;
private String bool;
private String num;
private String numInt;
private String numFloat;
private ZonedDateTime issuedAt;

@JsonIgnore
private int ignoredFoo;
Expand Down Expand Up @@ -114,6 +122,31 @@ public void setEmptySetter2(boolean emptySetter2) {
this.emptySetter2 = emptySetter2;
}

@JsonFormat(shape = JsonFormat.Shape.BOOLEAN)
public String getBool() {
return bool;
}

@JsonFormat(shape = JsonFormat.Shape.NUMBER)
public String getNum() {
return num;
}

@JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT)
public String getNumFloat() {
return numFloat;
}

@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT)
public String getNumInt() {
return numInt;
}

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssVV")
public java.time.ZonedDateTime getIssuedAt() {
return issuedAt;
}

public enum AnnotatedEnum {
non("N"),
@JsonProperty("oui")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
import io.fabric8.crd.example.annotated.Annotated;
import io.fabric8.crd.example.basic.Basic;
import io.fabric8.crd.example.extraction.CollectionCyclicSchemaSwap;
Expand All @@ -32,16 +33,17 @@
import io.fabric8.crd.generator.utils.Types;
import io.fabric8.kubernetes.api.model.AnyType;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder;
import io.fabric8.kubernetes.api.model.apiextensions.v1.ValidationRule;
import io.sundr.model.TypeDef;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static io.fabric8.crd.generator.CRDGenerator.YAML_MAPPER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -103,7 +105,7 @@ 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, 15);
Map<String, JSONSchemaProps> spec = assertSchemaHasNumberOfProperties(specSchema, 20);

// check descriptions are present
assertTrue(spec.containsKey("from-field"));
Expand All @@ -120,47 +122,19 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti
assertNull(spec.get("emptySetter").getDescription());
assertTrue(spec.containsKey("anEnum"));

final JSONSchemaProps min = spec.get("min");
assertNull(min.getDefault());
assertEquals(-5.0, min.getMinimum());
assertNull(min.getMaximum());
assertNull(min.getPattern());
assertNull(min.getNullable());

final JSONSchemaProps max = spec.get("max");
assertNull(max.getDefault());
assertEquals(5.0, max.getMaximum());
assertNull(max.getMinimum());
assertNull(max.getPattern());
assertNull(max.getNullable());

final JSONSchemaProps pattern = spec.get("singleDigit");
assertNull(pattern.getDefault());
assertEquals("\\b[1-9]\\b", pattern.getPattern());
assertNull(pattern.getMinimum());
assertNull(pattern.getMaximum());
assertNull(pattern.getNullable());

final JSONSchemaProps nullable = spec.get("nullable");
assertNull(nullable.getDefault());
assertTrue(nullable.getNullable());
assertNull(nullable.getMinimum());
assertNull(nullable.getMaximum());
assertNull(nullable.getPattern());

final JSONSchemaProps defaultValue = spec.get("defaultValue");
assertEquals("my-value", YAML_MAPPER.writeValueAsString(defaultValue.getDefault()).trim());
assertNull(defaultValue.getNullable());
assertNull(defaultValue.getMinimum());
assertNull(defaultValue.getMaximum());
assertNull(defaultValue.getPattern());

final JSONSchemaProps defaultValue2 = spec.get("defaultValue2");
assertEquals("my-value2", YAML_MAPPER.writeValueAsString(defaultValue2.getDefault()).trim());
assertNull(defaultValue2.getNullable());
assertNull(defaultValue2.getMinimum());
assertNull(defaultValue2.getMaximum());
assertNull(defaultValue2.getPattern());
Function<String, JSONSchemaPropsBuilder> type = t -> new JSONSchemaPropsBuilder().withType(t);
assertEquals(type.apply("integer").withMinimum(-5.0).build(), spec.get("min"));
assertEquals(type.apply("integer").withMaximum(5.0).build(), spec.get("max"));
assertEquals(type.apply("string").withPattern("\\b[1-9]\\b").build(), spec.get("singleDigit"));
assertEquals(type.apply("string").withNullable(true).build(), spec.get("nullable"));
assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value")).build(), spec.get("defaultValue"));
assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value2")).build(), spec.get("defaultValue2"));
assertEquals(type.apply("string").withEnum(TextNode.valueOf("non"), TextNode.valueOf("oui")).build(), spec.get("anEnum"));
assertEquals(type.apply("boolean").build(), spec.get("bool"));
assertEquals(type.apply("number").build(), spec.get("num"));
assertEquals(type.apply("number").build(), spec.get("numFloat"));
assertEquals(type.apply("integer").build(), spec.get("numInt"));
assertEquals(type.apply("string").build(), spec.get("issuedAt"));

// check required list, should register properties with their modified name if needed
final List<String> required = specSchema.getRequired();
Expand All @@ -169,12 +143,6 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti
assertTrue(required.contains("emptySetter2"));
assertTrue(required.contains("from-getter"));

// check the enum values
final JSONSchemaProps anEnum = spec.get("anEnum");
final List<JsonNode> enumValues = anEnum.getEnum();
assertEquals(2, enumValues.size());
enumValues.stream().map(JsonNode::textValue).forEach(s -> assertTrue("oui".equals(s) || "non".equals(s)));

// check ignored fields
assertFalse(spec.containsKey("ignoredFoo"));
assertFalse(spec.containsKey("ignoredBar"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,13 @@ public GeneratorResult generateJava() {
MethodDeclaration fieldSetter = objField.createSetter();

if (prop.getClassType().equals(DATETIME_NAME)) {
final String jsonFormat = "com.fasterxml.jackson.annotation.JsonFormat";
fieldGetter.addAnnotation(new SingleMemberAnnotationExpr(
new Name("com.fasterxml.jackson.annotation.JsonFormat"),
new NameExpr("pattern = \"" + config.getSerDatetimeFormat() + "\"")));
new Name(jsonFormat),
new NameExpr("shape = " + jsonFormat + ".Shape.STRING, pattern = \"" + config.getSerDatetimeFormat() + "\"")));
fieldSetter.addAnnotation(new SingleMemberAnnotationExpr(
new Name("com.fasterxml.jackson.annotation.JsonFormat"),
new NameExpr("pattern = \"" + config.getDeserDatetimeFormat() + "\"")));
new Name(jsonFormat),
new NameExpr("shape = " + jsonFormat + ".Shape.STRING, pattern = \"" + config.getDeserDatetimeFormat() + "\"")));
}

if (isRequired) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ public class CronTabSpec implements io.fabric8.kubernetes.api.model.KubernetesRe
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SKIP)
private java.time.ZonedDateTime issuedAt;

@com.fasterxml.jackson.annotation.JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssVV")
@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssVV")
public java.time.ZonedDateTime getIssuedAt() {
return issuedAt;
}

@com.fasterxml.jackson.annotation.JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss[XXX][VV]")
@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss[XXX][VV]")
public void setIssuedAt(java.time.ZonedDateTime issuedAt) {
this.issuedAt = issuedAt;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ public class CronTabSpec implements io.fabric8.kubernetes.api.builder.Editable<C
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SKIP)
private java.time.ZonedDateTime issuedAt;

@com.fasterxml.jackson.annotation.JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssVV")
@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssVV")
public java.time.ZonedDateTime getIssuedAt() {
return issuedAt;
}

@com.fasterxml.jackson.annotation.JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss[XXX][VV]")
@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss[XXX][VV]")
public void setIssuedAt(java.time.ZonedDateTime issuedAt) {
this.issuedAt = issuedAt;
}
Expand Down

0 comments on commit a8fba97

Please sign in to comment.