schema-markdown 1.1.6 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,91 +3,105 @@
3
3
  [![npm](https://img.shields.io/npm/v/schema-markdown)](https://www.npmjs.com/package/schema-markdown)
4
4
  [![GitHub](https://img.shields.io/github/license/craigahobbs/schema-markdown-js)](https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE)
5
5
 
6
- **Schema Markdown** is a human-friendly schema definition language and schema validator. Here are
7
- its features at a glance:
8
-
9
- - Schema-validate JSON objects
10
- - Human-friendly schema definition
11
- - Validates member value and length contraints
12
- - Validation *type-massages* string member values
13
- - Pure JavaScript
6
+ **schema-markdown** is a schema definition and validation library.
14
7
 
15
8
 
16
9
  ## Links
17
10
 
18
- - [Schema Markdown Language Reference](https://craigahobbs.github.io/schema-markdown/schema-markdown.html)
19
- - [Documentation on GitHub Pages](https://craigahobbs.github.io/schema-markdown-js/)
20
- - [Package on npm](https://www.npmjs.com/package/schema-markdown)
21
- - [Source code on GitHub](https://github.com/craigahobbs/schema-markdown-js)
11
+ - [The Schema Markdown Language](https://craigahobbs.github.io/schema-markdown-js/language/)
12
+ - [API Documentation](https://craigahobbs.github.io/schema-markdown-js/)
13
+ - [Source code](https://github.com/craigahobbs/schema-markdown-js)
22
14
 
23
15
 
24
- ## Usage
16
+ ## Define a Schema
25
17
 
26
- To schema-validate an object, first parse its *Schema Markdown* using the
27
- [SchemaMarkdownParser](https://craigahobbs.github.io/schema-markdown-js/module-lib_parser.SchemaMarkdownParser.html)
28
- class:
18
+ Schemas are defined using the
19
+ [Schema Markdown language](https://craigahobbs.github.io/schema-markdown-js/language/),
20
+ which is parsed by the
21
+ [parseSchemaMarkdown](https://craigahobbs.github.io/schema-markdown-js/module-lib_parser.html#.parseSchemaMarkdown)
22
+ function. For example:
29
23
 
30
24
  ``` javascript
31
- import {SchemaMarkdownParser} from 'schema-markdown/parser.js';
32
- import {validateType} from 'schema-markdown/schema.js';
33
-
34
- const parser = new SchemaMarkdownParser(`\
35
- # An aggregation function
36
- enum Aggregation
37
- Average
38
- Sum
25
+ import {parseSchemaMarkdown} from 'schema-markdown/parser.js';
39
26
 
27
+ export const modelTypes = parseSchemaMarkdown(`\
40
28
  # An aggregate numerical operation
41
- struct Operation
29
+ struct Aggregation
30
+
42
31
  # The aggregation function - default is "Sum"
43
- optional Aggregation aggregation
32
+ optional AggregationFunction aggregation
44
33
 
45
- # The numbers to operate on
34
+ # The numbers to aggregate on
46
35
  int[len > 0] numbers
36
+
37
+ # An aggregation function
38
+ enum AggregationFunction
39
+ Average
40
+ Sum
47
41
  `);
48
42
  ```
49
43
 
50
- Then, validate an object using the
44
+
45
+ ## Validate using a Schema
46
+
47
+ To validate an object using the schema, use the
51
48
  [validateType](https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.html#.validateType)
52
- function:
49
+ function. For example:
53
50
 
54
51
  ``` javascript
55
- const obj = validateType(parser.types, 'Operation', {
56
- 'numbers': [1, 2, '3', 4]
57
- });
52
+ import {validateType} from 'schema-markdown/schema.js';
53
+
54
+ const obj = validateType(modelTypes, 'Aggregation', {'numbers': [1, 2, '3', 4]});
58
55
  console.assert(obj.numbers[2] === 3);
59
56
  ```
60
57
 
61
58
  Notice that the numerical input '3' above is *type-massaged* to the integer 3 by validation.
59
+
62
60
  Validation fails if the object does not match the schema:
63
61
 
64
62
  ``` javascript
65
63
  try {
66
- validateType(parser.types, 'Operation', {
67
- 'numbers': [1, 2, 'asdf', 4]
68
- });
64
+ validateType(modelTypes, 'Aggregation', {'numbers': [1, 2, 'asdf', 4]});
69
65
  } catch ({message}) {
70
66
  console.assert(message === "Invalid value \"asdf\" (type 'string') for member 'numbers.2', expected type 'int'", message);
71
67
  }
72
68
  ```
73
69
 
74
- Validation also fails if a member contraint is violated:
70
+ Validation also fails if a member constraint is violated:
75
71
 
76
72
  ``` javascript
77
73
  try {
78
- validateType(parser.types, 'Operation', {
79
- 'numbers': []
80
- });
74
+ validateType(modelTypes, 'Aggregation', {'numbers': []});
81
75
  } catch ({message}) {
82
76
  console.assert(message === "Invalid value [] (type 'object') for member 'numbers', expected type 'array' [len > 0]", message);
83
77
  }
84
78
  ```
85
79
 
86
80
 
81
+ ## Document a Schema
82
+
83
+ To document the schema, download the
84
+ [documentation application](https://github.com/craigahobbs/schema-markdown-doc#the-schema-markdown-documentation-viewer)
85
+ stub and save the type model as JSON:
86
+
87
+ ~~~
88
+ curl -O https://craigahobbs.github.io/schema-markdown-doc/extra/index.html
89
+ node --input-type=module \
90
+ -e 'import {modelTypes} from "model.js"; console.log(JSON.stringify(modelTypes))' \
91
+ > model.json
92
+ ~~~
93
+
94
+ To host locally, start a local static web server:
95
+
96
+ ```
97
+ python3 -m http.server
98
+ ```
99
+
100
+
87
101
  ## Development
88
102
 
89
- schema-markdown is developed using [javascript-build](https://github.com/craigahobbs/javascript-build#readme)
90
- and it was started using [javascript-template](https://github.com/craigahobbs/javascript-template#readme):
103
+ This package is developed using [javascript-build](https://github.com/craigahobbs/javascript-build#readme).
104
+ It was started using [javascript-template](https://github.com/craigahobbs/javascript-template#readme) as follows:
91
105
 
92
106
  ```
93
107
  template-specialize javascript-template/template/ schema-markdown-js/ -k package schema-markdown -k name 'Craig A. Hobbs' -k email 'craigahobbs@gmail.com' -k github 'craigahobbs' -k noapp 1
package/lib/parser.js CHANGED
@@ -5,106 +5,457 @@
5
5
 
6
6
  import {validateTypeModelErrors} from './schemaUtil.js';
7
7
 
8
- /* eslint-disable prefer-template */
8
+
9
+ // Built-in types
10
+ const builtinTypes = new Set(['bool', 'date', 'datetime', 'float', 'int', 'object', 'string', 'uuid']);
9
11
 
10
12
 
11
13
  // Schema Markdown regex
12
- const RE_PART_ID = '(?:[A-Za-z]\\w*)';
13
- const RE_PART_ATTR_GROUP =
14
+ const rPartId = '(?:[A-Za-z]\\w*)';
15
+ const rPartAttrGroup =
14
16
  '(?:(?<nullable>nullable)' +
15
17
  '|(?<op><=|<|>|>=|==)\\s*(?<opnum>-?\\d+(?:\\.\\d+)?)' +
16
18
  '|(?<ltype>len)\\s*(?<lop><=|<|>|>=|==)\\s*(?<lopnum>\\d+))';
17
- const RE_PART_ATTR = RE_PART_ATTR_GROUP.replace(/\(\?<[^>]+>/g, '(?:');
18
- const RE_PART_ATTRS = '(?:' + RE_PART_ATTR + '(?:\\s*,\\s*' + RE_PART_ATTR + ')*)';
19
- const RE_ATTR_GROUP = new RegExp(RE_PART_ATTR_GROUP);
20
- const RE_FIND_ATTRS = new RegExp(RE_PART_ATTR + '(?:\\s*,\\s*|\\s*$)', 'g');
21
- const RE_LINE_CONT = /\\\s*$/;
22
- const RE_COMMENT = /^\s*(?:#-.*|#(?<doc>.*))?$/;
23
- const RE_GROUP = /^group(?:\s+"(?<group>.+?)")?\s*$/;
24
- const RE_ACTION = new RegExp('^action\\s+(?<id>' + RE_PART_ID + ')');
25
- const RE_PART_BASE_IDS = '(?:\\s*\\(\\s*(?<base_ids>' + RE_PART_ID + '(?:\\s*,\\s*' + RE_PART_ID + ')*)\\s*\\)\\s*)';
26
- const RE_BASE_IDS_SPLIT = /\s*,\s*/;
27
- const RE_DEFINITION = new RegExp('^(?<type>struct|union|enum)\\s+(?<id>' + RE_PART_ID + ')' + RE_PART_BASE_IDS + '?\\s*$');
28
- const RE_SECTION = new RegExp('^\\s+(?<type>path|query|input|output|errors)' + RE_PART_BASE_IDS + '?\\s*$');
29
- const RE_SECTION_PLAIN = /^\s+(?<type>urls)\s*$/;
30
- const RE_PART_TYPEDEF =
31
- '(?<type>' + RE_PART_ID + ')' +
32
- '(?:\\s*\\(\\s*(?<attrs>' + RE_PART_ATTRS + ')\\s*\\))?' +
19
+ const rPartAttr = rPartAttrGroup.replace(/\(\?<[^>]+>/g, '(?:');
20
+ const rPartAttrs = `(?:${rPartAttr}(?:\\s*,\\s*${rPartAttr})*)`;
21
+ const rAttrGroup = new RegExp(rPartAttrGroup);
22
+ const rFindAttrs = new RegExp(`${rPartAttr}(?:\\s*,\\s*|\\s*$)`, 'g');
23
+ const rLineCont = /\\\s*$/;
24
+ const rComment = /^\s*(?:#-.*|#(?<doc>.*))?$/;
25
+ const rGroup = /^group(?:\s+"(?<group>.+?)")?\s*$/;
26
+ const rAction = new RegExp(`^action\\s+(?<id>${rPartId})`);
27
+ const rPartBaseIds = `(?:\\s*\\(\\s*(?<baseIds>${rPartId}(?:\\s*,\\s*${rPartId})*)\\s*\\)\\s*)`;
28
+ const rBaseIdsSplit = /\s*,\s*/;
29
+ const rDefinition = new RegExp(`^(?<type>struct|union|enum)\\s+(?<id>${rPartId})${rPartBaseIds}?\\s*$`);
30
+ const rSection = new RegExp(`^\\s+(?<type>path|query|input|output|errors)${rPartBaseIds}?\\s*$`);
31
+ const rSectionPlain = /^\s+(?<type>urls)\s*$/;
32
+ const rPartTypedef =
33
+ `(?<type>${rPartId})` +
34
+ `(?:\\s*\\(\\s*(?<attrs>${rPartAttrs})\\s*\\))?` +
33
35
  '(?:' +
34
- '(?:\\s*\\[\\s*(?<array>' + RE_PART_ATTRS + '?)\\s*\\])?' +
36
+ `(?:\\s*\\[\\s*(?<array>${rPartAttrs}?)\\s*\\])?` +
35
37
  '|' +
36
38
  '(?:' +
37
- '\\s*:\\s*(?<dictValueType>' + RE_PART_ID + ')' +
38
- '(?:\\s*\\(\\s*(?<dictValueAttrs>' + RE_PART_ATTRS + ')\\s*\\))?' +
39
+ `\\s*:\\s*(?<dictValueType>${rPartId})` +
40
+ `(?:\\s*\\(\\s*(?<dictValueAttrs>${rPartAttrs})\\s*\\))?` +
39
41
  ')?' +
40
- '(?:\\s*\\{\\s*(?<dict>' + RE_PART_ATTRS + '?)\\s*\\})?' +
42
+ `(?:\\s*\\{\\s*(?<dict>${rPartAttrs}?)\\s*\\})?` +
41
43
  ')' +
42
- '\\s+(?<id>' + RE_PART_ID + ')';
43
- const RE_TYPEDEF = new RegExp('^typedef\\s+' + RE_PART_TYPEDEF + '\\s*$');
44
- const RE_MEMBER = new RegExp('^\\s+(?<optional>optional\\s+)?' + RE_PART_TYPEDEF + '\\s*$');
45
- const RE_VALUE = new RegExp('^\\s+(?<id>' + RE_PART_ID + ')\\s*$');
46
- const RE_VALUE_QUOTED = /^\s+"(?<id>.*?)"\s*$/;
47
- const RE_URL = /^\s+(?<method>[A-Za-z]+|\*)(?:\s+(?<path>\/[^\s]*))?/;
44
+ `\\s+(?<id>${rPartId})`;
45
+ const rTypedef = new RegExp(`^typedef\\s+${rPartTypedef}\\s*$`);
46
+ const rMember = new RegExp(`^\\s+(?<optional>optional\\s+)?${rPartTypedef}\\s*$`);
47
+ const rValue = new RegExp(`^\\s+(?<id>${rPartId})\\s*$`);
48
+ const rValueQuoted = /^\s+"(?<id>.*?)"\s*$/;
49
+ const rURL = /^\s+(?<method>[A-Za-z]+|\*)(?:\s+(?<path>\/[^\s]*))?/;
50
+ const rLineSplit = /\r?\n/;
48
51
 
49
52
 
50
- // Built-in types
51
- const BUILTIN_TYPES = new Set(['bool', 'date', 'datetime', 'float', 'int', 'object', 'string', 'uuid']);
53
+ /**
54
+ * Parse Schema Markdown from a string or an iterator of strings
55
+ *
56
+ * @param {string|string[]} text - The Schema Markdown text
57
+ * @param {Object} [options.types=''] - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
58
+ * @param {string} [options.filename=''] - The name of file being parsed (for error messages)
59
+ * @param {boolean} [options.validate=true] - If true, validate after parsing
60
+ * @returns {Object} The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
61
+ * @throws [SchemaMarkdownParserError]{@link module:lib/parser.SchemaMarkdownParserError}
62
+ */
63
+ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate = true} = {}) {
64
+ // Current parser state
65
+ const errorMap = {};
66
+ const filepos = {};
67
+ let action = null;
68
+ let urls = null;
69
+ let userType = null;
70
+ let doc = [];
71
+ let docGroup = null;
72
+ let linenum = 0;
73
+
74
+ // Helper function to add an error message
75
+ const addError = (msg, errorFilename, errorLinenum) => {
76
+ const errorMsg = `${errorFilename}:${errorLinenum}: error: ${msg}`;
77
+ errorMap[errorMsg] = [errorFilename, errorLinenum, errorMsg];
78
+ };
79
+
80
+ // Helper function to get documentation strings
81
+ const getDoc = () => {
82
+ let result = null;
83
+ if (doc.length) {
84
+ result = doc;
85
+ doc = [];
86
+ }
87
+ return result;
88
+ };
89
+
90
+ // Line-split all script text
91
+ const lines = [];
92
+ if (typeof text === 'string') {
93
+ lines.push(...text.split(rLineSplit));
94
+ } else {
95
+ for (const textPart of text) {
96
+ lines.push(...textPart.split(rLineSplit));
97
+ }
98
+ }
99
+ lines.push('');
52
100
 
101
+ // Process each line
102
+ const lineContinuation = [];
103
+ for (const linePart of lines) {
104
+ linenum += 1;
53
105
 
54
- // Helper function to parse an attributes string - returns an attributes model
55
- function parseAttr(attrsString) {
56
- let attrs = null;
57
- if (typeof attrsString !== 'undefined') {
58
- for (const [attrString] of attrsString.matchAll(RE_FIND_ATTRS)) {
59
- if (attrs === null) {
60
- attrs = {};
106
+ // Line continuation?
107
+ const linePartNoContinuation = linePart.replace(rLineCont, '');
108
+ if (lineContinuation.length || linePartNoContinuation !== linePart) {
109
+ lineContinuation.push(linePartNoContinuation);
110
+ }
111
+ if (linePartNoContinuation !== linePart) {
112
+ continue;
113
+ }
114
+ let line;
115
+ if (lineContinuation.length) {
116
+ line = lineContinuation.join('');
117
+ lineContinuation.length = 0;
118
+ } else {
119
+ line = linePart;
120
+ }
121
+
122
+ // Match syntax
123
+ let matchName = 'comment';
124
+ let match = line.match(rComment);
125
+ if (match === null) {
126
+ matchName = 'group';
127
+ match = line.match(rGroup);
128
+ }
129
+ if (match === null) {
130
+ matchName = 'action';
131
+ match = line.match(rAction);
132
+ }
133
+ if (match === null) {
134
+ matchName = 'definition';
135
+ match = line.match(rDefinition);
136
+ }
137
+ if (match === null && action !== null) {
138
+ matchName = 'section';
139
+ match = line.match(rSection);
140
+ }
141
+ if (match === null && action !== null) {
142
+ matchName = 'sectionPlain';
143
+ match = line.match(rSectionPlain);
144
+ }
145
+ if (match === null && userType !== null && 'enum' in userType) {
146
+ matchName = 'value';
147
+ match = line.match(rValue);
148
+ if (match === null) {
149
+ match = line.match(rValueQuoted);
61
150
  }
62
- const matchAttr = attrString.match(RE_ATTR_GROUP);
63
- const attrOp = matchAttr.groups.op;
64
- const attrLengthOp = matchAttr.groups.lop;
151
+ }
152
+ if (match === null && userType !== null && 'struct' in userType) {
153
+ matchName = 'member';
154
+ match = line.match(rMember);
155
+ }
156
+ if (match === null && urls !== null) {
157
+ matchName = 'urls';
158
+ match = line.match(rURL);
159
+ }
160
+ if (match === null) {
161
+ matchName = 'typedef';
162
+ match = line.match(rTypedef);
163
+ }
164
+ if (match === null) {
165
+ matchName = null;
166
+ }
65
167
 
66
- if (typeof matchAttr.groups.nullable !== 'undefined') {
67
- attrs.nullable = true;
68
- } else if (typeof attrOp !== 'undefined') {
69
- const attrValue = parseFloat(matchAttr.groups.opnum);
70
- if (attrOp === '<') {
71
- attrs.lt = attrValue;
72
- } else if (attrOp === '<=') {
73
- attrs.lte = attrValue;
74
- } else if (attrOp === '>') {
75
- attrs.gt = attrValue;
76
- } else if (attrOp === '>=') {
77
- attrs.gte = attrValue;
78
- } else {
79
- attrs.eq = attrValue;
168
+ // Comment?
169
+ if (matchName === 'comment') {
170
+ const docString = match.groups.doc;
171
+ if (typeof docString !== 'undefined') {
172
+ doc.push(!docString.startsWith(' ') ? docString : docString.slice(1));
173
+ }
174
+
175
+ // Documentation group?
176
+ } else if (matchName === 'group') {
177
+ docGroup = match.groups.group;
178
+ if (typeof docGroup !== 'undefined') {
179
+ docGroup = docGroup.trim();
180
+ } else {
181
+ docGroup = null;
182
+ }
183
+
184
+ // Action?
185
+ } else if (matchName === 'action') {
186
+ const actionId = match.groups.id;
187
+
188
+ // Action already defined?
189
+ if (actionId in types) {
190
+ addError(`Redefinition of action '${actionId}'`, filename, linenum);
191
+ }
192
+
193
+ // Clear parser state
194
+ urls = null;
195
+ userType = null;
196
+ const actionDoc = getDoc();
197
+
198
+ // Create the new action
199
+ action = {'name': actionId};
200
+ types[actionId] = {'action': action};
201
+ if (actionDoc !== null) {
202
+ action.doc = actionDoc;
203
+ }
204
+ if (docGroup !== null) {
205
+ action.docGroup = docGroup;
206
+ }
207
+
208
+ // Definition?
209
+ } else if (matchName === 'definition') {
210
+ const definitionString = match.groups.type;
211
+ const definitionId = match.groups.id;
212
+ const definitionBaseIds = match.groups.baseIds;
213
+
214
+ // Type already defined?
215
+ if (builtinTypes.has(definitionId) || definitionId in types) {
216
+ addError(`Redefinition of type '${definitionId}'`, filename, linenum);
217
+ }
218
+
219
+ // Clear parser state
220
+ action = null;
221
+ urls = null;
222
+ const definitionDoc = getDoc();
223
+
224
+ // Struct definition
225
+ if (definitionString === 'struct' || definitionString === 'union') {
226
+ // Create the new struct type
227
+ const struct = {'name': definitionId};
228
+ userType = {'struct': struct};
229
+ types[definitionId] = userType;
230
+ if (definitionDoc !== null) {
231
+ struct.doc = definitionDoc;
232
+ }
233
+ if (docGroup !== null) {
234
+ struct.docGroup = docGroup;
235
+ }
236
+ if (definitionString === 'union') {
237
+ struct.union = true;
238
+ }
239
+ if (typeof definitionBaseIds !== 'undefined') {
240
+ struct.bases = definitionBaseIds.split(rBaseIdsSplit);
80
241
  }
242
+
243
+ // Enum definition
81
244
  } else {
82
- // typeof attrLengthOp !== 'undefined'
83
- const attrValue = parseInt(matchAttr.groups.lopnum, 10);
84
- if (attrLengthOp === '<') {
85
- attrs.lenLT = attrValue;
86
- } else if (attrLengthOp === '<=') {
87
- attrs.lenLTE = attrValue;
88
- } else if (attrLengthOp === '>') {
89
- attrs.lenGT = attrValue;
90
- } else if (attrLengthOp === '>=') {
91
- attrs.lenGTE = attrValue;
92
- } else {
93
- attrs.lenEq = attrValue;
245
+ // definitionString == 'enum':
246
+ // Create the new enum type
247
+ const enum_ = {'name': definitionId};
248
+ userType = {'enum': enum_};
249
+ types[definitionId] = userType;
250
+ if (definitionDoc !== null) {
251
+ enum_.doc = definitionDoc;
252
+ }
253
+ if (docGroup !== null) {
254
+ enum_.docGroup = docGroup;
255
+ }
256
+ if (typeof definitionBaseIds !== 'undefined') {
257
+ enum_.bases = definitionBaseIds.split(rBaseIdsSplit);
258
+ }
259
+ }
260
+
261
+ // Record the definition's line number
262
+ filepos[definitionId] = linenum;
263
+
264
+ // Action section?
265
+ } else if (matchName === 'section') {
266
+ const sectionString = match.groups.type;
267
+ const sectionBaseIds = match.groups.baseIds;
268
+
269
+ // Action section redefinition?
270
+ if (sectionString in action) {
271
+ addError(`Redefinition of action ${sectionString}`, filename, linenum);
272
+ }
273
+
274
+ // Clear parser state
275
+ urls = null;
276
+
277
+ // Set the action section type
278
+ const sectionTypeName = `${action.name}_${sectionString}`;
279
+ action[sectionString] = sectionTypeName;
280
+ if (sectionString === 'errors') {
281
+ const enum_ = {'name': sectionTypeName};
282
+ userType = {'enum': enum_};
283
+ types[sectionTypeName] = userType;
284
+ if (typeof sectionBaseIds !== 'undefined') {
285
+ enum_.bases = sectionBaseIds.split(rBaseIdsSplit);
286
+ }
287
+ } else {
288
+ const struct = {'name': sectionTypeName};
289
+ userType = {'struct': struct};
290
+ types[sectionTypeName] = userType;
291
+ if (typeof sectionBaseIds !== 'undefined') {
292
+ struct.bases = sectionBaseIds.split(rBaseIdsSplit);
94
293
  }
95
294
  }
295
+
296
+ // Record the definition's line number
297
+ filepos[sectionTypeName] = linenum;
298
+
299
+ // Plain action section?
300
+ } else if (matchName === 'sectionPlain') {
301
+ const sectionString = match.groups.type;
302
+
303
+ // Action section redefinition?
304
+ if (sectionString in action) {
305
+ addError(`Redefinition of action ${sectionString}`, filename, linenum);
306
+ }
307
+
308
+ // Clear parser state
309
+ userType = null;
310
+
311
+ // Update the parser state
312
+ urls = [];
313
+
314
+ // Enum value?
315
+ } else if (matchName === 'value') {
316
+ const valueString = match.groups.id;
317
+
318
+ // Add the enum value
319
+ const enum_ = userType.enum;
320
+ if (!('values' in enum_)) {
321
+ enum_.values = [];
322
+ }
323
+ const enumValue = {'name': valueString};
324
+ enum_.values.push(enumValue);
325
+ const enumValueDoc = getDoc();
326
+ if (enumValueDoc !== null) {
327
+ enumValue.doc = enumValueDoc;
328
+ }
329
+
330
+ // Record the definition's line number
331
+ filepos[`${enum_.name}.${valueString}`] = linenum;
332
+
333
+ // Struct member?
334
+ } else if (matchName === 'member') {
335
+ const optional = typeof match.groups.optional !== 'undefined';
336
+ const memberName = match.groups.id;
337
+
338
+ // Add the member
339
+ const {struct} = userType;
340
+ if (!('members' in struct)) {
341
+ struct.members = [];
342
+ }
343
+ const [memberType, memberAttr] = parseTypedef(match);
344
+ const memberDoc = getDoc();
345
+ const member = {
346
+ 'name': memberName,
347
+ 'type': memberType
348
+ };
349
+ struct.members.push(member);
350
+ if (memberAttr !== null) {
351
+ member.attr = memberAttr;
352
+ }
353
+ if (memberDoc !== null) {
354
+ member.doc = memberDoc;
355
+ }
356
+ if (optional) {
357
+ member.optional = true;
358
+ }
359
+
360
+ // Record the definition's line number
361
+ filepos[`${struct.name}.${memberName}`] = linenum;
362
+
363
+ // URL?
364
+ } else if (matchName === 'urls') {
365
+ const {method, path} = match.groups;
366
+
367
+ // Create the action URL object
368
+ const actionUrl = {};
369
+ if (method !== '*') {
370
+ actionUrl.method = method;
371
+ }
372
+ if (typeof path !== 'undefined') {
373
+ actionUrl.path = path;
374
+ }
375
+
376
+ // Duplicate URL?
377
+ if (urls.some((url) => url.method === actionUrl.method && url.path === actionUrl.path)) {
378
+ addError(`Duplicate URL: ${method} ${'path' in actionUrl ? actionUrl.path : ''}`, filename, linenum);
379
+ }
380
+
381
+ // Add the URL
382
+ if (!('urls' in action)) {
383
+ action.urls = urls;
384
+ }
385
+ urls.push(actionUrl);
386
+
387
+ // Typedef?
388
+ } else if (matchName === 'typedef') {
389
+ const definitionId = match.groups.id;
390
+
391
+ // Type already defined?
392
+ if (builtinTypes.has(definitionId) || definitionId in types) {
393
+ addError(`Redefinition of type '${definitionId}'`, filename, linenum);
394
+ }
395
+
396
+ // Clear parser state
397
+ action = null;
398
+ urls = null;
399
+ userType = null;
400
+ const typedefDoc = getDoc();
401
+
402
+ // Create the typedef
403
+ const [typedefType, typedefAttr] = parseTypedef(match);
404
+ const typedef = {
405
+ 'name': definitionId,
406
+ 'type': typedefType
407
+ };
408
+ types[definitionId] = {'typedef': typedef};
409
+ if (typedefAttr !== null) {
410
+ typedef.attr = typedefAttr;
411
+ }
412
+ if (typedefDoc !== null) {
413
+ typedef.doc = typedefDoc;
414
+ }
415
+ if (docGroup !== null) {
416
+ typedef.docGroup = docGroup;
417
+ }
418
+
419
+ // Record the definition's line number
420
+ filepos[definitionId] = linenum;
421
+
422
+ // Unrecognized line syntax
423
+ } else {
424
+ addError('Syntax error', filename, linenum);
96
425
  }
97
426
  }
98
- return attrs;
99
- }
100
427
 
428
+ // Validate the type model, if requested
429
+ if (validate) {
430
+ for (const [typeName, memberName, errorMsg] of validateTypeModelErrors(types)) {
431
+ let errorFilename = filename;
432
+ let errorLinenum = null;
433
+ if (memberName !== null) {
434
+ errorLinenum = filepos[`${typeName}.${memberName}`] ?? null;
435
+ }
436
+ if (errorLinenum === null) {
437
+ errorLinenum = filepos[typeName] ?? null;
438
+ }
439
+ if (errorLinenum === null) {
440
+ errorFilename = '';
441
+ errorLinenum = 1;
442
+ }
443
+ addError(errorMsg, errorFilename, errorLinenum);
444
+ }
445
+ }
101
446
 
102
- // Helper function to create a type model
103
- function createType(typeName) {
104
- if (BUILTIN_TYPES.has(typeName)) {
105
- return {'builtin': typeName};
447
+ // Raise a parser exception if there are any errors
448
+ const errors = Array.from(Object.values(errorMap)).sort(compareTuple).map(([,, msg]) => msg);
449
+ if (errors.length) {
450
+ throw new SchemaMarkdownParserError(errors);
106
451
  }
107
- return {'user': typeName};
452
+
453
+ return types;
454
+ }
455
+
456
+
457
+ function compareTuple(v1, v2) {
458
+ return v1.reduce((tot, val, idx) => (tot !== 0 ? tot : (val < v2[idx] ? -1 : (val > v2[idx] ? 1 : 0))), 0);
108
459
  }
109
460
 
110
461
 
@@ -159,9 +510,61 @@ function parseTypedef(matchTypedef) {
159
510
  }
160
511
 
161
512
 
162
- // Helper function to compare tuples/lists
163
- const compareTuple =
164
- (v1, v2) => (v1.reduce((tot, val, idx) => (tot !== 0 ? tot : (val < v2[idx] ? -1 : (val > v2[idx] ? 1 : 0))), 0));
513
+ // Helper function to create a type model
514
+ function createType(typeName) {
515
+ if (builtinTypes.has(typeName)) {
516
+ return {'builtin': typeName};
517
+ }
518
+ return {'user': typeName};
519
+ }
520
+
521
+
522
+ // Helper function to parse an attributes string - returns an attributes model
523
+ function parseAttr(attrsString) {
524
+ let attrs = null;
525
+ if (typeof attrsString !== 'undefined') {
526
+ for (const [attrString] of attrsString.matchAll(rFindAttrs)) {
527
+ if (attrs === null) {
528
+ attrs = {};
529
+ }
530
+ const matchAttr = attrString.match(rAttrGroup);
531
+ const attrOp = matchAttr.groups.op;
532
+ const attrLengthOp = matchAttr.groups.lop;
533
+
534
+ if (typeof matchAttr.groups.nullable !== 'undefined') {
535
+ attrs.nullable = true;
536
+ } else if (typeof attrOp !== 'undefined') {
537
+ const attrValue = parseFloat(matchAttr.groups.opnum);
538
+ if (attrOp === '<') {
539
+ attrs.lt = attrValue;
540
+ } else if (attrOp === '<=') {
541
+ attrs.lte = attrValue;
542
+ } else if (attrOp === '>') {
543
+ attrs.gt = attrValue;
544
+ } else if (attrOp === '>=') {
545
+ attrs.gte = attrValue;
546
+ } else {
547
+ attrs.eq = attrValue;
548
+ }
549
+ } else {
550
+ // typeof attrLengthOp !== 'undefined'
551
+ const attrValue = parseInt(matchAttr.groups.lopnum, 10);
552
+ if (attrLengthOp === '<') {
553
+ attrs.lenLT = attrValue;
554
+ } else if (attrLengthOp === '<=') {
555
+ attrs.lenLTE = attrValue;
556
+ } else if (attrLengthOp === '>') {
557
+ attrs.lenGT = attrValue;
558
+ } else if (attrLengthOp === '>=') {
559
+ attrs.lenGTE = attrValue;
560
+ } else {
561
+ attrs.lenEq = attrValue;
562
+ }
563
+ }
564
+ }
565
+ }
566
+ return attrs;
567
+ }
165
568
 
166
569
 
167
570
  /**
@@ -180,456 +583,3 @@ export class SchemaMarkdownParserError extends Error {
180
583
  this.errors = errors;
181
584
  }
182
585
  }
183
-
184
-
185
- /**
186
- * The parser class for Schema Markdown. Parsing can occur at initialization time or by calling the
187
- * "parseString" method, which can be called repeatedly.
188
- *
189
- * @property {Object} types - The map of user type name to user type model
190
- * @property {Object} errorMap - Set of filename, linenum, and error-message tuples
191
- * @property {Object} filepos - Map of definition-name to filename/linenum-tuples
192
- */
193
- export class SchemaMarkdownParser {
194
- /**
195
- * Create a Schema Markdown parser instance
196
- *
197
- * @param {string} [text=null]: An optional Schema Markdown string to parse
198
- * @param {Object} [types=null]: An optional map of user type name to user type model
199
- * @throws {SchemaMarkdownParserError}
200
- */
201
- constructor(text = null, types = null) {
202
- this.types = types !== null ? types : {};
203
- this.errorMap = {};
204
- this.filepos = {};
205
-
206
- // Parse the Schema Markdown string, if any
207
- if (text !== null) {
208
- this.parseString(text);
209
- }
210
- }
211
-
212
- addError(msg, filename, linenum) {
213
- const errorMsg = `${filename}:${linenum}: error: ${msg}`;
214
- this.errorMap[errorMsg] = [filename, linenum, errorMsg];
215
- }
216
-
217
- getFilepos(typeName, typeKey) {
218
- let filepos = null;
219
- if (typeKey !== null) {
220
- const key = `${typeName}.${typeKey}`;
221
- filepos = key in this.filepos ? this.filepos[key] : null;
222
- }
223
- if (filepos === null) {
224
- filepos = typeName in this.filepos ? this.filepos[typeName] : null;
225
- }
226
- if (filepos === null) {
227
- filepos = ['', 1];
228
- }
229
- return filepos;
230
- }
231
-
232
- /**
233
- * The list of parser error strings
234
- */
235
- get errors() {
236
- return Array.from(Object.values(this.errorMap)).sort(compareTuple).map(([,, msg]) => msg);
237
- }
238
-
239
- /**
240
- * Finalize a parsing operation. You only need to call this method if you set the "parse"
241
- * method's "finalize" argument to false.
242
- *
243
- * @throws {SchemaMarkdownParserError}
244
- */
245
- finalize() {
246
- // Do the finalization
247
- for (const [typeName, memberName, errorMsg] of validateTypeModelErrors(this.types)) {
248
- this.addError(errorMsg, ...this.getFilepos(typeName, memberName));
249
- }
250
-
251
- // Raise a parser exception if there are any errors
252
- const {errors} = this;
253
- if (errors.length) {
254
- throw new SchemaMarkdownParserError(errors);
255
- }
256
- }
257
-
258
- /**
259
- * Parse a Schema Markdown string. This method can be called repeatedly.
260
- *
261
- * @param {string} text - The Schema Markdown string
262
- * @param {string} [filename=''] - The name of file being parsed (for error messages)
263
- * @param {boolean} [finalize=true] - If true, resolve names after parsing. Be sure to call the
264
- * "finalize" method when finished.
265
- * @throws {SchemaMarkdownParserError}
266
- */
267
- parseString(text, filename = '', finalize = true) {
268
- this.parse(text.split(/\r?\n/), filename, finalize);
269
- }
270
-
271
- /**
272
- * Parse Schema Markdown from an iterator of line strings (e.g an input stream). This method can
273
- * be called repeatedly.
274
- *
275
- * @param {string[]} lines - An array of Schema Markdown line strings
276
- * @param {string} [filename=''] - The name of file being parsed (for error messages)
277
- * @param {boolean} [finalize=true] - If true, resolve names after parsing. Be sure to call the
278
- * "finalize" method when finished.
279
- * @throws {SchemaMarkdownParserError}
280
- */
281
- parse(lines, filename = '', finalize = true) {
282
- // Current parser state
283
- let action = null;
284
- let urls = null;
285
- let userType = null;
286
- let doc = [];
287
- let docGroup = null;
288
- let linenum = 0;
289
-
290
- // Helper function to get documentation strings
291
- const getDoc = () => {
292
- let result = null;
293
- if (doc.length) {
294
- result = doc;
295
- doc = [];
296
- }
297
- return result;
298
- };
299
-
300
- // Process each line
301
- const lineContinuation = [];
302
- const lineGroups = [lines, ['']];
303
- for (const lineGroup of lineGroups) {
304
- for (const linePart of lineGroup) {
305
- linenum += 1;
306
-
307
- // Line continuation?
308
- const linePartNoContinuation = linePart.replace(RE_LINE_CONT, '');
309
- if (lineContinuation.length || linePartNoContinuation !== linePart) {
310
- lineContinuation.push(linePartNoContinuation);
311
- }
312
- if (linePartNoContinuation !== linePart) {
313
- continue;
314
- }
315
- let line;
316
- if (lineContinuation.length) {
317
- line = lineContinuation.join('');
318
- lineContinuation.length = 0;
319
- } else {
320
- line = linePart;
321
- }
322
-
323
- // Match syntax
324
- let matchName = 'comment';
325
- let match = line.match(RE_COMMENT);
326
- if (match === null) {
327
- matchName = 'group';
328
- match = line.match(RE_GROUP);
329
- }
330
- if (match === null) {
331
- matchName = 'action';
332
- match = line.match(RE_ACTION);
333
- }
334
- if (match === null) {
335
- matchName = 'definition';
336
- match = line.match(RE_DEFINITION);
337
- }
338
- if (match === null && action !== null) {
339
- matchName = 'section';
340
- match = line.match(RE_SECTION);
341
- }
342
- if (match === null && action !== null) {
343
- matchName = 'section_plain';
344
- match = line.match(RE_SECTION_PLAIN);
345
- }
346
- if (match === null && userType !== null && 'enum' in userType) {
347
- matchName = 'value';
348
- match = line.match(RE_VALUE);
349
- if (match === null) {
350
- match = line.match(RE_VALUE_QUOTED);
351
- }
352
- }
353
- if (match === null && userType !== null && 'struct' in userType) {
354
- matchName = 'member';
355
- match = line.match(RE_MEMBER);
356
- }
357
- if (match === null && urls !== null) {
358
- matchName = 'urls';
359
- match = line.match(RE_URL);
360
- }
361
- if (match === null) {
362
- matchName = 'typedef';
363
- match = line.match(RE_TYPEDEF);
364
- }
365
- if (match === null) {
366
- matchName = null;
367
- }
368
-
369
- // Comment?
370
- if (matchName === 'comment') {
371
- const docString = match.groups.doc;
372
- if (typeof docString !== 'undefined') {
373
- doc.push(!docString.startsWith(' ') ? docString : docString.slice(1));
374
- }
375
-
376
- // Documentation group?
377
- } else if (matchName === 'group') {
378
- docGroup = match.groups.group;
379
- if (typeof docGroup !== 'undefined') {
380
- docGroup = docGroup.trim();
381
- } else {
382
- docGroup = null;
383
- }
384
-
385
- // Action?
386
- } else if (matchName === 'action') {
387
- const actionId = match.groups.id;
388
-
389
- // Action already defined?
390
- if (actionId in this.types) {
391
- this.addError(`Redefinition of action '${actionId}'`, filename, linenum);
392
- }
393
-
394
- // Clear parser state
395
- urls = null;
396
- userType = null;
397
- const actionDoc = getDoc();
398
-
399
- // Create the new action
400
- action = {'name': actionId};
401
- this.types[actionId] = {'action': action};
402
- if (actionDoc !== null) {
403
- action.doc = actionDoc;
404
- }
405
- if (docGroup !== null) {
406
- action.docGroup = docGroup;
407
- }
408
-
409
- // Definition?
410
- } else if (matchName === 'definition') {
411
- const definitionString = match.groups.type;
412
- const definitionId = match.groups.id;
413
- const definitionBaseIds = match.groups.base_ids;
414
-
415
- // Type already defined?
416
- if (BUILTIN_TYPES.has(definitionId) || definitionId in this.types) {
417
- this.addError(`Redefinition of type '${definitionId}'`, filename, linenum);
418
- }
419
-
420
- // Clear parser state
421
- action = null;
422
- urls = null;
423
- const definitionDoc = getDoc();
424
-
425
- // Struct definition
426
- if (definitionString === 'struct' || definitionString === 'union') {
427
- // Create the new struct type
428
- const struct = {'name': definitionId};
429
- userType = {'struct': struct};
430
- this.types[definitionId] = userType;
431
- if (definitionDoc !== null) {
432
- struct.doc = definitionDoc;
433
- }
434
- if (docGroup !== null) {
435
- struct.docGroup = docGroup;
436
- }
437
- if (definitionString === 'union') {
438
- struct.union = true;
439
- }
440
- if (typeof definitionBaseIds !== 'undefined') {
441
- struct.bases = definitionBaseIds.split(RE_BASE_IDS_SPLIT);
442
- }
443
-
444
- // Enum definition
445
- } else {
446
- // definition_string == 'enum':
447
- // Create the new enum type
448
- const enum_ = {'name': definitionId};
449
- userType = {'enum': enum_};
450
- this.types[definitionId] = userType;
451
- if (definitionDoc !== null) {
452
- enum_.doc = definitionDoc;
453
- }
454
- if (docGroup !== null) {
455
- enum_.docGroup = docGroup;
456
- }
457
- if (typeof definitionBaseIds !== 'undefined') {
458
- enum_.bases = definitionBaseIds.split(RE_BASE_IDS_SPLIT);
459
- }
460
- }
461
-
462
- // Record finalization information
463
- this.filepos[definitionId] = [filename, linenum];
464
-
465
- // Action section?
466
- } else if (matchName === 'section') {
467
- const sectionString = match.groups.type;
468
- const sectionBaseIds = match.groups.base_ids;
469
-
470
- // Action section redefinition?
471
- if (sectionString in action) {
472
- this.addError(`Redefinition of action ${sectionString}`, filename, linenum);
473
- }
474
-
475
- // Clear parser state
476
- urls = null;
477
-
478
- // Set the action section type
479
- const sectionTypeName = `${action.name}_${sectionString}`;
480
- action[sectionString] = sectionTypeName;
481
- if (sectionString === 'errors') {
482
- const enum_ = {'name': sectionTypeName};
483
- userType = {'enum': enum_};
484
- this.types[sectionTypeName] = userType;
485
- if (typeof sectionBaseIds !== 'undefined') {
486
- enum_.bases = sectionBaseIds.split(RE_BASE_IDS_SPLIT);
487
- }
488
- } else {
489
- const struct = {'name': sectionTypeName};
490
- userType = {'struct': struct};
491
- this.types[sectionTypeName] = userType;
492
- if (typeof sectionBaseIds !== 'undefined') {
493
- struct.bases = sectionBaseIds.split(RE_BASE_IDS_SPLIT);
494
- }
495
- }
496
-
497
- // Record finalization information
498
- this.filepos[sectionTypeName] = [filename, linenum];
499
-
500
- // Plain action section?
501
- } else if (matchName === 'section_plain') {
502
- const sectionString = match.groups.type;
503
-
504
- // Action section redefinition?
505
- if (sectionString in action) {
506
- this.addError(`Redefinition of action ${sectionString}`, filename, linenum);
507
- }
508
-
509
- // Clear parser state
510
- userType = null;
511
-
512
- // Update the parser state
513
- urls = [];
514
-
515
- // Enum value?
516
- } else if (matchName === 'value') {
517
- const valueString = match.groups.id;
518
-
519
- // Add the enum value
520
- const enum_ = userType.enum;
521
- if (!('values' in enum_)) {
522
- enum_.values = [];
523
- }
524
- const enumValue = {'name': valueString};
525
- enum_.values.push(enumValue);
526
- const enumValueDoc = getDoc();
527
- if (enumValueDoc !== null) {
528
- enumValue.doc = enumValueDoc;
529
- }
530
-
531
- // Record finalization information
532
- this.filepos[`${enum_.name}.${valueString}`] = [filename, linenum];
533
-
534
- // Struct member?
535
- } else if (matchName === 'member') {
536
- const optional = typeof match.groups.optional !== 'undefined';
537
- const memberName = match.groups.id;
538
-
539
- // Add the member
540
- const {struct} = userType;
541
- if (!('members' in struct)) {
542
- struct.members = [];
543
- }
544
- const [memberType, memberAttr] = parseTypedef(match);
545
- const memberDoc = getDoc();
546
- const member = {
547
- 'name': memberName,
548
- 'type': memberType
549
- };
550
- struct.members.push(member);
551
- if (memberAttr !== null) {
552
- member.attr = memberAttr;
553
- }
554
- if (memberDoc !== null) {
555
- member.doc = memberDoc;
556
- }
557
- if (optional) {
558
- member.optional = true;
559
- }
560
-
561
- // Record finalization information
562
- this.filepos[`${struct.name}.${memberName}`] = [filename, linenum];
563
-
564
- // URL?
565
- } else if (matchName === 'urls') {
566
- const {method, path} = match.groups;
567
-
568
- // Create the action URL object
569
- const actionUrl = {};
570
- if (method !== '*') {
571
- actionUrl.method = method;
572
- }
573
- if (typeof path !== 'undefined') {
574
- actionUrl.path = path;
575
- }
576
-
577
- // Duplicate URL?
578
- if (urls.some((url) => url.method === actionUrl.method && url.path === actionUrl.path)) {
579
- this.addError(`Duplicate URL: ${method} ${'path' in actionUrl ? actionUrl.path : ''}`, filename, linenum);
580
- }
581
-
582
- // Add the URL
583
- if (!('urls' in action)) {
584
- action.urls = urls;
585
- }
586
- urls.push(actionUrl);
587
-
588
- // Typedef?
589
- } else if (matchName === 'typedef') {
590
- const definitionId = match.groups.id;
591
-
592
- // Type already defined?
593
- if (BUILTIN_TYPES.has(definitionId) || definitionId in this.types) {
594
- this.addError(`Redefinition of type '${definitionId}'`, filename, linenum);
595
- }
596
-
597
- // Clear parser state
598
- action = null;
599
- urls = null;
600
- userType = null;
601
- const typedefDoc = getDoc();
602
-
603
- // Create the typedef
604
- const [typedefType, typedefAttr] = parseTypedef(match);
605
- const typedef = {
606
- 'name': definitionId,
607
- 'type': typedefType
608
- };
609
- this.types[definitionId] = {'typedef': typedef};
610
- if (typedefAttr !== null) {
611
- typedef.attr = typedefAttr;
612
- }
613
- if (typedefDoc !== null) {
614
- typedef.doc = typedefDoc;
615
- }
616
- if (docGroup !== null) {
617
- typedef.docGroup = docGroup;
618
- }
619
-
620
- // Record finalization information
621
- this.filepos[definitionId] = [filename, linenum];
622
-
623
- // Unrecognized line syntax
624
- } else {
625
- this.addError('Syntax error', filename, linenum);
626
- }
627
- }
628
- }
629
-
630
- // Finalize, if requested
631
- if (finalize) {
632
- this.finalize();
633
- }
634
- }
635
- }
package/lib/schema.js CHANGED
@@ -10,10 +10,10 @@ import {validateTypeModelErrors} from './schemaUtil.js';
10
10
  /**
11
11
  * Get a user type's referenced type model
12
12
  *
13
- * @param {Object} types - The map of user type name to user type model
13
+ * @param {Object} types - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
14
14
  * @param {string} typeName - The type name
15
15
  * @param {Object} [referencedTypes=null] - Optional map of referenced user type name to user type model
16
- * @returns {Object}
16
+ * @returns {Object} The referenced [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
17
17
  */
18
18
  export function getReferencedTypes(types, typeName, referencedTypes = {}) {
19
19
  return getReferencedTypesHelper(types, {'user': typeName}, referencedTypes);
@@ -104,7 +104,7 @@ export class ValidationError extends Error {
104
104
  /**
105
105
  * Schema Markdown type model validation error constructor
106
106
  *
107
- * @param {string} [memberFqn=null] - Optional fully-qualified member name
107
+ * @param {string} [memberFqn=null] - The fully-qualified member name
108
108
  */
109
109
  constructor(message, memberFqn = null) {
110
110
  super(message);
@@ -117,12 +117,12 @@ export class ValidationError extends Error {
117
117
  * Type-validate a value using a user type model. Container values are duplicated since some member types are
118
118
  * transformed during validation.
119
119
  *
120
- * @param {Object} types - The map of user type name to user type model
120
+ * @param {Object} types - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
121
121
  * @param {string} typeName - The type name
122
122
  * @param {Object} value - The value object to validate
123
- * @param {string} [memberFqn=null] - Optional fully-qualified member name
123
+ * @param {?string} [memberFqn=null] - The fully-qualified member name
124
124
  * @returns {Object} The validated, transformed value object
125
- * @throws {ValidationError} A validation error occurred
125
+ * @throws [ValidationError]{@link module:lib/schema.ValidationError}
126
126
  */
127
127
  export function validateType(types, typeName, value, memberFqn = null) {
128
128
  if (!(typeName in types)) {
@@ -448,9 +448,10 @@ function validateAttr(type, attr, value, memberFqn) {
448
448
  /**
449
449
  * Get the struct's members (inherited members first)
450
450
  *
451
- * @param {Object} types - The map of user type name to user type model
452
- * @param {Object} struct - The struct model
453
- * @returns {Array<Object>} The array of struct member models
451
+ * @param {Object} types - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
452
+ * @param {Object} struct - The [struct model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Struct'}
453
+ * @returns {Array<Object>} The array of
454
+ * [struct member models]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='StructMember'}
454
455
  */
455
456
  export function getStructMembers(types, struct) {
456
457
  // No base structs?
@@ -480,9 +481,10 @@ export function getStructMembers(types, struct) {
480
481
  /**
481
482
  * Get the enum's values (inherited values first)
482
483
  *
483
- * @param {Object} types - The map of user type name to user type model
484
- * @param {Object} enum_ - The enum model
485
- * @returns {Array<Object>} The array of enum value models
484
+ * @param {Object} types - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
485
+ * @param {Object} enum_ - The [enum model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Enum'}
486
+ * @returns {Array<Object>} The array of
487
+ * [enum value models]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='EnumValue'}
486
488
  */
487
489
  export function getEnumValues(types, enum_) {
488
490
  // No base enums?
@@ -512,9 +514,9 @@ export function getEnumValues(types, enum_) {
512
514
  /**
513
515
  * Validate a type model's types object
514
516
  *
515
- * @param {Object} types - The map of user type name to user type model
516
- * @returns {Object} The validated, transformed types object
517
- * @throws {ValidationError} A validation error occurred
517
+ * @param {Object} types - The [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
518
+ * @returns {Object} The validated [type model]{@link https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types'}
519
+ * @throws [ValidationError]{@link module:lib/schema.ValidationError}
518
520
  */
519
521
  export function validateTypeModel(types) {
520
522
  // Validate with the type model
package/lib/typeModel.js CHANGED
@@ -3,11 +3,13 @@
3
3
 
4
4
  /** @module lib/typeModel */
5
5
 
6
- import {SchemaMarkdownParser} from './parser.js';
6
+ import {parseSchemaMarkdown} from './parser.js';
7
7
 
8
8
 
9
- // The Schema Markdown type model defined as Schema Markdown
10
- const typeModelSmd = `\
9
+ /**
10
+ * The Schema Markdown type model
11
+ */
12
+ export const typeModel = parseSchemaMarkdown(`\
11
13
  # Map of user type name to user type model
12
14
  typedef UserType{len > 0} Types
13
15
 
@@ -240,10 +242,4 @@ struct ActionURL
240
242
 
241
243
  # The URL path. If not provided, uses the default URL path of "/<actionName>".
242
244
  optional string path
243
- `;
244
-
245
-
246
- /**
247
- * The Schema Markdown type model
248
- */
249
- export const typeModel = (new SchemaMarkdownParser(typeModelSmd)).types;
245
+ `);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "schema-markdown",
4
- "version": "1.1.6",
5
- "description": "Human-friendly schema definition language and schema validator",
4
+ "version": "1.2.2",
5
+ "description": "A schema definition and validation library",
6
6
  "keywords": [
7
7
  "schema",
8
8
  "definition",
@@ -24,10 +24,10 @@
24
24
  "test": "ava -v test/"
25
25
  },
26
26
  "devDependencies": {
27
- "ava": "~4.2",
28
- "c8": "~7.11",
29
- "eslint": "~8.14",
27
+ "ava": "~4.3",
28
+ "c8": "~7.12",
29
+ "eslint": "~8.21",
30
30
  "jsdoc": "~3.6",
31
- "jsdom": "~19.0"
31
+ "jsdom": "~20.0"
32
32
  }
33
33
  }