January 19

1 comments

Programming standards for debugging RPG Webservices

By NickLitten

January 19, 2018

IBM i

Debugging RPG Webservices can be a pain in the proverbial; finding the IBM i background jobs that are running the REST Service, starting job service mode to get at the program variables and then discovering that the program was not compiled with DBGVIEW(*SOURCE) so you cant see the variables anyway. #aaaargh

Debugging IBM i Webservices made EASY

However you are writing your IBM i Webservices (aka API), the chances are that you will be using an API development tool like SOAPUI or POSTMAN for debugging and testing the returned variables.

Both of these tools mean that we can easily look at the JSON or XML that is being returned from the webservice.

So, why dont we simply design our RPGLE or SQLRPGLE webservice program to build debugging into it at design (or modification) time?

I’ve been using this technique for a while, it’s easy and really makes debugging a snap

DEBUG="Y" turns on DEBUG MODE

The basic idea is that in our program code we receive an input variable that turns on debug mode. If we are in debug mode then we simply return all kinds of extra information in the returned JSON (for example). We can return job information, program variables or simply status messages.

(1) All our RPG programs should use the PSDS

The PSDS (Program Status Data Structure) is a super useful block of knowledge. Use it as standard in all programs. Shove it in a copybook that you always load.

Mine looks like this:

dcl-ds psds PSDS qualified;
 program char(10) pos(1);
 statusCode char(5) pos(11);
 rpgStatus char(8) pos(21);
 rpgRoutine char(8) pos(29);
 parmCount char(3) pos(37);
 errorCode char(7) pos(40);
 programLibrary char(10) pos(81);
 messageText char(80) pos(91);
 fileName char(8) pos(201);
 fileErrorRoutine char(5) pos(209);
 rpgFileRoutine char(6) pos(214);
 rpgStatement char(6) pos(214);
 fileRecordFormat char(8) pos(236);
 job char(10) pos(244);
 jobuser char(10) pos(254);
 jobNumber char(6) pos(264);
 myDate zoned(6) pos(276);
 myTime zoned(6) pos(282);
 timeHour char(2) pos(282);
 timeMinute char(2) pos(284);
 timeSecond char(2) pos(286);
 compileDate char(6) pos(288);
 compileTime char(6) pos(294);
 compileLevel char(4) pos(300);
 sourceFile char(10) pos(304);
 sourceLib char(10) pos(314);
 sourceFileMember char(10) pos(324);
end-ds;

We will come back to this later

(2) URI Input Variable

Define an input variable just like all our other inputs but call it debug. Here is an example of a program header comment I did yesterday:

 // PROGRAM INPUT via URI:
 // Pass in URI input of DEBUG='1' to put program into debug mode (returned via JSON)
 // + propertyName - name of webservice
 // + fetchstart - numeric - starting row. dft=1 ie: FETCHSTART=1
 // + fetchlimit - numeric - number of rows to read. dft=999999
 // + fetchRoomStart char(10) - starting room in range to retrieve
 // + fetchRoomEnd char(10) - ending room in range to retrieve
 // + sequence char(50); // dft=*blanks(room) or 'room', 'wing' or 'occupancyStatus'
 // + room char(10); // *blanks=*ALL or Roomnumber
 // + aid char(1); // AID Code *blanks=*ALL 'A'=ACTIVE 'I'=INACTIVE 'D'=TO BE DELETED
 // + wing char(2); // filter for Wingtype *blanks=*ALL
 // + roomType char(4); // filter for Room type *blanks=*ALL
 // + floor char(3); // filter for Floor *blanks=*ALL
 // + noService char(1); // filter for No Service *blanks=*ALL
 // + smoking char(1); // filter for Smoking *blanks=*ALL SMOKING=Y, NON=N
 // + connectingRooms char(1); // filter for Connecting Room *blanks=*ALL
 // + notInSuite char(1); // filter for Not In Suite *blanks=*ALL

I prefer to create a qualified data structure and then define all my input parms inside that data structure.

This has nice code-readability benefit because any input variable will be called datastructurename.inputname which makes life easier for programmers who modify my code later:

 // -------------------------------------------------
 // | ALL URI INPUTS - add new URI input parms here |
 // -------------------------------------------------
 dcl-ds inputParm qualified;
   debug ind inz(*off); // run in debug mode
   ResID char(30); // reservation ID
   sequence char(50); // dft=*blanks(room) or 'room', 'wing' or 'occupancyStatus'
   room char(5); // *blanks=*ALL or Roomnumber (right adjusted)
   fetchroomstart char(5); // *blanks=*ALL or Roomnumber (right adjusted)
   fetchroomend char(5); // *blanks=*ALL or Roomnumber (right adjusted)
   aid char(1); // AID Code *blanks=*ALL 'A'=active 'I'=inactive 'D'=to be deleted
   wing char(2); // filter for Wingtype *blanks=*ALL
   roomType char(4); // filter for Room type *blanks=*ALL
   floor char(3); // filter for Floor *blanks=*ALL
   noService char(1); // filter for No Service *blanks=*ALL
   smoking char(1); // filter for Smoking *blanks=*ALL
   connectingRooms char(1); // filter for Connecting Room *blanks=*ALL
   notInSuite char(1); // filter for Not In Suite *blanks=*ALL
 end-ds;

So… in your code you would check in your incoming parameters from URI or data attachment (JSON, XML, whatever) and populate each of these fields and when they are populated.

If the value DEBUG is passed in then the value of inputparm.debug would be *ON

if input_from_URI_debug="something";
 inputparm.debug = *on;
endif;

Then in our code we just build sections that do things like this:

JSON CODE EXAMPLE USING Y.A.J.L.

yajl_genOpen(*ON); // *ON for easier to read JSON

yajl_beginObj();

yajl_addChar('webservice':'The name of this webservice is here V001');

if inputparm.debug;
 yajl_addChar('Job Name':psds.job);
 yajl_addChar('Job User':psds.jobuser);
 yajl_addChar('Job Number':psds.jobnumber);
 yajl_addChar('debug-SQLSTM':%trim(sqlstm));
 yajl_addChar('debug-fetchStartNumeric':%char(fetchStartNumeric));
 yajl_addChar('debug-fetchCountNumeric':%char(fetchCountNumeric));
endif;

So you can see that I always return a variable called “webservice” and I think of this as a decription and a version number of the code in question. When I update the code I increment the V001 number so we can always easily see which version of the code we are running.

But then you can see that if we are in debug mode the first thing it returns is the job name from the PSDS – this gives us the job name for easy debugging using STRSRVJOB if we want, but it also allows us to return all kinds of information at this header level in the webservice.

Of course, we can use the same technique anywhere in the code to return a more granular or detail based information set:

While reading file data:

 if inputparm.debug;
  yajl_addChar('debug-PROC':'get_next_reservation'); // show a constant
  yajl_addChar('debug-accomodationType':accomodationType); // show a variable
  yajl_addChar('debug-nextReservationDate':%char(nextReservationDate)); // return a numeric as character
 endif;

and of course it makes catching errors nice an easy:

exec sql
 fetch next from c1 into :ds_RMP;

If sqlstt <> '00000' and %subst(sqlstt:1:2) <> '01' and %subst(sqlstt:1:2) <> '02';
 if inputparm.debug;
   yajl_addChar('debug-SQL-UnknownError':'row'+%char($Count1));
   yajl_addChar('debug-SQL-SQLSTT':sqlstt); 
 endif;
 leave;
 elseif %subst(sqlstt:1:2) = '01';
  if inputparm.debug;
    yajl_addChar('debug-SQL-Warning':'row'+%char($Count1));
    yajl_addChar('debug-SQL-SQLSTT':sqlstt);
  endif;
  leave;
 elseif %subst(sqlstt:1:2) = '02';
  if inputparm.debug;
    yajl_addChar('debug-SQL-EOF':'row'+%char($Count1));
    yajl_addChar('debug-SQL-SQLSTT':sqlstt); 
  endif;
  leave;
 else;

// now do some business logic with the data we just read

blah blah blah

The nice thing is that if we test this exact code with SOAPUI I see this when I run it in normal mode:

then we can simply add an extra variable to your input URI saying DEBUG=’1′ (the value doesnt matter it just has to be non-blank)

Debugging rpg webservices
Programming standards for debugging rpg webservices

and then we run it and see all the glorious extra gubbins

Programming standards for debugging rpg webservices

It’s a really simple technique to adopt and it’s got the added bonus of being able to switch into debug mode when your in Unit Test, QA or even production mode. Of course, you could add any kind of security you wish to this technique.

Ahh the deep joys of RPG coding.

#lovingit

#sorrymcdonalds

  • Hi Nick
    I think it may me very useful for those who are dealing with developing of Web Services running on iSeries
    But alas, in my case … Last time I am dealing with Web services running on another platform and iSeries is acting like a client sending XML data to WEBSERVICE on other platform.
    Please advice does this technic may be applied for client part too ?
    Thank in advance
    Eli

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Join the IBM i Community for FREE Presentations, Lessons, Hints and Tips

    >