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 e18cb182..7c253031 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 @@ -438,10 +438,13 @@ public class QInstanceValidator for(QFieldSection section : table.getSections()) { validateTableSection(qInstance, table, section, fieldNamesInSections); - if(section.getTier().equals(Tier.T1)) + if(assertCondition(section.getTier() != null, "Table " + tableName + " " + section.getName() + " is missing its tier")) { - assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1"); - tier1Section = section; + if(section.getTier().equals(Tier.T1)) + { + assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1"); + tier1Section = section; + } } assertCondition(!usedSectionNames.contains(section.getName()), "Table " + tableName + " has more than 1 section named " + section.getName()); @@ -1099,13 +1102,34 @@ public class QInstanceValidator boolean hasFields = CollectionUtils.nullSafeHasContents(section.getFieldNames()); boolean hasWidget = StringUtils.hasContent(section.getWidgetName()); - if(assertCondition(hasFields || hasWidget, "Table " + table.getName() + " section " + section.getName() + " does not have any fields or a widget.")) + String sectionPrefix = "Table " + table.getName() + " section " + section.getName() + " "; + if(assertCondition(hasFields || hasWidget, sectionPrefix + "does not have any fields or a widget.")) { if(table.getFields() != null && hasFields) { for(String fieldName : section.getFieldNames()) { - assertCondition(table.getFields().containsKey(fieldName), "Table " + table.getName() + " section " + section.getName() + " specifies fieldName " + fieldName + ", which is not a field on this table."); + if(fieldName.contains(".")) + { + String[] parts = fieldName.split("\\."); + String otherTableName = parts[0]; + String foreignFieldName = parts[1]; + + if(assertCondition(qInstance.getTable(otherTableName) != null, sectionPrefix + "join-field " + fieldName + ", which is referencing an unrecognized table name [" + otherTableName + "]")) + { + List matchedExposedJoins = CollectionUtils.nonNullList(table.getExposedJoins()).stream().filter(ej -> otherTableName.equals(ej.getJoinTable())).toList(); + if(assertCondition(CollectionUtils.nullSafeHasContents(matchedExposedJoins), sectionPrefix + "join-field " + fieldName + ", referencing table [" + otherTableName + "] which is not an exposed join on this table.")) + { + assertCondition(!matchedExposedJoins.get(0).getIsMany(), sectionPrefix + "join-field " + fieldName + " references an is-many join, which is not supported."); + } + assertCondition(qInstance.getTable(otherTableName).getFields().containsKey(foreignFieldName), sectionPrefix + "join-field " + fieldName + " specifies a fieldName [" + foreignFieldName + "] which does not exist in that table [" + otherTableName + "]."); + } + } + else + { + assertCondition(table.getFields().containsKey(fieldName), sectionPrefix + "specifies fieldName " + fieldName + ", which is not a field on this table."); + } + assertCondition(!fieldNamesInSections.contains(fieldName), "Table " + table.getName() + " has field " + fieldName + " listed more than once in its field sections."); fieldNamesInSections.add(fieldName); @@ -1113,7 +1137,7 @@ public class QInstanceValidator } else if(hasWidget) { - assertCondition(qInstance.getWidget(section.getWidgetName()) != null, "Table " + table.getName() + " section " + section.getName() + " specifies widget " + section.getWidgetName() + ", which is not a widget in this instance."); + assertCondition(qInstance.getWidget(section.getWidgetName()) != null, sectionPrefix + "specifies widget " + section.getWidgetName() + ", which is not a widget in this instance."); } } } 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 62d14fbb..84e48161 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 @@ -822,6 +822,55 @@ class QInstanceValidatorTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testSectionsWithJoinFields() + { + Consumer putAllFieldsInASection = table -> table.addSection(new QFieldSection() + .withName("section0") + .withTier(Tier.T1) + .withFieldNames(new ArrayList<>(table.getFields().keySet()))); + + assertValidationFailureReasons(qInstance -> + { + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_ORDER); + putAllFieldsInASection.accept(table); + table.getSections().get(0).getFieldNames().add(TestUtils.TABLE_NAME_LINE_ITEM + ".sku"); + }, "orderLine.sku references an is-many join, which is not supported"); + + assertValidationSuccess(qInstance -> + { + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM); + putAllFieldsInASection.accept(table); + table.getSections().get(0).getFieldNames().add(TestUtils.TABLE_NAME_ORDER + ".orderNo"); + }); + + assertValidationFailureReasons(qInstance -> + { + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM); + putAllFieldsInASection.accept(table); + table.getSections().get(0).getFieldNames().add(TestUtils.TABLE_NAME_ORDER + ".asdf"); + }, "order.asdf specifies a fieldName [asdf] which does not exist in that table [order]."); + + assertValidationFailureReasons(qInstance -> + { + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM); + putAllFieldsInASection.accept(table); + table.getSections().get(0).getFieldNames().add("foo.bar"); + }, "unrecognized table name [foo]"); + + assertValidationFailureReasons(qInstance -> + { + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM); + putAllFieldsInASection.accept(table); + table.getSections().get(0).getFieldNames().add(TestUtils.TABLE_NAME_SHAPE + ".id"); + }, "[shape] which is not an exposed join on this table"); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java index a38be542..21942e00 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java @@ -590,6 +590,7 @@ public class TestUtils .withFieldName("order.storeId") .withJoinNameChain(List.of("orderLineItem"))) .withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("lineItemLineItemExtrinsic")) + .withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ORDER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false)) .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))