diff --git a/docs/docs/connect_your_rest_apis.md b/docs/docs/connect_your_rest_apis.md index 2c8f3842..ceac9bae 100644 --- a/docs/docs/connect_your_rest_apis.md +++ b/docs/docs/connect_your_rest_apis.md @@ -16,8 +16,10 @@ The `Elasticsearch` adapter can be configured by passing an object. The configur ```jsx const searchApi = new ESSearchAPI({ - url: 'https://my.es.backend.org/search/', - timeout: 5000 + axios: { + url: 'https://my.es.backend.org/search/', + timeout: 5000, + } }); class App extends Component { @@ -37,9 +39,11 @@ The `Invenio` adapter works in a very similar way: ```jsx const searchApi = new InvenioSearchApi({ - url: 'https://zenodo.org/api/records/', - timeout: 5000, - headers: { Accept: 'application/vnd.zenodo.v1+json' }, + axios: { + url: 'https://zenodo.org/api/records/', + timeout: 5000, + headers: { Accept: 'application/vnd.zenodo.v1+json' }, + } }); class App extends Component { @@ -97,8 +101,10 @@ const MyRequestSerializer = new MyRequestSerializer(); const MyResponseSerializer = new MyResponseSerializer(); const searchApi = new ESSearchAPI({ - url: 'https://my.es.backend.org/search/', - timeout: 5000, + axios: { + url: 'https://my.es.backend.org/search/', + timeout: 5000, + }, es: { requestSerializer: MyRequestSerializer, responseSerializer: MyResponseSerializer, diff --git a/src/lib/api/contrib/elasticsearch/ESSearchApi.js b/src/lib/api/contrib/elasticsearch/ESSearchApi.js index ceb6a62a..6b35eee8 100644 --- a/src/lib/api/contrib/elasticsearch/ESSearchApi.js +++ b/src/lib/api/contrib/elasticsearch/ESSearchApi.js @@ -14,17 +14,24 @@ import { ESResponseSerializer } from './ESResponseSerializer'; export class ESSearchApi { constructor(config) { - this.validateConfig(config); + this.axiosConfig = _get(config, 'axios', {}); + this.validateAxiosConfig(); this.initSerializers(config); - this.initAxios(config); + this.initInterceptors(config); + this.initAxios(); } - validateConfig(config) { - if (!_hasIn(config, 'url')) { + validateAxiosConfig() { + if (!_hasIn(this.axiosConfig, 'url')) { throw new Error('ESSearchApi config: `node` field is required.'); } } + initInterceptors(config) { + this.requestInterceptor = _get(config, 'interceptors.request', undefined); + this.responseInterceptor = _get(config, 'interceptors.response', undefined); + } + initSerializers(config) { const requestSerializerCls = _get( config, @@ -41,13 +48,24 @@ export class ESSearchApi { this.responseSerializer = new responseSerializerCls(); } - initAxios(config) { - delete config.es; - const axiosConfig = { - baseURL: config.url, - ...config, - }; - this.http = axios.create(axiosConfig); + initAxios() { + this.http = axios.create(this.axiosConfig); + this.addInterceptors(); + } + + addInterceptors() { + if (this.requestInterceptor) { + this.http.interceptors.request.use( + this.requestInterceptor.resolve, + this.requestInterceptor.reject + ); + } + if (this.responseInterceptor) { + this.http.interceptors.request.use( + this.responseInterceptor.resolve, + this.responseInterceptor.reject + ); + } } /** diff --git a/src/lib/api/contrib/elasticsearch/ESSearchApi.test.js b/src/lib/api/contrib/elasticsearch/ESSearchApi.test.js index 4a4d8d8a..31624949 100644 --- a/src/lib/api/contrib/elasticsearch/ESSearchApi.test.js +++ b/src/lib/api/contrib/elasticsearch/ESSearchApi.test.js @@ -25,9 +25,11 @@ class MockedResponseSerializer { describe('test ESSearchApi class', () => { it('should use the provided configuration', async () => { const searchApi = new ESSearchApi({ - url: 'https://mydomain.test.com/api/', - timeout: 5000, - headers: { Accept: 'application/json' }, + axios: { + url: 'https://mydomain.test.com/api/', + timeout: 5000, + headers: { Accept: 'application/json' }, + }, es: { requestSerializer: MockedRequestSerializer, responseSerializer: MockedResponseSerializer, @@ -48,11 +50,32 @@ describe('test ESSearchApi class', () => { expect(mockedAxios.history.post.length).toBe(1); const request = mockedAxios.history.post[0]; - expect(request.baseURL).toBe('https://mydomain.test.com/api/'); + expect(request.url).toBe('https://mydomain.test.com/api/'); expect(request.method).toBe('post'); expect(request.timeout).toBe(5000); expect(request.data).toEqual('q=test'); expect(response).toEqual(mockedResponse); }); + + it('should properly use relative URLs', async () => { + const searchApi = new ESSearchApi({ + axios: { + url: '/api/records', + }, + es: { + requestSerializer: MockedRequestSerializer, + responseSerializer: MockedResponseSerializer, + }, + }); + const mockedAxios = new MockAdapter(searchApi.http); + + const mockedResponse = { hits: [{ result: '1' }] }; + mockedAxios.onAny().reply(200, mockedResponse); + await searchApi.search({ q: 'test' }); + expect(mockedAxios.history.post.length).toBe(1); + + const request = mockedAxios.history.post[0]; + expect(request.url).toBe('/api/records'); + }); }); diff --git a/src/lib/api/contrib/invenio/InvenioSearchApi.js b/src/lib/api/contrib/invenio/InvenioSearchApi.js index 947a929a..6d9a51d5 100644 --- a/src/lib/api/contrib/invenio/InvenioSearchApi.js +++ b/src/lib/api/contrib/invenio/InvenioSearchApi.js @@ -14,17 +14,24 @@ import { InvenioResponseSerializer } from './InvenioResponseSerializer'; export class InvenioSearchApi { constructor(config) { - this.validateConfig(config); + this.axiosConfig = _get(config, 'axios', {}); + this.validateAxiosConfig(); this.initSerializers(config); - this.initAxios(config); + this.initInterceptors(config); + this.initAxios(); } - validateConfig(config) { - if (!_hasIn(config, 'url')) { + validateAxiosConfig() { + if (!_hasIn(this.axiosConfig, 'url')) { throw new Error('InvenioSearchApi config: `url` field is required.'); } } + initInterceptors(config) { + this.requestInterceptor = _get(config, 'interceptors.request', undefined); + this.responseInterceptor = _get(config, 'interceptors.response', undefined); + } + initSerializers(config) { const requestSerializerCls = _get( config, @@ -41,30 +48,26 @@ export class InvenioSearchApi { this.responseSerializer = new responseSerializerCls(); } - initAxios(config) { - delete config.invenio; + initAxios() { const axiosConfig = { paramsSerializer: this.requestSerializer.serialize, - baseURL: config.url, // transform URL to baseURL to have clean external APIs - ...config, + ...this.axiosConfig, }; this.http = axios.create(axiosConfig); - if (config.interceptors) { - this.addInterceptors(config.interceptors); - } + this.addInterceptors(); } - addInterceptors(interceptors) { - if (interceptors.request) { + addInterceptors() { + if (this.requestInterceptor) { this.http.interceptors.request.use( - interceptors.request.resolve, - interceptors.request.reject + this.requestInterceptor.resolve, + this.requestInterceptor.reject ); } - if (interceptors.response) { - this.http.interceptors.response.use( - interceptors.response.resolve, - interceptors.response.reject + if (this.responseInterceptor) { + this.http.interceptors.request.use( + this.responseInterceptor.resolve, + this.responseInterceptor.reject ); } } diff --git a/src/lib/api/contrib/invenio/InvenioSearchApi.test.js b/src/lib/api/contrib/invenio/InvenioSearchApi.test.js index ae4eba64..f1c29a51 100644 --- a/src/lib/api/contrib/invenio/InvenioSearchApi.test.js +++ b/src/lib/api/contrib/invenio/InvenioSearchApi.test.js @@ -25,9 +25,11 @@ class MockedResponseSerializer { describe('test InvenioSearchApi class', () => { it('should use the provided configuration', async () => { const searchApi = new InvenioSearchApi({ - url: 'https://mydomain.test.com/api/', - timeout: 5000, - headers: { Accept: 'application/json' }, + axios: { + url: 'https://mydomain.test.com/api/', + timeout: 5000, + headers: { Accept: 'application/json' }, + }, invenio: { requestSerializer: MockedRequestSerializer, responseSerializer: MockedResponseSerializer, @@ -48,7 +50,7 @@ describe('test InvenioSearchApi class', () => { expect(mockedAxios.history.get.length).toBe(1); const request = mockedAxios.history.get[0]; - expect(request.baseURL).toBe('https://mydomain.test.com/api/'); + expect(request.url).toBe('https://mydomain.test.com/api/'); expect(request.method).toBe('get'); expect(request.timeout).toBe(5000); expect(request.headers).toEqual({ Accept: 'application/json' }); @@ -57,4 +59,21 @@ describe('test InvenioSearchApi class', () => { expect(response).toEqual(mockedResponse); }); + + it('should properly use relative URLs', async () => { + const searchApi = new InvenioSearchApi({ + axios: { + url: '/api/records', + }, + }); + const mockedAxios = new MockAdapter(searchApi.http); + + const mockedResponse = { hits: [{ result: '1' }] }; + mockedAxios.onAny().reply(200, mockedResponse); + await searchApi.search({ q: 'test' }); + expect(mockedAxios.history.get.length).toBe(1); + + const request = mockedAxios.history.get[0]; + expect(request.url).toBe('/api/records'); + }); });