mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Initial build of api aware middleware
This commit is contained in:
@ -338,7 +338,7 @@ public class ApiImplementation
|
||||
else if(apiFieldMetaData.getCustomValueMapper() != null)
|
||||
{
|
||||
ApiFieldCustomValueMapper customValueMapper = QCodeLoader.getAdHoc(ApiFieldCustomValueMapper.class, apiFieldMetaData.getCustomValueMapper());
|
||||
customValueMapper.customizeFilterCriteria(queryInput, filter, criteria, name, apiFieldMetaData);
|
||||
customValueMapper.customizeFilterCriteriaForQueryOrCount(queryInput, filter, criteria, name, apiFieldMetaData);
|
||||
}
|
||||
|
||||
filter.addCriteria(criteria);
|
||||
@ -389,8 +389,14 @@ public class ApiImplementation
|
||||
/////////////////////////////
|
||||
if(includeCount)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - at one time we wondered if we might need a call to customValueMapper.customizeFilterCriteriaForQueryOrCount //
|
||||
// as the filter would have already gone through there, but not other attributes of the input, e.g, joins... //
|
||||
// but, instead we're trying to just put the query joins in here FROM the query input... //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(tableName);
|
||||
countInput.setQueryJoins(queryInput.getQueryJoins());
|
||||
countInput.setFilter(filter);
|
||||
countInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
|
@ -31,6 +31,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
import com.kingsrook.qqq.api.model.APIVersionRange;
|
||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput;
|
||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||
@ -39,10 +40,13 @@ import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -51,6 +55,8 @@ import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
*******************************************************************************/
|
||||
public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApiFieldsInput, GetTableApiFieldsOutput>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GetTableApiFieldsAction.class);
|
||||
|
||||
private static Map<ApiNameVersionAndTableName, List<QFieldMetaData>> fieldListCache = new HashMap<>();
|
||||
private static Map<ApiNameVersionAndTableName, Map<String, QFieldMetaData>> fieldMapCache = new HashMap<>();
|
||||
|
||||
@ -141,13 +147,16 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
||||
QTableMetaData table = QContext.getQInstance().getTable(input.getTableName());
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Unrecognized table name: " + input.getTableName()));
|
||||
throw (new QNotFoundException("Unrecognized table name: " + input.getTableName()));
|
||||
}
|
||||
|
||||
// todo - verify the table is in this version?
|
||||
|
||||
APIVersion version = new APIVersion(input.getVersion());
|
||||
// todo - validate the version?
|
||||
|
||||
APIVersionRange tableApiVersionRange = getApiVersionRange(input.getApiName(), table);
|
||||
if(!tableApiVersionRange.includes(version))
|
||||
{
|
||||
throw (new QNotFoundException("Table [" + input.getTableName() + "] was not found in this version of this api."));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// get fields on the table which are in this version //
|
||||
@ -180,6 +189,42 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private APIVersionRange getApiVersionRange(String apiName, QTableMetaData table) throws QNotFoundException
|
||||
{
|
||||
ApiTableMetaDataContainer apiTableMetaDataContainer = ApiTableMetaDataContainer.of(table);
|
||||
if(apiTableMetaDataContainer == null)
|
||||
{
|
||||
LOG.debug("Returning not found because table doesn't have an apiTableMetaDataContainer", logPair("tableName", table.getName()));
|
||||
throw (new QNotFoundException("Table [" + table.getName() + "] was not found in this api."));
|
||||
}
|
||||
|
||||
ApiTableMetaData apiTableMetaData = apiTableMetaDataContainer.getApis().get(apiName);
|
||||
if(apiTableMetaData == null)
|
||||
{
|
||||
LOG.debug("Returning not found because api isn't present in table's apiTableMetaDataContainer", logPair("apiName", apiName), logPair("tableName", table.getName()));
|
||||
throw (new QNotFoundException("Table [" + table.getName() + "] was not found in this api."));
|
||||
}
|
||||
|
||||
if(apiTableMetaData.getInitialVersion() != null)
|
||||
{
|
||||
if(apiTableMetaData.getFinalVersion() != null)
|
||||
{
|
||||
return (APIVersionRange.betweenAndIncluding(apiTableMetaData.getInitialVersion(), apiTableMetaData.getFinalVersion()));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (APIVersionRange.afterAndIncluding(apiTableMetaData.getInitialVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
return (APIVersionRange.none());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -29,6 +29,9 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.api.actions.output.ApiOutputMapWrapper;
|
||||
import com.kingsrook.qqq.api.actions.output.ApiOutputQRecordWrapper;
|
||||
import com.kingsrook.qqq.api.actions.output.ApiOutputRecordWrapperInterface;
|
||||
import com.kingsrook.qqq.api.javalin.QBadRequestException;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
import com.kingsrook.qqq.api.model.APIVersionRange;
|
||||
@ -84,6 +87,46 @@ public class QRecordApiAdapter
|
||||
** ApiFieldCustomValueMapperBulkSupportInterface's in the bulky way.
|
||||
*******************************************************************************/
|
||||
public static ArrayList<Map<String, Serializable>> qRecordsToApiMapList(List<QRecord> records, String tableName, String apiName, String apiVersion) throws QException
|
||||
{
|
||||
Map<String, ApiFieldCustomValueMapper> fieldValueMappers = getFieldValueMappers(records, tableName, apiName, apiVersion);
|
||||
|
||||
ArrayList<Map<String, Serializable>> rs = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
ApiOutputMapWrapper apiOutputMap = qRecordToApiMap(record, tableName, apiName, apiVersion, fieldValueMappers, new ApiOutputMapWrapper(new LinkedHashMap<>()));
|
||||
rs.add(apiOutputMap.getContents());
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** version of the qRecordToApiMap that returns QRecords, not maps.
|
||||
** useful for cases where we're staying inside QQQ, but working with an api-
|
||||
** versioned application.
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> qRecordsToApiVersionedQRecordList(List<QRecord> records, String tableName, String apiName, String apiVersion) throws QException
|
||||
{
|
||||
Map<String, ApiFieldCustomValueMapper> fieldValueMappers = getFieldValueMappers(records, tableName, apiName, apiVersion);
|
||||
|
||||
List<QRecord> rs = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
ApiOutputQRecordWrapper apiOutputQRecord = qRecordToApiMap(record, tableName, apiName, apiVersion, fieldValueMappers, new ApiOutputQRecordWrapper(new QRecord().withTableName(tableName)));
|
||||
rs.add(apiOutputQRecord.getContents());
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static Map<String, ApiFieldCustomValueMapper> getFieldValueMappers(List<QRecord> records, String tableName, String apiName, String apiVersion) throws QException
|
||||
{
|
||||
Map<String, ApiFieldCustomValueMapper> fieldValueMappers = new HashMap<>();
|
||||
|
||||
@ -91,8 +134,6 @@ public class QRecordApiAdapter
|
||||
for(QFieldMetaData field : tableApiFields)
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
|
||||
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(apiName, field);
|
||||
|
||||
if(apiFieldMetaData.getCustomValueMapper() != null)
|
||||
{
|
||||
if(!fieldValueMappers.containsKey(apiFieldMetaData.getCustomValueMapper().getName()))
|
||||
@ -107,31 +148,24 @@ public class QRecordApiAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Map<String, Serializable>> rs = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
rs.add(QRecordApiAdapter.qRecordToApiMap(record, tableName, apiName, apiVersion, fieldValueMappers));
|
||||
}
|
||||
|
||||
return (rs);
|
||||
return fieldValueMappers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** private version of convert a QRecord to a map for the API - takes params to
|
||||
** private version of convert a QRecord to a map for the API (or, another
|
||||
** QRecord - whatever object is in the `O output` param). Takes params to
|
||||
** support working in bulk w/ customizers much better.
|
||||
*******************************************************************************/
|
||||
private static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiName, String apiVersion, Map<String, ApiFieldCustomValueMapper> fieldValueMappers) throws QException
|
||||
private static <C, O extends ApiOutputRecordWrapperInterface<C, O>> O qRecordToApiMap(QRecord record, String tableName, String apiName, String apiVersion, Map<String, ApiFieldCustomValueMapper> fieldValueMappers, O output) throws QException
|
||||
{
|
||||
if(record == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
List<QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldList(new GetTableApiFieldsAction.ApiNameVersionAndTableName(apiName, apiVersion, tableName));
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
List<QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldList(new GetTableApiFieldsAction.ApiNameVersionAndTableName(apiName, apiVersion, tableName));
|
||||
|
||||
/////////////////////////////////////////
|
||||
// iterate over the table's api fields //
|
||||
@ -172,7 +206,7 @@ public class QRecordApiAdapter
|
||||
value = Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
outputRecord.put(apiFieldName, value);
|
||||
output.putValue(apiFieldName, value);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -187,16 +221,18 @@ public class QRecordApiAdapter
|
||||
continue;
|
||||
}
|
||||
|
||||
ArrayList<Map<String, Serializable>> associationList = new ArrayList<>();
|
||||
outputRecord.put(association.getName(), associationList);
|
||||
ArrayList<O> associationList = new ArrayList<>();
|
||||
|
||||
for(QRecord associatedRecord : CollectionUtils.nonNullList(CollectionUtils.nonNullMap(record.getAssociatedRecords()).get(association.getName())))
|
||||
{
|
||||
associationList.add(qRecordToApiMap(associatedRecord, association.getAssociatedTableName(), apiName, apiVersion));
|
||||
ApiOutputRecordWrapperInterface<C, O> apiOutputAssociation = output.newSibling(associatedRecord.getTableName());
|
||||
associationList.add(qRecordToApiMap(associatedRecord, association.getAssociatedTableName(), apiName, apiVersion, fieldValueMappers, apiOutputAssociation.unwrap()));
|
||||
}
|
||||
|
||||
output.putAssociation(association.getName(), associationList);
|
||||
}
|
||||
|
||||
return (outputRecord);
|
||||
return (output);
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.output;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** implementation of ApiOutputRecordWrapperInterface that wraps a Map
|
||||
***************************************************************************/
|
||||
public class ApiOutputMapWrapper implements ApiOutputRecordWrapperInterface<Map<String, Serializable>, ApiOutputMapWrapper>
|
||||
{
|
||||
private Map<String, Serializable> apiMap;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ApiOutputMapWrapper(Map<String, Serializable> apiMap)
|
||||
{
|
||||
this.apiMap = apiMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void putValue(String key, Serializable value)
|
||||
{
|
||||
apiMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void putAssociation(String key, List<ApiOutputMapWrapper> values)
|
||||
{
|
||||
ArrayList<Map<String, Serializable>> associatedMaps = new ArrayList<>(values.stream().map(oqr -> oqr.apiMap).toList());
|
||||
apiMap.put(key, associatedMaps);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public ApiOutputMapWrapper newSibling(String tableName)
|
||||
{
|
||||
return new ApiOutputMapWrapper(new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Map<String, Serializable> getContents()
|
||||
{
|
||||
return this.apiMap;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.output;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** implementation of ApiOutputRecordWrapperInterface that wraps a QRecord
|
||||
***************************************************************************/
|
||||
public class ApiOutputQRecordWrapper implements ApiOutputRecordWrapperInterface<QRecord, ApiOutputQRecordWrapper>
|
||||
{
|
||||
private QRecord record;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ApiOutputQRecordWrapper(QRecord record)
|
||||
{
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void putValue(String key, Serializable value)
|
||||
{
|
||||
record.setValue(key, value);
|
||||
record.setDisplayValue(key, ValueUtils.getValueAsString(value)); // todo is this useful?
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void putAssociation(String key, List<ApiOutputQRecordWrapper> values)
|
||||
{
|
||||
List<QRecord> records = values.stream().map(oqr -> oqr.record).toList();
|
||||
record.withAssociatedRecords(key, records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ApiOutputQRecordWrapper newSibling(String tableName)
|
||||
{
|
||||
return (new ApiOutputQRecordWrapper(new QRecord().withTableName(tableName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QRecord getContents()
|
||||
{
|
||||
return this.record;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.output;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** interface to define wrappers for either a Map of values (e.g., the
|
||||
** original/native return type for the API), or a QRecord. Built for use
|
||||
** by QRecordApiAdapter - not clear ever useful outside of there.
|
||||
**
|
||||
** Type params are:
|
||||
** C: the wrapped Contents
|
||||
** A: the child-type... e.g:
|
||||
** class Child implements ApiOutputRecordInterface(Something, Child)
|
||||
***************************************************************************/
|
||||
public interface ApiOutputRecordWrapperInterface<C, A extends ApiOutputRecordWrapperInterface<C, A>>
|
||||
{
|
||||
/***************************************************************************
|
||||
** put a value in the wrapped object
|
||||
***************************************************************************/
|
||||
void putValue(String key, Serializable value);
|
||||
|
||||
/***************************************************************************
|
||||
** put associated wrapper-objects in the wrapped object
|
||||
***************************************************************************/
|
||||
void putAssociation(String key, List<A> values);
|
||||
|
||||
/***************************************************************************
|
||||
** create a new "sibling" object to this - e.g., a wrapper around a new
|
||||
** instance of the contents object
|
||||
***************************************************************************/
|
||||
ApiOutputRecordWrapperInterface<C, A> newSibling(String tableName);
|
||||
|
||||
/***************************************************************************
|
||||
** get the wrapped contents object
|
||||
***************************************************************************/
|
||||
C getContents();
|
||||
|
||||
/***************************************************************************
|
||||
** return this, but as the `A` type...
|
||||
***************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
default A unwrap()
|
||||
{
|
||||
return (A) this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.executors;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface ApiAwareExecutorInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getApiName()
|
||||
{
|
||||
return (QContext.getQSession().getValue("apiName"));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getApiVersion()
|
||||
{
|
||||
return (QContext.getQSession().getValue("apiVersion"));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default ApiInstanceMetaData getApiInstanceMetaData()
|
||||
{
|
||||
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(QContext.getQInstance());
|
||||
ApiInstanceMetaData apiInstanceMetaData = apiInstanceMetaDataContainer.getApis().get(getApiName());
|
||||
return apiInstanceMetaData;
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.executors;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.api.actions.ApiImplementation;
|
||||
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiOperation;
|
||||
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.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
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.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.TableCountExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.io.TableCountInput;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.io.TableCountOutputInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareTableCountExecutor extends TableCountExecutor implements ApiAwareExecutorInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void execute(TableCountInput input, TableCountOutputInterface output) throws QException
|
||||
{
|
||||
List<String> badRequestMessages = new ArrayList<>();
|
||||
|
||||
// todo - new operation? move all this to the api impl class??
|
||||
// todo table api name vs. internal name??
|
||||
String apiName = getApiName();
|
||||
String apiVersion = getApiVersion();
|
||||
QTableMetaData table = ApiImplementation.validateTableAndVersion(getApiInstanceMetaData(), apiVersion, input.getTableName(), ApiOperation.QUERY_BY_QUERY_STRING);
|
||||
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(input.getTableName());
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(countInput, TablePermissionSubType.READ);
|
||||
Map<String, QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsAction.ApiNameVersionAndTableName(apiName, apiVersion, table.getName()));
|
||||
|
||||
countInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS); // todo param
|
||||
countInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
countInput.setIncludeDistinctCount(input.getIncludeDistinct());
|
||||
|
||||
countInput.setQueryJoins(input.getJoins()); // todo - what are version implications here??
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// take care of managing criteria, which may not be in this version, etc //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter filter = Objects.requireNonNullElseGet(input.getFilter(), () -> new QQueryFilter());
|
||||
QueryExecutorUtils.manageCriteriaFields(filter, tableApiFields, badRequestMessages, apiName, countInput);
|
||||
|
||||
//////////////////////////////////////////
|
||||
// no more badRequest checks below here //
|
||||
//////////////////////////////////////////
|
||||
QueryExecutorUtils.throwIfBadRequestMessages(badRequestMessages);
|
||||
|
||||
//
|
||||
CountAction countAction = new CountAction();
|
||||
countInput.setFilter(filter);
|
||||
CountOutput countOutput = countAction.execute(countInput);
|
||||
|
||||
// todo - removed field handling...
|
||||
|
||||
// todo display values...
|
||||
|
||||
output.setCount(ValueUtils.getValueAsLong(countOutput.getCount()));
|
||||
output.setDistinctCount(ValueUtils.getValueAsLong(countOutput.getDistinctCount()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.executors;
|
||||
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
|
||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.TableMetaDataExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.io.TableMetaDataInput;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.io.TableMetaDataOutputInterface;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareTableMetaDataExecutor extends TableMetaDataExecutor implements ApiAwareExecutorInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ApiAwareTableMetaDataExecutor.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void execute(TableMetaDataInput input, TableMetaDataOutputInterface output) throws QException
|
||||
{
|
||||
QTableMetaData table = getQTableMetaData(input);
|
||||
|
||||
Map<String, QFieldMetaData> fieldMap = getFieldsForApiVersion(input.getTableName());
|
||||
|
||||
com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput tableMetaDataInput = new com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput();
|
||||
tableMetaDataInput.setTableName(input.getTableName());
|
||||
PermissionsHelper.checkTablePermissionThrowing(tableMetaDataInput, TablePermissionSubType.READ);
|
||||
|
||||
QBackendMetaData backendForTable = QContext.getQInstance().getBackendForTable(table.getName());
|
||||
TableMetaDataOutput tableMetaDataOutput = new TableMetaDataOutput();
|
||||
tableMetaDataOutput.setTable(new QFrontendTableMetaData(tableMetaDataInput, backendForTable, table, true, true, fieldMap));
|
||||
|
||||
adjustExposedJoinsForApi(tableMetaDataOutput);
|
||||
|
||||
output.setTableMetaData(tableMetaDataOutput.getTable());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void adjustExposedJoinsForApi(TableMetaDataOutput tableMetaDataOutput) throws QException
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(tableMetaDataOutput.getTable().getExposedJoins()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<QFrontendExposedJoin> iterator = tableMetaDataOutput.getTable().getExposedJoins().iterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
QFrontendExposedJoin frontendExposedJoin = iterator.next();
|
||||
String tableName = frontendExposedJoin.getJoinTable().getName();
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(tableName);
|
||||
QBackendMetaData backendForTable = QContext.getQInstance().getBackendForTable(tableName);
|
||||
Map<String, QFieldMetaData> joinFieldMap = getFieldsForApiVersion(tableName);
|
||||
|
||||
com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput tableMetaDataInput = new com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput();
|
||||
frontendExposedJoin.setJoinTable(new QFrontendTableMetaData(tableMetaDataInput, backendForTable, joinTable, true, true, joinFieldMap));
|
||||
}
|
||||
catch(QNotFoundException e)
|
||||
{
|
||||
LOG.debug("Removing exposed-join table that isn't in api version", logPair("mainTable", tableMetaDataOutput.getTable().getName()), logPair("joinTable", tableName), logPair("apiName", getApiName()), logPair("apiVersion", getApiVersion()));
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private QTableMetaData getQTableMetaData(TableMetaDataInput input) throws QNotFoundException
|
||||
{
|
||||
String tableName = input.getTableName();
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QNotFoundException("Table [" + tableName + "] was not found."));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private Map<String, QFieldMetaData> getFieldsForApiVersion(String tableName) throws QException
|
||||
{
|
||||
GetTableApiFieldsInput getTableApiFieldsInput = new GetTableApiFieldsInput()
|
||||
.withApiName(getApiName())
|
||||
.withVersion(getApiVersion())
|
||||
.withTableName(tableName);
|
||||
|
||||
GetTableApiFieldsOutput tableApiFieldsOutput = new GetTableApiFieldsAction().execute(getTableApiFieldsInput);
|
||||
List<QFieldMetaData> fields = tableApiFieldsOutput.getFields();
|
||||
Map<String, QFieldMetaData> fieldMap = CollectionUtils.listToMap(fields, f -> f.getName());
|
||||
return fieldMap;
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.executors;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.api.actions.ApiImplementation;
|
||||
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
|
||||
import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
|
||||
import com.kingsrook.qqq.api.model.actions.ApiFieldCustomValueMapper;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiOperation;
|
||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
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.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
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.model.metadata.tables.QTableMetaData;
|
||||
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.javalin.QJavalinMetaData;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.TableQueryExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.io.TableQueryInput;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.io.TableQueryOutputInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareTableQueryExecutor extends TableQueryExecutor implements ApiAwareExecutorInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void execute(TableQueryInput input, TableQueryOutputInterface output) throws QException
|
||||
{
|
||||
List<String> badRequestMessages = new ArrayList<>();
|
||||
|
||||
// todo - new operation? move all this to the api impl class??
|
||||
// todo table api name vs. internal name??
|
||||
String apiName = getApiName();
|
||||
String apiVersion = getApiVersion();
|
||||
QTableMetaData table = ApiImplementation.validateTableAndVersion(getApiInstanceMetaData(), apiVersion, input.getTableName(), ApiOperation.QUERY_BY_QUERY_STRING);
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(input.getTableName());
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);
|
||||
Map<String, QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsAction.ApiNameVersionAndTableName(apiName, apiVersion, table.getName()));
|
||||
|
||||
queryInput.setIncludeAssociations(true);
|
||||
// queryInput.setShouldFetchHeavyFields(true); // diffs from raw api
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS); // todo param
|
||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
queryInput.setQueryJoins(input.getJoins()); // todo - what are version implications here??
|
||||
|
||||
QQueryFilter filter = Objects.requireNonNullElseGet(input.getFilter(), () -> new QQueryFilter());
|
||||
queryInput.setFilter(filter);
|
||||
|
||||
if(filter.getLimit() == null)
|
||||
{
|
||||
QJavalinUtils.handleQueryNullLimit(QJavalinMetaData.of(QContext.getQInstance()), queryInput, null);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// take care of managing order-by fields and criteria, which may not be in this version, etc //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
manageOrderByFields(filter, tableApiFields, badRequestMessages, apiName, queryInput);
|
||||
QueryExecutorUtils.manageCriteriaFields(filter, tableApiFields, badRequestMessages, apiName, queryInput);
|
||||
|
||||
//////////////////////////////////////////
|
||||
// no more badRequest checks below here //
|
||||
//////////////////////////////////////////
|
||||
QueryExecutorUtils.throwIfBadRequestMessages(badRequestMessages);
|
||||
|
||||
///////////////////////
|
||||
// execute the query //
|
||||
///////////////////////
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
|
||||
List<QRecord> versionedRecords = QRecordApiAdapter.qRecordsToApiVersionedQRecordList(queryOutput.getRecords(), table.getName(), getApiName(), getApiVersion());
|
||||
|
||||
QValueFormatter.setDisplayValuesInRecordsIncludingPossibleValueTranslations(table, versionedRecords);
|
||||
|
||||
output.setRecords(versionedRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void manageOrderByFields(QQueryFilter filter, Map<String, QFieldMetaData> tableApiFields, List<String> badRequestMessages, String apiName, QueryInput queryInput)
|
||||
{
|
||||
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(filter.getOrderBys()))
|
||||
{
|
||||
String apiFieldName = orderBy.getFieldName();
|
||||
QFieldMetaData field = tableApiFields.get(apiFieldName);
|
||||
if(field == null)
|
||||
{
|
||||
badRequestMessages.add("Unrecognized orderBy field name: " + apiFieldName + ".");
|
||||
}
|
||||
else
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
|
||||
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
|
||||
{
|
||||
orderBy.setFieldName(apiFieldMetaData.getReplacedByFieldName());
|
||||
}
|
||||
else if(apiFieldMetaData.getCustomValueMapper() != null)
|
||||
{
|
||||
ApiFieldCustomValueMapper customValueMapper = QCodeLoader.getAdHoc(ApiFieldCustomValueMapper.class, apiFieldMetaData.getCustomValueMapper());
|
||||
customValueMapper.customizeFilterOrderBy(queryInput, orderBy, apiFieldName, apiFieldMetaData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.executors;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.api.model.actions.ApiFieldCustomValueMapper;
|
||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** shared code for query & count executors
|
||||
*******************************************************************************/
|
||||
public class QueryExecutorUtils
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static void manageCriteriaFields(QQueryFilter filter, Map<String, QFieldMetaData> tableApiFields, List<String> badRequestMessages, String apiName, QueryOrCountInputInterface input)
|
||||
{
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(filter.getCriteria()))
|
||||
{
|
||||
String apiFieldName = criteria.getFieldName();
|
||||
QFieldMetaData field = tableApiFields.get(apiFieldName);
|
||||
if(field == null)
|
||||
{
|
||||
badRequestMessages.add("Unrecognized criteria field name: " + apiFieldName + ".");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
|
||||
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
|
||||
{
|
||||
criteria.setFieldName(apiFieldMetaData.getReplacedByFieldName());
|
||||
}
|
||||
else if(apiFieldMetaData.getCustomValueMapper() != null)
|
||||
{
|
||||
ApiFieldCustomValueMapper customValueMapper = QCodeLoader.getAdHoc(ApiFieldCustomValueMapper.class, apiFieldMetaData.getCustomValueMapper());
|
||||
customValueMapper.customizeFilterCriteriaForQueryOrCount(input, filter, criteria, apiFieldName, apiFieldMetaData);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
badRequestMessages.add("Error processing criteria field " + apiFieldName + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
static void throwIfBadRequestMessages(List<String> badRequestMessages) throws QBadRequestException
|
||||
{
|
||||
if(!badRequestMessages.isEmpty())
|
||||
{
|
||||
if(badRequestMessages.size() == 1)
|
||||
{
|
||||
throw (new QBadRequestException(badRequestMessages.get(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QBadRequestException("Request failed with " + badRequestMessages.size() + " reasons: " + StringUtils.join("\n", badRequestMessages)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.TableCountSpecV1;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.TableMetaDataSpecV1;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.TableQuerySpecV1;
|
||||
import io.javalin.http.Context;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareMiddlewareVersionV1 extends MiddlewareVersionV1
|
||||
{
|
||||
private Map<String, ApiNameAndVersions> apiNameAndVersionsByPath = new HashMap<>();
|
||||
|
||||
private List<AbstractEndpointSpec<?, ?, ?>> specs;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private record ApiNameAndVersions(String apiName, Set<String> apiVersions)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ApiAwareMiddlewareVersionV1()
|
||||
{
|
||||
this.specs = defineEndpointSpecs();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void addVersion(String apiName, APIVersion apiVersion)
|
||||
{
|
||||
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(QContext.getQInstance());
|
||||
ApiInstanceMetaData apiInstanceMetaData = apiInstanceMetaDataContainer.getApis().get(apiName);
|
||||
String apiPath = apiInstanceMetaData.getPath();
|
||||
|
||||
apiPath = Objects.requireNonNullElse(apiPath, "").replaceFirst("^/", "").replaceFirst("/$", "");
|
||||
String apiVersionString = apiVersion.toString().replaceFirst("^/", "").replaceFirst("/$", "");
|
||||
|
||||
ApiNameAndVersions apiNameAndVersions = apiNameAndVersionsByPath.computeIfAbsent(apiPath, (p) -> new ApiNameAndVersions(apiName, new HashSet<>()));
|
||||
apiNameAndVersions.apiVersions().add(apiVersionString);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public List<AbstractEndpointSpec<?, ?, ?>> defineEndpointSpecs()
|
||||
{
|
||||
List<AbstractEndpointSpec<?, ?, ?>> specs = new ArrayList<>(super.getEndpointSpecs());
|
||||
|
||||
ListIterator<AbstractEndpointSpec<?, ?, ?>> listIterator = specs.listIterator();
|
||||
while(listIterator.hasNext())
|
||||
{
|
||||
AbstractEndpointSpec<?, ?, ?> spec = listIterator.next();
|
||||
if(spec.getClass().equals(TableMetaDataSpecV1.class))
|
||||
{
|
||||
listIterator.set(new ApiAwareTableMetaDataSpecV1());
|
||||
}
|
||||
else if(spec.getClass().equals(TableQuerySpecV1.class))
|
||||
{
|
||||
listIterator.set(new ApiAwareTableQuerySpecV1());
|
||||
}
|
||||
else if(spec.getClass().equals(TableCountSpecV1.class))
|
||||
{
|
||||
listIterator.set(new ApiAwareTableCountSpecV1());
|
||||
}
|
||||
}
|
||||
|
||||
return (specs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<AbstractEndpointSpec<?, ?, ?>> getEndpointSpecs()
|
||||
{
|
||||
return (specs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getVersionBasePath()
|
||||
{
|
||||
// return ("/" + getVersion() + "/" + apiPath + "/" + apiVersion + "/");
|
||||
return ("/" + getVersion() + "/{applicationApiPath}/{applicationApiVersion}/");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void preExecute(Context context) throws QException
|
||||
{
|
||||
String apiPath = context.pathParam("applicationApiPath");
|
||||
String apiVersion = context.pathParam("applicationApiVersion");
|
||||
|
||||
ApiNameAndVersions apiNameAndVersions = apiNameAndVersionsByPath.get(apiPath);
|
||||
if(apiNameAndVersions != null)
|
||||
{
|
||||
Set<String> allowedVersions = apiNameAndVersions.apiVersions();
|
||||
if(allowedVersions.contains(apiVersion))
|
||||
{
|
||||
QSession session = QContext.getQSession();
|
||||
session.setValue("apiName", apiNameAndVersions.apiName());
|
||||
session.setValue("apiVersion", apiVersion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new QNotFoundException("No API exists at the requested path.");
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.api.middleware.executors.ApiAwareTableCountExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.TableCountExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.TableCountSpecV1;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareTableCountSpecV1 extends TableCountSpecV1
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public TableCountExecutor newExecutor()
|
||||
{
|
||||
return new ApiAwareTableCountExecutor();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.api.middleware.executors.ApiAwareTableMetaDataExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.TableMetaDataExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.TableMetaDataSpecV1;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareTableMetaDataSpecV1 extends TableMetaDataSpecV1
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public TableMetaDataExecutor newExecutor()
|
||||
{
|
||||
return new ApiAwareTableMetaDataExecutor();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.api.middleware.executors.ApiAwareTableQueryExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.executors.TableQueryExecutor;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.TableQuerySpecV1;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiAwareTableQuerySpecV1 extends TableQuerySpecV1
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public TableQueryExecutor newExecutor()
|
||||
{
|
||||
return new ApiAwareTableQueryExecutor();
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.api.model.actions;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -70,6 +71,7 @@ public abstract class ApiFieldCustomValueMapper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "0.26.0 changed QueryInput to QueryOrCountInputInterface")
|
||||
public void customizeFilterCriteria(QueryInput queryInput, QQueryFilter filter, QFilterCriteria criteria, String apiFieldName, ApiFieldMetaData apiFieldMetaData)
|
||||
{
|
||||
/////////////////////
|
||||
@ -78,6 +80,18 @@ public abstract class ApiFieldCustomValueMapper
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void customizeFilterCriteriaForQueryOrCount(QueryOrCountInputInterface input, QQueryFilter filter, QFilterCriteria criteria, String apiFieldName, ApiFieldMetaData apiFieldMetaData)
|
||||
{
|
||||
if(input instanceof QueryInput queryInput)
|
||||
{
|
||||
customizeFilterCriteria(queryInput, filter, criteria, apiFieldName, apiFieldMetaData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
@ -216,6 +217,12 @@ public class ApiTableMetaDataContainer extends QSupplementalTableMetaData
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsAction.ApiNameVersionAndTableName(apiName, version.toString(), tableMetaData.getName()));
|
||||
}
|
||||
catch(QNotFoundException qnfe)
|
||||
{
|
||||
/////////////////////////////
|
||||
// skip tables not in apis //
|
||||
/////////////////////////////
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
String message = "Error validating ApiTableMetaData for table: " + tableMetaData.getName() + ", api: " + apiName + ", version: " + version;
|
||||
|
@ -22,7 +22,11 @@
|
||||
package com.kingsrook.qqq.api;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.api.implementations.savedreports.RenderSavedReportProcessApiMetaDataEnricher;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
@ -74,12 +78,14 @@ 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.TablesPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryModuleBackendVariantSetting;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaUpdateStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
@ -101,12 +107,19 @@ public class TestUtils
|
||||
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
|
||||
public static final String TABLE_NAME_ORDER_EXTRINSIC = "orderExtrinsic";
|
||||
|
||||
public static final String MEMORY_BACKEND_WITH_VARIANTS_NAME = "memoryWithVariants";
|
||||
public static final String TABLE_NAME_MEMORY_VARIANT_OPTIONS = "memoryVariantOptions";
|
||||
public static final String TABLE_NAME_MEMORY_VARIANT_DATA = "memoryVariantData";
|
||||
|
||||
public static final String PROCESS_NAME_GET_PERSON_INFO = "getPersonInfo";
|
||||
public static final String PROCESS_NAME_TRANSFORM_PEOPLE = "transformPeople";
|
||||
|
||||
public static final String API_NAME = "test-api";
|
||||
public static final String ALTERNATIVE_API_NAME = "person-api";
|
||||
|
||||
public static final String API_PATH = "/api/";
|
||||
public static final String ALTERNATIVE_API_PATH = "/person-api/";
|
||||
|
||||
public static final String V2022_Q4 = "2022.Q4";
|
||||
public static final String V2023_Q1 = "2023.Q1";
|
||||
public static final String V2023_Q2 = "2023.Q2";
|
||||
@ -139,12 +152,14 @@ public class TestUtils
|
||||
|
||||
qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
|
||||
|
||||
defineMemoryBackendVariantUseCases(qInstance);
|
||||
|
||||
addSavedReports(qInstance);
|
||||
|
||||
qInstance.withSupplementalMetaData(new ApiInstanceMetaDataContainer()
|
||||
.withApiInstanceMetaData(new ApiInstanceMetaData()
|
||||
.withName(API_NAME)
|
||||
.withPath("/api/")
|
||||
.withPath(API_PATH)
|
||||
.withLabel("Test API")
|
||||
.withDescription("QQQ Test API")
|
||||
.withContactEmail("contact@kingsrook.com")
|
||||
@ -154,7 +169,7 @@ public class TestUtils
|
||||
.withFutureVersions(List.of(new APIVersion(V2023_Q2))))
|
||||
.withApiInstanceMetaData(new ApiInstanceMetaData()
|
||||
.withName(ALTERNATIVE_API_NAME)
|
||||
.withPath("/person-api/")
|
||||
.withPath(ALTERNATIVE_API_PATH)
|
||||
.withLabel("Person-Only API")
|
||||
.withDescription("QQQ Test API, that only has the Person table.")
|
||||
.withContactEmail("contact@kingsrook.com")
|
||||
@ -331,6 +346,8 @@ public class TestUtils
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(TABLE_NAME_PERSON)
|
||||
.withLabel("Person")
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields("firstName", "lastName")
|
||||
.withBackendName(MEMORY_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("email"))
|
||||
@ -342,6 +359,7 @@ public class TestUtils
|
||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
|
||||
.withField(new QFieldMetaData("email", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("bestFriendPersonId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||
// .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("customValue", QFieldType.INTEGER).withPossibleValueSourceName(POSSIBLE_VALUE_SOURCE_CUSTOM))
|
||||
@ -551,6 +569,7 @@ public class TestUtils
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -579,6 +598,33 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void insertTim2Shoes() throws QException
|
||||
{
|
||||
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON).withRecord(getTim2ShoesRecord()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QRecord getTim2ShoesRecord() throws QException
|
||||
{
|
||||
return 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"))
|
||||
.withValue("photo", "ABCD".getBytes())
|
||||
.withValue("bestFriendPersonId", 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -683,4 +729,38 @@ public class TestUtils
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void defineMemoryBackendVariantUseCases(QInstance qInstance)
|
||||
{
|
||||
qInstance.addBackend(new QBackendMetaData()
|
||||
.withName(MEMORY_BACKEND_WITH_VARIANTS_NAME)
|
||||
.withBackendType(MemoryBackendModule.class)
|
||||
.withUsesVariants(true)
|
||||
.withBackendVariantsConfig(new BackendVariantsConfig()
|
||||
.withVariantTypeKey(TABLE_NAME_MEMORY_VARIANT_OPTIONS)
|
||||
.withOptionsTableName(TABLE_NAME_MEMORY_VARIANT_OPTIONS)
|
||||
.withBackendSettingSourceFieldNameMap(Map.of(MemoryModuleBackendVariantSetting.PRIMARY_KEY, "id"))
|
||||
));
|
||||
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(TABLE_NAME_MEMORY_VARIANT_DATA)
|
||||
.withBackendName(MEMORY_BACKEND_WITH_VARIANTS_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
);
|
||||
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(TABLE_NAME_MEMORY_VARIANT_OPTIONS)
|
||||
.withBackendName(MEMORY_BACKEND_NAME) // note, the version without variants!
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,12 +35,14 @@ 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.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
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;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType.STRING;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
@ -60,7 +62,16 @@ class GetTableApiFieldsActionTest extends BaseTest
|
||||
*******************************************************************************/
|
||||
private List<? extends QFieldMetaData> getFields(String tableName, String version) throws QException
|
||||
{
|
||||
return new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withApiName(TestUtils.API_NAME).withTableName(tableName).withVersion(version)).getFields();
|
||||
return (getFields(TestUtils.API_NAME, tableName, version));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<? extends QFieldMetaData> getFields(String apiName, String tableName, String version) throws QException
|
||||
{
|
||||
return new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withApiName(apiName).withTableName(tableName).withVersion(version)).getFields();
|
||||
}
|
||||
|
||||
|
||||
@ -113,4 +124,47 @@ class GetTableApiFieldsActionTest extends BaseTest
|
||||
assertEquals(Set.of("a", "b", "d"), fieldListToNameSet.apply(getFields(TABLE_NAME, "3")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTablesNotFound() throws QException
|
||||
{
|
||||
String tableNameVersion2plus = "tableNameVersion2plus";
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(tableNameVersion2plus)
|
||||
.withSupplementalMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("2")))
|
||||
.withField(new QFieldMetaData("a", STRING)));
|
||||
|
||||
String tableNameVersion2through4 = "tableNameVersion2through4";
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(tableNameVersion2through4)
|
||||
.withSupplementalMetaData(new ApiTableMetaDataContainer().withApiTableMetaData(TestUtils.API_NAME, new ApiTableMetaData().withInitialVersion("2").withFinalVersion("4")))
|
||||
.withField(new QFieldMetaData("a", STRING)));
|
||||
|
||||
String tableNameNoApis = "tableNameNoApis";
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(tableNameNoApis)
|
||||
.withField(new QFieldMetaData("a", STRING)));
|
||||
|
||||
new QInstanceEnricher(qInstance).enrich();
|
||||
|
||||
assertThatThrownBy(() -> getFields("no-such-table", "1")).isInstanceOf(QNotFoundException.class);
|
||||
|
||||
assertThatThrownBy(() -> getFields(tableNameVersion2plus, "1")).isInstanceOf(QNotFoundException.class);
|
||||
getFields(tableNameVersion2plus, "2");
|
||||
assertThatThrownBy(() -> getFields("noSuchApi", tableNameVersion2plus, "2")).isInstanceOf(QNotFoundException.class);
|
||||
|
||||
assertThatThrownBy(() -> getFields(tableNameVersion2through4, "1")).isInstanceOf(QNotFoundException.class);
|
||||
getFields(tableNameVersion2through4, "2");
|
||||
getFields(tableNameVersion2through4, "3");
|
||||
getFields(tableNameVersion2through4, "4");
|
||||
assertThatThrownBy(() -> getFields(tableNameVersion2through4, "5")).isInstanceOf(QNotFoundException.class);
|
||||
|
||||
assertThatThrownBy(() -> getFields(tableNameNoApis, "1")).isInstanceOf(QNotFoundException.class);
|
||||
}
|
||||
|
||||
}
|
@ -25,7 +25,6 @@ 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;
|
||||
@ -58,13 +57,7 @@ class QRecordApiAdapterTest extends BaseTest
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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"))
|
||||
.withValue("photo", "ABCD".getBytes());
|
||||
QRecord person = TestUtils.getTim2ShoesRecord();
|
||||
|
||||
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!
|
||||
@ -204,4 +197,60 @@ class QRecordApiAdapterTest extends BaseTest
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQRecordsToApiVersionedQRecordList() throws QException
|
||||
{
|
||||
QRecord person = TestUtils.getTim2ShoesRecord();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QRecord has values corresponding to what's defined in the QInstance (and the underlying backend system) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord pastQRecordRecord = QRecordApiAdapter.qRecordsToApiVersionedQRecordList(List.of(person), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2022_Q4).get(0);
|
||||
assertEquals(2, pastQRecordRecord.getValueInteger("shoeCount")); // old field name - not currently in the QTable, but we can still get its value!
|
||||
assertFalse(pastQRecordRecord.getValues().containsKey("noOfShoes")); // current field name - doesn't appear in old api-version
|
||||
assertFalse(pastQRecordRecord.getValues().containsKey("cost")); // a current field name, but also not in this old api version
|
||||
assertEquals("QUJDRA==", pastQRecordRecord.getValueString("photo")); // base64 version of "ABCD".getBytes()
|
||||
|
||||
QRecord currentQRecord = QRecordApiAdapter.qRecordsToApiVersionedQRecordList(List.of(person), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q1).get(0);
|
||||
assertFalse(currentQRecord.getValues().containsKey("shoeCount")); // old field name - not in this current api version
|
||||
assertEquals(2, currentQRecord.getValueInteger("noOfShoes")); // current field name - value here as we expect
|
||||
assertFalse(currentQRecord.getValues().containsKey("cost")); // future field name - not in the current api (we added the field during new dev, and didn't change the api)
|
||||
|
||||
QRecord futureQRecord = QRecordApiAdapter.qRecordsToApiVersionedQRecordList(List.of(person), TestUtils.TABLE_NAME_PERSON, TestUtils.API_NAME, TestUtils.V2023_Q2).get(0);
|
||||
assertFalse(futureQRecord.getValues().containsKey("shoeCount")); // old field name - also not in this future api version
|
||||
assertEquals(2, futureQRecord.getValueInteger("noOfShoes")); // current field name - still here.
|
||||
assertEquals(new BigDecimal("3.50"), futureQRecord.getValueBigDecimal("cost")); // future field name appears now that we've requested this future api version.
|
||||
|
||||
for(QRecord specialRecord : List.of(pastQRecordRecord, currentQRecord, futureQRecord))
|
||||
{
|
||||
assertEquals(LocalDate.parse("1980-05-31"), specialRecord.getValueLocalDate("birthDay")); // use the apiFieldName
|
||||
assertFalse(specialRecord.getValues().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))
|
||||
{
|
||||
QRecord alternativeQRecord = QRecordApiAdapter.qRecordsToApiVersionedQRecordList(List.of(person), TestUtils.TABLE_NAME_PERSON, TestUtils.ALTERNATIVE_API_NAME, version).get(0);
|
||||
for(String key : person.getValues().keySet())
|
||||
{
|
||||
if(key.equals("photo"))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ok, well, skip the blob field (should be base64 version, and is covered elsewhere) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
assertEquals(person.getValueString(key), ValueUtils.getValueAsString(alternativeQRecord.getValueString(key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.api.TestUtils;
|
||||
import com.kingsrook.qqq.api.middleware.specs.v1.ApiAwareMiddlewareVersionV1;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class ApiAwareSpecTestBase extends SpecTestBase
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected QInstance defineQInstance() throws QException
|
||||
{
|
||||
return (TestUtils.defineInstance());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected void primeTestData(QInstance qInstance) throws Exception
|
||||
{
|
||||
QContext.withTemporaryContext(new CapturedContext(qInstance, new QSystemUserSession()), () ->
|
||||
{
|
||||
TestUtils.insertSimpsons();
|
||||
TestUtils.insertTim2Shoes();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected AbstractMiddlewareVersion getMiddlewareVersion()
|
||||
{
|
||||
ApiAwareMiddlewareVersionV1 apiAwareMiddlewareVersionV1 = new ApiAwareMiddlewareVersionV1();
|
||||
|
||||
apiAwareMiddlewareVersionV1.addVersion(TestUtils.API_NAME, new APIVersion(TestUtils.V2022_Q4));
|
||||
apiAwareMiddlewareVersionV1.addVersion(TestUtils.API_NAME, new APIVersion(TestUtils.V2023_Q1));
|
||||
apiAwareMiddlewareVersionV1.addVersion(TestUtils.API_NAME, new APIVersion(TestUtils.V2023_Q2));
|
||||
apiAwareMiddlewareVersionV1.addVersion(TestUtils.ALTERNATIVE_API_NAME, new APIVersion(TestUtils.V2022_Q4));
|
||||
apiAwareMiddlewareVersionV1.addVersion(TestUtils.ALTERNATIVE_API_NAME, new APIVersion(TestUtils.V2023_Q1));
|
||||
apiAwareMiddlewareVersionV1.addVersion(TestUtils.ALTERNATIVE_API_NAME, new APIVersion(TestUtils.V2023_Q2));
|
||||
|
||||
return apiAwareMiddlewareVersionV1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected String getBaseUrlAndPath(String apiPath, String version)
|
||||
{
|
||||
String path = "/qqq/" + getVersion() + "/" + apiPath + "/" + version;
|
||||
return "http://localhost:" + PORT + path.replaceAll("/+", "/").replaceFirst("/$", "");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.api.TestUtils;
|
||||
import com.kingsrook.qqq.api.middleware.specs.ApiAwareSpecTestBase;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
|
||||
import io.javalin.http.ContentType;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for ApiAwareTableQuerySpecV1
|
||||
*******************************************************************************/
|
||||
class ApiAwareTableCountSpecV1Test extends ApiAwareSpecTestBase
|
||||
{
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected AbstractEndpointSpec<?, ?, ?> getSpec()
|
||||
{
|
||||
return new ApiAwareTableCountSpecV1();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected String getVersion()
|
||||
{
|
||||
return "v1";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testBasicSuccess()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/count")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Simpson")))))
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertThat(jsonObject.getInt("count")).isEqualTo(5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQueryByOldFieldName()
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// old field name - make sure works in old api //
|
||||
/////////////////////////////////////////////////
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2022_Q4) + "/table/person/count")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("shoeCount", QCriteriaOperator.EQUALS, 2)))))
|
||||
.asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertThat(jsonObject.getInt("count")).isGreaterThanOrEqualTo(1);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// old field name - make sure fails in current api //
|
||||
/////////////////////////////////////////////////////
|
||||
response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/count")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("shoeCount", QCriteriaOperator.EQUALS, 2)))))
|
||||
.asString();
|
||||
assertEquals(400, response.getStatus());
|
||||
jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals("Unrecognized criteria field name: shoeCount.", jsonObject.getString("error"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.api.TestUtils;
|
||||
import com.kingsrook.qqq.api.middleware.specs.ApiAwareSpecTestBase;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
class ApiAwareTableMetaDataSpecV1Test extends ApiAwareSpecTestBase
|
||||
{
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected AbstractEndpointSpec<?, ?, ?> getSpec()
|
||||
{
|
||||
return new ApiAwareTableMetaDataSpecV1();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected String getVersion()
|
||||
{
|
||||
return "v1";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testBasicSuccess()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/metaData/table/person").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertThat(jsonObject.getJSONObject("fields").getJSONObject("noOfShoes").getString("label")).isEqualTo("No Of Shoes");
|
||||
assertFalse(jsonObject.getJSONObject("fields").has("shoeCount"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQueryOldVersion()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2022_Q4) + "/metaData/table/person").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertFalse(jsonObject.getJSONObject("fields").has("noOfShoes"));
|
||||
assertThat(jsonObject.getJSONObject("fields").getJSONObject("shoeCount").getString("label")).isEqualTo("Shoe Count");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.middleware.specs.v1;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.api.TestUtils;
|
||||
import com.kingsrook.qqq.api.middleware.specs.ApiAwareSpecTestBase;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
|
||||
import io.javalin.http.ContentType;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for ApiAwareTableQuerySpecV1
|
||||
*******************************************************************************/
|
||||
class ApiAwareTableQuerySpecV1Test extends ApiAwareSpecTestBase
|
||||
{
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected AbstractEndpointSpec<?, ?, ?> getSpec()
|
||||
{
|
||||
return new ApiAwareTableQuerySpecV1();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected String getVersion()
|
||||
{
|
||||
return "v1";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testBasicSuccess()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, "Simpson")))))
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertThat(records.length()).isGreaterThanOrEqualTo(1);
|
||||
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
assertThat(record.getString("recordLabel")).contains("Simpson");
|
||||
assertThat(record.getString("tableName")).isEqualTo("person");
|
||||
assertThat(record.getJSONObject("values").getString("lastName")).isEqualTo("Simpson");
|
||||
assertThat(record.getJSONObject("displayValues").getString("lastName")).isEqualTo("Simpson");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDisplayValues()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("bestFriendPersonId", QCriteriaOperator.IS_NOT_BLANK)))))
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertThat(records.length()).isGreaterThanOrEqualTo(1);
|
||||
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
assertThat(record.getString("tableName")).isEqualTo("person");
|
||||
assertThat(record.getJSONObject("values").getInt("bestFriendPersonId")).isEqualTo(1);
|
||||
assertThat(record.getJSONObject("displayValues").getString("bestFriendPersonId")).isEqualTo("Homer Simpson");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNoBody()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertThat(records.length()).isGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Note - same data cases as in QRecordApiAdapterTest
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testVersions()
|
||||
{
|
||||
String requestBody = JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, "Tim"))));
|
||||
|
||||
/////////////////
|
||||
// old version //
|
||||
/////////////////
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2022_Q4) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(requestBody)
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
assertThat(record.getJSONObject("values").getInt("shoeCount")).isEqualTo(2);
|
||||
assertFalse(record.getJSONObject("values").has("noOfShoes"));
|
||||
assertFalse(record.getJSONObject("values").has("cost"));
|
||||
assertThat(record.getJSONObject("values").getString("photo")).isEqualTo("QUJDRA==");
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// current version //
|
||||
/////////////////////
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(requestBody)
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
assertFalse(record.getJSONObject("values").has("shoeCount"));
|
||||
assertThat(record.getJSONObject("values").getInt("noOfShoes")).isEqualTo(2);
|
||||
assertFalse(record.getJSONObject("values").has("cost"));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// future version ... not actually yet exposed, so, can't query on it //
|
||||
// (unlike the QRecordApiAdapterTest that this is based on, that doesn't care about supported versions or not) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q2) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(requestBody)
|
||||
.asString();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
assertFalse(record.getJSONObject("values").has("shoeCount"));
|
||||
assertThat(record.getJSONObject("values").getInt("noOfShoes")).isEqualTo(2);
|
||||
assertThat(record.getJSONObject("values").getString("cost")).isEqualTo("3.50");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQueryByOldFieldName()
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// old field name - make sure works in old api //
|
||||
/////////////////////////////////////////////////
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2022_Q4) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("shoeCount", QCriteriaOperator.EQUALS, 2)))))
|
||||
.asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
JSONArray records = jsonObject.getJSONArray("records");
|
||||
assertThat(records.length()).isGreaterThanOrEqualTo(1);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// old field name - make sure fails in current api //
|
||||
/////////////////////////////////////////////////////
|
||||
response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.body(JsonUtils.toJson(Map.of("filter", new QQueryFilter(new QFilterCriteria("shoeCount", QCriteriaOperator.EQUALS, 2)))))
|
||||
.asString();
|
||||
assertEquals(400, response.getStatus());
|
||||
jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals("Unrecognized criteria field name: shoeCount.", jsonObject.getString("error"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNotFoundCases()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1 + "-no-such-version") + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.asString();
|
||||
assertEquals(404, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals("No API exists at the requested path.", jsonObject.getString("error"));
|
||||
|
||||
response = Unirest.post(getBaseUrlAndPath("no-such-api", TestUtils.V2023_Q1) + "/table/person/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.asString();
|
||||
assertEquals(404, response.getStatus());
|
||||
jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals("No API exists at the requested path.", jsonObject.getString("error"));
|
||||
|
||||
response = Unirest.post(getBaseUrlAndPath(TestUtils.API_PATH, TestUtils.V2023_Q1) + "/table/no-such-table/query")
|
||||
.contentType(ContentType.APPLICATION_JSON.getMimeType())
|
||||
.asString();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - better as 403 (to make non-tables look like non-permissed tables, to avoid leaking that bit of data? //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertEquals(404, response.getStatus());
|
||||
jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertEquals("Could not find a table named no-such-table in this api.", jsonObject.getString("error"));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user