@gregiteen/ssss-cli 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Zero-dependency JSON Schema validator for SSSS core and bundle schemas.
3
+ *
4
+ * Implements validation for standard JSON Schema Draft 2020-12 constructs
5
+ * used by SSSS schemas:
6
+ * - type (single or union)
7
+ * - properties & additionalProperties
8
+ * - required
9
+ * - items
10
+ * - enum
11
+ * - pattern
12
+ * - $ref (local root-anchored pointers: #/$defs/...)
13
+ */
14
+
15
+ function resolveRef(root, ref) {
16
+ if (!ref.startsWith('#/')) {
17
+ throw new Error(`Only local root-anchored $ref pointers are supported: ${ref}`);
18
+ }
19
+ const parts = ref.slice(2).split('/');
20
+ let current = root;
21
+ for (const part of parts) {
22
+ if (current == null || typeof current !== 'object') {
23
+ throw new Error(`Unable to resolve $ref pointer: ${ref}`);
24
+ }
25
+ current = current[part];
26
+ }
27
+ return current;
28
+ }
29
+
30
+ function matchType(value, expectedType) {
31
+ if (Array.isArray(expectedType)) {
32
+ return expectedType.some((t) => matchType(value, t));
33
+ }
34
+ switch (expectedType) {
35
+ case 'string':
36
+ return typeof value === 'string';
37
+ case 'number':
38
+ return typeof value === 'number' && !Number.isNaN(value);
39
+ case 'integer':
40
+ return Number.isInteger(value);
41
+ case 'boolean':
42
+ return typeof value === 'boolean';
43
+ case 'object':
44
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
45
+ case 'array':
46
+ return Array.isArray(value);
47
+ case 'null':
48
+ return value === null;
49
+ default:
50
+ return true;
51
+ }
52
+ }
53
+
54
+ export function validateJsonSchema(data, schema, options = {}) {
55
+ const root = options.rootSchema || schema;
56
+ const errors = [];
57
+
58
+ function validate(value, curSchema, path) {
59
+ if (!curSchema || typeof curSchema !== 'object') return;
60
+
61
+ if (curSchema.$ref) {
62
+ const resolved = resolveRef(root, curSchema.$ref);
63
+ validate(value, resolved, path);
64
+ return;
65
+ }
66
+
67
+ if (curSchema.type !== undefined) {
68
+ if (!matchType(value, curSchema.type)) {
69
+ errors.push({
70
+ path,
71
+ message: `Expected type ${JSON.stringify(curSchema.type)}, but received ${Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value}.`,
72
+ });
73
+ return;
74
+ }
75
+ }
76
+
77
+ if (curSchema.enum !== undefined && !curSchema.enum.includes(value)) {
78
+ errors.push({
79
+ path,
80
+ message: `Value ${JSON.stringify(value)} is not in enum: ${curSchema.enum.map((e) => JSON.stringify(e)).join(', ')}.`,
81
+ });
82
+ }
83
+
84
+ if (typeof value === 'string' && curSchema.pattern !== undefined) {
85
+ const reg = new RegExp(curSchema.pattern);
86
+ if (!reg.test(value)) {
87
+ errors.push({
88
+ path,
89
+ message: `String does not match pattern ${curSchema.pattern}.`,
90
+ });
91
+ }
92
+ }
93
+
94
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
95
+ if (Array.isArray(curSchema.required)) {
96
+ for (const reqKey of curSchema.required) {
97
+ if (value[reqKey] === undefined) {
98
+ errors.push({
99
+ path: path ? `${path}.${reqKey}` : reqKey,
100
+ message: `Missing required property '${reqKey}'.`,
101
+ });
102
+ }
103
+ }
104
+ }
105
+
106
+ const definedKeys = new Set(Object.keys(curSchema.properties || {}));
107
+ for (const [key, val] of Object.entries(value)) {
108
+ const subPath = path ? `${path}.${key}` : key;
109
+ if (definedKeys.has(key)) {
110
+ validate(val, curSchema.properties[key], subPath);
111
+ } else if (curSchema.additionalProperties === false) {
112
+ errors.push({
113
+ path: subPath,
114
+ message: `Unexpected property '${key}' not defined in schema.`,
115
+ });
116
+ } else if (typeof curSchema.additionalProperties === 'object' && curSchema.additionalProperties !== null) {
117
+ validate(val, curSchema.additionalProperties, subPath);
118
+ }
119
+ }
120
+ }
121
+
122
+ if (Array.isArray(value) && curSchema.items) {
123
+ for (let i = 0; i < value.length; i++) {
124
+ validate(value[i], curSchema.items, `${path}[${i}]`);
125
+ }
126
+ }
127
+ }
128
+
129
+ validate(data, schema, '');
130
+ return {
131
+ valid: errors.length === 0,
132
+ errors,
133
+ };
134
+ }