Support multiple api's within a q instance. For science!

This commit is contained in:
2023-04-04 13:40:32 -05:00
parent e35761e0a6
commit e779c392bb
21 changed files with 1171 additions and 341 deletions

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.utils; package com.kingsrook.qqq.backend.core.utils;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -48,4 +51,49 @@ public class ObjectUtils
throw (new NullPointerException("all null objects")); throw (new NullPointerException("all null objects"));
} }
/*******************************************************************************
** Like Objects.requireNonNullElse, only use an (unsafe) supplier as the first
** arg, and only if it throws, return the 2nd arg
*******************************************************************************/
public static <T> T tryElse(UnsafeSupplier<T, ?> supplier, T defaultIfThrew)
{
try
{
return (supplier.get());
}
catch(Exception e)
{
return (defaultIfThrew);
}
}
/*******************************************************************************
** Like Objects.requireNonNullElse, only use an (unsafe) supplier as the first
** arg, and if it throws or returns null, then return the 2nd arg
*******************************************************************************/
public static <T> T tryAndRequireNonNullElse(UnsafeSupplier<T, ?> supplier, T defaultIfThrew)
{
try
{
T t = supplier.get();
if(t != null)
{
return (t);
}
}
catch(Exception e)
{
//////////
// noop //
//////////
}
return (defaultIfThrew);
}
} }

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.utils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/******************************************************************************* /*******************************************************************************
@ -36,8 +37,9 @@ class ObjectUtilsTest
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@SuppressWarnings({ "DataFlowIssue", "ConfusingArgumentToVarargsMethod" })
@Test @Test
void test() void testRequireNonNullElse()
{ {
assertThatThrownBy(() -> ObjectUtils.requireNonNullElse(null)).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> ObjectUtils.requireNonNullElse(null)).isInstanceOf(NullPointerException.class);
assertThatThrownBy(() -> ObjectUtils.requireNonNullElse(null, null)).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> ObjectUtils.requireNonNullElse(null, null)).isInstanceOf(NullPointerException.class);
@ -47,4 +49,34 @@ class ObjectUtilsTest
assertEquals("c", ObjectUtils.requireNonNullElse(null, null, "c")); assertEquals("c", ObjectUtils.requireNonNullElse(null, null, "c"));
} }
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings({ "StringOperationCanBeSimplified", "DataFlowIssue" })
@Test
void testTryElse()
{
String nullString = null;
assertEquals("tried", ObjectUtils.tryElse(() -> "tried".toString(), "else"));
assertEquals("else", ObjectUtils.tryElse(() -> nullString.toString(), "else"));
assertNull(ObjectUtils.tryElse(() -> null, "else"));
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings({ "StringOperationCanBeSimplified", "DataFlowIssue" })
@Test
void testTryAndRequireNonNullElse()
{
String nullString = null;
assertEquals("tried", ObjectUtils.tryAndRequireNonNullElse(() -> "tried".toString(), "else"));
assertEquals("else", ObjectUtils.tryAndRequireNonNullElse(() -> nullString.toString(), "else"));
assertEquals("else", ObjectUtils.tryAndRequireNonNullElse(() -> null, "else"));
}
} }

View File

@ -30,15 +30,16 @@ import java.util.Comparator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.api.model.APIVersion; import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.APIVersionRange; import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput; import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.api.model.openapi.Components; import com.kingsrook.qqq.api.model.openapi.Components;
import com.kingsrook.qqq.api.model.openapi.Contact; import com.kingsrook.qqq.api.model.openapi.Contact;
import com.kingsrook.qqq.api.model.openapi.Content; import com.kingsrook.qqq.api.model.openapi.Content;
@ -187,21 +188,43 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
*******************************************************************************/ *******************************************************************************/
public GenerateOpenApiSpecOutput execute(GenerateOpenApiSpecInput input) throws QException public GenerateOpenApiSpecOutput execute(GenerateOpenApiSpecInput input) throws QException
{ {
String version = input.getVersion();
String basePath = "/api/" + version + "/";
QInstance qInstance = QContext.getQInstance(); QInstance qInstance = QContext.getQInstance();
String version = input.getVersion();
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(qInstance);
if(apiInstanceMetaDataContainer == null)
{
throw new QException("No ApiInstanceMetaDataContainer exists in this instance");
}
if(!StringUtils.hasContent(input.getApiName()))
{
throw new QException("Missing required input: apiName");
}
ApiInstanceMetaData apiInstanceMetaData = apiInstanceMetaDataContainer.getApiInstanceMetaData(input.getApiName());
if(apiInstanceMetaData == null)
{
throw new QException("Could not find apiInstanceMetaData named [" + input.getApiName() + "] in this instance");
}
if(!StringUtils.hasContent(input.getVersion()))
{
throw new QException("Missing required input: version");
}
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
if(!apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(version))) if(!apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(version)))
{ {
throw (new QException("[" + version + "] is not a supported API Version.")); throw (new QException("[" + version + "] is not a supported API Version."));
} }
String basePath = apiInstanceMetaData.getPath() + version + "/";
String apiName = apiInstanceMetaData.getName();
OpenAPI openAPI = new OpenAPI() OpenAPI openAPI = new OpenAPI()
.withVersion("3.0.3") .withVersion("3.0.3")
.withInfo(new Info() .withInfo(new Info()
.withTitle(apiInstanceMetaData.getName()) .withTitle(apiInstanceMetaData.getLabel())
.withDescription(apiInstanceMetaData.getDescription()) .withDescription(apiInstanceMetaData.getDescription())
.withContact(new Contact() .withContact(new Contact()
.withEmail(apiInstanceMetaData.getContactEmail())) .withEmail(apiInstanceMetaData.getContactEmail()))
@ -272,7 +295,14 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
continue; continue;
} }
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaDataContainer == null)
{
LOG.debug("Omitting table [" + tableName + "] because it does not have an apiTableMetaDataContainer");
continue;
}
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiName);
if(apiTableMetaData == null) if(apiTableMetaData == null)
{ {
LOG.debug("Omitting table [" + tableName + "] because it does not have any apiTableMetaData"); LOG.debug("Omitting table [" + tableName + "] because it does not have any apiTableMetaData");
@ -312,8 +342,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
String primaryKeyName = table.getPrimaryKeyField(); String primaryKeyName = table.getPrimaryKeyField();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
String primaryKeyLabel = primaryKeyField.getLabel(); String primaryKeyLabel = primaryKeyField.getLabel();
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField); String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(apiName, primaryKeyField);
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields(); List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version).withApiName(apiName)).getFields();
/////////////////////////////// ///////////////////////////////
// permissions for the table // // permissions for the table //
@ -365,13 +395,13 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
for(QFieldMetaData field : tableApiFields) for(QFieldMetaData field : tableApiFields)
{ {
Schema fieldSchema = getFieldSchema(table, field); Schema fieldSchema = getFieldSchema(table, field);
tableFields.put(ApiFieldMetaData.getEffectiveApiFieldName(field), fieldSchema); tableFields.put(ApiFieldMetaData.getEffectiveApiFieldName(apiInstanceMetaData.getName(), field), fieldSchema);
} }
////////////////////////////////// //////////////////////////////////
// recursively add associations // // recursively add associations //
////////////////////////////////// //////////////////////////////////
addAssociations(table, tableSchema); addAssociations(apiName, table, tableSchema);
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// table as a search result (the base search result, plus the table itself) // // table as a search result (the base search result, plus the table itself) //
@ -770,13 +800,13 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void addAssociations(QTableMetaData table, Schema tableSchema) private static void addAssociations(String apiName, QTableMetaData table, Schema tableSchema)
{ {
for(Association association : CollectionUtils.nonNullList(table.getAssociations())) for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{ {
String associatedTableName = association.getAssociatedTableName(); String associatedTableName = association.getAssociatedTableName();
QTableMetaData associatedTable = QContext.getQInstance().getTable(associatedTableName); QTableMetaData associatedTable = QContext.getQInstance().getTable(associatedTableName);
ApiTableMetaData associatedApiTableMetaData = Objects.requireNonNullElse(ApiTableMetaData.of(associatedTable), new ApiTableMetaData()); ApiTableMetaData associatedApiTableMetaData = ObjectUtils.tryElse(() -> ApiTableMetaDataContainer.of(associatedTable).getApiTableMetaData(apiName), new ApiTableMetaData());
String associatedTableApiName = StringUtils.hasContent(associatedApiTableMetaData.getApiTableName()) ? associatedApiTableMetaData.getApiTableName() : associatedTableName; String associatedTableApiName = StringUtils.hasContent(associatedApiTableMetaData.getApiTableName()) ? associatedApiTableMetaData.getApiTableName() : associatedTableName;
tableSchema.getProperties().put(association.getName(), new Schema() tableSchema.getProperties().put(association.getName(), new Schema()

View File

@ -30,13 +30,16 @@ import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput; import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput; import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction; import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
@ -73,7 +76,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
fieldList.sort(Comparator.comparing(QFieldMetaData::getLabel)); fieldList.sort(Comparator.comparing(QFieldMetaData::getLabel));
for(QFieldMetaData field : fieldList) for(QFieldMetaData field : fieldList)
{ {
if(!isExcluded(field) && getApiVersionRange(field).includes(version)) if(!isExcluded(input.getApiName(), field) && getApiVersionRange(input.getApiName(), field).includes(version))
{ {
fields.add(field); fields.add(field);
} }
@ -82,9 +85,9 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
// look for removed fields (e.g., not currently in the table anymore), that are in this version // // look for removed fields (e.g., not currently in the table anymore), that are in this version //
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(table))) for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(input.getApiName(), table)))
{ {
if(!isExcluded(field) && getApiVersionRangeForRemovedField(field).includes(version)) if(!isExcluded(input.getApiName(), field) && getApiVersionRangeForRemovedField(input.getApiName(), field).includes(version))
{ {
fields.add(field); fields.add(field);
} }
@ -98,9 +101,9 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private boolean isExcluded(QFieldMetaData field) private boolean isExcluded(String apiName, QFieldMetaData field)
{ {
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field); ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
if(apiFieldMetaData != null && BooleanUtils.isTrue(apiFieldMetaData.getIsExcluded())) if(apiFieldMetaData != null && BooleanUtils.isTrue(apiFieldMetaData.getIsExcluded()))
{ {
return (true); return (true);
@ -114,14 +117,14 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private APIVersionRange getApiVersionRangeForRemovedField(QFieldMetaData field) private APIVersionRange getApiVersionRangeForRemovedField(String apiName, QFieldMetaData field)
{ {
ApiFieldMetaData middlewareMetaData = ApiFieldMetaData.of(field); ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null) if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
{ {
if(StringUtils.hasContent(middlewareMetaData.getFinalVersion())) if(StringUtils.hasContent(apiFieldMetaData.getFinalVersion()))
{ {
return (APIVersionRange.betweenAndIncluding(middlewareMetaData.getInitialVersion(), middlewareMetaData.getFinalVersion())); return (APIVersionRange.betweenAndIncluding(apiFieldMetaData.getInitialVersion(), apiFieldMetaData.getFinalVersion()));
} }
else else
{ {
@ -139,12 +142,12 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private APIVersionRange getApiVersionRange(QFieldMetaData field) private APIVersionRange getApiVersionRange(String apiName, QFieldMetaData field)
{ {
ApiFieldMetaData middlewareMetaData = ApiFieldMetaData.of(field); ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null) if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
{ {
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion())); return (APIVersionRange.afterAndIncluding(apiFieldMetaData.getInitialVersion()));
} }
return (APIVersionRange.none()); return (APIVersionRange.none());
@ -155,9 +158,19 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private List<QFieldMetaData> getRemovedApiFields(QTableMetaData table) private static ApiFieldMetaData getApiFieldMetaData(String apiName, QFieldMetaData field)
{ {
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); return ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
}
/*******************************************************************************
**
*******************************************************************************/
private List<QFieldMetaData> getRemovedApiFields(String apiName, QTableMetaData table)
{
ApiTableMetaData apiTableMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiTableMetaDataContainer.of(table).getApiTableMetaData(apiName), new ApiTableMetaData());
if(apiTableMetaData != null) if(apiTableMetaData != null)
{ {
return (apiTableMetaData.getRemovedApiFields()); return (apiTableMetaData.getRemovedApiFields());

View File

@ -32,6 +32,7 @@ import java.util.stream.Collectors;
import com.kingsrook.qqq.api.javalin.QBadRequestException; import com.kingsrook.qqq.api.javalin.QBadRequestException;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput; import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -40,7 +41,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.Pair; import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -53,17 +54,17 @@ public class QRecordApiAdapter
{ {
private static final QLogger LOG = QLogger.getLogger(QRecordApiAdapter.class); private static final QLogger LOG = QLogger.getLogger(QRecordApiAdapter.class);
private static Map<Pair<String, String>, List<QFieldMetaData>> fieldListCache = new HashMap<>(); private static Map<ApiNameVersionAndTableName, List<QFieldMetaData>> fieldListCache = new HashMap<>();
private static Map<Pair<String, String>, Map<String, QFieldMetaData>> fieldMapCache = new HashMap<>(); private static Map<ApiNameVersionAndTableName, Map<String, QFieldMetaData>> fieldMapCache = new HashMap<>();
/******************************************************************************* /*******************************************************************************
** Convert a QRecord to a map for the API ** Convert a QRecord to a map for the API
*******************************************************************************/ *******************************************************************************/
public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiVersion) throws QException public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiName, String apiVersion) throws QException
{ {
List<QFieldMetaData> tableApiFields = getTableApiFieldList(tableName, apiVersion); List<QFieldMetaData> tableApiFields = getTableApiFieldList(new ApiNameVersionAndTableName(apiName, apiVersion, tableName));
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>(); LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
///////////////////////////////////////// /////////////////////////////////////////
@ -71,9 +72,8 @@ public class QRecordApiAdapter
///////////////////////////////////////// /////////////////////////////////////////
for(QFieldMetaData field : tableApiFields) for(QFieldMetaData field : tableApiFields)
{ {
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field); ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(apiName, field);
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName())) if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
{ {
outputRecord.put(apiFieldName, record.getValue(apiFieldMetaData.getReplacedByFieldName())); outputRecord.put(apiFieldName, record.getValue(apiFieldMetaData.getReplacedByFieldName()));
@ -96,7 +96,7 @@ public class QRecordApiAdapter
for(QRecord associatedRecord : CollectionUtils.nonNullList(CollectionUtils.nonNullMap(record.getAssociatedRecords()).get(association.getName()))) for(QRecord associatedRecord : CollectionUtils.nonNullList(CollectionUtils.nonNullMap(record.getAssociatedRecords()).get(association.getName())))
{ {
associationList.add(qRecordToApiMap(associatedRecord, association.getAssociatedTableName(), apiVersion)); associationList.add(qRecordToApiMap(associatedRecord, association.getAssociatedTableName(), apiName, apiVersion));
} }
} }
@ -108,12 +108,12 @@ public class QRecordApiAdapter
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public static QRecord apiJsonObjectToQRecord(JSONObject jsonObject, String tableName, String apiVersion, boolean includePrimaryKey) throws QException public static QRecord apiJsonObjectToQRecord(JSONObject jsonObject, String tableName, String apiName, String apiVersion, boolean includePrimaryKey) throws QException
{ {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// make map of apiFieldNames (e.g., names as api uses them) to QFieldMetaData // // make map of apiFieldNames (e.g., names as api uses them) to QFieldMetaData //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Map<String, QFieldMetaData> apiFieldsMap = getTableApiFieldMap(tableName, apiVersion); Map<String, QFieldMetaData> apiFieldsMap = getTableApiFieldMap(new ApiNameVersionAndTableName(apiName, apiVersion, tableName));
List<String> unrecognizedFieldNames = new ArrayList<>(); List<String> unrecognizedFieldNames = new ArrayList<>();
QRecord qRecord = new QRecord(); QRecord qRecord = new QRecord();
@ -153,7 +153,7 @@ public class QRecordApiAdapter
} }
} }
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field); ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName())) if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
{ {
qRecord.setValue(apiFieldMetaData.getReplacedByFieldName(), value); qRecord.setValue(apiFieldMetaData.getReplacedByFieldName(), value);
@ -178,7 +178,7 @@ public class QRecordApiAdapter
{ {
if(subObject instanceof JSONObject subJsonObject) if(subObject instanceof JSONObject subJsonObject)
{ {
QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiVersion, includePrimaryKey); QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiName, apiVersion, includePrimaryKey);
qRecord.withAssociatedRecord(association.getName(), subRecord); qRecord.withAssociatedRecord(association.getName(), subRecord);
} }
else else
@ -214,16 +214,15 @@ public class QRecordApiAdapter
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static Map<String, QFieldMetaData> getTableApiFieldMap(String tableName, String apiVersion) throws QException private static Map<String, QFieldMetaData> getTableApiFieldMap(ApiNameVersionAndTableName apiNameVersionAndTableName) throws QException
{ {
Pair<String, String> key = new Pair<>(tableName, apiVersion); if(!fieldMapCache.containsKey(apiNameVersionAndTableName))
if(!fieldMapCache.containsKey(key))
{ {
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f -> (ApiFieldMetaData.getEffectiveApiFieldName(f)), f -> f)); Map<String, QFieldMetaData> map = getTableApiFieldList(apiNameVersionAndTableName).stream().collect(Collectors.toMap(f -> (ApiFieldMetaData.getEffectiveApiFieldName(apiNameVersionAndTableName.apiName(), f)), f -> f));
fieldMapCache.put(key, map); fieldMapCache.put(apiNameVersionAndTableName, map);
} }
return (fieldMapCache.get(key)); return (fieldMapCache.get(apiNameVersionAndTableName));
} }
@ -231,14 +230,27 @@ public class QRecordApiAdapter
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static List<QFieldMetaData> getTableApiFieldList(String tableName, String apiVersion) throws QException private static List<QFieldMetaData> getTableApiFieldList(ApiNameVersionAndTableName apiNameVersionAndTableName) throws QException
{ {
Pair<String, String> key = new Pair<>(tableName, apiVersion); if(!fieldListCache.containsKey(apiNameVersionAndTableName))
if(!fieldListCache.containsKey(key))
{ {
List<QFieldMetaData> value = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields(); List<QFieldMetaData> value = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput()
fieldListCache.put(key, value); .withTableName(apiNameVersionAndTableName.tableName())
.withVersion(apiNameVersionAndTableName.apiVersion())
.withApiName(apiNameVersionAndTableName.apiName())).getFields();
fieldListCache.put(apiNameVersionAndTableName, value);
} }
return (fieldListCache.get(key)); return (fieldListCache.get(apiNameVersionAndTableName));
} }
/*******************************************************************************
**
*******************************************************************************/
private record ApiNameVersionAndTableName(String apiName, String apiVersion, String tableName)
{
}
} }

View File

@ -44,7 +44,9 @@ import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.metadata.APILogMetaDataProvider; import com.kingsrook.qqq.api.model.metadata.APILogMetaDataProvider;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType; import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction; import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
@ -92,6 +94,7 @@ import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModu
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder; import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
@ -121,7 +124,10 @@ public class QJavalinApiHandler
private static QInstance qInstance; private static QInstance qInstance;
private static Map<String, Map<String, QTableMetaData>> tableApiNameMap = new HashMap<>(); /////////////////////////////////////
// key: Pair<apiName, apiVersion> //
/////////////////////////////////////
private static Map<Pair<String, String>, Map<String, QTableMetaData>> tableApiNameMap = new HashMap<>();
private static Map<String, Integer> apiLogUserIdCache = new HashMap<>(); private static Map<String, Integer> apiLogUserIdCache = new HashMap<>();
@ -156,56 +162,59 @@ public class QJavalinApiHandler
ApiBuilder.get("/api/docs/js/rapidoc.min.js", (context) -> QJavalinApiHandler.serveResource(context, "rapidoc/rapidoc-9.3.4.min.js", MapBuilder.of("Content-Type", ContentType.JAVASCRIPT))); ApiBuilder.get("/api/docs/js/rapidoc.min.js", (context) -> QJavalinApiHandler.serveResource(context, "rapidoc/rapidoc-9.3.4.min.js", MapBuilder.of("Content-Type", ContentType.JAVASCRIPT)));
ApiBuilder.get("/api/docs/css/qqq-api-styles.css", (context) -> QJavalinApiHandler.serveResource(context, "rapidoc/rapidoc-overrides.css", MapBuilder.of("Content-Type", ContentType.CSS))); ApiBuilder.get("/api/docs/css/qqq-api-styles.css", (context) -> QJavalinApiHandler.serveResource(context, "rapidoc/rapidoc-overrides.css", MapBuilder.of("Content-Type", ContentType.CSS)));
////////////////////////////////////////////// ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(qInstance);
// default page is the current version spec // for(Map.Entry<String, ApiInstanceMetaData> entry : apiInstanceMetaDataContainer.getApis().entrySet())
//////////////////////////////////////////////
ApiBuilder.get("/api/", QJavalinApiHandler::doSpecHtml);
ApiBuilder.path("/api/{version}", () -> // todo - configurable, that /api/ bit?
{ {
//////////////////////////////////////////// ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
// default page for a version is its spec // String rootPath = apiInstanceMetaData.getPath();
////////////////////////////////////////////
ApiBuilder.get("/", QJavalinApiHandler::doSpecHtml);
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml); //////////////////////////////////////////////
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson); // default page is the current version spec //
ApiBuilder.get("/openapi.html", QJavalinApiHandler::doSpecHtml); //////////////////////////////////////////////
ApiBuilder.get(rootPath, context -> doSpecHtml(context, apiInstanceMetaData));
ApiBuilder.path("/{tableName}", () -> ApiBuilder.path(rootPath + "{version}", () ->
{ {
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml); ////////////////////////////////////////////
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson); // default page for a version is its spec //
////////////////////////////////////////////
ApiBuilder.get("/", context -> doSpecHtml(context, apiInstanceMetaData));
ApiBuilder.post("/", QJavalinApiHandler::doInsert); ApiBuilder.get("/openapi.yaml", context -> doSpecYaml(context, apiInstanceMetaData));
ApiBuilder.get("/openapi.json", context -> doSpecJson(context, apiInstanceMetaData));
ApiBuilder.get("/openapi.html", context -> doSpecHtml(context, apiInstanceMetaData));
ApiBuilder.get("/query", QJavalinApiHandler::doQuery); ApiBuilder.path("/{tableName}", () ->
// ApiBuilder.post("/query", QJavalinApiHandler::doQuery); {
ApiBuilder.get("/openapi.yaml", context -> doSpecYaml(context, apiInstanceMetaData));
ApiBuilder.get("/openapi.json", context -> doSpecJson(context, apiInstanceMetaData));
ApiBuilder.post("/bulk", QJavalinApiHandler::bulkInsert); ApiBuilder.post("/", context -> doInsert(context, apiInstanceMetaData));
ApiBuilder.patch("/bulk", QJavalinApiHandler::bulkUpdate);
ApiBuilder.delete("/bulk", QJavalinApiHandler::bulkDelete);
////////////////////////////////////////////////////////////////// ApiBuilder.get("/query", context -> doQuery(context, apiInstanceMetaData));
// remember to keep the wildcard paths after the specific paths // // ApiBuilder.post("/query", context -> doQuery(context, apiInstanceMetaData));
//////////////////////////////////////////////////////////////////
ApiBuilder.get("/{primaryKey}", QJavalinApiHandler::doGet); ApiBuilder.post("/bulk", context -> bulkInsert(context, apiInstanceMetaData));
ApiBuilder.patch("/{primaryKey}", QJavalinApiHandler::doUpdate); ApiBuilder.patch("/bulk", context -> bulkUpdate(context, apiInstanceMetaData));
ApiBuilder.delete("/{primaryKey}", QJavalinApiHandler::doDelete); ApiBuilder.delete("/bulk", context -> bulkDelete(context, apiInstanceMetaData));
//////////////////////////////////////////////////////////////////
// remember to keep the wildcard paths after the specific paths //
//////////////////////////////////////////////////////////////////
ApiBuilder.get("/{primaryKey}", context -> doGet(context, apiInstanceMetaData));
ApiBuilder.patch("/{primaryKey}", context -> doUpdate(context, apiInstanceMetaData));
ApiBuilder.delete("/{primaryKey}", context -> doDelete(context, apiInstanceMetaData));
});
}); });
});
ApiBuilder.get("/api/versions.json", QJavalinApiHandler::doVersions); ////////////////////////////////////////////////////////////////////////////////////////////////////////////
// default all other requests under the root path (for the methods we support) to a standard 404 response //
ApiBuilder.before("/*", QJavalinApiHandler::setupCORS); ////////////////////////////////////////////////////////////////////////////////////////////////////////////
ApiBuilder.get(rootPath + "*", QJavalinApiHandler::doPathNotFound);
////////////////////////////////////////////////////////////////////////////////////////////// ApiBuilder.delete(rootPath + "*", QJavalinApiHandler::doPathNotFound);
// default all other /api/ requests (for the methods we support) to a standard 404 response // ApiBuilder.patch(rootPath + "*", QJavalinApiHandler::doPathNotFound);
////////////////////////////////////////////////////////////////////////////////////////////// ApiBuilder.post(rootPath + "*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.get("/api/*", QJavalinApiHandler::doPathNotFound); }
ApiBuilder.delete("/api/*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.patch("/api/*", QJavalinApiHandler::doPathNotFound);
ApiBuilder.post("/api/*", QJavalinApiHandler::doPathNotFound);
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
// if the main implementation class has a hot-swapper installed, use it here too // // if the main implementation class has a hot-swapper installed, use it here too //
@ -241,10 +250,8 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doVersions(Context context) private static void doVersions(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
Map<String, Object> rs = new HashMap<>(); Map<String, Object> rs = new HashMap<>();
rs.put("supportedVersions", apiInstanceMetaData.getSupportedVersions().stream().map(String::valueOf).collect(Collectors.toList())); rs.put("supportedVersions", apiInstanceMetaData.getSupportedVersions().stream().map(String::valueOf).collect(Collectors.toList()));
rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString()); rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString());
@ -401,7 +408,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doSpecYaml(Context context) private static void doSpecYaml(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
try try
{ {
@ -409,6 +416,8 @@ public class QJavalinApiHandler
String version = context.pathParam("version"); String version = context.pathParam("version");
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version); GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
input.setApiName(apiInstanceMetaData.getName());
try try
{ {
if(StringUtils.hasContent(context.pathParam("tableName"))) if(StringUtils.hasContent(context.pathParam("tableName")))
@ -438,13 +447,14 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doSpecJson(Context context) private static void doSpecJson(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
try try
{ {
QContext.init(qInstance, null); QContext.init(qInstance, null);
String version = context.pathParam("version"); String version = context.pathParam("version");
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version); GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
input.setApiName(apiInstanceMetaData.getName());
try try
{ {
@ -475,7 +485,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doSpecHtml(Context context) private static void doSpecHtml(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version; String version;
@ -485,11 +495,10 @@ public class QJavalinApiHandler
} }
catch(Exception e) catch(Exception e)
{ {
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
version = apiInstanceMetaData.getCurrentVersion().toString(); version = apiInstanceMetaData.getCurrentVersion().toString();
} }
doSpecHtml(context, version); doSpecHtml(context, version, apiInstanceMetaData);
} }
@ -497,12 +506,11 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doSpecHtml(Context context, String version) private static void doSpecHtml(Context context, String version, ApiInstanceMetaData apiInstanceMetaData)
{ {
try try
{ {
QBrandingMetaData branding = qInstance.getBranding(); QBrandingMetaData branding = qInstance.getBranding();
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
if(!apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(version))) if(!apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(version)))
{ {
@ -536,7 +544,7 @@ public class QJavalinApiHandler
StringBuilder otherVersionOptions = new StringBuilder(); StringBuilder otherVersionOptions = new StringBuilder();
for(APIVersion supportedVersion : apiInstanceMetaData.getSupportedVersions()) for(APIVersion supportedVersion : apiInstanceMetaData.getSupportedVersions())
{ {
otherVersionOptions.append("<option value=\"/api/").append(supportedVersion).append("/openapi.html\">").append(supportedVersion).append("</option>"); otherVersionOptions.append("<option value=\"").append(apiInstanceMetaData.getPath()).append(supportedVersion).append("/openapi.html\">").append(supportedVersion).append("</option>");
} }
html = html.replace("{otherVersionOptions}", otherVersionOptions.toString()); html = html.replace("{otherVersionOptions}", otherVersionOptions.toString());
@ -565,7 +573,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doGet(Context context) private static void doGet(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -574,7 +582,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
GetInput getInput = new GetInput(); GetInput getInput = new GetInput();
@ -605,7 +613,7 @@ public class QJavalinApiHandler
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey)); + table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
} }
Map<String, Serializable> outputRecord = QRecordApiAdapter.qRecordToApiMap(record, tableName, version); Map<String, Serializable> outputRecord = QRecordApiAdapter.qRecordToApiMap(record, tableName, apiInstanceMetaData.getName(), version);
QJavalinAccessLogger.logEndSuccess(); QJavalinAccessLogger.logEndSuccess();
String resultString = JsonUtils.toJson(outputRecord); String resultString = JsonUtils.toJson(outputRecord);
@ -817,7 +825,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doQuery(Context context) private static void doQuery(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -828,7 +836,7 @@ public class QJavalinApiHandler
{ {
List<String> badRequestMessages = new ArrayList<>(); List<String> badRequestMessages = new ArrayList<>();
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
QueryInput queryInput = new QueryInput(); QueryInput queryInput = new QueryInput();
@ -1023,7 +1031,7 @@ public class QJavalinApiHandler
ArrayList<Map<String, Serializable>> records = new ArrayList<>(); ArrayList<Map<String, Serializable>> records = new ArrayList<>();
for(QRecord record : queryOutput.getRecords()) for(QRecord record : queryOutput.getRecords())
{ {
records.add(QRecordApiAdapter.qRecordToApiMap(record, tableName, version)); records.add(QRecordApiAdapter.qRecordToApiMap(record, tableName, apiInstanceMetaData.getName(), version));
} }
///////////////////////////// /////////////////////////////
@ -1057,11 +1065,11 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static QTableMetaData validateTableAndVersion(Context context, String version, String tableApiName) throws QNotFoundException private static QTableMetaData validateTableAndVersion(Context context, ApiInstanceMetaData apiInstanceMetaData, String version, String tableApiName) throws QNotFoundException
{ {
QNotFoundException qNotFoundException = new QNotFoundException("Could not find any resources at path " + context.path()); QNotFoundException qNotFoundException = new QNotFoundException("Could not find any resources at path " + context.path());
QTableMetaData table = getTableByApiName(version, tableApiName); QTableMetaData table = getTableByApiName(apiInstanceMetaData.getName(), version, tableApiName);
if(table == null) if(table == null)
{ {
@ -1073,7 +1081,13 @@ public class QJavalinApiHandler
throw (qNotFoundException); throw (qNotFoundException);
} }
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaDataContainer == null)
{
throw (qNotFoundException);
}
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiInstanceMetaData.getName());
if(apiTableMetaData == null) if(apiTableMetaData == null)
{ {
throw (qNotFoundException); throw (qNotFoundException);
@ -1085,7 +1099,7 @@ public class QJavalinApiHandler
} }
APIVersion requestApiVersion = new APIVersion(version); APIVersion requestApiVersion = new APIVersion(version);
List<APIVersion> supportedVersions = ApiInstanceMetaData.of(qInstance).getSupportedVersions(); List<APIVersion> supportedVersions = apiInstanceMetaData.getSupportedVersions();
if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion)) if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion))
{ {
throw (qNotFoundException); throw (qNotFoundException);
@ -1104,27 +1118,40 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static QTableMetaData getTableByApiName(String version, String tableApiName) private static QTableMetaData getTableByApiName(String apiName, String version, String tableApiName)
{ {
if(tableApiNameMap.get(version) == null) /////////////////////////////////////////////////////////////////////////////////////////////
// tableApiNameMap is a map of (apiName,apiVersion) => Map<String, QTableMetaData>. //
// that is to say, a 2-level map. The first level is keyed by (apiName,apiVersion) pairs. //
// the second level is keyed by tableApiNames. //
/////////////////////////////////////////////////////////////////////////////////////////////
Pair<String, String> key = new Pair<>(apiName, version);
if(tableApiNameMap.get(key) == null)
{ {
Map<String, QTableMetaData> map = new HashMap<>(); Map<String, QTableMetaData> map = new HashMap<>();
for(QTableMetaData table : qInstance.getTables().values()) for(QTableMetaData table : qInstance.getTables().values())
{ {
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
String name = table.getName(); if(apiTableMetaDataContainer != null)
if(apiTableMetaData != null && StringUtils.hasContent(apiTableMetaData.getApiTableName()))
{ {
name = apiTableMetaData.getApiTableName(); ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiName);
if(apiTableMetaData != null)
{
String name = table.getName();
if(StringUtils.hasContent(apiTableMetaData.getApiTableName()))
{
name = apiTableMetaData.getApiTableName();
}
map.put(name, table);
}
} }
map.put(name, table);
} }
tableApiNameMap.put(version, map); tableApiNameMap.put(key, map);
} }
return (tableApiNameMap.get(version).get(tableApiName)); return (tableApiNameMap.get(key).get(tableApiName));
} }
@ -1258,7 +1285,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doInsert(Context context) private static void doInsert(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -1266,7 +1293,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
InsertInput insertInput = new InsertInput(); InsertInput insertInput = new InsertInput();
@ -1288,7 +1315,7 @@ public class QJavalinApiHandler
JSONTokener jsonTokener = new JSONTokener(context.body().trim()); JSONTokener jsonTokener = new JSONTokener(context.body().trim());
JSONObject jsonObject = new JSONObject(jsonTokener); JSONObject jsonObject = new JSONObject(jsonTokener);
insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, false))); insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false)));
if(jsonTokener.more()) if(jsonTokener.more())
{ {
@ -1328,7 +1355,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void bulkInsert(Context context) private static void bulkInsert(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -1336,7 +1363,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
InsertInput insertInput = new InsertInput(); InsertInput insertInput = new InsertInput();
@ -1367,7 +1394,7 @@ public class QJavalinApiHandler
for(int i = 0; i < jsonArray.length(); i++) for(int i = 0; i < jsonArray.length(); i++)
{ {
JSONObject jsonObject = jsonArray.getJSONObject(i); JSONObject jsonObject = jsonArray.getJSONObject(i);
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, false)); recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false));
} }
if(jsonTokener.more()) if(jsonTokener.more())
@ -1437,7 +1464,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void bulkUpdate(Context context) private static void bulkUpdate(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -1445,7 +1472,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
UpdateInput updateInput = new UpdateInput(); UpdateInput updateInput = new UpdateInput();
@ -1476,7 +1503,7 @@ public class QJavalinApiHandler
for(int i = 0; i < jsonArray.length(); i++) for(int i = 0; i < jsonArray.length(); i++)
{ {
JSONObject jsonObject = jsonArray.getJSONObject(i); JSONObject jsonObject = jsonArray.getJSONObject(i);
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, true)); recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, true));
} }
if(jsonTokener.more()) if(jsonTokener.more())
@ -1579,7 +1606,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void bulkDelete(Context context) private static void bulkDelete(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -1587,7 +1614,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
DeleteInput deleteInput = new DeleteInput(); DeleteInput deleteInput = new DeleteInput();
@ -1710,7 +1737,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doUpdate(Context context) private static void doUpdate(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -1719,7 +1746,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
UpdateInput updateInput = new UpdateInput(); UpdateInput updateInput = new UpdateInput();
@ -1741,7 +1768,7 @@ public class QJavalinApiHandler
JSONTokener jsonTokener = new JSONTokener(context.body().trim()); JSONTokener jsonTokener = new JSONTokener(context.body().trim());
JSONObject jsonObject = new JSONObject(jsonTokener); JSONObject jsonObject = new JSONObject(jsonTokener);
QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version, false); QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, apiInstanceMetaData.getName(), version, false);
qRecord.setValue(table.getPrimaryKeyField(), primaryKey); qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
updateInput.setRecords(List.of(qRecord)); updateInput.setRecords(List.of(qRecord));
@ -1794,7 +1821,7 @@ public class QJavalinApiHandler
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void doDelete(Context context) private static void doDelete(Context context, ApiInstanceMetaData apiInstanceMetaData)
{ {
String version = context.pathParam("version"); String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName"); String tableApiName = context.pathParam("tableName");
@ -1803,7 +1830,7 @@ public class QJavalinApiHandler
try try
{ {
QTableMetaData table = validateTableAndVersion(context, version, tableApiName); QTableMetaData table = validateTableAndVersion(context, apiInstanceMetaData, version, tableApiName);
String tableName = table.getName(); String tableName = table.getName();
DeleteInput deleteInput = new DeleteInput(); DeleteInput deleteInput = new DeleteInput();

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
*******************************************************************************/ *******************************************************************************/
public class GenerateOpenApiSpecInput extends AbstractActionInput public class GenerateOpenApiSpecInput extends AbstractActionInput
{ {
private String apiName;
private String version; private String version;
private String tableName; private String tableName;
@ -95,4 +96,35 @@ public class GenerateOpenApiSpecInput extends AbstractActionInput
return (this); return (this);
} }
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public GenerateOpenApiSpecInput withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
} }

View File

@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
*******************************************************************************/ *******************************************************************************/
public class GetTableApiFieldsInput extends AbstractActionInput public class GetTableApiFieldsInput extends AbstractActionInput
{ {
private String apiName;
private String tableName; private String tableName;
private String version; private String version;
@ -95,4 +96,35 @@ public class GetTableApiFieldsInput extends AbstractActionInput
return (this); return (this);
} }
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public GetTableApiFieldsInput withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
} }

View File

@ -23,8 +23,9 @@ package com.kingsrook.qqq.api.model.metadata;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.kingsrook.qqq.api.model.APILog; import com.kingsrook.qqq.api.model.APILog;
import com.kingsrook.qqq.api.model.APIVersion; import com.kingsrook.qqq.api.model.APIVersion;
@ -104,12 +105,20 @@ public class APILogMetaDataProvider
new QPossibleValue<>(500, "500 (Internal Server Error)") new QPossibleValue<>(500, "500 (Internal Server Error)")
))); )));
List<QPossibleValue<?>> apiVersionPossibleValues = new ArrayList<>(); List<QPossibleValue<?>> apiVersionPossibleValues = new ArrayList<>();
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(instance);
LinkedHashSet<APIVersion> allVersions = new LinkedHashSet<>(); ////////////////////////////////////////////////////////////////////////////////////////////////////
allVersions.addAll(apiInstanceMetaData.getPastVersions()); // todo... this, this whole thing, should probably have "which api" as another field too... ugh. //
allVersions.addAll(apiInstanceMetaData.getSupportedVersions()); ////////////////////////////////////////////////////////////////////////////////////////////////////
allVersions.addAll(apiInstanceMetaData.getFutureVersions()); TreeSet<APIVersion> allVersions = new TreeSet<>();
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(instance);
for(Map.Entry<String, ApiInstanceMetaData> entry : apiInstanceMetaDataContainer.getApis().entrySet())
{
ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
allVersions.addAll(apiInstanceMetaData.getPastVersions());
allVersions.addAll(apiInstanceMetaData.getSupportedVersions());
allVersions.addAll(apiInstanceMetaData.getFutureVersions());
}
for(APIVersion version : allVersions) for(APIVersion version : allVersions)
{ {

View File

@ -24,14 +24,14 @@ package com.kingsrook.qqq.api.model.metadata;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.api.model.APIVersion; import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.api.model.openapi.Server; import com.kingsrook.qqq.api.model.openapi.Server;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMiddlewareInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -41,9 +41,11 @@ import org.apache.commons.lang.BooleanUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData public class ApiInstanceMetaData
{ {
private String name; private String name;
private String label;
private String path;
private String description; private String description;
private String contactEmail; private String contactEmail;
@ -59,58 +61,45 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
/******************************************************************************* /*******************************************************************************
** Constructor
** **
*******************************************************************************/ *******************************************************************************/
public ApiInstanceMetaData() public void validate(String apiName, QInstance qInstance, QInstanceValidator validator)
{ {
setType(ApiMiddlewareType.NAME); validator.assertCondition(Objects.equals(apiName, name), "Name mismatch for instance api (" + apiName + " != " + name + ")");
} validator.assertCondition(StringUtils.hasContent(name), "Missing name for api " + apiName);
if(validator.assertCondition(StringUtils.hasContent(path), "Missing path for api " + apiName))
{
validator.assertCondition(path.startsWith("/"), "Path for api " + apiName + " does not start with '/' (but it needs to).");
validator.assertCondition(path.endsWith("/"), "Path for api " + apiName + " does not end with '/' (but it needs to).");
}
validator.assertCondition(StringUtils.hasContent(label), "Missing label for api " + apiName);
/******************************************************************************* validator.assertCondition(StringUtils.hasContent(description), "Missing description for api " + apiName);
** validator.assertCondition(StringUtils.hasContent(contactEmail), "Missing contactEmail for api " + apiName);
*******************************************************************************/
public static ApiInstanceMetaData of(QInstance qInstance)
{
return ((ApiInstanceMetaData) qInstance.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void validate(QInstance qInstance, QInstanceValidator validator)
{
validator.assertCondition(StringUtils.hasContent(name), "Missing name for instance api");
validator.assertCondition(StringUtils.hasContent(description), "Missing description for instance api");
validator.assertCondition(StringUtils.hasContent(contactEmail), "Missing contactEmail for instance api");
Set<APIVersion> allVersions = new HashSet<>(); Set<APIVersion> allVersions = new HashSet<>();
if(validator.assertCondition(currentVersion != null, "Missing currentVersion for instance api")) if(validator.assertCondition(currentVersion != null, "Missing currentVersion for api " + apiName))
{ {
allVersions.add(currentVersion); allVersions.add(currentVersion);
} }
if(validator.assertCondition(supportedVersions != null, "Missing supportedVersions for instance api")) if(validator.assertCondition(supportedVersions != null, "Missing supportedVersions for api " + apiName))
{ {
validator.assertCondition(supportedVersions.contains(currentVersion), "supportedVersions [" + supportedVersions + "] does not contain currentVersion [" + currentVersion + "] for instance api"); validator.assertCondition(supportedVersions.contains(currentVersion), "supportedVersions [" + supportedVersions + "] does not contain currentVersion [" + currentVersion + "] for api " + apiName);
allVersions.addAll(supportedVersions); allVersions.addAll(supportedVersions);
} }
for(APIVersion pastVersion : CollectionUtils.nonNullList(pastVersions)) for(APIVersion pastVersion : CollectionUtils.nonNullList(pastVersions))
{ {
validator.assertCondition(pastVersion.compareTo(currentVersion) < 0, "pastVersion [" + pastVersion + "] is not lexicographically before currentVersion [" + currentVersion + "] for instance api"); validator.assertCondition(pastVersion.compareTo(currentVersion) < 0, "pastVersion [" + pastVersion + "] is not lexicographically before currentVersion [" + currentVersion + "] for api " + apiName);
allVersions.add(pastVersion); allVersions.add(pastVersion);
} }
for(APIVersion futureVersion : CollectionUtils.nonNullList(futureVersions)) for(APIVersion futureVersion : CollectionUtils.nonNullList(futureVersions))
{ {
validator.assertCondition(futureVersion.compareTo(currentVersion) > 0, "futureVersion [" + futureVersion + "] is not lexicographically after currentVersion [" + currentVersion + "] for instance api"); validator.assertCondition(futureVersion.compareTo(currentVersion) > 0, "futureVersion [" + futureVersion + "] is not lexicographically after currentVersion [" + currentVersion + "] for api " + apiName);
allVersions.add(futureVersion); allVersions.add(futureVersion);
} }
@ -119,12 +108,16 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
///////////////////////////////// /////////////////////////////////
for(QTableMetaData table : qInstance.getTables().values()) for(QTableMetaData table : qInstance.getTables().values())
{ {
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
if(apiTableMetaData != null) if(apiTableMetaDataContainer != null)
{ {
if(BooleanUtils.isNotTrue(apiTableMetaData.getIsExcluded())) ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApiTableMetaData(apiName);
if(apiTableMetaData != null)
{ {
validator.assertCondition(allVersions.contains(new APIVersion(apiTableMetaData.getInitialVersion())), "Table " + table.getName() + "'s initial API version is not a recognized version."); if(BooleanUtils.isNotTrue(apiTableMetaData.getIsExcluded()))
{
validator.assertCondition(allVersions.contains(new APIVersion(apiTableMetaData.getInitialVersion())), "Table " + table.getName() + "'s initial API version is not a recognized version for api " + apiName);
}
} }
} }
} }
@ -411,4 +404,66 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
return (this); return (this);
} }
/*******************************************************************************
** Getter for label
*******************************************************************************/
public String getLabel()
{
return (this.label);
}
/*******************************************************************************
** Setter for label
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label
*******************************************************************************/
public ApiInstanceMetaData withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for path
*******************************************************************************/
public String getPath()
{
return (this.path);
}
/*******************************************************************************
** Setter for path
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
*******************************************************************************/
public ApiInstanceMetaData withPath(String path)
{
this.path = path;
return (this);
}
} }

View File

@ -0,0 +1,137 @@
/*
* 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.api.model.metadata;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.api.ApiMiddlewareType;
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.QMiddlewareInstanceMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiInstanceMetaDataContainer extends QMiddlewareInstanceMetaData
{
private Map<String, ApiInstanceMetaData> apis;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiInstanceMetaDataContainer()
{
setType(ApiMiddlewareType.NAME);
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiInstanceMetaDataContainer of(QInstance qInstance)
{
return ((ApiInstanceMetaDataContainer) qInstance.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void validate(QInstance qInstance, QInstanceValidator validator)
{
for(Map.Entry<String, ApiInstanceMetaData> entry : CollectionUtils.nonNullMap(apis).entrySet())
{
entry.getValue().validate(entry.getKey(), qInstance, validator);
}
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public Map<String, ApiInstanceMetaData> getApis()
{
return (this.apis);
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public ApiInstanceMetaData getApiInstanceMetaData(String apiName)
{
if(this.apis == null)
{
return (null);
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/
public void setApis(Map<String, ApiInstanceMetaData> apis)
{
this.apis = apis;
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiInstanceMetaDataContainer withApis(Map<String, ApiInstanceMetaData> apis)
{
this.apis = apis;
return (this);
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiInstanceMetaDataContainer withApiInstanceMetaData(ApiInstanceMetaData apiInstanceMetaData)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
this.apis.put(apiInstanceMetaData.getName(), apiInstanceMetaData);
return (this);
}
}

View File

@ -22,16 +22,14 @@
package com.kingsrook.qqq.api.model.metadata.fields; package com.kingsrook.qqq.api.model.metadata.fields;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class ApiFieldMetaData extends QMiddlewareFieldMetaData public class ApiFieldMetaData
{ {
private String initialVersion; private String initialVersion;
private String finalVersion; private String finalVersion;
@ -44,35 +42,18 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
/******************************************************************************* /*******************************************************************************
** Constructor
** **
*******************************************************************************/ *******************************************************************************/
public ApiFieldMetaData() public static String getEffectiveApiFieldName(String apiName, QFieldMetaData field)
{ {
setType("api"); ApiFieldMetaDataContainer apiFieldMetaDataContainer = ApiFieldMetaDataContainer.of(field);
} if(apiFieldMetaDataContainer != null)
/*******************************************************************************
**
*******************************************************************************/
public static ApiFieldMetaData of(QFieldMetaData field)
{
return ((ApiFieldMetaData) field.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
public static String getEffectiveApiFieldName(QFieldMetaData field)
{
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
if(apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.apiFieldName))
{ {
return (apiFieldMetaData.apiFieldName); ApiFieldMetaData apiFieldMetaData = apiFieldMetaDataContainer.getApiFieldMetaData(apiName);
if(apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.apiFieldName))
{
return (apiFieldMetaData.apiFieldName);
}
} }
return (field.getName()); return (field.getName());

View File

@ -0,0 +1,121 @@
/*
* 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.api.model.metadata.fields;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class ApiFieldMetaDataContainer extends QMiddlewareFieldMetaData
{
private Map<String, ApiFieldMetaData> apis;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiFieldMetaDataContainer()
{
setType("api");
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiFieldMetaDataContainer of(QFieldMetaData field)
{
return ((ApiFieldMetaDataContainer) field.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public Map<String, ApiFieldMetaData> getApis()
{
return (this.apis);
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public ApiFieldMetaData getApiFieldMetaData(String apiName)
{
if(this.apis == null)
{
return (null);
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/
public void setApis(Map<String, ApiFieldMetaData> apis)
{
this.apis = apis;
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiFieldMetaDataContainer withApis(Map<String, ApiFieldMetaData> apis)
{
this.apis = apis;
return (this);
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiFieldMetaDataContainer withApiFieldMetaData(String apiName, ApiFieldMetaData apiFieldMetaData)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
this.apis.put(apiName, apiFieldMetaData);
return (this);
}
}

View File

@ -27,8 +27,8 @@ import java.util.List;
import com.kingsrook.qqq.api.ApiMiddlewareType; import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.api.model.APIVersionRange; import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -36,7 +36,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class ApiTableMetaData extends QMiddlewareTableMetaData public class ApiTableMetaData
{ {
private String initialVersion; private String initialVersion;
private String finalVersion; private String finalVersion;
@ -48,16 +48,6 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/*******************************************************************************
**
*******************************************************************************/
public static ApiTableMetaData of(QTableMetaData table)
{
return ((ApiTableMetaData) table.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -78,16 +68,13 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@Override public void enrich(String apiName, QTableMetaData table)
public void enrich(QTableMetaData table)
{ {
super.enrich(table);
if(initialVersion != null) if(initialVersion != null)
{ {
for(QFieldMetaData field : table.getFields().values()) for(QFieldMetaData field : table.getFields().values())
{ {
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(field); ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(apiName, field);
if(apiFieldMetaData.getInitialVersion() == null) if(apiFieldMetaData.getInitialVersion() == null)
{ {
apiFieldMetaData.setInitialVersion(initialVersion); apiFieldMetaData.setInitialVersion(initialVersion);
@ -96,7 +83,7 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
for(QFieldMetaData field : CollectionUtils.nonNullList(removedApiFields)) for(QFieldMetaData field : CollectionUtils.nonNullList(removedApiFields))
{ {
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(field); ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(apiName, field);
if(apiFieldMetaData.getInitialVersion() == null) if(apiFieldMetaData.getInitialVersion() == null)
{ {
apiFieldMetaData.setInitialVersion(initialVersion); apiFieldMetaData.setInitialVersion(initialVersion);
@ -110,25 +97,20 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static ApiFieldMetaData ensureFieldHasApiMiddlewareMetaData(QFieldMetaData field) private static ApiFieldMetaData ensureFieldHasApiMiddlewareMetaData(String apiName, QFieldMetaData field)
{ {
if(field.getMiddlewareMetaData(ApiMiddlewareType.NAME) == null) if(field.getMiddlewareMetaData(ApiMiddlewareType.NAME) == null)
{ {
field.withMiddlewareMetaData(new ApiFieldMetaData()); field.withMiddlewareMetaData(new ApiFieldMetaDataContainer());
} }
return (ApiFieldMetaData.of(field)); ApiFieldMetaDataContainer apiFieldMetaDataContainer = ApiFieldMetaDataContainer.of(field);
} if(apiFieldMetaDataContainer.getApiFieldMetaData(apiName) == null)
{
apiFieldMetaDataContainer.withApiFieldMetaData(apiName, new ApiFieldMetaData());
}
return (apiFieldMetaDataContainer.getApiFieldMetaData(apiName));
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiTableMetaData()
{
setType("api");
} }

View File

@ -0,0 +1,138 @@
/*
* 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.api.model.metadata.tables;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ApiTableMetaDataContainer extends QMiddlewareTableMetaData
{
private Map<String, ApiTableMetaData> apis;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ApiTableMetaDataContainer()
{
setType("api");
}
/*******************************************************************************
**
*******************************************************************************/
public static ApiTableMetaDataContainer of(QTableMetaData table)
{
return ((ApiTableMetaDataContainer) table.getMiddlewareMetaData(ApiMiddlewareType.NAME));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void enrich(QTableMetaData table)
{
super.enrich(table);
for(Map.Entry<String, ApiTableMetaData> entry : CollectionUtils.nonNullMap(apis).entrySet())
{
entry.getValue().enrich(entry.getKey(), table);
}
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public Map<String, ApiTableMetaData> getApis()
{
return (this.apis);
}
/*******************************************************************************
** Getter for apis
*******************************************************************************/
public ApiTableMetaData getApiTableMetaData(String apiName)
{
if(this.apis == null)
{
return (null);
}
return (this.apis.get(apiName));
}
/*******************************************************************************
** Setter for apis
*******************************************************************************/
public void setApis(Map<String, ApiTableMetaData> apis)
{
this.apis = apis;
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiTableMetaDataContainer withApis(Map<String, ApiTableMetaData> apis)
{
this.apis = apis;
return (this);
}
/*******************************************************************************
** Fluent setter for apis
*******************************************************************************/
public ApiTableMetaDataContainer withApiTableMetaData(String apiName, ApiTableMetaData apiTableMetaData)
{
if(this.apis == null)
{
this.apis = new LinkedHashMap<>();
}
this.apis.put(apiName, apiTableMetaData);
return (this);
}
}

View File

@ -25,8 +25,11 @@ package com.kingsrook.qqq.api;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.api.model.APIVersion; import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
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;
@ -57,6 +60,9 @@ public class TestUtils
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic"; public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic"; public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic";
public static final String API_NAME = "test-api";
public static final String ALTERNATIVE_API_NAME = "person-api";
public static final String V2022_Q4 = "2022.Q4"; public static final String V2022_Q4 = "2022.Q4";
public static final String V2023_Q1 = "2023.Q1"; public static final String V2023_Q1 = "2023.Q1";
public static final String V2023_Q2 = "2023.Q2"; public static final String V2023_Q2 = "2023.Q2";
@ -85,14 +91,27 @@ public class TestUtils
qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous")); qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData() qInstance.withMiddlewareMetaData(new ApiInstanceMetaDataContainer()
.withName("TestAPI") .withApiInstanceMetaData(new ApiInstanceMetaData()
.withDescription("QQQ Test API") .withName(API_NAME)
.withContactEmail("contact@kingsrook.com") .withPath("/api/")
.withCurrentVersion(new APIVersion(CURRENT_API_VERSION)) .withLabel("Test API")
.withSupportedVersions(List.of(new APIVersion(V2022_Q4), new APIVersion(V2023_Q1))) .withDescription("QQQ Test API")
.withPastVersions(List.of(new APIVersion(V2022_Q4))) .withContactEmail("contact@kingsrook.com")
.withFutureVersions(List.of(new APIVersion(V2023_Q2))) .withCurrentVersion(new APIVersion(CURRENT_API_VERSION))
.withSupportedVersions(List.of(new APIVersion(V2022_Q4), new APIVersion(V2023_Q1)))
.withPastVersions(List.of(new APIVersion(V2022_Q4)))
.withFutureVersions(List.of(new APIVersion(V2023_Q2))))
.withApiInstanceMetaData(new ApiInstanceMetaData()
.withName(ALTERNATIVE_API_NAME)
.withPath("/person-api/")
.withLabel("Person-Only API")
.withDescription("QQQ Test API, that only has the Person table.")
.withContactEmail("contact@kingsrook.com")
.withCurrentVersion(new APIVersion(CURRENT_API_VERSION))
.withSupportedVersions(List.of(new APIVersion(V2022_Q4), new APIVersion(V2023_Q1)))
.withPastVersions(List.of(new APIVersion(V2022_Q4)))
.withFutureVersions(List.of(new APIVersion(V2023_Q2))))
); );
return (qInstance); return (qInstance);
@ -117,20 +136,10 @@ public class TestUtils
*******************************************************************************/ *******************************************************************************/
public static QTableMetaData defineTablePerson() public static QTableMetaData defineTablePerson()
{ {
return new QTableMetaData() QTableMetaData table = new QTableMetaData()
.withName(TABLE_NAME_PERSON) .withName(TABLE_NAME_PERSON)
.withLabel("Person") .withLabel("Person")
.withBackendName(MEMORY_BACKEND_NAME) .withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData()
.withInitialVersion(V2022_Q4)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in 2022.Q4, this table had a "shoeCount" field. but for the 2023.Q1 version, we renamed it to noOfShoes! //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
.withRemovedApiField(new QFieldMetaData("shoeCount", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS)
.withMiddlewareMetaData(new ApiFieldMetaData().withFinalVersion(V2022_Q4).withReplacedByFieldName("noOfShoes")))
)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withUniqueKey(new UniqueKey("email")) .withUniqueKey(new UniqueKey("email"))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
@ -138,24 +147,48 @@ public class TestUtils
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false)) .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("firstName", QFieldType.STRING)) .withField(new QFieldMetaData("firstName", QFieldType.STRING))
.withField(new QFieldMetaData("lastName", QFieldType.STRING)) .withField(new QFieldMetaData("lastName", QFieldType.STRING))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE) .withField(new QFieldMetaData("birthDate", QFieldType.DATE))
.withMiddlewareMetaData(new ApiFieldMetaData().withApiFieldName("birthDay"))
)
.withField(new QFieldMetaData("email", QFieldType.STRING)) .withField(new QFieldMetaData("email", QFieldType.STRING))
// .withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_STATE)) // .withField(new QFieldMetaData("homeStateId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_STATE))
// .withField(new QFieldMetaData("favoriteShapeId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_SHAPE)) // .withField(new QFieldMetaData("favoriteShapeId", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_SHAPE))
// .withField(new QFieldMetaData("customValue", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_CUSTOM)) // .withField(new QFieldMetaData("customValue", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_CUSTOM))
.withField(new QFieldMetaData("noOfShoes", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS) .withField(new QFieldMetaData("noOfShoes", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS))
.withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion(V2023_Q1))) .withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY));
///////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 2 new fields - they'll appear in future versions of the API // // make some changes to this table in the "main" api (but leave it like the backend in the ALTERNATIVE_API_NAME) //
///////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
.withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY) table.withMiddlewareMetaData(new ApiTableMetaDataContainer()
.withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion(V2023_Q2))) .withApiTableMetaData(API_NAME, new ApiTableMetaData()
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY) .withInitialVersion(V2022_Q4)
.withMiddlewareMetaData(new ApiFieldMetaData().withIsExcluded(true)))
; //////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in 2022.Q4, this table had a "shoeCount" field. but for the 2023.Q1 version, we renamed it to noOfShoes! //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
.withRemovedApiField(new QFieldMetaData("shoeCount", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS)
.withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(API_NAME,
new ApiFieldMetaData().withFinalVersion(V2022_Q4).withReplacedByFieldName("noOfShoes"))))
)
.withApiTableMetaData(ALTERNATIVE_API_NAME, new ApiTableMetaData().withInitialVersion(V2022_Q4)));
/////////////////////////////////////////////////////
// change the name for this field for the main api //
/////////////////////////////////////////////////////
table.getField("birthDate").withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(API_NAME, new ApiFieldMetaData().withApiFieldName("birthDay")));
////////////////////////////////////////////////////////////////////////////////
// See above - we renamed this field (in the backend) for the 2023_Q1 version //
////////////////////////////////////////////////////////////////////////////////
table.getField("noOfShoes").withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(API_NAME, new ApiFieldMetaData().withInitialVersion(V2023_Q1)));
/////////////////////////////////////////////////////////////////////////////////////////////////
// 2 new fields - one will appear in a future version of the API, the other is always excluded //
/////////////////////////////////////////////////////////////////////////////////////////////////
table.getField("cost").withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(API_NAME, new ApiFieldMetaData().withInitialVersion(V2023_Q2)));
table.getField("price").withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(API_NAME, new ApiFieldMetaData().withIsExcluded(true)));
return (table);
} }
@ -168,7 +201,7 @@ public class TestUtils
return new QTableMetaData() return new QTableMetaData()
.withName(TABLE_NAME_ORDER) .withName(TABLE_NAME_ORDER)
.withBackendName(MEMORY_BACKEND_NAME) .withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4)) .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion(V2022_Q4)))
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withAssociation(new Association().withName("orderLines").withAssociatedTableName(TABLE_NAME_LINE_ITEM).withJoinName("orderLineItem")) .withAssociation(new Association().withName("orderLines").withAssociatedTableName(TABLE_NAME_LINE_ITEM).withJoinName("orderLineItem"))
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_ORDER_EXTRINSIC).withJoinName("orderOrderExtrinsic")) .withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_ORDER_EXTRINSIC).withJoinName("orderOrderExtrinsic"))
@ -191,7 +224,7 @@ public class TestUtils
return new QTableMetaData() return new QTableMetaData()
.withName(TABLE_NAME_LINE_ITEM) .withName(TABLE_NAME_LINE_ITEM)
.withBackendName(MEMORY_BACKEND_NAME) .withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4)) .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion(V2022_Q4)))
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("lineItemLineItemExtrinsic")) .withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("lineItemLineItemExtrinsic"))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
@ -213,7 +246,7 @@ public class TestUtils
return new QTableMetaData() return new QTableMetaData()
.withName(TABLE_NAME_LINE_ITEM_EXTRINSIC) .withName(TABLE_NAME_LINE_ITEM_EXTRINSIC)
.withBackendName(MEMORY_BACKEND_NAME) .withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4)) .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion(V2022_Q4)))
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false)) .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
@ -233,7 +266,7 @@ public class TestUtils
return new QTableMetaData() return new QTableMetaData()
.withName(TABLE_NAME_ORDER_EXTRINSIC) .withName(TABLE_NAME_ORDER_EXTRINSIC)
.withBackendName(MEMORY_BACKEND_NAME) .withBackendName(MEMORY_BACKEND_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(V2022_Q4)) .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion(V2022_Q4)))
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false)) .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))

View File

@ -28,6 +28,7 @@ import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -52,7 +53,7 @@ class GenerateOpenApiSpecActionTest extends BaseTest
@Test @Test
void test() throws QException void test() throws QException
{ {
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)); GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION).withApiName(TestUtils.API_NAME));
System.out.println(output.getYaml()); System.out.println(output.getYaml());
} }
@ -71,8 +72,8 @@ class GenerateOpenApiSpecActionTest extends BaseTest
.withBackendName(TestUtils.MEMORY_BACKEND_NAME) .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4))); .withInitialVersion(TestUtils.V2022_Q4))));
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("hiddenTable") .withName("hiddenTable")
@ -80,16 +81,16 @@ class GenerateOpenApiSpecActionTest extends BaseTest
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withIsHidden(true) .withIsHidden(true)
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4))); .withInitialVersion(TestUtils.V2022_Q4))));
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("excludedTable") .withName("excludedTable")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME) .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withIsExcluded(true))); .withIsExcluded(true))));
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("tableWithoutApiMetaData") .withName("tableWithoutApiMetaData")
@ -102,17 +103,17 @@ class GenerateOpenApiSpecActionTest extends BaseTest
.withBackendName(TestUtils.MEMORY_BACKEND_NAME) .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withInitialVersion(TestUtils.V2023_Q2))); .withInitialVersion(TestUtils.V2023_Q2))));
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("tableWithOnlyPastVersions") .withName("tableWithOnlyPastVersions")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME) .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4) .withInitialVersion(TestUtils.V2022_Q4)
.withFinalVersion(TestUtils.V2022_Q4))); .withFinalVersion(TestUtils.V2022_Q4))));
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("tableWithNoSupportedCapabilities") .withName("tableWithNoSupportedCapabilities")
@ -120,10 +121,10 @@ class GenerateOpenApiSpecActionTest extends BaseTest
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withoutCapabilities(Capability.TABLE_QUERY, Capability.TABLE_GET, Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE) .withoutCapabilities(Capability.TABLE_QUERY, Capability.TABLE_GET, Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE)
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4))); .withInitialVersion(TestUtils.V2022_Q4))));
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)); GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION).withApiName(TestUtils.API_NAME));
Set<String> apiPaths = output.getOpenAPI().getPaths().keySet(); Set<String> apiPaths = output.getOpenAPI().getPaths().keySet();
assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/supportedTable/"))); assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/supportedTable/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/hiddenTable/"))); assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/hiddenTable/")));
@ -149,11 +150,11 @@ class GenerateOpenApiSpecActionTest extends BaseTest
.withBackendName(TestUtils.MEMORY_BACKEND_NAME) .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withApiTableName("externalName") .withApiTableName("externalName")
.withInitialVersion(TestUtils.V2022_Q4))); .withInitialVersion(TestUtils.V2022_Q4))));
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)); GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION).withApiName(TestUtils.API_NAME));
Set<String> apiPaths = output.getOpenAPI().getPaths().keySet(); Set<String> apiPaths = output.getOpenAPI().getPaths().keySet();
assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/externalName/"))); assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/externalName/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/internalName/"))); assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/internalName/")));
@ -167,9 +168,31 @@ class GenerateOpenApiSpecActionTest extends BaseTest
@Test @Test
void testBadVersion() void testBadVersion()
{ {
assertThatThrownBy(() -> new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion("NotAVersion"))) assertThatThrownBy(() -> new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withApiName(TestUtils.API_NAME)))
.isInstanceOf(QException.class)
.hasMessageContaining("Missing required input: version");
assertThatThrownBy(() -> new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion("NotAVersion").withApiName(TestUtils.API_NAME)))
.isInstanceOf(QException.class) .isInstanceOf(QException.class)
.hasMessageContaining("not a supported API Version"); .hasMessageContaining("not a supported API Version");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testBadApiName()
{
assertThatThrownBy(() -> new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withApiName("Not an api")))
.isInstanceOf(QException.class)
.hasMessageContaining("Could not find apiInstanceMetaData named");
assertThatThrownBy(() -> new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)))
.isInstanceOf(QException.class)
.hasMessageContaining("Missing required input: apiName");
} }
} }

View File

@ -27,9 +27,12 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.kingsrook.qqq.api.BaseTest; import com.kingsrook.qqq.api.BaseTest;
import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput; import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher; import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
@ -57,7 +60,7 @@ class GetTableApiFieldsActionTest extends BaseTest
*******************************************************************************/ *******************************************************************************/
private List<? extends QFieldMetaData> getFields(String tableName, String version) throws QException private List<? extends QFieldMetaData> getFields(String tableName, String version) throws QException
{ {
return new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields(); return new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withApiName(TestUtils.API_NAME).withTableName(tableName).withVersion(version)).getFields();
} }
@ -71,11 +74,11 @@ class GetTableApiFieldsActionTest extends BaseTest
QInstance qInstance = QContext.getQInstance(); QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName(TABLE_NAME) .withName(TABLE_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion("1")) .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("1")))
.withField(new QFieldMetaData("a", STRING)) // inherit versionRange from the table .withField(new QFieldMetaData("a", STRING)) // inherit versionRange from the table
.withField(new QFieldMetaData("b", STRING).withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion("1"))) .withField(new QFieldMetaData("b", STRING).withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(TestUtils.API_NAME, new ApiFieldMetaData().withInitialVersion("1"))))
.withField(new QFieldMetaData("c", STRING).withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion("2"))) .withField(new QFieldMetaData("c", STRING).withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(TestUtils.API_NAME, new ApiFieldMetaData().withInitialVersion("2"))))
.withField(new QFieldMetaData("d", STRING).withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion("3"))) .withField(new QFieldMetaData("d", STRING).withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(TestUtils.API_NAME, new ApiFieldMetaData().withInitialVersion("3"))))
); );
new QInstanceEnricher(qInstance).enrich(); new QInstanceEnricher(qInstance).enrich();
@ -95,13 +98,13 @@ class GetTableApiFieldsActionTest extends BaseTest
QInstance qInstance = QContext.getQInstance(); QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName(TABLE_NAME) .withName(TABLE_NAME)
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion("1") .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("1")
.withRemovedApiField(new QFieldMetaData("c", STRING).withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion("1").withFinalVersion("2"))) .withRemovedApiField(new QFieldMetaData("c", STRING).withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(TestUtils.API_NAME, new ApiFieldMetaData().withInitialVersion("1").withFinalVersion("2"))))
) ))
.withField(new QFieldMetaData("a", STRING)) // inherit versionRange from the table .withField(new QFieldMetaData("a", STRING)) // inherit versionRange from the table
.withField(new QFieldMetaData("b", STRING).withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion("1"))) .withField(new QFieldMetaData("b", STRING).withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(TestUtils.API_NAME, new ApiFieldMetaData().withInitialVersion("1"))))
// we used to have "c" here... now it's in the removed list above! // we used to have "c" here... now it's in the removed list above!
.withField(new QFieldMetaData("d", STRING).withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion("3"))) .withField(new QFieldMetaData("d", STRING).withMiddlewareMetaData(new ApiFieldMetaDataContainer().withApiFieldMetaData(TestUtils.API_NAME, new ApiFieldMetaData().withInitialVersion("3"))))
); );
new QInstanceEnricher(qInstance).enrich(); new QInstanceEnricher(qInstance).enrich();

View File

@ -33,6 +33,7 @@ import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.javalin.QBadRequestException; import com.kingsrook.qqq.api.javalin.QBadRequestException;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -62,17 +63,17 @@ class QRecordApiAdapterTest extends BaseTest
.withValue("cost", new BigDecimal("3.50")) .withValue("cost", new BigDecimal("3.50"))
.withValue("price", new BigDecimal("9.99")); .withValue("price", new BigDecimal("9.99"));
Map<String, Serializable> pastApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.V2022_Q4); Map<String, Serializable> pastApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2022_Q4);
assertEquals(2, pastApiRecord.get("shoeCount")); // old field name - not currently in the QTable, but we can still get its value! assertEquals(2, pastApiRecord.get("shoeCount")); // old field name - not currently in the QTable, but we can still get its value!
assertFalse(pastApiRecord.containsKey("noOfShoes")); // current field name - doesn't appear in old api-version assertFalse(pastApiRecord.containsKey("noOfShoes")); // current field name - doesn't appear in old api-version
assertFalse(pastApiRecord.containsKey("cost")); // a current field name, but also not in this old api version assertFalse(pastApiRecord.containsKey("cost")); // a current field name, but also not in this old api version
Map<String, Serializable> currentApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1); Map<String, Serializable> currentApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q1);
assertFalse(currentApiRecord.containsKey("shoeCount")); // old field name - not in this current api version assertFalse(currentApiRecord.containsKey("shoeCount")); // old field name - not in this current api version
assertEquals(2, currentApiRecord.get("noOfShoes")); // current field name - value here as we expect assertEquals(2, currentApiRecord.get("noOfShoes")); // current field name - value here as we expect
assertFalse(currentApiRecord.containsKey("cost")); // future field name - not in the current api (we added the field during new dev, and didn't change the api) assertFalse(currentApiRecord.containsKey("cost")); // future field name - not in the current api (we added the field during new dev, and didn't change the api)
Map<String, Serializable> futureApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q2); Map<String, Serializable> futureApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q2);
assertFalse(futureApiRecord.containsKey("shoeCount")); // old field name - also not in this future api version assertFalse(futureApiRecord.containsKey("shoeCount")); // old field name - also not in this future api version
assertEquals(2, futureApiRecord.get("noOfShoes")); // current field name - still here. assertEquals(2, futureApiRecord.get("noOfShoes")); // current field name - still here.
assertEquals(new BigDecimal("3.50"), futureApiRecord.get("cost")); // future field name appears now that we've requested this future api version. assertEquals(new BigDecimal("3.50"), futureApiRecord.get("cost")); // future field name appears now that we've requested this future api version.
@ -82,6 +83,18 @@ class QRecordApiAdapterTest extends BaseTest
assertEquals(LocalDate.parse("1980-05-31"), apiRecord.get("birthDay")); // use the apiFieldName assertEquals(LocalDate.parse("1980-05-31"), apiRecord.get("birthDay")); // use the apiFieldName
assertFalse(apiRecord.containsKey("price")); // excluded field never appears assertFalse(apiRecord.containsKey("price")); // excluded field never appears
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// confirm that for the alternative api, we get a record that looks just like the input record (per its api meta data) //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(String version : List.of(TestUtils.V2022_Q4, TestUtils.V2023_Q1, TestUtils.V2023_Q2))
{
Map<String, Serializable> alternativeApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.ALTERNATIVE_API_NAME, version);
for(String key : person.getValues().keySet())
{
assertEquals(person.getValueString(key), ValueUtils.getValueAsString(alternativeApiRecord.get(key)));
}
}
} }
@ -97,7 +110,7 @@ class QRecordApiAdapterTest extends BaseTest
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
QRecord recordFromOldApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" QRecord recordFromOldApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "shoeCount": 2} {"firstName": "Tim", "shoeCount": 2}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2022_Q4, true); """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2022_Q4, true);
assertEquals(2, recordFromOldApi.getValueInteger("noOfShoes")); assertEquals(2, recordFromOldApi.getValueInteger("noOfShoes"));
/////////////////////////////////////////// ///////////////////////////////////////////
@ -105,7 +118,7 @@ class QRecordApiAdapterTest extends BaseTest
/////////////////////////////////////////// ///////////////////////////////////////////
QRecord recordFromCurrentApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" QRecord recordFromCurrentApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "noOfShoes": 2} {"firstName": "Tim", "noOfShoes": 2}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1, true); """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q1, true);
assertEquals(2, recordFromCurrentApi.getValueInteger("noOfShoes")); assertEquals(2, recordFromCurrentApi.getValueInteger("noOfShoes"));
///////////////////////////////////////////// /////////////////////////////////////////////
@ -113,7 +126,7 @@ class QRecordApiAdapterTest extends BaseTest
///////////////////////////////////////////// /////////////////////////////////////////////
QRecord recordFromFutureApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" QRecord recordFromFutureApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "noOfShoes": 2, "cost": 3.50} {"firstName": "Tim", "noOfShoes": 2, "cost": 3.50}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q2, true); """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q2, true);
assertEquals(2, recordFromFutureApi.getValueInteger("noOfShoes")); assertEquals(2, recordFromFutureApi.getValueInteger("noOfShoes"));
assertEquals(new BigDecimal("3.50"), recordFromFutureApi.getValueBigDecimal("cost")); assertEquals(new BigDecimal("3.50"), recordFromFutureApi.getValueBigDecimal("cost"));
@ -122,7 +135,7 @@ class QRecordApiAdapterTest extends BaseTest
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
QRecord recordWithApiFieldName = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" QRecord recordWithApiFieldName = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "birthDay": "1976-05-28"} {"firstName": "Tim", "birthDay": "1976-05-28"}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q2, true); """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q2, true);
assertEquals("1976-05-28", recordWithApiFieldName.getValueString("birthDate")); assertEquals("1976-05-28", recordWithApiFieldName.getValueString("birthDate"));
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
@ -130,7 +143,7 @@ class QRecordApiAdapterTest extends BaseTest
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "noOfShoes": 2} {"firstName": "Tim", "noOfShoes": 2}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2022_Q4, true)) """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2022_Q4, true))
.isInstanceOf(QBadRequestException.class) .isInstanceOf(QBadRequestException.class)
.hasMessageContaining("unrecognized field name: noOfShoes"); .hasMessageContaining("unrecognized field name: noOfShoes");
@ -139,7 +152,7 @@ class QRecordApiAdapterTest extends BaseTest
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "cost": 2} {"firstName": "Tim", "cost": 2}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1, true)) """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q1, true))
.isInstanceOf(QBadRequestException.class) .isInstanceOf(QBadRequestException.class)
.hasMessageContaining("unrecognized field name: cost"); .hasMessageContaining("unrecognized field name: cost");
@ -150,7 +163,7 @@ class QRecordApiAdapterTest extends BaseTest
{ {
assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "price": 2} {"firstName": "Tim", "price": 2}
"""), TestUtils.TABLE_NAME_PERSON, version, true)) """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, version, true))
.isInstanceOf(QBadRequestException.class) .isInstanceOf(QBadRequestException.class)
.hasMessageContaining("unrecognized field name: price"); .hasMessageContaining("unrecognized field name: price");
} }
@ -160,7 +173,7 @@ class QRecordApiAdapterTest extends BaseTest
//////////////////////////////////////////// ////////////////////////////////////////////
QRecord recordWithoutNonEditableFields = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" QRecord recordWithoutNonEditableFields = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "birthDay": "1976-05-28", "createDate": "2023-03-31T11:44:28Z", "id": 256} {"firstName": "Tim", "birthDay": "1976-05-28", "createDate": "2023-03-31T11:44:28Z", "id": 256}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1, false); """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q1, false);
assertFalse(recordWithoutNonEditableFields.getValues().containsKey("createDate")); assertFalse(recordWithoutNonEditableFields.getValues().containsKey("createDate"));
assertFalse(recordWithoutNonEditableFields.getValues().containsKey("id")); assertFalse(recordWithoutNonEditableFields.getValues().containsKey("id"));
@ -169,7 +182,7 @@ class QRecordApiAdapterTest extends BaseTest
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
QRecord recordWithoutNonEditablePrimaryKeyFields = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject(""" QRecord recordWithoutNonEditablePrimaryKeyFields = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
{"firstName": "Tim", "birthDay": "1976-05-28", "createDate": "2023-03-31T11:44:28Z", "id": 256} {"firstName": "Tim", "birthDay": "1976-05-28", "createDate": "2023-03-31T11:44:28Z", "id": 256}
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1, true); """), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q1, true);
assertFalse(recordWithoutNonEditablePrimaryKeyFields.getValues().containsKey("createDate")); assertFalse(recordWithoutNonEditablePrimaryKeyFields.getValues().containsKey("createDate"));
assertEquals(256, recordWithoutNonEditablePrimaryKeyFields.getValues().get("id")); assertEquals(256, recordWithoutNonEditablePrimaryKeyFields.getValues().get("id"));

View File

@ -22,11 +22,14 @@
package com.kingsrook.qqq.api.javalin; package com.kingsrook.qqq.api.javalin;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.api.BaseTest; import com.kingsrook.qqq.api.BaseTest;
import com.kingsrook.qqq.api.TestUtils; import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -49,6 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.FullyAnonymousAuthenticationModule; import com.kingsrook.qqq.backend.core.modules.authentication.implementations.FullyAnonymousAuthenticationModule;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import io.javalin.apibuilder.EndpointGroup;
import kong.unirest.HttpResponse; import kong.unirest.HttpResponse;
import kong.unirest.Unirest; import kong.unirest.Unirest;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
@ -91,13 +95,14 @@ class QJavalinApiHandlerTest extends BaseTest
.withBackendName(TestUtils.MEMORY_BACKEND_NAME) .withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData() .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData()
.withApiTableName("externalName") .withApiTableName("externalName")
.withInitialVersion(TestUtils.V2022_Q4))); .withInitialVersion(TestUtils.V2022_Q4))));
qJavalinImplementation = new QJavalinImplementation(qInstance); qJavalinImplementation = new QJavalinImplementation(qInstance);
qJavalinImplementation.startJavalinServer(PORT); qJavalinImplementation.startJavalinServer(PORT);
qJavalinImplementation.getJavalinService().routes(new QJavalinApiHandler(qInstance).getRoutes()); EndpointGroup routes = new QJavalinApiHandler(qInstance).getRoutes();
qJavalinImplementation.getJavalinService().routes(routes);
} }
@ -124,7 +129,7 @@ class QJavalinApiHandlerTest extends BaseTest
System.out.println(response.getBody()); System.out.println(response.getBody());
assertThat(response.getBody()) assertThat(response.getBody())
.contains(""" .contains("""
title: "TestAPI" title: "Test API"
""") """)
.contains(""" .contains("""
/person/query: /person/query:
@ -274,6 +279,77 @@ class QJavalinApiHandlerTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testQuery200AlternativeApi()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/person-api/" + VERSION + "/person/query").asString();
assertEquals(HttpStatus.OK_200, response.getStatus());
JSONObject jsonObject = new JSONObject(response.getBody());
assertEquals(0, jsonObject.getInt("count"));
assertEquals(1, jsonObject.getInt("pageNo"));
assertEquals(50, jsonObject.getInt("pageSize"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableOnlyInOneApi()
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// the person table (which most of our tests are against) is in both api's in this instance (/api/ and /person-api/). //
// do a request here for the order table, which is only in /api //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/order/query").asString();
assertEquals(HttpStatus.OK_200, response.getStatus());
JSONObject jsonObject = new JSONObject(response.getBody());
assertEquals(0, jsonObject.getInt("count"));
assertEquals(1, jsonObject.getInt("pageNo"));
assertEquals(50, jsonObject.getInt("pageSize"));
///////////////////////////////////////////////////////////////////////
// now make sure we get a 404 for it under the /person-api/ api path //
///////////////////////////////////////////////////////////////////////
response = Unirest.get(BASE_URL + "/person-api/" + VERSION + "/order/query").asString();
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldDifferencesBetweenApis() throws QException
{
insertPersonRecord(1, "Homer", "Simpson", LocalDate.of(1970, Month.JANUARY, 1));
/////////////////////////////////////////////////////////////
// on the main api, birthDate has been renamed to birthDay //
/////////////////////////////////////////////////////////////
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/1").asString();
assertEquals(HttpStatus.OK_200, response.getStatus());
JSONObject jsonObject = new JSONObject(response.getBody());
assertEquals("1970-01-01", jsonObject.getString("birthDay"));
assertFalse(jsonObject.has("birthDate"));
///////////////////////////////////////////
// but it's birthDate on the /person-api //
///////////////////////////////////////////
response = Unirest.get(BASE_URL + "/person-api/" + VERSION + "/person/1").asString();
assertEquals(HttpStatus.OK_200, response.getStatus());
jsonObject = new JSONObject(response.getBody());
assertEquals("1970-01-01", jsonObject.getString("birthDate"));
assertFalse(jsonObject.has("birthDay"));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -1168,10 +1244,20 @@ class QJavalinApiHandlerTest extends BaseTest
** **
*******************************************************************************/ *******************************************************************************/
private static void insertPersonRecord(Integer id, String firstName, String lastName) throws QException private static void insertPersonRecord(Integer id, String firstName, String lastName) throws QException
{
insertPersonRecord(id, firstName, lastName, null);
}
/*******************************************************************************
**
*******************************************************************************/
private static void insertPersonRecord(Integer id, String firstName, String lastName, LocalDate birthDate) throws QException
{ {
InsertInput insertInput = new InsertInput(); InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON); insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName))); insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName).withValue("birthDate", birthDate)));
new InsertAction().execute(insertInput); new InsertAction().execute(insertInput);
} }

View File

@ -23,8 +23,10 @@ package com.kingsrook.qqq.api.model.metadata;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.model.APIVersion; import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -46,15 +48,7 @@ class ApiInstanceMetaDataTest
@Test @Test
void testValidationPasses() void testValidationPasses()
{ {
assertValidationErrors(new ApiInstanceMetaData() assertValidationErrors(makeBaselineValidApiInstanceMetaDataWithVersions(), List.of());
.withName("QQQ API")
.withDescription("Test API for QQQ")
.withContactEmail("contact@kingsrook.com")
.withCurrentVersion(new APIVersion("2023.Q1"))
.withSupportedVersions(List.of(new APIVersion("2022.Q3"), new APIVersion("2022.Q4"), new APIVersion("2023.Q1")))
.withPastVersions(List.of(new APIVersion("2022.Q2"), new APIVersion("2022.Q3"), new APIVersion("2022.Q4")))
.withFutureVersions(List.of(new APIVersion("2023.Q2"))),
List.of());
} }
@ -67,6 +61,8 @@ class ApiInstanceMetaDataTest
{ {
assertValidationErrors(new ApiInstanceMetaData(), List.of( assertValidationErrors(new ApiInstanceMetaData(), List.of(
"Missing name", "Missing name",
"Missing label",
"Missing path",
"Missing description", "Missing description",
"Missing contactEmail", "Missing contactEmail",
"Missing currentVersion", "Missing currentVersion",
@ -118,11 +114,11 @@ class ApiInstanceMetaDataTest
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("myValidTable") .withName("myValidTable")
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion("2023.Q1"))); .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("2023.Q1"))));
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("myInvalidTable") .withName("myInvalidTable")
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion("2022.Q1"))); .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("notAVersion"))));
assertValidationErrors(qInstance, makeBaselineValidApiInstanceMetaData() assertValidationErrors(qInstance, makeBaselineValidApiInstanceMetaData()
.withCurrentVersion(new APIVersion("2023.Q1")) .withCurrentVersion(new APIVersion("2023.Q1"))
@ -131,7 +127,7 @@ class ApiInstanceMetaDataTest
qInstance.addTable(new QTableMetaData() qInstance.addTable(new QTableMetaData()
.withName("myFutureValidTable") .withName("myFutureValidTable")
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion("2024.Q1"))); .withMiddlewareMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("2024.Q1"))));
assertValidationErrors(qInstance, makeBaselineValidApiInstanceMetaData() assertValidationErrors(qInstance, makeBaselineValidApiInstanceMetaData()
.withCurrentVersion(new APIVersion("2023.Q1")) .withCurrentVersion(new APIVersion("2023.Q1"))
@ -143,19 +139,46 @@ class ApiInstanceMetaDataTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPathSlashes()
{
assertValidationErrors(makeBaselineValidApiInstanceMetaDataWithVersions().withPath("myPath/"), List.of("does not start with '/'"));
assertValidationErrors(makeBaselineValidApiInstanceMetaDataWithVersions().withPath("/yourPath"), List.of("does not end with '/'"));
assertValidationErrors(makeBaselineValidApiInstanceMetaDataWithVersions().withPath("any/path"), List.of("does not start with '/'", "does not end with '/'"));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static ApiInstanceMetaData makeBaselineValidApiInstanceMetaData() private static ApiInstanceMetaData makeBaselineValidApiInstanceMetaData()
{ {
return (new ApiInstanceMetaData() return (new ApiInstanceMetaData()
.withName("QQQ API") .withName(TestUtils.API_NAME)
.withPath("/api/")
.withLabel("QQQ API")
.withDescription("Test API for QQQ") .withDescription("Test API for QQQ")
.withContactEmail("contact@kingsrook.com")); .withContactEmail("contact@kingsrook.com"));
} }
/*******************************************************************************
**
*******************************************************************************/
private static ApiInstanceMetaData makeBaselineValidApiInstanceMetaDataWithVersions()
{
return (makeBaselineValidApiInstanceMetaData()
.withCurrentVersion(new APIVersion("1"))
.withSupportedVersions(List.of(new APIVersion("1"))));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -172,10 +195,10 @@ class ApiInstanceMetaDataTest
*******************************************************************************/ *******************************************************************************/
private void assertValidationErrors(QInstance qInstance, ApiInstanceMetaData apiInstanceMetaData, List<String> expectedErrors) private void assertValidationErrors(QInstance qInstance, ApiInstanceMetaData apiInstanceMetaData, List<String> expectedErrors)
{ {
qInstance.withMiddlewareMetaData(apiInstanceMetaData); qInstance.withMiddlewareMetaData(new ApiInstanceMetaDataContainer().withApiInstanceMetaData(apiInstanceMetaData));
QInstanceValidator validator = new QInstanceValidator(); QInstanceValidator validator = new QInstanceValidator();
apiInstanceMetaData.validate(qInstance, validator); apiInstanceMetaData.validate(apiInstanceMetaData.getName(), qInstance, validator);
List<String> errors = validator.getErrors(); List<String> errors = validator.getErrors();
assertEquals(expectedErrors.size(), errors.size(), "Expected # of validation errors (got: " + errors + ")"); assertEquals(expectedErrors.size(), errors.size(), "Expected # of validation errors (got: " + errors + ")");