Skip to content

Commit

Permalink
Add crd-generator and java-generator support for Format annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
matteriben committed Apr 26, 2024
1 parent 8a19f1f commit 3c4a861
Show file tree
Hide file tree
Showing 14 changed files with 165 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* Fix #5867: (crd-generator) Imply schemaFrom via JsonFormat shape (SchemaFrom takes precedence)
* Fix #5867: (java-generator) Add JsonFormat shape to date-time
* Fix #5954: (crd-generator) Sort required properties to ensure deterministic output
* Fix #5867: (crd-generator) Add support to define `format` from `@Format` annotation
* Fix #5867: (java-generator) Add support to define `@Format` annotation from `format`

#### Dependency Upgrade

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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_FORMAT = "io.fabric8.generator.annotation.Format";
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 @@ -177,6 +178,7 @@ protected static class SchemaPropsOptions {
final String defaultValue;
final Double min;
final Double max;
final String format;
final String pattern;
final boolean nullable;
final boolean required;
Expand All @@ -187,19 +189,21 @@ protected static class SchemaPropsOptions {
defaultValue = null;
min = null;
max = null;
format = 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, Double max, String format, String pattern,
List<KubernetesValidationRule> validationRules,
boolean nullable, boolean required, boolean preserveUnknownFields) {
this.defaultValue = defaultValue;
this.min = min;
this.max = max;
this.format = format;
this.pattern = pattern;
this.nullable = nullable;
this.required = required;
Expand All @@ -219,6 +223,10 @@ public Optional<Double> getMax() {
return Optional.ofNullable(max);
}

public Optional<String> getFormat() {
return Optional.ofNullable(format);
}

public Optional<String> getPattern() {
return Optional.ofNullable(pattern);
}
Expand Down Expand Up @@ -383,6 +391,7 @@ private T internalFromImpl(TypeDef definition, LinkedHashMap<String, String> vis
facade.defaultValue,
facade.min,
facade.max,
facade.format,
facade.pattern,
facade.validationRules,
facade.nullable,
Expand Down Expand Up @@ -425,6 +434,7 @@ private static class PropertyOrAccessor {
private String defaultValue;
private Double min;
private Double max;
private String format;
private String pattern;
private List<KubernetesValidationRule> validationRules;
private boolean nullable;
Expand Down Expand Up @@ -464,6 +474,9 @@ public void process() {
case ANNOTATION_MIN:
min = (Double) a.getParameters().get(VALUE);
break;
case ANNOTATION_FORMAT:
format = (String) a.getParameters().get(VALUE);
break;
case ANNOTATION_PATTERN:
pattern = (String) a.getParameters().get(VALUE);
break;
Expand Down Expand Up @@ -526,6 +539,10 @@ public Optional<Double> getMin() {
return Optional.ofNullable(min);
}

public Optional<String> getFormat() {
return Optional.ofNullable(format);
}

public Optional<String> getPattern() {
return Optional.ofNullable(pattern);
}
Expand Down Expand Up @@ -579,6 +596,7 @@ private static class PropertyFacade {
private String defaultValue;
private Double min;
private Double max;
private String format;
private String pattern;
private boolean nullable;
private boolean required;
Expand Down Expand Up @@ -611,6 +629,7 @@ public PropertyFacade(Property property, Map<String, Method> potentialAccessors,
defaultValue = null;
min = null;
max = null;
format = null;
pattern = null;
validationRules = new LinkedList<>();
}
Expand Down Expand Up @@ -641,6 +660,7 @@ public Property process() {
defaultValue = p.getDefault().orElse(defaultValue);
min = p.getMin().orElse(min);
max = p.getMax().orElse(max);
format = p.getFormat().orElse(format);
pattern = p.getPattern().orElse(pattern);
p.getValidationRules().ifPresent(rules -> validationRules.addAll(rules));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public void addProperty(Property property, JSONSchemaPropsBuilder builder,
});
options.getMin().ifPresent(schema::setMinimum);
options.getMax().ifPresent(schema::setMaximum);
options.getFormat().ifPresent(schema::setFormat);
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 @@ -80,6 +80,7 @@ public void addProperty(Property property, JSONSchemaPropsBuilder builder,
});
options.getMin().ifPresent(schema::setMinimum);
options.getMax().ifPresent(schema::setMaximum);
options.getFormat().ifPresent(schema::setFormat);
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 @@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.fabric8.generator.annotation.Default;
import io.fabric8.generator.annotation.Format;
import io.fabric8.generator.annotation.Max;
import io.fabric8.generator.annotation.Min;
import io.fabric8.generator.annotation.Nullable;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class AnnotatedSpec {
private String numInt;
private String numFloat;
private ZonedDateTime issuedAt;
private String password;

@JsonIgnore
private int ignoredFoo;
Expand Down Expand Up @@ -97,6 +99,11 @@ public int getMin() {
return 1;
}

@Format("password")
public String getPassword() {
return password;
}

@Pattern("\\b[1-9]\\b")
public String getSingleDigit() {
return "1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,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, 20);
Map<String, JSONSchemaProps> spec = assertSchemaHasNumberOfProperties(specSchema, 21);

// check descriptions are present
assertTrue(spec.containsKey("from-field"));
Expand All @@ -125,6 +125,7 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti
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").withFormat("password").build(), spec.get("password"));
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"));
Expand Down
24 changes: 24 additions & 0 deletions doc/CRD-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,30 @@ The field will have the `maximum` property in the generated CRD, such as:
type: object
```

### io.fabric8.generator.annotation.Format

If a field or one of its accessors is annotated with `io.fabric8.generator.annotation.Format`

```java
public class ExampleSpec {
@Format("password")
String someValue;
}
```

The field will have the `format` property in the generated CRD, such as:

```yaml
spec:
properties:
someValue:
format: password
type: string
required:
- someValue
type: object
```

### io.fabric8.generator.annotation.Pattern

If a field or one of its accessors is annotated with `io.fabric8.generator.annotation.Pattern`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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;

/**
* Java representation of the {@code format} field of JSONSchemaProps.
*
* <p>
* The following formats are validated by Kubernetes:
* </p>
* <ul>
* <li>{@code bsonobjectid}: a bson object ID, i.e. a 24 characters hex string</li>
* <li>{@code uri}: an URI as parsed by Golang net/url.ParseRequestURI</li>
* <li>{@code email}: an email address as parsed by Golang net/mail.ParseAddress</li>
* <li>{@code hostname}: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].</li>
* <li>{@code ipv4}: an IPv4 IP as parsed by Golang net.ParseIP</li>
* <li>{@code ipv6}: an IPv6 IP as parsed by Golang net.ParseIP</li>
* <li>{@code cidr}: a CIDR as parsed by Golang net.ParseCIDR</li>
* <li>{@code mac}: a MAC address as parsed by Golang net.ParseMAC</li>
* <li>{@code uuid}: an UUID that allows uppercase defined by the regex
* {@code (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$}</li>
* <li>{@code uuid3}: an UUID3 that allows uppercase defined by the regex
* {@code (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$}</li>
* <li>{@code uuid4}: an UUID4 that allows uppercase defined by the regex
* {@code (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$}</li>
* <li>{@code uuid5}: an UUID5 that allows uppercase defined by the regex
* {@code (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$}</li>
* <li>{@code isbn}: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"</li>
* <li>{@code isbn10}: an ISBN10 number string like "0321751043"</li>
* <li>{@code isbn13}: an ISBN13 number string like "978-0321751041"</li>
* <li>{@code creditcard}: a credit card number defined by the regex
* {@code ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$}
* with any non digit characters mixed in</li>
* <li>{@code ssn}: a U.S. social security number following the regex {@code ^\d{3}[- ]?\d{2}[- ]?\d{4}$}</li>
* <li>{@code hexcolor}: an hexadecimal color code like "#FFFFFF: following the regex
* {@code ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$}</li>
* <li>{@code rgbcolor}: an RGB color code like rgb like "rgb(255,255,2559"</li>
* <li>{@code byte}: base64 encoded binary data</li>
* <li>{@code password}: any kind of string</li>
* <li>{@code date}: a date string like "2006-01-02" as defined by full-date in RFC3339</li>
* <li>{@code duration}: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration
* format</li>
* <li>{@code date-time}: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.</li>
* </ul>
* <p>
* Unknown formats are ignored by Kubernetes and if another consumer is unaware of the meaning of the format,
* they shall fall back to using the basic type without format.
* </p>
*
* @see <a href=
* "https://kubernetes.io/docs/reference/kubernetes-api/extend-resources/custom-resource-definition-v1/#JSONSchemaProps">
* Kubernetes Docs - API Reference - CRD v1 - JSONSchemaProps
* </a>
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Format {
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static final AnnotationExpr newGeneratedAnnotation() {

protected Double maximum;
protected Double minimum;
protected String format;
protected String pattern;

public Double getMaximum() {
Expand All @@ -66,6 +67,10 @@ public Double getMinimum() {
return minimum;
}

public String getFormat() {
return format;
}

public String getPattern() {
return pattern;
}
Expand Down Expand Up @@ -109,6 +114,7 @@ protected AbstractJSONSchema2Pojo(Config config, String description, final boole
if (validationProperties != null) {
this.maximum = validationProperties.getMaximum();
this.minimum = validationProperties.getMinimum();
this.format = validationProperties.getFormat();
this.pattern = validationProperties.getPattern();
}
}
Expand Down Expand Up @@ -268,6 +274,7 @@ private static AbstractJSONSchema2Pojo fromJsonSchema(
ValidationProperties.Builder.getInstance()
.withMaximum(prop.getMaximum())
.withMinimum(prop.getMinimum())
.withFormat(prop.getFormat())
.withPattern(prop.getPattern())
.build());
case ARRAY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ public GeneratorResult generateJava() {
new Name("io.fabric8.generator.annotation.Min"),
new DoubleLiteralExpr(prop.getMinimum())));
}
if (prop.getFormat() != null) {
objField.addAnnotation(
new SingleMemberAnnotationExpr(
new Name("io.fabric8.generator.annotation.Format"),
new StringLiteralExpr(StringEscapeUtils.escapeJava(prop.getFormat()))));
}
if (prop.getPattern() != null) {
objField.addAnnotation(
new SingleMemberAnnotationExpr(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
public class ValidationProperties {
private final Double maximum;
private final Double minimum;
private final String format;
private final String pattern;

private ValidationProperties(final Double maximum, final Double minimum, final String pattern) {
private ValidationProperties(final Double maximum, final Double minimum, final String format, final String pattern) {
this.maximum = maximum;
this.minimum = minimum;
this.format = format;
this.pattern = pattern;
}

Expand All @@ -38,13 +40,18 @@ public Double getMinimum() {
return minimum;
}

public String getFormat() {
return format;
}

public String getPattern() {
return pattern;
}

public static final class Builder {
private Double maximum;
private Double minimum;
private String format;
private String pattern;

private Builder() {
Expand All @@ -64,13 +71,18 @@ public Builder withMinimum(final Double minimum) {
return this;
}

public Builder withFormat(final String format) {
this.format = format;
return this;
}

public Builder withPattern(final String pattern) {
this.pattern = pattern;
return this;
}

public ValidationProperties build() {
return new ValidationProperties(maximum, minimum, pattern);
return new ValidationProperties(maximum, minimum, format, pattern);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ public class AkkaMicroserviceStatus implements io.fabric8.kubernetes.api.model.K
* Total number of available pods targeted by this AkkaMicroservice.
*/
@com.fasterxml.jackson.annotation.JsonProperty("availableReplicas")
@io.fabric8.generator.annotation.Format("int32")
@com.fasterxml.jackson.annotation.JsonPropertyDescription("Total number of available pods targeted by this AkkaMicroservice.")
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SKIP)
private Integer availableReplicas;
Expand Down
Loading

0 comments on commit 3c4a861

Please sign in to comment.