Add HTTP status code return on APIs with OpenEdge
Lucas Bicalho
Business Intelligence Developer | BI development in telecom | Statistics | Software Developer | Progress OpenEdge | Java
In the previous article, I talked about API pagination with OpenEdge on PASOE. Today, I'll use that same API to add HTTP status code in the return. If you didn't read the previous article, I strongly encourage you to do so, then we'll be in the same page here.
Sections of this article:
1. Main HTTP status code
Here I'll pass briefly through the main codes used, note that this is not a complete list! And thanks to ChatGPT, here goes a summary:
2xx Success:
3xx Redirection:
4xx Client Errors:
5xx Server Errors:
To get details about it, I recommend you to take a look here.
2. Code (the .p file)
I duplicated the previous item.p into an item2.p file.
First, I changed the property useReturnValue to "true" in the REST main annotation. Setting it to true, allow us to use the retVal interface parameter to return our custom HTTP status code on the REST Resource URI Editor:
@openapi.openedge.export FILE(type="REST", executionMode="external", useReturnValue="true", writeDataSetBeforeImage="false")
/*------------------------------------------------------------------------
? ? File? ? ? ? : item2.p
? ? Purpose? ? ?: Improvement of item.p program, to use HTTP Status Code
? ? Syntax? ? ? :
? ? Description :?
? ? Author(s)? ?: Lucas Bicalho
? ? Created? ? ?: Sun Jul 09 12:55:18 BRT 2023
? ? Notes? ? ? ?: Added:
? ? ? ? ? ? ? ? ? ? - http status code
? ? ? ? ? ? ? ? ? ? - finnaly block?
? ? ? ? ? ? ? ? ? ? - qty of records to the target table (item)
? ----------------------------------------------------------------------*/.
Then I added a few things:
USING AppServer.Utils.TableRowsCounter.
BLOCK-LEVEL ON ERROR UNDO, THROW.
DEFINE TEMP-TABLE ttItem NO-UNDO
? ? FIELD ItemNum? ? LIKE Item.ItemNum?
? ? FIELD ItemName? ?LIKE Item.ItemName?
? ? FIELD Price? ? ? LIKE Item.Price?
? ? INDEX ItemNum IS PRIMARY IS UNIQUE ItemNum.
? ??
DEFINE DATA-SOURCE srcItem FOR Item.
DEFINE DATASET dsItem FOR ttItem.
DEFINE QUERY qItem FOR ttItem.
DEFINE VARIABLE tableRowsCounter AS AppServer.Utils.TableRowsCounter NO-UNDO.
DEFINE VARIABLE cStatusCode? ? ? AS CHARACTER NO-UNDO INIT "400".
DEFINE INPUT PARAM? ? ? ? ? ? ? iQty? ? ? ?AS INTEGER? ?NO-UNDO.
DEFINE INPUT-OUTPUT PARAM? ? ? ?pRowId? ? ?AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAM? ? ? ? ? ? ?iTotalRows AS INTEGER? ?NO-UNDO.
DEFINE OUTPUT PARAM? ? ? ? ? ? ?cStatus? ? AS CHARACTER NO-UNDO INIT "Unknown error!".
DEFINE OUTPUT PARAM DATASET FOR dsItem.
Note that the default code for cStatusCode is 400 (bad request), I'll update its value depending on the part of the code:
/* Initial validation */
IF iQty = ? THEN DO: // if null, the client didn't informed the param on url
? ? ASSIGN
? ? ? ? cStatus = "Quantity parameter must be informed!".
? ? RETURN cStatusCode. // bad request
END.
ELSE IF iQty <= 0 THEN DO:
? ? ASSIGN
? ? ? ? cStatus = "Quantity parameter must be greater than 0!".
? ? RETURN cStatusCode. // bad request
END.
ELSE IF iQty > 200 THEN
DO:
? ? ASSIGN
? ? ? ? cStatus = "Not alowed! Maximum of 200 records per request!".
? ? RETURN cStatusCode. // bad request
END.
Then, the main logic is executed (nothing changed in this part from the original) and after all, I set the code 200, if nothing unexpected happened:
// ... main logic block ... //
ASSIGN
? ? tableRowsCounter = NEW TableRowsCounter()
? ? iTotalRows = tableRowsCounter:CountRows('sports2020.item')
? ? cStatus = "Success!"
? ? cStatusCode = "200".
I'm planning to explain the tableRowsCounter() in a next article. But its function is just to count the amount of records of the item table in the sports2020 database.
So the CATCH error is added, with code 500 (internal server error):
领英推荐
CATCH err AS Progress.Lang.Error:
? ? DEFINE VARIABLE iMessage AS INTEGER NO-UNDO.
? ? ASSIGN?
? ? ? ? cStatusCode = "500".
? ? ? ??
? ? DO WHILE iMessage < err:NumMessages:
? ? ? ? ASSIGN?
? ? ? ? ? ? cStatus = SUBSTITUTE ("&1~n&2", cStatus, err:GetMessage(iMessage))
? ? ? ? ? ? iMessage = iMessage + 1.
? ? END.
? ? IF err:CallStack <> ? THEN DO:
? ? ? ? ASSIGN?
? ? ? ? ? ? cStatus = SUBSTITUTE ("&1~n~nCall Stack:~n&2", cStatus, err:CallStack).
? ? END.
END CATCH.
And at the end, on the FINALLY block, the current value of HTTP status held in the cStatusCode variable is returned:
FINALLY:
? ? RETURN cStatusCode.? ??
END FINALLY.
To see the full code, click here.
3. Mapping the .p as an endpoint (retVal)
Here I added a new resource (item2), and picked the item2.p file on the GET verb. Then I linked the input parameters:
Now, on the output tab, we see the retVal value. This interface parameter holds the cStatusCode variable value:
Ok, now that our API is mapped, the next step is testing on a PASOE local instance.
4. Testing on a PASOE local instance
Testing initial validation, the expected result is to return a 400 HTTP status code (bad request). See the three validations tested: i) to pass iQty parameter; ii) iQty parameter must be greater than 0; iii) iQty must be between 1 and 200.
Now, testing with: i) Passing iQty = 1; ii) Passing the pRowId to get the next page.
All of those returning the status code 200 (OK), as expected:
Now, testing CATCH error.
To raise an unexpected condition, I pass a pRowId that ditn't exist:
The result is HTTP status code = 500 (internal server error).
Great! All the expected results worked just fine. We see here an example of how to return custom HTTP status code on an API, depending on the conditions, validations, or anything you may consider on the API design stage.
And again, if you're interested in exploring the complete code, you can find it by?click here.
???? Aos meus colegas do Brasil, se for do seu interesse que eu traduza este artigo, por favor, mencione nos comentários que o farei com o maior prazer!
Hope to see you around!