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/cOpenApiSpecification.pkg b/Web API Library/AppSrc/WebApi/cOpenApiSpecification.pkg index 895415c..1ce8de2 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 @@ -228,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 @@ -313,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 @@ -963,7 +991,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 +1021,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 +1034,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 +1075,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 +1095,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 +1119,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 +1188,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 +1197,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 +1314,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 +1331,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/cRestChildCollection.pkg b/Web API Library/AppSrc/WebApi/cRestChildCollection.pkg index a991899..a3c7bd6 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,23 +36,24 @@ 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 + Send CreateResponseBodyArray of hoIterator sNodeName (&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 - 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) @@ -64,31 +66,14 @@ 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 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 +88,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/cRestDataset.pkg b/Web API Library/AppSrc/WebApi/cRestDataset.pkg index e53bf7a..c4c0ed3 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 @@ -13,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 @@ -33,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 @@ -42,15 +53,16 @@ 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 //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 @@ -59,9 +71,15 @@ 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 - Get CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName to hoResponseObject + Send CreateResponseBodyObject of webapicallcontext.hoIterator webapicallcontext.sMainTableName (&hoResponseObject) Send CurrentRecordToResponseBody hoResponseObject webapicallcontext.hoIterator @@ -73,6 +91,10 @@ Class cRestDataset is a cBaseRestDataset Send Find of hoDD NEXT_RECORD iFindIndex Loop + If (hoResponseObject <> 0) Begin + Send CleanupHandle of webapicallcontext.hoIterator hoResponseObject + End + //Set the status code Move C_WEBAPI_OK to webapicallcontext.iStatusCode Move "OK" to webapicallcontext.sShortStatusMessage @@ -118,7 +140,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 @@ -201,7 +223,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 @@ -316,7 +338,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 @@ -424,7 +446,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 @@ -468,7 +490,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 @@ -492,36 +514,93 @@ 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 sOldFilter + Integer iIndex iFile iLimit iAmountOfDataObjects iPos iFoundIndex iOffset 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 + + // 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 - If (IsNumeric(Self, iLimit)) Begin - Set piLimitResults to iLimit + 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 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,21 +621,27 @@ 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 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 + Send Clear of hoDD + Get psOldSQLfilter to sOldFilter + // Return the old filter + Set psSQLFilter of hoDD to sOldFilter End End_Procedure diff --git a/Web API Library/AppSrc/WebApi/cRestEntity.pkg b/Web API Library/AppSrc/WebApi/cRestEntity.pkg index 7a86234..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 @@ -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..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 "" @@ -100,7 +102,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 +116,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 @@ -171,8 +215,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 +223,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 +334,38 @@ 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 iPrecision + 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 + + // 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 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 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 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