Skip to content
Snippets Groups Projects
Select Git revision
  • b96c4d10615d673c51c26197d23bb26285598040
  • master default protected
  • 1.2.4
  • 1.2.3
  • 1.2.2
  • 1.2.1
  • 1.2.0
  • v1.1.0
8 results

index.html

Blame
  • restapi.mjs 6.84 KiB
    /**
     * Copyright schukai GmbH and contributors 2022. All Rights Reserved.
     * Node module: @schukai/monster
     * This file is licensed under the AGPLv3 License.
     * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
     */
    
    import { internalSymbol, instanceSymbol } from "../../../constants.mjs";
    import { isObject } from "../../../types/is.mjs";
    import { Server } from "../server.mjs";
    import { WriteError } from "./restapi/writeerror.mjs";
    
    export { RestAPI };
    
    /**
     * The RestAPI is a class that enables a REST API server.
     *
     * @externalExample ../../../../example/data/datasource/server/restapi.mjs
     * @license AGPLv3
     * @since 1.22.0
     * @copyright schukai GmbH
     * @memberOf Monster.Data.Datasource.Server
     * @summary The RestAPI is a class that binds a REST API server.
     */
    class RestAPI extends Server {
        /**
         *
         * @param {Object} [options] options contains definitions for the datasource.
         */
        constructor(options) {
            super();
    
            if (isObject(options)) {
                this.setOptions(options);
            }
        }
    
        /**
         * This method is called by the `instanceof` operator.
         * @returns {symbol}
         * @since 2.1.0
         */
        static get [instanceSymbol]() {
            return Symbol.for("@schukai/monster/data/datasource/server/restapi");
        }
    
        /**
         * @property {Object} write={} Options
         * @property {Object} write.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor}
         * @property {string} write.init.method=POST
         * @property {Object} write.init.headers Object containing any custom headers that you want to apply to the request.
         * @property {string} write.responseCallback Callback function to be executed after the request has been completed.
         * @property {string} write.acceptedStatus=[200,201]
         * @property {string} write.url URL
         * @property {Object} write.mapping the mapping is applied before writing.
         * @property {String} write.mapping.transformer Transformer to select the appropriate entries
         * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing.
         * @property {Object} write.report
         * @property {String} write.report.path Path to validations
         * @property {Object} write.sheathing
         * @property {Object} write.sheathing.object Object to be wrapped
         * @property {string} write.sheathing.path Path to the data
         * @property {Object} read={} Options
         * @property {Object} read.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor}
         * @property {string} read.init.method=GET
         * @property {string} read.acceptedStatus=[200]
         * @property {string} read.url URL
         * @property {Object} read.mapping the mapping is applied after reading.
         * @property {String} read.mapping.transformer Transformer to select the appropriate entries
         * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading.
         */
        get defaults() {
            return Object.assign({}, super.defaults, {
                write: {
                    init: {
                        method: "POST",
                    },
                    responseCallback: undefined,
                    acceptedStatus: [200, 201],
                    url: undefined,
                    mapping: {
                        transformer: undefined,
                        callbacks: [],
                    },
                    sheathing: {
                        object: undefined,
                        path: undefined,
                    },
                    report: {
                        path: undefined,
                    },
                },
                read: {
                    init: {
                        method: "GET",
                    },
                    responseCallback: undefined,
                    acceptedStatus: [200],
                    url: undefined,
                    mapping: {
                        transformer: undefined,
                        callbacks: [],
                    },
                },
            });
        }
    
        /**
         * @return {Promise}
         * @throws {Error} the options does not contain a valid json definition
         * @throws {TypeError} value is not a object
         * @throws {Error} the data cannot be read
         */
        read() {
            const self = this;
    
            let init = self.getOption("read.init");
            if (!isObject(init)) init = {};
            if (!init["method"]) init["method"] = "GET";
    
            let callback = self.getOption("read.responseCallback");
            if(!callback) callback = (obj) => {
                self.set(self.transformServerPayload.call(self, obj));
            };
            
            return fetchData.call(this, "read", callback);
        }
    
        /**
         * @return {Promise}
         * @throws {WriteError} the data cannot be written
         */
        write() {
            const self = this;
    
            let init = self.getOption("write.init");
            if (!isObject(init)) init = {};
            if (typeof init["headers"] !== "object") {
                init["headers"] = {
                    "Content-Type": "application/json",
                };
            }
            if (!init["method"]) init["method"] = "POST";
    
            let obj = self.prepareServerPayload(self.get());
            init["body"] = JSON.stringify(obj);
    
            let callback = self.getOption("write.responseCallback");
            return fetchData.call(this, init, "write", callback);
        }
    
        /**
         * @return {RestAPI}
         */
        getClone() {
            const self = this;
            return new RestAPI(
                self[internalSymbol].getRealSubject()["options"].read,
                self[internalSymbol].getRealSubject()["options"].write,
            );
        }
    }
    
    function fetchData(init, key, callback) {
        const self = this;
        let response;
    
        return fetch(self.getOption(`${key}.url`), init)
            .then((resp) => {
                response = resp;
    
                const acceptedStatus = self.getOption(`${key}.acceptedStatus`, [200]);
    
                if (acceptedStatus.indexOf(resp.status) === -1) {
                    throw Error(`the data cannot be ${key} (response ${resp.status})`);
                }
    
                return resp.text();
            })
            .then((body) => {
                let obj;
    
                try {
                    obj = JSON.parse(body);
                } catch (e) {
                    if (body.length > 100) {
                        body = `${body.substring(0, 97)}...`;
                    }
    
                    throw new Error(`the response does not contain a valid json (actual: ${body}).`);
                }
    
                if (callback && isFunction(callback)) {
                    callback(obj);
                }
                return response;
            });
    }