diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 41f7ff2b..9ceacae1 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -74,6 +74,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; @@ -928,13 +929,8 @@ public class QInstanceValidator assertCondition(Objects.equals(fieldName, field.getName()), "Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + "."); - if(field.getPossibleValueSourceName() != null) - { - assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null, - "Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + "."); - } - String prefix = "Field " + fieldName + " in table " + tableName + " "; + validateFieldPossibleValueSourceAttributes(qInstance, field, prefix); /////////////////////////////////////////////////// // validate things we know about field behaviors // @@ -1039,6 +1035,31 @@ public class QInstanceValidator + /*************************************************************************** + ** + ***************************************************************************/ + private void validateFieldPossibleValueSourceAttributes(QInstance qInstance, QFieldMetaData field, String prefix) + { + if(field.getPossibleValueSourceName() != null) + { + assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null, + prefix + "has an unrecognized possibleValueSourceName " + field.getPossibleValueSourceName()); + + assertCondition(field.getInlinePossibleValueSource() == null, prefix.trim() + " has both a possibleValueSourceName and an inlinePossibleValueSource, which is not allowed."); + } + + if(field.getInlinePossibleValueSource() != null) + { + String name = "inlinePossibleValueSource for " + prefix.trim(); + if(assertCondition(QPossibleValueSourceType.ENUM.equals(field.getInlinePossibleValueSource().getType()), name + " must have a type of ENUM.")) + { + validatePossibleValueSource(qInstance, name, field.getInlinePossibleValueSource()); + } + } + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -1546,6 +1567,16 @@ public class QInstanceValidator } } + for(QFieldMetaData field : process.getInputFields()) + { + validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", input field " + field.getName()); + } + + for(QFieldMetaData field : process.getOutputFields()) + { + validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", output field " + field.getName()); + } + if(process.getCancelStep() != null) { if(assertCondition(process.getCancelStep().getCode() != null, "Cancel step is missing a code reference, in process " + processName)) @@ -1948,78 +1979,88 @@ public class QInstanceValidator qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) -> { assertCondition(Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + "."); - if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName)) + validatePossibleValueSource(qInstance, pvsName, possibleValueSource); + }); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private void validatePossibleValueSource(QInstance qInstance, String name, QPossibleValueSource possibleValueSource) + { + if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + name)) + { + //////////////////////////////////////////////////////////////////////////////////////////////// + // assert about fields that should and should not be set, based on possible value source type // + // do additional type-specific validations as well // + //////////////////////////////////////////////////////////////////////////////////////////////// + switch(possibleValueSource.getType()) + { + case ENUM -> { - //////////////////////////////////////////////////////////////////////////////////////////////// - // assert about fields that should and should not be set, based on possible value source type // - // do additional type-specific validations as well // - //////////////////////////////////////////////////////////////////////////////////////////////// - switch(possibleValueSource.getType()) + assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + name + " should not have a tableName."); + assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + name + " should not have searchFields."); + assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + name + " should not have orderByFields."); + assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + name + " should not have a customCodeReference."); + + assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + name + " is missing enum values"); + } + case TABLE -> + { + assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + name + " should not have enum values."); + assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + name + " should not have a customCodeReference."); + + QTableMetaData tableMetaData = null; + if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + name + " is missing a tableName.")) { - case ENUM -> - { - assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName."); - assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + pvsName + " should not have searchFields."); - assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + pvsName + " should not have orderByFields."); - assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference."); - - assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values"); - } - case TABLE -> - { - assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values."); - assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference."); - - QTableMetaData tableMetaData = null; - if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName.")) - { - tableMetaData = qInstance.getTable(possibleValueSource.getTableName()); - assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + "."); - } - - if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + pvsName + " is missing searchFields.")) - { - if(tableMetaData != null) - { - QTableMetaData finalTableMetaData = tableMetaData; - for(String searchField : possibleValueSource.getSearchFields()) - { - assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + pvsName + " has an unrecognized searchField: " + searchField); - } - } - } - - if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + pvsName + " is missing orderByFields.")) - { - if(tableMetaData != null) - { - QTableMetaData finalTableMetaData = tableMetaData; - - for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields()) - { - assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + pvsName + " has an unrecognized orderByField: " + orderByField.getFieldName()); - } - } - } - } - case CUSTOM -> - { - assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values."); - assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName."); - assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + pvsName + " should not have searchFields."); - assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + pvsName + " should not have orderByFields."); - - if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference.")) - { - validateSimpleCodeReference("PossibleValueSource " + pvsName + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class); - } - } - default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType()); + tableMetaData = qInstance.getTable(possibleValueSource.getTableName()); + assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + name + "."); } - runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance); + if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + name + " is missing searchFields.")) + { + if(tableMetaData != null) + { + QTableMetaData finalTableMetaData = tableMetaData; + for(String searchField : possibleValueSource.getSearchFields()) + { + assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + name + " has an unrecognized searchField: " + searchField); + } + } + } + + if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + name + " is missing orderByFields.")) + { + if(tableMetaData != null) + { + QTableMetaData finalTableMetaData = tableMetaData; + + for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields()) + { + assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + name + " has an unrecognized orderByField: " + orderByField.getFieldName()); + } + } + } } - }); + case CUSTOM -> + { + assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + name + " should not have enum values."); + assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + name + " should not have a tableName."); + assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + name + " should not have searchFields."); + assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + name + " should not have orderByFields."); + + if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + name + " is missing a customCodeReference.")) + { + validateSimpleCodeReference("PossibleValueSource " + name + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class); + } + } + default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType()); + } + + runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java index d78d469d..5e5e61f0 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java @@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole; import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; @@ -73,10 +74,12 @@ public class QFieldMetaData implements Cloneable // propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" // /////////////////////////////////////////////////////////////////////////////////// - private String displayFormat = "%s"; + private String displayFormat = "%s"; private Serializable defaultValue; - private String possibleValueSourceName; - private QQueryFilter possibleValueSourceFilter; + + private String possibleValueSourceName; + private QQueryFilter possibleValueSourceFilter; + private QPossibleValueSource inlinePossibleValueSource; private Integer maxLength; private Set> behaviors; @@ -1058,4 +1061,35 @@ public class QFieldMetaData implements Cloneable QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents); } + + /******************************************************************************* + ** Getter for inlinePossibleValueSource + *******************************************************************************/ + public QPossibleValueSource getInlinePossibleValueSource() + { + return (this.inlinePossibleValueSource); + } + + + + /******************************************************************************* + ** Setter for inlinePossibleValueSource + *******************************************************************************/ + public void setInlinePossibleValueSource(QPossibleValueSource inlinePossibleValueSource) + { + this.inlinePossibleValueSource = inlinePossibleValueSource; + } + + + + /******************************************************************************* + ** Fluent setter for inlinePossibleValueSource + *******************************************************************************/ + public QFieldMetaData withInlinePossibleValueSource(QPossibleValueSource inlinePossibleValueSource) + { + this.inlinePossibleValueSource = inlinePossibleValueSource; + return (this); + } + + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java index 54b8aba6..38dfdc54 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java @@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehaviorForFron import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -56,6 +57,7 @@ public class QFrontendFieldMetaData private List adornments; private List helpContents; + private QPossibleValueSource inlinePossibleValueSource; private List behaviors; @@ -81,6 +83,7 @@ public class QFrontendFieldMetaData this.adornments = fieldMetaData.getAdornments(); this.defaultValue = fieldMetaData.getDefaultValue(); this.helpContents = fieldMetaData.getHelpContents(); + this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource(); for(FieldBehavior behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors())) { @@ -218,6 +221,17 @@ public class QFrontendFieldMetaData + /******************************************************************************* + ** Getter for inlinePossibleValueSource + ** + *******************************************************************************/ + public QPossibleValueSource getInlinePossibleValueSource() + { + return inlinePossibleValueSource; + } + + + /******************************************************************************* ** Getter for fieldBehaviors ** diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index e6651f3a..612d575b 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -265,6 +265,38 @@ public class QInstanceValidatorTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testTableFieldInlinePossibleValueSource() + { + //////////////////////////////////////////////////// + // make sure can't have both named and inline PVS // + //////////////////////////////////////////////////// + assertValidationFailureReasonsAllowingExtraReasons((qInstance) -> qInstance.getTable("person").getField("homeStateId") + .withInlinePossibleValueSource(new QPossibleValueSource().withType(QPossibleValueSourceType.TABLE).withTableName("person")), + "both a possibleValueSourceName and an inlinePossibleValueSource"); + + ///////////////////////////////////////////// + // make require inline PVS to be enum type // + ///////////////////////////////////////////// + assertValidationFailureReasonsAllowingExtraReasons((qInstance) -> qInstance.getTable("person").getField("homeStateId") + .withPossibleValueSourceName(null) + .withInlinePossibleValueSource(new QPossibleValueSource().withType(QPossibleValueSourceType.TABLE)), + "must have a type of ENUM"); + + //////////////////////////////////////////////////// + // make sure validation on the inline PVS happens // + //////////////////////////////////////////////////// + assertValidationFailureReasonsAllowingExtraReasons((qInstance) -> qInstance.getTable("person").getField("homeStateId") + .withPossibleValueSourceName(null) + .withInlinePossibleValueSource(new QPossibleValueSource().withType(QPossibleValueSourceType.ENUM)), + "missing enum values"); + } + + + /******************************************************************************* ** Test that if a process specifies a table that doesn't exist, that it fails. ** @@ -717,8 +749,8 @@ public class QInstanceValidatorTest extends BaseTest @Test public void test_validateFieldWithMissingPossibleValueSource() { - assertValidationFailureReasons((qInstance) -> qInstance.getTable("person").getField("homeStateId").setPossibleValueSourceName("not a real possible value source"), - "Unrecognized possibleValueSourceName"); + assertValidationFailureReasonsAllowingExtraReasons((qInstance) -> qInstance.getTable("person").getField("homeStateId").setPossibleValueSourceName("not a real possible value source"), + "unrecognized possibleValueSourceName"); }