-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcRESTAPIObject.pkg
More file actions
1580 lines (1263 loc) · 57.9 KB
/
cRESTAPIObject.pkg
File metadata and controls
1580 lines (1263 loc) · 57.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//==============================================================================
// cRESTApiObject.pkg
// ------------------
// Objects of this class should be placed inside objects of the
// cRESTResourceHandler class, with which they will automatically register
// themselves.
//
// They will then handle the various requests to that resource handler, through
// their property settings and augmentation/override of the various methods:
//
// * PreProc: Get constraining records (if any) into the DD buffer - this will
// happen automatically for single-segment relationships, but can be
// overridden in more complex cases
// * RequiredConditions: Get required - non-constraining - records into the DD
// buffer for create or update
// * IndexFromOrder: can be provided to allow the calling application to
// determine the ordering of lists based on an "Order=fieldName" query
// parameter
// * SeedFieldFromOrder: can be provided to allow a calling application to
// determine where to start in a list via a "Start=value" query parameter;
// used in conjunction with a "Limit=count" query parameter to implement
// pagination
// * InstID: The key field which will be used for finding instances
// * SetListDefaults: The default list of fields for the collection list
// * PostProc: Set up any required additional elements for the returned JSON
//
//------------------------------------------------------------------------------
// Date Author Comments
// ---------- ------ --------------------------------------------------------
// 11/08/2020 MJP Refactored to centralise the return of instance data in
// order to handle that consistently and incorporated the
// PostProc processing there, removing the need to Forward
// Send that. Also fixed a number of minor bugs, most
// relating to that stuff being handled inconsistantly
// across the different methods.
// 20/12/2018 MJP Added AddListColumn, AddInstanceColumn,
// AddReadOnlyColumn and AddExcludeColumn to keep the
// naming an usage of these methods consistent, then
// removed AddInstanceFields, AddReadOnlyFields and
// AddExcludeFields (but kept SetListDefaults because *I*
// like to do it that way).
// Changed AddFunctionColumn to _AddFunctionColumn at
// Sture's suggestion (as were the the rest of these
// changes).
// Changed pbInstanceFieldsAll to pbAllInstanceColumns.
// Changed DefineAllInstanceFeilds to
// DefineAllInstaceColumns
// AddAllInstanceFields to AddAllInstanceColumns
// 17/12/2018 MJP Refactored to use proper TableName.ColumnName instead of
// string column names, again, as per Sture's suggestions;
// this change also allows for parent fields and calculated
// fields to appear in lists and instances
// 14/12/2018 MJP Changed class name to cRESTApiObject as per Sture's
// suggestions
// **/09/2018 MJP Moved a lot of functionality from cRESTfulService into
// here where it more properly belongs I think
// 21/08/2018 MJP Original
//==============================================================================
Use UI
Use cCharTranslate.pkg
Define C_restProcPrefix for "__proc__"
Register_Procedure AddCollection Handle hoObj String sName String sHref
Register_Procedure AddLink Handle hoObj String sName String sValue String sHRef
Register_Function BaseURL Returns String
Register_Function UrlEncode String sValue Returns String
Struct stRESTField
Integer iTable
Integer iColumn
Integer iDFType
Integer iJType
String sName
Handle hoObject
Handle hfFunction
End_Struct
Class cRESTApiObject is a cObject
Procedure Construct_Object
Forward Send Construct_Object
Property Handle phoDD
Property Integer piKeyColumn 1
Property Integer piIndex 1
Property String psCollName
Property String psInstName "data"
Property stRESTField[] patFields
Property Integer[] paiDefaultListFields
Property Integer[] paiDefaultInstanceFields
Property Integer[] paiExcludeFields
Property Integer[] paiReadOnlyFields
Property Boolean pbReadOnly False
Property Boolean pbNoUpdate False
Property Boolean pbNoDelete False
Property Boolean pbDisableInstance False
Property Boolean pbAllowFieldSelection True
Property Integer piDefaultLimit C_restDefaultLimit
Property Boolean pbAllInstanceColumns True
Property String[] pasUpdateProcNames
Property Handle[] pahmUpdateProcedures
Object oTrans is a cCharTranslate
End_Object
End_Procedure
Function IsSystemFileObject Returns Boolean
Function_Return False
End_Function
Procedure End_Construct_Object
Integer iTable
String sFile sHomeDir sPath sOpen sField
String[] asFields
Handle htTable
Boolean bSys
// Assertion:
Get Main_File of (phoDD(Self)) to htTable
Get_Attribute DF_FILE_IS_SYSTEM_FILE of htTable to bSys
If (bSys and not (IsSystemFileObject(Self))) Begin
Error 681 ("cRESTApiObject" * ;
Name(Self) + ;
"'s Main_File is a system table - use cRESTApiSystemObject instead")
End
// If this has not already been called by one or other of the methods,
// call it now:
If not (SizeOfArray(patFields(Self))) ;
Send DefineColumns
// And this one...
If (pbAllInstanceColumns(Self) and ;
not (SizeOfArray(paiDefaultInstanceFields(Self)))) ;
Send DefineAllInstanceColumns
// Register with containing cResourceHandler object
Delegate Send SetHandler Self
// Is this the first API object to be registered with this handler?
String sIntPath
Delegate Get psInterfacePath to sIntPath
If (sIntPath = "") ;
Set psInterfacePath to (psCollName(Self))
Forward Send End_Construct_Object
End_Procedure
// Function FieldJsonType
// ======================
// The function takes a table handle (assumed to be open) and a column integer
// and returns the appropriate JSON type from the database data type
Function FieldJsonType Handle hTable Integer iFld Returns Integer
Integer iType iJType iPrc
Get_Attribute DF_FIELD_TYPE of hTable iFld to iType
If (iType = DF_BCD) Begin
Get_Attribute DF_FIELD_PRECISION of hTable iFld to iPrc
If (iPrc = 0) ;
Move jsonTypeInteger to iJType
Else ;
Move jsonTypeDouble to iJType
End
Else If (iType = DF_OVERLAP) ;
Move jsonTypeOverlap to iJType
Else If (iType = DF_BINARY) ;
Move jsonTypeBinary to iJType
Else ;
Move jsonTypeString to iJType
Function_Return iJType
End_Function
// Function RESTField
// ==================
// Takes the table and column integers for a field and returns a struct with
// those plus the DataFlex and JSON field types and the field name
Function RESTField Handle htTable Integer iCol Returns stRESTField
String sName sTab
stRESTField tField
Move htTable to tField.iTable
Move iCol to tField.iColumn
Get_Attribute DF_FIELD_TYPE of htTable iCol to tField.iDFType
Get_Attribute DF_FIELD_NAME of htTable iCol to sName
If (htTable <> Main_File(phoDD(Self))) Begin
Get_Attribute DF_FILE_DISPLAY_NAME of htTable to sTab
Move (sTab + "." + sName) to tField.sName
End
Else ;
Move sName to tField.sName
Get FieldJsonType htTable iCol to tField.iJType
Function_Return tField
End_Function
// Procedure DefineColumns
// =======================
// Loads the patFields array property with what we will want to know about
// the table's columns
Procedure DefineColumns
Integer i iFields
stRESTField[] atFields
Handle htTable
Get Main_File of (phoDD(Self)) to htTable
Get_Attribute DF_FILE_NUMBER_FIELDS of htTable to iFields
For i from 0 to iFields
Get RESTField htTable i to atFields[i]
Loop
Set patFields to atFields
End_Procedure
// Procedure DefineAllInstanceFields
// =================================
// Adds all the tables fields to the
Procedure DefineAllInstanceColumns
Integer[] aiFields
Integer iMax i
Get_Attribute DF_FILE_NUMBER_FIELDS of (Main_File(phoDD(Self))) to iMax
For i from 1 to iMax
Move i to aiFields[i - 1]
Loop
Set paiDefaultInstanceFields to aiFields
End_Procedure
// Procedure SetListDefaults
// =========================
// Sets the default columns to return for each instance in a List operation
// Procedure SetListDefaults
// Integer[] aiFields
// stRESTField[] atFields
// Integer i iLast iTab iCol j iArg
// Handle htMainTab
//
// Get Main_File of (phoDD(Self)) to htMainTab
//
// Move (num_arguments / 2) to iLast
//
// For i from 1 to iLast
// Move (i * 2 - 1) to iArg
// Move iArg& to iTab
// Move (i * 2) to iArg
// Move iArg& to iCol
//
// If (iTab <> htMainTab) Begin
// If not (SizeOfArray(patFields(Self))) ;
// Send DefineColumns
// Get patFields to atFields
// Move (SizeOfArray(atFields)) to j
// Get RESTField iTab iCol to atFields[j]
// Move j to aiFields[i - 1]
// Set patFields to atFields
// End
// Else ;
// Move iCol to aiFields[i - 1]
//
// Loop
//
// Set paiDefaultListFields to aiFields
// End_Procedure
// Procedure AddListColumn
// =======================
// Adds a column to the list operation.
// Call with File_Field TableName.ColumnName.
Procedure AddListColumn Handle htTable Integer iColumn
stRESTField[] atFields
Integer[] aiFields
Integer i j
Get paiDefaultListFields to aiFields
Move (SizeOfArray(aiFields)) to i
If (htTable <> Main_File(phoDD(Self))) Begin
If not (SizeOfArray(patFields(Self))) ;
Send DefineColumns
Get patFields to atFields
Move (SizeOfArray(atFields)) to j
Get RESTField htTable iColumn to atFields[j]
Set patFields to atFields
Move j to aiFields[i]
End
Else ;
Move iColumn to aiFields[i]
Set paiDefaultListFields to aiFields
End_Procedure
// Procedure AddInstanceColumn
// ===========================
// Adds a column to the instance operation.
// Call with File_Field TableName.ColumnName.
Procedure AddInstanceColumn Handle htTable Integer iColumn
stRESTField[] atFields
Integer[] aiFields
Integer i j
// If (pbAllInstanceColumns(Self) and ;
// (SizeOfArray(paiDefaultInstanceFields(Self)) = 0)) ;
// Send DefineAllInstanceColumns
Get paiDefaultInstanceFields to aiFields
Move (SizeOfArray(aiFields)) to i
If (htTable <> Main_File(phoDD(Self))) Begin
If not (SizeOfArray(patFields(Self))) ;
Send DefineColumns
Get patFields to atFields
Move (SizeOfArray(atFields)) to j
Get RESTField htTable iColumn to atFields[j]
Set patFields to atFields
Move j to aiFields[i]
End
Else ;
Move iColumn to aiFields[i]
Set paiDefaultInstanceFields to aiFields
End_Procedure
// Procedure AddReadOnlyColumn
// ===========================
// Adds a column to be treated as Read-Only.
// Call with Field TableName.ColumnName or
// (RefTable(TableName.ColumnName)).
Procedure AddReadOnlyColumn Integer iColumn
Integer[] aiFields
Get paiReadOnlyFields to aiFields
Move iColumn to aiFields[SizeOfArray(aiFields)]
Set paiReadOnlyFields to aiFields
End_Procedure
// Procedure AddExcludeColumn
// ===========================
// Adds a column to be excluded from all operations.
// Call with Field TableName.ColumnName or
// (RefTable(TableName.ColumnName)).
Procedure AddExcludeColumn Integer iColumn
Integer[] aiFields
Get paiExcludeFields to aiFields
Move iColumn to aiFields[SizeOfArray(aiFields)]
Set paiExcludeFields to aiFields
End_Procedure
// Function AddFunctionColumn
// ==========================
// SHOULD NOT BE CALLED DIRECTLY! Just removes duplication from the two
// procedures below: AddListFunctionColumn and AddInstanceFunctionColumn
// which are the ones you SHOULD call.
Function _AddFunctionColumn String sName Handle hoObject ;
Handle hfFunction Integer iJsonType Returns Integer
stRESTField[] atFields
Integer i
// If patFields is not yet populated, do that now first
If not (SizeOfArray(patFields(Self))) ;
Send DefineColumns
Get patFields to atFields
Move (SizeOfArray(atFields)) to i
Move hoObject to atFields[i].hoObject
Move hfFunction to atFields[i].hfFunction
Move sName to atFields[i].sName
Move iJsonType to atFields[i].iJType
Set patFields to atFields
Function_Return i
End_Function
// Procedure AddListFunctionColumn
// ===============================
// Allows adding a calculated column to a listing
Procedure AddListFunctionColumn String sName Handle hoObject ;
Handle hfFunction Integer iJsonType
Integer[] aiFields
Integer iPos
Get _AddFunctionColumn sName hoObject hfFunction iJsonType to iPos
Get paiDefaultListFields to aiFields
Move iPos to aiFields[SizeOfArray(aiFields)]
Set paiDefaultListFields to aiFields
End_Procedure
// Procedure AddInstanceFunctionColumn
// ===================================
// Allows adding a calculated column to an instance
Procedure AddInstanceFunctionColumn String sName Handle hoObject ;
Handle hfFunction Integer iJsonType
Integer[] aiFields
Integer iPos
If (pbAllInstanceColumns(Self) and ;
not (SizeOfArray(paiDefaultInstanceFields(Self)))) ;
Send DefineAllInstanceColumns
Get _AddFunctionColumn sName hoObject hfFunction iJsonType to iPos
Get paiDefaultInstanceFields to aiFields
Move iPos to aiFields[SizeOfArray(aiFields)]
Set paiDefaultInstanceFields to aiFields
End_Procedure
// Function JsonFromDD
// ===================
// Takes a DD object and returns a JSON object populated with the current
// DD values for it. If aiFields is not empty it is used as a list of the
// columns to include, otherwise all will be returned. Overlaps will be
// skipped. Binary fields will only be returned if the property
// pbReturnBinary is set to True (False by default).
Function JsonFromDD Handle hoDD Integer[] aiFields Returns Handle
Handle hoJson htTable hoField
Integer i iFields iMax iField iType
stRESTField[] atFields
stRESTField tField
String sName sValue
Integer[] aiExclude
UChar[] ucaVal
Get Main_File of hoDD to htTable
Get paiExcludeFields to aiExclude
Get patFields to atFields
Get CreateJsonObject to hoJson
Move (SizeOfArray(aiFields) - 1) to iMax
For i from 0 to iMax
Move aiFields[i] to iField
// Do not return RECNUM
If (aiFields[i] <= 0) ;
Break Begin
// Do not return excluded fields
If (SearchArray(iField, aiExclude) > -1) ;
Break Begin
Move atFields[iField] to tField
If (tField.hfFunction) Begin
If ((tField.iJType = jsonTypeObject) or ;
(tField.iJType = jsonTypeArray)) Begin
Get tField.hfFunction of tField.hoObject Self to hoField
If hoField Begin
Send SetMember of hoJson tField.sName hoField
Send Destroy of hoField
End
End
Else Begin
Get tField.hfFunction of tField.hoObject Self to sValue
Send SetMemberValue of hoJson tField.sName tField.iJType sValue
End
End
Else Begin
If (tField.iDFType <> DF_OVERLAP) Begin // Ignore overlaps
If ((tField.iDFType = DF_BINARY) and ;
pbReturnBinary(Self)) Begin
Get File_Field_Current_UCAValue of hoDD ;
tField.iTable tField.iColumn to ucaVal
Get Base64EncodeUCharArray of oTrans ucaVal to ucaVal
Send SetMemberValue of hoJson tField.sName jsonTypeString ;
(UCharArrayToString(ucaVal))
End
Else Begin
Get File_Field_Current_Value of hoDD tField.iTable tField.iColumn to sValue
If (tField.iDFType = DF_DATE) Begin
Move (ConvertToClient(DF_DATE, sValue)) to sValue
End
If (tField.iDFType = DF_DATETIME) Begin
Move (ConvertToClient(DF_DATETIME, sValue)) to sValue
End
Send SetMemberValue of hoJson tField.sName tField.iJType (Trim(sValue))
End
End
End
Loop
Function_Return hoJson
End_Function
// Procedure OnUnrecognizedField
// =============================
// Developer hook for handling updates containing JSON elememts which do
// NOT correspond to a field in the table.
Procedure OnUnrecognizedField String sMemberName Handle hoMember
// This will by default simply trigger the 4100 error so we carefully
// ignored below, but can be overridden to do whatever the developer
// requires.
Error 4100 sMemberName
// If you do NOT want to generate this error, override the method
// WITHOUT forward sending it.
End_Procedure
Procedure RegisterUpdateHandler String sName Handle hmProc
String[] asNames
Handle[] ahmProcs
Integer iIdx
Move (Lowercase(sName)) to sName
Get pasUpdateProcNames to asNames
Move (SearchArray(sName, asNames)) to iIdx
If (iIdx <> -1) ; // already registered
Procedure_Return
Move (SizeOfArray(asNames)) to iIdx
Get pahmUpdateProcedures to ahmProcs
Move sName to asNames[iIdx]
Move hmProc to ahmProcs[iIdx]
Set pasUpdateProcNames to asNames
Set pahmUpdateProcedures to ahmProcs
End_Procedure
Function UpdateHandlerProc String sProc Returns Handle
String[] asNames
Handle[] ahmProcs
Integer iIdx
Get pasUpdateProcNames to asNames
Move (SearchArray(sProc, asNames)) to iIdx
If (iIDx = -1) ; // not found
Function_Return 0
Get pahmUpdateProcedures to ahmProcs
Function_Return ahmProcs[iIdx]
End_Function
// Procedure UpdateDDFromJson
// ==========================
// The procedure can be used to update a DDO with data from identically
// named fields (NOT case-sesitive) in a passed JSON object. Pass it
// the handle of the DD object to be updated and the handle of a JSON
// object with the appropriate members. You can then use the Should_Save
// property of the DDO to determine whether a save is required. It will
// ignore JSON NULLs.
Procedure UpdateDDFromJson Handle hoDD Handle hoJson
Integer iMembs i iType iField iOpt
String sMemb sJVal sDVal sProc
Handle htTable hoMember hmProc
Integer[] aiExclude aiReadOnly
UChar[] ucaVal
Boolean bProcessed
If not hoJson ;
Procedure_Return
If not (JsonType(hoJson) = jsonTypeObject) ;
Procedure_Return
Get paiExcludeFields to aiExclude
Get paiReadOnlyFields to aiReadOnly
Get Main_File of hoDD to htTable
Get MemberCount of hoJson to iMembs
Decrement iMembs
For i from 0 to iMembs
Move False to bProcessed
Get MemberNameByIndex of hoJson i to sMemb
If (MemberJsonType(hoJson, sMemb) = jsonTypeNull) ;
Break Begin // Skip nulls - no use to us
If (Lowercase(Left(sMemb, Length(C_restProcPrefix))) = C_restProcPrefix) Begin
Move (Lowercase(Right(sMemb, (Length(sMemb) - Length(C_restProcPrefix))))) to sProc
Get UpdateHandlerProc sProc to hmProc
If hmProc Begin
Send hmProc (Member(hoJson, sMemb))
Move True to bProcessed
End
End
If bProcessed ;
Break Begin
// We ignore the error 4100 which will occur will occur if member
// is NOT a field in the table when Field_Map tries to evaluate it
// because we want to handle that ourselves below using the
// OnUnrecognizedField developer hook.
Send IgnoreError 4100
Field_Map htTable sMemb to iField
Send TrapError 4100
If (SearchArray(iField, aiExclude) > -1) ;
Break Begin
If (SearchArray(iField, aiReadOnly) > -1) ;
Break Begin
If (iField <> 0) Begin
Get MemberValue of hoJson sMemb to sJVal
Get Field_Current_Value of hoDD iField to sDVal
Get_Attribute DF_FIELD_TYPE of htTable iField to iType
If (iType = DF_BINARY) Begin
Move (StringToUCharArray(sJVal)) to ucaVal
Get Base64DecodeUCharArray of oTrans ucaVal to ucaVal
Set Field_Current_UCAValue of hoDD iField to ucaVal
Set Field_Changed_State of hoDD iField to True
End
Else Begin
If (iType = DF_DATE) ;
Move (ConvertFromClient(typeDate, sJVal)) to sJVal
If (iType = DF_DATETIME) ;
Move (ConvertFromClient(typeDateTime, sJVal)) to sJVal
Get Field_Option of hoDD iField DD_CAPSLOCK to iOpt
If (iOpt = 1) ;
Move (Uppercase(sJVal)) to sJVal
If (sJVal <> sDVal) Begin
Set Field_Changed_Value of hoDD iField to sJVal
End
End
End
Else Begin
Get Member of hoJson sMemb to hoMember
Send OnUnrecognizedField sMemb hoMember Self
End
Loop
End_Procedure
// Function RequiredConditions
// ===========================
// For override - used to find reqired parent records not in direct line
// from JSON in the passed data - will usually only be required by create.
// However see FindRequirement, which this can be used in most cases to call
// as a one-liner to do what it needs.
Function RequiredConditions Handle hoJson Integer iMode Returns Boolean
Function_Return True
End_Function
// Function FindRequirement
// ========================
// This function is designed to remove the code duplication inherent in all
// the RequiredCondition functions in the various instances. It will
// perform what most of them do in one line. It is designed to be called in
// one line from the RequiredCondition function if things are that simple
// (which is very often the case). Six arguments is really too many, but
// them's the breaks.
Function FindRequirement Handle hoJson Integer iMode String sRequirement ;
Handle hoDD Integer iIdx Integer iField Returns Boolean
Boolean bFound
String sID
Move False to bFound
If (iMode = C_restModeCreate) Begin
If (HasMember(hoJson, sRequirement)) Begin
Get MemberValue of hoJson sRequirement to sID
Get FindRecord hoDD iField iIdx sID to bFound
End
If not bFound Send RequirementMissing ;
("A valid '" + sRequirement + "' element must be present in the passed JSON")
End
Else ;
Move True to bFound
Function_Return bFound
End_Function
// Function FindMultiSegRequirement
// ================================
// This function is designed to provide the same mechanism as FindRequirement
// above, but to work with multi-segment keys by passing arrays of both
// reqirements and fields. Because of this added complexity it cannot
// use the simple FindRecord mechanism as in FindRequirement, but must do all
// of the work in-line.
Function FindMultiSegRequirement Handle hoJson Integer iMode String[] asReqs ;
Handle hoDD Integer iIdx Integer[] aiCols ;
Returns Boolean
Boolean bFound
Integer i iMax
Handle hTab
String sVal sCol sMsg sTab
String[] asColVals
Move False to bFound
If (iMode = C_restModeCreate) Begin
Move (SizeOfArray(asReqs) - 1) to iMax
Get Main_File of hoDD to hTab
Send Clear of hoDD
For i from 0 to iMax
If (HasMember(hoJson, asReqs[i])) Begin
Get MemberValue of hoJson asReqs[i] to sVal
Set_Field_Value hTab aiCols[i] to sVal
Move sVal to asColVals[i]
End
Else Begin
Send RequirementMissing ;
("A valid '" + asReqs[i] * "' element must be present in the passed JSON")
Function_Return False
End
Loop
// The key fields in index iIdx should now be populated
Send Find of hoDD GE iIdx
Move (Found) to bFound
If bFound Begin
For i from 0 to iMax
Get Field_Current_Value of hoDD aiCols[i] to sVal
Move (sVal = asColVals[i]) to bFound
Loop
If not bFound ;
Break
End
If not bFound Begin
For i from 0 to iMax
Get_Attribute DF_FIELD_NAME of hTab aiCols[i] to sCol
Move (sMsg + sCol + "=" + asReqs[i]) to sMsg
If (i < iMax) Move (sMsg + ", ") to sMsg
Loop
Get_Attribute DF_FILE_LOGICAL_NAME of hTab to sTab
Send RequirementMissing ;
("A row with values: " + sMsg * "could not be found in parent table" * sTab)
Function_Return False
End
End
Function_Return True
End_Function
// Function CheckRequirement
// =========================
// Similar to FindRequirement, but rather than checking that a matching
// record exists in a table, this simply checks that it has been passed
// in the JSON
Function CheckRequirement Handle hoJson Integer iMode String sRequirement Returns Boolean
Boolean bOK
String sVal
Move False to bOK
If (iMode = C_restModeCreate) Begin
If (HasMember(hoJson, sRequirement)) Begin
Get MemberValue of hoJson sRequirement to sVal
If (sVal <> "") Move True to bOK
End
If not bOK Send RequirementMissing ;
("A '" + sRequirement + "' element must be present in the passed JSON")
End
Else ;
Move True to bOK
Function_Return bOK
End_Function
// Procedure SetRequirement
// ========================
// Like the CheckRequirement above, but if the requirement is missing from
// the JSON, it just puts it in there - this should really be in the Creating
// method of the DD, but if not we can do it here
Procedure SetRequirement Handle hoJson Integer iMode String sRequirement String sValue
If (iMode = C_restModeCreate) Begin
If not (HasMember(hoJson, sRequirement)) ;
Send SetMemberValue of hoJson sRequirement jsonTypeString sValue
End
End_Procedure
// Procedure RequirementMissing
// ============================
// What to send if RequiredConditions fails
Procedure RequirementMissing String sErrMsg
Send SetStandardResponseStatus 400
Send OutputError "Requirement missing" sErrMsg
End_Procedure
// Function PreProc
// ================
// Will attempt to find related records for the current table so that
// the constrains defined by the Constrain_File relationships can be
// fulfilled.
//
// It will use the instance IDs defined in the RequestPath
// (via the PathPart n function) to determine the values to use to find
// the parent record(s).
//
// It will fail (return False) with a runtime error if there are
// relationships based on multipe columns involved, which will then require
// overriding it to manually find the correct parent record(s) involved.
Function PreProc Returns Integer
Handle hoDD htConstr hoParent htMain htParent
Handle[] ahoDDOs
Integer[] aiFlds aiIdxs
Integer i iLast iFlds iField iIdx
String sVal
Boolean bFound bRelFound
Get phoDD to hoDD
If not hoDD ;
Function_Return False // There must be a phoDD defined
Send Rebuild_Constraints of hoDD
Repeat
Get Constrain_File of hoDD to htConstr
If not htConstr ;
Break // If there is no Constrain_File, exit the loop
Get Main_File of hoDD to htMain
Get Which_Data_Set of hoDD htConstr to hoParent
Move (InsertInArray(ahoDDOs, 0, hoParent)) to ahoDDOs // Push it on top
Get_Attribute DF_FILE_NUMBER_FIELDS of htMain to iFlds
Move False to bRelFound
For i from 1 to iFlds
If (pbUseDDRelates(hoDD)) ;
Get Field_Related_File of hoDD i to htParent
Else ;
Get_Attribute DF_FIELD_RELATED_FILE of htMain i to htParent
If (htParent = htConstr) Begin
If bRelFound Begin // Multi-column relationship!
Error 683 "Multi-Column relationship detected - override Function PreProc to manually find constraining records"
Function_Return False
End
If (pbUseDDRelates(hoDD)) ;
Get Field_Related_Field of hoDD i to iField
Else ;
Get_Attribute DF_FIELD_RELATED_FIELD of htMain i to iField
Get_Attribute DF_FIELD_INDEX of htParent iField to iIdx
Move True to bRelFound
End
Loop
Move (InsertInArray(aiFlds, 0, iField)) to aiFlds // Push it on top
Move (InsertInArray(aiIdxs, 0, iIdx)) to aiIdxs // Push it on top
Move hoParent to hoDD // Move UP the relationship line
Loop
Move True to bFound
Move (SizeOfArray(ahoDDOs) - 1) to iLast
// Run through the relationships from the top
For i from 0 to iLast
Get PathPart ((i * 2) + 1) to sVal
Get FindRecord ahoDDOs[i] aiFlds[i] aiIdxs[i] sVal to bFound
Until not bFound
Function_Return bFound
End_Function
// Procedure PostProc
// ==================
// For augmentation - used to add decoration to the response data, such as
// links and child collections. The default will add the owning collection
// so you should Forward Send in instances.
Procedure PostProc Handle hoResp Integer iMode
End_Procedure
// Function IndexFromOrder
// =======================
// For override - returns the index for a specified textual ordering
Function IndexFromOrder String sOrder Returns Integer
Function_Return (piIndex(Self))
End_Function
// Function SeedFieldFromOrder
// ===========================
// For override - returns the field to seed with the "start" query value
// based on the textual ordering
Function SeedFieldFromOrder String sOrder Returns Integer
Function_Return (piKeyColumn(Self))
End_Function
// Function InstID
// ===============
// Can be overridden - returns the ID value for the current instance
// within a collection. The reason you might want to override it is if
// you have a compound (multi-column) key you need to put into a URL, so
// just the single piKeyColumn is insufficient to identify the resource.
Function InstID Returns String
String sVal
Integer iType iCol
Handle hTable hoDD
Get piKeyColumn to iCol
Get phoDD to hoDD
Get Field_Current_Value of hoDD iCol to sVal
Get Main_file of hoDD to hTable
Get_Attribute DF_FIELD_TYPE of hTable iCol to iType
If (iType = DF_DATE) ;
Move (ConvertToClient(typeDate, sVal)) to sVal
If (iType = DF_DATETIME) ;
Move (ConvertToClient(typeDateTime, sVal)) to sVal
Function_Return (UrlEncode(Self, sVal))
End_Function
// Function InstURL
// ================
// Returns the URL of the instance
Function InstURL Integer iMode Returns String
String sURL
If (iMode = C_restModeCreate) ;
Move (BaseURL(Self) + "/" + EncodedPath(Self) + "/" + InstID(Self)) to sURL
Else ;
Move (BaseURL(Self) + "/" + EncodedPath(Self)) to sURL
Function_Return sURL
End_Function
// Procedure Metadata
// ==================
// Returns a collection's metadata (just the field definitions)
Procedure Metadata
Integer[] aiExclude aiReadOnly
Handle hTab hoDD hoResp hoMeta hoValObj hoVTObj
Integer i j k iFlds iType iLen iPre iFVType iMax
String sName sType sVal sDesc
Get phoDD to hoDD
Get Main_File of hoDD to hTab
Get paiExcludeFields to aiExclude
Get paiReadOnlyFields to aiReadOnly
Get CreateJsonArray to hoMeta
Get_Attribute DF_FILE_NUMBER_FIELDS of hTab to iFlds
For i from 1 to iFlds
Get_Attribute DF_FIELD_NAME of hTab i to sName
Get_Attribute DF_FIELD_TYPE of hTab i to iType
If (SearchArray(i, aiExclude) > -1) ;
Break Begin
If (iType <> DF_OVERLAP) Begin
Get_Attribute DF_FIELD_LENGTH of hTab i to iLen
Get_Attribute DF_FIELD_PRECISION of hTab i to iPre
Move "" to sType
If (iType = DF_ASCII)
Move ("String (" + String(iLen) + ")") to sType
If (iType = DF_TEXT) ;
Move ("Text as String (" + String(iLen) + ")") to sType
If (iType = DF_BCD) ;
Move ("Number (" + String(iLen - iPre) + "." + String(iPre) + ")") ;
to sType
If (iType = DF_DATE) ;
Move "Date as String (YYYY-MM-DD)" to sType