-
-
Notifications
You must be signed in to change notification settings - Fork 111
/
concerto.js
211 lines (190 loc) · 6.62 KB
/
concerto.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const URIJS = require('urijs');
const RESOURCE_SCHEME = 'resource';
const { TypedStack } = require('@accordproject/concerto-util');
const ObjectValidator = require('./serializer/objectvalidator');
// Types needed for TypeScript generation.
/* eslint-disable no-unused-vars */
/* istanbul ignore next */
if (global === undefined) {
const ModelManager = require('./modelmanager');
}
/* eslint-enable no-unused-vars */
/**
* Runtime API for Concerto.
*
* @class
* @memberof module:concerto-core
*/
class Concerto {
/**
* Create a Concerto instance.
* @param {ModelManager} modelManager - The this.modelManager to use for validation etc.
*/
constructor(modelManager) {
this.modelManager = modelManager;
}
/**
* Validates the instance against its model.
* @param {*} obj the input object
* @param {*} [options] the validation options
* @throws {Error} - if the instance if invalid with respect to the model
*/
validate(obj, options) {
const classDeclaration = this.getTypeDeclaration(obj);
const parameters = {};
parameters.stack = new TypedStack(obj);
const objectValidator = new ObjectValidator(this, options);
classDeclaration.accept(objectValidator, parameters);
}
/**
* Returns the model manager
* @returns {ModelManager} the model manager associated with this Concerto class
*/
getModelManager() {
return this.modelManager;
}
/**
* Returns true if the input object is a Concerto object
* @param {*} obj the input object
* @return {boolean} true if the object has a $class attribute
*/
isObject(obj) {
return typeof obj === 'object' && obj.$class;
}
/**
* Returns the ClassDeclaration for an object, or throws an exception
* @param {*} obj the input object
* @throw {Error} an error if the object does not have a $class attribute
* @return {*} the ClassDeclaration for the type
*/
getTypeDeclaration(obj) {
if (!obj.$class) {
throw new Error('Input object does not have a $class attribute.');
}
return this.modelManager.getType(obj.$class);
}
/**
* Gets the identifier for an object
* @param {*} obj the input object
* @return {string} The identifier for this object
*/
getIdentifier(obj) {
const typeDeclaration = this.getTypeDeclaration(obj);
const idField = typeDeclaration.getIdentifierFieldName();
if (!idField) {
throw new Error(`Object does not have an identifier: ${JSON.stringify(obj)}`);
}
return obj[idField];
}
/**
* Returns true if the object has an identifier
* @param {*} obj the input object
* @return {boolean} is the object has been defined with an identifier in the model
*/
isIdentifiable(obj) {
const typeDeclaration = this.getTypeDeclaration(obj);
return !typeDeclaration.isSystemIdentified() && typeDeclaration.getIdentifierFieldName() !== null;
}
/**
* Returns true if the object is a relationship. Relationships are strings
* of the form: 'resource:org.accordproject.Order#001' (a relationship)
* to the 'Order' identifiable, with the id 001.
* @param {*} obj the input object
* @return {boolean} true if the object is a relationship
*/
isRelationship(obj) {
return typeof obj === 'string' && obj.startsWith(`${RESOURCE_SCHEME}:`);
}
/**
* Set the identifier for an object. This method does *not* mutate the
* input object, use the return object.
* @param {*} obj the input object
* @param {string} id the new identifier
* @returns {*} the input object with the identifier set
*/
setIdentifier(obj, id) {
const typeDeclaration = this.getTypeDeclaration(obj);
const idField = typeDeclaration.getIdentifierFieldName();
const clone = JSON.parse(JSON.stringify(obj));
clone[idField] = id;
return clone;
}
/**
* Returns the fully qualified identifier for an object
* @param {*} obj the input object
* @returns {string} the fully qualified identifier
*/
getFullyQualifiedIdentifier(obj) {
this.getTypeDeclaration(obj);
return `${obj.$class}#${this.getIdentifier(obj)}`;
}
/**
* Returns a URI for an object
* @param {*} obj the input object
* @return {string} the URI for the object
*/
toURI(obj) {
this.getTypeDeclaration(obj);
return `${RESOURCE_SCHEME}:${obj.$class}#${encodeURI(this.getIdentifier(obj))}`;
}
/**
* Parses a resource URI into typeDeclaration and id components.
*
* @param {string} uri the input URI
* @returns {*} an object with typeDeclaration and id attributes
* @throws {Error} if the URI is invalid or the type does not exist
* in the model manager
*/
fromURI(uri) {
let uriComponents;
try {
uriComponents = URIJS.parse(uri);
} catch (err) {
throw new Error('Invalid URI: ' + uri);
}
const scheme = uriComponents.protocol;
if (scheme && scheme !== RESOURCE_SCHEME) {
throw new Error('Invalid URI scheme: ' + uri);
}
if (uriComponents.username || uriComponents.password || uriComponents.port || uriComponents.query) {
throw new Error('Invalid resource URI format: ' + uri);
}
return {
typeDeclaration: this.getTypeDeclaration({
$class: uriComponents.path
}),
id: decodeURIComponent(uriComponents.fragment)
};
}
/**
* Returns the short type name
* @param {*} obj the input object
* @returns {string} the short type name
*/
getType(obj) {
return this.getTypeDeclaration(obj).getName();
}
/**
* Returns the namespace for the object
* @param {*} obj the input object
* @returns {string} the namespace
*/
getNamespace(obj) {
return this.getTypeDeclaration(obj).getNamespace();
}
}
module.exports = Concerto;