Skip to content

Commit

Permalink
fix fabric8io#5233: allowing schemaswap to have a configurable level
Browse files Browse the repository at this point in the history
  • Loading branch information
shawkins authored and manusa committed Jun 20, 2023
1 parent 5d9c41d commit aacfd29
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#### Improvements
* Fix #5166: Remove opinionated messages from Config's `errorMessages` and deprecate it
* Fix #5233: Generalized SchemaSwap to allow for cycle expansion

#### Dependency Upgrade

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,7 @@ public boolean isPreserveUnknownFields() {
*/
protected T internalFrom(TypeDef definition, String... ignore) {
InternalSchemaSwaps schemaSwaps = new InternalSchemaSwaps();
T ret = internalFromImpl(definition, new HashSet<>(), schemaSwaps, ignore);
schemaSwaps.throwIfUnmatchedSwaps();
return ret;
return internalFromImpl(definition, new HashSet<>(), schemaSwaps, ignore);
}

private static ClassRef extractClassRef(Object type) {
Expand Down Expand Up @@ -244,15 +242,15 @@ private void extractSchemaSwap(ClassRef definitionType, Object annotation, Inter
schemaSwaps.registerSwap(definitionType,
extractClassRef(schemaSwap.originalType()),
schemaSwap.fieldName(),
extractClassRef(schemaSwap.targetType()));
extractClassRef(schemaSwap.targetType()), schemaSwap.depth());

} else if (annotation instanceof AnnotationRef
&& ((AnnotationRef) annotation).getClassRef().getFullyQualifiedName().equals(ANNOTATION_SCHEMA_SWAP)) {
Map<String, Object> params = ((AnnotationRef) annotation).getParameters();
schemaSwaps.registerSwap(definitionType,
extractClassRef(params.get("originalType")),
(String) params.get("fieldName"),
extractClassRef(params.getOrDefault("targetType", void.class)));
extractClassRef(params.getOrDefault("targetType", void.class)), (Integer) params.getOrDefault("depth", 0));

} else {
throw new IllegalArgumentException("Unmanaged annotation type passed to the SchemaSwaps: " + annotation);
Expand All @@ -273,19 +271,25 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, InternalSche

boolean preserveUnknownFields = isJsonNode;

definition.getAnnotations().forEach(annotation -> extractSchemaSwaps(definition.toReference(), annotation, schemaSwaps));
schemaSwaps = schemaSwaps.branchAnnotations();
final InternalSchemaSwaps swaps = schemaSwaps;
definition.getAnnotations().forEach(annotation -> extractSchemaSwaps(definition.toReference(), annotation, swaps));

// index potential accessors by name for faster lookup
final Map<String, Method> accessors = indexPotentialAccessors(definition);

for (Property property : definition.getProperties()) {
if (isJsonNode) {
break;
}
String name = property.getName();
if (property.isStatic() || ignores.contains(name)) {
LOGGER.debug("Ignoring property {}", name);
continue;
}

ClassRef potentialSchemaSwap = schemaSwaps.lookupAndMark(definition.toReference(), name).orElse(null);
schemaSwaps = schemaSwaps.branchDepths();
ClassRef potentialSchemaSwap = schemaSwaps.lookupAndMark(definition.toReference(), name, visited::clear);
final PropertyFacade facade = new PropertyFacade(property, accessors, potentialSchemaSwap);
final Property possiblyRenamedProperty = facade.process();
name = possiblyRenamedProperty.getName();
Expand Down Expand Up @@ -320,6 +324,7 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, InternalSche
addProperty(possiblyRenamedProperty, builder, possiblyUpdatedSchema, options);
}

swaps.throwIfUnmatchedSwaps();
return build(builder, required, preserveUnknownFields);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,71 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;

public class InternalSchemaSwaps {
private final Map<Key, Value> swaps = new HashMap<>();
// swaps applicable above this point
private final Map<Key, Value> parentSwaps;
// swaps applicable to the current context
private final Map<Key, Value> swaps;
// current depths of all swaps
private final Map<Key, Integer> swapDepths;

public InternalSchemaSwaps() {
this(new HashMap<>(), new HashMap<>(), new HashMap<>());
}

private InternalSchemaSwaps(Map<Key, Value> swaps, Map<Key, Integer> swapDepths, Map<Key, Value> parentSwaps) {
this.parentSwaps = parentSwaps;
this.swaps = swaps;
this.swapDepths = swapDepths;
}

public InternalSchemaSwaps branchDepths() {
InternalSchemaSwaps result = new InternalSchemaSwaps(this.swaps, new HashMap<>(), this.parentSwaps);
result.swapDepths.putAll(this.swapDepths);
return result;
}

public InternalSchemaSwaps branchAnnotations() {
Map<Key, Value> combined = new HashMap<>(swaps);
combined.putAll(parentSwaps);
InternalSchemaSwaps result = new InternalSchemaSwaps(new HashMap<>(), this.swapDepths, combined);
return result;
}

public void registerSwap(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType) {
Value value = new Value(definitionType, originalType, fieldName, targetType);
swaps.put(new Key(originalType, fieldName), value);
public void registerSwap(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType,
int depth) {
Value value = new Value(definitionType, originalType, fieldName, targetType, depth);
Key key = new Key(originalType, fieldName);
if (parentSwaps.containsKey(key)) {
// it's simplest for now to just disallow this
throw new IllegalArgumentException("Nested SchemaSwap: " + value);
}
if (swaps.put(key, value) != null) {
throw new IllegalArgumentException("Duplicate SchemaSwap: " + value);
}
}

public Optional<ClassRef> lookupAndMark(ClassRef originalType, String name) {
Value value = swaps.get(new Key(originalType, name));
public ClassRef lookupAndMark(ClassRef originalType, String name, Runnable swapApplicableAction) {
Key key = new Key(originalType, name);
Value value = swaps.getOrDefault(key, parentSwaps.get(key));
if (value != null) {
if (value.depth > 0) {
swapApplicableAction.run();
}
int currentDepth = swapDepths.getOrDefault(key, 0);
swapDepths.put(key, currentDepth+1);
value.markUsed();
return Optional.of(value.getTargetType());
} else {
return Optional.empty();
if (currentDepth == value.depth) {
return value.getTargetType();
}
if (currentDepth > value.depth) {
throw new IllegalStateException("Somthing has gone wrong with tracking swap depths, please raise an issue.");
}
}
return null;
}

public void throwIfUnmatchedSwaps() {
Expand Down Expand Up @@ -100,12 +145,14 @@ private static class Value {
private final ClassRef targetType;
private boolean used;
private final ClassRef definitionType;
private final int depth;

public Value(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType) {
public Value(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType, int depth) {
this.definitionType = definitionType;
this.originalType = originalType;
this.fieldName = fieldName;
this.targetType = targetType;
this.depth = depth;
this.used = false;
}

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

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

/**
* Annotation that allows replacing a nested schema with one from another class.
Expand Down Expand Up @@ -52,4 +56,12 @@
* The default value of {@code void.class} causes the field to be skipped
*/
Class<?> targetType() default void.class;

/**
* For fields that may include a reference cycle, how many expansions to include in the output before including the
* {@link #targetType()}
* <p>
* The default value of 0 replaces the field with the {@link #targetType()} without any expansion
*/
int depth() default 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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.extraction;

import io.fabric8.crd.generator.annotation.SchemaSwap;
import io.fabric8.kubernetes.api.model.AnyType;
import io.fabric8.kubernetes.client.CustomResource;

import java.util.List;

@SchemaSwap(originalType = CollectionCyclicSchemaSwap.Level.class, fieldName = "levels", targetType = AnyType.class, depth = 2)
public class CollectionCyclicSchemaSwap extends CustomResource<CollectionCyclicSchemaSwap.Spec, Void> {

public static class Spec {
private MyObject myObject;
private List<Level> levels;
}

public static class Level {
private MyObject myObject;
private List<Level> levels;
}

public static class MyObject {
private int value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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.extraction;

import io.fabric8.crd.generator.annotation.SchemaSwap;
import io.fabric8.kubernetes.client.CustomResource;

import java.util.List;

@SchemaSwap(originalType = CyclicSchemaSwap.Level.class, fieldName = "level", depth = 1)
public class CyclicSchemaSwap extends CustomResource<CyclicSchemaSwap.Spec, Void> {

public static class Spec {
private MyObject myObject;
private Level root;
private List<Level> roots; // should not interfere with the rendering depth of level of its sibling
}

public static class Level {
private MyObject myObject;
private Level level;
}

public static class MyObject {
private int value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* 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.extraction;

import io.fabric8.crd.generator.annotation.SchemaSwap;
import io.fabric8.kubernetes.client.CustomResource;

public class NestedSchemaSwap extends CustomResource<NestedSchemaSwap.Spec, Void> {

@SchemaSwap(originalType = End.class, fieldName = "value", targetType = String.class)
public static class Spec {
private Intermediate one;
private Intermediate another;
}

@SchemaSwap(originalType = End.class, fieldName = "value", targetType = Void.class)
public static class Intermediate {
private End one;
}

public static class End {
private int value;
}
}
Loading

0 comments on commit aacfd29

Please sign in to comment.