ketting
Version:
Opiniated HATEAOS / Rest client.
189 lines • 7.06 kB
JavaScript
;
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.factory = exports.HalState = void 0;
const base_state_1 = require("./base-state");
const util_1 = require("../http/util");
const link_1 = require("../link");
const uri_1 = require("../util/uri");
/**
* Represents a resource state in the HAL format
*/
class HalState extends base_state_1.BaseState {
serializeBody() {
return JSON.stringify(Object.assign({ _links: this.serializeLinks() }, this.data));
}
serializeLinks() {
const links = {
self: { href: this.uri },
};
for (const link of this.links.getAll()) {
const { rel, context } = link, attributes = __rest(link, ["rel", "context"]);
if (rel === 'self') {
// skip
continue;
}
if (links[rel] === undefined) {
// First link of its kind
links[rel] = attributes;
}
else if (Array.isArray(links[rel])) {
// Add link to link array.
links[rel].push(attributes);
}
else {
// 1 link with this rel existed, so we will transform it to an array.
links[rel] = [links[rel], attributes];
}
}
return links;
}
clone() {
return new HalState(this.uri, this.data, new Headers(this.headers), new link_1.Links(this.uri, this.links), [], this.actionInfo);
}
}
exports.HalState = HalState;
/**
* Turns a HTTP response into a HalState
*/
const factory = async (uri, response) => {
var _a;
const body = await response.json();
const links = util_1.parseLink(uri, response.headers.get('Link'));
// The HAL factory is also respondible for plain JSON, which might be an
// array.
if (Array.isArray(body)) {
return new HalState(uri, body, response.headers, links, []);
}
links.add(...parseHalLinks(uri, body));
// Remove _links and _embedded from body
const { _embedded, _links, _templates } = body, newBody = __rest(body, ["_embedded", "_links", "_templates"]);
const halForm = (_a = body._templates) === null || _a === void 0 ? void 0 : _a.default;
return new HalState(uri, newBody, response.headers, links, parseHalEmbedded(uri, body, response.headers), halForm ? [parseHalForm(uri, halForm)] : []);
};
exports.factory = factory;
/**
* Parse the Hal _links object and populate the 'links' property.
*/
function parseHalLinks(context, body) {
var _a, _b;
if (body._links === undefined) {
return [];
}
const result = [];
/**
* We're capturing all rel-link pairs so we don't duplicate them if they
* re-appear in _embedded.
*
* Links that are embedded _should_ appear in both lists, but not everyone
* does this.
*/
const foundLinks = new Set();
for (const [relType, links] of Object.entries(body._links)) {
const linkList = Array.isArray(links) ? links : [links];
for (const link of linkList) {
foundLinks.add(relType + ';' + link.href);
}
result.push(...parseHalLink(context, relType, linkList));
}
if (body._embedded) {
// eslint-disable-next-line prefer-const
for (let [rel, innerBodies] of Object.entries(body._embedded)) {
if (!Array.isArray(innerBodies)) {
innerBodies = [innerBodies];
}
for (const innerBody of innerBodies) {
const href = (_b = (_a = innerBody === null || innerBody === void 0 ? void 0 : innerBody._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href;
if (!href) {
continue;
}
if (foundLinks.has(rel + ';' + href)) {
continue;
}
result.push({
rel: rel,
href: href,
context: context,
});
}
}
}
return result;
}
/**
* Parses a single HAL link from a _links object
*/
function parseHalLink(context, rel, links) {
const result = [];
for (const link of links) {
result.push(Object.assign({ rel,
context }, link));
}
return result;
}
/**
* Parse the HAL _embedded object. Right now we're just grabbing the
* information from _embedded and turn it into links.
*/
function parseHalEmbedded(context, body, headers) {
if (body._embedded === undefined) {
return [];
}
const result = [];
for (const embedded of Object.values(body._embedded)) {
let embeddedList;
if (!Array.isArray(embedded)) {
embeddedList = [embedded];
}
else {
embeddedList = embedded;
}
for (const embeddedItem of embeddedList) {
if (embeddedItem._links === undefined || embeddedItem._links.self === undefined || Array.isArray(embeddedItem._links.self)) {
// Skip any embedded without a self link.
continue;
}
// Remove _links and _embedded from body
const { _embedded, _links } = embeddedItem, newBody = __rest(embeddedItem, ["_embedded", "_links"]);
result.push(new HalState(uri_1.resolve(context, embeddedItem._links.self.href), newBody, new Headers({
'Content-Type': headers.get('Content-Type'),
}), new link_1.Links(context, parseHalLinks(context, embeddedItem)),
// Parsing nested embedded items. Note that we assume that the base url is relative to
// the outermost parent, not relative to the embedded item. HAL is not clear on this.
parseHalEmbedded(context, embeddedItem, headers)));
}
}
return result;
}
function parseHalForm(context, templ) {
return {
uri: context,
name: 'default',
title: templ.title,
method: templ.method,
contentType: templ.contentType || 'application/json',
fields: templ.properties ? templ.properties.map(prop => parseHalField(prop)) : [],
};
}
function parseHalField(halField) {
return {
name: halField.name,
type: 'text',
required: halField.required || false,
readOnly: halField.readOnly || false,
value: halField.value,
pattern: halField.regex ? new RegExp(halField.regex) : undefined,
label: halField.prompt,
};
}
//# sourceMappingURL=hal.js.map