semantic-network
Version:
A utility library for manipulating a list of links that form a semantic interface to a network of resources.
506 lines • 27.9 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldResolverUtil = void 0;
var semantic_link_1 = require("semantic-link");
var formTypes_1 = require("../types/formTypes");
var linkRelation_1 = require("../linkRelation");
var anylogger_1 = __importDefault(require("anylogger"));
var formUtil_1 = require("./formUtil");
var resourceMergeFactory_1 = require("../representation/resourceMergeFactory");
var instanceOfCollection_1 = require("./instanceOf/instanceOfCollection");
var instanceOfDocumentRepresentationGroup_1 = require("./instanceOf/instanceOfDocumentRepresentationGroup");
var instanceOfDocumentRepresentation_1 = require("./instanceOf/instanceOfDocumentRepresentation");
var instanceOfUriListValue_1 = require("./instanceOf/instanceOfUriListValue");
var instanceOfSimpleValue_1 = require("./instanceOf/instanceOfSimpleValue");
var log = (0, anylogger_1.default)('FieldResolverUtil');
/**
* An enumeration across the return type from a form field
*/
var FieldValueType;
(function (FieldValueType) {
/**
* The value is single
*/
FieldValueType[FieldValueType["single"] = 0] = "single";
/**
* The value is contained in an array
*
* Note: this closely aligns with the {@link FormItem.multiple} flag when set of the {@link FieldType.Select}
*/
FieldValueType[FieldValueType["multiple"] = 1] = "multiple";
/**
* The value is contained in an object
*/
FieldValueType[FieldValueType["group"] = 2] = "group";
})(FieldValueType || (FieldValueType = {}));
/**
* Mapping strategy between the across-the-wire field types and internal types
*
* TODO: this needs to be injectable/extendable
*/
function formType(formItem) {
switch (formItem.type) {
case formTypes_1.FieldType.Select:
return FieldValueType.multiple;
case formTypes_1.FieldType.Group:
case formTypes_1.FieldType.Collection:
return FieldValueType.group;
case formTypes_1.FieldType.Text:
case formTypes_1.FieldType.TextArea:
case formTypes_1.FieldType.Password:
case formTypes_1.FieldType.Address:
case formTypes_1.FieldType.Email:
case formTypes_1.FieldType.Uri:
case formTypes_1.FieldType.Currency:
case formTypes_1.FieldType.Number:
case formTypes_1.FieldType.Height:
case formTypes_1.FieldType.Checkbox:
case formTypes_1.FieldType.Date:
case formTypes_1.FieldType.DateTime:
case formTypes_1.FieldType.Tel:
case formTypes_1.FieldType.EmailList:
case formTypes_1.FieldType.Signature:
case formTypes_1.FieldType.AddressPostal:
default:
return FieldValueType.single;
}
}
var FieldResolverUtil = /** @class */ (function () {
function FieldResolverUtil() {
}
/**
* Recursive, resolving merger that adds fields to a document based on a (create or edit) form
* and the allowed fields with their type.
*
* Note: field names are converted to tracked names
*/
FieldResolverUtil.resolveFields = function (document, form, options) {
return __awaiter(this, void 0, void 0, function () {
var defaultFields, fieldsToResolve, fieldsToResolve_1, fieldsToResolve_1_1, field, formItem, fieldValue, fieldResolver, e_1_1;
var e_1, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
defaultFields = __assign({}, options).defaultFields;
log.debug('resolving fields on \'%s\'', semantic_link_1.LinkUtil.getUri(document, linkRelation_1.LinkRelation.Self));
fieldsToResolve = formUtil_1.FormUtil.fieldsToResolve(document, form, defaultFields);
_b.label = 1;
case 1:
_b.trys.push([1, 7, 8, 9]);
fieldsToResolve_1 = __values(fieldsToResolve), fieldsToResolve_1_1 = fieldsToResolve_1.next();
_b.label = 2;
case 2:
if (!!fieldsToResolve_1_1.done) return [3 /*break*/, 6];
field = fieldsToResolve_1_1.value;
formItem = formUtil_1.FormUtil.findByField(form, field);
if (!formItem) return [3 /*break*/, 4];
return [4 /*yield*/, this.resolve(document[field], formItem, options)];
case 3:
fieldValue = _b.sent();
if (fieldValue) {
fieldResolver = __assign({}, options).fieldResolver;
log.debug('resolving field \'%s\'', field);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS2538: Type 'Omit ' cannot be used as an index type.
(document)[field] = fieldResolver ?
fieldResolver(field, fieldValue, options) :
fieldValue;
}
else {
// an undefined result adds 'undefined' to field rather than remove
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS2538: Type 'Omit ' cannot be used as an index type.
document[field] = fieldValue;
// catering 'false' which would log this error (probably do !! on if)
if (fieldValue === undefined || fieldValue === null) {
log.warn('Field \'%s\' is not resolved', field);
}
}
return [3 /*break*/, 5];
case 4:
log.info('Field \'%s\' is not matched in form', field);
_b.label = 5;
case 5:
fieldsToResolve_1_1 = fieldsToResolve_1.next();
return [3 /*break*/, 2];
case 6: return [3 /*break*/, 9];
case 7:
e_1_1 = _b.sent();
e_1 = { error: e_1_1 };
return [3 /*break*/, 9];
case 8:
try {
if (fieldsToResolve_1_1 && !fieldsToResolve_1_1.done && (_a = fieldsToResolve_1.return)) _a.call(fieldsToResolve_1);
}
finally { if (e_1) throw e_1.error; }
return [7 /*endfinally*/];
case 9: return [2 /*return*/, document];
}
});
});
};
/**
* Takes an incoming polymorphic field ({@link SimpleValue}, {@link UriListValue} or {@link ResourceValue}) and
* checks if its value is valid against the form. To do this, it will also resolve fields if necessary.
*
* Resolutions are performed via injected {@link MergeOptions.resolver} and {@link MergeOptions.resourceResolver}
*
* @param fieldValue
* @param item
* @param options?
*/
FieldResolverUtil.resolve = function (fieldValue, item, options) {
return __awaiter(this, void 0, void 0, function () {
var _a, resolver, field, resource, uri;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = __assign({}, options).resolver, resolver = _a === void 0 ? resourceMergeFactory_1.noopResolver : _a;
return [4 /*yield*/, this.resolveByType(fieldValue, item, options)];
case 1:
field = _b.sent();
if (field && (0, instanceOfSimpleValue_1.instanceOfSimpleValue)(field)) {
return [2 /*return*/, resolver.resolve(field)];
}
else if ((0, instanceOfUriListValue_1.instanceOfUriListValue)(field)) {
return [2 /*return*/, field.map(function (uri) { return resolver.resolve(uri); })];
}
if (!(0, semantic_link_1.instanceOfLinkedRepresentation)(fieldValue)) return [3 /*break*/, 3];
return [4 /*yield*/, this.resolveResource(fieldValue, item, options)];
case 2:
resource = _b.sent();
if (resource) {
uri = semantic_link_1.LinkUtil.getUri(resource, linkRelation_1.LinkRelation.Self);
if (uri) {
return [2 /*return*/, resolver.resolve(uri)];
}
}
_b.label = 3;
case 3:
log.debug('Field type unknown - returning default value');
return [2 /*return*/, fieldValue];
}
});
});
};
/**
* Resolves any {@link FormItem.id} as a {@link LinkedRepresentation} when there is a {@link MergeOptions.resourceResolver}.
*
* Note:
* - current strategy to resolve on the title of the Canonical on the field value.
* - there is no placement of the resolution onto the item
*
* @param formItem form item to process for resolution
* @param fieldValue
* @param options
*/
FieldResolverUtil.resolveResource = function (fieldValue, formItem, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var _b, _c, resourceResolverRelNameStrategy, resourceResolver, relName, item;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_b = __assign({}, options), _c = _b.resourceResolverRelNameStrategy, resourceResolverRelNameStrategy = _c === void 0 ? this.resourceResolverRelNameStrategy : _c, resourceResolver = _b.resourceResolver;
relName = resourceResolverRelNameStrategy(fieldValue);
if (!relName) return [3 /*break*/, 5];
item = (_a = formItem.items) === null || _a === void 0 ? void 0 : _a.find(function (x) { return x.name === relName; });
if (!item) return [3 /*break*/, 4];
if (!item.id) return [3 /*break*/, 3];
if (!resourceResolver) return [3 /*break*/, 2];
log.debug('matching items collection with resolver relName \'%s\' on %s', relName, formItem.id);
return [4 /*yield*/, resourceResolver(relName)(fieldValue, options)];
case 1: return [2 /*return*/, _d.sent()];
case 2: return [3 /*break*/, 4];
case 3:
log.debug('form item might expected \id\ attribute of resource');
_d.label = 4;
case 4: return [3 /*break*/, 6];
case 5:
log.bind('rel name strategy did not return name on %s', semantic_link_1.LinkUtil.getUri(fieldValue, linkRelation_1.LinkRelation.Self));
_d.label = 6;
case 6: return [2 /*return*/, fieldValue];
}
});
});
};
/**
*
* Strategy is to resolve the lookup key as the title from the Canonical link relation
* @param resource field resource being resolved
*/
FieldResolverUtil.resourceResolverRelNameStrategy = function (resource) {
return semantic_link_1.LinkUtil.getTitle(resource, linkRelation_1.LinkRelation.Canonical);
};
/**
* If a {@link MergeOptions.resolver} has been provided then there is potentially a pooled resource available
* requiring resolution
*
* @param fieldValue
* @param item
* @param options contains the resolver to be used
*/
FieldResolverUtil.resolveByPooled = function (fieldValue, item, options) {
return __awaiter(this, void 0, void 0, function () {
var _a, resolver, resource, uri;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = __assign({}, options).resolver, resolver = _a === void 0 ? resourceMergeFactory_1.noopResolver : _a;
if (!(0, instanceOfSimpleValue_1.instanceOfSimpleValue)(fieldValue)) return [3 /*break*/, 1];
return [2 /*return*/, resolver.resolve(fieldValue)];
case 1:
if (!(0, instanceOfUriListValue_1.instanceOfUriListValue)(fieldValue)) return [3 /*break*/, 2];
return [2 /*return*/, fieldValue.map(function (uri) { return resolver.resolve(uri); })];
case 2:
if (!(0, semantic_link_1.instanceOfLinkedRepresentation)(fieldValue)) return [3 /*break*/, 5];
return [4 /*yield*/, this.resolveResource(fieldValue, item, options)];
case 3:
resource = _b.sent();
if (!resource) return [3 /*break*/, 5];
uri = semantic_link_1.LinkUtil.getUri(resource, linkRelation_1.LinkRelation.Self);
if (!uri) return [3 /*break*/, 5];
return [4 /*yield*/, this.resolveByPooled(uri, item, options)];
case 4: return [2 /*return*/, _b.sent()];
case 5:
log.debug('Field type unknown - returning default value');
return [2 /*return*/, fieldValue];
}
});
});
};
/**
*
* @param fieldValue
* @param formItem
* @param options
*/
FieldResolverUtil.resolveByType = function (fieldValue, formItem, options) {
return __awaiter(this, void 0, void 0, function () {
var _a, value, resourceResolver;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = formType(formItem);
switch (_a) {
case FieldValueType.single: return [3 /*break*/, 1];
case FieldValueType.multiple: return [3 /*break*/, 2];
case FieldValueType.group: return [3 /*break*/, 7];
}
return [3 /*break*/, 14];
case 1:
if (fieldValue && !(0, instanceOfSimpleValue_1.instanceOfSimpleValue)(fieldValue) && !(typeof fieldValue === 'object')) {
log.warn('Unexpected type \'%s\' on form type %s with \'%s\'', typeof fieldValue, formItem.type, formItem.name);
return [2 /*return*/, undefined];
}
if (formItem.multiple) {
log.warn('Unexpected attribute \'multiple\' on form type %s with \'%s\'', formItem.type, formItem.name);
}
return [2 /*return*/, fieldValue];
case 2: return [4 /*yield*/, formUtil_1.FormUtil.resolveItemsFromCollection(formItem, options)];
case 3:
_b.sent();
return [4 /*yield*/, this.findValueInItems(formItem, fieldValue, options)];
case 4:
value = _b.sent();
if (!(!value && (0, instanceOfDocumentRepresentation_1.instanceOfDocumentRepresentation)(fieldValue))) return [3 /*break*/, 6];
resourceResolver = __assign({}, options).resourceResolver;
if (!resourceResolver) return [3 /*break*/, 6];
return [4 /*yield*/, resourceResolver(formItem.name)(fieldValue, options)];
case 5:
value = _b.sent();
_b.label = 6;
case 6:
// check the value returned needs to be an enumeration.
if (formItem.multiple) {
// return back an Uri[] removing all undefined to meet {@link UriListValue}
return [2 /*return*/, ((0, instanceOfSimpleValue_1.instanceOfSimpleValue)(value) ? [value] : value)
.filter(function (x) { return !!x; })];
// check that the value is part of the provided items enumeration in the form
}
else {
// undefined is acceptable as it is {@link SimpleValue}
return [2 /*return*/, value];
}
_b.label = 7;
case 7:
if (!formItem.multiple) return [3 /*break*/, 11];
if (!(0, instanceOfDocumentRepresentationGroup_1.instanceOfDocumentRepresentationGroup)(fieldValue)) return [3 /*break*/, 9];
return [4 /*yield*/, Promise.all(fieldValue.map(function (value) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!value) return [3 /*break*/, 2];
return [4 /*yield*/, this.resolveFields(value, formItem, options)];
case 1: return [2 /*return*/, _a.sent()];
case 2: return [2 /*return*/];
}
});
}); }))];
case 8: return [2 /*return*/, (_b.sent())
// remove any undefined
.filter(function (x) { return !!x; })];
case 9:
log.error('Group with multiple must be implemented as an array of Document Representation');
_b.label = 10;
case 10: return [3 /*break*/, 13];
case 11:
if (!(0, instanceOfDocumentRepresentation_1.instanceOfDocumentRepresentation)(fieldValue)) {
log.warn('Group must be implemented a DocumentRepresentation');
}
return [4 /*yield*/, this.resolveFields(fieldValue, formItem, options)];
case 12:
// otherwise it is a single group and resolve treating the object as a LinkedRepresentation
// throw new Error('Group not implemented');
return [2 /*return*/, _b.sent()];
case 13: return [3 /*break*/, 15];
case 14:
log.warn('Unknown form type \'%s\' on \'%s\'', formItem.type, formItem.name);
return [2 /*return*/, undefined];
case 15: return [2 /*return*/];
}
});
});
};
/**
* Matches a field value against the form items switching across {@link FieldValue} type.
*
* - {@link SimpleValue} - look through the list by value
* - {@link UriListValue} - look across the list by value
* - {@link ResourceValue} - look into the resource's {@link LinkRelation.Canonical} to locate the 'title'
*
* @param formItem a form item (with items eager loaded)
* @param fieldValue
* @param options
*/
FieldResolverUtil.findValueInItems = function (formItem, fieldValue, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var uri_1;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!(fieldValue && (formItem === null || formItem === void 0 ? void 0 : formItem.items))) return [3 /*break*/, 5];
if (!(0, instanceOfSimpleValue_1.instanceOfSimpleValue)(fieldValue)) return [3 /*break*/, 1];
if (formItem.items.some(function (item) { return item.value === fieldValue; })) {
return [2 /*return*/, fieldValue];
}
return [3 /*break*/, 4];
case 1:
if (!(0, instanceOfUriListValue_1.instanceOfUriListValue)(fieldValue)) return [3 /*break*/, 3];
return [4 /*yield*/, Promise.all(fieldValue.map(function (val) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.findValueInItems(formItem, val, options)];
case 1: return [2 /*return*/, _a.sent()];
}
}); }); }))];
case 2: return [2 /*return*/, _b.sent()];
case 3:
if ((0, instanceOfDocumentRepresentation_1.instanceOfDocumentRepresentation)(fieldValue)) {
// a lazy-loaded set of items is currently loaded from a collection
if ((0, instanceOfCollection_1.instanceOfCollection)(formItem.items)) {
if ((0, semantic_link_1.instanceOfLinkedRepresentation)(fieldValue)) {
uri_1 = semantic_link_1.LinkUtil.getUri(fieldValue, linkRelation_1.LinkRelation.Self);
if (uri_1) {
if (formItem.items.items.some(function (item) { return semantic_link_1.LinkUtil.getUri(item, linkRelation_1.LinkRelation.Self) === uri_1; })) {
return [2 /*return*/, uri_1]; // return type Uri
} // else fall through to return undefined
}
else {
log.warn('Field value not a linked representation');
}
} // else fall through to return undefined
}
else {
if (Array.isArray(formItem.items)) {
log.debug('Form items already loaded or recursively nothing to load: count \'%s\'', (_a = formItem.items) === null || _a === void 0 ? void 0 : _a.length);
}
else {
log.warn('Form items type not found \'%s\'', typeof fieldValue);
}
// else fall through to return undefined
}
}
else {
log.error('Field value type not found \'%s\'', typeof fieldValue);
// else fall through to return undefined
}
_b.label = 4;
case 4: return [3 /*break*/, 6];
case 5:
log.warn('No items on form \'%s\'', formItem.name);
_b.label = 6;
case 6:
log.debug('Value \'%s\' is not found on \'%s\' - still allowing value', fieldValue, formItem.name);
return [2 /*return*/, undefined];
}
});
});
};
return FieldResolverUtil;
}());
exports.FieldResolverUtil = FieldResolverUtil;
//# sourceMappingURL=fieldResolverUtil.js.map