1
+ const xml2js = require ( 'xml2js' ) ;
2
+ const debug = require ( 'debug' ) ( 'matching' ) ;
3
+
4
+ /**
5
+ * Applies all template options to a response string. E.g. puts out map values
6
+ * @param {* } response
7
+ * @param {* } templateOptions
8
+ */
9
+ function applyTemplateOptionsToResponse ( response , templateOptions ) {
10
+
11
+ if ( templateOptions . map ) {
12
+ for ( let key in templateOptions . map ) {
13
+ response = response . replace ( "{{" + key + "}}" , templateOptions . map [ key ] ) ;
14
+ }
15
+ }
16
+ return response ;
17
+ }
18
+
19
+
20
+
21
+ /**
22
+ * Merges a newly returned options object from processCondition() into the existing options
23
+ * @param {* } oldOptions Old options- this gets mut'd!
24
+ * @param {* } newOptions new options
25
+ */
26
+ function mergeInOptions ( oldOptions , newOptions ) {
27
+ if ( typeof newOptions == 'object' ) {
28
+ for ( let key1 in newOptions ) {
29
+ if ( oldOptions [ key1 ] ) {
30
+ if ( Array . isArray ( oldOptions [ key1 ] ) ) {
31
+ oldOptions [ key1 ] = oldOptions [ key1 ] . concat ( newOptions [ key1 ] ) ;
32
+ } else if ( typeof oldOptions == 'object' ) {
33
+ for ( let key2 in newOptions [ key1 ] ) {
34
+ oldOptions [ key1 ] [ key2 ] = newOptions [ key1 ] [ key2 ] ;
35
+ }
36
+ } else {
37
+ oldOptions [ key1 ] = newOptions [ key1 ] ;
38
+ }
39
+ } else {
40
+ oldOptions [ key1 ] = newOptions [ key1 ] ;
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+
47
+ /**
48
+ * Given a condition string, process this condition.
49
+ * @param {string } field Flattened field name (e.g. "params.person.firstName")
50
+ * @param {string } conditionString The condition string (e.g. map:firstName)
51
+ * @param {object } flatPayload Flattened payload
52
+ * @return Returns either an object that contains new options for the template, OR false if the condition is considered failed. ONLY false is considered a failure- {} or null is a pass!
53
+ */
54
+ function processCondition ( field , conditionString , flatPayload ) {
55
+ let split = conditionString . split ( ":" , 2 ) ;
56
+ let condNum , payloadNum ;
57
+ try {
58
+ switch ( split [ 0 ] ) {
59
+ case "map" :
60
+ var map = { } ;
61
+ if ( flatPayload [ field ] === undefined )
62
+ return false ;
63
+ map [ split [ 1 ] ] = flatPayload [ field ] || '' ;
64
+ return { map} ;
65
+ case "lt" :
66
+ if ( flatPayload [ field ] === undefined )
67
+ return false ;
68
+ condNum = parseFloat ( split [ 1 ] ) ;
69
+ payloadNum = parseFloat ( flatPayload [ field ] ) ;
70
+ return payloadNum < condNum ;
71
+ case "gt" :
72
+ if ( flatPayload [ field ] === undefined )
73
+ return false ;
74
+ condNum = parseFloat ( split [ 1 ] ) ;
75
+ payloadNum = parseFloat ( flatPayload [ field ] ) ;
76
+ return payloadNum > condNum ;
77
+
78
+ case "any" :
79
+ return flatPayload [ field ] !== undefined ;
80
+ case "regex" :
81
+ var reg = new RegExp ( split [ 1 ] ) ;
82
+ return flatPayload [ field ] . match ( reg ) !== null ;
83
+ default :
84
+ return { } ;
85
+ }
86
+ } catch ( e ) {
87
+ console . log ( e ) ;
88
+ debug ( e ) ;
89
+ return false ;
90
+ }
91
+ }
92
+
93
+
94
+ /**
95
+ * Iterates through multiple ; separated conditions
96
+ * @param {* } field
97
+ * @param {* } conditionString
98
+ * @param {* } flatPayload
99
+ */
100
+ function preProcessCondition ( field , conditionString , flatPayload ) {
101
+ if ( typeof conditionString != "string" ) {
102
+ return false ;
103
+ }
104
+ var split = conditionString . split ( ";" ) ;
105
+ var opts = { } ;
106
+ for ( let splString of split ) {
107
+ let newOpts = processCondition ( field , splString , flatPayload ) ;
108
+ if ( newOpts === false )
109
+ return false ;
110
+ mergeInOptions ( opts , newOpts ) ;
111
+ }
112
+ return opts ;
113
+ }
114
+
115
+
116
+ /**
117
+ * Tests a payload against a request using a template. Returns any template options that were parsed (e.g. mapping vars)
118
+ * @param {* } flatTemplate The flattened template
119
+ * @param {* } rrpair RR Pair in question
120
+ * @param {* } flatPayload Flattened payload
121
+ * @param {* } flatReqData Flattened reqData from RR pair
122
+ * @param {* } path Path of this req (for debug logging)
123
+ */
124
+ function matchOnTemplate ( flatTemplate , rrpair , flatPayload , flatReqData , path ) {
125
+ var returnOptions = { } ;
126
+ const trimmedPayload = { } ; const trimmedReqData = { } ;
127
+ var hasBlank = false ;
128
+ for ( let field in flatTemplate ) {
129
+
130
+ //If we have a condition here, handle its special properties
131
+ if ( flatTemplate [ field ] ) {
132
+ var ret = preProcessCondition ( field , flatTemplate [ field ] , flatPayload ) ;
133
+ if ( ret !== false ) {
134
+ mergeInOptions ( returnOptions , ret ) ;
135
+ } else {
136
+ return false ;
137
+ }
138
+ //Otherwise add this to the list to get literal equals'd
139
+ } else {
140
+ hasBlank = true ;
141
+ trimmedPayload [ field ] = flatPayload [ field ] ;
142
+ trimmedReqData [ field ] = flatReqData [ field ] ;
143
+ }
144
+ }
145
+
146
+ logEvent ( path , rrpair . label , 'received payload (from template): ' + JSON . stringify ( trimmedPayload , null , 2 ) ) ;
147
+ logEvent ( path , rrpair . label , 'expected payload (from template): ' + JSON . stringify ( trimmedReqData , null , 2 ) ) ;
148
+
149
+ if ( hasBlank && ! deepEquals ( trimmedPayload , trimmedReqData ) ) {
150
+ return false ;
151
+ }
152
+
153
+ // make sure we're not comparing {} == {}
154
+ if ( hasBlank && JSON . stringify ( trimmedPayload ) === '{}' ) {
155
+ return false ;
156
+ }
157
+ return returnOptions ;
158
+ }
159
+
160
+
161
+ /**
162
+ * Given a template and the payload type, parse this template and flatten it.
163
+ * @param {* } template
164
+ * @param {* } payloadType
165
+ * @return Flattened template, or false if parsing fails.
166
+ */
167
+ function parseAndFlattenTemplate ( template , payloadType ) {
168
+ if ( payloadType === 'XML' ) {
169
+ let ret = false ;
170
+ xml2js . parseString ( template , function ( err , xmlTemplate ) {
171
+ if ( err ) {
172
+ logEvent ( err ) ;
173
+ ret = false ;
174
+ }
175
+ ret = flattenObject ( xmlTemplate ) ;
176
+ } ) ;
177
+ return ret ;
178
+ }
179
+ else if ( payloadType === 'JSON' ) {
180
+ try {
181
+ return flattenObject ( JSON . parse ( template ) ) ;
182
+ }
183
+ catch ( e ) {
184
+ debug ( e ) ;
185
+ return false ;
186
+ }
187
+ } else {
188
+ return false ;
189
+ }
190
+
191
+ }
192
+
193
+
194
+ module . exports = {
195
+ matchOnTemplate : matchOnTemplate ,
196
+ applyTemplateOptionsToResponse :applyTemplateOptionsToResponse ,
197
+ preProcessCondition :preProcessCondition ,
198
+ parseAndFlattenTemplate : parseAndFlattenTemplate
199
+ }
0 commit comments