@techspokes/typescript-wsdl-client 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/checkedPlanning.js +27 -4
- package/dist/cli.js +17 -1
- package/dist/client/generateClient.js +72 -16
- package/dist/client/generateOperations.js +30 -11
- package/dist/client/generateUtils.js +5 -1
- package/dist/compiler/canonicalGraph.d.ts +4 -15
- package/dist/compiler/canonicalGraph.js +1 -8
- package/dist/compiler/componentRecordProjection.d.ts +3 -0
- package/dist/compiler/componentRecordProjection.js +10 -0
- package/dist/compiler/declarationIdentity.d.ts +22 -0
- package/dist/compiler/declarationIdentity.js +9 -0
- package/dist/compiler/directCodec.d.ts +22 -2
- package/dist/compiler/directCodec.js +40 -7
- package/dist/compiler/directOperation.d.ts +26 -4
- package/dist/compiler/directOperation.js +59 -5
- package/dist/compiler/directSoap.js +24 -7
- package/dist/compiler/directSourceCapsule.js +59 -3
- package/dist/compiler/directStructure.js +6 -2
- package/dist/compiler/directStructurePlan.d.ts +10 -1
- package/dist/compiler/directStructurePlan.js +27 -7
- package/dist/compiler/directValues.d.ts +15 -3
- package/dist/compiler/directValues.js +20 -4
- package/dist/compiler/legacyDeclarations.d.ts +26 -0
- package/dist/compiler/legacyDeclarations.js +144 -0
- package/dist/compiler/projectionScalar.d.ts +18 -1
- package/dist/compiler/projectionScalar.js +47 -13
- package/dist/compiler/rpcStringValues.d.ts +30 -0
- package/dist/compiler/rpcStringValues.js +196 -0
- package/dist/compiler/rpcTypedValues.d.ts +48 -0
- package/dist/compiler/rpcTypedValues.js +190 -0
- package/dist/compiler/schemaCompiler.js +44 -109
- package/dist/compiler/semanticCatalog.d.ts +2 -2
- package/dist/gateway/generateComposedGateway.d.ts +16 -0
- package/dist/gateway/generateComposedGateway.js +65 -0
- package/dist/gateway/generateGateway.js +1 -0
- package/dist/gateway/generators.js +19 -4
- package/dist/gateway/helpers.d.ts +1 -0
- package/dist/gateway/helpers.js +15 -0
- package/dist/generation/componentShape.js +25 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/loader/orderedSyntax.d.ts +4 -0
- package/dist/loader/orderedSyntax.js +18 -4
- package/dist/multiServicePipeline.d.ts +41 -0
- package/dist/multiServicePipeline.js +287 -0
- package/dist/openapi/composeOpenAPI.d.ts +29 -0
- package/dist/openapi/composeOpenAPI.js +322 -0
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +2 -0
- package/docs/agent-skill.md +10 -1
- package/docs/api-reference.md +127 -9
- package/docs/checked-gateway.md +2 -0
- package/docs/checked-generation.md +11 -5
- package/docs/cli-reference.md +25 -5
- package/docs/concepts.md +4 -5
- package/docs/configuration.md +10 -3
- package/docs/corpus.md +1 -1
- package/docs/gateway-guide.md +104 -27
- package/docs/generated-code.md +12 -6
- package/docs/migration-playbook.md +4 -0
- package/docs/migration.md +124 -6
- package/docs/output-anatomy.md +20 -17
- package/docs/production.md +7 -5
- package/docs/start-here.md +12 -2
- package/docs/supported-patterns.md +17 -0
- package/docs/testing.md +2 -0
- package/docs/troubleshooting.md +3 -1
- package/llms.txt +9 -1
- package/package.json +2 -2
- package/src/runtime/clientStreamMethods.tpl.txt +156 -148
- package/src/runtime/operationsStreamHelper.tpl.txt +6 -6
- package/src/runtime/streamXml.ts +48 -20
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/** Bounded SOAP 1.1 RPC string serialization, without a second schema engine.
|
|
2
|
+
* @since 1.1.9
|
|
3
|
+
* @see https://www.w3.org/TR/2000/NOTE-SOAP-20000508/ Sections 5 and 7.
|
|
4
|
+
*/
|
|
5
|
+
import { isXmlNCName, resolveLexicalQName, syntaxAttribute, syntaxElements, XSD_NAMESPACE, XML_NAMESPACE } from '../loader/orderedSyntax.js';
|
|
6
|
+
import { isXmlCharacters } from './directStructurePlan.js';
|
|
7
|
+
import { directDataOwnership } from './directExecutionPolicy.js';
|
|
8
|
+
const SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
|
|
9
|
+
const ENC = 'http://schemas.xmlsoap.org/soap/encoding/';
|
|
10
|
+
const XSI = 'http://www.w3.org/2001/XMLSchema-instance';
|
|
11
|
+
/** Build canonical inline string parameters in source declaration order.
|
|
12
|
+
* @param root Source-owned typed parameters.
|
|
13
|
+
* @param value Data-only application object.
|
|
14
|
+
* @param control Shared invocation ownership and work budget.
|
|
15
|
+
* @returns A generated RPC wrapper and preserved string values.
|
|
16
|
+
* @throws Through the supplied failure callback for invalid application values.
|
|
17
|
+
*/
|
|
18
|
+
export function expandRpcStrings(root, value, control) {
|
|
19
|
+
const { budget, check, fail } = control;
|
|
20
|
+
let owned;
|
|
21
|
+
try {
|
|
22
|
+
owned = directDataOwnership(budget).snapshot(value, check);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof TypeError)
|
|
26
|
+
return fail('value-invalid', 'rpc-data-object-required');
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
if (!owned || typeof owned !== 'object' || Array.isArray(owned))
|
|
30
|
+
return fail('value-invalid', 'rpc-data-object-required');
|
|
31
|
+
const object = owned;
|
|
32
|
+
const keys = Object.keys(object), names = new Set(root.parts.map(part => { check(); return part.name; }));
|
|
33
|
+
budget.chargeWork(64 + keys.length * 32 + names.size * 32);
|
|
34
|
+
if (keys.length !== root.parts.length || keys.some(key => { check(); return !names.has(key); }))
|
|
35
|
+
return fail('value-invalid', 'rpc-required-string-parts');
|
|
36
|
+
const namespaces = { xml: XML_NAMESPACE, '': '', rpc: root.bodyNamespace, xsi: XSI, xsd: XSD_NAMESPACE, enc: ENC, soap: SOAP };
|
|
37
|
+
const attribute = (namespace, local, value) => ({ name: { namespace, local }, lexicalName: local, value });
|
|
38
|
+
const result = Object.create(null);
|
|
39
|
+
const children = root.parts.map(part => {
|
|
40
|
+
check();
|
|
41
|
+
const text = object[part.name];
|
|
42
|
+
if (typeof text !== 'string')
|
|
43
|
+
return fail('value-invalid', 'rpc-required-string-parts');
|
|
44
|
+
budget.chargeWork(64 + text.length * 8);
|
|
45
|
+
if (!isXmlCharacters(text))
|
|
46
|
+
return fail('value-invalid', 'rpc-string-xml-characters');
|
|
47
|
+
result[part.name] = text;
|
|
48
|
+
return { kind: 'element', name: { namespace: '', local: part.name }, lexicalName: part.name, namespaces, baseUri: root.source.uri,
|
|
49
|
+
attributes: [attribute(XSI, 'type', 'xsd:string')], children: [{ kind: 'text', value: text, source: root.source }], source: root.source };
|
|
50
|
+
});
|
|
51
|
+
return { payload: { kind: 'element', name: root.wrapper, lexicalName: 'rpc:' + root.wrapper.local, namespaces,
|
|
52
|
+
baseUri: root.source.uri, attributes: [attribute(SOAP, 'encodingStyle', ENC)], children, source: root.source }, value: result };
|
|
53
|
+
}
|
|
54
|
+
/** Decode only the selected inline/local-reference string profile within one retained Body.
|
|
55
|
+
* @param root Source-owned typed parameters and body namespace.
|
|
56
|
+
* @param payload Observed RPC wrapper or observed SOAP Body.
|
|
57
|
+
* @param output Whether SOAP response local names are insignificant.
|
|
58
|
+
* @param control Shared native invocation budget and explicit failure callback.
|
|
59
|
+
* @returns Values keyed by the original WSDL part names.
|
|
60
|
+
* @throws Through the failure callback for malformed, ambiguous or unsupported encoded values.
|
|
61
|
+
*/
|
|
62
|
+
export function readRpcStrings(root, payload, output, control) {
|
|
63
|
+
const { budget, check, fail } = control;
|
|
64
|
+
const elements = (node) => {
|
|
65
|
+
check();
|
|
66
|
+
budget.chargeWork(32 + node.children.length * 8 + node.attributes.length * 16);
|
|
67
|
+
if (node.children.some(c => c.kind === 'text' && /[^\t\r\n ]/.test(c.value)))
|
|
68
|
+
return fail('mismatch', 'rpc-unexpected-text', node);
|
|
69
|
+
return syntaxElements(node);
|
|
70
|
+
};
|
|
71
|
+
const encoding = (node) => {
|
|
72
|
+
const declared = syntaxAttribute(node, 'encodingStyle', SOAP);
|
|
73
|
+
if (declared !== undefined && declared.trim() !== ENC)
|
|
74
|
+
return fail('unsupported', 'rpc-encoding-style', node);
|
|
75
|
+
};
|
|
76
|
+
const rootMarker = (node) => {
|
|
77
|
+
const marker = syntaxAttribute(node, 'root', ENC);
|
|
78
|
+
if (marker !== undefined && marker !== '0' && marker !== '1')
|
|
79
|
+
return fail('mismatch', 'rpc-root-marker', node);
|
|
80
|
+
return marker;
|
|
81
|
+
};
|
|
82
|
+
const attrs = (node, wrapper = false) => {
|
|
83
|
+
encoding(node);
|
|
84
|
+
rootMarker(node);
|
|
85
|
+
for (const attr of node.attributes) {
|
|
86
|
+
check();
|
|
87
|
+
if (attr.name.namespace === SOAP && attr.name.local === 'encodingStyle' || attr.name.namespace === ENC && attr.name.local === 'root'
|
|
88
|
+
|| attr.name.namespace === '' && attr.name.local === 'id' || !wrapper && (attr.name.namespace === '' && attr.name.local === 'href'
|
|
89
|
+
|| attr.name.namespace === XSI && attr.name.local === 'type'))
|
|
90
|
+
continue;
|
|
91
|
+
return fail('unsupported', 'rpc-encoded-attribute', node);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const body = payload.name.namespace === SOAP && payload.name.local === 'Body' ? payload : undefined;
|
|
95
|
+
if (body) {
|
|
96
|
+
encoding(body);
|
|
97
|
+
if (body.attributes.some(a => a.name.namespace !== SOAP || a.name.local !== 'encodingStyle'))
|
|
98
|
+
return fail('unsupported', 'rpc-body-attribute', body);
|
|
99
|
+
}
|
|
100
|
+
const top = body ? elements(body) : [payload];
|
|
101
|
+
const ids = new Map();
|
|
102
|
+
const pending = [...top];
|
|
103
|
+
while (pending.length) {
|
|
104
|
+
check();
|
|
105
|
+
const node = pending.pop();
|
|
106
|
+
budget.chargeWork(32 + node.children.length * 8);
|
|
107
|
+
const id = syntaxAttribute(node, 'id');
|
|
108
|
+
if (id !== undefined) {
|
|
109
|
+
if (!isXmlNCName(id) || ids.has(id))
|
|
110
|
+
return fail('mismatch', 'rpc-duplicate-or-invalid-id', node);
|
|
111
|
+
ids.set(id, node);
|
|
112
|
+
}
|
|
113
|
+
pending.push(...syntaxElements(node));
|
|
114
|
+
}
|
|
115
|
+
const wrappers = top.filter(node => {
|
|
116
|
+
check();
|
|
117
|
+
return node.name.namespace === root.bodyNamespace && rootMarker(node) !== '0'
|
|
118
|
+
&& syntaxAttribute(node, 'href') === undefined && syntaxAttribute(node, 'type', XSI) === undefined
|
|
119
|
+
&& (!output ? node.name.local === root.wrapper.local : true);
|
|
120
|
+
});
|
|
121
|
+
if (wrappers.length !== 1)
|
|
122
|
+
return fail('mismatch', 'rpc-unambiguous-response-root', payload);
|
|
123
|
+
const wrapper = wrappers[0];
|
|
124
|
+
attrs(wrapper, true);
|
|
125
|
+
const parameters = elements(wrapper);
|
|
126
|
+
if (parameters.length !== root.parts.length)
|
|
127
|
+
return fail('mismatch', 'rpc-result-accessor-count', wrapper);
|
|
128
|
+
const visited = new Set([wrapper]);
|
|
129
|
+
const stringValue = (initial) => {
|
|
130
|
+
const chain = new Set();
|
|
131
|
+
let node = initial, inheritedType = true;
|
|
132
|
+
// @constraints Flat local-reference chains must not consume the JavaScript call stack.
|
|
133
|
+
for (;;) {
|
|
134
|
+
check();
|
|
135
|
+
budget.chargeWork(32 + node.children.length * 8);
|
|
136
|
+
attrs(node);
|
|
137
|
+
if (chain.has(node))
|
|
138
|
+
return fail('unsupported', 'rpc-cyclic-reference', node);
|
|
139
|
+
chain.add(node);
|
|
140
|
+
visited.add(node);
|
|
141
|
+
const explicit = syntaxAttribute(node, 'type', XSI);
|
|
142
|
+
const implicit = node.name.namespace === ENC && node.name.local === 'string';
|
|
143
|
+
if (node.name.namespace === ENC && !implicit)
|
|
144
|
+
return fail('unsupported', 'rpc-string-type', node);
|
|
145
|
+
if (explicit !== undefined) {
|
|
146
|
+
let type;
|
|
147
|
+
try {
|
|
148
|
+
type = resolveLexicalQName(explicit, node);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return fail('unsupported', 'rpc-string-type', node);
|
|
152
|
+
}
|
|
153
|
+
if (type.local !== 'string' || type.namespace !== XSD_NAMESPACE && type.namespace !== ENC)
|
|
154
|
+
return fail('unsupported', 'rpc-string-type', node);
|
|
155
|
+
}
|
|
156
|
+
else if (!inheritedType && !implicit)
|
|
157
|
+
return fail('unsupported', 'rpc-string-type-required', node);
|
|
158
|
+
const href = syntaxAttribute(node, 'href');
|
|
159
|
+
if (href !== undefined) {
|
|
160
|
+
if (syntaxElements(node).length || node.children.some(c => c.kind === 'text' && /[^\t\r\n ]/.test(c.value)))
|
|
161
|
+
return fail('mismatch', 'rpc-reference-content', node);
|
|
162
|
+
if (!href.startsWith('#') || !isXmlNCName(href.slice(1)))
|
|
163
|
+
return fail('unsupported', 'rpc-local-reference-required', node);
|
|
164
|
+
const target = ids.get(href.slice(1));
|
|
165
|
+
if (!target)
|
|
166
|
+
return fail('mismatch', 'rpc-dangling-reference', node);
|
|
167
|
+
node = target;
|
|
168
|
+
inheritedType = false;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (syntaxElements(node).length)
|
|
172
|
+
return fail('unsupported', 'rpc-string-compound-value', node);
|
|
173
|
+
const value = node.children.map(c => c.kind === 'text' ? c.value : '').join('');
|
|
174
|
+
budget.chargeWork(32 + value.length * 8);
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const result = Object.create(null);
|
|
179
|
+
root.parts.forEach((part, index) => {
|
|
180
|
+
check();
|
|
181
|
+
const accessor = parameters[index];
|
|
182
|
+
if (accessor.name.namespace !== '' || !output && accessor.name.local !== part.name)
|
|
183
|
+
return fail('mismatch', 'rpc-parameter-name-order', accessor);
|
|
184
|
+
if (rootMarker(accessor) !== undefined)
|
|
185
|
+
return fail('unsupported', 'rpc-accessor-root-marker', accessor);
|
|
186
|
+
result[part.name] = stringValue(accessor);
|
|
187
|
+
});
|
|
188
|
+
for (const independent of top) {
|
|
189
|
+
check();
|
|
190
|
+
if (independent === wrapper)
|
|
191
|
+
continue;
|
|
192
|
+
if (!visited.has(independent) || rootMarker(independent) === '1')
|
|
193
|
+
return fail('mismatch', 'rpc-unexpected-body-content', independent);
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { GraphNode, Reference } from './canonicalGraph.js';
|
|
2
|
+
import type { SemanticContext } from './semanticContext.js';
|
|
3
|
+
import type { RpcMessageRoot } from './directOperation.js';
|
|
4
|
+
import type { DirectProjectionPlan } from './directStructurePlan.js';
|
|
5
|
+
import type { ExpandedName, SyntaxElement } from '../loader/orderedSyntax.js';
|
|
6
|
+
type Scalar = string | boolean;
|
|
7
|
+
export type RpcTypedValue = Readonly<Record<string, Scalar | Readonly<Record<string, boolean>>>>;
|
|
8
|
+
type Fail = (kind: 'value-invalid' | 'unsupported' | 'mismatch', rule: string, node?: SyntaxElement) => never;
|
|
9
|
+
type Control = {
|
|
10
|
+
check(): void;
|
|
11
|
+
fail: Fail;
|
|
12
|
+
};
|
|
13
|
+
export interface RpcPartPlan {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly reference: Reference;
|
|
16
|
+
readonly owner: GraphNode;
|
|
17
|
+
readonly type: ExpandedName;
|
|
18
|
+
readonly projection: DirectProjectionPlan;
|
|
19
|
+
}
|
|
20
|
+
/** Plan actual message parts; original source references remain native authority.
|
|
21
|
+
* @param context Exact qualified context.
|
|
22
|
+
* @param root Source-owned RPC message.
|
|
23
|
+
* @param control Shared cancellation and refusal controls.
|
|
24
|
+
* @returns Native scalar/flat Boolean record plans in WSDL declaration order.
|
|
25
|
+
*/
|
|
26
|
+
export declare function planRpcParts(context: SemanticContext, root: RpcMessageRoot, control: Control): readonly RpcPartPlan[];
|
|
27
|
+
/** Expand typed values with native restrictions and canonical inline type accessors.
|
|
28
|
+
* @param context Exact qualified context.
|
|
29
|
+
* @param root Source-owned RPC parameters.
|
|
30
|
+
* @param plans Native type plans.
|
|
31
|
+
* @param application Data-only JSON value.
|
|
32
|
+
* @param control Shared invocation controls.
|
|
33
|
+
* @returns Canonical RPC wrapper plus its validated application value.
|
|
34
|
+
*/
|
|
35
|
+
export declare function expandRpcTyped(context: SemanticContext, root: RpcMessageRoot, plans: readonly RpcPartPlan[], application: unknown, control: Control): {
|
|
36
|
+
payload: SyntaxElement;
|
|
37
|
+
value: RpcTypedValue;
|
|
38
|
+
};
|
|
39
|
+
/** Validate actual dispatched inline parameters through native scalar/particle duties.
|
|
40
|
+
* @param context Exact source context.
|
|
41
|
+
* @param root Source-owned RPC input.
|
|
42
|
+
* @param plans Native type plans.
|
|
43
|
+
* @param payload Actual outgoing envelope Body or wrapper.
|
|
44
|
+
* @param control Shared execution controls.
|
|
45
|
+
* @returns Validated values keyed by original message part names.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readRpcTyped(context: SemanticContext, root: RpcMessageRoot, plans: readonly RpcPartPlan[], payload: SyntaxElement, control: Control): RpcTypedValue;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/** Typed RPC composition over native scalar and record owners.
|
|
2
|
+
* @since 1.1.9
|
|
3
|
+
* @constraints Canonical inline parameters; no encoded request graph or array support.
|
|
4
|
+
*/
|
|
5
|
+
import { globalId } from './canonicalGraph.js';
|
|
6
|
+
import { planProjectionType, matchOrderedProjection, isXmlCharacters } from './directStructurePlan.js';
|
|
7
|
+
import { assessProjectionTypeOperand } from './projectionScalar.js';
|
|
8
|
+
import { resolveLexicalQName, syntaxAttribute, syntaxElements, XSD_NAMESPACE, XML_NAMESPACE } from '../loader/orderedSyntax.js';
|
|
9
|
+
import { directDataOwnership } from './directExecutionPolicy.js';
|
|
10
|
+
const SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
|
|
11
|
+
const ENC = 'http://schemas.xmlsoap.org/soap/encoding/';
|
|
12
|
+
const XSI = 'http://www.w3.org/2001/XMLSchema-instance';
|
|
13
|
+
/** Plan actual message parts; original source references remain native authority.
|
|
14
|
+
* @param context Exact qualified context.
|
|
15
|
+
* @param root Source-owned RPC message.
|
|
16
|
+
* @param control Shared cancellation and refusal controls.
|
|
17
|
+
* @returns Native scalar/flat Boolean record plans in WSDL declaration order.
|
|
18
|
+
*/
|
|
19
|
+
export function planRpcParts(context, root, control) {
|
|
20
|
+
const found = context.lookup(globalId('message', root.message));
|
|
21
|
+
if (found.kind !== 'found' || found.node.kind !== 'wsdl' || found.node.role !== 'message')
|
|
22
|
+
return control.fail('unsupported', 'rpc-message-owner');
|
|
23
|
+
const owner = found.node;
|
|
24
|
+
return Object.freeze(root.parts.map(part => {
|
|
25
|
+
control.check();
|
|
26
|
+
context.budget.chargeWork(128);
|
|
27
|
+
const resolved = context.resolve(part.type);
|
|
28
|
+
const type = resolved.kind === 'builtin' ? resolved.name : resolved.kind === 'found' && resolved.node.identity.kind === 'global'
|
|
29
|
+
? resolved.node.identity.name : control.fail('unsupported', 'rpc-named-type-required');
|
|
30
|
+
const projection = planProjectionType(context, owner, part.type, control.check, () => control.fail('unsupported', 'rpc-native-type-shape'));
|
|
31
|
+
const scalar = (value) => value.kind === 'scalar' && !value.list && !value.nillable
|
|
32
|
+
&& ['string', 'boolean', 'decimal', 'integer', 'date', 'dateTime'].includes(value.builtin.local);
|
|
33
|
+
if (projection.root.kind === 'scalar') {
|
|
34
|
+
if (!scalar(projection.root))
|
|
35
|
+
return control.fail('unsupported', 'rpc-scalar-representation');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const record = projection.records.find(record => record.type.id === projection.root.type);
|
|
39
|
+
if (projection.records.length !== 1 || record.content !== 'element-only' || record.attributes.length || record.attributeWildcard
|
|
40
|
+
|| !record.ordered || record.keyed || record.contextual || record.slots.some(slot => !scalar(slot.value)
|
|
41
|
+
|| slot.value.kind !== 'scalar' || slot.value.builtin.local !== 'boolean' || slot.particle.occurs.min !== '1' || slot.particle.occurs.max !== '1'))
|
|
42
|
+
return control.fail('unsupported', 'rpc-flat-boolean-record');
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze({ name: part.name, reference: part.type, owner, type, projection });
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
function scalar(context, owner, reference, shape, lexical, node, control) {
|
|
48
|
+
if (shape.kind !== 'scalar' || !isXmlCharacters(lexical))
|
|
49
|
+
return control.fail('value-invalid', 'rpc-scalar-characters', node);
|
|
50
|
+
const assessed = assessProjectionTypeOperand(context, owner, reference, { value: lexical, context: { namespaces: node.namespaces, source: node.source } }, control.check);
|
|
51
|
+
if (assessed.kind !== 'projection-scalar-operand')
|
|
52
|
+
return control.fail('value-invalid', 'rule' in assessed ? assessed.rule : 'rpc-scalar-budget', node);
|
|
53
|
+
if (shape.constraint?.kind === 'fixed' && assessed.operand.canonical !== shape.constraint.canonical)
|
|
54
|
+
return control.fail('value-invalid', 'fixed-element-value', node);
|
|
55
|
+
return shape.builtin.local === 'boolean' ? assessed.operand.canonical === 'true' : lexical;
|
|
56
|
+
}
|
|
57
|
+
function recordOf(part) { return part.projection.records[0]; }
|
|
58
|
+
function object(value, names, control) {
|
|
59
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
60
|
+
return control.fail('value-invalid', 'rpc-data-object-required');
|
|
61
|
+
const keys = Object.keys(value);
|
|
62
|
+
if (keys.length !== names.length || keys.some(key => !names.includes(key)))
|
|
63
|
+
return control.fail('value-invalid', 'rpc-required-parts');
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
/** Expand typed values with native restrictions and canonical inline type accessors.
|
|
67
|
+
* @param context Exact qualified context.
|
|
68
|
+
* @param root Source-owned RPC parameters.
|
|
69
|
+
* @param plans Native type plans.
|
|
70
|
+
* @param application Data-only JSON value.
|
|
71
|
+
* @param control Shared invocation controls.
|
|
72
|
+
* @returns Canonical RPC wrapper plus its validated application value.
|
|
73
|
+
*/
|
|
74
|
+
export function expandRpcTyped(context, root, plans, application, control) {
|
|
75
|
+
let owned;
|
|
76
|
+
try {
|
|
77
|
+
owned = directDataOwnership(context.budget).snapshot(application, control.check);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof TypeError)
|
|
81
|
+
return control.fail('value-invalid', 'rpc-data-object-required');
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
const input = object(owned, plans.map(part => part.name), control);
|
|
85
|
+
const namespaces = { xml: XML_NAMESPACE, '': '', rpc: root.bodyNamespace, xsi: XSI, xsd: XSD_NAMESPACE, enc: ENC, soap: SOAP };
|
|
86
|
+
const typeNamespaces = [...new Set(plans.map(part => part.type.namespace))].filter(ns => ns !== XSD_NAMESPACE).sort();
|
|
87
|
+
typeNamespaces.forEach((namespace, index) => { namespaces['t' + index] = namespace; });
|
|
88
|
+
const element = (name, value, shape, owner, reference, type) => {
|
|
89
|
+
control.check();
|
|
90
|
+
context.budget.chargeWork(256);
|
|
91
|
+
const prefix = type?.namespace === XSD_NAMESPACE ? 'xsd' : type ? 't' + typeNamespaces.indexOf(type.namespace) : '';
|
|
92
|
+
const node = { kind: 'element', name, lexicalName: name.local, namespaces, baseUri: root.source.uri, source: root.source,
|
|
93
|
+
attributes: type ? [{ name: { namespace: XSI, local: 'type' }, lexicalName: 'xsi:type', value: prefix + ':' + type.local }] : [], children: [] };
|
|
94
|
+
if (shape.kind !== 'scalar')
|
|
95
|
+
return control.fail('unsupported', 'rpc-scalar-required');
|
|
96
|
+
const lexical = shape.builtin.local === 'boolean' ? typeof value === 'boolean' ? String(value) : control.fail('value-invalid', 'boolean-required')
|
|
97
|
+
: typeof value === 'string' ? value : control.fail('value-invalid', 'string-required');
|
|
98
|
+
scalar(context, owner, reference, shape, lexical, node, control);
|
|
99
|
+
return { ...node, children: [{ kind: 'text', value: lexical, source: root.source }] };
|
|
100
|
+
};
|
|
101
|
+
const children = plans.map(part => {
|
|
102
|
+
if (part.projection.root.kind === 'scalar')
|
|
103
|
+
return element({ namespace: '', local: part.name }, input[part.name], part.projection.root, part.owner, part.reference, part.type);
|
|
104
|
+
const record = recordOf(part), value = object(input[part.name], record.slots.map(slot => slot.element.name.local), control);
|
|
105
|
+
const fields = record.slots.map(slot => element(slot.element.name, value[slot.element.name.local], slot.value, slot.element, slot.element.type, slot.value.kind === 'scalar' ? slot.value.builtin : undefined));
|
|
106
|
+
const prefix = 't' + typeNamespaces.indexOf(part.type.namespace);
|
|
107
|
+
return { kind: 'element', name: { namespace: '', local: part.name }, lexicalName: part.name, namespaces, baseUri: root.source.uri, source: root.source,
|
|
108
|
+
attributes: [{ name: { namespace: XSI, local: 'type' }, lexicalName: 'xsi:type', value: prefix + ':' + part.type.local }], children: fields };
|
|
109
|
+
});
|
|
110
|
+
const payload = { kind: 'element', name: root.wrapper, lexicalName: 'rpc:' + root.wrapper.local, namespaces,
|
|
111
|
+
baseUri: root.source.uri, source: root.source, attributes: [{ name: { namespace: SOAP, local: 'encodingStyle' }, lexicalName: 'soap:encodingStyle', value: ENC }], children };
|
|
112
|
+
return { payload, value: readRpcTyped(context, root, plans, payload, control) };
|
|
113
|
+
}
|
|
114
|
+
/** Validate actual dispatched inline parameters through native scalar/particle duties.
|
|
115
|
+
* @param context Exact source context.
|
|
116
|
+
* @param root Source-owned RPC input.
|
|
117
|
+
* @param plans Native type plans.
|
|
118
|
+
* @param payload Actual outgoing envelope Body or wrapper.
|
|
119
|
+
* @param control Shared execution controls.
|
|
120
|
+
* @returns Validated values keyed by original message part names.
|
|
121
|
+
*/
|
|
122
|
+
export function readRpcTyped(context, root, plans, payload, control) {
|
|
123
|
+
const elements = (node) => {
|
|
124
|
+
control.check();
|
|
125
|
+
context.budget.chargeWork(64 + node.children.length * 16);
|
|
126
|
+
if (node.children.some(child => child.kind === 'text' && /[^\t\r\n ]/.test(child.value)))
|
|
127
|
+
return control.fail('mismatch', 'rpc-unexpected-text', node);
|
|
128
|
+
return syntaxElements(node);
|
|
129
|
+
};
|
|
130
|
+
const attrs = (node, type) => {
|
|
131
|
+
for (const attribute of node.attributes) {
|
|
132
|
+
control.check();
|
|
133
|
+
if (attribute.name.namespace === SOAP && attribute.name.local === 'encodingStyle' && attribute.value === ENC)
|
|
134
|
+
continue;
|
|
135
|
+
if (type && attribute.name.namespace === XSI && attribute.name.local === 'type') {
|
|
136
|
+
let actual;
|
|
137
|
+
try {
|
|
138
|
+
actual = resolveLexicalQName(attribute.value, node);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return control.fail('unsupported', 'rpc-typed-accessor', node);
|
|
142
|
+
}
|
|
143
|
+
if (actual.namespace === type.namespace && actual.local === type.local)
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
return control.fail('unsupported', 'rpc-typed-accessor', node);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
if (payload.name.namespace === SOAP && payload.name.local === 'Body') {
|
|
150
|
+
attrs(payload);
|
|
151
|
+
const wrappers = elements(payload);
|
|
152
|
+
if (wrappers.length !== 1)
|
|
153
|
+
return control.fail('mismatch', 'rpc-request-wrapper', payload);
|
|
154
|
+
payload = wrappers[0];
|
|
155
|
+
}
|
|
156
|
+
if (payload.name.namespace !== root.wrapper.namespace || payload.name.local !== root.wrapper.local)
|
|
157
|
+
return control.fail('mismatch', 'rpc-request-wrapper', payload);
|
|
158
|
+
attrs(payload);
|
|
159
|
+
const parameters = elements(payload);
|
|
160
|
+
if (parameters.length !== plans.length)
|
|
161
|
+
return control.fail('mismatch', 'rpc-parameter-count', payload);
|
|
162
|
+
const result = Object.create(null);
|
|
163
|
+
plans.forEach((part, index) => {
|
|
164
|
+
const node = parameters[index];
|
|
165
|
+
if (node.name.namespace !== '' || node.name.local !== part.name)
|
|
166
|
+
return control.fail('mismatch', 'rpc-parameter-name-order', node);
|
|
167
|
+
attrs(node, part.type);
|
|
168
|
+
const readScalar = (node, owner, reference, shape) => {
|
|
169
|
+
if (syntaxElements(node).length)
|
|
170
|
+
return control.fail('mismatch', 'rpc-scalar-content', node);
|
|
171
|
+
return scalar(context, owner, reference, shape, node.children.map(child => child.kind === 'text' ? child.value : '').join(''), node, control);
|
|
172
|
+
};
|
|
173
|
+
if (part.projection.root.kind === 'scalar')
|
|
174
|
+
result[part.name] = readScalar(node, part.owner, part.reference, part.projection.root);
|
|
175
|
+
else {
|
|
176
|
+
const record = recordOf(part), fields = elements(node);
|
|
177
|
+
const matched = matchOrderedProjection(context, record, fields.map(field => field.name), control.check);
|
|
178
|
+
if (!matched || matched.some(slot => 'kind' in slot))
|
|
179
|
+
return control.fail('mismatch', 'rpc-native-record-content', node);
|
|
180
|
+
const value = Object.create(null);
|
|
181
|
+
fields.forEach(field => {
|
|
182
|
+
const slot = record.slots.find(slot => slot.element.name.local === field.name.local && slot.element.name.namespace === field.name.namespace);
|
|
183
|
+
attrs(field, slot.value.kind === 'scalar' ? slot.value.builtin : undefined);
|
|
184
|
+
value[slot.element.name.local] = readScalar(field, slot.element, slot.element.type, slot.value);
|
|
185
|
+
});
|
|
186
|
+
result[part.name] = value;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return result;
|
|
190
|
+
}
|