From 107477b8cecab7577328b4d01a3b057de2b030be Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 4 Nov 2025 10:31:18 +0100 Subject: [PATCH 01/10] Improved label finding Previously certain attributes were fetched from the database for every single record. Now these properties are defined on object initialization and only the property is retrieved for each find. Should give a pretty good performance increase. --- .../AppSrc/WebApi/cRestChildCollection.pkg | 36 ++++++------ Web API Library/AppSrc/WebApi/cRestEntity.pkg | 57 +++++++------------ Web API Library/AppSrc/WebApi/cRestField.pkg | 57 ++++++++++--------- .../DDSrc/cApiLogsDataDictionary.dd | 2 +- 4 files changed, 68 insertions(+), 84 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg index a991899..cc3f21a 100644 --- a/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg +++ b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg @@ -71,24 +71,7 @@ Class cRestChildCollection is a cObject End_Procedure Function SchemaName Returns String - Integer iFile - String sTableName - Handle hoServer - - Get psNodeName to sTableName - - //If the node name was not manually set default to the name of the table - If (sTableName <> "") Begin - Function_Return sTableName - End - - Get Server to hoServer - - Get Main_File of hoServer to iFile - - Get_Attribute DF_FILE_LOGICAL_NAME of iFile to sTableName - - Function_Return sTableName + Function_Return (psNodeName(Self)) End_Function Function FieldName Returns String @@ -103,4 +86,21 @@ Class cRestChildCollection is a cObject Function IsRequired Returns Boolean Function_Return False End_Function + + Procedure AfterAttachDDO + Integer iFile + Handle hoServer + String sTableName + + Get Server to hoServer + Get Main_File of hoServer to iFile + + If (hoServer = 0 and iFile = 0) ; + Procedure_Return + + If (psNodeName(Self) = "") Begin + Get_Attribute DF_FILE_LOGICAL_NAME of iFile to sTableName + Set psNodeName to sTableName + End + End_Procedure End_Class \ No newline at end of file diff --git a/Web API Library/AppSrc/WebApi/cRestEntity.pkg b/Web API Library/AppSrc/WebApi/cRestEntity.pkg index 7a86234..f318389 100644 --- a/Web API Library/AppSrc/WebApi/cRestEntity.pkg +++ b/Web API Library/AppSrc/WebApi/cRestEntity.pkg @@ -96,45 +96,9 @@ Class cRestEntity is a cObject Loop End_Procedure - //Retrieves the field name that manages the connection between the main table and the parent. - Function FieldName Returns String - Handle hoDD - Integer iFile iField - String sFieldName - - // Get the main Data Dictionaries - Get Main_DD to hoDD - Get Data_File to iFile - Get Data_Field to iField - - If (iField > 0) Begin - Get_Attribute DF_FIELD_NAME of iFile iField to sFieldName - Function_Return sFieldName - End - - Function_Return "" - End_Function - //Returns the name of the table - Function SchemaName Returns String - Integer iFile - String sTableName - Handle hoServer - - Get psNodeName to sTableName - - //If the node name was not manually set default to the name of the table - If (sTableName <> "") Begin - Function_Return sTableName - End - - Get Server to hoServer - - Get Main_File of hoServer to iFile - - Get_Attribute DF_FILE_LOGICAL_NAME of iFile to sTableName - - Function_Return sTableName + Function SchemaName Returns String + Function_Return (psNodeName(Self)) End_Function //Checks if the field is fiterable @@ -159,6 +123,23 @@ Class cRestEntity is a cObject Function_Return iRelField End_Function + + Procedure AfterAttachDDO + Integer iFile + Handle hoServer + String sTableName + + Get Server to hoServer + Get Main_File of hoServer to iFile + + If (hoServer = 0 and iFile = 0) ; + Procedure_Return + + If (psNodeName(Self) = "") Begin + Get_Attribute DF_FILE_LOGICAL_NAME of iFile to sTableName + Set psNodeName to sTableName + End + End_Procedure Procedure End_Construct_Object Forward Send End_Construct_Object diff --git a/Web API Library/AppSrc/WebApi/cRestField.pkg b/Web API Library/AppSrc/WebApi/cRestField.pkg index 1480eb0..8e16c31 100644 --- a/Web API Library/AppSrc/WebApi/cRestField.pkg +++ b/Web API Library/AppSrc/WebApi/cRestField.pkg @@ -171,8 +171,6 @@ Class cRestField is a cObject This will either be psFieldName if it is set or it will get the DF_FIELD_Name. */ Function FieldName Returns String - Handle hoDD - Integer iField iFile String sFieldName Get psFieldName to sFieldName @@ -181,37 +179,14 @@ Class cRestField is a cObject Function_Return sFieldName End - Get Main_DD to hoDD - - Get Data_File to iFile - Get Data_Field to iField - - If (hoDD <> 0 and iFile <> 0) Begin - Get File_Field_Label of hoDD iFile iField DD_LABEL_TAG to sFieldName - Function_Return sFieldName - End - Error DFERR_PROGRAM "Non data aware cRestfields must have a psFieldName" Function_Return "" End_Function //Returns the DF_FIELD_TYPE of the current item. - Function FieldType Returns Integer - Integer iFile iField eFieldType - - Get Data_File to iFile - Get Data_Field to iField - - //If the field has data binding get the field type from the database. - If (iFile <> 0) Begin - Get_Attribute DF_FIELD_TYPE of iFile iField to eFieldType - End - Else Begin - Get peFieldType to eFieldType - End - - Function_Return eFieldType + Function FieldType Returns Integer + Function_Return (peFieldType(Self)) End_Function //Returns the Status help field of the data dictionary @@ -315,4 +290,32 @@ Class cRestField is a cObject Function_Return bRequired End_Function + + // Fired by the cBaseDeo interface. Sets default values. + // This way we only need to query database APIs once instead of for every single find. + Procedure AfterAttachDDO + String sFieldName + Integer iFile iField + Integer eFieldType + Handle hoDD + + Get Server to hoDD + Get Data_File to iFile + Get Data_Field to iField + + // If we have no data binding no need to determine defaults here + If (iFile = 0 and iField = 0) ; + Procedure_Return + + // If the field name isnt + If (psFieldName(Self) = "") Begin + Get Field_Label of hoDD iField DD_LABEL_TAG to sFieldName + Set psFieldName to sFieldName + End + + Get_Attribute DF_FIELD_TYPE of iFile iField to eFieldType + Set peFieldType to eFieldType + + + End_Procedure End_Class \ No newline at end of file diff --git a/Web API Sample/DDSrc/cApiLogsDataDictionary.dd b/Web API Sample/DDSrc/cApiLogsDataDictionary.dd index 85c7a04..e2edf34 100644 --- a/Web API Sample/DDSrc/cApiLogsDataDictionary.dd +++ b/Web API Sample/DDSrc/cApiLogsDataDictionary.dd @@ -33,7 +33,7 @@ Class cApiLogsDataDictionary is a DataDictionary Move (Cast(ApiLogs.TimeResponse, DateTime)) to dtResponseTime Move (dtResponseTime - dtIncomingTime) to tsTimeBetween - Move (SpanMilliseconds(tsTimeBetween)) to iTimeInMiliseconds + Move (SpanTotalMilliseconds(tsTimeBetween)) to iTimeInMiliseconds Move iTimeInMiliseconds to ApiLogs.TimeTakenInMiliseconds End_Procedure From 750317e99ec45bbd35806a2b6e38d4a4530bcdc4 Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 4 Nov 2025 14:22:40 +0100 Subject: [PATCH 02/10] Implemented sql constrains Applications that support sql filters can now use them to optimize constraints a little. --- .../AppSrc/WebApi/cRestDataset.pkg | 5 +- Web API Library/AppSrc/WebApi/cRestField.pkg | 68 +++++++++++++++---- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cRestDataset.pkg b/Web API Library/AppSrc/WebApi/cRestDataset.pkg index e53bf7a..a9d4691 100644 --- a/Web API Library/AppSrc/WebApi/cRestDataset.pkg +++ b/Web API Library/AppSrc/WebApi/cRestDataset.pkg @@ -17,7 +17,7 @@ Class cRestDataset is a cBaseRestDataset Property Integer piLimitResults 0 //Determines what index will be used to perform finds in the dataset - Property Integer piFindIndex 1 + Property Integer piFindIndex 1 End_Procedure @@ -556,7 +556,8 @@ Class cRestDataset is a cBaseRestDataset //Only clear the Main_DD if there is one. If (hoDD <> 0) Begin - Send Clear of hoDD + Send Clear of hoDD + Set psSQLFilter of hoDD to "" End End_Procedure diff --git a/Web API Library/AppSrc/WebApi/cRestField.pkg b/Web API Library/AppSrc/WebApi/cRestField.pkg index 8e16c31..f30f309 100644 --- a/Web API Library/AppSrc/WebApi/cRestField.pkg +++ b/Web API Library/AppSrc/WebApi/cRestField.pkg @@ -100,7 +100,12 @@ Class cRestField is a cObject Procedure AddConstrain String sConstrain String sFilterType Integer iFile iField + Integer eFieldType + Handle hoMainDD hoServer + String sSqlFilter + Get Main_DD to hoMainDD + Get Server to hoServer Get Data_File to iFile Get Data_Field to iField @@ -109,22 +114,59 @@ Class cRestField is a cObject Procedure_Return End - //Check what type of constrain we need to use - If (sFilterType = "GE") Begin - Vconstrain iFile iField GE sConstrain - End - Else If (sFilterType = "GT") Begin - Vconstrain iFile iField GT sConstrain - End - Else If (sFilterType = "LT") Begin - Vconstrain iFile iField LT sConstrain - End - Else If (sFilterType = "LE") Begin - Vconstrain iFile iField LE sConstrain + // Check if we can use sql filters. Only apply them to the main dd. + If (hoMainDD = hoServer and SupportsSQLFilters(hoMainDD)) Begin + Set pbUseDDSQLFilters of hoMainDD to True + Get psSQLFilter of hoMainDD to sSqlFilter + Get peFieldType to eFieldType + + If (eFieldType = DF_ASCII or eFieldType = DF_TEXT) Begin + Get SQLEscapedStr of hoMainDD sConstrain to sConstrain + Move ("'" + sConstrain + "'") to sConstrain + End + + //Check what type of constrain we need to use + If (sFilterType = "GE") Begin + Get SQLStrAppend of hoMainDD sSqlFilter (SQLStrFileFieldName(hoMainDD, iField) + " >= " + sConstrain) to sSqlFilter + End + Else If (sFilterType = "GT") Begin + Get SQLStrAppend of hoMainDD sSqlFilter (SQLStrFileFieldName(hoMainDD, iField) + " > " + sConstrain) to sSqlFilter + End + Else If (sFilterType = "LE") Begin + Get SQLStrAppend of hoMainDD sSqlFilter (SQLStrFileFieldName(hoMainDD, iField) + " <= " + sConstrain) to sSqlFilter + End + Else If (sFilterType = "LT") Begin + Get SQLStrAppend of hoMainDD sSqlFilter (SQLStrFileFieldName(hoMainDD, iField) + " < " + sConstrain) to sSqlFilter + End + Else If (sFilterType = "NE") Begin + Get SQLStrAppend of hoMainDD sSqlFilter (SQLStrFileFieldName(hoMainDD, iField) + " != " + sConstrain) to sSqlFilter + End + Else Begin + Get SQLStrAppend of hoMainDD sSqlFilter (SQLStrFileFieldName(hoMainDD, iField) + " = " + sConstrain) to sSqlFilter + End + + Set psSQLFilter of hoMainDD to sSqlFilter End Else Begin - Vconstrain iFile iField EQ sConstrain + // Apply constrain using vconstrain command + If (sFilterType = "GE") Begin + Vconstrain iFile iField GE sConstrain + End + Else If (sFilterType = "GT") Begin + Vconstrain iFile iField GT sConstrain + End + Else If (sFilterType = "LT") Begin + Vconstrain iFile iField LT sConstrain + End + Else If (sFilterType = "LE") Begin + Vconstrain iFile iField LE sConstrain + End + Else Begin + Vconstrain iFile iField EQ sConstrain + End End + + End_Procedure From 2746f4bd56d806aeb77e2c249c9bb48a89f23587 Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 4 Nov 2025 14:45:30 +0100 Subject: [PATCH 03/10] Turn find index for cRestChildCollection into a property Allow developers to determine what index they want to use for finds on a child table. --- Web API Library/AppSrc/WebApi/cRestChildCollection.pkg | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg index cc3f21a..193afd3 100644 --- a/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg +++ b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg @@ -17,6 +17,7 @@ Class cRestChildCollection is a cObject Property Boolean pbReadOnly True Property Integer piLimitResult 0 + Property Integer piFindIndex 1 End_Procedure @@ -35,19 +36,20 @@ Class cRestChildCollection is a cObject */ Procedure AppendToBody Handle hoResponseBody Handle hoIterator Handle hoNestedArray hoNestedObject hoChild hoServer - Integer iChildCount iIndex iLimit iRecordsRetrieved + Integer iChildCount iIndex iLimit iRecordsRetrieved iFindIndex String sNodeName Get Server to hoServer Get SchemaName to sNodeName Get Child_Count to iChildCount Get piLimitResult to iLimit + Get piFindIndex to iFindIndex //Create nested object Get CreateResponseBodyArray of hoIterator sNodeName to hoNestedArray //Start finding the child records - Send Find of hoServer FIRST_RECORD 1 + Send Find of hoServer FIRST_RECORD iFindIndex While ( (Found) and not(Err) and ((iLimit = 0) or (iLimit > 0 and iRecordsRetrieved < iLimit)) ) //When we find a record create a new nested object @@ -64,7 +66,7 @@ Class cRestChildCollection is a cObject Increment iRecordsRetrieved //Keep finding child records - Send Find of hoServer NEXT_RECORD 1 + Send Find of hoServer NEXT_RECORD iFindIndex Loop Send AppendNestedObject of hoIterator hoNestedArray hoResponseBody sNodeName From 568cecf72dc91e3c314a8d23f24f09b76ed03a2a Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 4 Nov 2025 15:43:19 +0100 Subject: [PATCH 04/10] Allow query params to be passed twice It is now possible to pass query parameters twice. This allows you to apply two types of constraints to the same column --- .../AppSrc/WebApi/cRestDataset.pkg | 76 +++++++++++++++---- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cRestDataset.pkg b/Web API Library/AppSrc/WebApi/cRestDataset.pkg index a9d4691..4cf97d0 100644 --- a/Web API Library/AppSrc/WebApi/cRestDataset.pkg +++ b/Web API Library/AppSrc/WebApi/cRestDataset.pkg @@ -1,5 +1,10 @@ Use WebApi\cBaseRestDataset.pkg +Struct tWebQueryParams + String sKey + String sValue +End_Struct + //This class exposes a resource in a endpoint //This should make use of the resource's data dictionary object Class cRestDataset is a cBaseRestDataset @@ -492,20 +497,57 @@ Class cRestDataset is a cBaseRestDataset End_Procedure + // Helper function that returns all query parameters based on psRequestQueryString + Function QueryParams Returns tWebQueryParams[] + String[] aParams aParam + String sQueryString + WString wsUnescape + Integer i iVoid + tWebQueryParams[] requestParams + + Get psRequestQueryString to sQueryString + + Get StrSplitToArray sQueryString "&" to aParams + For i from 0 to (SizeOfArray(aParams) - 1) + Get StrSplitToArray aParams[i] "=" to aParam + + If (SizeOfArray(aParam) > 1) Begin + Move aParam[0] to wsUnescape + If (SizeOfWString(wsUnescape) > 0) Begin + Move (UrlUnescapeW(AddressOf(wsUnescape), 0, 0, URL_UNESCAPE_INPLACE ior URL_UNESCAPE_AS_UTF8)) to iVoid + End + + // Key will be lowercased for case insensitive finds + Move (Lowercase(aParam[0])) to requestParams[i].sKey + Move aParam[1] to requestParams[i].sValue + + End + Loop + + // Sort it based on the key value + Move (SortArray(requestParams, False)) to requestParams + Function_Return requestParams + End_Function + //This procedure should be responsible for applying filters Procedure HandleQueryParams Handle[] hoExposedDataObjects Handle hoDD - String sQueryParam sFieldName sFilterType - Integer iIndex iFile iLimit iAmountOfDataObjects iPos + String sQueryParam sFilterType + Integer iIndex iFile iLimit iAmountOfDataObjects iPos iFoundIndex Boolean bFilterable + tWebQueryParams[] requestParams + tWebQueryParams dummySearch Get Main_DD to hoDD //Below are query params that can be applied regardless of what table it is. This includes params such as limit. - Get UrlParameter "Limit" to iLimit + Get QueryParams to requestParams - If (IsNumeric(Self, iLimit)) Begin - Set piLimitResults to iLimit + // Apply limit if there is one + Move "limit" to dummySearch.sKey + Move (SearchArray(dummySearch, requestParams)) to iFoundIndex + If (iFoundIndex <> -1) Begin + Set piLimitResults to requestParams[iFoundIndex].sValue End Move (SizeOfArray(hoExposedDataObjects)-1) to iAmountOfDataObjects @@ -515,13 +557,21 @@ Class cRestDataset is a cBaseRestDataset For iIndex from 0 to iAmountOfDataObjects Get IsFilterable of hoExposedDataObjects[iIndex] to bFilterable + // Reset this for each field we're walking through + Move 0 to iFoundIndex If bFilterable Begin - Get FieldName of hoExposedDataObjects[iIndex] to sFieldName - Get UrlParameter sFieldName to sQueryParam - - //If there is a query param apply the constrain - If (sQueryParam <> "") Begin + Get FieldName of hoExposedDataObjects[iIndex] to dummySearch.sKey + Move (Lowercase(dummySearch.sKey)) to dummySearch.sKey + + While (iFoundIndex <> -1) + Move (SearchArray(dummySearch, requestParams, iFoundIndex)) to iFoundIndex + + If (iFoundIndex = -1) ; + Break + + Move requestParams[iFoundIndex].sValue to sQueryParam + //Check what type of constrain we're dealing with. If nothing is specified we default to EQ If (Pos("(GE)", sQueryParam, 0) <> 0) Begin Move "GE" to sFilterType @@ -542,9 +592,9 @@ Class cRestDataset is a cBaseRestDataset //Add the constrain Send AddConstrain of hoExposedDataObjects[iIndex] sQueryParam sFilterType - End - End - + Move (iFoundIndex + 1) to iFoundIndex + Loop + End Loop End_Procedure From 2098ccdfb58cad8899f97d25be5e66deffeb007d Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Thu, 6 Nov 2025 09:49:44 +0100 Subject: [PATCH 05/10] Optimized json handles No longer create and destroy handles during a for loop. The handle will be created once and then be reused with InitializeJsonObject again. This should help improve performance --- .../AppSrc/WebApi/cBaseWebApiIterator.pkg | 17 +++++--- .../AppSrc/WebApi/cJSONIterator.pkg | 43 +++++++++++-------- .../AppSrc/WebApi/cOpenApiEndpoint.pkg | 2 +- .../AppSrc/WebApi/cRestChildCollection.pkg | 4 +- .../AppSrc/WebApi/cRestDataset.pkg | 16 ++++--- Web API Library/AppSrc/WebApi/cRestEntity.pkg | 2 +- .../AppSrc/WebApi/cWebApiLoginEndpoint.pkg | 2 +- .../AppSrc/WebApi/cXMLIterator.pkg | 30 ++++++------- 8 files changed, 64 insertions(+), 52 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cBaseWebApiIterator.pkg b/Web API Library/AppSrc/WebApi/cBaseWebApiIterator.pkg index 68f8bc3..138ace4 100644 --- a/Web API Library/AppSrc/WebApi/cBaseWebApiIterator.pkg +++ b/Web API Library/AppSrc/WebApi/cBaseWebApiIterator.pkg @@ -31,16 +31,21 @@ Class cBaseWebApiIterator is a cObject End_Procedure //This should create a array - Function CreateResponseBodyArray String sTableName Returns Handle + Procedure CreateResponseBodyArray String sTableName Handle ByRef hoBody //Should be augmented in iterators - Error DFERR_PROGRAM "Override this Function" - End_Function + Error DFERR_PROGRAM "Override this Procedure" + End_Procedure //This should create a object - Function CreateResponseBodyObject String sTableName Returns Handle + Procedure CreateResponseBodyObject String sTableName Handle ByRef hoBody //Should be augmented in iterators - Error DFERR_PROGRAM "Override this Function" - End_Function + Error DFERR_PROGRAM "Override this Procedure" + End_Procedure + + Procedure CleanupHandle Handle hoObject + // Destroy the object + Error DFERR_PROGRAM "Override this Procedure" + End_Procedure //Adds values to the response body Procedure ModifyResponseBody Handle hoResponseBody Variant vValue String sFieldName Integer eDataType diff --git a/Web API Library/AppSrc/WebApi/cJSONIterator.pkg b/Web API Library/AppSrc/WebApi/cJSONIterator.pkg index 767f469..fbe4f8d 100644 --- a/Web API Library/AppSrc/WebApi/cJSONIterator.pkg +++ b/Web API Library/AppSrc/WebApi/cJSONIterator.pkg @@ -23,27 +23,34 @@ Class cJSONIterator is a cBaseWebApiIterator Send OutputString sStringifiedJson End_Procedure - //This should create and return a JSON array - Function CreateResponseBodyArray String sTableName Returns Handle - Handle hoJsonArray + //This should create a JSON array + Procedure CreateResponseBodyArray String sTableName Handle ByRef hoBody - //Create the JSON array and initialize it - Get Create (RefClass(cJsonObject)) to hoJsonArray - Send InitializeJsonType of hoJsonArray jsonTypeArray + If (hoBody = 0) Begin + //Create the JSON array + Get Create (RefClass(cJsonObject)) to hoBody + End - Function_Return hoJsonArray - End_Function + // Initialize the array + Send InitializeJsonType of hoBody jsonTypeArray + End_Procedure - //This should create and return a JSON Object - Function CreateResponseBodyObject String sTableName Returns Handle - Handle hoJsonObject + //This should create a JSON Object + Procedure CreateResponseBodyObject String sTableName Handle ByRef hoBody - //Create the JSON Object and initialize it - Get Create (RefClass(cJsonObject)) to hoJsonObject - Send InitializeJsonType of hoJsonObject jsonTypeObject - - Function_Return hoJsonObject - End_Function + // If the handle is 0 create a json object. If its not 0 its already created we can just reinitialize it + If (hoBody = 0) Begin + //Create the JSON Object + Get Create (RefClass(cJsonObject)) to hoBody + End + + Send InitializeJsonType of hoBody jsonTypeObject + End_Procedure + + Procedure CleanupHandle Handle hoObject + // Destroy the object + Send Destroy of hoObject + End_Procedure //This should append to a response body Procedure ModifyResponseBody Handle hoResponseBody Variant vValue String sFieldName Integer eDataType @@ -66,12 +73,10 @@ Class cJSONIterator is a cBaseWebApiIterator //This should append a JSON object to a JSON array. Procedure AppendToResponseArray Handle hoNestedObject Handle hoResponseArray Send AddMember of hoResponseArray hoNestedObject - Send Destroy of hoNestedObject End_Procedure Procedure AppendNestedObject Handle hoNestedObject Handle hoResponseBody String sNestedObjectName Send SetMember of hoResponseBody sNestedObjectName hoNestedObject - Send Destroy of hoNestedObject End_Procedure //This should parse the request body into a data type understandable by DataFlex. diff --git a/Web API Library/AppSrc/WebApi/cOpenApiEndpoint.pkg b/Web API Library/AppSrc/WebApi/cOpenApiEndpoint.pkg index a2906b3..8ceccc4 100644 --- a/Web API Library/AppSrc/WebApi/cOpenApiEndpoint.pkg +++ b/Web API Library/AppSrc/WebApi/cOpenApiEndpoint.pkg @@ -18,7 +18,7 @@ Class cOpenApiEndpoint is a cBaseRestDataset Procedure OnHttpGet tWebApiCallContext ByRef webapicallcontext //Just parse to the response body. The OpenApiField will do the rest - Get CreateResponseBodyObject of webapicallcontext.hoIterator "OpenApi" to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator "OpenApi" (&webapicallcontext.hoResponseBody) Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator Move C_WEBAPI_OK to webapicallcontext.iStatusCode Move "OK" to webapicallcontext.sShortStatusMessage diff --git a/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg index 193afd3..a3c7bd6 100644 --- a/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg +++ b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg @@ -46,14 +46,14 @@ Class cRestChildCollection is a cObject Get piFindIndex to iFindIndex //Create nested object - Get CreateResponseBodyArray of hoIterator sNodeName to hoNestedArray + Send CreateResponseBodyArray of hoIterator sNodeName (&hoNestedArray) //Start finding the child records Send Find of hoServer FIRST_RECORD iFindIndex While ( (Found) and not(Err) and ((iLimit = 0) or (iLimit > 0 and iRecordsRetrieved < iLimit)) ) //When we find a record create a new nested object - Get CreateResponseBodyObject of hoIterator sNodeName to hoNestedObject + Send CreateResponseBodyObject of hoIterator sNodeName (&hoNestedObject) //Loop through this objects children and append their values to the nested object For iIndex from 0 to (iChildCount - 1) diff --git a/Web API Library/AppSrc/WebApi/cRestDataset.pkg b/Web API Library/AppSrc/WebApi/cRestDataset.pkg index 4cf97d0..540aecd 100644 --- a/Web API Library/AppSrc/WebApi/cRestDataset.pkg +++ b/Web API Library/AppSrc/WebApi/cRestDataset.pkg @@ -47,7 +47,7 @@ Class cRestDataset is a cBaseRestDataset Get RetrieveExposedDataFields to hoExposedDataObjects //Create the responsebody array - Get CreateResponseBodyArray of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyArray of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) Send Clear of hoDD @@ -66,7 +66,7 @@ Class cRestDataset is a cBaseRestDataset //Keep finding records untill there are no more records to be found While ( (Found) and not(Err) and ((iLimit = 0) or (iLimit > 0 and iRecordsRetrieved < iLimit)) ) //Each record is a seperate object - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to hoResponseObject + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&hoResponseObject) Send CurrentRecordToResponseBody hoResponseObject webapicallcontext.hoIterator @@ -78,6 +78,8 @@ Class cRestDataset is a cBaseRestDataset Send Find of hoDD NEXT_RECORD iFindIndex Loop + Send CleanupHandle of webapicallcontext.hoIterator hoResponseObject + //Set the status code Move C_WEBAPI_OK to webapicallcontext.iStatusCode Move "OK" to webapicallcontext.sShortStatusMessage @@ -123,7 +125,7 @@ Class cRestDataset is a cBaseRestDataset //If the record is found return it. If it is not found return a not found If (Found and not(Err)) Begin //Create the response body - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator Move C_WEBAPI_OK to webapicallcontext.iStatusCode Move "OK" to webapicallcontext.sShortStatusMessage @@ -206,7 +208,7 @@ Class cRestDataset is a cBaseRestDataset //If either the validation or the save fails we return a status code 400 bad request If (not(bErr)) Begin - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) Move C_WEBAPI_CREATED to webapicallcontext.iStatusCode Move "Created" to webapicallcontext.sShortStatusMessage Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator @@ -321,7 +323,7 @@ Class cRestDataset is a cBaseRestDataset Move "OK" to webapicallcontext.sShortStatusMessage //Formulate the response - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator End_Procedure @@ -429,7 +431,7 @@ Class cRestDataset is a cBaseRestDataset Move "OK" to webapicallcontext.sShortStatusMessage //Formulate the response - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator End_Procedure @@ -473,7 +475,7 @@ Class cRestDataset is a cBaseRestDataset //Get all the exposed fields Get RetrieveExposedDataFields to hoExposedDataObjects //Create a response object - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) //Populate the response Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator //Finally actually delete the resource diff --git a/Web API Library/AppSrc/WebApi/cRestEntity.pkg b/Web API Library/AppSrc/WebApi/cRestEntity.pkg index f318389..26d3936 100644 --- a/Web API Library/AppSrc/WebApi/cRestEntity.pkg +++ b/Web API Library/AppSrc/WebApi/cRestEntity.pkg @@ -30,7 +30,7 @@ Class cRestEntity is a cObject Get SchemaName to sNodeName //Create nested object - Get CreateResponseBodyObject of hoIterator sNodeName to hoNestedObject + Send CreateResponseBodyObject of hoIterator sNodeName (&hoNestedObject) //Get the child count. Get Child_Count to iChildCount diff --git a/Web API Library/AppSrc/WebApi/cWebApiLoginEndpoint.pkg b/Web API Library/AppSrc/WebApi/cWebApiLoginEndpoint.pkg index 9cd7d0f..88f753c 100644 --- a/Web API Library/AppSrc/WebApi/cWebApiLoginEndpoint.pkg +++ b/Web API Library/AppSrc/WebApi/cWebApiLoginEndpoint.pkg @@ -47,7 +47,7 @@ Class cWebApiLoginEndpoint is a cBaseRestDataset //Set status codes and create the body Move C_WEBAPI_OK to webapicallcontext.iStatusCode Move "OK" to webapicallcontext.sShortStatusMessage - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to webapicallcontext.hoResponseBody + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&webapicallcontext.hoResponseBody) Send CurrentRecordToResponseBody webapicallcontext.hoResponseBody webapicallcontext.hoIterator End Else Begin diff --git a/Web API Library/AppSrc/WebApi/cXMLIterator.pkg b/Web API Library/AppSrc/WebApi/cXMLIterator.pkg index 4bbeb60..777e62f 100644 --- a/Web API Library/AppSrc/WebApi/cXMLIterator.pkg +++ b/Web API Library/AppSrc/WebApi/cXMLIterator.pkg @@ -33,27 +33,25 @@ Class cXMLIterator is a cBaseWebApiIterator End_Procedure // Creates and returns an XML document with a root element named "Array" - Function CreateResponseBodyArray String sTableName Returns Handle - Handle hoXml hoElement + Procedure CreateResponseBodyArray String sTableName Handle ByRef hoBody + Handle hoXml Get phoXmlDocument to hoXml If (hoXml = 0) Begin Get Create (RefClass(cXMLDOMDocument)) to hoXml Set phoXmlDocument to hoXml - Get CreateDocumentElement of hoXml sTableName to hoElement + Get CreateDocumentElement of hoXml sTableName to hoBody End Else Begin - Get CreateElementNode of hoXml sTableName '' to hoElement + Get CreateElementNode of hoXml sTableName '' to hoBody End - Send AddAttribute of hoElement "type" "array" - - Function_Return hoElement - End_Function + Send AddAttribute of hoBody "type" "array" + End_Procedure // Creates and returns an XML document with a root element named "Object" - Function CreateResponseBodyObject String sTableName Returns Handle + Procedure CreateResponseBodyObject String sTableName Handle ByRef hoBody Handle hoXml hoElement Get phoXmlDocument to hoXml @@ -61,14 +59,16 @@ Class cXMLIterator is a cBaseWebApiIterator If (hoXml = 0) Begin Get Create (RefClass(cXMLDOMDocument)) to hoXml Set phoXmlDocument to hoXml - Get CreateDocumentElement of hoXml sTableName to hoElement + Get CreateDocumentElement of hoXml sTableName to hoBody End Else Begin - Get CreateElementNode of hoXml sTableName '' to hoElement - End - - Function_Return hoElement - End_Function + Get CreateElementNode of hoXml sTableName '' to hoBody + End + End_Procedure + + // Empty override + Procedure CleanupHandle Handle hoObject + End_Procedure // Adds an element to the XML response with specified data type and value Procedure ModifyResponseBody Handle hoResponseBody Variant vValue String sFieldName Integer eDataType From 8e59a3a1c0f00ccd8e689bfa4ace0af3ec4bcf0f Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 18 Nov 2025 11:42:36 +0100 Subject: [PATCH 06/10] Implement number field for openapi specification --- .../AppSrc/WebApi/cOpenApiSpecification.pkg | 22 ++++++++++++------- Web API Library/AppSrc/WebApi/cRestField.pkg | 10 ++++++++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg index 895415c..27a48c8 100644 --- a/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg +++ b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg @@ -963,7 +963,7 @@ Class cOpenApiSpecification is a cObject //This should add query parameters to the GET endpoint { Visibility = Private } Procedure ApplyQueryParams Handle hoVerbJson Handle hoEndpoint - Integer iIndex iEnumIndex eFieldType + Integer iIndex iEnumIndex eFieldType iPrecision Boolean bFilterable Handle hoParametersArrayJson hoParameterJson hoSchemaJson hoEnumsJson Handle hoKeyField @@ -993,6 +993,7 @@ Class cOpenApiSpecification is a cObject //Get all needed info from the field Get FieldName of hoExposedDataObjects[iIndex] to sFieldName Get FieldType of hoExposedDataObjects[iIndex] to eFieldType + Get piPrecision of hoExposedDataObjects[iIndex] to iPrecision Get FieldValidationTable of hoExposedDataObjects[iIndex] to avValidationTable //Set the json members @@ -1005,7 +1006,7 @@ Class cOpenApiSpecification is a cObject Move "" to sFieldFormat //Determine the field type and formatting - Send FieldTypeToOpenApiType eFieldType (&sFieldType) (&sFieldFormat) + Send FieldTypeToOpenApiType eFieldType (&sFieldType) (&sFieldFormat) iPrecision Send SetMemberValue of hoSchemaJson "type" jsonTypeString sFieldType @@ -1046,6 +1047,7 @@ Class cOpenApiSpecification is a cObject Handle hoKeyfield String sFieldName sFieldType sFieldFormat Integer eFieldType + Integer iPrecision Get RetrieveKeyField of hoEndpoint to hoKeyField //Just return if there is no keyfield @@ -1065,13 +1067,14 @@ Class cOpenApiSpecification is a cObject //Get the field name of the unique key Get FieldType of hoKeyField to eFieldType + Get piPrecision of hoKeyfield to iPrecision //Set the path parameter values Send SetMemberValue of hoParameterJson "in" jsonTypeString "path" Send SetMemberValue of hoParameterJson "name" jsonTypeString "Id" Send SetMemberValue of hoParameterJson "required" jsonTypeBoolean True - Send FieldTypeToOpenApiType eFieldType (&sFieldType) (&sFieldFormat) + Send FieldTypeToOpenApiType eFieldType (&sFieldType) (&sFieldFormat) iPrecision Send SetMemberValue of hoSchemaJson "type" jsonTypeString sFieldType @@ -1088,7 +1091,7 @@ Class cOpenApiSpecification is a cObject //This will parse the fields defined inside of a dataset { Visibility = Private } Procedure FieldToOpenApi Handle hoField Handle hoPropertiesJson - Integer eFieldType iChildCount iIndex + Integer eFieldType iChildCount iIndex iPrecision String sFieldName sFieldType sFieldHelp sNestedSchemaName sFieldFormat Handle hoNestedPropertiesObject hoNestedSchema hoNestedSchemaProperties hoForeignSchemaChild hoItems Handle[] hoNestedFields @@ -1157,6 +1160,7 @@ Class cOpenApiSpecification is a cObject Get FieldName of hoField to sFieldName Get FieldType of hoField to eFieldType Get FieldHelp of hoField to sFieldHelp + Get piPrecision of hoField to iPrecision Get pbReadOnly of hoField to bReadOnly Get pbWriteOnly of hoField to bWriteOnly Get FieldValidationTable of hoField to avValidationTable @@ -1165,7 +1169,7 @@ Class cOpenApiSpecification is a cObject Send InitializeJsonType of hoNestedPropertiesObject jsonTypeObject //Determine the type for the OpenApi spec - Send FieldTypeToOpenApiType eFieldType (&sFieldType) (&sFieldFormat) + Send FieldTypeToOpenApiType eFieldType (&sFieldType) (&sFieldFormat) iPrecision Send SetMemberValue of hoNestedPropertiesObject "type" jsonTypeString sFieldType @@ -1282,7 +1286,7 @@ Class cOpenApiSpecification is a cObject //Helper function to map df field types to the ones used in the OpenApi specification { Visibility = Private } - Procedure FieldTypeToOpenApiType Integer eDataflexFieldType String ByRef sFieldType String ByRef sFieldFormat + Procedure FieldTypeToOpenApiType Integer eDataflexFieldType String ByRef sFieldType String ByRef sFieldFormat Integer iPrecision If (eDataflexFieldType = DF_ASCII or eDataflexFieldType = DF_TEXT) Begin Move "string" to sFieldType @@ -1299,10 +1303,12 @@ Class cOpenApiSpecification is a cObject Move "string" to sFieldType Move "binary" to sFieldFormat End - Else If (eDataflexFieldType = DF_BCD) Begin + Else If (eDataflexFieldType = DF_BCD and iPrecision = 0) Begin Move "integer" to sFieldType End - + Else If (eDataflexFieldType = DF_BCD) Begin + Move "number" to sFieldType + End End_Procedure { Visibility = Private } diff --git a/Web API Library/AppSrc/WebApi/cRestField.pkg b/Web API Library/AppSrc/WebApi/cRestField.pkg index f30f309..23371b3 100644 --- a/Web API Library/AppSrc/WebApi/cRestField.pkg +++ b/Web API Library/AppSrc/WebApi/cRestField.pkg @@ -23,6 +23,8 @@ Class cRestField is a cObject //Needed for non data aware fields Property Integer peFieldType DF_ASCII + //Precision for numeric fields + Property Integer piPrecision 0 //Only used for fields with no data binding Property String psExampleValue "" @@ -337,7 +339,7 @@ Class cRestField is a cObject // This way we only need to query database APIs once instead of for every single find. Procedure AfterAttachDDO String sFieldName - Integer iFile iField + Integer iFile iField iPrecision Integer eFieldType Handle hoDD @@ -358,6 +360,12 @@ Class cRestField is a cObject Get_Attribute DF_FIELD_TYPE of iFile iField to eFieldType Set peFieldType to eFieldType + // If we're dealing with a numeric type check the precision to check if it should show as integer or number. + If (eFieldType = DF_BCD) Begin + Get_Attribute DF_FIELD_PRECISION of iFile iField to iPrecision + Set piPrecision to iPrecision + End + End_Procedure End_Class \ No newline at end of file From 3dd3464be65be941f997c460959ba40fe34ab35f Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 18 Nov 2025 13:14:47 +0100 Subject: [PATCH 07/10] Log errors to event log --- .../AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg b/Web API Library/AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg index d343139..effbba7 100644 --- a/Web API Library/AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg +++ b/Web API Library/AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg @@ -20,12 +20,15 @@ Class cWebApiErrorHandler_Mixin is a Mixin Get pbDebugMode to bDebugMode //If a error is reported we should remember that it happened. Set pbUnexpectedError to True + CallStackDump sCallstack + + If (ghInetSession <> 0) Begin + Send ReportErrorEvent of ghInetSession ErrNum sCallstack Err_Line 0 0 + End If (bDebugMode) Begin Get pasErrorCallstack to asCallstack - CallStackDump sCallstack - Move sCallstack to asCallstack[-1] Set pasErrorCallstack to asCallstack From 2d21297b4085adce295e8a15c91c0732c5ad1d2a Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 18 Nov 2025 13:25:29 +0100 Subject: [PATCH 08/10] Reverse proxy support for servers --- .../AppSrc/WebApi/cOpenApiSpecification.pkg | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg index 27a48c8..b17295f 100644 --- a/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg +++ b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg @@ -66,8 +66,9 @@ Class cOpenApiSpecification is a cObject { Visibility = Private } Procedure GenerateServersInfo Handle hoOpenApiSpecJson Handle hoServersJson hoServerJson hoHttpApi - String sApiPath sRouterPath sDescription sApiRoot sServerName sSecureString + String sApiPath sRouterPath sDescription sApiRoot sServerName sSecureString sForwardedProto Boolean bSecure + Integer iServerPort Get GetWebApiObject to hoHttpApi Get psPath of hoHttpApi to sApiPath @@ -77,15 +78,25 @@ Class cOpenApiSpecification is a cObject If (sApiRoot = "") Begin Get ServerVariable of ghoWebServiceDispatcher "SERVER_NAME" to sServerName Get ServerVariable of ghoWebServiceDispatcher "URL" to sApiRoot - Get ServerVariable of ghoWebServiceDispatcher "SERVER_PORT_SECURE" to bSecure + Get ServerVariable of ghoWebServiceDispatcher "HTTP_X_FORWARDED_PROTO" to sForwardedProto - //Add http or https based on the SERVER_PORT_SECURE setting - If bSecure Begin - Move "https://" to sSecureString + // Check for reverse proxy + If (sForwardedProto <> "") Begin + Move (sForwardedProto + "://") to sSecureString End Else Begin - Move "http://" to sSecureString + Get ServerVariable of ghoWebServiceDispatcher "SERVER_PORT" to iServerPort + // 443 is the default secure port + Move (iServerPort = 443) to bSecure + //Add http or https based on the SERVER_PORT setting + If bSecure Begin + Move "https://" to sSecureString + End + Else Begin + Move "http://" to sSecureString + End End + //The url contains /OpenApi since its part of the requesting url, we can take it out of the url. Move (Replace("/OpenApi", sApiRoot, "")) to sApiRoot Move (sSecureString + sServerName + sApiRoot) to sApiRoot From 24de861440246aea077f206447bee5e14fe788de Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 18 Nov 2025 13:57:27 +0100 Subject: [PATCH 09/10] Implement pagination Implemented pagination for the cRestDataset. It is now possible to pass a offset in the query parameters. Since data dictionaries don't actually support an offset it still has to find the rows, it just skips over the first few depending on the offset. --- .../AppSrc/WebApi/cRestDataset.pkg | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cRestDataset.pkg b/Web API Library/AppSrc/WebApi/cRestDataset.pkg index 540aecd..c4c0ed3 100644 --- a/Web API Library/AppSrc/WebApi/cRestDataset.pkg +++ b/Web API Library/AppSrc/WebApi/cRestDataset.pkg @@ -18,11 +18,17 @@ Class cRestDataset is a cBaseRestDataset // This setting determines if it should use the path as table name Property Boolean pbUsePathAsTableName False - //This determines how many records we return during a get all + // This determines how many records we return during a get all Property Integer piLimitResults 0 + // This determines how many records to skip during a GET request. + Property Integer piOffset 0 //Determines what index will be used to perform finds in the dataset - Property Integer piFindIndex 1 + Property Integer piFindIndex 1 + + // Grabs the old psSQLFilter and restores it + { Visibility=Private } + Property String psOldSQLfilter "" End_Procedure @@ -38,7 +44,7 @@ Class cRestDataset is a cBaseRestDataset String sFieldValue sFieldName Handle hoDD hoChild hoResponseObject Handle[] hoExposedDataObjects - Integer iIndex iLimit iRecordsRetrieved iFindIndex + Integer iIndex iLimit iRecordsRetrieved iFindIndex iOffset //Get the Main_DD Get Main_DD to hoDD @@ -54,8 +60,9 @@ Class cRestDataset is a cBaseRestDataset //Map the query params and set the constrains Send HandleQueryParams hoExposedDataObjects - //Should always be called after HandleQueryParams. Because the property is set there based on the query params. + //Should always be called after HandleQueryParams. Because these property are set there based on the query params. Get piLimitResults to iLimit + Get piOffset to iOffset //Get the index we need to find the record Get piFindIndex to iFindIndex @@ -64,7 +71,13 @@ Class cRestDataset is a cBaseRestDataset Send Find of hoDD FIRST_RECORD iFindIndex //Keep finding records untill there are no more records to be found - While ( (Found) and not(Err) and ((iLimit = 0) or (iLimit > 0 and iRecordsRetrieved < iLimit)) ) + While ( (Found) and not(Err) and ((iLimit = 0) or (iLimit > 0 and iRecordsRetrieved < iLimit)) ) + + // Since data dictionaries don't really support an offset, we still need to find the records, just don't process them. + While (iOffset <> 0) + Send Find of hoDD NEXT_RECORD iFindIndex + Decrement iOffset + Loop //Each record is a seperate object Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&hoResponseObject) @@ -78,7 +91,9 @@ Class cRestDataset is a cBaseRestDataset Send Find of hoDD NEXT_RECORD iFindIndex Loop - Send CleanupHandle of webapicallcontext.hoIterator hoResponseObject + If (hoResponseObject <> 0) Begin + Send CleanupHandle of webapicallcontext.hoIterator hoResponseObject + End //Set the status code Move C_WEBAPI_OK to webapicallcontext.iStatusCode @@ -534,8 +549,8 @@ Class cRestDataset is a cBaseRestDataset //This procedure should be responsible for applying filters Procedure HandleQueryParams Handle[] hoExposedDataObjects Handle hoDD - String sQueryParam sFilterType - Integer iIndex iFile iLimit iAmountOfDataObjects iPos iFoundIndex + String sQueryParam sFilterType sOldFilter + Integer iIndex iFile iLimit iAmountOfDataObjects iPos iFoundIndex iOffset Boolean bFilterable tWebQueryParams[] requestParams tWebQueryParams dummySearch @@ -552,8 +567,20 @@ Class cRestDataset is a cBaseRestDataset Set piLimitResults to requestParams[iFoundIndex].sValue End + Move "offset" to dummySearch.sKey + Move (SearchArray(dummySearch, requestParams)) to iFoundIndex + If (iFoundIndex <> -1) Begin + Set piOffset to requestParams[iFoundIndex].sValue + End + Move (SizeOfArray(hoExposedDataObjects)-1) to iAmountOfDataObjects + // Remember the old sql filter + If (SupportsSQLFilters(hoDD)) Begin + Get psSQLFilter of hoDD to sOldFilter + Set psOldSQLfilter to sOldFilter + End + //Reset all the current constrains to make room for the new ones Send Rebuild_Constraints of hoDD @@ -603,13 +630,18 @@ Class cRestDataset is a cBaseRestDataset Procedure ResetState Handle hoDD + String sOldFilter Get Main_DD to hoDD + Set piLimitResults to 0 + Set piOffset to 0 //Only clear the Main_DD if there is one. If (hoDD <> 0) Begin Send Clear of hoDD - Set psSQLFilter of hoDD to "" + Get psOldSQLfilter to sOldFilter + // Return the old filter + Set psSQLFilter of hoDD to sOldFilter End End_Procedure From 2630ec5698a0e2d32c968011b5c30ccebf178164 Mon Sep 17 00:00:00 2001 From: Niels de Boer Date: Tue, 18 Nov 2025 14:52:21 +0100 Subject: [PATCH 10/10] GET requests should show that they return an array and not a single object --- .../AppSrc/WebApi/cOpenApiSpecification.pkg | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg index b17295f..1ce8de2 100644 --- a/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg +++ b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg @@ -239,7 +239,7 @@ Class cOpenApiSpecification is a cObject { Visibility = Private } Procedure ParseVerb String sCurrentVerb Handle hoEndpoint Handle hoPathsJson - Handle hoVerbJson hoResponsesJson hostatusCodeJson hoContentJson hoContentTypeJson hoSchemaJson hoTagsJson hoRequestBody hoEndpointJson + Handle hoVerbJson hoResponsesJson hostatusCodeJson hoContentJson hoContentTypeJson hoSchemaJson hoTagsJson hoRequestBody hoEndpointJson hoItemsJson String[] asIteratorTypes String sEndpointName sEndpointTableName sEndpointFullPath sTagName Integer iIteratorIndex @@ -324,7 +324,24 @@ Class cOpenApiSpecification is a cObject Send ParseErrorResponse sCurrentVerb hoResponsesJson Send SetMemberValue of hoStatusCodeJson "description" jsonTypeString (SFormat("A %1", sEndpointName)) - Send SetMemberValue of hoSchemaJson "$ref" jsonTypeString (SFormat("#/components/schemas/%1", sEndpointTableName)) + + // GET requests should return an array. + If (sCurrentVerb = C_WEBAPI_GET) Begin + Get Create (RefClass(cJsonObject)) to hoItemsJson + Send InitializeJsonType of hoItemsJson jsonTypeObject + + // schema should become type array + Send SetMemberValue of hoSchemaJson "type" jsonTypeString "array" + // $ref should be appended to the items object + Send SetMemberValue of hoItemsJson "$ref" jsonTypeString (SFormat("#/components/schemas/%1", sEndpointTableName)) + Send SetMember of hoSchemaJson "items" hoItemsJson + + Send Destroy of hoItemsJson + End + Else Begin + Send SetMemberValue of hoSchemaJson "$ref" jsonTypeString (SFormat("#/components/schemas/%1", sEndpointTableName)) + End + Send AddMemberValue of hoTagsJson jsonTypeString sTagName Send SetMember of hoEndpointJson (Lowercase(sCurrentVerb)) hoVerbJson