Something went wrong on our end
Select Git revision
Monster_DOM.html
-
Volker Schukai authoredVolker Schukai authored
restapi.mjs 8.24 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} from "../../constants.mjs";
import {isObject} from "../../types/is.mjs";
import {Datasource} from "../datasource.mjs";
import {Pathfinder} from "../pathfinder.mjs";
import {Pipe} from "../pipe.mjs";
import {WriteError} from "./restapi/writeerror.mjs";
export {RestAPI}
/**
* The RestAPI is a class that enables a REST API server.
*
* ```
* <script type="module">
* import {RestAPI} from '@schukai/monster/source/data/datasource/restapi.mjs';
* new RestAPI()
* </script>
* ```
*
* @example
*
* import {RestAPI} from '@schukai/monster/source/data/datasource/restapi.mjs';
*
* const ds = new RestAPI({
* url: 'https://httpbin.org/get'
* },{
* url: 'https://httpbin.org/post'
* });
*
* ds.set({flag:true})
* ds.write().then(()=>console.log('done'));
* ds.read().then(()=>console.log('done'));
*
* @license AGPLv3
* @since 1.22.0
* @copyright schukai GmbH
* @memberOf Monster.Data.Datasource
* @summary The LocalStorage class encapsulates the access to data objects.
*/
class RestAPI extends Datasource {
/**
*
* @param {Object} [readDefinition] An options object containing any custom settings that you want to apply to the read request.
* @param {Object} [writeDefinition] An options object containing any custom settings that you want to apply to the write request.
* @throws {TypeError} value is not a string
*/
constructor(readDefinition, writeDefinition) {
super();
const options = {}
if (isObject(readDefinition)) options.read = readDefinition;
if (isObject(writeDefinition)) options.write = writeDefinition;
this.setOptions(options);
}
/**
* @property {string} url=undefined Defines the resource that you wish to fetch.
* @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 {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 {Object} write.report
* @property {String} write.report.path Path to validations
* @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing.
* @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',
},
acceptedStatus: [200, 201],
url: undefined,
mapping: {
transformer: undefined,
callbacks: []
},
report: {
path: undefined
}
},
read: {
init: {
method: 'GET'
},
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 response;
let init = self.getOption('read.init');
if (!isObject(init)) init = {};
return fetch(self.getOption('read.url'), init).then(resp => {
response = resp;
const acceptedStatus = self.getOption('read.acceptedStatus', [200]);
if (acceptedStatus.indexOf(resp.status) === -1) {
throw Error('the data cannot be read (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 + ').');
}
let transformation = self.getOption('read.mapping.transformer');
if (transformation !== undefined) {
const pipe = new Pipe(transformation);
obj = pipe.run(obj);
}
self.set(obj);
return response;
})
}
/**
* @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'
}
}
let obj = self.get();
let transformation = self.getOption('write.mapping.transformer');
if (transformation !== undefined) {
const pipe = new Pipe(transformation);
obj = pipe.run(obj);
}
let sheathingObject = self.getOption('write.sheathing.object');
let sheathingPath = self.getOption('write.sheathing.path');
let reportPath = self.getOption('write.report.path');
if (sheathingObject && sheathingPath) {
const sub = obj;
obj = sheathingObject;
(new Pathfinder(obj)).setVia(sheathingPath, sub);
}
init['body'] = JSON.stringify(obj);
return fetch(self.getOption('write.url'), init).then(response => {
const acceptedStatus = self.getOption('write.acceptedStatus', [200, 2001]);
if (acceptedStatus.indexOf(response.status) === -1) {
return response.text().then((body) => {
let obj, validation;
try {
obj = JSON.parse(body);
validation = new Pathfinder(obj).getVia(reportPath)
} catch (e) {
if (body.length > 100) {
body = body.substring(0, 97) + '...';
}
throw new Error('the response does not contain a valid json (actual: ' + body + ').');
}
throw new WriteError('the data cannot be written (response ' + response.status + ')', response, validation)
})
}
return response;
});
}
/**
* @return {RestAPI}
*/
getClone() {
const self = this;
return new RestAPI(self[internalSymbol].getRealSubject()['options'].read, self[internalSymbol].getRealSubject()['options'].write);
}
}
/**
* This callback can be passed to a datasource and is used to adapt data structures.
*
* @callback Monster.Data.Datasource~exampleCallback
* @param {*} value Value
* @param {string} key Key
* @memberOf Monster.Data
* @see Monster.Data.Datasource
*/