@stxt-lang/core 0.9.0 → 0.10.0

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.
Files changed (54) hide show
  1. package/out/all.d.ts +6 -0
  2. package/out/all.js +8 -1
  3. package/out/core/Constants.d.ts +7 -0
  4. package/out/core/Constants.js +7 -0
  5. package/out/core/LineParser.d.ts +1 -1
  6. package/out/core/LineParser.js +4 -4
  7. package/out/core/NamespaceValidator.d.ts +7 -0
  8. package/out/core/NamespaceValidator.js +9 -0
  9. package/out/core/NodeCreator.js +1 -1
  10. package/out/core/Parser.d.ts +5 -0
  11. package/out/core/Parser.js +12 -8
  12. package/out/exceptions/ParseException.d.ts +7 -1
  13. package/out/exceptions/ParseException.js +8 -2
  14. package/out/exceptions/RuntimeException.d.ts +6 -1
  15. package/out/exceptions/RuntimeException.js +7 -3
  16. package/out/schema/NodeDefinition.d.ts +1 -1
  17. package/out/schema/NodeDefinition.js +2 -2
  18. package/out/schema/Schema.d.ts +1 -1
  19. package/out/schema/Schema.js +2 -2
  20. package/out/schema/SchemaParser.d.ts +2 -1
  21. package/out/schema/SchemaParser.js +24 -9
  22. package/out/schema/SchemaProviderMemory.d.ts +2 -1
  23. package/out/schema/SchemaProviderMemory.js +8 -2
  24. package/out/schema/SchemaValidator.js +7 -7
  25. package/out/schema/TypeRegistry.js +1 -1
  26. package/out/schema/type/BASE64.d.ts +9 -0
  27. package/out/schema/type/BASE64.js +32 -15
  28. package/out/schema/type/DATE.d.ts +1 -1
  29. package/out/schema/type/DATE.js +4 -3
  30. package/out/schema/type/ENUM.js +1 -1
  31. package/out/schema/type/GROUP.js +1 -1
  32. package/out/schema/type/INLINE.js +1 -1
  33. package/out/schema/type/MARKDOWN.js +1 -1
  34. package/out/schema/type/TEXT.js +1 -1
  35. package/out/schema/type/TIME.d.ts +1 -1
  36. package/out/schema/type/TIME.js +4 -3
  37. package/out/schema/type/TIMESTAMP.d.ts +4 -1
  38. package/out/schema/type/TIMESTAMP.js +9 -3
  39. package/out/schema/type/URL.d.ts +10 -3
  40. package/out/schema/type/URL.js +11 -24
  41. package/out/schema/type/binaryValue.d.ts +6 -4
  42. package/out/schema/type/binaryValue.js +8 -8
  43. package/out/schema/type/dateTime.d.ts +9 -0
  44. package/out/schema/type/dateTime.js +23 -0
  45. package/out/schema/type/rangeType.d.ts +11 -0
  46. package/out/schema/type/rangeType.js +30 -0
  47. package/out/schema/type/regexType.js +1 -1
  48. package/out/template/ChildLineParser.d.ts +1 -1
  49. package/out/template/ChildLineParser.js +10 -4
  50. package/out/template/TemplateParser.d.ts +3 -2
  51. package/out/template/TemplateParser.js +30 -14
  52. package/out/template/TemplateSchemaProviderMemory.d.ts +4 -3
  53. package/out/template/TemplateSchemaProviderMemory.js +5 -8
  54. package/package.json +1 -1
package/out/all.d.ts CHANGED
@@ -5,6 +5,12 @@ export { Parser } from "./core/Parser";
5
5
  export { ParseResult } from "./core/ParseResult";
6
6
  export { Line } from "./core/Line";
7
7
  export { Constants } from "./core/Constants";
8
+ /**
9
+ * Version of the STXT specifications (STXT-SPEC, STXT-TREE-SPEC, STXT-SCHEMA-SPEC,
10
+ * STXT-TEMPLATE-SPEC, STXT-DISCOVERY-SPEC) this library implements, distinct from the version of
11
+ * the package. Same value as `Constants.SPEC_VERSION`.
12
+ */
13
+ export declare const SPEC_VERSION: string;
8
14
  export { parseLine } from "./core/LineParser";
9
15
  export { StringUtils } from "./core/StringUtils";
10
16
  export { ParseException } from "./exceptions/ParseException";
package/out/all.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Anything that should be consumable by third parties (e.g. the VSCode extension)
4
4
  // has to be re-exported from here.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DiscoveryError = exports.DiscoveryResult = exports.DiscoveryResolver = exports.transformTemplateNodeToSchema = exports.toCanonicalJson = exports.toCanonicalTree = exports.IndentStyle = exports.NodeWriter = exports.ConditionalValidator = exports.UnifiedSchemaProvider = exports.transformNodeToSchema = exports.ChildDefinition = exports.NodeDefinition = exports.SchemaValidator = exports.Schema = exports.ValidationException = exports.ParseException = exports.StringUtils = exports.parseLine = exports.Constants = exports.Line = exports.ParseResult = exports.Parser = exports.TextNode = exports.InlineNode = exports.Node = void 0;
6
+ exports.DiscoveryError = exports.DiscoveryResult = exports.DiscoveryResolver = exports.transformTemplateNodeToSchema = exports.toCanonicalJson = exports.toCanonicalTree = exports.IndentStyle = exports.NodeWriter = exports.ConditionalValidator = exports.UnifiedSchemaProvider = exports.transformNodeToSchema = exports.ChildDefinition = exports.NodeDefinition = exports.SchemaValidator = exports.Schema = exports.ValidationException = exports.ParseException = exports.StringUtils = exports.parseLine = exports.SPEC_VERSION = exports.Constants = exports.Line = exports.ParseResult = exports.Parser = exports.TextNode = exports.InlineNode = exports.Node = void 0;
7
7
  var Node_1 = require("./core/Node");
8
8
  Object.defineProperty(exports, "Node", { enumerable: true, get: function () { return Node_1.Node; } });
9
9
  var InlineNode_1 = require("./core/InlineNode");
@@ -18,6 +18,13 @@ var Line_1 = require("./core/Line");
18
18
  Object.defineProperty(exports, "Line", { enumerable: true, get: function () { return Line_1.Line; } });
19
19
  var Constants_1 = require("./core/Constants");
20
20
  Object.defineProperty(exports, "Constants", { enumerable: true, get: function () { return Constants_1.Constants; } });
21
+ const Constants_2 = require("./core/Constants");
22
+ /**
23
+ * Version of the STXT specifications (STXT-SPEC, STXT-TREE-SPEC, STXT-SCHEMA-SPEC,
24
+ * STXT-TEMPLATE-SPEC, STXT-DISCOVERY-SPEC) this library implements, distinct from the version of
25
+ * the package. Same value as `Constants.SPEC_VERSION`.
26
+ */
27
+ exports.SPEC_VERSION = Constants_2.Constants.SPEC_VERSION;
21
28
  var LineParser_1 = require("./core/LineParser");
22
29
  Object.defineProperty(exports, "parseLine", { enumerable: true, get: function () { return LineParser_1.parseLine; } });
23
30
  var StringUtils_1 = require("./core/StringUtils");
@@ -1,5 +1,12 @@
1
1
  /** Characters and sizes fixed by STXT-SPEC that the parser and the writer share. */
2
2
  export declare class Constants {
3
+ /**
4
+ * Version of the STXT specifications (STXT-SPEC, STXT-TREE-SPEC, STXT-SCHEMA-SPEC,
5
+ * STXT-TEMPLATE-SPEC, STXT-DISCOVERY-SPEC) this library implements. It is distinct from the
6
+ * version of the package: the package may be released many times against the same
7
+ * specification version.
8
+ */
9
+ static readonly SPEC_VERSION: string;
3
10
  /** Character that opens a comment line. */
4
11
  static readonly COMMENT_CHAR: string;
5
12
  /** Number of spaces that make up one indentation level. */
@@ -5,6 +5,13 @@ exports.Constants = void 0;
5
5
  class Constants {
6
6
  }
7
7
  exports.Constants = Constants;
8
+ /**
9
+ * Version of the STXT specifications (STXT-SPEC, STXT-TREE-SPEC, STXT-SCHEMA-SPEC,
10
+ * STXT-TEMPLATE-SPEC, STXT-DISCOVERY-SPEC) this library implements. It is distinct from the
11
+ * version of the package: the package may be released many times against the same
12
+ * specification version.
13
+ */
14
+ Constants.SPEC_VERSION = "1.0";
8
15
  /** Character that opens a comment line. */
9
16
  Constants.COMMENT_CHAR = "#";
10
17
  /** Number of spaces that make up one indentation level. */
@@ -16,7 +16,7 @@ import { Line } from "./Line";
16
16
  * @param numLine line number, for the error messages.
17
17
  * @param validate false to split the line without enforcing the indentation rules.
18
18
  * @returns the line already split into indentation and content.
19
- * @throws ParseException with code `MIXED_INDENTATION`, `INVALID_NUMBER_SPACES` or
19
+ * @throws ParseException with code `INDENTATION_MIXED`, `INDENTATION_SPACES_NOT_VALID` or
20
20
  * `INDENTATION_LEVEL_NOT_VALID` if the indentation is not valid.
21
21
  */
22
22
  export declare function parseLine(line: string, lastNodeBlock: boolean, lastLevel: number, numLine: number, validate?: boolean): Line;
@@ -22,7 +22,7 @@ const Line_1 = require("./Line");
22
22
  * @param numLine line number, for the error messages.
23
23
  * @param validate false to split the line without enforcing the indentation rules.
24
24
  * @returns the line already split into indentation and content.
25
- * @throws ParseException with code `MIXED_INDENTATION`, `INVALID_NUMBER_SPACES` or
25
+ * @throws ParseException with code `INDENTATION_MIXED`, `INDENTATION_SPACES_NOT_VALID` or
26
26
  * `INDENTATION_LEVEL_NOT_VALID` if the indentation is not valid.
27
27
  */
28
28
  function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
@@ -66,7 +66,7 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
66
66
  // The prefix covering the block level must be homogeneous (spec 10.2, rule 2);
67
67
  // empty lines are always preserved and are exempt from it (spec 10.3)
68
68
  if (validate && sawSpace && sawTab && text.length > 0) {
69
- throw new ParseException_1.ParseException(numLine, "MIXED_INDENTATION", `Mixed tabs and spaces in indentation`);
69
+ throw new ParseException_1.ParseException(numLine, "INDENTATION_MIXED", `Mixed tabs and spaces in indentation`);
70
70
  }
71
71
  return new Line_1.Line(level, text, false, true, pointer);
72
72
  }
@@ -83,11 +83,11 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
83
83
  }
84
84
  // Tabs and spaces mixed in the indentation (spec 8.1 and 8.3)
85
85
  if (validate && sawSpace && sawTab) {
86
- throw new ParseException_1.ParseException(numLine, "MIXED_INDENTATION", `Mixed tabs and spaces in indentation`);
86
+ throw new ParseException_1.ParseException(numLine, "INDENTATION_MIXED", `Mixed tabs and spaces in indentation`);
87
87
  }
88
88
  // Indentation with spaces that is not a multiple of 4
89
89
  if (validate && spaces > 0) {
90
- throw new ParseException_1.ParseException(numLine, "INVALID_NUMBER_SPACES", `There are ${spaces} spaces before node`);
90
+ throw new ParseException_1.ParseException(numLine, "INDENTATION_SPACES_NOT_VALID", `There are ${spaces} spaces before node`);
91
91
  }
92
92
  // Validate the level (spec 11.3). Comments included (spec 9): lastLevel is the level of the
93
93
  // last NODE, a comment never becomes the reference.
@@ -18,5 +18,12 @@ export declare class NamespaceValidator {
18
18
  * @param lineNumber line number, for the error message.
19
19
  * @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
20
20
  */
21
+ /**
22
+ * Tells whether a namespace matches the format, without throwing.
23
+ *
24
+ * @param namespace already normalized namespace to check.
25
+ * @returns true if it matches the format; false when it is null, empty or malformed.
26
+ */
27
+ static isValid(namespace: string | null | undefined): boolean;
21
28
  static validateNamespaceFormat(namespace: string | null | undefined, lineNumber: number): void;
22
29
  }
@@ -11,6 +11,15 @@ class NamespaceValidator {
11
11
  * @param lineNumber line number, for the error message.
12
12
  * @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
13
13
  */
14
+ /**
15
+ * Tells whether a namespace matches the format, without throwing.
16
+ *
17
+ * @param namespace already normalized namespace to check.
18
+ * @returns true if it matches the format; false when it is null, empty or malformed.
19
+ */
20
+ static isValid(namespace) {
21
+ return !!namespace && NamespaceValidator.NAMESPACE_FORMAT.test(namespace);
22
+ }
14
23
  static validateNamespaceFormat(namespace, lineNumber) {
15
24
  if (!namespace) {
16
25
  return;
@@ -49,7 +49,7 @@ function createNode(lineIndent, lineNumber) {
49
49
  value = line.substring(nodeIndex + Constants_1.Constants.SEP_NODE.length);
50
50
  }
51
51
  if (textNode && StringUtils_1.StringUtils.trim(value).length > 0) {
52
- throw new ParseException_1.ParseException(lineNumber, "INLINE_VALUE_NOT_VALID", `Line not valid: ${line}`);
52
+ throw new ParseException_1.ParseException(lineNumber, "BLOCK_VALUE_NOT_ALLOWED", `Line not valid: ${line}`);
53
53
  }
54
54
  // The namespace the line declares, if any (empty when it inherits)
55
55
  const nameNamespace = NameNamespaceParser_1.NameNamespaceParser.parse(name, null, lineNumber, line);
@@ -41,6 +41,11 @@ export declare class Parser {
41
41
  */
42
42
  parseResult(content: string): ParseResult;
43
43
  private processLine;
44
+ /**
45
+ * Records an error raised while parsing or validating a line. Typed exceptions travel as they
46
+ * are; anything else is wrapped under the code `UNEXPECTED_ERROR` (as a ValidationException
47
+ * when it was raised by a validator, so that the subtype still tells the phase apart).
48
+ */
44
49
  private handleError;
45
50
  private closeToLevel;
46
51
  private removeUTF8BOM;
@@ -6,6 +6,7 @@ const LineParser_1 = require("./LineParser");
6
6
  const NodeCreator_1 = require("./NodeCreator");
7
7
  const ParseResult_1 = require("./ParseResult");
8
8
  const ParseException_1 = require("../exceptions/ParseException");
9
+ const ValidationException_1 = require("../exceptions/ValidationException");
9
10
  /**
10
11
  * Line-by-line STXT parsing engine. It knows nothing about schemas: semantic validation is
11
12
  * plugged in through {@link Parser.registerValidator} and {@link Parser.registerObserver}.
@@ -145,17 +146,20 @@ class Parser {
145
146
  this.handleError(e, lineNumber, result);
146
147
  }
147
148
  }
148
- handleError(e, line, result, errorCode = "UNEXPECTED_ERROR", unknownErrorCode = "UNKNOWN_ERROR") {
149
+ /**
150
+ * Records an error raised while parsing or validating a line. Typed exceptions travel as they
151
+ * are; anything else is wrapped under the code `UNEXPECTED_ERROR` (as a ValidationException
152
+ * when it was raised by a validator, so that the subtype still tells the phase apart).
153
+ */
154
+ handleError(e, line, result, validating = false) {
149
155
  if (e instanceof ParseException_1.ParseException) {
150
156
  result.addError(e);
151
157
  }
152
- else if (e instanceof Error) {
153
- // Turn generic errors into a ParseException
154
- result.addError(new ParseException_1.ParseException(line, errorCode, e.message));
155
- }
156
158
  else {
157
- // Unknown error
158
- result.addError(new ParseException_1.ParseException(line, unknownErrorCode, String(e)));
159
+ const message = e instanceof Error ? e.message : String(e);
160
+ result.addError(validating
161
+ ? new ValidationException_1.ValidationException(line, "UNEXPECTED_ERROR", message)
162
+ : new ParseException_1.ParseException(line, "UNEXPECTED_ERROR", message));
159
163
  }
160
164
  }
161
165
  closeToLevel(stack, targetLevel, result) {
@@ -170,7 +174,7 @@ class Parser {
170
174
  });
171
175
  }
172
176
  catch (e) {
173
- this.handleError(e, completed.getLine(), result, "VALIDATION_ERROR", "UNKNOWN_VALIDATION_ERROR");
177
+ this.handleError(e, completed.getLine(), result, true);
174
178
  }
175
179
  });
176
180
  // Hand it over to the observers
@@ -15,6 +15,12 @@ export declare class ParseException extends Error {
15
15
  * @param message descriptive message.
16
16
  */
17
17
  constructor(line: number, code: string, message: string);
18
- /** @returns a readable representation of the error, with its line and its code. */
18
+ /**
19
+ * The string form carries the frame that `message` deliberately leaves out: `message` is only
20
+ * the description, and the code and the line are separate fields that whoever formats output
21
+ * composes. Same framing in every port since 0.10.0.
22
+ *
23
+ * @returns `[CODE] line N: message`.
24
+ */
19
25
  toString(): string;
20
26
  }
@@ -20,9 +20,15 @@ class ParseException extends Error {
20
20
  this.code = code;
21
21
  Object.setPrototypeOf(this, ParseException.prototype);
22
22
  }
23
- /** @returns a readable representation of the error, with its line and its code. */
23
+ /**
24
+ * The string form carries the frame that `message` deliberately leaves out: `message` is only
25
+ * the description, and the code and the line are separate fields that whoever formats output
26
+ * composes. Same framing in every port since 0.10.0.
27
+ *
28
+ * @returns `[CODE] line N: message`.
29
+ */
24
30
  toString() {
25
- return `${this.name} [line=${this.line}, code=${this.code}]: ${this.message}`;
31
+ return `[${this.code}] line ${this.line}: ${this.message}`;
26
32
  }
27
33
  }
28
34
  exports.ParseException = ParseException;
@@ -15,6 +15,11 @@ export declare class RuntimeException extends Error {
15
15
  constructor(code: string, message: string);
16
16
  /** @returns the error code in UPPERCASE. */
17
17
  getCode(): string;
18
- /** @returns a readable representation of the error, with its code. */
18
+ /**
19
+ * The string form carries the frame that `message` deliberately leaves out; same framing in
20
+ * every port since 0.10.0.
21
+ *
22
+ * @returns `[CODE] message`.
23
+ */
19
24
  toString(): string;
20
25
  }
@@ -23,10 +23,14 @@ class RuntimeException extends Error {
23
23
  getCode() {
24
24
  return this.code;
25
25
  }
26
- /** @returns a readable representation of the error, with its code. */
26
+ /**
27
+ * The string form carries the frame that `message` deliberately leaves out; same framing in
28
+ * every port since 0.10.0.
29
+ *
30
+ * @returns `[CODE] message`.
31
+ */
27
32
  toString() {
28
- const message = this.message;
29
- return `${this.name}[${this.code}]${message ? `: ${message}` : ""}`;
33
+ return `[${this.code}] ${this.message}`;
30
34
  }
31
35
  }
32
36
  exports.RuntimeException = RuntimeException;
@@ -45,7 +45,7 @@ export declare class NodeDefinition {
45
45
  * Adds the definition of an expected child.
46
46
  *
47
47
  * @param childDefinition definition of the child to add.
48
- * @throws ValidationException with code `CHILD_DEF_ALREADY_DEFINED` if a definition for that child already existed.
48
+ * @throws ValidationException with code `CHILD_DUPLICATED` if a definition for that child already existed.
49
49
  */
50
50
  addChildDefinition(childDefinition: ChildDefinition): void;
51
51
  /**
@@ -67,12 +67,12 @@ class NodeDefinition {
67
67
  * Adds the definition of an expected child.
68
68
  *
69
69
  * @param childDefinition definition of the child to add.
70
- * @throws ValidationException with code `CHILD_DEF_ALREADY_DEFINED` if a definition for that child already existed.
70
+ * @throws ValidationException with code `CHILD_DUPLICATED` if a definition for that child already existed.
71
71
  */
72
72
  addChildDefinition(childDefinition) {
73
73
  const qname = childDefinition.getQualifiedName();
74
74
  if (this.children.has(qname)) {
75
- throw new ValidationException_1.ValidationException(0, "CHILD_DEF_ALREADY_DEFINED", `Exists a previous node definition with: ${qname}`);
75
+ throw new ValidationException_1.ValidationException(0, "CHILD_DUPLICATED", `Exists a previous node definition with: ${qname}`);
76
76
  }
77
77
  this.children.set(qname, childDefinition);
78
78
  }
@@ -28,7 +28,7 @@ export declare class Schema {
28
28
  * Adds the definition of a node to this schema.
29
29
  *
30
30
  * @param nodeDefinition node definition to add.
31
- * @throws ValidationException with code `NODE_DEF_ALREADY_DEFINED` if there already was a node definition with the same name.
31
+ * @throws ValidationException with code `NODE_DUPLICATED` if there already was a node definition with the same name.
32
32
  */
33
33
  addNodeDefinition(nodeDefinition: NodeDefinition): void;
34
34
  /** @returns the namespace this schema applies to. */
@@ -37,12 +37,12 @@ class Schema {
37
37
  * Adds the definition of a node to this schema.
38
38
  *
39
39
  * @param nodeDefinition node definition to add.
40
- * @throws ValidationException with code `NODE_DEF_ALREADY_DEFINED` if there already was a node definition with the same name.
40
+ * @throws ValidationException with code `NODE_DUPLICATED` if there already was a node definition with the same name.
41
41
  */
42
42
  addNodeDefinition(nodeDefinition) {
43
43
  const qname = nodeDefinition.getCanonicalName();
44
44
  if (this.nodes.has(qname)) {
45
- throw new ValidationException_1.ValidationException(0, "NODE_DEF_ALREADY_DEFINED", `Exists a previous node definition with: ${qname}`);
45
+ throw new ValidationException_1.ValidationException(0, "NODE_DUPLICATED", `Exists a previous node definition with: ${qname}`);
46
46
  }
47
47
  this.nodes.set(qname, nodeDefinition);
48
48
  }
@@ -5,6 +5,7 @@ import { Node } from "../core/Node";
5
5
  *
6
6
  * @param node root node of the schema document, `Schema (@stxt.schema): ...`.
7
7
  * @returns the schema the document defines.
8
- * @throws ValidationException if the document is not a valid `@stxt.schema` one.
8
+ * @throws ValidationException with `SCHEMA_ROOT_NOT_VALID`, `SCHEMA_NAMESPACE_EMPTY`, `SCHEMA_NODE_NOT_INLINE`
9
+ * or the code of the first rule of the schema language the document breaks, if the document is not a valid `@stxt.schema` one.
9
10
  */
10
11
  export declare function transformNodeToSchema(node: Node): Schema;
@@ -6,7 +6,8 @@ const NodeDefinition_1 = require("./NodeDefinition");
6
6
  const ChildDefinition_1 = require("./ChildDefinition");
7
7
  const InlineNode_1 = require("../core/InlineNode");
8
8
  const ValidationException_1 = require("../exceptions/ValidationException");
9
- const RuntimeException_1 = require("../exceptions/RuntimeException");
9
+ const NamespaceValidator_1 = require("../core/NamespaceValidator");
10
+ const StringUtils_1 = require("../core/StringUtils");
10
11
  const NameNamespaceParser_1 = require("../core/NameNamespaceParser");
11
12
  const TypeRegistry_1 = require("./TypeRegistry");
12
13
  /**
@@ -14,7 +15,8 @@ const TypeRegistry_1 = require("./TypeRegistry");
14
15
  *
15
16
  * @param node root node of the schema document, `Schema (@stxt.schema): ...`.
16
17
  * @returns the schema the document defines.
17
- * @throws ValidationException if the document is not a valid `@stxt.schema` one.
18
+ * @throws ValidationException with `SCHEMA_ROOT_NOT_VALID`, `SCHEMA_NAMESPACE_EMPTY`, `SCHEMA_NODE_NOT_INLINE`
19
+ * or the code of the first rule of the schema language the document breaks, if the document is not a valid `@stxt.schema` one.
18
20
  */
19
21
  function transformNodeToSchema(node) {
20
22
  // Node name
@@ -22,12 +24,20 @@ function transformNodeToSchema(node) {
22
24
  const namespaceSchema = node.getNamespace();
23
25
  // Get the name and the namespace
24
26
  if (nodeName !== "schema" || namespaceSchema !== Schema_1.Schema.SCHEMA_NAMESPACE) {
25
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_STXT_SCHEMA", `Expected schema(${Schema_1.Schema.SCHEMA_NAMESPACE}) but got ${nodeName}(${namespaceSchema})`);
27
+ throw new ValidationException_1.ValidationException(node.getLine(), "SCHEMA_ROOT_NOT_VALID", `Expected schema(${Schema_1.Schema.SCHEMA_NAMESPACE}) but got ${nodeName}(${namespaceSchema})`);
26
28
  }
27
29
  const root = inline(node);
30
+ // The target namespace: required, and with a valid format
31
+ const targetNamespace = StringUtils_1.StringUtils.lowerCase(root.getValue());
32
+ if (!targetNamespace || targetNamespace.trim().length === 0) {
33
+ throw new ValidationException_1.ValidationException(root.getLine(), "SCHEMA_NAMESPACE_EMPTY", "Schema namespace is empty");
34
+ }
35
+ if (!NamespaceValidator_1.NamespaceValidator.isValid(targetNamespace)) {
36
+ throw new ValidationException_1.ValidationException(root.getLine(), "SCHEMA_ROOT_NOT_VALID", `Schema namespace not valid: ${targetNamespace}`);
37
+ }
28
38
  // Get the description
29
39
  const descrip = root.getChild("description")?.getText();
30
- const schema = new Schema_1.Schema(root.getValue(), root.getLine(), descrip);
40
+ const schema = new Schema_1.Schema(targetNamespace, root.getLine(), descrip);
31
41
  // Used to check that every child is defined
32
42
  const allNames = new Set();
33
43
  // Get the nodes
@@ -55,7 +65,7 @@ function inline(node) {
55
65
  if (node instanceof InlineNode_1.InlineNode) {
56
66
  return node;
57
67
  }
58
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_SCHEMA", `Node '${node.getName()}' must be inline in a schema`);
68
+ throw new ValidationException_1.ValidationException(node.getLine(), "SCHEMA_NODE_NOT_INLINE", `Node '${node.getName()}' must be inline in a schema`);
59
69
  }
60
70
  /** Builds the definition of a node from a `Node:` entry of the schema document. */
61
71
  function createFrom(node, namespace) {
@@ -82,14 +92,19 @@ function createFrom(node, namespace) {
82
92
  let valuesNodes = n.getChildrenByName("values");
83
93
  if (valuesNodes && valuesNodes.length > 0) {
84
94
  if (type !== "ENUM") {
85
- throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_ONLY_SUPPORTED_BY_ENUM", `Values only supported for type ENUM, not for type ${type}`);
95
+ throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
86
96
  }
87
97
  if (valuesNodes.length > 1) {
88
- throw new RuntimeException_1.RuntimeException("INVALID_SIZE_VALUES", `Unexpected number of values: ${valuesNodes.length}`);
98
+ throw new ValidationException_1.ValidationException(valuesNodes[1].getLine(), "VALUES_DUPLICATED", `Node '${n.getValue()}' defines 'Values' ${valuesNodes.length} times`);
89
99
  }
90
100
  const valuesNode = valuesNodes[0];
91
101
  const values = inline(valuesNode).getChildrenByName("value");
92
102
  for (const v of values) {
103
+ // An empty Value: is a schema error (STXT-SCHEMA-SPEC 7.2, condition 14 of section 13):
104
+ // an enumeration whose only valid value is the empty string makes no sense
105
+ if (v.getText().length === 0) {
106
+ throw new ValidationException_1.ValidationException(v.getLine(), "VALUE_EMPTY", "Value of ENUM cannot be empty");
107
+ }
93
108
  result.addValue(v.getText(), v.getLine());
94
109
  }
95
110
  // For the final ENUM check
@@ -97,7 +112,7 @@ function createFrom(node, namespace) {
97
112
  }
98
113
  // Look at the enum
99
114
  if (type === "ENUM" && (!valuesNodes || valuesNodes.length === 0)) {
100
- throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_EMPTY_FOR_ENUM", "ENUM Type must include values");
115
+ throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
101
116
  }
102
117
  return result;
103
118
  }
@@ -126,7 +141,7 @@ function getInteger(node, name) {
126
141
  const raw = n.getText();
127
142
  const parsed = Number.parseInt(raw, 10);
128
143
  if (Number.isNaN(parsed)) {
129
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_INTEGER", `Integer not valid: ${raw}`);
144
+ throw new ValidationException_1.ValidationException(node.getLine(), "CARDINALITY_NOT_VALID", `Integer not valid: ${raw}`);
130
145
  }
131
146
  return parsed;
132
147
  }
@@ -28,7 +28,8 @@ export declare class SchemaProviderMemory implements SchemaProvider {
28
28
  * own namespace.
29
29
  *
30
30
  * @param txt text of the `@stxt.schema` document.
31
- * @throws ParseException or ValidationException if the document is not a valid schema.
31
+ * @throws ParseException or ValidationException if the document is not a valid schema; in
32
+ * particular `SCHEMA_MULTIPLE_ROOTS` if it does not hold exactly one root node.
32
33
  */
33
34
  addSchema(txt: string): void;
34
35
  /** Removes every schema registered in this provider (the parent one is left untouched). */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SchemaProviderMemory = void 0;
4
4
  const Parser_1 = require("../core/Parser");
5
5
  const StringUtils_1 = require("../core/StringUtils");
6
+ const ValidationException_1 = require("../exceptions/ValidationException");
6
7
  const SchemaParser_1 = require("./SchemaParser");
7
8
  const SchemaProviderMeta_1 = require("./SchemaProviderMeta");
8
9
  const SchemaValidator_1 = require("./SchemaValidator");
@@ -47,11 +48,16 @@ class SchemaProviderMemory {
47
48
  * own namespace.
48
49
  *
49
50
  * @param txt text of the `@stxt.schema` document.
50
- * @throws ParseException or ValidationException if the document is not a valid schema.
51
+ * @throws ParseException or ValidationException if the document is not a valid schema; in
52
+ * particular `SCHEMA_MULTIPLE_ROOTS` if it does not hold exactly one root node.
51
53
  */
52
54
  addSchema(txt) {
53
55
  const parser = new Parser_1.Parser();
54
- const node = parser.parse(txt)[0];
56
+ const nodes = parser.parse(txt);
57
+ if (nodes.length !== 1) {
58
+ throw new ValidationException_1.ValidationException(0, "SCHEMA_MULTIPLE_ROOTS", `A schema document must hold exactly 1 root node, got ${nodes.length}`);
59
+ }
60
+ const node = nodes[0];
55
61
  // A schema that does not validate against its meta-schema must not be
56
62
  // registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
57
63
  const schemaValidator = new SchemaValidator_1.SchemaValidator(new SchemaProviderMeta_1.SchemaProviderMeta(), true);
@@ -65,7 +65,7 @@ class SchemaValidator {
65
65
  const schemaNode = schema.getNodeDefinition(node.getCanonicalName());
66
66
  if (!schemaNode) {
67
67
  const error = `NOT EXIST NODE ${node.getCanonicalName()} for namespace ${schema.getNamespace()}`;
68
- errors.push(new ValidationException_1.ValidationException(node.getLine(), "NODE_NOT_EXIST_IN_SCHEMA", error));
68
+ errors.push(new ValidationException_1.ValidationException(node.getLine(), "NODE_NOT_DEFINED_IN_SCHEMA", error));
69
69
  return errors;
70
70
  }
71
71
  errors.push(...SchemaValidator.validateValue(schemaNode, node));
@@ -93,7 +93,7 @@ class SchemaValidator {
93
93
  const nodeType = nodeDef.getType();
94
94
  const validator = TypeRegistry_1.TypeRegistry.get(nodeType);
95
95
  if (!validator) {
96
- errors.push(new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_SUPPORTED", `Node type not supported: ${nodeType}`));
96
+ errors.push(new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_VALID", `Node type not supported: ${nodeType}`));
97
97
  return errors;
98
98
  }
99
99
  try {
@@ -104,10 +104,10 @@ class SchemaValidator {
104
104
  errors.push(e);
105
105
  }
106
106
  else if (e instanceof Error) {
107
- errors.push(new ValidationException_1.ValidationException(node.getLine(), "VALIDATION_ERROR", e.message));
107
+ errors.push(new ValidationException_1.ValidationException(node.getLine(), "UNEXPECTED_ERROR", e.message));
108
108
  }
109
109
  else {
110
- errors.push(new ValidationException_1.ValidationException(node.getLine(), "UNKNOWN_VALIDATION_ERROR", String(e)));
110
+ errors.push(new ValidationException_1.ValidationException(node.getLine(), "UNEXPECTED_ERROR", String(e)));
111
111
  }
112
112
  }
113
113
  return errors;
@@ -135,14 +135,14 @@ class SchemaValidator {
135
135
  const min = childDef.getMin(); // number | null
136
136
  const max = childDef.getMax(); // number | null
137
137
  if (min !== null && childCount < min) {
138
- errors.push(new ValidationException_1.ValidationException(node.getLine(), "INVALID_NUMBER", `${childCount} nodes of '${childDef.getQualifiedName()}' and min is ${min}`));
138
+ errors.push(new ValidationException_1.ValidationException(node.getLine(), "TOO_FEW_CHILDREN", `${childCount} nodes of '${childDef.getQualifiedName()}' and min is ${min}`));
139
139
  }
140
140
  if (max !== null && childCount > max) {
141
141
  // Error on the parent
142
- errors.push(new ValidationException_1.ValidationException(node.getLine(), "INVALID_NUMBER", `${childCount} nodes of '${childDef.getQualifiedName()}' and max is ${max}`));
142
+ errors.push(new ValidationException_1.ValidationException(node.getLine(), "TOO_MANY_CHILDREN", `${childCount} nodes of '${childDef.getQualifiedName()}' and max is ${max}`));
143
143
  // Error on each child node beyond the allowed maximum
144
144
  for (const child of children) {
145
- errors.push(new ValidationException_1.ValidationException(child.getLine(), "INVALID_NUMBER", `Too many '${childDef.getQualifiedName()}' nodes: found ${childCount}, max is ${max}`));
145
+ errors.push(new ValidationException_1.ValidationException(child.getLine(), "TOO_MANY_CHILDREN", `Too many '${childDef.getQualifiedName()}' nodes: found ${childCount}, max is ${max}`));
146
146
  }
147
147
  }
148
148
  return errors;
@@ -47,7 +47,7 @@ class TypeRegistry {
47
47
  static register(instance) {
48
48
  const name = instance.getName();
49
49
  if (this.REGISTRY.has(name)) {
50
- throw new RuntimeException_1.RuntimeException("DUPLICATED_TYPE", `Type already defined: ${name}`);
50
+ throw new RuntimeException_1.RuntimeException("TYPE_DUPLICATED", `Type already defined: ${name}`);
51
51
  }
52
52
  this.REGISTRY.set(name, instance);
53
53
  }
@@ -1,3 +1,12 @@
1
1
  import { Type } from "../Type";
2
+ /**
3
+ * STXT-SCHEMA-SPEC 9.5: standard Base64 (not URL-safe), padding optional, no leftover bits,
4
+ * never empty. The check is a regular expression plus the leftover-bits rule, never the
5
+ * platform decoder, which silently ignores characters outside the alphabet.
6
+ *
7
+ * @param value value already stripped of blanks.
8
+ * @returns whether it is valid Base64.
9
+ */
10
+ export declare function isValidBase64(value: string): boolean;
2
11
  /** `BASE64` type: checks that the content is valid Base64. */
3
12
  export declare const BASE64: Type;
@@ -1,29 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BASE64 = void 0;
4
+ exports.isValidBase64 = isValidBase64;
4
5
  const ValidationException_1 = require("../../exceptions/ValidationException");
5
6
  const binaryValue_1 = require("./binaryValue");
7
+ /** Standard alphabet of RFC 4648 section 4, used to check the leftover bits of the last character. */
8
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
+ /**
10
+ * Shape of a Base64 value (STXT-SCHEMA-SPEC 9.5): groups of four characters of the standard
11
+ * alphabet, with an optional final group of two or three characters whose `=` padding may be
12
+ * omitted. The empty string matches here and is rejected separately.
13
+ */
14
+ const SHAPE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}(?:==)?|[A-Za-z0-9+/]{3}=?)?$/;
15
+ /**
16
+ * STXT-SCHEMA-SPEC 9.5: standard Base64 (not URL-safe), padding optional, no leftover bits,
17
+ * never empty. The check is a regular expression plus the leftover-bits rule, never the
18
+ * platform decoder, which silently ignores characters outside the alphabet.
19
+ *
20
+ * @param value value already stripped of blanks.
21
+ * @returns whether it is valid Base64.
22
+ */
23
+ function isValidBase64(value) {
24
+ if (value.length === 0 || !SHAPE.test(value)) {
25
+ return false;
26
+ }
27
+ const data = value.replace(/=+$/, "");
28
+ const rest = data.length % 4;
29
+ if (rest === 0) {
30
+ return true;
31
+ }
32
+ // The last character encodes 6 bits; with 2 characters 4 of them are leftover, with 3, 2 of them.
33
+ const last = ALPHABET.indexOf(data.charAt(data.length - 1));
34
+ const mask = rest === 2 ? 0x0f : 0x03;
35
+ return (last & mask) === 0;
36
+ }
6
37
  /** `BASE64` type: checks that the content is valid Base64. */
7
38
  exports.BASE64 = {
8
39
  getName() {
9
40
  return "BASE64";
10
41
  },
11
42
  validate(ndef, n) {
12
- const raw = (0, binaryValue_1.binaryValue)(n);
13
- try {
14
- // Try to decode it
15
- const buf = Buffer.from(raw, "base64");
16
- // Re-encode to check consistency
17
- // (this keeps partially valid strings out)
18
- const reencoded = buf.toString("base64");
19
- // Normalize the padding before comparing
20
- const normalizedInput = raw.replace(/=+$/, "");
21
- const normalizedReencoded = reencoded.replace(/=+$/, "");
22
- if (normalizedInput !== normalizedReencoded) {
23
- throw new ValidationException_1.ValidationException(n.getLine(), "INVALID_VALUE", `Node '${n.getName()}' Invalid Base64`);
24
- }
25
- }
26
- catch {
43
+ if (!isValidBase64((0, binaryValue_1.binaryValue)(n))) {
27
44
  throw new ValidationException_1.ValidationException(n.getLine(), "INVALID_VALUE", `Node '${n.getName()}' Invalid Base64`);
28
45
  }
29
46
  },
@@ -1,2 +1,2 @@
1
- /** `DATE` type: checks the `YYYY-MM-DD` format. */
1
+ /** `DATE` type: `YYYY-MM-DD`, an existing date of the proleptic Gregorian calendar (STXT-SCHEMA-SPEC 9.4). */
2
2
  export declare const DATE: import("../Type").Type;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DATE = void 0;
4
- const regexType_1 = require("./regexType");
5
- /** `DATE` type: checks the `YYYY-MM-DD` format. */
6
- exports.DATE = (0, regexType_1.regexType)("DATE", /^\d{4}-\d{2}-\d{2}$/, "Invalid date");
4
+ const rangeType_1 = require("./rangeType");
5
+ const dateTime_1 = require("./dateTime");
6
+ /** `DATE` type: `YYYY-MM-DD`, an existing date of the proleptic Gregorian calendar (STXT-SCHEMA-SPEC 9.4). */
7
+ exports.DATE = (0, rangeType_1.rangeType)("DATE", /^(\d{4})-(\d{2})-(\d{2})$/, m => (0, dateTime_1.isValidDate)(Number(m[1]), Number(m[2]), Number(m[3])), "Invalid date");
7
8
  //# sourceMappingURL=DATE.js.map