UNPKG

6.71 kBJavaScriptView Raw
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'use strict';
16
17const URIJS = require('urijs');
18const RESOURCE_SCHEME = 'resource';
19const TypedStack = require('./serializer/typedstack');
20const ObjectValidator = require('./serializer/objectvalidator');
21
22/**
23 * Runtime API for Concerto.
24 *
25 * @class
26 * @memberof module:concerto-core
27 */
28class 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
209module.exports = Concerto;