Compare commits

..

No commits in common. "32a8321c0e64d6a96a3526e9d21a71c419e4700e" and "c2cd6c2f23599e1808345655d7628676be58620e" have entirely different histories.

7 changed files with 22 additions and 54 deletions

View File

@ -127,14 +127,12 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
new HsHostingAssetEntityPatcher(em, current).apply(body); new HsHostingAssetEntityPatcher(em, current).apply(body);
// TODO.refa: draft for an alternative API
// validate(current)/ / self-validation, hashing passwords etc. // validate(current)/ / self-validation, hashing passwords etc.
// .then(HsHostingAssetEntityValidatorRegistry::prepareForSave) // hashing passwords etc. // .then(HsHostingAssetEntityValidatorRegistry::prepareForSave) // hashing passwords etc.
// .then(assetRepo::save) // .then(assetRepo::save)
// .then(HsHostingAssetEntityValidatorRegistry::validateInContext) // .then(HsHostingAssetEntityValidatorRegistry::validateInContext)
// // In this last step we need the entity and the mapped resource instance, // .then(this::mapToResource) using postprocessProperties to remove write-only + add read-only properties
// // which is exactly what a postmapper takes as arguments.
// .then(this::mapToResource) using postProcessProperties to remove write-only + add read-only properties
final var saved = validated(assetRepo.save(current)); final var saved = validated(assetRepo.save(current));
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);

View File

@ -52,7 +52,7 @@ public class HsHostingAssetEntityValidatorRegistry {
public static void postprocessProperties(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) { public static void postprocessProperties(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) {
final var validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
final var config = validator.postProcess(entity, asMap(resource)); final var config = validator.postprocess(entity, asMap(resource));
resource.setConfig(config); resource.setConfig(config);
} }

View File

@ -89,7 +89,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value); throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
} }
public Map<String, Object> postProcess(final E entity, final Map<String, Object> config) { public Map<String, Object> postprocess(final E entity, final Map<String, Object> config) {
final var copy = new HashMap<>(config); final var copy = new HashMap<>(config);
stream(propertyValidators).forEach(p -> { stream(propertyValidators).forEach(p -> {
if ( p.writeOnly) { if ( p.writeOnly) {

View File

@ -11,7 +11,7 @@ public class IntegerProperty extends ValidatableProperty<Integer> {
private final static String[] KEY_ORDER = Array.join( private final static String[] KEY_ORDER = Array.join(
ValidatableProperty.KEY_ORDER_HEAD, ValidatableProperty.KEY_ORDER_HEAD,
Array.of("unit", "min", "minFrom", "max", "maxFrom", "step"), Array.of("unit", "min", "max", "step"),
ValidatableProperty.KEY_ORDER_TAIL); ValidatableProperty.KEY_ORDER_TAIL);
private String unit; private String unit;

View File

@ -12,10 +12,9 @@ public class StringProperty extends ValidatableProperty<String> {
private static final String[] KEY_ORDER = Array.join( private static final String[] KEY_ORDER = Array.join(
ValidatableProperty.KEY_ORDER_HEAD, ValidatableProperty.KEY_ORDER_HEAD,
Array.of("matchesRegEx", "minLength", "maxLength"), Array.of("values"),
ValidatableProperty.KEY_ORDER_TAIL, ValidatableProperty.KEY_ORDER_TAIL);
Array.of("hidden")); private Pattern regExPattern;
private Pattern matchesRegEx;
private Integer minLength; private Integer minLength;
private Integer maxLength; private Integer maxLength;
private boolean hidden; private boolean hidden;
@ -39,7 +38,7 @@ public class StringProperty extends ValidatableProperty<String> {
} }
public StringProperty matchesRegEx(final String regExPattern) { public StringProperty matchesRegEx(final String regExPattern) {
this.matchesRegEx = Pattern.compile(regExPattern); this.regExPattern = Pattern.compile(regExPattern);
return this; return this;
} }
@ -56,8 +55,8 @@ public class StringProperty extends ValidatableProperty<String> {
if (maxLength != null && propValue.length()>maxLength) { if (maxLength != null && propValue.length()>maxLength) {
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length()); result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length());
} }
if (matchesRegEx != null && !matchesRegEx.matcher(propValue).matches()) { if (regExPattern != null && !regExPattern.matcher(propValue).matches()) {
result.add(propertyName + "' is expected to be match " + matchesRegEx + " but " + display(propValue) + " does not match"); result.add(propertyName + "' is expected to be match " + regExPattern + " but " + display(propValue) + " does not match");
} }
if (readOnly && propValue != null) { if (readOnly && propValue != null) {
result.add(propertyName + "' is readonly but given as " + display(propValue)); result.add(propertyName + "' is readonly but given as " + display(propValue));

View File

@ -25,7 +25,7 @@ import static java.util.Optional.ofNullable;
public abstract class ValidatableProperty<T> { public abstract class ValidatableProperty<T> {
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName"); protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage"); protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "isTotalsValidator", "thresholdPercentage");
final Class<T> type; final Class<T> type;
final String propertyName; final String propertyName;
@ -33,7 +33,6 @@ public abstract class ValidatableProperty<T> {
private Boolean required; private Boolean required;
private T defaultValue; private T defaultValue;
protected Function<PropertiesProvider, T> computedBy; protected Function<PropertiesProvider, T> computedBy;
protected boolean computed; // used in descriptor, because computedBy cannot be rendered to a text string
protected boolean readOnly; protected boolean readOnly;
protected boolean writeOnly; protected boolean writeOnly;
@ -178,32 +177,26 @@ public abstract class ValidatableProperty<T> {
// Add entries according to the given order // Add entries according to the given order
for (String key : keyOrder) { for (String key : keyOrder) {
final Optional<Object> propValue = getPropertyValue(key); final Optional<Object> propValue = getPropertyValue(key);
propValue.filter(ValidatableProperty::isToBeRendered).ifPresent(o -> sortedMap.put(key, o)); propValue.ifPresent(o -> sortedMap.put(key, o));
} }
return sortedMap; return sortedMap;
} }
private static boolean isToBeRendered(final Object v) {
return !(v instanceof Boolean b) || b;
}
@SneakyThrows @SneakyThrows
private Optional<Object> getPropertyValue(final String key) { private Optional<Object> getPropertyValue(final String key) {
return getPropertyValue(getClass(), key);
}
@SneakyThrows
private Optional<Object> getPropertyValue(final Class<?> clazz, final String key) {
try { try {
final var field = clazz.getDeclaredField(key); final var field = getClass().getDeclaredField(key);
field.setAccessible(true); field.setAccessible(true);
return Optional.ofNullable(arrayToList(field.get(this))); return Optional.ofNullable(arrayToList(field.get(this)));
} catch (final NoSuchFieldException exc) { } catch (final NoSuchFieldException e1) {
if (clazz.getSuperclass() != null) { try {
return getPropertyValue(clazz.getSuperclass(), key); final var field = getClass().getSuperclass().getDeclaredField(key);
field.setAccessible(true);
return Optional.ofNullable(arrayToList(field.get(this)));
} catch (final NoSuchFieldException e2) {
return Optional.empty();
} }
throw exc;
} }
} }
@ -227,7 +220,6 @@ public abstract class ValidatableProperty<T> {
public ValidatableProperty<T> computedBy(final Function<PropertiesProvider, T> compute) { public ValidatableProperty<T> computedBy(final Function<PropertiesProvider, T> compute) {
this.computedBy = compute; this.computedBy = compute;
this.computed = true;
return this; return this;
} }

View File

@ -107,25 +107,4 @@ class HsUnixUserHostingAssetValidatorUnitTest {
assertThat(result).containsExactly( assertThat(result).containsExactly(
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'"); "'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'");
} }
@Test
void describesItsProperties() {
// given
final var validator = HsHostingAssetEntityValidatorRegistry.forType(UNIX_USER);
// when
final var props = validator.properties();
// then
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD hard quota, unit=GB, maxFrom=SSD}",
"{type=integer, propertyName=SSD soft quota, unit=GB, maxFrom=SSD hard quota}",
"{type=integer, propertyName=HDD hard quota, unit=GB, maxFrom=HDD}",
"{type=integer, propertyName=HDD soft quota, unit=GB, maxFrom=HDD hard quota}",
"{type=enumeration, propertyName=shell, values=[/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd], defaultValue=/bin/false}",
"{type=string, propertyName=homedir, readOnly=true, computed=true}",
"{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, hidden=true}",
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, hidden=true}"
);
}
} }