vlabmichl
vlabmichl

Reputation: 43

Admin-On-REST custom REST client fails to compile

After starting with the Admin-On-REST Tutorial I tried to bind the app to my existing ArangoDB backend with a custom REST-Client implementation. The backend exposes a simple auto-generated "default" FOXX HTTP API.

That basically worked with the simple client. For better integration I'm aiming for using a custom client based on the json client. Therefore I copied the admin-on-rest/src/rest/jsonServer.js file into my project. I modified it to that file content:

import { stringify } from 'query-string';
import { fetchJson, flattenObject } from 'admin-on-rest';
import {
    GET_LIST,
    GET_ONE,
    GET_MANY,
    GET_MANY_REFERENCE,
    CREATE,
    UPDATE,
    DELETE,
} from 'admin-on-rest';

/**
 * Maps admin-on-rest queries to a json-server powered REST API
 *
 * @see https://github.com/typicode/json-server
 * @example
 * GET_LIST     => GET http://my.api.url/posts?_sort=title&_order=ASC&_start=0&_end=24
 * GET_ONE      => GET http://my.api.url/posts/123
 * GET_MANY     => GET http://my.api.url/posts/123, GET http://my.api.url/posts/456, GET http://my.api.url/posts/789
 * UPDATE       => PUT http://my.api.url/posts/123
 * CREATE       => POST http://my.api.url/posts/123
 * DELETE       => DELETE http://my.api.url/posts/123
 */
export default (apiUrl, httpClient = fetchJson) => {
    /**
     * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
     * @param {String} resource Name of the resource to fetch, e.g. 'posts'
     * @param {Object} params The REST request params, depending on the type
     * @returns {Object} { url, options } The HTTP request parameters
     */
    const convertRESTRequestToHTTP = (type, resource, params) => {
        let url = '';
        const options = {};
        switch (type) {
            case GET_LIST: {
                const { page, perPage } = params.pagination;
                const { field, order } = params.sort;
                const query = {
                    ...flattenObject(params.filter),
                    _sort: field,
                    _order: order,
                    _start: (page - 1) * perPage,
                    _end: page * perPage,
                };
                url = `${apiUrl}/${resource}?${stringify(query)}`;
                break;
            }
            case GET_ONE:
                url = `${apiUrl}/${resource}/${params.id}`;
                break;
            case GET_MANY_REFERENCE: {
                const { page, perPage } = params.pagination;
                const { field, order } = params.sort;
                const query = {
                    ...flattenObject(params.filter),
                    [params.target]: params.id,
                    _sort: field,
                    _order: order,
                    _start: (page - 1) * perPage,
                    _end: page * perPage,
                };
                url = `${apiUrl}/${resource}?${stringify(query)}`;
                break;
            }
            case UPDATE:
                url = `${apiUrl}/${resource}/${params.id}`;
                options.method = 'PUT';
                options.body = JSON.stringify(params.data);
                break;
            case CREATE:
                url = `${apiUrl}/${resource}`;
                options.method = 'POST';
                options.body = JSON.stringify(params.data);
                break;
            case DELETE:
                url = `${apiUrl}/${resource}/${params.id}`;
                options.method = 'DELETE';
                break;
            default:
                throw new Error(`Unsupported fetch action type ${type}`);
        }
        return { url, options };
    };

    /**
     * @param {Object} response HTTP response from fetch()
     * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
     * @param {String} resource Name of the resource to fetch, e.g. 'posts'
     * @param {Object} params The REST request params, depending on the type
     * @returns {Object} REST response
     */
    const convertHTTPResponseToREST = (response, type, resource, params) => {
        const { headers, json } = response;
        switch (type) {
            case GET_LIST:
            return {
                data: json.map((obj) => {
                  obj.id = obj._key;
                  delete obj['_key'];
                  return obj;
                }),
                total: headers.get('X-Total-Count')
            };
            case GET_MANY_REFERENCE:
                if (!headers.has('x-total-count')) {
                    throw new Error(
                        'The X-Total-Count header is missing in the HTTP Response. The jsonServer REST client expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?'
                    );
                }
                return {
                    data: json,
                    total: parseInt(
                        headers
                            .get('x-total-count')
                            .split('/')
                            .pop(),
                        10
                    ),
                };
            case CREATE:
                return { data: { ...params.data, id: json.id } };
            default:
                return { data: json };
        }
    };

    /**
     * @param {string} type Request type, e.g GET_LIST
     * @param {string} resource Resource name, e.g. "posts"
     * @param {Object} payload Request parameters. Depends on the request type
     * @returns {Promise} the Promise for a REST response
     */
    return (type, resource, params) => {
        // json-server doesn't handle WHERE IN requests, so we fallback to calling GET_ONE n times instead
        if (type === GET_MANY) {
            return Promise.all(
                params.ids.map(id => httpClient(`${apiUrl}/${resource}/${id}`))
            ).then(responses => ({
                data: responses.map(response => response.json),
            }));
        }

        const { url, options } = convertRESTRequestToHTTP(
            type,
            resource,
            params
        );

        return httpClient(url, options).then(response =>
            convertHTTPResponseToREST(response, type, resource, params)
        );
    };
};

It's much like the original - I only edited:

Finally I bound it into the App by importing it through the App.js file so that it looks like:

import CustomRestClientJson from './CustomRestClientJson'

const App = () => (
    <Admin restClient={CustomRestClientJson('http://localhost:8529/_db/orgraph/admin')}>
      <Resource name="assets" list={AssetList} />
    </Admin>
);

The problem is that:

a.) either the App fails in the browser with an Object(...) is not a function error (thrown by the call to the ...flattenObject(params.filter) call from within the clients convertRESTRequestToHTTP method) while trying to load the data.

b.) or the yarn build / compilation fails when I change the import statements of my client from:

import { stringify } from 'query-string';
import { fetchJson, flattenObject } from 'admin-on-rest';
import {
    GET_LIST,
    GET_ONE,
    GET_MANY,
    GET_MANY_REFERENCE,
    CREATE,
    UPDATE,
    DELETE,
} from 'admin-on-rest';

to:

import { stringify } from 'query-string';
import { fetchJson, flattenObject } from 'admin-on-rest/src/util/fetch';
import {
    GET_LIST,
    GET_ONE,
    GET_MANY,
    GET_MANY_REFERENCE,
    CREATE,
    UPDATE,
    DELETE
} from 'admin-on-rest/src/rest/types';

with the following error:

Failed to compile.

./node_modules/admin-on-rest/src/util/fetch.js
Module parse failed: Unexpected token (20:24)
You may need an appropriate loader to handle this file type.
|     }
|
|     return fetch(url, { ...options, headers: requestHeaders })
|         .then(response =>
|             response.text().then(text => ({

So, as I'm not a React programmer I'd love to hear about how I could fix this issue to be able to bind to my backend for further testing.

;-)

Thanks for any tips!

Upvotes: 3

Views: 604

Answers (1)

asliwinski
asliwinski

Reputation: 1714

I haven't read the whole description, but the error you got should be resolved by changing

import { fetchJson, flattenObject } from 'admin-on-rest/src/util/fetch';

to:

import { fetchJson, flattenObject } from 'admin-on-rest/lib/util/fetch';

Your project's babel will not transpile any code under node_modules. Whatever you import from outside of your src must be transpiled already.

Upvotes: 2

Related Questions