schema-markdown 1.0.6 → 1.1.3

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
@@ -24,13 +24,14 @@ its features at a glance:
24
24
  ## Usage
25
25
 
26
26
  To schema-validate an object, first parse its *Schema Markdown* using the
27
- [SchemaMarkdownParser](https://craigahobbs.github.io/schema-markdown-js/SchemaMarkdownParser.html)
27
+ [SchemaMarkdownParser](https://craigahobbs.github.io/schema-markdown-js/module-lib_parser.SchemaMarkdownParser.html)
28
28
  class:
29
29
 
30
30
  ``` javascript
31
- import * as smd from 'schema_markdown/index.js';
31
+ import {SchemaMarkdownParser} from 'schema-markdown/parser.js';
32
+ import {validateType} from 'schema-markdown/schema.js';
32
33
 
33
- const parser = new smd.SchemaMarkdownParser(`\
34
+ const parser = new SchemaMarkdownParser(`\
34
35
  # An aggregation function
35
36
  enum Aggregation
36
37
  Average
@@ -47,11 +48,11 @@ struct Operation
47
48
  ```
48
49
 
49
50
  Then, validate an object using the
50
- [validateType](https://craigahobbs.github.io/schema-markdown-js/global.html#validateType)
51
+ [validateType](https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.html#.validateType)
51
52
  function:
52
53
 
53
54
  ``` javascript
54
- const obj = smd.validateType(parser.types, 'Operation', {
55
+ const obj = validateType(parser.types, 'Operation', {
55
56
  'numbers': [1, 2, '3', 4]
56
57
  });
57
58
  console.assert(obj.numbers[2] === 3);
@@ -62,7 +63,7 @@ Validation fails if the object does not match the schema:
62
63
 
63
64
  ``` javascript
64
65
  try {
65
- smd.validateType(parser.types, 'Operation', {
66
+ validateType(parser.types, 'Operation', {
66
67
  'numbers': [1, 2, 'asdf', 4]
67
68
  });
68
69
  } catch ({message}) {
@@ -74,7 +75,7 @@ Validation also fails if a member contraint is violated:
74
75
 
75
76
  ``` javascript
76
77
  try {
77
- smd.validateType(parser.types, 'Operation', {
78
+ validateType(parser.types, 'Operation', {
78
79
  'numbers': []
79
80
  });
80
81
  } catch ({message}) {
@@ -85,8 +86,8 @@ try {
85
86
 
86
87
  ## Development
87
88
 
88
- This project is developed using [javascript-build](https://github.com/craigahobbs/javascript-build#readme). It was started
89
- using [javascript-template](https://github.com/craigahobbs/javascript-template#readme) as follows:
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):
90
91
 
91
92
  ```
92
93
  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/encode.js ADDED
@@ -0,0 +1,130 @@
1
+ // Licensed under the MIT License
2
+ // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
+
4
+ /** @module lib/encode */
5
+
6
+
7
+ /**
8
+ * Encode an object as a query/hash string. Dictionaries, lists, and tuples are recursed. Each member key is expressed
9
+ * in fully-qualified form. List keys are the index into the list, and are in order.
10
+ *
11
+ * @param {Object} obj - The parameters object
12
+ * @returns {string}
13
+ */
14
+ export function encodeQueryString(obj) {
15
+ return encodeQueryStringHelper(obj).join('&');
16
+ }
17
+
18
+
19
+ // Helper function for encodeQueryString
20
+ function encodeQueryStringHelper(obj, memberFqn = null, keyValues = []) {
21
+ const objType = typeof obj;
22
+ if (Array.isArray(obj)) {
23
+ if (obj.length === 0) {
24
+ keyValues.push(memberFqn !== null ? `${memberFqn}=` : '');
25
+ } else {
26
+ for (let ix = 0; ix < obj.length; ix++) {
27
+ encodeQueryStringHelper(obj[ix], memberFqn !== null ? `${memberFqn}.${ix}` : `${ix}`, keyValues);
28
+ }
29
+ }
30
+ } else if (obj instanceof Date) {
31
+ const objEncoded = encodeURIComponent(obj.toISOString());
32
+ keyValues.push(memberFqn !== null ? `${memberFqn}=${objEncoded}` : `${objEncoded}`);
33
+ } else if (obj !== null && typeof obj === 'object') {
34
+ const keys = Object.keys(obj).sort();
35
+ if (keys.length === 0) {
36
+ keyValues.push(memberFqn !== null ? `${memberFqn}=` : '');
37
+ } else {
38
+ for (const key of keys) {
39
+ const keyEncoded = encodeQueryStringHelper(key);
40
+ encodeQueryStringHelper(obj[key], memberFqn !== null ? `${memberFqn}.${keyEncoded}` : `${keyEncoded}`, keyValues);
41
+ }
42
+ }
43
+ } else if (obj === null || objType === 'boolean' || objType === 'number') {
44
+ keyValues.push(memberFqn !== null ? `${memberFqn}=${obj}` : `${obj}`);
45
+ } else {
46
+ const objEncoded = encodeURIComponent(obj);
47
+ keyValues.push(memberFqn !== null ? `${memberFqn}=${objEncoded}` : `${objEncoded}`);
48
+ }
49
+ return keyValues;
50
+ }
51
+
52
+
53
+ /**
54
+ * Decode an object from a query/hash string. Each member key of the query string is expressed in fully-qualified
55
+ * form. List keys are the index into the list, must be in order.
56
+ *
57
+ * @param {string} paramStr - The parameters string
58
+ * @returns {Object}
59
+ */
60
+ export function decodeQueryString(paramStr) {
61
+ // Decode the parameter string key/values
62
+ const result = [null];
63
+ const keyValues = paramStr.split('&');
64
+ keyValues.forEach((keyValue, ixKeyValue) => {
65
+ const [keyFqn, valueEncoded = null] = keyValue.split('=', 2);
66
+ if (valueEncoded === null) {
67
+ // Ignore hash IDs
68
+ if (ixKeyValue === keyValues.length - 1) {
69
+ return;
70
+ }
71
+ throw new Error(`Invalid key/value pair '${keyValue.slice(0, 100)}'`);
72
+ }
73
+ const value = decodeURIComponent(valueEncoded);
74
+
75
+ // Find/create the object on which to set the value
76
+ let parent = result;
77
+ let keyParent = 0;
78
+ const keys = keyFqn.split('.');
79
+ for (const keyEncoded of keys) {
80
+ let key = decodeURIComponent(keyEncoded);
81
+ let obj = parent[keyParent];
82
+
83
+ // Array key? First "key" of an array must start with "0".
84
+ if (Array.isArray(obj) || (obj === null && key === '0')) {
85
+ // Create this key's container, if necessary
86
+ if (obj === null) {
87
+ obj = [];
88
+ parent[keyParent] = obj;
89
+ }
90
+
91
+ // Parse the key as an integer
92
+ if (isNaN(key)) {
93
+ throw new Error(`Invalid array index '${key.slice(0, 100)}' in key '${keyFqn.slice(0, 100)}'`);
94
+ }
95
+ const keyOrig = key;
96
+ key = parseInt(key, 10);
97
+
98
+ // Append the value placeholder null
99
+ if (key === obj.length) {
100
+ obj.push(null);
101
+ } else if (key < 0 || key > obj.length) {
102
+ throw new Error(`Invalid array index '${keyOrig.slice(0, 100)}' in key '${keyFqn.slice(0, 100)}'`);
103
+ }
104
+ } else {
105
+ // Create this key's container, if necessary
106
+ if (obj === null) {
107
+ obj = {};
108
+ parent[keyParent] = obj;
109
+ }
110
+
111
+ // Create the index for this key
112
+ if (!(key in obj)) {
113
+ obj[key] = null;
114
+ }
115
+ }
116
+
117
+ // Update the parent object and key
118
+ parent = obj;
119
+ keyParent = key;
120
+ }
121
+
122
+ // Set the value
123
+ if (parent[keyParent] !== null) {
124
+ throw new Error(`Duplicate key '${keyFqn.slice(0, 100)}'`);
125
+ }
126
+ parent[keyParent] = value;
127
+ });
128
+
129
+ return result[0] !== null ? result[0] : {};
130
+ }
@@ -1,6 +1,8 @@
1
1
  // Licensed under the MIT License
2
2
  // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
3
 
4
+ /** @module lib/parser */
5
+
4
6
  import {validateTypeModelTypesErrors} from './schemaUtil.js';
5
7
 
6
8
  /* eslint-disable prefer-template */
@@ -1,6 +1,8 @@
1
1
  // Licensed under the MIT License
2
2
  // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
3
 
4
+ /** @module lib/schema */
5
+
4
6
  import {typeModel} from './typeModel.js';
5
7
  import {validateTypeModelTypesErrors} from './schemaUtil.js';
6
8
 
@@ -233,12 +235,12 @@ function validateTypeHelper(types, type, value, memberFqn) {
233
235
  for (let ixArrayValue = 0; ixArrayValue < valueNew.length; ixArrayValue++) {
234
236
  const memberFqnValue = memberFqn !== null ? `${memberFqn}.${ixArrayValue}` : `${ixArrayValue}`;
235
237
  let arrayValue = valueNew[ixArrayValue];
236
- if (arrayValue === null || (arrayValueNullable && arrayValue === 'null')) {
238
+ if (arrayValueNullable && (arrayValue === null || arrayValue === 'null')) {
237
239
  arrayValue = null;
238
240
  } else {
239
241
  arrayValue = validateTypeHelper(types, arrayType, arrayValue, memberFqnValue);
242
+ validateAttr(arrayType, arrayAttr, arrayValue, memberFqnValue);
240
243
  }
241
- validateAttr(arrayType, arrayAttr, arrayValue, memberFqnValue);
242
244
  valueCopy.push(arrayValue);
243
245
  }
244
246
 
@@ -267,20 +269,20 @@ function validateTypeHelper(types, type, value, memberFqn) {
267
269
  const memberFqnKey = memberFqn !== null ? `${memberFqn}.${dictKey}` : `${dictKey}`;
268
270
 
269
271
  // Validate the key
270
- if (dictKey === null || (dictKeyNullable && dictKey === 'null')) {
272
+ if (dictKeyNullable && (dictKey === null || dictKey === 'null')) {
271
273
  dictKey = null;
272
274
  } else {
273
275
  dictKey = validateTypeHelper(types, dictKeyType, dictKey, memberFqn);
276
+ validateAttr(dictKeyType, dictKeyAttr, dictKey, memberFqn);
274
277
  }
275
- validateAttr(dictKeyType, dictKeyAttr, dictKey, memberFqn);
276
278
 
277
279
  // Validate the value
278
- if (dictValue === null || (dictValueNullable && dictValue === 'null')) {
280
+ if (dictValueNullable && (dictValue === null || dictValue === 'null')) {
279
281
  dictValue = null;
280
282
  } else {
281
283
  dictValue = validateTypeHelper(types, dictType, dictValue, memberFqnKey);
284
+ validateAttr(dictType, dictAttr, dictValue, memberFqnKey);
282
285
  }
283
- validateAttr(dictType, dictAttr, dictValue, memberFqnKey);
284
286
 
285
287
  // Copy the key/value
286
288
  if (valueCopy instanceof Map) {
@@ -309,12 +311,12 @@ function validateTypeHelper(types, type, value, memberFqn) {
309
311
 
310
312
  // Validate the value
311
313
  const valueNullable = typedefAttr !== null && 'nullable' in typedefAttr && typedefAttr.nullable;
312
- if (value === null || (valueNullable && value === 'null')) {
314
+ if (valueNullable && (value === null || value === 'null')) {
313
315
  valueNew = null;
314
316
  } else {
315
317
  valueNew = validateTypeHelper(types, typedef.type, value, memberFqn);
318
+ validateAttr(type, typedefAttr, valueNew, memberFqn);
316
319
  }
317
- validateAttr(type, typedefAttr, valueNew, memberFqn);
318
320
 
319
321
  // enum?
320
322
  } else if ('enum' in userType) {
@@ -349,7 +351,8 @@ function validateTypeHelper(types, type, value, memberFqn) {
349
351
  for (const member of getStructMembers(types, struct)) {
350
352
  const memberName = member.name;
351
353
  const memberFqnMember = memberFqn !== null ? `${memberFqn}.${memberName}` : `${memberName}`;
352
- const memberOptional = 'optional' in member ? member.optional : false;
354
+ const memberOptional = 'optional' in member && member.optional;
355
+ const memberNullable = 'attr' in member && 'nullable' in member.attr && member.attr.nullable;
353
356
 
354
357
  // Missing non-optional member?
355
358
  if (!(valueNew instanceof Map ? valueNew.has(memberName) : memberName in valueNew)) {
@@ -359,10 +362,12 @@ function validateTypeHelper(types, type, value, memberFqn) {
359
362
  } else {
360
363
  // Validate the member value
361
364
  let memberValue = valueNew instanceof Map ? valueNew.get(memberName) : valueNew[memberName];
362
- if (memberValue !== null) {
365
+ if (memberNullable && (memberValue === null || memberValue === 'null')) {
366
+ memberValue = null;
367
+ } else {
363
368
  memberValue = validateTypeHelper(types, member.type, memberValue, memberFqnMember);
369
+ validateAttr(member.type, 'attr' in member ? member.attr : null, memberValue, memberFqnMember);
364
370
  }
365
- validateAttr(member.type, 'attr' in member ? member.attr : null, memberValue, memberFqnMember);
366
371
 
367
372
  // Copy the validated member
368
373
  if (valueCopy instanceof Map) {
@@ -403,12 +408,7 @@ function throwMemberError(type, value, memberFqn, attr = null) {
403
408
 
404
409
 
405
410
  function validateAttr(type, attr, value, memberFqn) {
406
- if (value === null) {
407
- const valueNullable = attr !== null && 'nullable' in attr && attr.nullable;
408
- if (!valueNullable) {
409
- throwMemberError(type, value, memberFqn);
410
- }
411
- } else if (attr !== null) {
411
+ if (attr !== null) {
412
412
  const length = Array.isArray(value) || typeof value === 'string' ? value.length
413
413
  : (typeof value === 'object' ? Object.keys(value).length : null);
414
414
 
File without changes
@@ -1,6 +1,8 @@
1
1
  // Licensed under the MIT License
2
2
  // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
3
 
4
+ /** @module lib/typeModel */
5
+
4
6
  import {SchemaMarkdownParser} from './parser.js';
5
7
 
6
8
 
@@ -251,7 +253,9 @@ struct ActionURL
251
253
  `;
252
254
 
253
255
 
254
- // The Schema Markdown type model
256
+ /**
257
+ * The Schema Markdown type model
258
+ */
255
259
  export const typeModel = {
256
260
  'title': 'The Schema Markdown Type Model',
257
261
  'types': (new SchemaMarkdownParser(typeModelSmd)).types
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "schema-markdown",
4
- "version": "1.0.6",
4
+ "version": "1.1.3",
5
5
  "description": "Human-friendly schema definition language and schema validator",
6
6
  "keywords": [
7
7
  "schema",
@@ -18,17 +18,16 @@
18
18
  "url": "https://github.com/craigahobbs"
19
19
  },
20
20
  "files": [
21
- "index.js",
22
- "src/schema-markdown/**/*"
21
+ "lib/**/*"
23
22
  ],
24
23
  "scripts": {
25
- "test": "ava -v 'src/tests/**/*.js'"
24
+ "test": "ava -v test/"
26
25
  },
27
26
  "devDependencies": {
28
27
  "ava": "^4.0.0-rc.1",
29
28
  "c8": "~7.10",
30
- "eslint": "~8.3",
29
+ "eslint": "~8.4",
31
30
  "jsdoc": "~3.6",
32
- "jsdom": "~18.1"
31
+ "jsdom": "~19.0"
33
32
  }
34
33
  }
package/index.js DELETED
@@ -1,4 +0,0 @@
1
- // Licensed under the MIT License
2
- // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
-
4
- export * from './src/schema-markdown/index.js';
@@ -1,129 +0,0 @@
1
- // Licensed under the MIT License
2
- // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
-
4
-
5
- /**
6
- * Encode an object as a query/hash string. Dictionaries, lists, and tuples are recursed. Each member key is expressed
7
- * in fully-qualified form. List keys are the index into the list, and are in order.
8
- *
9
- * @param {Object} obj - The parameters object
10
- * @returns {string}
11
- */
12
- export function encodeQueryString(obj) {
13
- return encodeQueryStringHelper(obj).join('&');
14
- }
15
-
16
-
17
- // Helper function for encodeQueryString
18
- function encodeQueryStringHelper(obj, memberFqn = null, keyValues = []) {
19
- const objType = typeof obj;
20
- if (Array.isArray(obj)) {
21
- if (obj.length === 0) {
22
- keyValues.push(memberFqn !== null ? `${memberFqn}=` : '');
23
- } else {
24
- for (let ix = 0; ix < obj.length; ix++) {
25
- encodeQueryStringHelper(obj[ix], memberFqn !== null ? `${memberFqn}.${ix}` : `${ix}`, keyValues);
26
- }
27
- }
28
- } else if (obj instanceof Date) {
29
- const objEncoded = encodeURIComponent(obj.toISOString());
30
- keyValues.push(memberFqn !== null ? `${memberFqn}=${objEncoded}` : `${objEncoded}`);
31
- } else if (obj !== null && typeof obj === 'object') {
32
- const keys = Object.keys(obj).sort();
33
- if (keys.length === 0) {
34
- keyValues.push(memberFqn !== null ? `${memberFqn}=` : '');
35
- } else {
36
- for (const key of keys) {
37
- const keyEncoded = encodeQueryStringHelper(key);
38
- encodeQueryStringHelper(obj[key], memberFqn !== null ? `${memberFqn}.${keyEncoded}` : `${keyEncoded}`, keyValues);
39
- }
40
- }
41
- } else if (obj === null || objType === 'boolean' || objType === 'number') {
42
- keyValues.push(memberFqn !== null ? `${memberFqn}=${obj}` : `${obj}`);
43
- } else {
44
- const objEncoded = encodeURIComponent(obj);
45
- keyValues.push(memberFqn !== null ? `${memberFqn}=${objEncoded}` : `${objEncoded}`);
46
- }
47
- return keyValues;
48
- }
49
-
50
-
51
- /**
52
- * Decode an object from a query/hash string. Each member key of the query string is expressed in fully-qualified
53
- * form. List keys are the index into the list, must be in order.
54
- *
55
- * @param {string} paramStr - The parameters string
56
- * @returns {Object}
57
- */
58
- export function decodeQueryString(paramStr) {
59
- // Decode the parameter string key/values
60
- const result = [null];
61
- paramStr.split('&').forEach(
62
- (keyValue, ixKeyValue, keyValuesFiltered) => {
63
- const [keyFqn, valueEncoded = null] = keyValue.split('=', 2);
64
- if (valueEncoded === null) {
65
- // Ignore hash IDs
66
- if (ixKeyValue === keyValuesFiltered.length - 1) {
67
- return;
68
- }
69
- throw new Error(`Invalid key/value pair '${keyValue.slice(0, 100)}'`);
70
- }
71
- const value = decodeURIComponent(valueEncoded);
72
-
73
- // Find/create the object on which to set the value
74
- let parent = result;
75
- let keyParent = 0;
76
- const keys = keyFqn.split('.');
77
- for (const keyEncoded of keys) {
78
- let key = decodeURIComponent(keyEncoded);
79
- let obj = parent[keyParent];
80
-
81
- // Array key? First "key" of an array must start with "0".
82
- if (Array.isArray(obj) || (obj === null && key === '0')) {
83
- // Create this key's container, if necessary
84
- if (obj === null) {
85
- obj = [];
86
- parent[keyParent] = obj;
87
- }
88
-
89
- // Parse the key as an integer
90
- if (isNaN(key)) {
91
- throw new Error(`Invalid array index '${key.slice(0, 100)}' in key '${keyFqn.slice(0, 100)}'`);
92
- }
93
- const keyOrig = key;
94
- key = parseInt(key, 10);
95
-
96
- // Append the value placeholder null
97
- if (key === obj.length) {
98
- obj.push(null);
99
- } else if (key < 0 || key > obj.length) {
100
- throw new Error(`Invalid array index '${keyOrig.slice(0, 100)}' in key '${keyFqn.slice(0, 100)}'`);
101
- }
102
- } else {
103
- // Create this key's container, if necessary
104
- if (obj === null) {
105
- obj = {};
106
- parent[keyParent] = obj;
107
- }
108
-
109
- // Create the index for this key
110
- if (!(key in obj)) {
111
- obj[key] = null;
112
- }
113
- }
114
-
115
- // Update the parent object and key
116
- parent = obj;
117
- keyParent = key;
118
- }
119
-
120
- // Set the value
121
- if (parent[keyParent] !== null) {
122
- throw new Error(`Duplicate key '${keyFqn.slice(0, 100)}'`);
123
- }
124
- parent[keyParent] = value;
125
- }
126
- );
127
-
128
- return result[0] !== null ? result[0] : {};
129
- }
@@ -1,26 +0,0 @@
1
- // Licensed under the MIT License
2
- // https://github.com/craigahobbs/schema-markdown-js/blob/main/LICENSE
3
-
4
- export {
5
- decodeQueryString,
6
- encodeQueryString
7
- } from './encode.js';
8
-
9
- export {
10
- SchemaMarkdownParser,
11
- SchemaMarkdownParserError
12
- } from './parser.js';
13
-
14
- export {
15
- ValidationError,
16
- getEnumValues,
17
- getReferencedTypes,
18
- getStructMembers,
19
- validateType,
20
- validateTypeModel,
21
- validateTypeModelTypes
22
- } from './schema.js';
23
-
24
- export {
25
- typeModel
26
- } from './typeModel.js';