@safeaccess/inline 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +1 -1
- package/CHANGELOG.md +10 -5
- package/LICENSE +1 -1
- package/README.md +56 -14
- package/dist/accessors/abstract-accessor.d.ts +22 -10
- package/dist/accessors/abstract-accessor.js +21 -8
- package/dist/accessors/abstract-integration-accessor.d.ts +22 -0
- package/dist/accessors/abstract-integration-accessor.js +23 -0
- package/dist/accessors/formats/any-accessor.d.ts +10 -8
- package/dist/accessors/formats/any-accessor.js +9 -8
- package/dist/accessors/formats/array-accessor.d.ts +2 -0
- package/dist/accessors/formats/array-accessor.js +2 -0
- package/dist/accessors/formats/env-accessor.d.ts +2 -0
- package/dist/accessors/formats/env-accessor.js +2 -0
- package/dist/accessors/formats/ini-accessor.d.ts +2 -0
- package/dist/accessors/formats/ini-accessor.js +2 -0
- package/dist/accessors/formats/json-accessor.d.ts +2 -0
- package/dist/accessors/formats/json-accessor.js +2 -0
- package/dist/accessors/formats/ndjson-accessor.d.ts +2 -0
- package/dist/accessors/formats/ndjson-accessor.js +2 -0
- package/dist/accessors/formats/object-accessor.d.ts +2 -0
- package/dist/accessors/formats/object-accessor.js +2 -0
- package/dist/accessors/formats/xml-accessor.d.ts +2 -0
- package/dist/accessors/formats/xml-accessor.js +2 -0
- package/dist/accessors/formats/yaml-accessor.d.ts +3 -1
- package/dist/accessors/formats/yaml-accessor.js +4 -2
- package/dist/cache/simple-path-cache.d.ts +51 -0
- package/dist/cache/simple-path-cache.js +72 -0
- package/dist/contracts/accessors-interface.d.ts +2 -0
- package/dist/contracts/factory-accessors-interface.d.ts +2 -0
- package/dist/contracts/filter-evaluator-interface.d.ts +28 -0
- package/dist/contracts/filter-evaluator-interface.js +1 -0
- package/dist/contracts/parse-integration-interface.d.ts +2 -0
- package/dist/contracts/parser-interface.d.ts +92 -0
- package/dist/contracts/parser-interface.js +1 -0
- package/dist/contracts/path-cache-interface.d.ts +7 -6
- package/dist/contracts/readable-accessors-interface.d.ts +11 -6
- package/dist/contracts/security-guard-interface.d.ts +2 -0
- package/dist/contracts/security-parser-interface.d.ts +2 -0
- package/dist/contracts/validatable-parser-interface.d.ts +59 -0
- package/dist/contracts/validatable-parser-interface.js +1 -0
- package/dist/contracts/writable-accessors-interface.d.ts +5 -0
- package/dist/core/accessor-factory.d.ts +124 -0
- package/dist/core/accessor-factory.js +157 -0
- package/dist/core/dot-notation-parser.d.ts +34 -5
- package/dist/core/dot-notation-parser.js +51 -10
- package/dist/core/inline-builder-accessor.d.ts +82 -0
- package/dist/core/inline-builder-accessor.js +107 -0
- package/dist/exceptions/accessor-exception.d.ts +9 -0
- package/dist/exceptions/accessor-exception.js +9 -0
- package/dist/exceptions/invalid-format-exception.d.ts +5 -0
- package/dist/exceptions/invalid-format-exception.js +5 -0
- package/dist/exceptions/parser-exception.d.ts +4 -0
- package/dist/exceptions/parser-exception.js +4 -0
- package/dist/exceptions/path-not-found-exception.d.ts +4 -0
- package/dist/exceptions/path-not-found-exception.js +4 -0
- package/dist/exceptions/readonly-violation-exception.d.ts +4 -0
- package/dist/exceptions/readonly-violation-exception.js +4 -0
- package/dist/exceptions/security-exception.d.ts +6 -0
- package/dist/exceptions/security-exception.js +6 -0
- package/dist/exceptions/unsupported-type-exception.d.ts +4 -0
- package/dist/exceptions/unsupported-type-exception.js +4 -0
- package/dist/exceptions/yaml-parse-exception.d.ts +4 -0
- package/dist/exceptions/yaml-parse-exception.js +4 -0
- package/dist/index.js +2 -1
- package/dist/inline.d.ts +22 -56
- package/dist/inline.js +39 -111
- package/dist/parser/xml-parser.js +23 -10
- package/dist/parser/yaml-parser.d.ts +54 -7
- package/dist/parser/yaml-parser.js +268 -51
- package/dist/path-query/segment-filter-parser.d.ts +142 -0
- package/dist/path-query/segment-filter-parser.js +384 -0
- package/dist/path-query/segment-parser.d.ts +98 -0
- package/dist/path-query/segment-parser.js +283 -0
- package/dist/path-query/segment-path-resolver.d.ts +149 -0
- package/dist/path-query/segment-path-resolver.js +351 -0
- package/dist/path-query/segment-type.d.ts +85 -0
- package/dist/path-query/segment-type.js +35 -0
- package/dist/security/forbidden-keys.d.ts +2 -2
- package/dist/security/forbidden-keys.js +5 -5
- package/dist/security/security-guard.d.ts +3 -1
- package/dist/security/security-guard.js +5 -2
- package/dist/security/security-parser.d.ts +10 -1
- package/dist/security/security-parser.js +10 -1
- package/dist/type-format.d.ts +2 -0
- package/dist/type-format.js +2 -0
- package/package.json +11 -3
- package/src/accessors/abstract-accessor.ts +23 -19
- package/src/accessors/abstract-integration-accessor.ts +27 -0
- package/src/accessors/formats/any-accessor.ts +11 -11
- package/src/accessors/formats/array-accessor.ts +2 -0
- package/src/accessors/formats/env-accessor.ts +2 -0
- package/src/accessors/formats/ini-accessor.ts +2 -0
- package/src/accessors/formats/json-accessor.ts +2 -0
- package/src/accessors/formats/ndjson-accessor.ts +2 -0
- package/src/accessors/formats/object-accessor.ts +2 -0
- package/src/accessors/formats/xml-accessor.ts +2 -0
- package/src/accessors/formats/yaml-accessor.ts +4 -2
- package/src/cache/simple-path-cache.ts +77 -0
- package/src/contracts/accessors-interface.ts +2 -0
- package/src/contracts/factory-accessors-interface.ts +2 -0
- package/src/contracts/filter-evaluator-interface.ts +30 -0
- package/src/contracts/parse-integration-interface.ts +2 -0
- package/src/contracts/parser-interface.ts +114 -0
- package/src/contracts/path-cache-interface.ts +8 -6
- package/src/contracts/readable-accessors-interface.ts +11 -6
- package/src/contracts/security-guard-interface.ts +2 -0
- package/src/contracts/security-parser-interface.ts +2 -0
- package/src/contracts/validatable-parser-interface.ts +64 -0
- package/src/contracts/writable-accessors-interface.ts +5 -0
- package/src/core/accessor-factory.ts +173 -0
- package/src/core/dot-notation-parser.ts +74 -11
- package/src/core/inline-builder-accessor.ts +163 -0
- package/src/exceptions/accessor-exception.ts +9 -0
- package/src/exceptions/invalid-format-exception.ts +5 -0
- package/src/exceptions/parser-exception.ts +4 -0
- package/src/exceptions/path-not-found-exception.ts +4 -0
- package/src/exceptions/readonly-violation-exception.ts +4 -0
- package/src/exceptions/security-exception.ts +6 -0
- package/src/exceptions/unsupported-type-exception.ts +4 -0
- package/src/exceptions/yaml-parse-exception.ts +4 -0
- package/src/index.ts +3 -1
- package/src/inline.ts +42 -120
- package/src/parser/xml-parser.ts +31 -10
- package/src/parser/yaml-parser.ts +310 -45
- package/src/path-query/segment-filter-parser.ts +444 -0
- package/src/path-query/segment-parser.ts +321 -0
- package/src/path-query/segment-path-resolver.ts +521 -0
- package/src/path-query/segment-type.ts +82 -0
- package/src/security/forbidden-keys.ts +5 -5
- package/src/security/security-guard.ts +7 -2
- package/src/security/security-parser.ts +18 -3
- package/src/type-format.ts +2 -0
- package/stryker.config.json +8 -10
- package/tests/accessors/abstract-accessor.test.ts +217 -0
- package/tests/accessors/abstract-integration-accessor.test.ts +37 -0
- package/tests/accessors/formats/any-accessor.test.ts +57 -0
- package/tests/accessors/formats/array-accessor.test.ts +42 -0
- package/tests/accessors/formats/env-accessor.test.ts +103 -0
- package/tests/accessors/formats/ini-accessor.test.ts +186 -0
- package/tests/accessors/{json-accessor.test.ts → formats/json-accessor.test.ts} +6 -6
- package/tests/accessors/formats/ndjson-accessor.test.ts +49 -0
- package/tests/accessors/formats/object-accessor.test.ts +172 -0
- package/tests/accessors/formats/xml-accessor.test.ts +162 -0
- package/tests/accessors/formats/yaml-accessor.test.ts +36 -0
- package/tests/cache/simple-path-cache.test.ts +168 -0
- package/tests/core/accessor-factory.test.ts +157 -0
- package/tests/core/dot-notation-parser-edge-cases.test.ts +415 -0
- package/tests/core/dot-notation-parser.test.ts +0 -288
- package/tests/core/inline-builder-accessor.test.ts +114 -0
- package/tests/exceptions/accessor-exception.test.ts +28 -0
- package/tests/exceptions/invalid-format-exception.test.ts +31 -0
- package/tests/exceptions/path-not-found-exception.test.ts +33 -0
- package/tests/exceptions/readonly-violation-exception.test.ts +35 -0
- package/tests/exceptions/security-exception.test.ts +33 -0
- package/tests/exceptions/unsupported-type-exception.test.ts +33 -0
- package/tests/exceptions/yaml-parse-exception.test.ts +38 -0
- package/tests/mocks/fake-path-cache.ts +4 -3
- package/tests/parity-from.test.ts +118 -0
- package/tests/parity.test.ts +227 -10
- package/tests/parser/xml-parser-mutations.test.ts +579 -0
- package/tests/parser/xml-parser-scanner.test.ts +332 -0
- package/tests/parser/xml-parser.test.ts +10 -334
- package/tests/parser/yaml-parser-mutations.test.ts +750 -0
- package/tests/parser/yaml-parser.test.ts +844 -18
- package/tests/path-query/segment-filter-parser-mutations.test.ts +735 -0
- package/tests/path-query/segment-filter-parser.test.ts +1091 -0
- package/tests/path-query/segment-parser-mutations.test.ts +539 -0
- package/tests/path-query/segment-parser.test.ts +606 -0
- package/tests/path-query/segment-path-resolver-mutations.test.ts +626 -0
- package/tests/path-query/segment-path-resolver.test.ts +1009 -0
- package/tests/security/security-guard-advanced.test.ts +413 -0
- package/tests/security/security-guard-forbidden-keys.test.ts +87 -0
- package/tests/security/security-guard.test.ts +3 -484
- package/tests/security/security-parser.test.ts +18 -14
- package/vitest.config.ts +3 -3
- package/benchmarks/get.bench.ts +0 -26
- package/benchmarks/parse.bench.ts +0 -41
- package/tests/accessors/accessors.test.ts +0 -1017
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ParserInterface } from './parser-interface.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extended parser contract adding security validation capabilities.
|
|
5
|
+
*
|
|
6
|
+
* Adds structural validation and payload size assertion on top of
|
|
7
|
+
* the base {@link ParserInterface} CRUD operations.
|
|
8
|
+
*
|
|
9
|
+
* @internal Not part of the public API - used only by AbstractAccessor internally.
|
|
10
|
+
*/
|
|
11
|
+
export interface ValidatableParserInterface extends ParserInterface {
|
|
12
|
+
/**
|
|
13
|
+
* Retrieve a value at the given path, throwing when not found.
|
|
14
|
+
*
|
|
15
|
+
* @param data - Source data object.
|
|
16
|
+
* @param path - Dot-notation path.
|
|
17
|
+
* @returns Resolved value.
|
|
18
|
+
*
|
|
19
|
+
* @throws {PathNotFoundException} When the path does not exist.
|
|
20
|
+
*/
|
|
21
|
+
getStrict(data: Record<string, unknown>, path: string): unknown;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate data structure against security constraints.
|
|
25
|
+
*
|
|
26
|
+
* Assert key safety, maximum keys, and structural depth
|
|
27
|
+
* using configured security guards and parser options.
|
|
28
|
+
*
|
|
29
|
+
* @param data - Data to validate.
|
|
30
|
+
*
|
|
31
|
+
* @throws {SecurityException} When any constraint is violated.
|
|
32
|
+
*/
|
|
33
|
+
validate(data: Record<string, unknown>): void;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Assert that a raw string payload does not exceed size limits.
|
|
37
|
+
*
|
|
38
|
+
* @param input - Raw input string to check.
|
|
39
|
+
*
|
|
40
|
+
* @throws {SecurityException} When the payload exceeds the configured maximum.
|
|
41
|
+
*/
|
|
42
|
+
assertPayload(input: string): void;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Return the configured maximum structural nesting depth.
|
|
46
|
+
*
|
|
47
|
+
* Used by accessors that perform their own recursive traversal
|
|
48
|
+
* (e.g. ObjectAccessor) before the post-parse validation step runs.
|
|
49
|
+
*
|
|
50
|
+
* @returns Maximum allowed structural depth.
|
|
51
|
+
*/
|
|
52
|
+
getMaxDepth(): number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Return the configured maximum total key count.
|
|
56
|
+
*
|
|
57
|
+
* Used by format parsers that enforce a document element-count limit before
|
|
58
|
+
* structural traversal runs. Accessor implementations that wrap XML parsers
|
|
59
|
+
* can pass this value as an upper bound to prevent document-bombing attacks.
|
|
60
|
+
*
|
|
61
|
+
* @returns Maximum allowed key count.
|
|
62
|
+
*/
|
|
63
|
+
getMaxKeys(): number;
|
|
64
|
+
}
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* All mutations return a new instance with the modification applied,
|
|
5
5
|
* preserving the original accessor instance.
|
|
6
|
+
*
|
|
7
|
+
* @api
|
|
8
|
+
*
|
|
9
|
+
* @see AccessorsInterface Composite interface extending this contract.
|
|
10
|
+
* @see AbstractAccessor Base implementation enforcing readonly guards.
|
|
6
11
|
*/
|
|
7
12
|
export interface WritableAccessorsInterface {
|
|
8
13
|
/**
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
|
|
2
|
+
import type { ParseIntegrationInterface } from '../contracts/parse-integration-interface.js';
|
|
3
|
+
import { AbstractAccessor } from '../accessors/abstract-accessor.js';
|
|
4
|
+
import { ArrayAccessor } from '../accessors/formats/array-accessor.js';
|
|
5
|
+
import { ObjectAccessor } from '../accessors/formats/object-accessor.js';
|
|
6
|
+
import { JsonAccessor } from '../accessors/formats/json-accessor.js';
|
|
7
|
+
import { XmlAccessor } from '../accessors/formats/xml-accessor.js';
|
|
8
|
+
import { YamlAccessor } from '../accessors/formats/yaml-accessor.js';
|
|
9
|
+
import { IniAccessor } from '../accessors/formats/ini-accessor.js';
|
|
10
|
+
import { EnvAccessor } from '../accessors/formats/env-accessor.js';
|
|
11
|
+
import { NdjsonAccessor } from '../accessors/formats/ndjson-accessor.js';
|
|
12
|
+
import { AnyAccessor } from '../accessors/formats/any-accessor.js';
|
|
13
|
+
import { InvalidFormatException } from '../exceptions/invalid-format-exception.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Factory for creating typed format-specific accessors.
|
|
17
|
+
*
|
|
18
|
+
* Encapsulates the wiring between a parser and accessor construction,
|
|
19
|
+
* providing one method per supported format. Used internally by
|
|
20
|
+
* {@link Inline} to create accessors.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export class AccessorFactory {
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the factory with a parser, optional integration, and optional strict mode.
|
|
27
|
+
*
|
|
28
|
+
* @param parser - Parser for dot-notation resolution.
|
|
29
|
+
* @param defaultIntegration - Default integration for AnyAccessor.
|
|
30
|
+
* @param strictMode - Override strict mode for created accessors.
|
|
31
|
+
*/
|
|
32
|
+
constructor(
|
|
33
|
+
private readonly parser: ValidatableParserInterface,
|
|
34
|
+
private readonly defaultIntegration: ParseIntegrationInterface | null = null,
|
|
35
|
+
private readonly strictMode: boolean | null = null,
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return the underlying parser used by this factory.
|
|
40
|
+
*
|
|
41
|
+
* @returns The parser instance.
|
|
42
|
+
*/
|
|
43
|
+
getParser(): ValidatableParserInterface {
|
|
44
|
+
return this.parser;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply configured strict mode to a new accessor before hydration.
|
|
49
|
+
*
|
|
50
|
+
* @param accessor - Unhydrated accessor instance.
|
|
51
|
+
* @returns Same accessor with strict mode applied if configured.
|
|
52
|
+
*/
|
|
53
|
+
private applyOptions<T extends AbstractAccessor>(accessor: T): T {
|
|
54
|
+
if (this.strictMode !== null) {
|
|
55
|
+
return accessor.strict(this.strictMode) as T;
|
|
56
|
+
}
|
|
57
|
+
return accessor;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create an ArrayAccessor from raw array data.
|
|
62
|
+
*
|
|
63
|
+
* @param data - Source array or object.
|
|
64
|
+
* @returns Populated ArrayAccessor.
|
|
65
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
66
|
+
*/
|
|
67
|
+
array(data: Record<string, unknown> | unknown[]): ArrayAccessor {
|
|
68
|
+
return this.applyOptions(new ArrayAccessor(this.parser)).from(data);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create an ObjectAccessor from a source object.
|
|
73
|
+
*
|
|
74
|
+
* @param data - Source object.
|
|
75
|
+
* @returns Populated ObjectAccessor.
|
|
76
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
77
|
+
*/
|
|
78
|
+
object(data: object): ObjectAccessor {
|
|
79
|
+
return this.applyOptions(new ObjectAccessor(this.parser)).from(data);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a JsonAccessor from a JSON string.
|
|
84
|
+
*
|
|
85
|
+
* @param data - Raw JSON string.
|
|
86
|
+
* @returns Populated JsonAccessor.
|
|
87
|
+
* @throws {InvalidFormatException} When the JSON is malformed.
|
|
88
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
89
|
+
*/
|
|
90
|
+
json(data: string): JsonAccessor {
|
|
91
|
+
return this.applyOptions(new JsonAccessor(this.parser)).from(data);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create an XmlAccessor from an XML string.
|
|
96
|
+
*
|
|
97
|
+
* @param data - Raw XML string.
|
|
98
|
+
* @returns Populated XmlAccessor.
|
|
99
|
+
* @throws {InvalidFormatException} When the XML is malformed.
|
|
100
|
+
* @throws {SecurityException} When DOCTYPE is detected.
|
|
101
|
+
*/
|
|
102
|
+
xml(data: string): XmlAccessor {
|
|
103
|
+
return this.applyOptions(new XmlAccessor(this.parser)).from(data);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a YamlAccessor from a YAML string.
|
|
108
|
+
*
|
|
109
|
+
* @param data - Raw YAML string.
|
|
110
|
+
* @returns Populated YamlAccessor.
|
|
111
|
+
* @throws {YamlParseException} When the YAML is malformed.
|
|
112
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
113
|
+
*/
|
|
114
|
+
yaml(data: string): YamlAccessor {
|
|
115
|
+
return this.applyOptions(new YamlAccessor(this.parser)).from(data);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create an IniAccessor from an INI string.
|
|
120
|
+
*
|
|
121
|
+
* @param data - Raw INI string.
|
|
122
|
+
* @returns Populated IniAccessor.
|
|
123
|
+
* @throws {InvalidFormatException} When the INI is malformed.
|
|
124
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
125
|
+
*/
|
|
126
|
+
ini(data: string): IniAccessor {
|
|
127
|
+
return this.applyOptions(new IniAccessor(this.parser)).from(data);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create an EnvAccessor from a dotenv-formatted string.
|
|
132
|
+
*
|
|
133
|
+
* @param data - Raw dotenv string.
|
|
134
|
+
* @returns Populated EnvAccessor.
|
|
135
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
136
|
+
*/
|
|
137
|
+
env(data: string): EnvAccessor {
|
|
138
|
+
return this.applyOptions(new EnvAccessor(this.parser)).from(data);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create an NdjsonAccessor from a newline-delimited JSON string.
|
|
143
|
+
*
|
|
144
|
+
* @param data - Raw NDJSON string.
|
|
145
|
+
* @returns Populated NdjsonAccessor.
|
|
146
|
+
* @throws {InvalidFormatException} When any JSON line is malformed.
|
|
147
|
+
* @throws {SecurityException} When security constraints are violated.
|
|
148
|
+
*/
|
|
149
|
+
ndjson(data: string): NdjsonAccessor {
|
|
150
|
+
return this.applyOptions(new NdjsonAccessor(this.parser)).from(data);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create an AnyAccessor with automatic format detection.
|
|
155
|
+
*
|
|
156
|
+
* @param data - Raw data in any supported format.
|
|
157
|
+
* @param integration - Override integration (falls back to default).
|
|
158
|
+
* @returns Populated AnyAccessor.
|
|
159
|
+
* @throws {InvalidFormatException} When no integration is available.
|
|
160
|
+
*/
|
|
161
|
+
any(data: unknown, integration?: ParseIntegrationInterface | null): AnyAccessor {
|
|
162
|
+
const resolved = integration ?? this.defaultIntegration;
|
|
163
|
+
|
|
164
|
+
if (resolved === null) {
|
|
165
|
+
throw new InvalidFormatException(
|
|
166
|
+
'AnyAccessor requires a ParseIntegrationInterface. ' +
|
|
167
|
+
'Pass one directly or configure a default via Inline.withParserIntegration(i).fromAny(data).',
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return this.applyOptions(new AnyAccessor(this.parser, resolved)).from(data);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { SecurityGuardInterface } from '../contracts/security-guard-interface.js';
|
|
2
2
|
import type { SecurityParserInterface } from '../contracts/security-parser-interface.js';
|
|
3
3
|
import type { PathCacheInterface } from '../contracts/path-cache-interface.js';
|
|
4
|
+
import type { ValidatableParserInterface } from '../contracts/validatable-parser-interface.js';
|
|
4
5
|
import { SecurityGuard } from '../security/security-guard.js';
|
|
5
6
|
import { SecurityParser } from '../security/security-parser.js';
|
|
7
|
+
import { PathNotFoundException } from '../exceptions/path-not-found-exception.js';
|
|
8
|
+
import { SegmentFilterParser } from '../path-query/segment-filter-parser.js';
|
|
9
|
+
import { SegmentParser } from '../path-query/segment-parser.js';
|
|
10
|
+
import { SegmentPathResolver } from '../path-query/segment-path-resolver.js';
|
|
11
|
+
import type { Segment } from '../path-query/segment-type.js';
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Core dot-notation parser for reading, writing, and removing nested values.
|
|
@@ -10,28 +16,40 @@ import { SecurityParser } from '../security/security-parser.js';
|
|
|
10
16
|
* Provides path-based access to plain objects using dot-separated keys.
|
|
11
17
|
* Delegates security validation to SecurityGuard and SecurityParser.
|
|
12
18
|
*
|
|
19
|
+
* @internal
|
|
20
|
+
*
|
|
13
21
|
* @example
|
|
14
22
|
* const parser = new DotNotationParser();
|
|
15
23
|
* parser.get({ user: { name: 'Alice' } }, 'user.name'); // 'Alice'
|
|
16
24
|
*/
|
|
17
|
-
export class DotNotationParser {
|
|
25
|
+
export class DotNotationParser implements ValidatableParserInterface {
|
|
18
26
|
private readonly securityGuard: SecurityGuardInterface;
|
|
19
27
|
private readonly securityParser: SecurityParserInterface;
|
|
20
28
|
private readonly pathCache: PathCacheInterface | null;
|
|
29
|
+
private readonly segmentParser: SegmentParser;
|
|
30
|
+
private readonly segmentPathResolver: SegmentPathResolver;
|
|
21
31
|
|
|
22
32
|
/**
|
|
23
33
|
* @param securityGuard - Key-safety guard. Defaults to a new SecurityGuard instance.
|
|
24
34
|
* @param securityParser - Parser depth and size limits. Defaults to a new SecurityParser instance.
|
|
25
35
|
* @param pathCache - Optional path segment cache for repeated lookups.
|
|
36
|
+
* @param segmentParser - Path-string → segment converter.
|
|
37
|
+
* @param segmentPathResolver - Segment → value resolver.
|
|
26
38
|
*/
|
|
27
39
|
constructor(
|
|
28
40
|
securityGuard?: SecurityGuardInterface,
|
|
29
41
|
securityParser?: SecurityParserInterface,
|
|
30
42
|
pathCache?: PathCacheInterface,
|
|
43
|
+
segmentParser?: SegmentParser,
|
|
44
|
+
segmentPathResolver?: SegmentPathResolver,
|
|
31
45
|
) {
|
|
32
46
|
this.securityGuard = securityGuard ?? new SecurityGuard();
|
|
33
47
|
this.securityParser = securityParser ?? new SecurityParser();
|
|
34
48
|
this.pathCache = pathCache ?? null;
|
|
49
|
+
|
|
50
|
+
const filterParser = new SegmentFilterParser(this.securityGuard);
|
|
51
|
+
this.segmentParser = segmentParser ?? new SegmentParser(filterParser);
|
|
52
|
+
this.segmentPathResolver = segmentPathResolver ?? new SegmentPathResolver(filterParser);
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
/**
|
|
@@ -52,8 +70,50 @@ export class DotNotationParser {
|
|
|
52
70
|
return defaultValue;
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
const segments = this.
|
|
56
|
-
return this.
|
|
73
|
+
const segments = this.segmentPathCache(path);
|
|
74
|
+
return this.resolve(data, segments, defaultValue);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a pre-parsed segment array against data, returning the matched value.
|
|
79
|
+
*
|
|
80
|
+
* @param data - Source data object.
|
|
81
|
+
* @param segments - Typed segments from {@link SegmentParser}.
|
|
82
|
+
* @param defaultValue - Fallback returned when the path does not exist.
|
|
83
|
+
* @returns Resolved value or the default.
|
|
84
|
+
*/
|
|
85
|
+
resolve(
|
|
86
|
+
data: Record<string, unknown>,
|
|
87
|
+
segments: Segment[],
|
|
88
|
+
defaultValue: unknown = null,
|
|
89
|
+
): unknown {
|
|
90
|
+
return this.segmentPathResolver.resolve(
|
|
91
|
+
data,
|
|
92
|
+
segments,
|
|
93
|
+
0,
|
|
94
|
+
defaultValue,
|
|
95
|
+
this.securityParser.getMaxResolveDepth(),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Retrieve a value at the given path, throwing when not found.
|
|
101
|
+
*
|
|
102
|
+
* @param data - Source data object.
|
|
103
|
+
* @param path - Dot-notation path string.
|
|
104
|
+
* @returns Resolved value.
|
|
105
|
+
*
|
|
106
|
+
* @throws {PathNotFoundException} When the path does not exist.
|
|
107
|
+
*/
|
|
108
|
+
getStrict(data: Record<string, unknown>, path: string): unknown {
|
|
109
|
+
const sentinel = Object.create(null) as Record<string, never>;
|
|
110
|
+
const result = this.get(data, path, sentinel);
|
|
111
|
+
|
|
112
|
+
if (result === sentinel) {
|
|
113
|
+
throw new PathNotFoundException(`Path '${path}' not found.`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
57
117
|
}
|
|
58
118
|
|
|
59
119
|
/**
|
|
@@ -68,7 +128,7 @@ export class DotNotationParser {
|
|
|
68
128
|
* parser.set({}, 'user.name', 'Alice'); // { user: { name: 'Alice' } }
|
|
69
129
|
*/
|
|
70
130
|
set(data: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {
|
|
71
|
-
const segments = this.
|
|
131
|
+
const segments = this.segmentParser.parseKeys(path);
|
|
72
132
|
return this.setAt(data, segments, value);
|
|
73
133
|
}
|
|
74
134
|
|
|
@@ -103,7 +163,7 @@ export class DotNotationParser {
|
|
|
103
163
|
* parser.remove({ a: { b: 1 } }, 'a.b'); // { a: {} }
|
|
104
164
|
*/
|
|
105
165
|
remove(data: Record<string, unknown>, path: string): Record<string, unknown> {
|
|
106
|
-
const segments = this.
|
|
166
|
+
const segments = this.segmentParser.parseKeys(path);
|
|
107
167
|
return this.removeAt(data, segments);
|
|
108
168
|
}
|
|
109
169
|
|
|
@@ -188,7 +248,10 @@ export class DotNotationParser {
|
|
|
188
248
|
* @example
|
|
189
249
|
* parser.removeAt({ a: { b: 1 } }, ['a', 'b']); // { a: {} }
|
|
190
250
|
*/
|
|
191
|
-
removeAt(
|
|
251
|
+
removeAt(
|
|
252
|
+
data: Record<string, unknown>,
|
|
253
|
+
segments: Array<string | number>,
|
|
254
|
+
): Record<string, unknown> {
|
|
192
255
|
/* Stryker disable next-line ConditionalExpression,BlockStatement -- equivalent: empty segments → eraseAt hits undefined key → hasOwnProperty false → returns data anyway */
|
|
193
256
|
if (segments.length === 0) {
|
|
194
257
|
return data;
|
|
@@ -273,22 +336,22 @@ export class DotNotationParser {
|
|
|
273
336
|
}
|
|
274
337
|
|
|
275
338
|
/**
|
|
276
|
-
*
|
|
339
|
+
* Retrieve parsed segments from cache or parse and cache the path.
|
|
277
340
|
*
|
|
278
341
|
* @param path - Dot-notation path string.
|
|
279
|
-
* @returns
|
|
342
|
+
* @returns Cached or freshly parsed typed segments.
|
|
280
343
|
*/
|
|
281
|
-
private
|
|
344
|
+
private segmentPathCache(path: string): Segment[] {
|
|
282
345
|
if (this.pathCache !== null) {
|
|
283
346
|
const cached = this.pathCache.get(path);
|
|
284
347
|
if (cached !== null) {
|
|
285
348
|
return cached;
|
|
286
349
|
}
|
|
287
|
-
const segments =
|
|
350
|
+
const segments = this.segmentParser.parseSegments(path);
|
|
288
351
|
this.pathCache.set(path, segments);
|
|
289
352
|
return segments;
|
|
290
353
|
}
|
|
291
|
-
return
|
|
354
|
+
return this.segmentParser.parseSegments(path);
|
|
292
355
|
}
|
|
293
356
|
|
|
294
357
|
/**
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { SecurityGuardInterface } from '../contracts/security-guard-interface.js';
|
|
2
|
+
import type { SecurityParserInterface } from '../contracts/security-parser-interface.js';
|
|
3
|
+
import type { ParseIntegrationInterface } from '../contracts/parse-integration-interface.js';
|
|
4
|
+
import type { PathCacheInterface } from '../contracts/path-cache-interface.js';
|
|
5
|
+
import { SecurityGuard } from '../security/security-guard.js';
|
|
6
|
+
import { SecurityParser } from '../security/security-parser.js';
|
|
7
|
+
import { SimplePathCache } from '../cache/simple-path-cache.js';
|
|
8
|
+
import { DotNotationParser } from './dot-notation-parser.js';
|
|
9
|
+
import { AccessorFactory } from './accessor-factory.js';
|
|
10
|
+
import { SegmentFilterParser } from '../path-query/segment-filter-parser.js';
|
|
11
|
+
import { SegmentParser } from '../path-query/segment-parser.js';
|
|
12
|
+
import { SegmentPathResolver } from '../path-query/segment-path-resolver.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builder for configuring and constructing the internal components of SafeAccess Inline.
|
|
16
|
+
*
|
|
17
|
+
* Provides an immutable builder API: each `withXxx()` method returns a new
|
|
18
|
+
* instance with the specified override, leaving the original unchanged.
|
|
19
|
+
*
|
|
20
|
+
* Note: PHP's equivalent uses `__callStatic`/`__call` magic to expose
|
|
21
|
+
* protected methods publicly. TypeScript has no equivalent mechanism, so
|
|
22
|
+
* builder methods are public directly and static forwarding is explicit
|
|
23
|
+
* in the {@link Inline} subclass.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*
|
|
27
|
+
* @see Inline
|
|
28
|
+
*/
|
|
29
|
+
export class InlineBuilderAccessor {
|
|
30
|
+
protected readonly _guard: SecurityGuardInterface;
|
|
31
|
+
protected readonly _secParser: SecurityParserInterface;
|
|
32
|
+
protected readonly _pathCache: PathCacheInterface | null;
|
|
33
|
+
protected readonly _integration: ParseIntegrationInterface | null;
|
|
34
|
+
protected readonly _strictMode: boolean | null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a builder with optional component overrides.
|
|
38
|
+
*
|
|
39
|
+
* @param guard - Custom security guard, or undefined for default.
|
|
40
|
+
* @param secParser - Custom security parser, or undefined for default.
|
|
41
|
+
* @param pathCache - Custom path cache, or null/undefined for default.
|
|
42
|
+
* @param integration - Custom format integration, or null/undefined for none.
|
|
43
|
+
* @param strictMode - Strict mode override, or null/undefined for accessor default.
|
|
44
|
+
*/
|
|
45
|
+
constructor(
|
|
46
|
+
guard?: SecurityGuardInterface,
|
|
47
|
+
secParser?: SecurityParserInterface,
|
|
48
|
+
pathCache?: PathCacheInterface | null,
|
|
49
|
+
integration?: ParseIntegrationInterface | null,
|
|
50
|
+
strictMode?: boolean | null,
|
|
51
|
+
) {
|
|
52
|
+
this._guard = guard ?? new SecurityGuard();
|
|
53
|
+
this._secParser = secParser ?? new SecurityParser();
|
|
54
|
+
this._pathCache = pathCache ?? null;
|
|
55
|
+
this._integration = integration ?? null;
|
|
56
|
+
this._strictMode = strictMode ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize the builder with default or provided components.
|
|
61
|
+
*
|
|
62
|
+
* @returns Configured factory ready to create typed accessors.
|
|
63
|
+
*/
|
|
64
|
+
builder(): AccessorFactory {
|
|
65
|
+
const filterParser = new SegmentFilterParser(this._guard);
|
|
66
|
+
const segmentParser = new SegmentParser(filterParser);
|
|
67
|
+
const segmentPathResolver = new SegmentPathResolver(filterParser);
|
|
68
|
+
|
|
69
|
+
const dotNotationParser = new DotNotationParser(
|
|
70
|
+
this._guard,
|
|
71
|
+
this._secParser,
|
|
72
|
+
this._pathCache ?? new SimplePathCache(),
|
|
73
|
+
segmentParser,
|
|
74
|
+
segmentPathResolver,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return new AccessorFactory(dotNotationParser, this._integration, this._strictMode);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set a custom parser integration implementation.
|
|
82
|
+
*
|
|
83
|
+
* @param integration - Custom format integration to use.
|
|
84
|
+
* @returns New builder instance with the integration configured.
|
|
85
|
+
*/
|
|
86
|
+
withParserIntegration(integration: ParseIntegrationInterface): this {
|
|
87
|
+
return new (this.constructor as new (
|
|
88
|
+
guard: SecurityGuardInterface,
|
|
89
|
+
secParser: SecurityParserInterface,
|
|
90
|
+
pathCache: PathCacheInterface | null,
|
|
91
|
+
integration: ParseIntegrationInterface | null,
|
|
92
|
+
strictMode: boolean | null,
|
|
93
|
+
) => this)(this._guard, this._secParser, this._pathCache, integration, this._strictMode);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set a custom security guard implementation.
|
|
98
|
+
*
|
|
99
|
+
* @param guard - Custom guard implementation to use.
|
|
100
|
+
* @returns New builder instance with the guard configured.
|
|
101
|
+
*/
|
|
102
|
+
withSecurityGuard(guard: SecurityGuardInterface): this {
|
|
103
|
+
return new (this.constructor as new (
|
|
104
|
+
guard: SecurityGuardInterface,
|
|
105
|
+
secParser: SecurityParserInterface,
|
|
106
|
+
pathCache: PathCacheInterface | null,
|
|
107
|
+
integration: ParseIntegrationInterface | null,
|
|
108
|
+
strictMode: boolean | null,
|
|
109
|
+
) => this)(guard, this._secParser, this._pathCache, this._integration, this._strictMode);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Set a custom security parser implementation.
|
|
114
|
+
*
|
|
115
|
+
* @param parser - Custom parser implementation to use.
|
|
116
|
+
* @returns New builder instance with the parser configured.
|
|
117
|
+
*/
|
|
118
|
+
withSecurityParser(parser: SecurityParserInterface): this {
|
|
119
|
+
return new (this.constructor as new (
|
|
120
|
+
guard: SecurityGuardInterface,
|
|
121
|
+
secParser: SecurityParserInterface,
|
|
122
|
+
pathCache: PathCacheInterface | null,
|
|
123
|
+
integration: ParseIntegrationInterface | null,
|
|
124
|
+
strictMode: boolean | null,
|
|
125
|
+
) => this)(this._guard, parser, this._pathCache, this._integration, this._strictMode);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Set a custom path cache implementation.
|
|
130
|
+
*
|
|
131
|
+
* @param cache - Custom cache implementation to use.
|
|
132
|
+
* @returns New builder instance with the cache configured.
|
|
133
|
+
*/
|
|
134
|
+
withPathCache(cache: PathCacheInterface): this {
|
|
135
|
+
return new (this.constructor as new (
|
|
136
|
+
guard: SecurityGuardInterface,
|
|
137
|
+
secParser: SecurityParserInterface,
|
|
138
|
+
pathCache: PathCacheInterface | null,
|
|
139
|
+
integration: ParseIntegrationInterface | null,
|
|
140
|
+
strictMode: boolean | null,
|
|
141
|
+
) => this)(this._guard, this._secParser, cache, this._integration, this._strictMode);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Set the strict mode for all accessors created by this builder.
|
|
146
|
+
*
|
|
147
|
+
* @param strict - Whether to enable strict security validation.
|
|
148
|
+
* @returns New builder instance with the strict mode configured.
|
|
149
|
+
*
|
|
150
|
+
* @security Passing `false` disables all SecurityGuard and SecurityParser
|
|
151
|
+
* validation (key safety, payload size, depth and key-count limits).
|
|
152
|
+
* Only use with fully trusted, application-controlled input.
|
|
153
|
+
*/
|
|
154
|
+
withStrictMode(strict: boolean): this {
|
|
155
|
+
return new (this.constructor as new (
|
|
156
|
+
guard: SecurityGuardInterface,
|
|
157
|
+
secParser: SecurityParserInterface,
|
|
158
|
+
pathCache: PathCacheInterface | null,
|
|
159
|
+
integration: ParseIntegrationInterface | null,
|
|
160
|
+
strictMode: boolean | null,
|
|
161
|
+
) => this)(this._guard, this._secParser, this._pathCache, this._integration, strict);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base exception for all accessor-layer errors.
|
|
3
3
|
*
|
|
4
|
+
* @api
|
|
5
|
+
*
|
|
6
|
+
* @see InvalidFormatException Thrown on malformed input data.
|
|
7
|
+
* @see ParserException Thrown on parser-level operational errors.
|
|
8
|
+
* @see PathNotFoundException Thrown when a requested path does not exist.
|
|
9
|
+
* @see SecurityException Thrown on security constraint violations.
|
|
10
|
+
* @see ReadonlyViolationException Thrown on write attempts to a readonly accessor.
|
|
11
|
+
* @see UnsupportedTypeException Thrown when an unsupported format is requested.
|
|
12
|
+
*
|
|
4
13
|
* @example
|
|
5
14
|
* throw new AccessorException('Something went wrong.');
|
|
6
15
|
*/
|
|
@@ -3,6 +3,11 @@ import { AccessorException } from './accessor-exception.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when the input data cannot be parsed as the expected format.
|
|
5
5
|
*
|
|
6
|
+
* @api
|
|
7
|
+
*
|
|
8
|
+
* @see AccessorException Parent exception class.
|
|
9
|
+
* @see YamlParseException Specialized subclass for YAML parsing errors.
|
|
10
|
+
*
|
|
6
11
|
* @example
|
|
7
12
|
* throw new InvalidFormatException('Expected JSON string, got number.');
|
|
8
13
|
*/
|
|
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when an underlying parser encounters a structural error.
|
|
5
5
|
*
|
|
6
|
+
* @api
|
|
7
|
+
*
|
|
8
|
+
* @see AccessorException Parent exception class.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* throw new ParserException('Parser failed to process input.');
|
|
8
12
|
*/
|
|
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when a dot-notation path does not exist in the data.
|
|
5
5
|
*
|
|
6
|
+
* @api
|
|
7
|
+
*
|
|
8
|
+
* @see AccessorException Parent exception class.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* throw new PathNotFoundException("Path 'user.address.zip' not found.");
|
|
8
12
|
*/
|
|
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when a write operation is attempted on a readonly accessor.
|
|
5
5
|
*
|
|
6
|
+
* @api
|
|
7
|
+
*
|
|
8
|
+
* @see AccessorException Parent exception class.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* const accessor = Inline.fromJson('{}').readonly(true);
|
|
8
12
|
* accessor.set('key', 'value'); // throws ReadonlyViolationException
|
|
@@ -7,6 +7,12 @@ import { AccessorException } from './accessor-exception.js';
|
|
|
7
7
|
* methods, stream wrapper / protocol URI schemes, Node.js globals),
|
|
8
8
|
* payload size violations, key-count limits, and depth limit violations.
|
|
9
9
|
*
|
|
10
|
+
* @api
|
|
11
|
+
*
|
|
12
|
+
* @see AccessorException Parent exception class.
|
|
13
|
+
* @see SecurityGuard Validates keys against the forbidden list.
|
|
14
|
+
* @see SecurityParser Enforces payload, depth, and key-count limits.
|
|
15
|
+
*
|
|
10
16
|
* @example
|
|
11
17
|
* throw new SecurityException("Forbidden key '__proto__' detected.");
|
|
12
18
|
*/
|
|
@@ -3,6 +3,10 @@ import { AccessorException } from './accessor-exception.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Thrown when the requested format or TypeFormat value is not supported.
|
|
5
5
|
*
|
|
6
|
+
* @api
|
|
7
|
+
*
|
|
8
|
+
* @see AccessorException Parent exception class.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* throw new UnsupportedTypeException('TypeFormat.Csv is not supported.');
|
|
8
12
|
*/
|
|
@@ -6,6 +6,10 @@ import { InvalidFormatException } from './invalid-format-exception.js';
|
|
|
6
6
|
* Unsafe constructs include: tags (!! and !), anchors (&), aliases (*),
|
|
7
7
|
* and merge keys (<<).
|
|
8
8
|
*
|
|
9
|
+
* @api
|
|
10
|
+
*
|
|
11
|
+
* @see InvalidFormatException Parent exception class.
|
|
12
|
+
*
|
|
9
13
|
* @example
|
|
10
14
|
* throw new YamlParseException('YAML anchors are not supported (line 3).');
|
|
11
15
|
*/
|