Add table override name, isExcluded;

Remove more hidden, excluded, etc tables;
Update to comply with rules of openapi spec;
This commit is contained in:
2023-03-23 09:14:56 -05:00
parent c369430261
commit 11977624bf
8 changed files with 581 additions and 153 deletions

View File

@ -26,10 +26,14 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.api.model.openapi.Components;
import com.kingsrook.qqq.api.model.openapi.Contact;
import com.kingsrook.qqq.api.model.openapi.Content;
@ -54,9 +58,12 @@ 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.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -64,6 +71,7 @@ import com.kingsrook.qqq.backend.core.utils.YamlUtils;
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
import io.javalin.http.HttpStatus;
import org.apache.commons.lang.BooleanUtils;
/*******************************************************************************
@ -71,6 +79,9 @@ import io.javalin.http.HttpStatus;
*******************************************************************************/
public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateOpenApiSpecInput, GenerateOpenApiSpecOutput>
{
private static final QLogger LOG = QLogger.getLogger(GenerateOpenApiSpecAction.class);
/*******************************************************************************
**
@ -101,7 +112,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
openAPI.setTags(new ArrayList<>());
openAPI.setPaths(new LinkedHashMap<>());
LinkedHashMap<Integer, Response> componentResponses = new LinkedHashMap<>();
LinkedHashMap<String, Response> componentResponses = new LinkedHashMap<>();
LinkedHashMap<String, Schema> componentSchemas = new LinkedHashMap<>();
LinkedHashMap<String, SecurityScheme> securitySchemes = new LinkedHashMap<>();
openAPI.setComponents(new Components()
@ -119,6 +130,16 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withScopes(scopes)
))
);
securitySchemes.put("bearerAuth", new SecurityScheme()
.withType("http")
.withScheme("bearer")
.withBearerFormat("JWT")
);
securitySchemes.put("basicAuth", new SecurityScheme()
.withType("http")
.withScheme("basic")
);
componentSchemas.put("baseSearchResultFields", new Schema()
.withType("object")
@ -140,19 +161,58 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
///////////////////
for(QTableMetaData table : qInstance.getTables().values())
{
String tableName = table.getName();
if(table.getIsHidden())
{
LOG.debug("Omitting table [" + tableName + "] because it is marked as hidden");
continue;
}
String tableName = table.getName();
String tableNameUcFirst = StringUtils.ucFirst(table.getName());
String tableLabel = table.getLabel();
String primaryKeyName = table.getPrimaryKeyField();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
String primaryKeyLabel = primaryKeyField.getLabel();
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
if(apiTableMetaData == null)
{
LOG.debug("Omitting table [" + tableName + "] because it does not have any apiTableMetaData");
continue;
}
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
if(BooleanUtils.isTrue(apiTableMetaData.getIsExcluded()))
{
LOG.debug("Omitting table [" + tableName + "] because its apiTableMetaData marks it as excluded");
continue;
}
APIVersionRange apiVersionRange = apiTableMetaData.getApiVersionRange();
if(!apiVersionRange.includes(new APIVersion(version)))
{
LOG.debug("Omitting table [" + tableName + "] because its api version range [" + apiVersionRange + "] does not include this version [" + version + "]");
continue;
}
QBackendMetaData tableBackend = qInstance.getBackendForTable(tableName);
boolean queryCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_QUERY);
boolean getCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_GET);
boolean updateCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_UPDATE);
boolean deleteCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_DELETE);
boolean insertCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_INSERT);
boolean countCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_COUNT);
if(!queryCapability && !getCapability && !updateCapability && !deleteCapability && !insertCapability)
{
LOG.debug("Omitting table [" + tableName + "] because it does not have any supported capabilities");
continue;
}
String tableApiName = StringUtils.hasContent(apiTableMetaData.getApiTableName()) ? apiTableMetaData.getApiTableName() : tableName;
String tableApiNameUcFirst = StringUtils.ucFirst(tableApiName);
String tableLabel = table.getLabel();
String primaryKeyName = table.getPrimaryKeyField();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
String primaryKeyLabel = primaryKeyField.getLabel();
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(primaryKeyField);
String primaryKeyApiName = (apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.getApiFieldName())) ? apiFieldMetaData.getApiFieldName() : primaryKeyName;
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
if(StringUtils.hasContent(tableReadPermissionName))
@ -193,7 +253,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
// build the schemas for this table //
//////////////////////////////////////
LinkedHashMap<String, Schema> tableFieldsWithoutPrimaryKey = new LinkedHashMap<>();
componentSchemas.put(tableName + "WithoutPrimaryKey", new Schema()
componentSchemas.put(tableApiName + "WithoutPrimaryKey", new Schema()
.withType("object")
.withProperties(tableFieldsWithoutPrimaryKey));
@ -211,18 +271,18 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
);
}
componentSchemas.put(tableName, new Schema()
componentSchemas.put(tableApiName, new Schema()
.withType("object")
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/" + tableName + "WithoutPrimaryKey")))
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey")))
.withProperties(MapBuilder.of(
primaryKeyName, new Schema()
primaryKeyApiName, new Schema()
.withType(getFieldType(table.getField(primaryKeyName)))
.withFormat(getFieldFormat(table.getField(primaryKeyName)))
.withDescription(primaryKeyLabel + " for the " + tableLabel + ". Primary Key.")
))
);
componentSchemas.put(tableName + "SearchResult", new Schema()
componentSchemas.put(tableApiName + "SearchResult", new Schema()
.withType("object")
.withAllOf(ListBuilder.of(new Schema().withRef("#/components/schemas/baseSearchResultFields")))
.withProperties(MapBuilder.of(
@ -230,8 +290,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withType("array")
.withItems(new Schema()
.withAllOf(ListBuilder.of(
new Schema().withRef("#/components/schemas/" + tableName),
new Schema().withRef("#/components/schemas/" + tableName + "WithoutPrimaryKey")
new Schema().withRef("#/components/schemas/" + tableApiName),
new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey")
))
)
))
@ -243,7 +303,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
Method queryGet = new Method()
.withSummary("Search the " + tableLabel + " table using multiple query string fields.")
.withDescription("TODO")
.withOperationId("query" + tableNameUcFirst)
.withOperationId("query" + tableApiNameUcFirst)
.withTags(ListBuilder.of(tableLabel))
.withParameters(ListBuilder.of(
new Parameter()
@ -269,7 +329,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
""")
.withIn("query")
.withSchema(new Schema().withType("string"))
.withExamples(buildOrderByExamples(primaryKeyName, tableApiFields)),
.withExamples(buildOrderByExamples(primaryKeyApiName, tableApiFields)),
new Parameter()
.withName("booleanOperator")
.withDescription("Whether to combine query field as an AND or an OR. Default is AND.")
@ -280,9 +340,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withResponse(HttpStatus.OK.getCode(), new Response()
.withDescription("Successfully searched the " + tableLabel + " table (though may have found 0 records).")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema().withRef("#/components/schemas/" + tableName + "SearchResult"))
.withSchema(new Schema().withRef("#/components/schemas/" + tableApiName + "SearchResult"))
)))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableReadPermissionName))));
.withSecurity(getSecurity(tableReadPermissionName));
for(QFieldMetaData tableApiField : tableApiFields)
{
@ -306,23 +366,27 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
Method queryPost = new Method()
.withSummary("Search the " + tableLabel + " table by posting a QueryFilter object.")
.withTags(ListBuilder.of(tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableReadPermissionName))));
.withSecurity(getSecurity(tableReadPermissionName));
openAPI.getPaths().put("/" + tableName + "/query", new Path()
.withGet(queryGet)
.withPost(queryPost)
);
if(queryCapability)
{
openAPI.getPaths().put("/" + tableApiName + "/query", new Path()
.withGet(queryGet)
// todo!! .withPost(queryPost)
);
}
Method idGet = new Method()
.withSummary("Get one " + tableLabel + " by " + primaryKeyLabel)
.withDescription("TODO")
.withOperationId("get" + tableNameUcFirst)
.withOperationId("get" + tableApiNameUcFirst)
.withTags(ListBuilder.of(tableLabel))
.withParameters(ListBuilder.of(
new Parameter()
.withName(primaryKeyName)
.withName(primaryKeyApiName)
.withDescription(primaryKeyLabel + " of the " + tableLabel + " to get.")
.withIn("path")
.withRequired(true)
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
))
.withResponses(buildStandardErrorResponses())
@ -330,55 +394,60 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withResponse(HttpStatus.OK.getCode(), new Response()
.withDescription("Successfully got the requested " + tableLabel)
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema().withRef("#/components/schemas/" + tableName))
.withSchema(new Schema().withRef("#/components/schemas/" + tableApiName))
)))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableReadPermissionName))));
.withSecurity(getSecurity(tableReadPermissionName));
Method idPatch = new Method()
.withSummary("Update one " + tableLabel + ".")
.withDescription("TODO")
.withOperationId("update" + tableNameUcFirst)
.withOperationId("update" + tableApiNameUcFirst)
.withTags(ListBuilder.of(tableLabel))
.withParameters(ListBuilder.of(
new Parameter()
.withName(primaryKeyName)
.withName(primaryKeyApiName)
.withDescription(primaryKeyLabel + " of the " + tableLabel + " to update.")
.withIn("path")
.withRequired(true)
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
))
.withRequestBody(new RequestBody()
.withRequired(true)
.withDescription("Field values to update in the " + tableLabel + " record.")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema().withRef("#/components/schemas/" + tableName))
.withSchema(new Schema().withRef("#/components/schemas/" + tableApiName))
)))
.withResponses(buildStandardErrorResponses())
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
.withResponse(HttpStatus.NO_CONTENT.getCode(), new Response().withDescription("Successfully updated the requested " + tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableUpdatePermissionName))));
.withSecurity(getSecurity(tableUpdatePermissionName));
Method idDelete = new Method()
.withSummary("Delete one " + tableLabel + ".")
.withDescription("TODO")
.withOperationId("delete" + tableNameUcFirst)
.withOperationId("delete" + tableApiNameUcFirst)
.withTags(ListBuilder.of(tableLabel))
.withParameters(ListBuilder.of(
new Parameter()
.withName(primaryKeyName)
.withName(primaryKeyApiName)
.withDescription(primaryKeyLabel + " of the " + tableLabel + " to delete.")
.withIn("path")
.withRequired(true)
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
))
.withResponses(buildStandardErrorResponses())
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
.withResponse(HttpStatus.NO_CONTENT.getCode(), new Response().withDescription("Successfully deleted the requested " + tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableDeletePermissionName))));
.withSecurity(getSecurity(tableDeletePermissionName));
openAPI.getPaths().put("/" + tableName + "/{" + primaryKeyName + "}", new Path()
.withGet(idGet)
.withPatch(idPatch)
.withDelete(idDelete)
);
if(getCapability || updateCapability || deleteCapability)
{
openAPI.getPaths().put("/" + tableApiName + "/{" + primaryKeyApiName + "}", new Path()
.withGet(getCapability ? idGet : null)
.withPatch(updateCapability ? idPatch : null)
.withDelete(deleteCapability ? idDelete : null)
);
}
Method slashPost = new Method()
.withSummary("Create one " + tableLabel + " record.")
@ -386,7 +455,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withRequired(true)
.withDescription("Values for the " + tableLabel + " record to create.")
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema().withRef("#/components/schemas/" + tableName + "WithoutPrimaryKey"))
.withSchema(new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey"))
)))
.withResponses(buildStandardErrorResponses())
.withResponse(HttpStatus.CREATED.getCode(), new Response()
@ -394,18 +463,21 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("object")
.withProperties(MapBuilder.of(primaryKeyName, new Schema()
.withProperties(MapBuilder.of(primaryKeyApiName, new Schema()
.withType(getFieldType(primaryKeyField))
.withExample("47")
))
)
)))
.withTags(ListBuilder.of(tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableInsertPermissionName))));
.withSecurity(getSecurity(tableInsertPermissionName));
openAPI.getPaths().put("/" + tableName + "/", new Path()
.withPost(slashPost)
);
if(insertCapability)
{
openAPI.getPaths().put("/" + tableApiName + "/", new Path()
.withPost(slashPost)
);
}
////////////////
// bulk paths //
@ -418,11 +490,11 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("array")
.withItems(new Schema().withRef("#/components/schemas/" + tableName + "WithoutPrimaryKey"))))))
.withItems(new Schema().withRef("#/components/schemas/" + tableApiName + "WithoutPrimaryKey"))))))
.withResponses(buildStandardErrorResponses())
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyName, primaryKeyField, "post"))
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyApiName, primaryKeyField, "post"))
.withTags(ListBuilder.of(tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableInsertPermissionName))));
.withSecurity(getSecurity(tableInsertPermissionName));
Method bulkPatch = new Method()
.withSummary("Update multiple " + tableLabel + " records.")
@ -432,11 +504,11 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withContent(MapBuilder.of("application/json", new Content()
.withSchema(new Schema()
.withType("array")
.withItems(new Schema().withRef("#/components/schemas/" + tableName))))))
.withItems(new Schema().withRef("#/components/schemas/" + tableApiName))))))
.withResponses(buildStandardErrorResponses())
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyName, primaryKeyField, "patch"))
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyApiName, primaryKeyField, "patch"))
.withTags(ListBuilder.of(tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableUpdatePermissionName))));
.withSecurity(getSecurity(tableUpdatePermissionName));
Method bulkDelete = new Method()
.withSummary("Delete multiple " + tableLabel + " records.")
@ -449,20 +521,23 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
.withItems(new Schema().withType(getFieldType(primaryKeyField)))
.withExample(List.of(42, 47))))))
.withResponses(buildStandardErrorResponses())
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyName, primaryKeyField, "delete"))
.withResponse(HttpStatus.MULTI_STATUS.getCode(), buildMultiStatusResponse(tableLabel, primaryKeyApiName, primaryKeyField, "delete"))
.withTags(ListBuilder.of(tableLabel))
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableDeletePermissionName))));
.withSecurity(getSecurity(tableDeletePermissionName));
openAPI.getPaths().put("/" + tableName + "/bulk", new Path()
.withPost(bulkPost)
.withPatch(bulkPatch)
.withDelete(bulkDelete));
if(insertCapability || updateCapability || deleteCapability)
{
openAPI.getPaths().put("/" + tableApiName + "/bulk", new Path()
.withPost(insertCapability ? bulkPost : null)
.withPatch(updateCapability ? bulkPatch : null)
.withDelete(deleteCapability ? bulkDelete : null));
}
}
componentResponses.put(HttpStatus.BAD_REQUEST.getCode(), buildStandardErrorResponse("Bad Request. Some portion of the request's content was not acceptable to the server. See error message in body for details.", "Parameter id should be given an integer value, but received string: \"Foo\""));
componentResponses.put(HttpStatus.UNAUTHORIZED.getCode(), buildStandardErrorResponse("Unauthorized. The required authentication credentials were missing or invalid.", "The required authentication credentials were missing or invalid."));
componentResponses.put(HttpStatus.FORBIDDEN.getCode(), buildStandardErrorResponse("Forbidden. You do not have permission to access the requested resource.", "You do not have permission to access the requested resource."));
componentResponses.put(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), buildStandardErrorResponse("Internal Server Error. An error occurred in the server processing the request.", "Database connection error. Try again later."));
componentResponses.put("error" + HttpStatus.BAD_REQUEST.getCode(), buildStandardErrorResponse("Bad Request. Some portion of the request's content was not acceptable to the server. See error message in body for details.", "Parameter id should be given an integer value, but received string: \"Foo\""));
componentResponses.put("error" + HttpStatus.UNAUTHORIZED.getCode(), buildStandardErrorResponse("Unauthorized. The required authentication credentials were missing or invalid.", "The required authentication credentials were missing or invalid."));
componentResponses.put("error" + HttpStatus.FORBIDDEN.getCode(), buildStandardErrorResponse("Forbidden. You do not have permission to access the requested resource.", "You do not have permission to access the requested resource."));
componentResponses.put("error" + HttpStatus.INTERNAL_SERVER_ERROR.getCode(), buildStandardErrorResponse("Internal Server Error. An error occurred in the server processing the request.", "Database connection error. Try again later."));
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecOutput();
output.setOpenAPI(openAPI);
@ -473,11 +548,25 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private static List<Map<String, List<String>>> getSecurity(String permissionName)
{
return ListBuilder.of(
MapBuilder.of("OAuth2", List.of(permissionName)),
MapBuilder.of("bearerAuth", List.of(permissionName)),
MapBuilder.of("basicAuth", List.of(permissionName))
);
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("checkstyle:indentation")
private Response buildMultiStatusResponse(String tableLabel, String primaryKeyName, QFieldMetaData primaryKeyField, String method)
private Response buildMultiStatusResponse(String tableLabel, String primaryKeyApiName, QFieldMetaData primaryKeyField, String method)
{
List<Object> example = switch(method.toLowerCase())
{
@ -485,7 +574,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
MapBuilder.of(LinkedHashMap::new)
.with("statusCode", HttpStatus.CREATED.getCode())
.with("statusText", HttpStatus.CREATED.getMessage())
.with(primaryKeyName, "47").build(),
.with(primaryKeyApiName, "47").build(),
MapBuilder.of(LinkedHashMap::new)
.with("statusCode", HttpStatus.BAD_REQUEST.getCode())
.with("statusText", HttpStatus.BAD_REQUEST.getMessage())
@ -517,7 +606,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
properties.put("error", new Schema().withType("string"));
if(method.equalsIgnoreCase("post"))
{
properties.put(primaryKeyName, new Schema().withType(getFieldType(primaryKeyField)));
properties.put(primaryKeyApiName, new Schema().withType(getFieldType(primaryKeyField)));
}
return new Response()
@ -538,7 +627,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
/*******************************************************************************
**
*******************************************************************************/
private Map<String, Example> buildOrderByExamples(String primaryKeyName, List<? extends QFieldMetaData> tableApiFields)
private Map<String, Example> buildOrderByExamples(String primaryKeyApiName, List<? extends QFieldMetaData> tableApiFields)
{
Map<String, Example> rs = new LinkedHashMap<>();
@ -547,7 +636,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
for(QFieldMetaData tableApiField : tableApiFields)
{
String name = tableApiField.getName();
if(primaryKeyName.equals(name) || fieldsForExample4.contains(name) || fieldsForExample5.contains(name))
if(primaryKeyApiName.equals(name) || fieldsForExample4.contains(name) || fieldsForExample5.contains(name))
{
continue;
}
@ -567,16 +656,16 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
}
}
rs.put(primaryKeyName, new ExampleWithSingleValue()
.withSummary("order by " + primaryKeyName + " (by default is ascending)")
rs.put(primaryKeyApiName, new ExampleWithSingleValue()
.withSummary("order by " + primaryKeyApiName + " (by default is ascending)")
.withValue("id"));
rs.put(primaryKeyName + "Desc", new ExampleWithSingleValue()
.withSummary("order by " + primaryKeyName + " (descending)")
rs.put(primaryKeyApiName + "Desc", new ExampleWithSingleValue()
.withSummary("order by " + primaryKeyApiName + " (descending)")
.withValue("id desc"));
rs.put(primaryKeyName + "Asc", new ExampleWithSingleValue()
.withSummary("order by " + primaryKeyName + " (explicitly ascending)")
rs.put(primaryKeyApiName + "Asc", new ExampleWithSingleValue()
.withSummary("order by " + primaryKeyApiName + " (explicitly ascending)")
.withValue("id asc"));
if(fieldsForExample4.size() == 2)
@ -669,10 +758,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
private static Map<Integer, Response> buildStandardErrorResponses()
{
return MapBuilder.of(
HttpStatus.BAD_REQUEST.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.BAD_REQUEST.getCode()),
HttpStatus.UNAUTHORIZED.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.UNAUTHORIZED.getCode()),
HttpStatus.FORBIDDEN.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.FORBIDDEN.getCode()),
HttpStatus.INTERNAL_SERVER_ERROR.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.INTERNAL_SERVER_ERROR.getCode())
HttpStatus.BAD_REQUEST.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.BAD_REQUEST.getCode()),
HttpStatus.UNAUTHORIZED.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.UNAUTHORIZED.getCode()),
HttpStatus.FORBIDDEN.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.FORBIDDEN.getCode()),
HttpStatus.INTERNAL_SERVER_ERROR.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.INTERNAL_SERVER_ERROR.getCode())
);
}

View File

@ -34,7 +34,6 @@ import java.util.Set;
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
@ -88,6 +87,7 @@ import io.javalin.apibuilder.ApiBuilder;
import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import org.apache.commons.lang.BooleanUtils;
import org.eclipse.jetty.http.HttpStatus;
import org.json.JSONArray;
import org.json.JSONObject;
@ -102,7 +102,9 @@ public class QJavalinApiHandler
{
private static final QLogger LOG = QLogger.getLogger(QJavalinApiHandler.class);
static QInstance qInstance;
private static QInstance qInstance;
private static Map<String, Map<String, QTableMetaData>> tableApiNameMap = new HashMap<>();
@ -198,35 +200,19 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static APIVersionRange getApiVersionRange(QTableMetaData table)
{
ApiTableMetaData middlewareMetaData = ApiTableMetaData.of(table);
if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null)
{
return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion()));
}
return (APIVersionRange.none());
}
/*******************************************************************************
**
*******************************************************************************/
private static void doGet(Context context)
{
String version = context.pathParam("version");
String tableName = context.pathParam("tableName");
String primaryKey = context.pathParam("primaryKey");
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
String primaryKey = context.pathParam("primaryKey");
try
{
QTableMetaData table = qInstance.getTable(tableName);
validateTableAndVersion(context, version, table);
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
String tableName = table.getName();
GetInput getInput = new GetInput();
@ -276,16 +262,16 @@ public class QJavalinApiHandler
*******************************************************************************/
private static void doQuery(Context context)
{
String version = context.pathParam("version");
String tableName = context.pathParam("tableName");
QQueryFilter filter = null;
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
QQueryFilter filter = null;
try
{
List<String> badRequestMessages = new ArrayList<>();
QTableMetaData table = qInstance.getTable(tableName);
validateTableAndVersion(context, version, table);
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
String tableName = table.getName();
QueryInput queryInput = new QueryInput();
setupSession(context, queryInput);
@ -506,24 +492,74 @@ public class QJavalinApiHandler
/*******************************************************************************
**
*******************************************************************************/
private static void validateTableAndVersion(Context context, String version, QTableMetaData table) throws QNotFoundException
private static QTableMetaData validateTableAndVersion(Context context, String version, String tableApiName) throws QNotFoundException
{
QNotFoundException qNotFoundException = new QNotFoundException("Could not find any resources at path " + context.path());
QTableMetaData table = getTableByApiName(version, tableApiName);
if(table == null)
{
throw (new QNotFoundException("Could not find any resources at path " + context.path()));
throw (qNotFoundException);
}
if(BooleanUtils.isTrue(table.getIsHidden()))
{
throw (qNotFoundException);
}
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
if(apiTableMetaData == null)
{
throw (qNotFoundException);
}
if(BooleanUtils.isTrue(apiTableMetaData.getIsExcluded()))
{
throw (qNotFoundException);
}
APIVersion requestApiVersion = new APIVersion(version);
List<APIVersion> supportedVersions = ApiInstanceMetaData.of(qInstance).getSupportedVersions();
if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion))
{
throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path()));
throw (qNotFoundException);
}
if(!getApiVersionRange(table).includes(requestApiVersion))
if(!apiTableMetaData.getApiVersionRange().includes(requestApiVersion))
{
throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path()));
throw (qNotFoundException);
}
return (table);
}
/*******************************************************************************
**
*******************************************************************************/
private static QTableMetaData getTableByApiName(String version, String tableApiName)
{
if(tableApiNameMap.get(version) == null)
{
Map<String, QTableMetaData> map = new HashMap<>();
for(QTableMetaData table : qInstance.getTables().values())
{
ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table);
String name = table.getName();
if(apiTableMetaData != null && StringUtils.hasContent(apiTableMetaData.getApiTableName()))
{
name = apiTableMetaData.getApiTableName();
}
map.put(name, table);
}
tableApiNameMap.put(version, map);
}
return (tableApiNameMap.get(version).get(tableApiName));
}
@ -662,13 +698,13 @@ public class QJavalinApiHandler
*******************************************************************************/
private static void doInsert(Context context)
{
String version = context.pathParam("version");
String tableName = context.pathParam("tableName");
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
try
{
QTableMetaData table = qInstance.getTable(tableName);
validateTableAndVersion(context, version, table);
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
String tableName = table.getName();
InsertInput insertInput = new InsertInput();
@ -722,13 +758,13 @@ public class QJavalinApiHandler
*******************************************************************************/
private static void bulkInsert(Context context)
{
String version = context.pathParam("version");
String tableName = context.pathParam("tableName");
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
try
{
QTableMetaData table = qInstance.getTable(tableName);
validateTableAndVersion(context, version, table);
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
String tableName = table.getName();
InsertInput insertInput = new InsertInput();
@ -820,14 +856,14 @@ public class QJavalinApiHandler
*******************************************************************************/
private static void doUpdate(Context context)
{
String version = context.pathParam("version");
String tableName = context.pathParam("tableName");
String primaryKey = context.pathParam("primaryKey");
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
String primaryKey = context.pathParam("primaryKey");
try
{
QTableMetaData table = qInstance.getTable(tableName);
validateTableAndVersion(context, version, table);
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
String tableName = table.getName();
UpdateInput updateInput = new UpdateInput();
@ -899,14 +935,14 @@ public class QJavalinApiHandler
*******************************************************************************/
private static void doDelete(Context context)
{
String version = context.pathParam("version");
String tableName = context.pathParam("tableName");
String primaryKey = context.pathParam("primaryKey");
String version = context.pathParam("version");
String tableApiName = context.pathParam("tableName");
String primaryKey = context.pathParam("primaryKey");
try
{
QTableMetaData table = qInstance.getTable(tableName);
validateTableAndVersion(context, version, table);
QTableMetaData table = validateTableAndVersion(context, version, tableApiName);
String tableName = table.getName();
DeleteInput deleteInput = new DeleteInput();

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.api.model.metadata.tables;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.api.ApiMiddlewareType;
import com.kingsrook.qqq.api.model.APIVersionRange;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
@ -40,6 +41,9 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
private String initialVersion;
private String finalVersion;
private String apiTableName;
private Boolean isExcluded;
private List<QFieldMetaData> removedApiFields;
@ -54,6 +58,23 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
/*******************************************************************************
**
*******************************************************************************/
public APIVersionRange getApiVersionRange()
{
if(getInitialVersion() == null)
{
return APIVersionRange.none();
}
return (getFinalVersion() != null
? APIVersionRange.betweenAndIncluding(getInitialVersion(), getFinalVersion())
: APIVersionRange.afterAndIncluding(getInitialVersion()));
}
/*******************************************************************************
**
*******************************************************************************/
@ -218,4 +239,66 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData
return (this);
}
/*******************************************************************************
** Getter for apiTableName
*******************************************************************************/
public String getApiTableName()
{
return (this.apiTableName);
}
/*******************************************************************************
** Setter for apiTableName
*******************************************************************************/
public void setApiTableName(String apiTableName)
{
this.apiTableName = apiTableName;
}
/*******************************************************************************
** Fluent setter for apiTableName
*******************************************************************************/
public ApiTableMetaData withApiTableName(String apiTableName)
{
this.apiTableName = apiTableName;
return (this);
}
/*******************************************************************************
** Getter for isExcluded
*******************************************************************************/
public Boolean getIsExcluded()
{
return (this.isExcluded);
}
/*******************************************************************************
** Setter for isExcluded
*******************************************************************************/
public void setIsExcluded(Boolean isExcluded)
{
this.isExcluded = isExcluded;
}
/*******************************************************************************
** Fluent setter for isExcluded
*******************************************************************************/
public ApiTableMetaData withIsExcluded(Boolean isExcluded)
{
this.isExcluded = isExcluded;
return (this);
}
}

View File

@ -32,7 +32,7 @@ public class Components
{
private Map<String, Example> examples;
private Map<String, Schema> schemas;
private Map<Integer, Response> responses;
private Map<String, Response> responses;
private Map<String, SecurityScheme> securitySchemes;
@ -102,7 +102,7 @@ public class Components
/*******************************************************************************
** Getter for responses
*******************************************************************************/
public Map<Integer, Response> getResponses()
public Map<String, Response> getResponses()
{
return (this.responses);
}
@ -112,7 +112,7 @@ public class Components
/*******************************************************************************
** Setter for responses
*******************************************************************************/
public void setResponses(Map<Integer, Response> responses)
public void setResponses(Map<String, Response> responses)
{
this.responses = responses;
}
@ -122,7 +122,7 @@ public class Components
/*******************************************************************************
** Fluent setter for responses
*******************************************************************************/
public Components withResponses(Map<Integer, Response> responses)
public Components withResponses(Map<String, Response> responses)
{
this.responses = responses;
return (this);

View File

@ -32,6 +32,7 @@ public class Parameter
{
private String name;
private String description;
private Boolean required;
private String in;
private Schema schema;
private Boolean explode;
@ -223,4 +224,35 @@ public class Parameter
return (this);
}
/*******************************************************************************
** Getter for required
*******************************************************************************/
public Boolean getRequired()
{
return (this.required);
}
/*******************************************************************************
** Setter for required
*******************************************************************************/
public void setRequired(Boolean required)
{
this.required = required;
}
/*******************************************************************************
** Fluent setter for required
*******************************************************************************/
public Parameter withRequired(Boolean required)
{
this.required = required;
return (this);
}
}

View File

@ -28,6 +28,8 @@ package com.kingsrook.qqq.api.model.openapi;
public class SecurityScheme
{
private String type;
private String scheme;
private String bearerFormat;
@ -60,4 +62,66 @@ public class SecurityScheme
return (this);
}
/*******************************************************************************
** Getter for scheme
*******************************************************************************/
public String getScheme()
{
return (this.scheme);
}
/*******************************************************************************
** Setter for scheme
*******************************************************************************/
public void setScheme(String scheme)
{
this.scheme = scheme;
}
/*******************************************************************************
** Fluent setter for scheme
*******************************************************************************/
public SecurityScheme withScheme(String scheme)
{
this.scheme = scheme;
return (this);
}
/*******************************************************************************
** Getter for bearerFormat
*******************************************************************************/
public String getBearerFormat()
{
return (this.bearerFormat);
}
/*******************************************************************************
** Setter for bearerFormat
*******************************************************************************/
public void setBearerFormat(String bearerFormat)
{
this.bearerFormat = bearerFormat;
}
/*******************************************************************************
** Fluent setter for bearerFormat
*******************************************************************************/
public SecurityScheme withBearerFormat(String bearerFormat)
{
this.bearerFormat = bearerFormat;
return (this);
}
}

View File

@ -22,19 +22,21 @@
package com.kingsrook.qqq.api.actions;
import java.util.Set;
import com.kingsrook.qqq.api.BaseTest;
import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
@ -49,23 +51,111 @@ class GenerateOpenApiSpecActionTest extends BaseTest
@Test
void test() throws QException
{
String version = TestUtils.V2023_Q1;
QInstance qInstance = QContext.getQInstance();
qInstance.withMiddlewareMetaData(new ApiInstanceMetaData()
.withCurrentVersion(new APIVersion(version))
);
for(QTableMetaData table : qInstance.getTables().values())
{
table.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(version));
break;
}
new QInstanceEnricher(qInstance).enrich();
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(version));
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION));
System.out.println(output.getYaml());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testExcludedTables() throws QException
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData()
.withName("supportedTable")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4)));
qInstance.addTable(new QTableMetaData()
.withName("hiddenTable")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withIsHidden(true)
.withMiddlewareMetaData(new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4)));
qInstance.addTable(new QTableMetaData()
.withName("excludedTable")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData()
.withIsExcluded(true)));
qInstance.addTable(new QTableMetaData()
.withName("tableWithoutApiMetaData")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)));
qInstance.addTable(new QTableMetaData()
.withName("tableWithFutureVersion")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData()
.withInitialVersion(TestUtils.V2023_Q2)));
qInstance.addTable(new QTableMetaData()
.withName("tableWithOnlyPastVersions")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4)
.withFinalVersion(TestUtils.V2022_Q4)));
qInstance.addTable(new QTableMetaData()
.withName("tableWithNoSupportedCapabilities")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withoutCapabilities(Capability.TABLE_QUERY, Capability.TABLE_GET, Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE)
.withMiddlewareMetaData(new ApiTableMetaData()
.withInitialVersion(TestUtils.V2022_Q4)));
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION));
Set<String> apiPaths = output.getOpenAPI().getPaths().keySet();
assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/supportedTable/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/hiddenTable/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/excludedTable/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithoutApiMetaData/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithoutApiMetaData/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithFutureVersion/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithNoSupportedCapabilities/")));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testApiTableName() throws QException
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData()
.withName("internalName")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData()
.withApiTableName("externalName")
.withInitialVersion(TestUtils.V2022_Q4)));
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION));
Set<String> apiPaths = output.getOpenAPI().getPaths().keySet();
assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/externalName/")));
assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/internalName/")));
}
}

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.api.BaseTest;
import com.kingsrook.qqq.api.TestUtils;
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -35,6 +36,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import kong.unirest.HttpResponse;
@ -72,6 +76,16 @@ class QJavalinApiHandlerTest extends BaseTest
static void beforeAll() throws QInstanceValidationException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(new QTableMetaData()
.withName("internalName")
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withMiddlewareMetaData(new ApiTableMetaData()
.withApiTableName("externalName")
.withInitialVersion(TestUtils.V2022_Q4)));
qJavalinImplementation = new QJavalinImplementation(qInstance);
qJavalinImplementation.startJavalinServer(PORT);
qJavalinImplementation.getJavalinService().routes(new QJavalinApiHandler(qInstance).getRoutes());
@ -686,13 +700,33 @@ class QJavalinApiHandlerTest extends BaseTest
@Test
void testDelete404()
{
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/1")
.asString();
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/1").asString();
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find Person with Id of 1", response);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRenamedTable()
{
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/internalName/query").asString();
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/externalName/query").asString();
assertEquals(HttpStatus.OK_200, response.getStatus());
JSONObject jsonObject = new JSONObject(response.getBody());
assertEquals(0, jsonObject.getInt("count"));
}
}
/*******************************************************************************
**
*******************************************************************************/