diff --git a/.gitignore b/.gitignore
index 97d59a0..f881bc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
# AppHtml
**/apphtml/global.asa
+**/apphtml/web.config
+**/apphtml/cssthemes/
+**/apphtml/dfengine/
# AppSrc
**/appsrc/**/*.prn
@@ -14,6 +17,7 @@
**/appsrc/**/*.pkd
**/appsrc/**/*.prn
**/appsrc/**/*.prp
+**/appsrc/**/*.cfg
**/AppSrc/config/classlist.xml
@@ -23,6 +27,10 @@
**/data/**/*.bad
**/data/**/*.hdr
**/data/**/*.cch
+**/data/**/*.dat
+**/data/**/*.k1
+**/data/**/*.k2
+**/data/**/*.k3
# DDSrc
**/ddsrc/**/*.def
diff --git a/README.md b/README.md
index 656d6af..8605dca 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ Swagger UI is capable of describing the following aspects of your REST api:
## Overview
- The [Sample](Web%20API%20Sample) folder contains a sample workspace which contains a REST api built with the library.
- The [Library](Web%20API%20Library) folder contains the actual library that should be attached to your workspace.
+- The [AppSrc\config](Web%20API%20Library\AppSrc\config) folder contains DataFlex Studio Create New templates for common Web API Framework objects.
- The [WebApi](Web%20API%20Library\AppHtml\WebApi) folder contains the javascript files needed to render the Swagger UI component in the browser. This should be copied to your application's AppHtml folder.
- The [Help](Web%20API%20Library\Help) folder contains a markdown file that serves as documentation for the WebApi framework.
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..da68a93 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,11 @@ 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
//Determines what index will be used to perform finds in the dataset
- Property Integer piFindIndex 1
+ Property Integer piFindIndex 1
End_Procedure
@@ -25,7 +30,7 @@ Class cRestDataset is a cBaseRestDataset
{ Visibility = Private }
Procedure OnHttpRequest tWebApiCallContext ByRef webapicallcontext
Forward Send OnHttpRequest (&webapicallcontext)
- Send ResetState
+ Send ResetState (&webapicallcontext)
End_Procedure
{ MethodType=Event }
@@ -33,7 +38,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 +47,17 @@ 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
+ Get piLimitResults to webapicallcontext.iLimit
//Map the query params and set the constrains
- Send HandleQueryParams hoExposedDataObjects
+ Send HandleQueryParams (&webapicallcontext) hoExposedDataObjects
- //Should always be called after HandleQueryParams. Because the property is set there based on the query params.
- Get piLimitResults to iLimit
+ //Should always be called after HandleQueryParams. Because these property are set there based on the query params.
+ Move webapicallcontext.iLimit to iLimit
+ Move webapicallcontext.iOffset to iOffset
//Get the index we need to find the record
Get piFindIndex to iFindIndex
@@ -59,9 +66,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 +86,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 +135,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 +218,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 +333,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 +441,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 +485,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 +509,92 @@ 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
+ Procedure HandleQueryParams tWebApiCallContext ByRef webapicallcontext Handle[] hoExposedDataObjects
Handle hoDD
- String sQueryParam sFieldName sFilterType
- Integer iIndex iFile iLimit iAmountOfDataObjects iPos
+ String sQueryParam sFilterType
+ 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
- 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
+ Move requestParams[iFoundIndex].sValue to webapicallcontext.iLimit
+ End
+
+ Move "offset" to dummySearch.sKey
+ Move (SearchArray(dummySearch, requestParams)) to iFoundIndex
+ If (iFoundIndex <> -1) Begin
+ Move requestParams[iFoundIndex].sValue to webapicallcontext.iOffset
End
Move (SizeOfArray(hoExposedDataObjects)-1) to iAmountOfDataObjects
+ // Remember the old sql filter
+ If (SupportsSQLFilters(hoDD)) Begin
+ Get psSQLFilter of hoDD to webapicallcontext.sOldSQLFilter
+ 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 +615,25 @@ 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
+ Procedure ResetState tWebApiCallContext ByRef webapicallcontext
Handle hoDD
Get Main_DD to hoDD
//Only clear the Main_DD if there is one.
If (hoDD <> 0) Begin
- Send Clear of hoDD
+ Send Clear of hoDD
+ If (SupportsSQLFilters(hoDD)) Begin
+ // Return the old filter
+ Set psSQLFilter of hoDD to webapicallcontext.sOldSQLFilter
+ End
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 b23d0c1..24a18c3 100644
--- a/Web API Library/AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg
+++ b/Web API Library/AppSrc/WebApi/cWebApiErrorHandler_Mixin.pkg
@@ -18,7 +18,6 @@ Class cWebApiErrorHandler_Mixin is a Mixin
String[] asCallstack
Get pbDebugMode to bDebugMode
-
//If a error is reported we should remember that it happened.
Set pbUnexpectedError to True
Send Error_Report of ghoErrorHandler ErrNum Err_Line sErrMsg
@@ -27,10 +26,15 @@ Class cWebApiErrorHandler_Mixin is a Mixin
If (IsDebuggerPresent() or bDebugMode) Begin
Get pasErrorCallstack to asCallstack
- CallStackDump sCallstack
Move sCallstack to asCallstack[-1]
Set pasErrorCallstack to asCallstack
+
+ //If we run in debug mode pop up the error window
+ If (IsDebuggerPresent()) Begin
+ Get ErrorDescription ErrNum sErrMsg to sCaption
+ ErrorDisplay ERR_Line sErrMsg sCaption C_$OK C_$Copy
+ End
End
End_Procedure
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 Library/AppSrc/WebApi/tWebApiCallContext.pkg b/Web API Library/AppSrc/WebApi/tWebApiCallContext.pkg
index 3a7052a..03912cd 100644
--- a/Web API Library/AppSrc/WebApi/tWebApiCallContext.pkg
+++ b/Web API Library/AppSrc/WebApi/tWebApiCallContext.pkg
@@ -38,6 +38,9 @@ Struct tWebApiCallContext
//Useful information in the current request
String sMainTableName
+ Integer iLimit
+ Integer iOffset
+ String sOldSQLFilter
//Status codes and potential error messages
Integer iStatusCode
diff --git a/Web API Library/AppSrc/config/RestDataset.tpl b/Web API Library/AppSrc/config/RestDataset.tpl
new file mode 100644
index 0000000..0b0e1e3
--- /dev/null
+++ b/Web API Library/AppSrc/config/RestDataset.tpl
@@ -0,0 +1,8 @@
+Use WebApi\cRestDataset.pkg
+
+Object oRestDataset is a cRestDataset
+ Set psPath to "ResourceName"
+
+ // Add your cRestField objects here
+
+End_Object
diff --git a/Web API Library/AppSrc/config/Templates.xml b/Web API Library/AppSrc/config/Templates.xml
new file mode 100644
index 0000000..86b2139
--- /dev/null
+++ b/Web API Library/AppSrc/config/Templates.xml
@@ -0,0 +1,34 @@
+
+
+ 58
+ REST Dataset
+ RestDataset.tpl
+
+ Creates a cRestDataset object
+ Creates a minimal cRestDataset object with only a psPath.
+
+
+ 51
+ Web API
+ WebApi.tpl
+
+ Creates a cWebApi object
+ Creates a minimal cWebApi object with a psPath and a default cJSONIterator.
+
+
+ 58
+ Web API Modifier
+ WebApiModifier.tpl
+
+ Creates a cWebApiModifier object
+ Creates a cWebApiModifier object with empty OnPreRequest and OnPostRequest events.
+
+
+ 58
+ Web API Auth Modifier
+ WebApiAuthModifier.tpl
+
+ Creates a cWebApiAuthModifier object
+ Creates a cWebApiAuthModifier object with empty OnAuth and OnDefineAuthRules procedures.
+
+
diff --git a/Web API Library/AppSrc/config/WebApi.tpl b/Web API Library/AppSrc/config/WebApi.tpl
new file mode 100644
index 0000000..297c005
--- /dev/null
+++ b/Web API Library/AppSrc/config/WebApi.tpl
@@ -0,0 +1,11 @@
+Use WebApi\cWebApi.pkg
+Use WebApi\cJSONIterator.pkg
+
+Object oWebApi is a cWebApi
+ Set psPath to "Api"
+
+ Send AddIterator (RefClass(cJSONIterator)) "application/json"
+
+ // Add your datasets and routers here
+
+End_Object
diff --git a/Web API Library/AppSrc/config/WebApiAuthModifier.tpl b/Web API Library/AppSrc/config/WebApiAuthModifier.tpl
new file mode 100644
index 0000000..676ea25
--- /dev/null
+++ b/Web API Library/AppSrc/config/WebApiAuthModifier.tpl
@@ -0,0 +1,10 @@
+Use WebApi\cWebApiAuthModifier.pkg
+
+Object oWebApiAuthModifier is a cWebApiAuthModifier
+
+ Procedure OnAuth tWebApiCallContext ByRef webapicallcontext
+ End_Procedure
+
+ Procedure OnDefineAuthRules Variant ByRef vSecurityStruct
+ End_Procedure
+End_Object
diff --git a/Web API Library/AppSrc/config/WebApiModifier.tpl b/Web API Library/AppSrc/config/WebApiModifier.tpl
new file mode 100644
index 0000000..6c522c2
--- /dev/null
+++ b/Web API Library/AppSrc/config/WebApiModifier.tpl
@@ -0,0 +1,10 @@
+Use WebApi\cWebApiModifier.pkg
+
+Object oWebApiModifier is a cWebApiModifier
+
+ Procedure OnPreRequest tWebApiCallContext ByRef webapicallcontext
+ End_Procedure
+
+ Procedure OnPostRequest tWebApiCallContext ByRef webapicallcontext
+ End_Procedure
+End_Object
diff --git a/Web API Library/AppSrc/config/classlist.xml b/Web API Library/AppSrc/config/classlist.xml
new file mode 100644
index 0000000..87198c3
--- /dev/null
+++ b/Web API Library/AppSrc/config/classlist.xml
@@ -0,0 +1,64 @@
+
+
+ 2026-06-03T00:00:00
+ 2
+
+
+
+ cWebApi
+
+ WebApi\cWebApi.pkg
+ Web API
+
+
+
+ cWebApiRouter
+
+ WebApi\cWebApiRouter.pkg
+ Web API
+
+
+
+ cRestDataset
+
+ WebApi\cRestDataset.pkg
+ Web API
+
+
+
+ cRestField
+ cWebForm.ico
+ WebApi\cRestField.pkg
+ Web API
+
+
+
+ cRestEntity
+
+ WebApi\cRestEntity.pkg
+ Web API
+
+
+
+ cRestChildCollection
+
+ WebApi\cRestChildCollection.pkg
+ Web API
+
+
+
+ cWebApiModifier
+
+ WebApi\cWebApiModifier.pkg
+ Web API
+
+
+
+ cWebApiAuthModifier
+
+ WebApi\cWebApiAuthModifier.pkg
+ Web API
+
+
+
+
diff --git a/Web API Sample/AppSrc/MyRestAPI.wo b/Web API Sample/AppSrc/MyRestAPI.wo
index 684de90..be10c77 100644
--- a/Web API Sample/AppSrc/MyRestAPI.wo
+++ b/Web API Sample/AppSrc/MyRestAPI.wo
@@ -18,7 +18,7 @@ Object oRestFramework is a cWebApi
Use ApiCallLogger.pkg
- Object oVersion1Router is a cWebApiRouter
+ Object oApiKeyRouter is a cWebApiRouter
Set psPath to "ApiKey"
Use LoginEndpoint.pkg
@@ -31,7 +31,7 @@ Object oRestFramework is a cWebApi
End_Object
- Object oVersion2Router is a cWebApiRouter
+ Object oJWTRouter is a cWebApiRouter
Set psPath to "JWT"
Object oJwtLogin is a cJWTLoginEndpoint
@@ -52,7 +52,7 @@ Object oRestFramework is a cWebApi
End_Object
- Object oVersion2Router is a cWebApiRouter
+ Object oBasicAuthRouter is a cWebApiRouter
Set psPath to "BasicAuth"
Object oBasicAuth is a cBasicAuth
diff --git a/Web API Sample/AppSrc/cBasicAuth.pkg b/Web API Sample/AppSrc/cBasicAuth.pkg
index 827c5ca..dbcb5fb 100644
--- a/Web API Sample/AppSrc/cBasicAuth.pkg
+++ b/Web API Sample/AppSrc/cBasicAuth.pkg
@@ -1,4 +1,5 @@
Use WebApi\cWebApiAuthModifier.pkg
+Use cWebAppUserDataDictionary.dd
Struct basicAuth
String type
@@ -18,14 +19,17 @@ Class cBasicAuth is a cWebApiAuthModifier
Property Integer piEditRights 1
Property Integer piDeleteRights 1
- End_Procedure
+ Object oWebAppUserDD is a cWebAppUserDataDictionary
+ End_Object
+
+ End_Procedure
Procedure OnAuth tWebApiCallContext ByRef webapicallcontext
- String sBase64EncodedAuth sDecodedString
+ String sBase64EncodedAuth sDecodedString sUserPassword
String[] asAuthorizationParts asDecodedUsernamePassword
Integer iLength iVoid iArraySize
Pointer pBaseDecodedString
- Boolean bOk bSessionOk
+ Boolean bOk bSessionOk bUpgrade
If (ghoWebSessionManager = 0) Begin
Error DFERR_PROGRAM "No session manager found. BasicAuth implementation uses the session manager to validate users."
@@ -54,10 +58,21 @@ Class cBasicAuth is a cWebApiAuthModifier
//First element will be the username and the second element will be the password
Get StrSplitToArray sDecodedString ":" to asDecodedUsernamePassword
- //Create a new session for the user that is trying to login
- Get RecreateSession of ghoWebSessionManager to bSessionOk
- //Attempt to log in the user
- Get UserLogin of ghoWebSessionManager asDecodedUsernamePassword[0] asDecodedUsernamePassword[1] to bOk
+ // Ensure we start with an empty state
+ Send Clear of oWebAppUserDD
+ Move asDecodedUsernamePassword[0] to WebAppUser.LoginName
+ Send Find of oWebAppUserDD EQ 1
+
+ If (Found) Begin
+ Get Field_Current_Value of oWebAppUserDD Field WebAppUser.Password to sUserPassword
+ Get VerifyPasswordHash of ghoWebSessionManager (Trim(sUserPassword)) (Trim(asDecodedUsernamePassword[1])) (&bUpgrade) to bOk
+ End
+ Else Begin
+ Move False to bOk
+ End
+
+ // Always clear when we're done.
+ Send Clear of oWebAppUserDD
If (not(bOk)) Begin
Move True to webapicallcontext.bErr
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