backend-api http://jira.mfd.msk:8088/browse/CLS-331 валидация запросов (action) по meta.json (обязательность полей)/

This commit is contained in:
AKurakin 2023-05-25 17:24:55 +03:00
parent 58b396c849
commit 14f7a814aa
6 changed files with 200 additions and 4 deletions

View file

@ -82,7 +82,9 @@ public class MetaServer extends MetaBase {
log.warn("META SERVER >>> {}", e.getLocalizedMessage());
continue;
}
actionObjectsExtracted.put(actionElement.getDestination(), oe);
String actionDestination = // обычно бывают =null о этому эффективнее по имени класса а не по: objectElement.getSubscription().destination + "/"+ actionElement.getDestination();
oe.getClassName();
actionObjectsExtracted.put(actionDestination, oe);
}
}
} catch (Throwable e) {

View file

@ -17,6 +17,7 @@ import ru.spcex.clearing.backendapi.errors.BackEndError;
import ru.spcex.clearing.backendapi.errors.NotFound404Exception;
import ru.spcex.clearing.backendapi.security.KeycloakUtils;
import ru.spcex.clearing.backendapi.service.IOperator;
import ru.spcex.clearing.backendapi.service.validation.ActionMetaValidation;
import ru.spcex.clearing.backendapi.service.validation.ActionValidationProvider;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
@ -39,13 +40,16 @@ public class OperatorImpl implements IOperator {
private final Producer<String, Object> kafka;
private final ImdgId idGenerator;
private final ImdgProvider imdgProvider;
private final ActionMetaValidation actionMetaValidation;
private final ActionValidationProvider validation;
private final Imdg<User> userImdg;
public OperatorImpl(Producer<String, Object> kafka, ImdgProvider imdgProvider, ActionValidationProvider validation) {
public OperatorImpl(Producer<String, Object> kafka, ImdgProvider imdgProvider,
ActionMetaValidation actionMetaValidation, ActionValidationProvider validation) {
this.kafka = kafka;
this.idGenerator = imdgProvider.getImdgIdGenerator();
this.imdgProvider = imdgProvider;
this.actionMetaValidation = actionMetaValidation;
this.validation = validation;
this.userImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_User, User.class);
}
@ -112,6 +116,18 @@ public class OperatorImpl implements IOperator {
if (validationErrors.size() > 0) {
throw new ActionValidationException(validationErrors);
}
// Валидация по мете (рекомендуется использовать её,а не iAction.validate();
IValidator metaValidator = actionMetaValidation.getValidator(destination, iAction);
if (metaValidator != null) {
metaValidator.tillFirstError().ifPresent(enumMessage -> {
if (enumMessage.getSubject().equalsById(BackEndError.ResourceNotFound.getId())) {
throw new NotFound404Exception(enumMessage);
} else {
throw new ActionValidationException(enumMessage);
}
});
}
//валидация требующая IMDG поиска других сущностей и т.д.
IValidator validator = validation.getValidator(destination, iAction);
if (validator != null) {

View file

@ -0,0 +1,115 @@
package ru.spcex.clearing.backendapi.service.validation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ru.spcex.clearing.backendapi.domain.actions.IAction;
import ru.spcex.clearing.backendapi.errors.BackEndError;
import ru.spcex.clearing.backendapi.meta.FieldExtracted;
import ru.spcex.clearing.backendapi.meta.MetaServer;
import ru.spcex.clearing.backendapi.meta.ObjectExtracted;
import ru.spcex.platform.imdg.validation.ImdgValidationContext;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.validation.IValidationRule;
import ru.spcex.platform.utils.validation.IValidator;
import ru.spcex.platform.utils.validation.ValidatorImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
/**
* Проверка обязательности полей для IAction по мете
*/
@Component
public class ActionMetaValidation implements InitializingBean {
protected final Logger log = LoggerFactory.getLogger(getClass());
private final Map<String, Function<Object, IValidator>> validators;
protected final MetaServer meta;
@Autowired
public ActionMetaValidation(MetaServer meta) {
this.validators = new HashMap<>();
this.meta = meta;
}
@Override
public void afterPropertiesSet() {
for (Map.Entry<String, ObjectExtracted> entry : meta.getActionObjectsExtracted().entrySet()) {
if (StringUtils.isEmpty(entry.getKey())) continue;
Function<Object, IValidator> validator = validatorConstructor(entry.getValue());
if (validator == null) continue;
validators.put(entry.getKey(), validator);
}
log.debug("Init {} IAction validator for: {}", validators.size(), validators.keySet());
}
@SuppressWarnings("unchecked")
public IValidator getValidator(String destination, Object action) {
if (action == null)
return null;
//if (destination == null) destination = action.getClass().getName();
String actionDestination = action.getClass().getName(); // см. реализацию в MetaServer String actionDestination =...
Function<Object, IValidator> constructor = (Function<Object, IValidator>) validators.get(actionDestination);
if (constructor != null)
return constructor.apply(action);
return null;
}
private <T extends Object> Function<T, IValidator> validatorConstructor(ObjectExtracted metaAction) {
if (!metaAction.getFields().stream().anyMatch(field -> field.getField().isRequired())) {
log.debug("Action {}: no any required fields. Do not need validator.", metaAction.getClassName());
// нет обязательных полей для валидации
return null;
}
final MetaValidatorRule metaValidatorRule = new MetaValidatorRule(metaAction);
return iAcc -> {
ImdgValidationContext<T> ctx = new ImdgValidationContext<>();
ctx.setValidatedObject(iAcc);
ValidatorImpl<ImdgValidationContext<T>> iValidator = new ValidatorImpl(ctx);
iValidator.addRule(metaValidatorRule);
// в дальнейшем можно улучшить и разделить валидатор по полям
return iValidator;
};
}
static class MetaValidatorRule implements IValidationRule<ImdgValidationContext<IAction>> {
public final ObjectExtracted metaAction;
public MetaValidatorRule(ObjectExtracted metaAction) {
this.metaAction = metaAction;
}
@Override
public Optional<EnumMessage> validate(ImdgValidationContext<IAction> context) {
IAction object = context.getValidatedObject();
try {
for (FieldExtracted field : metaAction.getFields()) {
if (field.getField().isRequired() != null && field.getField().isRequired()) {
Object value = field.extractValue(object);
if (value == null)
return of(BackEndError.ValidationError, field.getMemberName());
// if (value instanceof String && ((String)value).isEmpty()) // пустое поле, но не null
// return of(BackEndError.ValidationError, field.getMemberName());
}
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException("Validator error verify " + object + " by " + metaAction);
}
return empty();
}
@Override
public String ruleName() {
return getClass().getSimpleName() + " of " + metaField.getClassName();
}
}
}

View file

@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.spcex.clearing.backendapi.service.impl.OperatorImpl;
import ru.spcex.clearing.backendapi.service.validation.ActionMetaValidation;
import ru.spcex.clearing.backendapi.service.validation.ActionValidationProvider;
import ru.spcex.platform.imdg.iml.hazelcast.service.HazelcastService;
@ -18,8 +19,9 @@ public class IOperatorTest {
@Autowired
@Bean("iOperatorTest")
public OperatorImpl createIOperator(Producer<String, Object> kafka, ActionValidationProvider validationProvider) {
return new OperatorImpl(kafka, hazelcastServiceTest, validationProvider);
public OperatorImpl createIOperator(Producer<String, Object> kafka,
ActionMetaValidation actionMetaValidation, ActionValidationProvider validationProvider) {
return new OperatorImpl(kafka, hazelcastServiceTest, actionMetaValidation, validationProvider);
}
@Bean

View file

@ -0,0 +1,59 @@
package ru.spcex.clearing.backendapi.service.validation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import ru.spcex.clearing.backendapi.controller.request.cud.common.CommonDeleteAction;
import ru.spcex.clearing.backendapi.controller.request.cud.company.ListingNewAction;
import ru.spcex.clearing.backendapi.domain.actions.IAction;
import ru.spcex.clearing.backendapi.meta.GetResponseFactoryTestConfiguration;
import ru.spcex.clearing.backendapi.meta.MetaServer;
import ru.spcex.platform.utils.enumeration.EnumMessage;
import ru.spcex.platform.utils.enumeration.IEnumKey;
import ru.spcex.platform.utils.validation.IValidator;
import java.util.ArrayList;
import java.util.Collection;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = GetResponseFactoryTestConfiguration.class)
class ActionMetaValidationTest {
@Autowired @Qualifier("metaJsonTest")
protected MetaServer meta;
@Test
void getValidator() {
ActionMetaValidation metaValidator = new ActionMetaValidation(meta);
metaValidator.afterPropertiesSet();
{
IAction<?> action = new ListingNewAction();
assertTrue(action.validate().isEmpty());
IValidator v = metaValidator.getValidator(null, action);
assertNull(v, "Пока не прописан в мете");
}
{
IAction<?> action = new CommonDeleteAction();
assertFalse(action.validate().isEmpty()); // стандартный валидатор - требуется id
IValidator v = metaValidator.getValidator(null, action);
Collection<EnumMessage> vResult = v.validateAll();
assertEquals("[EnumMessage{subject=ValidationError, args: [id]}]", vResult.toString());
}
{
CommonDeleteAction action = new CommonDeleteAction();
action.setId(123L);
assertTrue(action.validate().isEmpty()); // стандартный валидатор - требуется id
IValidator v = metaValidator.getValidator(null, action);
Collection<EnumMessage> vResult = v.validateAll();
assertEquals("[]", vResult.toString());
}
}
}

View file

@ -4060,6 +4060,8 @@
"confirmation": "companyId,account",
"class": "ru.spcex.clearing.backendapi.controller.request.cud.common.CommonDeleteAction",
"fields": [
{"code": "id",
"type": 1,"name": "Идентификатор записи","shortname": "ID","link": "account","linkCode": "id","required": true