Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions docs/docs/connect_your_rest_apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 29 additions & 11 deletions src/lib/api/contrib/elasticsearch/ESSearchApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
);
}
}

/**
Expand Down
31 changes: 27 additions & 4 deletions src/lib/api/contrib/elasticsearch/ESSearchApi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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');
});
});
41 changes: 22 additions & 19 deletions src/lib/api/contrib/invenio/InvenioSearchApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
);
}
}
Expand Down
27 changes: 23 additions & 4 deletions src/lib/api/contrib/invenio/InvenioSearchApi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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' });
Expand All @@ -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');
});
});