mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
CE-773 change fileNameFieldName and contentsFieldName to default as null - add validation to tableBackendDetails, specifically implemented in filesystem module
This commit is contained in:
@ -480,6 +480,11 @@ public class QInstanceValidator
|
|||||||
validateTableCustomizer(tableName, entry.getKey(), entry.getValue());
|
validateTableCustomizer(tableName, entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(table.getBackendDetails() != null)
|
||||||
|
{
|
||||||
|
table.getBackendDetails().validate(qInstance, table, this);
|
||||||
|
}
|
||||||
|
|
||||||
validateTableAutomationDetails(qInstance, table);
|
validateTableAutomationDetails(qInstance, table);
|
||||||
validateTableUniqueKeys(table);
|
validateTableUniqueKeys(table);
|
||||||
validateAssociatedScripts(table);
|
validateAssociatedScripts(table);
|
||||||
|
@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
|||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QTableBackendDetailsDeserializer;
|
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QTableBackendDetailsDeserializer;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||||
|
|
||||||
@ -100,4 +103,16 @@ public abstract class QTableBackendDetails
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void validate(QInstance qInstance, QTableMetaData table, QInstanceValidator qInstanceValidator)
|
||||||
|
{
|
||||||
|
////////////////////////
|
||||||
|
// noop in base class //
|
||||||
|
////////////////////////
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,11 @@
|
|||||||
package com.kingsrook.qqq.backend.module.filesystem.base.model.metadata;
|
package com.kingsrook.qqq.backend.module.filesystem.base.model.metadata;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -35,11 +39,9 @@ public class AbstractFilesystemTableBackendDetails extends QTableBackendDetails
|
|||||||
private RecordFormat recordFormat;
|
private RecordFormat recordFormat;
|
||||||
private Cardinality cardinality;
|
private Cardinality cardinality;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
private String contentsFieldName;
|
||||||
// todo default these to null, and give validation error if not set for a cardinality=ONE table? //
|
private String fileNameFieldName;
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
private String contentsFieldName = "contents";
|
|
||||||
private String fileNameFieldName = "fileName";
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -243,4 +245,40 @@ public class AbstractFilesystemTableBackendDetails extends QTableBackendDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void validate(QInstance qInstance, QTableMetaData table, QInstanceValidator qInstanceValidator)
|
||||||
|
{
|
||||||
|
super.validate(qInstance, table, qInstanceValidator);
|
||||||
|
|
||||||
|
String prefix = "Table " + (table == null ? "null" : table.getName()) + " backend details - ";
|
||||||
|
if(qInstanceValidator.assertCondition(cardinality != null, prefix + "missing cardinality"))
|
||||||
|
{
|
||||||
|
if(cardinality.equals(Cardinality.ONE))
|
||||||
|
{
|
||||||
|
if(qInstanceValidator.assertCondition(StringUtils.hasContent(contentsFieldName), prefix + "missing contentsFieldName, which is required for Cardinality ONE"))
|
||||||
|
{
|
||||||
|
qInstanceValidator.assertCondition(table != null && table.getFields().containsKey(contentsFieldName), prefix + "contentsFieldName [" + contentsFieldName + "] is not a field on this table.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(qInstanceValidator.assertCondition(StringUtils.hasContent(fileNameFieldName), prefix + "missing fileNameFieldName, which is required for Cardinality ONE"))
|
||||||
|
{
|
||||||
|
qInstanceValidator.assertCondition(table != null && table.getFields().containsKey(fileNameFieldName), prefix + "fileNameFieldName [" + fileNameFieldName + "] is not a field on this table.");
|
||||||
|
}
|
||||||
|
|
||||||
|
qInstanceValidator.assertCondition(recordFormat == null, prefix + "has a recordFormat, which is not allowed for Cardinality ONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cardinality.equals(Cardinality.MANY))
|
||||||
|
{
|
||||||
|
qInstanceValidator.assertCondition(!StringUtils.hasContent(contentsFieldName), prefix + "has a contentsFieldName, which is not allowed for Cardinality MANY");
|
||||||
|
qInstanceValidator.assertCondition(!StringUtils.hasContent(fileNameFieldName), prefix + "has a fileNameFieldName, which is not allowed for Cardinality MANY");
|
||||||
|
qInstanceValidator.assertCondition(recordFormat != null, prefix + "missing recordFormat, which is required for Cardinality MANY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -142,8 +141,6 @@ public class TestUtils
|
|||||||
qInstance.addTable(defineMockPersonTable());
|
qInstance.addTable(defineMockPersonTable());
|
||||||
qInstance.addProcess(defineStreamedLocalCsvToMockETLProcess());
|
qInstance.addProcess(defineStreamedLocalCsvToMockETLProcess());
|
||||||
|
|
||||||
new QInstanceValidator().validate(qInstance);
|
|
||||||
|
|
||||||
return (qInstance);
|
return (qInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,6 +246,8 @@ public class TestUtils
|
|||||||
.withBackendDetails(new FilesystemTableBackendDetails()
|
.withBackendDetails(new FilesystemTableBackendDetails()
|
||||||
.withBasePath("blobs")
|
.withBasePath("blobs")
|
||||||
.withCardinality(Cardinality.ONE)
|
.withCardinality(Cardinality.ONE)
|
||||||
|
.withFileNameFieldName("fileName")
|
||||||
|
.withContentsFieldName("contents")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,6 +268,8 @@ public class TestUtils
|
|||||||
.withBackendDetails(new S3TableBackendDetails()
|
.withBackendDetails(new S3TableBackendDetails()
|
||||||
.withBasePath("blobs")
|
.withBasePath("blobs")
|
||||||
.withCardinality(Cardinality.ONE)
|
.withCardinality(Cardinality.ONE)
|
||||||
|
.withFileNameFieldName("fileName")
|
||||||
|
.withContentsFieldName("contents")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.module.filesystem.base.model.metadata;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||||
|
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.module.filesystem.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.module.filesystem.TestUtils;
|
||||||
|
import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemTableBackendDetails;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for AbstractFilesystemTableBackendDetails
|
||||||
|
*******************************************************************************/
|
||||||
|
class AbstractFilesystemTableBackendDetailsTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testValidInstancePasses() throws QInstanceValidationException
|
||||||
|
{
|
||||||
|
new QInstanceValidator().validate(QContext.getQInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testMissingCardinality() throws QException
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((QInstance qInstance) ->
|
||||||
|
{
|
||||||
|
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_S3).withBackendDetails(new FilesystemTableBackendDetails());
|
||||||
|
}, false, "missing cardinality");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCardinalityOneIssues() throws QException
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((QInstance qInstance) ->
|
||||||
|
{
|
||||||
|
qInstance.getTable(TestUtils.TABLE_NAME_BLOB_LOCAL_FS).withBackendDetails(new FilesystemTableBackendDetails()
|
||||||
|
.withCardinality(Cardinality.ONE)
|
||||||
|
);
|
||||||
|
}, false, "missing contentsFieldName", "missing fileNameFieldName");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((QInstance qInstance) ->
|
||||||
|
{
|
||||||
|
qInstance.getTable(TestUtils.TABLE_NAME_BLOB_LOCAL_FS).withBackendDetails(new FilesystemTableBackendDetails()
|
||||||
|
.withCardinality(Cardinality.ONE)
|
||||||
|
.withContentsFieldName("foo")
|
||||||
|
.withFileNameFieldName("bar")
|
||||||
|
);
|
||||||
|
}, false, "contentsFieldName [foo] is not a field", "fileNameFieldName [bar] is not a field");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((QInstance qInstance) ->
|
||||||
|
{
|
||||||
|
qInstance.getTable(TestUtils.TABLE_NAME_BLOB_LOCAL_FS).withBackendDetails(new FilesystemTableBackendDetails()
|
||||||
|
.withCardinality(Cardinality.ONE)
|
||||||
|
.withContentsFieldName("contents")
|
||||||
|
.withFileNameFieldName("fileName")
|
||||||
|
.withRecordFormat(RecordFormat.CSV)
|
||||||
|
);
|
||||||
|
}, false, "has a recordFormat");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCardinalityManyIssues() throws QException
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((QInstance qInstance) ->
|
||||||
|
{
|
||||||
|
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_CSV).withBackendDetails(new FilesystemTableBackendDetails()
|
||||||
|
.withCardinality(Cardinality.MANY)
|
||||||
|
);
|
||||||
|
}, false, "missing recordFormat");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((QInstance qInstance) ->
|
||||||
|
{
|
||||||
|
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_LOCAL_FS_CSV).withBackendDetails(new FilesystemTableBackendDetails()
|
||||||
|
.withCardinality(Cardinality.MANY)
|
||||||
|
.withRecordFormat(RecordFormat.CSV)
|
||||||
|
.withContentsFieldName("foo")
|
||||||
|
.withFileNameFieldName("bar")
|
||||||
|
);
|
||||||
|
}, false, "has a contentsFieldName", "has a fileNameFieldName");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Implementation for the overloads of this name.
|
||||||
|
*******************************************************************************/
|
||||||
|
private void assertValidationFailureReasons(Consumer<QInstance> setup, boolean allowExtraReasons, String... reasons) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
setup.accept(qInstance);
|
||||||
|
new QInstanceValidator().validate(qInstance);
|
||||||
|
fail("Should have thrown validationException");
|
||||||
|
}
|
||||||
|
catch(QInstanceValidationException e)
|
||||||
|
{
|
||||||
|
if(!allowExtraReasons)
|
||||||
|
{
|
||||||
|
int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
|
||||||
|
assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons)
|
||||||
|
+ "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", e.getReasons()) : "--"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String reason : reasons)
|
||||||
|
{
|
||||||
|
assertReason(reason, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Assert that an instance is valid!
|
||||||
|
*******************************************************************************/
|
||||||
|
private void assertValidationSuccess(Consumer<QInstance> setup) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
setup.accept(qInstance);
|
||||||
|
new QInstanceValidator().validate(qInstance);
|
||||||
|
}
|
||||||
|
catch(QInstanceValidationException e)
|
||||||
|
{
|
||||||
|
fail("Expected no validation errors, but received: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** utility method for asserting that a specific reason string is found within
|
||||||
|
** the list of reasons in the QInstanceValidationException.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void assertReason(String reason, QInstanceValidationException e)
|
||||||
|
{
|
||||||
|
assertNotNull(e.getReasons(), "Expected there to be a reason for the failure (but there was not)");
|
||||||
|
assertThat(e.getReasons())
|
||||||
|
.withFailMessage("Expected any of:\n%s\nTo match: [%s]", e.getReasons(), reason)
|
||||||
|
.anyMatch(s -> s.contains(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user