From ed5f5ec5ffe9ab603fb7b098ecccba9ef29d8d03 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Lanneer Date: Thu, 7 Dec 2023 12:57:32 +0100 Subject: [PATCH] search and count by post --- .../api/src/client/handles/messages-http.ts | 10 +-- .../lib/api/src/client/handles/search.ts | 44 ++++++++++++ .../request-factory/http-request-sender.ts | 9 +-- .../client/request-factory/request-sender.ts | 72 +++++++++++++++---- .../websocket-request-sender.ts | 10 +++ .../api/tests/client/http/search.http.spec.ts | 62 +++++++++++++++- 6 files changed, 181 insertions(+), 26 deletions(-) diff --git a/javascript/lib/api/src/client/handles/messages-http.ts b/javascript/lib/api/src/client/handles/messages-http.ts index e0d00595..667a8376 100644 --- a/javascript/lib/api/src/client/handles/messages-http.ts +++ b/javascript/lib/api/src/client/handles/messages-http.ts @@ -112,7 +112,7 @@ export class DefaultHttpMessagesHandle implements HttpMessagesHandle { claim(thingId: string, claimMessage: any, options?: MessagesOptions): Promise { const messageOptions = options === undefined ? DefaultMessagesOptions.getInstance() : options; messageOptions.addHeader(Header.CONTENT_TYPE, ContentType.JSON); - return this.requestFactory.fetchRequest({ + return this.requestFactory.fetchGenericJsonRequest({ verb: HttpVerb.POST, id: thingId, path: 'inbox/claim', @@ -135,7 +135,7 @@ export class DefaultHttpMessagesHandle implements HttpMessagesHandle { contentType: string = ContentType.JSON, options?: MessagesOptions): Promise { const messageOptions = options === undefined ? DefaultMessagesOptions.getInstance() : options; messageOptions.addHeader(Header.CONTENT_TYPE, contentType); - return this.requestFactory.fetchRequest({ + return this.requestFactory.fetchGenericJsonRequest({ verb: HttpVerb.POST, id: thingId, path: `inbox/messages/${messageSubject}`, @@ -158,7 +158,7 @@ export class DefaultHttpMessagesHandle implements HttpMessagesHandle { contentType: string = ContentType.JSON, options?: MessagesOptions): Promise { const messageOptions = options === undefined ? DefaultMessagesOptions.getInstance() : options; messageOptions.addHeader(Header.CONTENT_TYPE, contentType); - return this.requestFactory.fetchRequest({ + return this.requestFactory.fetchGenericJsonRequest({ verb: HttpVerb.POST, id: thingId, path: `outbox/messages/${messageSubject}`, @@ -183,7 +183,7 @@ export class DefaultHttpMessagesHandle implements HttpMessagesHandle { Promise { const messageOptions = options === undefined ? DefaultMessagesOptions.getInstance() : options; messageOptions.addHeader(Header.CONTENT_TYPE, contentType); - return this.requestFactory.fetchRequest({ + return this.requestFactory.fetchGenericJsonRequest({ verb: HttpVerb.POST, id: thingId, path: `features/${featureId}/inbox/messages/${messageSubject}`, @@ -208,7 +208,7 @@ export class DefaultHttpMessagesHandle implements HttpMessagesHandle { Promise { const messageOptions = options === undefined ? DefaultMessagesOptions.getInstance() : options; messageOptions.addHeader(Header.CONTENT_TYPE, contentType); - return this.requestFactory.fetchRequest({ + return this.requestFactory.fetchGenericJsonRequest({ verb: HttpVerb.POST, id: thingId, path: `features/${featureId}/outbox/messages/${messageSubject}`, diff --git a/javascript/lib/api/src/client/handles/search.ts b/javascript/lib/api/src/client/handles/search.ts index a538b420..45da2c29 100644 --- a/javascript/lib/api/src/client/handles/search.ts +++ b/javascript/lib/api/src/client/handles/search.ts @@ -25,6 +25,14 @@ export interface SearchHandle { */ search(options?: SearchOptions): Promise; + /** + * Searches for Things that match the restrictions set in options using a http post request. + * + * @param options - Options to use for the search. + * @returns The search result + */ + postSearch(options?: SearchOptions): Promise; + /** * Counts the Things that match the restrictions set in options. * @@ -32,6 +40,14 @@ export interface SearchHandle { * @returns The count */ count(options?: CountOptions): Promise; + + /** + * Counts the Things that match the restrictions set in options using a http post request. + * + * @param options - Options to use for the search. + * @returns The count + */ + postCount(options?: CountOptions): Promise; } /** @@ -66,6 +82,20 @@ export class DefaultSearchHandle implements SearchHandle { }); } + /** + * Searches for Things that match the restrictions set in options using a http post request. + * + * @param options - Options to use for the search. + * @returns The search result + */ + postSearch(options?: SearchOptions): Promise { + return this.requestFactory.fetchFormRequest({ + verb: HttpVerb.POST, + parser: SearchThingsResponse.fromObject, + payload: options?.getOptions() + }); + } + /** * Counts the Things that match the restrictions set in options. * @@ -81,4 +111,18 @@ export class DefaultSearchHandle implements SearchHandle { }); } + /** + * Counts the Things that match the restrictions set in options using a http post request. + * + * @param options - Options to use for the search. + * @returns The count + */ + postCount(options?: CountOptions): Promise { + return this.requestFactory.fetchFormRequest({ + verb: HttpVerb.POST, + parser: Number, + path: 'count', + payload: options?.getOptions() + }); + } } diff --git a/javascript/lib/api/src/client/request-factory/http-request-sender.ts b/javascript/lib/api/src/client/request-factory/http-request-sender.ts index 3642222d..2d56ed7b 100644 --- a/javascript/lib/api/src/client/request-factory/http-request-sender.ts +++ b/javascript/lib/api/src/client/request-factory/http-request-sender.ts @@ -31,7 +31,7 @@ export class HttpRequestSender extends RequestSender { public fetchRequest(options: FetchRequest): Promise { return this.requester.doRequest(options.verb, this.buildUrl(options.id, options.path, options.requestOptions), - this.buildHeader(options.requestOptions), JSON.stringify(options.payload)) + this.buildHeader(options.requestOptions), options.payload) .then(response => { if (response.status >= 200 && response.status < 300) { return response; @@ -52,12 +52,7 @@ export class HttpRequestSender extends RequestSender { let urlSuffix = id === undefined ? '' : `/${id}`; urlSuffix = path === undefined ? urlSuffix : `${urlSuffix}/${path}`; if (options !== undefined && options.getOptions().size > 0) { - const values = options.getOptions(); - let result = ''; - values.forEach((v, k) => { - result += `&${k}=${v}`; - }); - urlSuffix = `${urlSuffix}?${result.substr(1)}`; + urlSuffix = `${urlSuffix}?${this.mapToQueryParams(options.getOptions())}`; } const baseUrlWithSuffix = this.baseUrl.withPath(`${this.baseUrl.path}${urlSuffix}`); const authenticatedBaseUrl = authenticateWithUrl(baseUrlWithSuffix, this.authenticationProviders); diff --git a/javascript/lib/api/src/client/request-factory/request-sender.ts b/javascript/lib/api/src/client/request-factory/request-sender.ts index 76cf3209..45f19aef 100644 --- a/javascript/lib/api/src/client/request-factory/request-sender.ts +++ b/javascript/lib/api/src/client/request-factory/request-sender.ts @@ -18,7 +18,6 @@ import { RequestOptions } from '../../options/request.options'; * Handle to send requests. */ export abstract class RequestSender { - /** * Fetches the specified request and returns an object of type T. * @@ -27,8 +26,31 @@ export abstract class RequestSender { * @returns A Promise for the specified object */ public fetchJsonRequest(options: ParseRequest): Promise { - return this.fetchRequest(options) - .then(response => options.parser(response.body)); + return this.fetchGenericJsonRequest(options).then(response => options.parser(response.body)); + } + + /** + * Fetches the specified request and returns an object of type T. + * + * @typeParam T - The type of object to return + * @param options - The options to use for the request. + * @returns A Promise for the specified object + */ + public fetchFormRequest(options: FetchFormRequest): Promise { + return this.fetchRequest({ + ...options, + payload: options.payload + ? this.mapToQueryParams(options.payload) + : undefined + }).then(response => options.parser(response.body)); + } + + protected mapToQueryParams(map: Map): string { + let result = ''; + map.forEach((value, key) => { + result += `&${key}=${value}`; + }); + return result.substring(1); } /** @@ -39,16 +61,32 @@ export abstract class RequestSender { * @returns A Promise for the specified object */ public fetchPutRequest(options: ParseRequest): Promise> { - return this.fetchRequest(options) - .then(response => { - if (response.status === 201) { - return new PutResponse(options.parser(response.body), response.status, response.headers); - } - if (response.status === 204) { - return new PutResponse(null, response.status, response.headers); - } - return Promise.reject(`Received unknown status code: ${response.status}`); - }); + return this.fetchGenericJsonRequest(options).then(response => { + if (response.status === 201) { + return new PutResponse( + options.parser(response.body), + response.status, + response.headers + ); + } + if (response.status === 204) { + return new PutResponse(null, response.status, response.headers); + } + return Promise.reject(`Received unknown status code: ${response.status}`); + }); + } + + /** + * Fetches the specified request and checks if the request was successful. + * + * @param options - The options to use for the request. + * @returns A Promise for the response + */ + public fetchGenericJsonRequest(options: FetchRequest): Promise { + return this.fetchRequest({ + ...options, + payload: JSON.stringify(options.payload) + }); } /** @@ -90,6 +128,14 @@ export interface FetchRequest { payload?: any; } +/** + * The options needed for a Request. + */ +export interface FetchFormRequest extends ParseRequest { + /** The payload to send with he request. */ + payload?: Map; +} + /** * The options needed for a Request that parses the server response. */ diff --git a/javascript/lib/api/src/client/request-factory/websocket-request-sender.ts b/javascript/lib/api/src/client/request-factory/websocket-request-sender.ts index af2d9607..e8daecf3 100644 --- a/javascript/lib/api/src/client/request-factory/websocket-request-sender.ts +++ b/javascript/lib/api/src/client/request-factory/websocket-request-sender.ts @@ -119,6 +119,16 @@ export class WebSocketRequestSender extends RequestSender { }); } + /** + * Fetches the specified request and checks if the request was successful. + * + * @param options - The options to use for the request. + * @returns A Promise for the response + */ + public fetchGenericJsonRequest(options: FetchRequest): Promise { + return this.fetchRequest(options); + } + /** * Sends a Message and returns the response. * diff --git a/javascript/lib/api/tests/client/http/search.http.spec.ts b/javascript/lib/api/tests/client/http/search.http.spec.ts index a56e48c1..6a5e98bf 100644 --- a/javascript/lib/api/tests/client/http/search.http.spec.ts +++ b/javascript/lib/api/tests/client/http/search.http.spec.ts @@ -14,6 +14,7 @@ import { HttpVerb } from '../../../src/client/constants/http-verb'; import { SearchHandle } from '../../../src/client/handles/search'; import { SearchThingsResponse } from '../../../src/model/response'; +import { DefaultSearchOptions } from '../../../src/options/request.options'; import { HttpHelper as H } from './http.helper'; describe('Http Search Handle', () => { @@ -21,7 +22,43 @@ describe('Http Search Handle', () => { const handle: SearchHandle = H.thingsClient.getSearchHandle(); const errorHandle: SearchHandle = H.errorThingsClient.getSearchHandle(); - it('gets the Thing', () => { + const searchOptions = DefaultSearchOptions.getInstance() + .withLimit(0, 25) + .withFilter( + 'and(like(definition,"*test*"),and(or(ilike(/attributes/test,"*test*"),eq(/definition,"test"))))' + ) + .withFields("thingId", "definition", "attributes", "features"); + + const expectedPayload = + "option=limit(0%2C25)&filter=and(like(definition%2C%22*test*%22)%2Cand(or(ilike(%2Fattributes%2Ftest%2C%22*test*%22)%2Ceq(%2Fdefinition%2C%22test%22))))&fields=thingId%2Cdefinition%2Cattributes%2Cfeatures"; + + + it('gets the Thing by post with search options', () => { + const response = new SearchThingsResponse([H.thing], -1); + return H.test({ + toTest: () => handle.postSearch(searchOptions), + testBody: response.toObject(), + expected: response, + payload:expectedPayload, + request: baseRequest, + method: HttpVerb.POST, + status: 200, + }); + }); + + it("gets the Thing by post", () => { + const response = new SearchThingsResponse([H.thing], -1); + return H.test({ + toTest: () => handle.postSearch(), + testBody: response.toObject(), + expected: response, + request: baseRequest, + method: HttpVerb.POST, + status: 200, + }); + }); + + it("gets the Thing", () => { const response = new SearchThingsResponse([H.thing], -1); return H.test({ toTest: () => handle.search(), @@ -44,6 +81,29 @@ describe('Http Search Handle', () => { }); }); + it("counts Things by post with search options", () => { + return H.test({ + toTest: () => handle.postCount(searchOptions), + testBody: 4, + expected: 4, + request: `${baseRequest}/count`, + method: HttpVerb.POST, + payload: expectedPayload, + status: 200, + }); + }); + + it("counts Things by post", () => { + return H.test({ + toTest: () => handle.postCount(), + testBody: 4, + expected: 4, + request: `${baseRequest}/count`, + method: HttpVerb.POST, + status: 200, + }); + }); + it('returns a search error message', () => { return H.testError(() => errorHandle.search()); });