@stxt-lang/core 0.9.0 → 0.9.1

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 (42) hide show
  1. package/out/core/LineParser.d.ts +1 -1
  2. package/out/core/LineParser.js +4 -4
  3. package/out/core/NamespaceValidator.d.ts +7 -0
  4. package/out/core/NamespaceValidator.js +9 -0
  5. package/out/core/NodeCreator.js +1 -1
  6. package/out/core/Parser.d.ts +5 -0
  7. package/out/core/Parser.js +12 -8
  8. package/out/schema/NodeDefinition.d.ts +1 -1
  9. package/out/schema/NodeDefinition.js +2 -2
  10. package/out/schema/Schema.d.ts +1 -1
  11. package/out/schema/Schema.js +2 -2
  12. package/out/schema/SchemaParser.d.ts +2 -1
  13. package/out/schema/SchemaParser.js +19 -9
  14. package/out/schema/SchemaProviderMemory.d.ts +2 -1
  15. package/out/schema/SchemaProviderMemory.js +8 -2
  16. package/out/schema/SchemaValidator.js +7 -7
  17. package/out/schema/TypeRegistry.js +1 -1
  18. package/out/schema/type/DATE.d.ts +1 -1
  19. package/out/schema/type/DATE.js +4 -3
  20. package/out/schema/type/ENUM.js +1 -1
  21. package/out/schema/type/GROUP.js +1 -1
  22. package/out/schema/type/INLINE.js +1 -1
  23. package/out/schema/type/MARKDOWN.js +1 -1
  24. package/out/schema/type/TEXT.js +1 -1
  25. package/out/schema/type/TIME.d.ts +1 -1
  26. package/out/schema/type/TIME.js +4 -3
  27. package/out/schema/type/TIMESTAMP.d.ts +4 -1
  28. package/out/schema/type/TIMESTAMP.js +9 -3
  29. package/out/schema/type/URL.d.ts +10 -3
  30. package/out/schema/type/URL.js +11 -24
  31. package/out/schema/type/dateTime.d.ts +9 -0
  32. package/out/schema/type/dateTime.js +23 -0
  33. package/out/schema/type/rangeType.d.ts +11 -0
  34. package/out/schema/type/rangeType.js +30 -0
  35. package/out/schema/type/regexType.js +1 -1
  36. package/out/template/ChildLineParser.d.ts +1 -1
  37. package/out/template/ChildLineParser.js +4 -4
  38. package/out/template/TemplateParser.d.ts +3 -2
  39. package/out/template/TemplateParser.js +30 -14
  40. package/out/template/TemplateSchemaProviderMemory.d.ts +4 -3
  41. package/out/template/TemplateSchemaProviderMemory.js +5 -8
  42. package/package.json +1 -1
@@ -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
@@ -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,10 +92,10 @@ 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");
@@ -97,7 +107,7 @@ function createFrom(node, namespace) {
97
107
  }
98
108
  // Look at the enum
99
109
  if (type === "ENUM" && (!valuesNodes || valuesNodes.length === 0)) {
100
- throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_EMPTY_FOR_ENUM", "ENUM Type must include values");
110
+ throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
101
111
  }
102
112
  return result;
103
113
  }
@@ -126,7 +136,7 @@ function getInteger(node, name) {
126
136
  const raw = n.getText();
127
137
  const parsed = Number.parseInt(raw, 10);
128
138
  if (Number.isNaN(parsed)) {
129
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_INTEGER", `Integer not valid: ${raw}`);
139
+ throw new ValidationException_1.ValidationException(node.getLine(), "CARDINALITY_NOT_VALID", `Integer not valid: ${raw}`);
130
140
  }
131
141
  return parsed;
132
142
  }
@@ -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,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
@@ -10,7 +10,7 @@ exports.ENUM = {
10
10
  validate(nodeDef, node) {
11
11
  // INLINE value form (STXT-SCHEMA-SPEC 9.3): the block '>>' form is not allowed
12
12
  if (node.isTextNode()) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${node.getQualifiedName()}`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
14
14
  }
15
15
  const value = node.getText();
16
16
  const allowed = nodeDef.getValues(); // ReadonlySet<string>
@@ -10,7 +10,7 @@ exports.GROUP = {
10
10
  validate(nodeDef, node) {
11
11
  // NONE value form (STXT-SCHEMA-SPEC 9.2): neither an inline value nor a '>>' block
12
12
  if (node.isTextNode() || node.getText().length > 0) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_VALUE", `Node '${node.getName()}' has to be empty`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "VALUE_NOT_ALLOWED", `Node '${node.getName()}' has to be empty`);
14
14
  }
15
15
  },
16
16
  };
@@ -10,7 +10,7 @@ exports.INLINE = {
10
10
  validate(nodeDef, node) {
11
11
  // INLINE value form (STXT-SCHEMA-SPEC 9.2): the block '>>' form is not allowed
12
12
  if (node.isTextNode()) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${node.getQualifiedName()}`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
14
14
  }
15
15
  },
16
16
  };
@@ -13,7 +13,7 @@ exports.MARKDOWN = {
13
13
  },
14
14
  validate(nodeDef, node) {
15
15
  if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
16
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_CHILDREN_TEXT", `Not allowed children nodes in node ${node.getQualifiedName()}`);
16
+ throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED", `Not allowed children nodes in node ${node.getQualifiedName()}`);
17
17
  }
18
18
  },
19
19
  };
@@ -10,7 +10,7 @@ exports.TEXT = {
10
10
  },
11
11
  validate(nodeDef, node) {
12
12
  if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_CHILDREN_TEXT", `Not allowed children nodes in node ${node.getQualifiedName()}`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED", `Not allowed children nodes in node ${node.getQualifiedName()}`);
14
14
  }
15
15
  },
16
16
  };
@@ -1,2 +1,2 @@
1
- /** `TIME` type: checks the `HH:MM:SS` format. */
1
+ /** `TIME` type: `hh:mm:ss` in range (00-23, 00-59, 00-59); no fraction, no zone (STXT-SCHEMA-SPEC 9.4). */
2
2
  export declare const TIME: import("../Type").Type;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIME = void 0;
4
- const regexType_1 = require("./regexType");
5
- /** `TIME` type: checks the `HH:MM:SS` format. */
6
- exports.TIME = (0, regexType_1.regexType)("TIME", /^\d{2}:\d{2}:\d{2}$/, "Invalid time");
4
+ const rangeType_1 = require("./rangeType");
5
+ const dateTime_1 = require("./dateTime");
6
+ /** `TIME` type: `hh:mm:ss` in range (00-23, 00-59, 00-59); no fraction, no zone (STXT-SCHEMA-SPEC 9.4). */
7
+ exports.TIME = (0, rangeType_1.rangeType)("TIME", /^(\d{2}):(\d{2}):(\d{2})$/, m => (0, dateTime_1.isValidTime)(Number(m[1]), Number(m[2]), Number(m[3])), "Invalid time");
7
8
  //# sourceMappingURL=TIME.js.map
@@ -1,2 +1,5 @@
1
- /** `TIMESTAMP` type: checks an ISO-8601 timestamp. */
1
+ /**
2
+ * `TIMESTAMP` type: `DATE "T" hh:mm [":" ss ["." digits]] ["Z" | sign hh:mm]` (STXT-SCHEMA-SPEC
3
+ * 9.4). Date, time and offset in range; seconds, fraction (one or more digits) and zone optional.
4
+ */
2
5
  export declare const TIMESTAMP: import("../Type").Type;
@@ -1,7 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIMESTAMP = void 0;
4
- const regexType_1 = require("./regexType");
5
- /** `TIMESTAMP` type: checks an ISO-8601 timestamp. */
6
- exports.TIMESTAMP = (0, regexType_1.regexType)("TIMESTAMP", /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[+-]\d{2}:\d{2})?$/, "Invalid timestamp");
4
+ const rangeType_1 = require("./rangeType");
5
+ const dateTime_1 = require("./dateTime");
6
+ /**
7
+ * `TIMESTAMP` type: `DATE "T" hh:mm [":" ss ["." digits]] ["Z" | sign hh:mm]` (STXT-SCHEMA-SPEC
8
+ * 9.4). Date, time and offset in range; seconds, fraction (one or more digits) and zone optional.
9
+ */
10
+ exports.TIMESTAMP = (0, rangeType_1.rangeType)("TIMESTAMP", /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))?$/, m => (0, dateTime_1.isValidDate)(Number(m[1]), Number(m[2]), Number(m[3]))
11
+ && (0, dateTime_1.isValidTime)(Number(m[4]), Number(m[5]), m[6] === undefined ? 0 : Number(m[6]))
12
+ && (m[7] === undefined || (0, dateTime_1.isValidTime)(Number(m[7]), Number(m[8]), 0)), "Invalid timestamp");
7
13
  //# sourceMappingURL=TIMESTAMP.js.map
@@ -1,3 +1,10 @@
1
- import { Type } from "../Type";
2
- /** `URL` type: checks that the value is a syntactically valid URI/URL. */
3
- export declare const URL: Type;
1
+ /**
2
+ * `URL` type: an absolute URL with a mandatory scheme and host, following the grammar of
3
+ * STXT-SCHEMA-SPEC 9.4 — `scheme "://" [userinfo "@"] host [":" port] ["/" path] ["?" query]
4
+ * ["#" fragment]` — and not the platform URL parser, so every port accepts exactly the same
5
+ * values. Any scheme of the form letter + letters/digits/`+`/`-`/`.` is accepted; the host is
6
+ * non-empty (no TLD required, IPv6 in brackets, non-ASCII kept as it is); a value without a
7
+ * scheme, a scheme without `//` and host (`mailto:`, `urn:`, `file:///`), inner blanks or a
8
+ * non-numeric port are rejected. Nothing is resolved or normalised.
9
+ */
10
+ export declare const URL: import("../Type").Type;
@@ -1,28 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.URL = void 0;
4
- const ValidationException_1 = require("../../exceptions/ValidationException");
5
- /** `URL` type: checks that the value is a syntactically valid URI/URL. */
6
- exports.URL = {
7
- getName() {
8
- return "URL";
9
- },
10
- validate(ndef, n) {
11
- // INLINE value form (STXT-SCHEMA-SPEC 9.4): the block '>>' form is not allowed
12
- if (n.isTextNode()) {
13
- throw new ValidationException_1.ValidationException(n.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${n.getQualifiedName()}`);
14
- }
15
- const url = n.getText();
16
- try {
17
- const parsed = new globalThis.URL(url);
18
- const ok = !!parsed.protocol && !!parsed.hostname;
19
- if (!ok) {
20
- throw new ValidationException_1.ValidationException(n.getLine(), "INVALID_URL_STRUCTURE", `Invalid URL: ${url}`);
21
- }
22
- }
23
- catch {
24
- throw new ValidationException_1.ValidationException(n.getLine(), "INVALID_VALUE", `Invalid URL: ${url}`);
25
- }
26
- },
27
- };
4
+ const regexType_1 = require("./regexType");
5
+ /**
6
+ * `URL` type: an absolute URL with a mandatory scheme and host, following the grammar of
7
+ * STXT-SCHEMA-SPEC 9.4 — `scheme "://" [userinfo "@"] host [":" port] ["/" path] ["?" query]
8
+ * ["#" fragment]` — and not the platform URL parser, so every port accepts exactly the same
9
+ * values. Any scheme of the form letter + letters/digits/`+`/`-`/`.` is accepted; the host is
10
+ * non-empty (no TLD required, IPv6 in brackets, non-ASCII kept as it is); a value without a
11
+ * scheme, a scheme without `//` and host (`mailto:`, `urn:`, `file:///`), inner blanks or a
12
+ * non-numeric port are rejected. Nothing is resolved or normalised.
13
+ */
14
+ exports.URL = (0, regexType_1.regexType)("URL", /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(?:[^ \t/?#@]+@)?(?:\[[0-9A-Fa-f:.]+\]|[^ \t/?#@:[\]]+)(?::[0-9]+)?(?:\/[^ \t?#]*)?(?:\?[^ \t#]*)?(?:#[^ \t]*)?$/, "Invalid URL");
28
15
  //# sourceMappingURL=URL.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Calendar and clock ranges shared by `DATE`, `TIME` and `TIMESTAMP` (STXT-SCHEMA-SPEC 9.4):
3
+ * the shape of a value is checked by each type's regular expression, the ranges by these
4
+ * helpers. Never `Date.parse`, which rolls `2026-02-30` into March.
5
+ */
6
+ /** True if the year-month-day exists in the proleptic Gregorian calendar (year 0000-9999). */
7
+ export declare function isValidDate(year: number, month: number, day: number): boolean;
8
+ /** True if hour 00-23, minute 00-59, second 00-59 (no leap second). */
9
+ export declare function isValidTime(hour: number, minute: number, second: number): boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * Calendar and clock ranges shared by `DATE`, `TIME` and `TIMESTAMP` (STXT-SCHEMA-SPEC 9.4):
4
+ * the shape of a value is checked by each type's regular expression, the ranges by these
5
+ * helpers. Never `Date.parse`, which rolls `2026-02-30` into March.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.isValidDate = isValidDate;
9
+ exports.isValidTime = isValidTime;
10
+ /** True if the year-month-day exists in the proleptic Gregorian calendar (year 0000-9999). */
11
+ function isValidDate(year, month, day) {
12
+ if (month < 1 || month > 12 || day < 1) {
13
+ return false;
14
+ }
15
+ const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
16
+ const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
17
+ return day <= daysInMonth[month - 1];
18
+ }
19
+ /** True if hour 00-23, minute 00-59, second 00-59 (no leap second). */
20
+ function isValidTime(hour, minute, second) {
21
+ return hour <= 23 && minute <= 59 && second <= 59;
22
+ }
23
+ //# sourceMappingURL=dateTime.js.map
@@ -0,0 +1,11 @@
1
+ import { Type } from "../Type";
2
+ /**
3
+ * A type whose value must match a regular expression and whose captured groups must then pass
4
+ * a range check (the calendar and clock types of STXT-SCHEMA-SPEC 9.4). INLINE value form only.
5
+ *
6
+ * @param name name of the type.
7
+ * @param pattern shape of the value, with the groups `inRange` reads.
8
+ * @param inRange true if the captured groups are in range.
9
+ * @param error text of the `INVALID_VALUE` message.
10
+ */
11
+ export declare function rangeType(name: string, pattern: RegExp, inRange: (m: RegExpExecArray) => boolean, error: string): Type;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rangeType = rangeType;
4
+ const ValidationException_1 = require("../../exceptions/ValidationException");
5
+ /**
6
+ * A type whose value must match a regular expression and whose captured groups must then pass
7
+ * a range check (the calendar and clock types of STXT-SCHEMA-SPEC 9.4). INLINE value form only.
8
+ *
9
+ * @param name name of the type.
10
+ * @param pattern shape of the value, with the groups `inRange` reads.
11
+ * @param inRange true if the captured groups are in range.
12
+ * @param error text of the `INVALID_VALUE` message.
13
+ */
14
+ function rangeType(name, pattern, inRange, error) {
15
+ return {
16
+ getName: () => name,
17
+ validate(nodeDef, node) {
18
+ // INLINE value form (STXT-SCHEMA-SPEC 9.4): the block '>>' form is not allowed
19
+ if (node.isTextNode()) {
20
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
21
+ }
22
+ const value = node.getText();
23
+ const m = pattern.exec(value);
24
+ if (m === null || !inRange(m)) {
25
+ throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_VALUE", `${node.getName()}: ${error} (${value})`);
26
+ }
27
+ },
28
+ };
29
+ }
30
+ //# sourceMappingURL=rangeType.js.map
@@ -16,7 +16,7 @@ function regexType(name, pattern, error) {
16
16
  validate(nodeDef, node) {
17
17
  // INLINE value form (STXT-SCHEMA-SPEC 9.3/9.4): the block '>>' form is not allowed
18
18
  if (node.isTextNode()) {
19
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${node.getQualifiedName()}`);
19
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
20
20
  }
21
21
  const value = node.getText();
22
22
  if (!pattern.test(value)) {
@@ -9,7 +9,7 @@ export declare class ChildLineParser {
9
9
  * @param rawLine inline value of the node, `(min,max) TYPE [values]`.
10
10
  * @param lineNumber line number, for the error messages.
11
11
  * @returns the line already split into type, cardinality and values.
12
- * @throws ValidationException with code `INVALID_CHILD_LINE`, `INVALID_CHILD_COUNT`,
12
+ * @throws ValidationException with code `STRUCTURE_LINE_NOT_VALID`, `CARDINALITY_NOT_VALID`,
13
13
  * `MIN_GREATER_THAN_MAX` or `VALUE_DUPLICATED` if the line is not valid.
14
14
  */
15
15
  static parse(rawLine: string, lineNumber: number): ChildLine;
@@ -12,7 +12,7 @@ class ChildLineParser {
12
12
  * @param rawLine inline value of the node, `(min,max) TYPE [values]`.
13
13
  * @param lineNumber line number, for the error messages.
14
14
  * @returns the line already split into type, cardinality and values.
15
- * @throws ValidationException with code `INVALID_CHILD_LINE`, `INVALID_CHILD_COUNT`,
15
+ * @throws ValidationException with code `STRUCTURE_LINE_NOT_VALID`, `CARDINALITY_NOT_VALID`,
16
16
  * `MIN_GREATER_THAN_MAX` or `VALUE_DUPLICATED` if the line is not valid.
17
17
  */
18
18
  static parse(rawLine, lineNumber) {
@@ -21,7 +21,7 @@ class ChildLineParser {
21
21
  }
22
22
  const m = ChildLineParser.CHILD_LINE_PATTERN.exec(rawLine);
23
23
  if (!m) {
24
- throw new ValidationException_1.ValidationException(lineNumber, "INVALID_CHILD_LINE", `Line not valid: ${rawLine}`);
24
+ throw new ValidationException_1.ValidationException(lineNumber, "STRUCTURE_LINE_NOT_VALID", `Line not valid: ${rawLine}`);
25
25
  }
26
26
  // m[1]=count, m[2]=type, m[3]=values
27
27
  let type = m[2]?.trim() ?? "";
@@ -54,7 +54,7 @@ class ChildLineParser {
54
54
  else if (count.includes(",")) {
55
55
  const parts = count.split(",");
56
56
  if (parts.length !== 2) {
57
- throw new ValidationException_1.ValidationException(lineNumber, "INVALID_CHILD_COUNT", `Invalid count ${count} in line: ${rawLine}`);
57
+ throw new ValidationException_1.ValidationException(lineNumber, "CARDINALITY_NOT_VALID", `Invalid count ${count} in line: ${rawLine}`);
58
58
  }
59
59
  const aNum = ChildLineParser.parseCount(parts[0].trim(), count, rawLine, lineNumber);
60
60
  const bNum = ChildLineParser.parseCount(parts[1].trim(), count, rawLine, lineNumber);
@@ -97,7 +97,7 @@ class ChildLineParser {
97
97
  // num, min and max must be non-negative integers, with no trailing text (STXT-TEMPLATE-SPEC 7.1)
98
98
  static parseCount(num, count, rawLine, lineNumber) {
99
99
  if (!/^\d+$/.test(num)) {
100
- throw new ValidationException_1.ValidationException(lineNumber, "INVALID_CHILD_COUNT", `Invalid count ${count} in line: ${rawLine}`);
100
+ throw new ValidationException_1.ValidationException(lineNumber, "CARDINALITY_NOT_VALID", `Invalid count ${count} in line: ${rawLine}`);
101
101
  }
102
102
  return parseInt(num, 10);
103
103
  }
@@ -5,7 +5,8 @@ import { Schema } from "../schema/Schema";
5
5
  *
6
6
  * @param node root of the already parsed `@stxt.template` document.
7
7
  * @returns the resulting {@link Schema}.
8
- * @throws ValidationException if the template is not valid, with the line already shifted to the
9
- * one of the original document.
8
+ * @throws ValidationException with `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if the root is
9
+ * not `Template (@stxt.template): ns`, or with the code of the rule the template breaks, with the
10
+ * line already shifted to the one of the original document.
10
11
  */
11
12
  export declare function transformTemplateNodeToSchema(node: Node): Schema;
@@ -11,17 +11,33 @@ const StringUtils_1 = require("../core/StringUtils");
11
11
  const ChildLineParser_1 = require("./ChildLineParser");
12
12
  const ParseException_1 = require("../exceptions/ParseException");
13
13
  const TypeRegistry_1 = require("../schema/TypeRegistry");
14
+ const NamespaceValidator_1 = require("../core/NamespaceValidator");
15
+ /** Namespace of the template language itself, `@stxt.template`. */
16
+ const TEMPLATE_NAMESPACE = "@stxt.template";
14
17
  /**
15
18
  * Turns the tree of an already parsed `@stxt.template` document into an equivalent {@link Schema}.
16
19
  *
17
20
  * @param node root of the already parsed `@stxt.template` document.
18
21
  * @returns the resulting {@link Schema}.
19
- * @throws ValidationException if the template is not valid, with the line already shifted to the
20
- * one of the original document.
22
+ * @throws ValidationException with `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if the root is
23
+ * not `Template (@stxt.template): ns`, or with the code of the rule the template breaks, with the
24
+ * line already shifted to the one of the original document.
21
25
  */
22
26
  function transformTemplateNodeToSchema(node) {
27
+ // The root must be `Template (@stxt.template): ns`
28
+ if (node.getCanonicalName() !== "template" || node.getNamespace() !== TEMPLATE_NAMESPACE) {
29
+ throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_ROOT_NOT_VALID", `Expected template(${TEMPLATE_NAMESPACE}) but got ${node.getCanonicalName()}(${node.getNamespace()})`);
30
+ }
31
+ // The target namespace: required, and with a valid format
32
+ const targetNamespace = StringUtils_1.StringUtils.lowerCase(node.getText());
33
+ if (!targetNamespace || targetNamespace.trim().length === 0) {
34
+ throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_NAMESPACE_EMPTY", "Template namespace is empty");
35
+ }
36
+ if (!NamespaceValidator_1.NamespaceValidator.isValid(targetNamespace)) {
37
+ throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_ROOT_NOT_VALID", `Template namespace not valid: ${targetNamespace}`);
38
+ }
23
39
  // Set the namespace
24
- const result = new Schema_1.Schema(node.getText(), node.getLine(), undefined);
40
+ const result = new Schema_1.Schema(targetNamespace, node.getLine(), undefined);
25
41
  // Look for the structure node (a template is an inline root; a text root has none)
26
42
  const structure = node instanceof InlineNode_1.InlineNode ? node.getChild("structure") : null;
27
43
  if (!structure) {
@@ -76,7 +92,7 @@ function addToSchema(schema, node) {
76
92
  // A Structure line must use the template grammar's ':' form. The core parser
77
93
  // also accepts BLOCK nodes here, so reject them explicitly (STXT-TEMPLATE-SPEC 6.3).
78
94
  if (!(node instanceof InlineNode_1.InlineNode)) {
79
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_CHILD_LINE", "Template Structure lines must use ':'");
95
+ throw new ValidationException_1.ValidationException(node.getLine(), "STRUCTURE_LINE_NOT_VALID", "Template Structure lines must use ':'");
80
96
  }
81
97
  // Get the qualified name
82
98
  let namespace = node.getNamespace();
@@ -91,7 +107,7 @@ function addToSchema(schema, node) {
91
107
  // and no children (STXT-TEMPLATE-SPEC 6.4, 10 and 14.15)
92
108
  const type = cl.getType();
93
109
  if (type && type.trim().length > 0) {
94
- throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_DEFINITION_NOT_ALLOWED", "Not allowed type definition in external namespaces");
110
+ throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", "Not allowed type definition in external namespaces");
95
111
  }
96
112
  const values = cl.getValues();
97
113
  if (values) {
@@ -125,7 +141,7 @@ function addToSchema(schema, node) {
125
141
  // Same code as SchemaParser: a template is sugar equivalent to a schema
126
142
  // (STXT-TEMPLATE-SPEC 13), so the same condition must not change its code
127
143
  // depending on the entry point
128
- throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_ONLY_SUPPORTED_BY_ENUM", `Values only supported for type ENUM, not for type ${type}`);
144
+ throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
129
145
  }
130
146
  for (const v of values) {
131
147
  schemaNode.addValue(v, node.getLine());
@@ -133,13 +149,13 @@ function addToSchema(schema, node) {
133
149
  }
134
150
  // An ENUM with no list of values is an invalid template (STXT-TEMPLATE-SPEC 9 and 13.7)
135
151
  if (type === "ENUM" && (!values || values.length === 0)) {
136
- throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_EMPTY_FOR_ENUM", "ENUM Type must include values");
152
+ throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
137
153
  }
138
154
  }
139
155
  else {
140
156
  const type = cl.getType();
141
157
  if (!type || !type.startsWith("@")) {
142
- throw new ValidationException_1.ValidationException(node.getLine(), "NODE_DEFINED_MULTIPLE_TIMES", `Multiple node reference must start with @: ${node.getName()}`);
158
+ throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_REQUIRED", `Multiple node reference must start with @: ${node.getName()}`);
143
159
  }
144
160
  const reference = type.substring(1).trim();
145
161
  // Reference and explicit type on the same line (STXT-TEMPLATE-SPEC 14.13)
@@ -148,7 +164,7 @@ function addToSchema(schema, node) {
148
164
  throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_WITH_TYPE_NOT_ALLOWED", `Reference '@${node.getName()}' can not declare a type: ${explicitType}`);
149
165
  }
150
166
  if (StringUtils_1.StringUtils.normalize(reference) !== node.getCanonicalName()) {
151
- throw new ValidationException_1.ValidationException(node.getLine(), "NODE_REFERENCE_NOT_VALID", `Reference must be '@${node.getName()}', not '${reference}'`);
167
+ throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_NAME_NOT_VALID", `Reference must be '@${node.getName()}', not '${reference}'`);
152
168
  }
153
169
  // A reference may override the cardinality, but it may redefine neither the ENUM
154
170
  // values nor the children (STXT-TEMPLATE-SPEC 6.4)
@@ -171,7 +187,7 @@ function addToSchema(schema, node) {
171
187
  for (const child of childrenNode) {
172
188
  // STXT-TEMPLATE-SPEC 6.3: every Structure line uses ':', so a child is inline too
173
189
  if (!(child instanceof InlineNode_1.InlineNode)) {
174
- throw new ValidationException_1.ValidationException(child.getLine(), "INVALID_CHILD_LINE", "Template Structure lines must use ':'");
190
+ throw new ValidationException_1.ValidationException(child.getLine(), "STRUCTURE_LINE_NOT_VALID", "Template Structure lines must use ':'");
175
191
  }
176
192
  cl = ChildLineParser_1.ChildLineParser.parse(child.getValue(), child.getLine());
177
193
  const childName = child.getName();
@@ -213,20 +229,20 @@ function addDescriptions(schema, nodes) {
213
229
  }
214
230
  // No external descriptions
215
231
  if (namespace !== schema.getNamespace()) {
216
- throw new ValidationException_1.ValidationException(node.getLine(), "EXTERNAL_DESCRIPTION_NOT_ALLOWED", "Not allowed description in external namespaces");
232
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", "Not allowed description in external namespaces");
217
233
  }
218
234
  // No children either
219
235
  if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
220
- throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_DESCRIPTION_NOT_ALLOWED", "Not allowed children in description");
236
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_CHILDREN_NOT_ALLOWED", "Not allowed children in description");
221
237
  }
222
238
  // Look for the node in the schema
223
239
  const nodeDef = schema.getNodeDefinition(node.getName());
224
240
  if (!nodeDef) {
225
- throw new ValidationException_1.ValidationException(node.getLine(), "NODE_NOT_FOUND", `Not found node with name: ${node.getName()}`);
241
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_NODE_NOT_FOUND", `Not found node with name: ${node.getName()}`);
226
242
  }
227
243
  // No more than one entry per node is allowed (STXT-TEMPLATE-SPEC 12)
228
244
  if (nodeDef.getDescription() !== undefined) {
229
- throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_ALREADY_DEFINED", `Exists a previous description for node: ${node.getName()}`);
245
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_DUPLICATED", `Exists a previous description for node: ${node.getName()}`);
230
246
  }
231
247
  nodeDef.setDescription(node.getText());
232
248
  });
@@ -17,9 +17,10 @@ export declare class TemplateSchemaProviderMemory extends SchemaProviderMemory {
17
17
  * schema it produces.
18
18
  *
19
19
  * @param template text of the `@stxt.template` document.
20
- * @throws ValidationException with code `INVALID_SCHEMA` if the document does not hold exactly
21
- * one template or the resulting schema has no namespace, or the first validation error
22
- * if the template does not validate against the template meta-schema.
20
+ * @throws ValidationException with code `TEMPLATE_MULTIPLE_ROOTS` if the document does not hold
21
+ * exactly one root node, `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if that
22
+ * root is not `Template (@stxt.template): ns`, or the first validation error if the
23
+ * template does not validate against the template meta-schema.
23
24
  */
24
25
  addTemplate(template: string): void;
25
26
  }
@@ -30,15 +30,16 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
30
30
  * schema it produces.
31
31
  *
32
32
  * @param template text of the `@stxt.template` document.
33
- * @throws ValidationException with code `INVALID_SCHEMA` if the document does not hold exactly
34
- * one template or the resulting schema has no namespace, or the first validation error
35
- * if the template does not validate against the template meta-schema.
33
+ * @throws ValidationException with code `TEMPLATE_MULTIPLE_ROOTS` if the document does not hold
34
+ * exactly one root node, `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if that
35
+ * root is not `Template (@stxt.template): ns`, or the first validation error if the
36
+ * template does not validate against the template meta-schema.
36
37
  */
37
38
  addTemplate(template) {
38
39
  const parser = new Parser_1.Parser();
39
40
  const nodes = parser.parse(template);
40
41
  if (nodes.length !== 1) {
41
- throw new ValidationException_1.ValidationException(0, "INVALID_SCHEMA", `There are ${nodes.length}, and expected is 1`);
42
+ throw new ValidationException_1.ValidationException(0, "TEMPLATE_MULTIPLE_ROOTS", `A template document must hold exactly 1 root node, got ${nodes.length}`);
42
43
  }
43
44
  // A template that does not validate against the template meta-schema must not
44
45
  // be registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
@@ -49,10 +50,6 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
49
50
  }
50
51
  // Build the schema out of the template
51
52
  const sch = (0, TemplateParser_1.transformTemplateNodeToSchema)(nodes[0]);
52
- // Minimum safety check (Java checked the expected namespace here too)
53
- if (!sch.getNamespace() || sch.getNamespace().trim().length === 0) {
54
- throw new ValidationException_1.ValidationException(0, "INVALID_SCHEMA", "Schema namespace is empty");
55
- }
56
53
  this.schemas.set(sch.getNamespace(), sch);
57
54
  }
58
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Parser and schema validator for STXT, an indentation-based structured-text format.",
5
5
  "main": "out/all.js",
6
6
  "types": "out/all.d.ts",