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