@@ -94,6 +94,107 @@ function buildQueryOrAggregationMessageBody(
9494 return msgBody ;
9595}
9696
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+
97198export class AtlasAiService {
98199 private initPromise : Promise < void > | null = null ;
99200
@@ -240,110 +341,18 @@ export class AtlasAiService {
240341 return this . getQueryOrAggregationFromUserInput (
241342 AGGREGATION_URI ,
242343 input ,
243- this . validateAIAggregationResponse . bind ( this )
344+ validateAIAggregationResponse
244345 ) ;
245346 }
246347
247348 async getQueryFromUserInput ( input : GenerativeAiInput ) {
248349 return this . getQueryOrAggregationFromUserInput (
249350 QUERY_URI ,
250351 input ,
251- this . validateAIQueryResponse . bind ( this )
352+ validateAIQueryResponse
252353 ) ;
253354 }
254355
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-
347356 private validateAIFeatureEnablementResponse (
348357 response : any
349358 ) : asserts response is AIFeatureEnablement {
@@ -352,8 +361,4 @@ export class AtlasAiService {
352361 throw new Error ( 'Unexpected response: expected features to be an object' ) ;
353362 }
354363 }
355-
356- private hasExtraneousKeys ( obj : any , expectedKeys : string [ ] ) {
357- return Object . keys ( obj ) . some ( ( key ) => ! expectedKeys . includes ( key ) ) ;
358- }
359364}
0 commit comments