UNPKG

10.9 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'use strict';
15
16const Logger = require('@accordproject/concerto-core').Logger;
17
18const ErgoCompiler = require('@accordproject/ergo-compiler').Compiler;
19
20const ciceroVersion = require('../package.json').version;
21
22const semver = require('semver');
23
24const templateTypes = {
25 CONTRACT: 0,
26 CLAUSE: 1
27};
28/**
29 * Defines the metadata for a Template, including the name, version, README markdown.
30 * @class
31 * @public
32 */
33
34class Metadata {
35 /**
36 * Create the Metadata.
37 * <p>
38 * <strong>Note: Only to be called by framework code. Applications should
39 * retrieve instances from {@link Template}</strong>
40 * </p>
41 * @param {object} packageJson - the JS object for package.json (required)
42 * @param {String} readme - the README.md for the template (may be null)
43 * @param {object} samples - the sample markdown for the template in different locales,
44 * @param {object} request - the JS object for the sample request
45 * represented as an object whose keys are the locales and whose values are the sample markdown.
46 * For example:
47 * {
48 * default: 'default sample markdown',
49 * en: 'sample text in english',
50 * fr: 'exemple de texte français'
51 * }
52 * Locale keys (with the exception of default) conform to the IETF Language Tag specification (BCP 47).
53 * THe `default` key represents sample template text in a non-specified language, stored in a file called `sample.md`.
54 */
55 constructor(packageJson, readme, samples, request) {
56 // name of the runtime that this template targets (if the template contains compiled code)
57 this.runtime = null; // the version of Cicero that this template is compatible with
58
59 this.ciceroVersion = null;
60
61 if (!packageJson || typeof packageJson !== 'object') {
62 throw new Error('package.json is required and must be an object');
63 }
64
65 if (!packageJson.accordproject) {
66 // Catches the previous format for the package.json with `cicero`
67 if (packageJson.cicero && packageJson.cicero.version) {
68 const msg = "The template targets Cicero (".concat(packageJson.cicero.version, ") but the Cicero version is ").concat(ciceroVersion, ".");
69 Logger.error(msg);
70 throw new Error(msg);
71 }
72
73 throw new Error('Failed to find accordproject metadata in package.json');
74 }
75
76 if (!packageJson.accordproject.cicero) {
77 throw new Error('Failed to find accordproject cicero version in package.json');
78 }
79
80 if (!semver.valid(semver.coerce(packageJson.accordproject.cicero))) {
81 throw new Error('The cicero version must be a valid semantic version (semver) number.');
82 }
83
84 if (!semver.valid(semver.coerce(packageJson.version))) {
85 throw new Error('The template version must be a valid semantic version (semver) number.');
86 }
87
88 this.ciceroVersion = packageJson.accordproject.cicero;
89
90 if (!this.satisfiesCiceroVersion(ciceroVersion)) {
91 const msg = "The template targets Cicero version ".concat(this.ciceroVersion, " but the current Cicero version is ").concat(ciceroVersion, ".");
92 Logger.error(msg);
93 throw new Error(msg);
94 } // the runtime property is optional, and is only mandatory for templates that have been compiled
95
96
97 if (packageJson.accordproject.runtime && packageJson.accordproject.runtime !== 'ergo') {
98 ErgoCompiler.isValidTarget(packageJson.accordproject.runtime);
99 } else {
100 packageJson.accordproject.runtime = 'ergo';
101 }
102
103 this.runtime = packageJson.accordproject.runtime;
104
105 if (!samples || typeof samples !== 'object') {
106 throw new Error('sample.md is required');
107 }
108
109 if (request && typeof request !== 'object') {
110 throw new Error('request.json must be an object');
111 }
112
113 if (!packageJson.name || !this._validName(packageJson.name)) {
114 throw new Error('template name can only contain lowercase alphanumerics, _ or -');
115 }
116
117 this.packageJson = packageJson;
118
119 if (readme && typeof readme !== 'string') {
120 throw new Error('README must be a string');
121 }
122
123 if (!packageJson.keywords) {
124 packageJson.keywords = [];
125 }
126
127 if (packageJson.keywords && !Array.isArray(packageJson.keywords)) {
128 throw new Error('keywords property in package.json must be an array.');
129 }
130
131 if (packageJson.displayName && packageJson.displayName.length > 214) {
132 throw new Error('The template displayName property is limited to a maximum of 214 characters.');
133 }
134
135 this.readme = readme;
136 this.samples = samples;
137 this.request = request;
138 this.type = templateTypes.CONTRACT;
139
140 if (packageJson.accordproject && packageJson.accordproject.template) {
141 if (packageJson.accordproject.template !== 'contract' && packageJson.accordproject.template !== 'clause') {
142 throw new Error('A cicero template must be either a "contract" or a "clause".');
143 }
144
145 if (packageJson.accordproject.template === 'clause') {
146 this.type = templateTypes.CLAUSE;
147 }
148 } else {
149 Logger.warn('No cicero template type specified. Assuming that this is a contract template');
150 }
151 }
152 /**
153 * check to see if it is a valid name. for some reason regex is not working when this executes
154 * inside the chaincode runtime, which is why regex hasn't been used.
155 *
156 * @param {string} name the template name to check
157 * @returns {boolean} true if valid, false otherwise
158 *
159 * @private
160 */
161
162
163 _validName(name) {
164 const validChars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'];
165
166 for (let i = 0; i < name.length; i++) {
167 const strChar = name.charAt(i);
168
169 if (validChars.indexOf(strChar) === -1) {
170 return false;
171 }
172 }
173
174 return true;
175 }
176 /**
177 * Returns either a 0 (for a contract template), or 1 (for a clause template)
178 * @returns {number} the template type
179 */
180
181
182 getTemplateType() {
183 return this.type;
184 }
185 /**
186 * Returns the name of the runtime target for this template, or null if this template
187 * has not been compiled for a specific runtime.
188 * @returns {string} the name of the runtime
189 */
190
191
192 getRuntime() {
193 return this.runtime;
194 }
195 /**
196 * Returns the version of Cicero that this template is compatible with.
197 * i.e. which version of the runtime was this template built for?
198 * The version string conforms to the semver definition
199 * @returns {string} the semantic version
200 */
201
202
203 getCiceroVersion() {
204 return this.ciceroVersion;
205 }
206 /**
207 * Only returns true if the current cicero version satisfies the target version of this template
208 * @param {string} version the cicero version to check against
209 * @returns {string} the semantic version
210 */
211
212
213 satisfiesCiceroVersion(version) {
214 return semver.satisfies(version, this.getCiceroVersion(), {
215 includePrerelease: true
216 });
217 }
218 /**
219 * Returns the samples for this template.
220 * @return {object} the sample files for the template
221 */
222
223
224 getSamples() {
225 return this.samples;
226 }
227 /**
228 * Returns the sample request for this template.
229 * @return {object} the sample request for the template
230 */
231
232
233 getRequest() {
234 return this.request;
235 }
236 /**
237 * Returns the sample for this template in the given locale. This may be null.
238 * If no locale is specified returns the default sample if it has been specified.
239 *
240 * @param {string} locale the IETF language code for the language.
241 * @return {string} the sample file for the template in the given locale or null
242 */
243
244
245 getSample() {
246 let locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
247
248 if (!locale && 'default' in this.samples) {
249 return this.samples.default;
250 } else if (locale && locale in this.samples) {
251 return this.samples[locale];
252 } else {
253 return null;
254 }
255 }
256 /**
257 * Returns the README.md for this template. This may be null if the template does not have a README.md
258 * @return {String} the README.md file for the template or null
259 */
260
261
262 getREADME() {
263 return this.readme;
264 }
265 /**
266 * Returns the package.json for this template.
267 * @return {object} the Javascript object for package.json
268 */
269
270
271 getPackageJson() {
272 return this.packageJson;
273 }
274 /**
275 * Returns the name for this template.
276 * @return {string} the name of the template
277 */
278
279
280 getName() {
281 return this.packageJson.name;
282 }
283 /**
284 * Returns the display name for this template.
285 * @return {string} the display name of the template
286 */
287
288
289 getDisplayName() {
290 // Fallback for packages that don't have a displayName property.
291 if (!this.packageJson.displayName) {
292 // Convert `acceptance-of-delivery` or `acceptance_of_delivery` into `Acceptance Of Delivery`
293 return String(this.packageJson.name).split(/_|-/).map(word => word.replace(/^./, str => str.toUpperCase())).join(' ').trim();
294 }
295
296 return this.packageJson.displayName;
297 }
298 /**
299 * Returns the name for this template.
300 * @return {Array} the name of the template
301 */
302
303
304 getKeywords() {
305 if (this.packageJson.keywords.length < 1 || this.packageJson.keywords === undefined) {
306 return [];
307 } else {
308 return this.packageJson.keywords;
309 }
310 }
311 /**
312 * Returns the description for this template.
313 * @return {string} the description of the template
314 */
315
316
317 getDescription() {
318 return this.packageJson.description;
319 }
320 /**
321 * Returns the version for this template.
322 * @return {string} the description of the template
323 */
324
325
326 getVersion() {
327 return this.packageJson.version;
328 }
329 /**
330 * Returns the identifier for this template, formed from name@version.
331 * @return {string} the identifier of the template
332 */
333
334
335 getIdentifier() {
336 return this.packageJson.name + '@' + this.packageJson.version;
337 }
338 /**
339 * Return new Metadata for a target runtime
340 * @param {string} runtimeName - the target runtime name
341 * @return {object} the new Metadata
342 */
343
344
345 createTargetMetadata(runtimeName) {
346 const packageJson = JSON.parse(JSON.stringify(this.packageJson));
347 packageJson.accordproject.runtime = runtimeName;
348 return new Metadata(packageJson, this.readme, this.samples, this.request);
349 }
350
351}
352
353module.exports = Metadata;
\No newline at end of file