Skip to content

Commit

Permalink
DATAAPI-36: spec updated to support different response types
Browse files Browse the repository at this point in the history
  • Loading branch information
jjannek committed Dec 19, 2024
1 parent c06fdb5 commit bd3e5df
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 20 deletions.
2 changes: 1 addition & 1 deletion handlers/rest-apis/data/v1/SingleRecord.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ component {
, title = "Not found"
, message = "No [#arguments.entity#] record was found with ID [#arguments.recordId#]"
);
} else if ( IsStruct( updated ) ) { // updated and configured to either return the full record or the id only
} else if ( IsStruct( updated ) || IsArray( updated ) ) { // updated and configured to either return the full record or the id only (in an array)
restResponse.setData( updated );
} else { // updated but configured to return an empty response
restResponse.noData();
Expand Down
7 changes: 6 additions & 1 deletion i18n/dataapi.properties
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,24 @@ operation.get.by.id.params.recordId=ID of the **{1}** record that you wish to fe

operation.put.description=Used to batch update **{1}** records.
operation.put.200.description=Array of updated **{1}** records.
operation.put.200.description.idonly=Array of updated **{1}** records IDs.
operation.put.200.description.empty=Empty
operation.put.422.description=Data validation failure. Returned when one or more entities failed validation. No records will have been updated.
operation.put.body.description=Array of **{1}** objects to update. Missing fields per item will be ignored and only fields given will be updated.

operation.put.by.id.description=Used to update an individual **{1}** record by ID.
operation.put.by.id.200.description=Updated **{1}** record object
operation.put.by.id.200.description.idonly=ID of the updated **{1}** record object
operation.put.by.id.200.description.empty=Empty
operation.put.by.id.404.description=No record found for the given ID
operation.put.by.id.422.description=Data validation failure. Returned when one or more fields have failed validation.
operation.put.by.id.params.recordId=ID of the **{1}** record that you wish to update.
operation.put.by.id.body.description=**{1}** object with fields to update. Missing fields will be ignored.


operation.post.description=Used to create **{1}** records.
operation.post.200.description=Array of created **{1}** records.
operation.post.200.description.idonly=Array of created **{1}** record IDs.
operation.post.200.description.empty=Empty
operation.post.422.description=Data validation failure. Returned when one or more entities failed validation. No records will have been created.
operation.post.body.description=Array of **{1}** objects to create.

Expand Down
36 changes: 36 additions & 0 deletions services/DataApiConfigurationService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,42 @@ component {
return _entityConfigOption( arguments.entity, "responseTypeOnUpdate", DEFAULT_RESPONSE_TYPE );
}

public boolean function entityUseRecordResponseOnInsert( required string entity ) {
return isRecordResponseType( entityResponseTypeOnInsert( arguments.entity ) );
}

public boolean function entityUseIdOnlyResponseOnInsert( required string entity ) {
return isIdOnlyResponseType( entityResponseTypeOnInsert( arguments.entity ) );
}

public boolean function entityUseEmptyResponseOnInsert( required string entity ) {
return isEmptyResponseType( entityResponseTypeOnInsert( arguments.entity ) );
}

public boolean function entityUseRecordResponseOnUpdate( required string entity ) {
return isRecordResponseType( entityResponseTypeOnUpdate( arguments.entity ) );
}

public boolean function entityUseIdOnlyResponseOnUpdate( required string entity ) {
return isIdOnlyResponseType( entityResponseTypeOnUpdate( arguments.entity ) );
}

public boolean function entityUseEmptyResponseOnUpdate( required string entity ) {
return isEmptyResponseType( entityResponseTypeOnUpdate( arguments.entity ) );
}

public boolean function isRecordResponseType( required string responseType ) {
return arguments.responseType == VALID_RESPONSE_TYPES.RECORD;
}

public boolean function isIdOnlyResponseType( required string responseType ) {
return arguments.responseType == VALID_RESPONSE_TYPES.IDONLY;
}

public boolean function isEmptyResponseType( required string responseType ) {
return arguments.responseType == VALID_RESPONSE_TYPES.EMPTY;
}

public string function getEntityObject( required string entity, string namespace=_getDataApiNamespace() ) {
var args = arguments;
var cacheKey = "getEntityObject" & args.namespace & args.entity;
Expand Down
19 changes: 11 additions & 8 deletions services/DataApiService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ component {

interceptDataArgs.responseType = _getConfigService().validateResponseType( interceptDataArgs.responseType );

if ( interceptDataArgs.responseType == "empty" ) {
if ( _getConfigService().isEmptyResponseType( interceptDataArgs.responseType ) ) {
return "";
} else if ( interceptDataArgs.responseType == "idonly" ) {
} else if ( _getConfigService().isIdOnlyResponseType( interceptDataArgs.responseType ) ) {
return newId;
}

Expand All @@ -160,13 +160,16 @@ component {

interceptDataArgs.responseType = _getConfigService().validateResponseType( interceptDataArgs.responseType );

var isEmptyResponseType = _getConfigService().isEmptyResponseType( interceptDataArgs.responseType );
var isIdOnlyResponseType = _getConfigService().isIdOnlyResponseType( interceptDataArgs.responseType );

for( var record in records ) {
recordId = record[ idField ] ?: "";
if ( Len( Trim( recordId ) ) ) {
if ( updateSingleRecord( arguments.entity, record, recordId ) ) {
if ( interceptDataArgs.responseType == "empty" ) {
if ( isEmptyResponseType ) {
continue;
} else if ( interceptDataArgs.responseType == "idonly" ) {
} else if ( isIdOnlyResponseType ) {
ArrayAppend( updated, recordId );
} else {
ArrayAppend( updated, getSingleRecord( entity, recordId, [] ) );
Expand All @@ -181,7 +184,7 @@ component {

interceptDataArgs.responseType = _getConfigService().validateResponseType( interceptDataArgs.responseType );

return interceptDataArgs.responseType == "empty" ? "" : interceptDataArgs.updated;
return _getConfigService().isEmptyResponseType( interceptDataArgs.responseType ) ? "" : interceptDataArgs.updated;
}

public any function updateSingleRecord( required string entity, required struct data, required string recordId, boolean returnResponse=false ) {
Expand All @@ -208,11 +211,11 @@ component {
if ( recordsUpdated == 0 ) {
return 0;
}
else if ( interceptDataArgs.responseType == "record" ) {
else if ( _getConfigService().isRecordResponseType( interceptDataArgs.responseType ) ) {
return getSingleRecord( arguments.entity, arguments.recordId, [] );
}
else if ( interceptDataArgs.responseType == "idonly" ) {
return { id=arguments.recordId };
else if ( _getConfigService().isIdOnlyResponseType( interceptDataArgs.responseType ) ) {
return [ arguments.recordId ];
}
else { // empty
return "";
Expand Down
63 changes: 53 additions & 10 deletions services/DataApiSpecService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,17 @@ component {

private void function _addEntitySpecs( required struct spec ) {
var configService = _getConfigService();
var entities = _getConfigService().getEntities();
var entityNames = StructKeyArray( entities );
var tags = [];
var categories = {};
var entities = _getConfigService().getEntities();
var entityNames = StructKeyArray( entities );
var tags = [];
var categories = {};

var emptyResponseContent = { "text/plain" = { schema={ type="string" } } };
var idArrayResponseContent = { "application/json" = { schema={ type="array", items={ type="string" } } } };

var updateSingleResponseContent = "";
var updateMultiResponseContent = "";
var insertMultiResponseContent = "";

entityNames.sort( "textnocase" );

Expand All @@ -243,6 +250,21 @@ component {

tag[ "x-sort-order" ] = _i18nNamespaced( uri="dataapi:entity.#entityName#.sort.order", defaultValue=tag.name );

var insertResponseI18nSuffix = "";
var updateResponseI18nSuffix = "";

if ( configService.entityUseEmptyResponseOnInsert( entityName ) ) {
insertResponseI18nSuffix = ".empty";
} else if ( configService.entityUseIdOnlyResponseOnInsert( entityName ) ) {
insertResponseI18nSuffix = ".idonly";
}

if ( configService.entityUseEmptyResponseOnUpdate( entityName ) ) {
updateResponseI18nSuffix = ".empty";
} else if ( configService.entityUseIdOnlyResponseOnUpdate( entityName ) ) {
updateResponseI18nSuffix = ".idonly";
}

if ( Len( Trim( category ) ) ) {
tag[ "x-category" ] = category;

Expand Down Expand Up @@ -349,6 +371,18 @@ component {
}

if ( configService.entityVerbIsSupported( entityName, "put" ) ) {

if ( configService.entityUseEmptyResponseOnUpdate( entityName ) ) {
updateSingleResponseContent = emptyResponseContent;
updateMultiResponseContent = emptyResponseContent;
} else if ( configService.entityUseIdOnlyResponseOnUpdate( entityName ) ) {
updateSingleResponseContent = idArrayResponseContent;
updateMultiResponseContent = idArrayResponseContent;
} else { // record (default behaviour)
updateSingleResponseContent = { "application/json" = { schema={ "$ref"="##/components/schemas/#entityName#" } } };
updateMultiResponseContent = { "application/json" = { schema={ type="array", items={"$ref"="##/components/schemas/#entityName#" } } } };
}

spec.paths[ "/entity/#entityName#/" ].put = {
tags = [ entityTag ]
, summary = "PUT /entity/#entityName#/"
Expand All @@ -362,8 +396,8 @@ component {
}
, responses = {
"200" = {
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.put.200.description", defaultValue=_i18nNamespaced( uri="dataapi:operation.put.200.description", defaultValue="", data=[ entitySingular ] ) )
, content = { "application/json" = { schema={ type="array", items={"$ref"="##/components/schemas/#entityName#" } } } }
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.put.200.description#updateResponseI18nSuffix#", defaultValue=_i18nNamespaced( uri="dataapi:operation.put.200.description#updateResponseI18nSuffix#", defaultValue="", data=[ entitySingular ] ) )
, content = updateMultiResponseContent
}
, "422" = {
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.put.422.description", defaultValue=_i18nNamespaced( uri="dataapi:operation.put.422.description", defaultValue="", data=[ entitySingular ] ) )
Expand All @@ -385,8 +419,8 @@ component {
}
, responses = {
"200" = {
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.put.by.id.200.description", defaultValue=_i18nNamespaced( uri="dataapi:operation.put.by.id.200.description", defaultValue="", data=[ entitySingular ] ) )
, content = { "application/json" = { schema={ "$ref"="##/components/schemas/#entityName#" } } }
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.put.by.id.200.description#updateResponseI18nSuffix#", defaultValue=_i18nNamespaced( uri="dataapi:operation.put.by.id.200.description#updateResponseI18nSuffix#", defaultValue="", data=[ entitySingular ] ) )
, content = updateSingleResponseContent
}
, "404" = {
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.put.by.id.404.description", defaultValue=_i18nNamespaced( uri="dataapi:operation.put.by.id.404.description", defaultValue="", data=[ entitySingular ] ) )
Expand All @@ -407,6 +441,15 @@ component {
}

if ( configService.entityVerbIsSupported( entityName, "post" ) ) {

if ( configService.entityUseEmptyResponseOnInsert( entityName ) ) {
insertMultiResponseContent = emptyResponseContent;
} else if ( configService.entityUseIdOnlyResponseOnInsert( entityName ) ) {
insertMultiResponseContent = idArrayResponseContent;
} else { // record (default behaviour)
insertMultiResponseContent = { "application/json" = { schema={ type="array", items={"$ref"="##/components/schemas/#entityName#" } } } };
}

spec.paths[ "/entity/#entityName#/" ].post = {
tags = [ entityTag ]
, summary = "POST /entity/#entityName#/"
Expand All @@ -420,8 +463,8 @@ component {
}
, responses = {
"200" = {
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.post.200.description", defaultValue=_i18nNamespaced( uri="dataapi:operation.post.200.description", defaultValue="", data=[ entitySingular ] ) )
, content = { "application/json" = { schema={ type="array", items={"$ref"="##/components/schemas/#entityName#" } } } }
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.post.200.description#insertResponseI18nSuffix#", defaultValue=_i18nNamespaced( uri="dataapi:operation.post.200.description#insertResponseI18nSuffix#", defaultValue="", data=[ entitySingular ] ) )
, content = insertMultiResponseContent
}
, "422" = {
description = _i18nNamespaced( uri="dataapi:operation.#entityName#.post.422.description", defaultValue=_i18nNamespaced( uri="dataapi:operation.post.422.description", defaultValue="", data=[ entitySingular ] ) )
Expand Down

0 comments on commit bd3e5df

Please sign in to comment.