mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
implemented replacedBy on removed field; apiFieldName; exclude
This commit is contained in:
@ -22,14 +22,6 @@
|
|||||||
package com.kingsrook.qqq.api;
|
package com.kingsrook.qqq.api;
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
|
||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -37,30 +29,4 @@ public interface ApiMiddlewareType
|
|||||||
{
|
{
|
||||||
String NAME = "api";
|
String NAME = "api";
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
static ApiInstanceMetaData getApiInstanceMetaData(QInstance instance)
|
|
||||||
{
|
|
||||||
return ((ApiInstanceMetaData) instance.getMiddlewareMetaData(NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
static ApiTableMetaData getApiTableMetaData(QTableMetaData table)
|
|
||||||
{
|
|
||||||
return ((ApiTableMetaData) table.getMiddlewareMetaData(NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
static ApiFieldMetaData getApiFieldMetaData(QFieldMetaData field)
|
|
||||||
{
|
|
||||||
return ((ApiFieldMetaData) field.getMiddlewareMetaData(NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import java.util.ArrayList;
|
|||||||
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 com.kingsrook.qqq.api.ApiMiddlewareType;
|
|
||||||
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;
|
||||||
@ -82,7 +81,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
|
|
||||||
QInstance qInstance = QContext.getQInstance();
|
QInstance qInstance = QContext.getQInstance();
|
||||||
|
|
||||||
ApiInstanceMetaData apiInstanceMetaData = ApiMiddlewareType.getApiInstanceMetaData(qInstance);
|
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
|
||||||
|
|
||||||
OpenAPI openAPI = new OpenAPI()
|
OpenAPI openAPI = new OpenAPI()
|
||||||
.withVersion("3.0.3")
|
.withVersion("3.0.3")
|
||||||
@ -153,7 +152,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||||
String primaryKeyLabel = primaryKeyField.getLabel();
|
String primaryKeyLabel = primaryKeyField.getLabel();
|
||||||
|
|
||||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||||
|
|
||||||
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
|
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
|
||||||
if(StringUtils.hasContent(tableReadPermissionName))
|
if(StringUtils.hasContent(tableReadPermissionName))
|
||||||
|
@ -24,7 +24,6 @@ package com.kingsrook.qqq.api.actions;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
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.APIVersionRange;
|
import com.kingsrook.qqq.api.model.APIVersionRange;
|
||||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
||||||
@ -38,6 +37,7 @@ 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.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -70,7 +70,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
for(QFieldMetaData field : table.getFields().values())
|
for(QFieldMetaData field : table.getFields().values())
|
||||||
{
|
{
|
||||||
if(getApiVersionRange(field).includes(version))
|
if(!isExcluded(field) && getApiVersionRange(field).includes(version))
|
||||||
{
|
{
|
||||||
fields.add(field);
|
fields.add(field);
|
||||||
}
|
}
|
||||||
@ -79,14 +79,11 @@ 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 removedApiField : CollectionUtils.nonNullList(getRemovedApiFields(table)))
|
for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(table)))
|
||||||
{
|
{
|
||||||
APIVersionRange versionRange = getApiVersionRangeForRemovedField(removedApiField);
|
if(!isExcluded(field) && getApiVersionRangeForRemovedField(field).includes(version))
|
||||||
|
|
||||||
if(versionRange.includes(version))
|
|
||||||
{
|
{
|
||||||
// todo - dress this up w/ the old stuff
|
fields.add(field);
|
||||||
fields.add(removedApiField);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,12 +92,28 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private boolean isExcluded(QFieldMetaData field)
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
|
||||||
|
if(apiFieldMetaData != null && BooleanUtils.isTrue(apiFieldMetaData.getIsExcluded()))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private APIVersionRange getApiVersionRangeForRemovedField(QFieldMetaData field)
|
private APIVersionRange getApiVersionRangeForRemovedField(QFieldMetaData field)
|
||||||
{
|
{
|
||||||
ApiFieldMetaData middlewareMetaData = ApiMiddlewareType.getApiFieldMetaData(field);
|
ApiFieldMetaData middlewareMetaData = ApiFieldMetaData.of(field);
|
||||||
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
|
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
|
||||||
{
|
{
|
||||||
if(StringUtils.hasContent(middlewareMetaData.getFinalVersion()))
|
if(StringUtils.hasContent(middlewareMetaData.getFinalVersion()))
|
||||||
@ -125,7 +138,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private APIVersionRange getApiVersionRange(QFieldMetaData field)
|
private APIVersionRange getApiVersionRange(QFieldMetaData field)
|
||||||
{
|
{
|
||||||
ApiFieldMetaData middlewareMetaData = ApiMiddlewareType.getApiFieldMetaData(field);
|
ApiFieldMetaData middlewareMetaData = ApiFieldMetaData.of(field);
|
||||||
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
|
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
|
||||||
{
|
{
|
||||||
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion()));
|
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion()));
|
||||||
@ -141,7 +154,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private List<QFieldMetaData> getRemovedApiFields(QTableMetaData table)
|
private List<QFieldMetaData> getRemovedApiFields(QTableMetaData table)
|
||||||
{
|
{
|
||||||
ApiTableMetaData apiTableMetaData = ApiMiddlewareType.getApiTableMetaData(table);
|
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
|
||||||
if(apiTableMetaData != null)
|
if(apiTableMetaData != null)
|
||||||
{
|
{
|
||||||
return (apiTableMetaData.getRemovedApiFields());
|
return (apiTableMetaData.getRemovedApiFields());
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* 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.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import com.kingsrook.qqq.api.javalin.QBadRequestException;
|
||||||
|
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
||||||
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Methods for going back and forth from QRecords to API-versions of objects
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QRecordApiAdapter
|
||||||
|
{
|
||||||
|
private static Map<Pair<String, String>, List<QFieldMetaData>> fieldListCache = new HashMap<>();
|
||||||
|
private static Map<Pair<String, String>, Map<String, QFieldMetaData>> fieldMapCache = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Convert a QRecord to a map for the API
|
||||||
|
*******************************************************************************/
|
||||||
|
public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiVersion) throws QException
|
||||||
|
{
|
||||||
|
List<QFieldMetaData> tableApiFields = getTableApiFieldList(tableName, apiVersion);
|
||||||
|
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// iterate over the table's api fields //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
for(QFieldMetaData field : tableApiFields)
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
|
||||||
|
|
||||||
|
// todo - what about display values / possible values?
|
||||||
|
|
||||||
|
String apiFieldName = apiFieldMetaData.getApiFieldName();
|
||||||
|
if(!StringUtils.hasContent(apiFieldName))
|
||||||
|
{
|
||||||
|
apiFieldName = field.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
|
||||||
|
{
|
||||||
|
outputRecord.put(apiFieldName, record.getValue(apiFieldMetaData.getReplacedByFieldName()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputRecord.put(apiFieldName, record.getValue(field.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (outputRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QRecord apiJsonObjectToQRecord(JSONObject jsonObject, String tableName, String apiVersion) throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make map of apiFieldNames (e.g., names as api uses them) to QFieldMetaData //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Map<String, QFieldMetaData> apiFieldsMap = getTableApiFieldMap(tableName, apiVersion);
|
||||||
|
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||||
|
QRecord qRecord = new QRecord();
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// iterate over keys in the json object //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
for(String jsonKey : jsonObject.keySet())
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// if it's a valid api field name, process it //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
if(apiFieldsMap.containsKey(jsonKey))
|
||||||
|
{
|
||||||
|
QFieldMetaData field = apiFieldsMap.get(jsonKey);
|
||||||
|
Object value = jsonObject.get(jsonKey);
|
||||||
|
|
||||||
|
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
|
||||||
|
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
|
||||||
|
{
|
||||||
|
qRecord.setValue(apiFieldMetaData.getReplacedByFieldName(), value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qRecord.setValue(field.getName(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
// else add it to the list of unrecognized names //
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
unrecognizedFieldNames.add(jsonKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!unrecognizedFieldNames.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QBadRequestException("Request body contained " + unrecognizedFieldNames.size() + " unrecognized field name" + StringUtils.plural(unrecognizedFieldNames) + ": " + StringUtils.joinWithCommasAndAnd(unrecognizedFieldNames)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (qRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static Map<String, QFieldMetaData> getTableApiFieldMap(String tableName, String apiVersion) throws QException
|
||||||
|
{
|
||||||
|
Pair<String, String> key = new Pair<>(tableName, apiVersion);
|
||||||
|
if(!fieldMapCache.containsKey(key))
|
||||||
|
{
|
||||||
|
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f ->
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(f);
|
||||||
|
String apiFieldName = apiFieldMetaData.getApiFieldName();
|
||||||
|
if(!StringUtils.hasContent(apiFieldName))
|
||||||
|
{
|
||||||
|
apiFieldName = f.getName();
|
||||||
|
}
|
||||||
|
return (apiFieldName);
|
||||||
|
}, f -> f));
|
||||||
|
fieldMapCache.put(key, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (fieldMapCache.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static List<QFieldMetaData> getTableApiFieldList(String tableName, String apiVersion) throws QException
|
||||||
|
{
|
||||||
|
Pair<String, String> key = new Pair<>(tableName, apiVersion);
|
||||||
|
if(!fieldListCache.containsKey(key))
|
||||||
|
{
|
||||||
|
List<QFieldMetaData> value = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
||||||
|
fieldListCache.put(key, value);
|
||||||
|
}
|
||||||
|
return (fieldListCache.get(key));
|
||||||
|
}
|
||||||
|
}
|
@ -31,15 +31,13 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import com.kingsrook.qqq.api.ApiMiddlewareType;
|
|
||||||
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
|
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
|
||||||
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
|
import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
|
||||||
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.metadata.ApiInstanceMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
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;
|
||||||
@ -205,7 +203,7 @@ public class QJavalinApiHandler
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static APIVersionRange getApiVersionRange(QTableMetaData table)
|
private static APIVersionRange getApiVersionRange(QTableMetaData table)
|
||||||
{
|
{
|
||||||
ApiTableMetaData middlewareMetaData = ApiMiddlewareType.getApiTableMetaData(table);
|
ApiTableMetaData middlewareMetaData = ApiTableMetaData.of(table);
|
||||||
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
|
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
|
||||||
{
|
{
|
||||||
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion()));
|
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion()));
|
||||||
@ -259,7 +257,7 @@ public class QJavalinApiHandler
|
|||||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkedHashMap<String, Serializable> outputRecord = toApiRecord(record, tableName, version);
|
Map<String, Serializable> outputRecord = QRecordApiAdapter.qRecordToApiMap(record, tableName, version);
|
||||||
|
|
||||||
QJavalinAccessLogger.logEndSuccess();
|
QJavalinAccessLogger.logEndSuccess();
|
||||||
context.result(JsonUtils.toJson(outputRecord));
|
context.result(JsonUtils.toJson(outputRecord));
|
||||||
@ -477,7 +475,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(toApiRecord(record, tableName, version));
|
records.add(QRecordApiAdapter.qRecordToApiMap(record, tableName, version));
|
||||||
}
|
}
|
||||||
output.put("records", records);
|
output.put("records", records);
|
||||||
|
|
||||||
@ -516,7 +514,7 @@ public class QJavalinApiHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
APIVersion requestApiVersion = new APIVersion(version);
|
APIVersion requestApiVersion = new APIVersion(version);
|
||||||
List<APIVersion> supportedVersions = ApiMiddlewareType.getApiInstanceMetaData(qInstance).getSupportedVersions();
|
List<APIVersion> supportedVersions = ApiInstanceMetaData.of(qInstance).getSupportedVersions();
|
||||||
if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion))
|
if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion))
|
||||||
{
|
{
|
||||||
throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path()));
|
throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path()));
|
||||||
@ -689,7 +687,7 @@ public class QJavalinApiHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject(context.body());
|
JSONObject jsonObject = new JSONObject(context.body());
|
||||||
insertInput.setRecords(List.of(toQRecord(jsonObject, tableName, version)));
|
insertInput.setRecords(List.of(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version)));
|
||||||
}
|
}
|
||||||
catch(QBadRequestException qbre)
|
catch(QBadRequestException qbre)
|
||||||
{
|
{
|
||||||
@ -757,7 +755,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(toQRecord(jsonObject, tableName, version));
|
recordList.add(QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(recordList.isEmpty())
|
if(recordList.isEmpty())
|
||||||
@ -862,7 +860,7 @@ public class QJavalinApiHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject(context.body());
|
JSONObject jsonObject = new JSONObject(context.body());
|
||||||
QRecord qRecord = toQRecord(jsonObject, tableName, version);
|
QRecord qRecord = QRecordApiAdapter.apiJsonObjectToQRecord(jsonObject, tableName, version);
|
||||||
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
|
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
|
||||||
updateInput.setRecords(List.of(qRecord));
|
updateInput.setRecords(List.of(qRecord));
|
||||||
}
|
}
|
||||||
@ -956,59 +954,6 @@ public class QJavalinApiHandler
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private static LinkedHashMap<String, Serializable> toApiRecord(QRecord record, String tableName, String apiVersion) throws QException
|
|
||||||
{
|
|
||||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
|
||||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
|
||||||
for(QFieldMetaData tableApiField : tableApiFields)
|
|
||||||
{
|
|
||||||
// todo - what about display values / possible values
|
|
||||||
// todo - handle removed-from-this-version fields!!
|
|
||||||
outputRecord.put(tableApiField.getName(), record.getValue(tableApiField.getName()));
|
|
||||||
}
|
|
||||||
return (outputRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private static QRecord toQRecord(JSONObject jsonObject, String tableName, String apiVersion) throws QException
|
|
||||||
{
|
|
||||||
List<String> unrecognizedFieldNames = new ArrayList<>();
|
|
||||||
|
|
||||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
|
||||||
Map<String, ? extends QFieldMetaData> apiFieldsMap = tableApiFields.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
|
|
||||||
|
|
||||||
QRecord qRecord = new QRecord();
|
|
||||||
|
|
||||||
for(String jsonKey : jsonObject.keySet())
|
|
||||||
{
|
|
||||||
if(apiFieldsMap.containsKey(jsonKey))
|
|
||||||
{
|
|
||||||
QFieldMetaData field = apiFieldsMap.get(jsonKey);
|
|
||||||
qRecord.setValue(field.getName(), jsonObject.get(jsonKey));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unrecognizedFieldNames.add(jsonKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!unrecognizedFieldNames.isEmpty())
|
|
||||||
{
|
|
||||||
throw (new QBadRequestException("Request body contained " + unrecognizedFieldNames.size() + " unrecognized field name" + StringUtils.plural(unrecognizedFieldNames) + ": " + StringUtils.joinWithCommasAndAnd(unrecognizedFieldNames)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (qRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -32,14 +32,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class GetTableApiFieldsOutput extends AbstractActionOutput
|
public class GetTableApiFieldsOutput extends AbstractActionOutput
|
||||||
{
|
{
|
||||||
private List<? extends QFieldMetaData> fields;
|
private List<QFieldMetaData> fields;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for fields
|
** Getter for fields
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public List<? extends QFieldMetaData> getFields()
|
public List<QFieldMetaData> getFields()
|
||||||
{
|
{
|
||||||
return (this.fields);
|
return (this.fields);
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ public class GetTableApiFieldsOutput extends AbstractActionOutput
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for fields
|
** Setter for fields
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setFields(List<? extends QFieldMetaData> fields)
|
public void setFields(List<QFieldMetaData> fields)
|
||||||
{
|
{
|
||||||
this.fields = fields;
|
this.fields = fields;
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ public class GetTableApiFieldsOutput extends AbstractActionOutput
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for fields
|
** Fluent setter for fields
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public GetTableApiFieldsOutput withFields(List<? extends QFieldMetaData> fields)
|
public GetTableApiFieldsOutput withFields(List<QFieldMetaData> fields)
|
||||||
{
|
{
|
||||||
this.fields = fields;
|
this.fields = fields;
|
||||||
return (this);
|
return (this);
|
||||||
|
@ -63,6 +63,16 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static ApiInstanceMetaData of(QInstance qInstance)
|
||||||
|
{
|
||||||
|
return ((ApiInstanceMetaData) qInstance.getMiddlewareMetaData(ApiMiddlewareType.NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -103,7 +113,7 @@ public class ApiInstanceMetaData extends QMiddlewareInstanceMetaData
|
|||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
for(QTableMetaData table : qInstance.getTables().values())
|
for(QTableMetaData table : qInstance.getTables().values())
|
||||||
{
|
{
|
||||||
ApiTableMetaData apiTableMetaData = ApiMiddlewareType.getApiTableMetaData(table);
|
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
|
||||||
if(apiTableMetaData != null)
|
if(apiTableMetaData != null)
|
||||||
{
|
{
|
||||||
validator.assertCondition(allVersions.contains(new APIVersion(apiTableMetaData.getInitialVersion())), "Table " + table.getName() + "'s initial API version is not a recognized version.");
|
validator.assertCondition(allVersions.contains(new APIVersion(apiTableMetaData.getInitialVersion())), "Table " + table.getName() + "'s initial API version is not a recognized version.");
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
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.QMiddlewareFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +35,11 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
|
|||||||
private String initialVersion;
|
private String initialVersion;
|
||||||
private String finalVersion;
|
private String finalVersion;
|
||||||
|
|
||||||
|
private String apiFieldName;
|
||||||
|
|
||||||
|
private Boolean isExcluded;
|
||||||
|
private String replacedByFieldName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -46,6 +53,16 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static ApiFieldMetaData of(QFieldMetaData field)
|
||||||
|
{
|
||||||
|
return ((ApiFieldMetaData) field.getMiddlewareMetaData(ApiMiddlewareType.NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for initialVersion
|
** Getter for initialVersion
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -106,4 +123,97 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for replacedByFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getReplacedByFieldName()
|
||||||
|
{
|
||||||
|
return (this.replacedByFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for replacedByFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setReplacedByFieldName(String replacedByFieldName)
|
||||||
|
{
|
||||||
|
this.replacedByFieldName = replacedByFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for replacedByFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public ApiFieldMetaData withReplacedByFieldName(String replacedByFieldName)
|
||||||
|
{
|
||||||
|
this.replacedByFieldName = replacedByFieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for isExcluded
|
||||||
|
*******************************************************************************/
|
||||||
|
public Boolean getIsExcluded()
|
||||||
|
{
|
||||||
|
return (this.isExcluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for isExcluded
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setIsExcluded(Boolean isExcluded)
|
||||||
|
{
|
||||||
|
this.isExcluded = isExcluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for isExcluded
|
||||||
|
*******************************************************************************/
|
||||||
|
public ApiFieldMetaData withIsExcluded(Boolean isExcluded)
|
||||||
|
{
|
||||||
|
this.isExcluded = isExcluded;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiFieldName()
|
||||||
|
{
|
||||||
|
return (this.apiFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiFieldName(String apiFieldName)
|
||||||
|
{
|
||||||
|
this.apiFieldName = apiFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiFieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public ApiFieldMetaData withApiFieldName(String apiFieldName)
|
||||||
|
{
|
||||||
|
this.apiFieldName = apiFieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
|||||||
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.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;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -43,6 +44,16 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static ApiTableMetaData of(QTableMetaData table)
|
||||||
|
{
|
||||||
|
return ((ApiTableMetaData) table.getMiddlewareMetaData(ApiMiddlewareType.NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -55,12 +66,16 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
|
|||||||
{
|
{
|
||||||
for(QFieldMetaData field : table.getFields().values())
|
for(QFieldMetaData field : table.getFields().values())
|
||||||
{
|
{
|
||||||
if(field.getMiddlewareMetaData(ApiMiddlewareType.NAME) == null)
|
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(field);
|
||||||
|
if(apiFieldMetaData.getInitialVersion() == null)
|
||||||
{
|
{
|
||||||
field.withMiddlewareMetaData(new ApiFieldMetaData());
|
apiFieldMetaData.setInitialVersion(initialVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiFieldMetaData apiFieldMetaData = (ApiFieldMetaData) field.getMiddlewareMetaData(ApiMiddlewareType.NAME);
|
for(QFieldMetaData field : CollectionUtils.nonNullList(removedApiFields))
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = ensureFieldHasApiMiddlewareMetaData(field);
|
||||||
if(apiFieldMetaData.getInitialVersion() == null)
|
if(apiFieldMetaData.getInitialVersion() == null)
|
||||||
{
|
{
|
||||||
apiFieldMetaData.setInitialVersion(initialVersion);
|
apiFieldMetaData.setInitialVersion(initialVersion);
|
||||||
@ -71,6 +86,21 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static ApiFieldMetaData ensureFieldHasApiMiddlewareMetaData(QFieldMetaData field)
|
||||||
|
{
|
||||||
|
if(field.getMiddlewareMetaData(ApiMiddlewareType.NAME) == null)
|
||||||
|
{
|
||||||
|
field.withMiddlewareMetaData(new ApiFieldMetaData());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ApiFieldMetaData.of(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor
|
** Constructor
|
||||||
**
|
**
|
||||||
|
@ -25,6 +25,7 @@ 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.fields.ApiFieldMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
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;
|
||||||
@ -46,7 +47,11 @@ public class TestUtils
|
|||||||
public static final String MEMORY_BACKEND_NAME = "memory";
|
public static final String MEMORY_BACKEND_NAME = "memory";
|
||||||
public static final String TABLE_NAME_PERSON = "person";
|
public static final String TABLE_NAME_PERSON = "person";
|
||||||
|
|
||||||
public static final String API_VERSION = "2023.Q1";
|
public static final String V2023_Q1 = "2023.Q1";
|
||||||
|
public static final String V2022_Q4 = "2022.Q4";
|
||||||
|
public static final String V2023_Q2 = "2023.Q2";
|
||||||
|
|
||||||
|
public static final String CURRENT_API_VERSION = V2023_Q1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -65,8 +70,10 @@ public class TestUtils
|
|||||||
.withName("TestAPI")
|
.withName("TestAPI")
|
||||||
.withDescription("QQQ Test API")
|
.withDescription("QQQ Test API")
|
||||||
.withContactEmail("contact@kingsrook.com")
|
.withContactEmail("contact@kingsrook.com")
|
||||||
.withCurrentVersion(new APIVersion(API_VERSION))
|
.withCurrentVersion(new APIVersion(CURRENT_API_VERSION))
|
||||||
.withSupportedVersions(List.of(new APIVersion(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);
|
||||||
@ -95,7 +102,16 @@ public class TestUtils
|
|||||||
.withName(TABLE_NAME_PERSON)
|
.withName(TABLE_NAME_PERSON)
|
||||||
.withLabel("Person")
|
.withLabel("Person")
|
||||||
.withBackendName(MEMORY_BACKEND_NAME)
|
.withBackendName(MEMORY_BACKEND_NAME)
|
||||||
.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(API_VERSION))
|
.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))
|
||||||
@ -103,14 +119,23 @@ 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)
|
||||||
.withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
.withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion(V2023_Q1)))
|
||||||
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// 2 new fields - they'll appear in future versions of the API //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
.withField(new QFieldMetaData("cost", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY)
|
||||||
|
.withMiddlewareMetaData(new ApiFieldMetaData().withInitialVersion(V2023_Q2)))
|
||||||
|
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY)
|
||||||
|
.withMiddlewareMetaData(new ApiFieldMetaData().withIsExcluded(true)))
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class GenerateOpenApiSpecActionTest extends BaseTest
|
|||||||
@Test
|
@Test
|
||||||
void test() throws QException
|
void test() throws QException
|
||||||
{
|
{
|
||||||
String version = TestUtils.API_VERSION;
|
String version = TestUtils.V2023_Q1;
|
||||||
|
|
||||||
QInstance qInstance = QContext.getQInstance();
|
QInstance qInstance = QContext.getQInstance();
|
||||||
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData()
|
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData()
|
||||||
|
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* 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.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.Month;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.api.BaseTest;
|
||||||
|
import com.kingsrook.qqq.api.TestUtils;
|
||||||
|
import com.kingsrook.qqq.api.javalin.QBadRequestException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for QRecordApiAdapter
|
||||||
|
*******************************************************************************/
|
||||||
|
class QRecordApiAdapterTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQRecordToApiMap() throws QException
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// QRecord has values corresponding to what's defined in the QInstance (and the underlying backend system) //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QRecord person = new QRecord()
|
||||||
|
.withValue("firstName", "Tim")
|
||||||
|
.withValue("noOfShoes", 2)
|
||||||
|
.withValue("birthDate", LocalDate.of(1980, Month.MAY, 31))
|
||||||
|
.withValue("cost", new BigDecimal("3.50"))
|
||||||
|
.withValue("price", new BigDecimal("9.99"));
|
||||||
|
|
||||||
|
Map<String, Serializable> pastApiRecord = QRecordApiAdapter.qRecordToApiMap(person, TestUtils.TABLE_NAME_PERSON, TestUtils.V2022_Q4);
|
||||||
|
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("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);
|
||||||
|
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
|
||||||
|
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);
|
||||||
|
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(new BigDecimal("3.50"), futureApiRecord.get("cost")); // future field name appears now that we've requested this future api version.
|
||||||
|
|
||||||
|
for(Map<String, Serializable> apiRecord : List.of(pastApiRecord, currentApiRecord, futureApiRecord))
|
||||||
|
{
|
||||||
|
assertEquals(LocalDate.parse("1980-05-31"), apiRecord.get("birthDay")); // use the apiFieldName
|
||||||
|
assertFalse(apiRecord.containsKey("price")); // excluded field never appears
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testApiJsonObjectToQRecord() throws QException
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// past version took shoeCount - so we still take that, but now put it in noOfShoes field of qRecord //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QRecord recordFromOldApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "shoeCount": 2}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2022_Q4);
|
||||||
|
assertEquals(2, recordFromOldApi.getValueInteger("noOfShoes"));
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// current version takes it as noOfShoes //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
QRecord recordFromCurrentApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "noOfShoes": 2}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1);
|
||||||
|
assertEquals(2, recordFromCurrentApi.getValueInteger("noOfShoes"));
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// future version supports cost field too! //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
QRecord recordFromFutureApi = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "noOfShoes": 2, "cost": 3.50}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q2);
|
||||||
|
assertEquals(2, recordFromFutureApi.getValueInteger("noOfShoes"));
|
||||||
|
assertEquals(new BigDecimal("3.50"), recordFromFutureApi.getValueBigDecimal("cost"));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
// make sure apiFieldName is used (instead of table's fieldName) //
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
QRecord recordWithApiFieldName = QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "birthDay": "1976-05-28"}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q2);
|
||||||
|
assertEquals("1976-05-28", recordWithApiFieldName.getValueString("birthDate"));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// past version didn't have noOfShoes field (they called it shoeCount) -- fail if it was sent //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "noOfShoes": 2}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2022_Q4))
|
||||||
|
.isInstanceOf(QBadRequestException.class)
|
||||||
|
.hasMessageContaining("unrecognized field name: noOfShoes");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// current version doesn't have cost field - fail if you send it to us //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "cost": 2}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, TestUtils.V2023_Q1))
|
||||||
|
.isInstanceOf(QBadRequestException.class)
|
||||||
|
.hasMessageContaining("unrecognized field name: cost");
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
// excluded field always fails //
|
||||||
|
/////////////////////////////////
|
||||||
|
for(String version : List.of(TestUtils.V2022_Q4, TestUtils.V2023_Q1, TestUtils.V2023_Q2))
|
||||||
|
{
|
||||||
|
assertThatThrownBy(() -> QRecordApiAdapter.apiJsonObjectToQRecord(new JSONObject("""
|
||||||
|
{"firstName": "Tim", "price": 2}
|
||||||
|
"""), TestUtils.TABLE_NAME_PERSON, version))
|
||||||
|
.isInstanceOf(QBadRequestException.class)
|
||||||
|
.hasMessageContaining("unrecognized field name: price");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user