@@ -94,6 +94,107 @@ function buildQueryOrAggregationMessageBody(
94
94
return msgBody ;
95
95
}
96
96
97
+ function hasExtraneousKeys ( obj : any , expectedKeys : string [ ] ) {
98
+ return Object . keys ( obj ) . some ( ( key ) => ! expectedKeys . includes ( key ) ) ;
99
+ }
100
+
101
+ export function validateAIQueryResponse (
102
+ response : any
103
+ ) : asserts response is AIQuery {
104
+ const { content } = response ?? { } ;
105
+
106
+ if ( typeof content !== 'object' || content === null ) {
107
+ throw new Error ( 'Unexpected response: expected content to be an object' ) ;
108
+ }
109
+
110
+ if ( hasExtraneousKeys ( content , [ 'query' , 'aggregation' ] ) ) {
111
+ throw new Error (
112
+ 'Unexpected keys in response: expected query and aggregation'
113
+ ) ;
114
+ }
115
+
116
+ const { query, aggregation } = content ;
117
+
118
+ if ( ! query && ! aggregation ) {
119
+ throw new Error (
120
+ 'Unexpected response: expected query or aggregation, got none'
121
+ ) ;
122
+ }
123
+
124
+ if ( query && typeof query !== 'object' ) {
125
+ throw new Error ( 'Unexpected response: expected query to be an object' ) ;
126
+ }
127
+
128
+ if (
129
+ hasExtraneousKeys ( query , [
130
+ 'filter' ,
131
+ 'project' ,
132
+ 'collation' ,
133
+ 'sort' ,
134
+ 'skip' ,
135
+ 'limit' ,
136
+ ] )
137
+ ) {
138
+ throw new Error (
139
+ 'Unexpected keys in response: expected filter, project, collation, sort, skip, limit, aggregation'
140
+ ) ;
141
+ }
142
+
143
+ for ( const field of [
144
+ 'filter' ,
145
+ 'project' ,
146
+ 'collation' ,
147
+ 'sort' ,
148
+ 'skip' ,
149
+ 'limit' ,
150
+ ] ) {
151
+ if ( query [ field ] && typeof query [ field ] !== 'string' ) {
152
+ throw new Error (
153
+ `Unexpected response: expected field ${ field } to be a string, got ${ JSON . stringify (
154
+ query [ field ] ,
155
+ null ,
156
+ 2
157
+ ) } `
158
+ ) ;
159
+ }
160
+ }
161
+
162
+ if ( aggregation && typeof aggregation . pipeline !== 'string' ) {
163
+ throw new Error (
164
+ `Unexpected response: expected aggregation pipeline to be a string, got ${ JSON . stringify (
165
+ aggregation ,
166
+ null ,
167
+ 2
168
+ ) } `
169
+ ) ;
170
+ }
171
+ }
172
+
173
+ export function validateAIAggregationResponse (
174
+ response : any
175
+ ) : asserts response is AIAggregation {
176
+ const { content } = response ;
177
+
178
+ if ( typeof content !== 'object' || content === null ) {
179
+ throw new Error ( 'Unexpected response: expected content to be an object' ) ;
180
+ }
181
+
182
+ if ( hasExtraneousKeys ( content , [ 'aggregation' ] ) ) {
183
+ throw new Error ( 'Unexpected keys in response: expected aggregation' ) ;
184
+ }
185
+
186
+ if ( content . aggregation && typeof content . aggregation . pipeline !== 'string' ) {
187
+ // Compared to queries where we will always get the `query` field, for
188
+ // aggregations backend deletes the whole `aggregation` key if pipeline is
189
+ // empty, so we only validate `pipeline` key if `aggregation` key is present
190
+ throw new Error (
191
+ `Unexpected response: expected aggregation to be a string, got ${ String (
192
+ content . aggregation . pipeline
193
+ ) } `
194
+ ) ;
195
+ }
196
+ }
197
+
97
198
export class AtlasAiService {
98
199
private initPromise : Promise < void > | null = null ;
99
200
@@ -240,110 +341,18 @@ export class AtlasAiService {
240
341
return this . getQueryOrAggregationFromUserInput (
241
342
AGGREGATION_URI ,
242
343
input ,
243
- this . validateAIAggregationResponse . bind ( this )
344
+ validateAIAggregationResponse
244
345
) ;
245
346
}
246
347
247
348
async getQueryFromUserInput ( input : GenerativeAiInput ) {
248
349
return this . getQueryOrAggregationFromUserInput (
249
350
QUERY_URI ,
250
351
input ,
251
- this . validateAIQueryResponse . bind ( this )
352
+ validateAIQueryResponse
252
353
) ;
253
354
}
254
355
255
- private validateAIQueryResponse ( response : any ) : asserts response is AIQuery {
256
- const { content } = response ?? { } ;
257
-
258
- if ( typeof content !== 'object' || content === null ) {
259
- throw new Error ( 'Unexpected response: expected content to be an object' ) ;
260
- }
261
-
262
- if ( this . hasExtraneousKeys ( content , [ 'query' , 'aggregation' ] ) ) {
263
- throw new Error (
264
- 'Unexpected keys in response: expected query and aggregation'
265
- ) ;
266
- }
267
-
268
- const { query, aggregation } = content ;
269
-
270
- if ( typeof query !== 'object' || query === null ) {
271
- throw new Error ( 'Unexpected response: expected query to be an object' ) ;
272
- }
273
-
274
- if (
275
- this . hasExtraneousKeys ( query , [
276
- 'filter' ,
277
- 'project' ,
278
- 'collation' ,
279
- 'sort' ,
280
- 'skip' ,
281
- 'limit' ,
282
- ] )
283
- ) {
284
- throw new Error (
285
- 'Unexpected keys in response: expected filter, project, collation, sort, skip, limit, aggregation'
286
- ) ;
287
- }
288
-
289
- for ( const field of [
290
- 'filter' ,
291
- 'project' ,
292
- 'collation' ,
293
- 'sort' ,
294
- 'skip' ,
295
- 'limit' ,
296
- ] ) {
297
- if ( query [ field ] && typeof query [ field ] !== 'string' ) {
298
- throw new Error (
299
- `Unexpected response: expected field ${ field } to be a string, got ${ JSON . stringify (
300
- query [ field ] ,
301
- null ,
302
- 2
303
- ) } `
304
- ) ;
305
- }
306
- }
307
-
308
- if ( aggregation && typeof aggregation . pipeline !== 'string' ) {
309
- throw new Error (
310
- `Unexpected response: expected aggregation pipeline to be a string, got ${ JSON . stringify (
311
- aggregation ,
312
- null ,
313
- 2
314
- ) } `
315
- ) ;
316
- }
317
- }
318
-
319
- private validateAIAggregationResponse (
320
- response : any
321
- ) : asserts response is AIAggregation {
322
- const { content } = response ;
323
-
324
- if ( typeof content !== 'object' || content === null ) {
325
- throw new Error ( 'Unexpected response: expected content to be an object' ) ;
326
- }
327
-
328
- if ( this . hasExtraneousKeys ( content , [ 'aggregation' ] ) ) {
329
- throw new Error ( 'Unexpected keys in response: expected aggregation' ) ;
330
- }
331
-
332
- if (
333
- content . aggregation &&
334
- typeof content . aggregation . pipeline !== 'string'
335
- ) {
336
- // Compared to queries where we will always get the `query` field, for
337
- // aggregations backend deletes the whole `aggregation` key if pipeline is
338
- // empty, so we only validate `pipeline` key if `aggregation` key is present
339
- throw new Error (
340
- `Unexpected response: expected aggregation to be a string, got ${ String (
341
- content . aggregation . pipeline
342
- ) } `
343
- ) ;
344
- }
345
- }
346
-
347
356
private validateAIFeatureEnablementResponse (
348
357
response : any
349
358
) : asserts response is AIFeatureEnablement {
@@ -352,8 +361,4 @@ export class AtlasAiService {
352
361
throw new Error ( 'Unexpected response: expected features to be an object' ) ;
353
362
}
354
363
}
355
-
356
- private hasExtraneousKeys ( obj : any , expectedKeys : string [ ] ) {
357
- return Object . keys ( obj ) . some ( ( key ) => ! expectedKeys . includes ( key ) ) ;
358
- }
359
364
}
0 commit comments