@sfdxy/mule-lint 1.20.0 → 1.22.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 +63 -17
- package/dist/package.json +1 -1
- package/dist/src/core/XPathHelper.d.ts.map +1 -1
- package/dist/src/core/XPathHelper.js +8 -0
- package/dist/src/core/XPathHelper.js.map +1 -1
- package/dist/src/engine/LintEngine.d.ts +22 -0
- package/dist/src/engine/LintEngine.d.ts.map +1 -1
- package/dist/src/engine/LintEngine.js +105 -18
- package/dist/src/engine/LintEngine.js.map +1 -1
- package/dist/src/mcp/prompts/index.d.ts +1 -1
- package/dist/src/mcp/prompts/index.d.ts.map +1 -1
- package/dist/src/mcp/prompts/index.js +62 -1
- package/dist/src/mcp/prompts/index.js.map +1 -1
- package/dist/src/mcp/resources/index.js +114 -16
- package/dist/src/mcp/resources/index.js.map +1 -1
- package/dist/src/mcp/tools/getRuleDetails.d.ts.map +1 -1
- package/dist/src/mcp/tools/getRuleDetails.js +30 -1
- package/dist/src/mcp/tools/getRuleDetails.js.map +1 -1
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.d.ts +22 -0
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.d.ts.map +1 -0
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.js +43 -0
- package/dist/src/rules/api-led/ApikitConsoleProductionRule.js.map +1 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.d.ts +24 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.d.ts.map +1 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.js +53 -0
- package/dist/src/rules/api-led/ApikitMainFlowStructureRule.js.map +1 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.d.ts +25 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.d.ts.map +1 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.js +59 -0
- package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.js.map +1 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.d.ts +24 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.d.ts.map +1 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.js +58 -0
- package/dist/src/rules/connector/EventListenerNullGuardRule.js.map +1 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.d.ts +23 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.d.ts.map +1 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.js +52 -0
- package/dist/src/rules/connector/ReplayChannelConfigRule.js.map +1 -0
- package/dist/src/rules/dataweave/DataWeaveRules.d.ts +11 -4
- package/dist/src/rules/dataweave/DataWeaveRules.d.ts.map +1 -1
- package/dist/src/rules/dataweave/DataWeaveRules.js +20 -20
- package/dist/src/rules/dataweave/DataWeaveRules.js.map +1 -1
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.d.ts +25 -0
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.d.ts.map +1 -0
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.js +63 -0
- package/dist/src/rules/dataweave/DuplicateTransformLogicRule.js.map +1 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.d.ts +24 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.d.ts.map +1 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.js +65 -0
- package/dist/src/rules/error-handling/CatchAllLastRule.js.map +1 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.d.ts +28 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.d.ts.map +1 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.js +70 -0
- package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.js.map +1 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.d.ts +23 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.d.ts.map +1 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.js +73 -0
- package/dist/src/rules/error-handling/ErrorResponseStructureRule.js.map +1 -0
- package/dist/src/rules/error-handling/GenericErrorRule.d.ts +15 -3
- package/dist/src/rules/error-handling/GenericErrorRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/GenericErrorRule.js +58 -18
- package/dist/src/rules/error-handling/GenericErrorRule.js.map +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts +14 -15
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js +59 -38
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js.map +1 -1
- package/dist/src/rules/error-handling/TryScopeRule.d.ts +5 -0
- package/dist/src/rules/error-handling/TryScopeRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/TryScopeRule.js +30 -7
- package/dist/src/rules/error-handling/TryScopeRule.js.map +1 -1
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.d.ts +27 -0
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.d.ts.map +1 -0
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.js +46 -0
- package/dist/src/rules/http/ConnectionIdleTimeoutRule.js.map +1 -0
- package/dist/src/rules/index.d.ts +1 -1
- package/dist/src/rules/index.d.ts.map +1 -1
- package/dist/src/rules/index.js +50 -8
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/logging/LoggerPayloadRule.d.ts +15 -0
- package/dist/src/rules/logging/LoggerPayloadRule.d.ts.map +1 -1
- package/dist/src/rules/logging/LoggerPayloadRule.js +48 -4
- package/dist/src/rules/logging/LoggerPayloadRule.js.map +1 -1
- package/dist/src/rules/operations/FlowRefTargetExistsRule.d.ts +23 -0
- package/dist/src/rules/operations/FlowRefTargetExistsRule.d.ts.map +1 -0
- package/dist/src/rules/operations/FlowRefTargetExistsRule.js +58 -0
- package/dist/src/rules/operations/FlowRefTargetExistsRule.js.map +1 -0
- package/dist/src/rules/operations/UnusedFlowRule.d.ts +20 -0
- package/dist/src/rules/operations/UnusedFlowRule.d.ts.map +1 -1
- package/dist/src/rules/operations/UnusedFlowRule.js +73 -7
- package/dist/src/rules/operations/UnusedFlowRule.js.map +1 -1
- package/dist/src/rules/operations/UnusedVariableRule.d.ts +31 -0
- package/dist/src/rules/operations/UnusedVariableRule.d.ts.map +1 -0
- package/dist/src/rules/operations/UnusedVariableRule.js +103 -0
- package/dist/src/rules/operations/UnusedVariableRule.js.map +1 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.d.ts +28 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.d.ts.map +1 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.js +56 -0
- package/dist/src/rules/performance/ListenerReconnectForeverRule.js.map +1 -0
- package/dist/src/rules/performance/ReconnectionStrategyRule.d.ts +7 -4
- package/dist/src/rules/performance/ReconnectionStrategyRule.d.ts.map +1 -1
- package/dist/src/rules/performance/ReconnectionStrategyRule.js +44 -24
- package/dist/src/rules/performance/ReconnectionStrategyRule.js.map +1 -1
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.d.ts +36 -0
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.d.ts.map +1 -0
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.js +124 -0
- package/dist/src/rules/security/ConnectorCredentialsSecuredRule.js.map +1 -0
- package/dist/src/rules/security/HardcodedCredentialsRule.d.ts +4 -0
- package/dist/src/rules/security/HardcodedCredentialsRule.d.ts.map +1 -1
- package/dist/src/rules/security/HardcodedCredentialsRule.js +15 -0
- package/dist/src/rules/security/HardcodedCredentialsRule.js.map +1 -1
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.d.ts +25 -0
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.d.ts.map +1 -0
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.js +59 -0
- package/dist/src/rules/security/SecurePropertiesEncryptionRule.js.map +1 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.d.ts +23 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.d.ts.map +1 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.js +45 -0
- package/dist/src/rules/security/SecurePropertiesKeyRule.js.map +1 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.d.ts +25 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.d.ts.map +1 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.js +63 -0
- package/dist/src/rules/security/TlsKeystorePasswordRule.js.map +1 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.d.ts +26 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.d.ts.map +1 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.js +61 -0
- package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.js.map +1 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.d.ts +34 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.d.ts.map +1 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.js +76 -0
- package/dist/src/rules/standards/ConfigPropertiesOrderingRule.js.map +1 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.d.ts +25 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.d.ts.map +1 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.js +111 -0
- package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.js.map +1 -0
- package/dist/src/rules/yaml/YamlRules.d.ts +6 -2
- package/dist/src/rules/yaml/YamlRules.d.ts.map +1 -1
- package/dist/src/rules/yaml/YamlRules.js +15 -11
- package/dist/src/rules/yaml/YamlRules.js.map +1 -1
- package/dist/src/types/Rule.d.ts +13 -0
- package/dist/src/types/Rule.d.ts.map +1 -1
- package/docs/README.md +87 -27
- package/docs/best-practices/ci-cd.md +135 -0
- package/docs/best-practices/connector-patterns.md +253 -0
- package/docs/best-practices/dataweave-patterns.md +370 -0
- package/docs/best-practices/deployment-2026.md +171 -0
- package/docs/best-practices/error-handling.md +277 -0
- package/docs/best-practices/event-driven-patterns.md +424 -0
- package/docs/best-practices/logging.md +163 -0
- package/docs/best-practices/mulesoft-best-practices.md +72 -865
- package/docs/best-practices/performance.md +273 -0
- package/docs/best-practices/rules-catalog.md +337 -29
- package/docs/best-practices/security.md +181 -0
- package/docs/best-practices/testing.md +190 -0
- package/docs/best-practices/variable-contracts.md +191 -0
- package/docs/linter/architecture.md +119 -64
- package/package.json +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Connector Configuration Patterns
|
|
2
|
+
|
|
3
|
+
> **Applies to:** All (System APIs, Process APIs)
|
|
4
|
+
> **Related Rules:** `SEC-007` · `SEC-008` · `PERF-002` · `RES-001` · `RES-002` · `SF-001` · `SF-002`
|
|
5
|
+
> **Last Updated:** April 2026
|
|
6
|
+
|
|
7
|
+
## When to Read This
|
|
8
|
+
|
|
9
|
+
Read this when configuring Salesforce, NetSuite, HTTP, or Database connectors in Mule 4. Covers connector attribute gotchas, the entity-config pattern, and protocol negotiation.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Patterns
|
|
14
|
+
|
|
15
|
+
### Pattern 1: Entity Configuration (YAML-Driven)
|
|
16
|
+
|
|
17
|
+
**Use when:** building a System API that supports multiple entity types (Account, Contact, Order, etc.) with consistent CRUD operations.
|
|
18
|
+
|
|
19
|
+
Instead of hardcoding entity-specific details in flow XML, externalize them to per-entity YAML files:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
src/main/resources/entity-config/
|
|
23
|
+
├── account.yaml
|
|
24
|
+
├── contact.yaml
|
|
25
|
+
├── opportunity.yaml
|
|
26
|
+
└── order.yaml
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Entity config structure:**
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
# entity-config/account.yaml
|
|
33
|
+
entity:
|
|
34
|
+
account:
|
|
35
|
+
sObjectType: 'Account'
|
|
36
|
+
enabled: true
|
|
37
|
+
externalIdField: 'NetSuite_ID__c'
|
|
38
|
+
queryFields: 'Id, Name, BillingStreet, BillingCity, Phone, Website'
|
|
39
|
+
queryTemplate: 'SELECT {fields} FROM Account WHERE {filter} LIMIT {limit}'
|
|
40
|
+
writeback:
|
|
41
|
+
netSuiteIdField: 'NetSuite_ID__c'
|
|
42
|
+
errorField: 'NetSuite_Error__c'
|
|
43
|
+
lastSyncField: 'Last_NetSuite_Sync__c'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Load in global-config.xml:**
|
|
47
|
+
|
|
48
|
+
```xml
|
|
49
|
+
<configuration-properties doc:name="Account Config"
|
|
50
|
+
file="entity-config/account.yaml"/>
|
|
51
|
+
<configuration-properties doc:name="Contact Config"
|
|
52
|
+
file="entity-config/contact.yaml"/>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Benefits:**
|
|
56
|
+
|
|
57
|
+
- Add a new entity by creating a YAML file — no flow XML changes
|
|
58
|
+
- Entity behavior is visible, auditable, and environment-independent
|
|
59
|
+
- LLMs can read entity configs to understand available operations
|
|
60
|
+
|
|
61
|
+
### Pattern 2: Salesforce JWT Connector
|
|
62
|
+
|
|
63
|
+
**Use when:** authenticating to Salesforce via OAuth 2.0 JWT Bearer flow.
|
|
64
|
+
|
|
65
|
+
```xml
|
|
66
|
+
<salesforce:sfdc-config name="Salesforce_Config">
|
|
67
|
+
<salesforce:jwt-connection
|
|
68
|
+
consumerKey="${secure::salesforce.jwt.consumerKey}"
|
|
69
|
+
keyStore="${salesforce.jwt.keystorePath}"
|
|
70
|
+
storePassword="${secure::salesforce.jwt.storePassword}"
|
|
71
|
+
principal="${salesforce.jwt.principal}"
|
|
72
|
+
tokenEndpoint="${salesforce.jwt.tokenEndpoint}"
|
|
73
|
+
audienceUrl="${salesforce.jwt.audienceUrl}">
|
|
74
|
+
<reconnection>
|
|
75
|
+
<reconnect count="3" frequency="5000"/>
|
|
76
|
+
</reconnection>
|
|
77
|
+
</salesforce:jwt-connection>
|
|
78
|
+
</salesforce:sfdc-config>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> ⚠️ **Gotchas** (common mistakes that cause runtime failures):
|
|
82
|
+
>
|
|
83
|
+
> | Incorrect | Correct | Notes |
|
|
84
|
+
> | ----------------------------------- | ------------------------ | ---------------------------------- |
|
|
85
|
+
> | `salesforce:jwt-connection-config` | `salesforce:sfdc-config` | Outer config element name |
|
|
86
|
+
> | `keyStorePath="..."` | `keyStore="..."` | JWT connection attribute |
|
|
87
|
+
> | `type="Account"` (on upsert) | `objectType="Account"` | Upsert operation attribute |
|
|
88
|
+
> | `type="Account"` (on create/update) | `type="Account"` | Create/Update use `type` (correct) |
|
|
89
|
+
|
|
90
|
+
### Pattern 3: Protocol Negotiation (Dual-Protocol SAPI)
|
|
91
|
+
|
|
92
|
+
**Use when:** a System API must support both SOAP and REST protocols to the same backend (e.g., NetSuite).
|
|
93
|
+
|
|
94
|
+
The calling PAPI sends an `x-integration-protocol` header. The SAPI routes internally:
|
|
95
|
+
|
|
96
|
+
```xml
|
|
97
|
+
<!-- common/netsuite-process-subflow.xml -->
|
|
98
|
+
<sub-flow name="netsuite-process-subflow">
|
|
99
|
+
<choice>
|
|
100
|
+
<when expression="#[vars.protocol == 'SOAP']">
|
|
101
|
+
<flow-ref name="netsuite-soap-upsert-subflow"/>
|
|
102
|
+
</when>
|
|
103
|
+
<when expression="#[vars.protocol == 'REST']">
|
|
104
|
+
<flow-ref name="netsuite-rest-upsert-subflow"/>
|
|
105
|
+
</when>
|
|
106
|
+
</choice>
|
|
107
|
+
</sub-flow>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Protocol support matrix:**
|
|
111
|
+
|
|
112
|
+
| Record Type | SOAP | REST |
|
|
113
|
+
| ----------- | ---- | -------------- |
|
|
114
|
+
| Customer | ✅ | ✅ |
|
|
115
|
+
| Contact | ✅ | ✅ |
|
|
116
|
+
| Sales Order | ✅ | ❌ (SOAP-only) |
|
|
117
|
+
| Credit Memo | ✅ | ❌ (SOAP-only) |
|
|
118
|
+
|
|
119
|
+
### Pattern 4: HTTP Request Connector (with Pooling)
|
|
120
|
+
|
|
121
|
+
**Use when:** making outbound HTTP calls to downstream APIs.
|
|
122
|
+
|
|
123
|
+
```xml
|
|
124
|
+
<http:request-config name="SAPI_HTTP_Config"
|
|
125
|
+
responseTimeout="${https.request.responseTimeout}">
|
|
126
|
+
<http:request-connection host="${https.request.host}"
|
|
127
|
+
port="${https.request.port}"
|
|
128
|
+
connectionIdleTimeout="${https.connection.idleTimeout}"
|
|
129
|
+
maxConnections="${https.connection.maxConnections}">
|
|
130
|
+
<reconnection>
|
|
131
|
+
<reconnect frequency="${reconnection.frequency}"
|
|
132
|
+
count="${reconnection.attempts}"/>
|
|
133
|
+
</reconnection>
|
|
134
|
+
</http:request-connection>
|
|
135
|
+
<!-- Set correlation ID once — applied to every request automatically -->
|
|
136
|
+
<http:default-headers>
|
|
137
|
+
<http:default-header key="X-Correlation-Id" value="#[correlationId]"/>
|
|
138
|
+
</http:default-headers>
|
|
139
|
+
</http:request-config>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Rules:**
|
|
143
|
+
|
|
144
|
+
- Always set `responseTimeout` (avoid hanging connections)
|
|
145
|
+
- Set `connectionIdleTimeout >= responseTimeout` (Grizzly kills connections if idle fires first)
|
|
146
|
+
- Configure connection pooling for production (`maxConnections`)
|
|
147
|
+
|
|
148
|
+
### Pattern 5: Reconnection Strategies
|
|
149
|
+
|
|
150
|
+
**Use when:** configuring any connector that connects to an external system.
|
|
151
|
+
|
|
152
|
+
| Connector Type | Strategy | Example |
|
|
153
|
+
| -------------------------------------- | --------------------- | ---------------------------------- |
|
|
154
|
+
| **Event listeners** (SF, MQ) | `reconnect-forever` | Must auto-recover from disconnects |
|
|
155
|
+
| **Outbound connectors** (HTTP, SF, DB) | `reconnect count="3"` | Bounded retries with frequency |
|
|
156
|
+
| **HTTP Listener** | `reconnect-forever` | Server must always be up |
|
|
157
|
+
|
|
158
|
+
```xml
|
|
159
|
+
<!-- Listener — always reconnect -->
|
|
160
|
+
<http:listener-config name="httpListenerConfig">
|
|
161
|
+
<http:listener-connection host="0.0.0.0" port="${http.port}">
|
|
162
|
+
<reconnection>
|
|
163
|
+
<reconnect-forever frequency="5000"/>
|
|
164
|
+
</reconnection>
|
|
165
|
+
</http:listener-connection>
|
|
166
|
+
</http:listener-config>
|
|
167
|
+
|
|
168
|
+
<!-- Outbound — bounded retries -->
|
|
169
|
+
<salesforce:sfdc-config name="Salesforce_Config">
|
|
170
|
+
<salesforce:jwt-connection ...>
|
|
171
|
+
<reconnection>
|
|
172
|
+
<reconnect count="3" frequency="5000"/>
|
|
173
|
+
</reconnection>
|
|
174
|
+
</salesforce:jwt-connection>
|
|
175
|
+
</salesforce:sfdc-config>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Pattern 6: ObjectStore for Reference Data Caching
|
|
179
|
+
|
|
180
|
+
**Use when:** frequently-accessed reference data (customer records, config lookups) is fetched repeatedly during batch processing. Cache in ObjectStore with TTL to reduce API calls.
|
|
181
|
+
|
|
182
|
+
```xml
|
|
183
|
+
<!-- global.xml — configure the cache store -->
|
|
184
|
+
<os:object-store name="customer-cache-store"
|
|
185
|
+
entryTtl="${customer.cache.ttl}"
|
|
186
|
+
expirationInterval="${customer.cache.expirationInterval}"
|
|
187
|
+
maxEntries="200"/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Key rules:**
|
|
191
|
+
|
|
192
|
+
- Set `maxEntries` to prevent unbounded memory growth
|
|
193
|
+
- Set `entryTtl` appropriate to data volatility (e.g., 1 hour for customer data, 24 hours for country codes)
|
|
194
|
+
- Use `os:contains` + `os:retrieve` to check-then-get, not just `os:retrieve` with default (avoids computing defaults unnecessarily)
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## DWL Utility Module: `parseAndConvert`
|
|
199
|
+
|
|
200
|
+
The Salesforce connector requires Java-typed values (DateTime, Date, Boolean) — not strings. Use a shared DWL module for type coercion:
|
|
201
|
+
|
|
202
|
+
```dataweave
|
|
203
|
+
%dw 2.0
|
|
204
|
+
// dwl/modules/salesforceUtility.dwl
|
|
205
|
+
|
|
206
|
+
var dateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})?$/
|
|
207
|
+
var dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/
|
|
208
|
+
|
|
209
|
+
fun parseAndConvert(obj: Object) =
|
|
210
|
+
obj mapObject ((value, key) ->
|
|
211
|
+
if ((value is String) and ((value as String) matches dateTimePattern))
|
|
212
|
+
(key): (value as String) as DateTime
|
|
213
|
+
else if ((value is String) and ((value as String) matches dateOnlyPattern))
|
|
214
|
+
(key): (value as String) as Date
|
|
215
|
+
else if ((value is String) and ((value as String) == ""))
|
|
216
|
+
(key): null
|
|
217
|
+
else if (value is Object)
|
|
218
|
+
(key): parseAndConvert(value)
|
|
219
|
+
else
|
|
220
|
+
(key): value
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Usage in flow XML:**
|
|
225
|
+
|
|
226
|
+
```xml
|
|
227
|
+
<ee:transform>
|
|
228
|
+
<ee:set-payload><![CDATA[%dw 2.0
|
|
229
|
+
import dwl::modules::salesforceUtility
|
|
230
|
+
output application/java
|
|
231
|
+
---
|
|
232
|
+
salesforceUtility::parseAndConvert(payload)
|
|
233
|
+
]]></ee:set-payload>
|
|
234
|
+
</ee:transform>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
> ⚠️ **DWL import path rule:** Always use the full path prefix `dwl::modules::moduleName`. Short paths like `modules::moduleName` will fail at runtime.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Checklist
|
|
242
|
+
|
|
243
|
+
- [ ] Entity configs externalized to `entity-config/{entity}.yaml`
|
|
244
|
+
- [ ] All connector credentials use `${secure::...}` property placeholders
|
|
245
|
+
- [ ] Reconnection strategies configured on all connectors
|
|
246
|
+
- [ ] `responseTimeout` explicitly set on HTTP request configs
|
|
247
|
+
- [ ] No hardcoded connector attribute values (hosts, ports, keys)
|
|
248
|
+
- [ ] DWL modules imported with full `dwl::modules::` path prefix
|
|
249
|
+
- [ ] Salesforce payloads pass through `parseAndConvert()` before connector call
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
**See also:** [Security](security.md) · [Performance](performance.md) · [DataWeave Patterns](dataweave-patterns.md)
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# DataWeave Best Practices
|
|
2
|
+
|
|
3
|
+
> **Applies to:** All
|
|
4
|
+
> **Related Rules:** `DW-001` · `DW-002` · `DW-003` · `DW-004` · `DW-005`
|
|
5
|
+
> **Last Updated:** April 2026
|
|
6
|
+
|
|
7
|
+
## When to Read This
|
|
8
|
+
|
|
9
|
+
Read this when writing DataWeave transformations, creating reusable DWL modules, or debugging type coercion issues with connectors.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Key Principles
|
|
14
|
+
|
|
15
|
+
1. **Externalize complex transforms** — inline DWL > 5 lines should be in a `.dwl` file
|
|
16
|
+
2. **Create reusable modules** — shared logic goes in `dwl/modules/`
|
|
17
|
+
3. **Use full import paths** — `import X from dwl::modules::Y` (short paths fail at runtime)
|
|
18
|
+
4. **Match connector type expectations** — SF connector needs Java types, not strings
|
|
19
|
+
5. **Use kebab-case for file names** — `transform-order.dwl`, not `transformOrder.dwl`
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Project Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/main/resources/dwl/
|
|
27
|
+
├── modules/ # Reusable DWL modules
|
|
28
|
+
│ ├── salesforceUtility.dwl # Type coercion for SF connector
|
|
29
|
+
│ ├── oauth1Header.dwl # OAuth 1.0 HMAC-SHA256 header generator
|
|
30
|
+
│ └── dateUtils.dwl # Shared date formatting functions
|
|
31
|
+
├── transforms/ # Entity-specific transforms
|
|
32
|
+
│ ├── sf-account-to-ns-customer.dwl
|
|
33
|
+
│ ├── salesforce-upsert-response.dwl
|
|
34
|
+
│ ├── error-payload.dwl
|
|
35
|
+
│ └── ...
|
|
36
|
+
├── lookups/ # Enum mapping modules
|
|
37
|
+
│ ├── countryMap.dwl # ISO country → target system enum
|
|
38
|
+
│ └── currencyMap.dwl # ISO currency → target system ID
|
|
39
|
+
└── dictionaries/ # Environment-specific lookup data
|
|
40
|
+
├── customer-segment-dictionary-dev.json
|
|
41
|
+
└── customer-segment-dictionary-prod.json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Patterns
|
|
47
|
+
|
|
48
|
+
### Pattern 1: External DWL Transform Files
|
|
49
|
+
|
|
50
|
+
**Use when:** any transform exceeds 5 lines of DataWeave.
|
|
51
|
+
|
|
52
|
+
```xml
|
|
53
|
+
<!-- ❌ Bad — large inline transform -->
|
|
54
|
+
<ee:transform>
|
|
55
|
+
<ee:set-payload><![CDATA[%dw 2.0
|
|
56
|
+
<!-- 50+ lines of DataWeave -->
|
|
57
|
+
]]></ee:set-payload>
|
|
58
|
+
</ee:transform>
|
|
59
|
+
|
|
60
|
+
<!-- ✅ Good — external file reference -->
|
|
61
|
+
<ee:transform doc:name="SF Account → NS Customer">
|
|
62
|
+
<ee:message>
|
|
63
|
+
<ee:set-payload resource="dwl/transforms/sf-account-to-ns-customer.dwl"/>
|
|
64
|
+
</ee:message>
|
|
65
|
+
</ee:transform>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Pattern 2: Reusable DWL Modules
|
|
69
|
+
|
|
70
|
+
**Use when:** logic is shared across multiple transforms (date formatting, type coercion, enum lookups).
|
|
71
|
+
|
|
72
|
+
```dataweave
|
|
73
|
+
// dwl/modules/dateUtils.dwl
|
|
74
|
+
%dw 2.0
|
|
75
|
+
|
|
76
|
+
fun formatDate(date: DateTime) =
|
|
77
|
+
date as String {format: "yyyy-MM-dd"}
|
|
78
|
+
|
|
79
|
+
fun formatDateTime(date: DateTime) =
|
|
80
|
+
date as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"}
|
|
81
|
+
|
|
82
|
+
fun maskPII(value: String) =
|
|
83
|
+
value[0 to 2] ++ "****" ++ value[-2 to -1]
|
|
84
|
+
|
|
85
|
+
fun toErrorResponse(error, correlationId: String) = {
|
|
86
|
+
correlationId: correlationId,
|
|
87
|
+
timestamp: now(),
|
|
88
|
+
error: error.errorType.identifier,
|
|
89
|
+
message: error.description
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Import with full path:**
|
|
94
|
+
|
|
95
|
+
```dataweave
|
|
96
|
+
%dw 2.0
|
|
97
|
+
import dwl::modules::dateUtils
|
|
98
|
+
output application/json
|
|
99
|
+
---
|
|
100
|
+
{
|
|
101
|
+
createdDate: dateUtils::formatDate(payload.CreatedDate)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> ⚠️ **Critical:** Always use `dwl::modules::moduleName` as the import path. Short paths like `modules::moduleName` will compile but **fail at runtime**.
|
|
106
|
+
|
|
107
|
+
### Pattern 3: Type Coercion for Connectors
|
|
108
|
+
|
|
109
|
+
**Use when:** passing data to the Salesforce connector (or any connector that expects Java types).
|
|
110
|
+
|
|
111
|
+
The SF connector rejects ISO datetime strings — they must be Java DateTime objects:
|
|
112
|
+
|
|
113
|
+
```dataweave
|
|
114
|
+
// dwl/modules/salesforceUtility.dwl
|
|
115
|
+
%dw 2.0
|
|
116
|
+
|
|
117
|
+
var dateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})?$/
|
|
118
|
+
var dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/
|
|
119
|
+
|
|
120
|
+
fun parseAndConvert(obj: Object) =
|
|
121
|
+
obj mapObject ((value, key) ->
|
|
122
|
+
if ((value is String) and ((value as String) matches dateTimePattern))
|
|
123
|
+
(key): (value as String) as DateTime
|
|
124
|
+
else if ((value is String) and ((value as String) matches dateOnlyPattern))
|
|
125
|
+
(key): (value as String) as Date
|
|
126
|
+
else if ((value is String) and ((value as String) == ""))
|
|
127
|
+
(key): null
|
|
128
|
+
else if (value is Object)
|
|
129
|
+
(key): parseAndConvert(value)
|
|
130
|
+
else
|
|
131
|
+
(key): value
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**When to use `output application/java`:**
|
|
136
|
+
|
|
137
|
+
```dataweave
|
|
138
|
+
%dw 2.0
|
|
139
|
+
import dwl::modules::salesforceUtility
|
|
140
|
+
output application/java // ← Required for SF connector inputs
|
|
141
|
+
---
|
|
142
|
+
salesforceUtility::parseAndConvert(payload)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use `output application/java` for transforms that feed directly into connectors (SF, DB, NetSuite SOAP). Use `output application/json` for HTTP response payloads.
|
|
146
|
+
|
|
147
|
+
### Pattern 4: Cross-System Value Mapping
|
|
148
|
+
|
|
149
|
+
**Use when:** translating enum/picklist values between systems (country codes, currency codes, payment terms, industry classifications). There are 4 strategies — choose based on your data characteristics.
|
|
150
|
+
|
|
151
|
+
#### Strategy A: Rich-Object DWL Lookup (Standardized Reference Data)
|
|
152
|
+
|
|
153
|
+
**Use when:** stable reference data (ISO countries, currencies), ≤ 300 entries, same across all environments, callers need multiple fields (`.name`, `.erp`).
|
|
154
|
+
|
|
155
|
+
```dataweave
|
|
156
|
+
// dwl/lookups/countryMap.dwl
|
|
157
|
+
%dw 2.0
|
|
158
|
+
|
|
159
|
+
fun countryMap() = {
|
|
160
|
+
"US": { "name": "United States", "erp": "_unitedStates" },
|
|
161
|
+
"CA": { "name": "Canada", "erp": "_canada" },
|
|
162
|
+
"GB": { "name": "United Kingdom", "erp": "_unitedKingdom" }
|
|
163
|
+
// 250+ entries for full ISO 3166...
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Usage — always null-guard before dereferencing:**
|
|
168
|
+
|
|
169
|
+
```dataweave
|
|
170
|
+
import countryMap from dwl::lookups::countryMap
|
|
171
|
+
---
|
|
172
|
+
{
|
|
173
|
+
(shippingCountry: countryMap()[payload.ShippingCountry].erp)
|
|
174
|
+
if (payload.ShippingCountry? and (countryMap()[payload.ShippingCountry] != null))
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Strategy B: Environment-Specific JSON Dictionary
|
|
179
|
+
|
|
180
|
+
**Use when:** target system IDs differ between dev/staging/prod (e.g., ERP sandbox has different internal IDs than production). Flat key → value mapping loaded via `readUrl`.
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
// dwl/dictionaries/customer-industry-dictionary-dev.json
|
|
184
|
+
{
|
|
185
|
+
"Education": "1",
|
|
186
|
+
"Financial Services": "13",
|
|
187
|
+
"Government": "14"
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
// dwl/dictionaries/customer-industry-dictionary-prod.json
|
|
193
|
+
{
|
|
194
|
+
"Education": "101",
|
|
195
|
+
"Financial Services": "113",
|
|
196
|
+
"Government": "114"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Load dynamically using environment property:**
|
|
201
|
+
|
|
202
|
+
```dataweave
|
|
203
|
+
var industry = readUrl(
|
|
204
|
+
"classpath://dwl/dictionaries/customer-industry-dictionary-$(p('mule.env')).json",
|
|
205
|
+
"application/json"
|
|
206
|
+
)
|
|
207
|
+
---
|
|
208
|
+
{
|
|
209
|
+
(industryId: industry[payload.Industry]) if (payload.Industry?)
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Strategy C: Bidirectional DWL Module (Enum Translation)
|
|
214
|
+
|
|
215
|
+
**Use when:** business enums must translate in both directions (CRM → ERP and ERP → CRM), ≤ 50 values, stable values (payment terms, billing frequency).
|
|
216
|
+
|
|
217
|
+
**Option 1 — Computed inverse (recommended for 1:1 mappings):**
|
|
218
|
+
|
|
219
|
+
```dataweave
|
|
220
|
+
// dwl/lookups/paymentTerms.dwl
|
|
221
|
+
%dw 2.0
|
|
222
|
+
|
|
223
|
+
// Single source of truth
|
|
224
|
+
var crmToErp = {
|
|
225
|
+
"Net 15": "1", "Net 30": "2", "Net 45": "8",
|
|
226
|
+
"Net 60": "3", "Due on receipt": "4"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Auto-computed inverse — no dual maintenance
|
|
230
|
+
var erpToCrm = crmToErp pluck ((value, key) -> { (value): key as String })
|
|
231
|
+
reduce ((item, acc = {}) -> acc ++ item)
|
|
232
|
+
|
|
233
|
+
fun mapPaymentTermToERP(crmValue: String) = crmToErp[crmValue]
|
|
234
|
+
fun mapPaymentTermToCRM(erpId: String) = erpToCrm[erpId]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Option 2 — Explicit dual dictionary (required for many-to-one):**
|
|
238
|
+
|
|
239
|
+
When 3 CRM statuses map to 1 ERP status, the computed inverse is ambiguous. Maintain both directions explicitly:
|
|
240
|
+
|
|
241
|
+
```dataweave
|
|
242
|
+
var statusMap = {
|
|
243
|
+
crmToErp: { "Open": "1", "In Progress": "1", "Closed": "2" },
|
|
244
|
+
erpToCrm: { "1": "Open", "2": "Closed" }
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Strategy D: External API / ObjectStore Cache (Dynamic Catalogs)
|
|
249
|
+
|
|
250
|
+
**Use when:** > 500 values, business users manage mappings at runtime, multiple apps share the same mapping, change frequency > quarterly.
|
|
251
|
+
|
|
252
|
+
```xml
|
|
253
|
+
<os:object-store name="product-sku-cache"
|
|
254
|
+
entryTtl="3600000"
|
|
255
|
+
expirationInterval="300000"
|
|
256
|
+
maxEntries="500"/>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Decision Tree
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
Does the mapping differ between dev/staging/prod?
|
|
263
|
+
├── YES → Strategy B (Environment-Specific JSON Dictionary)
|
|
264
|
+
└── NO
|
|
265
|
+
├── Need lookup in both directions (CRM↔ERP)?
|
|
266
|
+
│ ├── YES, ≤ 50 values → Strategy C (Bidirectional DWL)
|
|
267
|
+
│ └── YES, > 50 values → Strategy D (External)
|
|
268
|
+
└── NO (one direction only)
|
|
269
|
+
├── Need rich objects (name + ID)? → Strategy A (Rich-Object DWL)
|
|
270
|
+
└── Simple key→value? → Strategy A (flat DWL function)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Testing guidance:**
|
|
274
|
+
|
|
275
|
+
- **Completeness:** When CRM adds a new picklist value, the lookup silently returns `null`. Add MUnit tests that validate all known enum values have mappings.
|
|
276
|
+
- **Symmetry:** For 1:1 bidirectional mappings, `mapToERP(mapFromERP(x)) == x` should hold.
|
|
277
|
+
- **Environment parity:** For Strategy B, verify that dev and prod dictionary files have the **same keys** (only IDs should differ).
|
|
278
|
+
|
|
279
|
+
> ⚠️ **Always null-guard lookups** — `countryMap()[code]` returns `null` for unknown codes. Dereferencing `.erp` on `null` causes a runtime error.
|
|
280
|
+
|
|
281
|
+
### Pattern 5: Array Normalization for Connectors
|
|
282
|
+
|
|
283
|
+
**Use when:** connector operations (upsert, create, update) always expect arrays, but input may be a single object.
|
|
284
|
+
|
|
285
|
+
```dataweave
|
|
286
|
+
%dw 2.0
|
|
287
|
+
import dwl::modules::salesforceUtility
|
|
288
|
+
output application/java
|
|
289
|
+
---
|
|
290
|
+
var parsed = salesforceUtility::parseAndConvert(payload)
|
|
291
|
+
---
|
|
292
|
+
if (parsed is Array) parsed else [parsed]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Pattern 6: Error Payload Template
|
|
296
|
+
|
|
297
|
+
**Use when:** building standardized error response payloads.
|
|
298
|
+
|
|
299
|
+
```dataweave
|
|
300
|
+
// dwl/transforms/error-payload.dwl
|
|
301
|
+
%dw 2.0
|
|
302
|
+
output application/json
|
|
303
|
+
---
|
|
304
|
+
{
|
|
305
|
+
timestamp: now() as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"},
|
|
306
|
+
correlationId: vars.correlationId default "",
|
|
307
|
+
entity: vars.entity default "",
|
|
308
|
+
salesforceId: vars.salesforceId default "",
|
|
309
|
+
errorType: error.errorType.identifier default "UNKNOWN",
|
|
310
|
+
message: error.detailedDescription default error.description default "",
|
|
311
|
+
environment: p('mule.env')
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Java 17 DataWeave Considerations
|
|
318
|
+
|
|
319
|
+
Since Mule 4.9+ mandates Java 17:
|
|
320
|
+
|
|
321
|
+
- `try()` requires explicit import: `import try from dw::Runtime`
|
|
322
|
+
- `error.errorType.identifier` — use `.identifier`, not `.asString` (doesn't exist)
|
|
323
|
+
- Batch Kryo serialization — don't use `output application/java` for HTTP payloads in batch steps
|
|
324
|
+
- `DateTime` coercion is stricter in Java 17 — always test date parsing patterns
|
|
325
|
+
|
|
326
|
+
**Safe error description extraction using `try()`:**
|
|
327
|
+
|
|
328
|
+
```dataweave
|
|
329
|
+
%dw 2.0
|
|
330
|
+
import try from dw::Runtime
|
|
331
|
+
var errorDesc = try(() -> error.errorMessage.payload.errorDescription default "")
|
|
332
|
+
---
|
|
333
|
+
{
|
|
334
|
+
"Subject": "Failed to sync record",
|
|
335
|
+
"Error": if (errorDesc.success and !isBlank(errorDesc.result))
|
|
336
|
+
errorDesc.result
|
|
337
|
+
else (error.detailedDescription default "")
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
> ⚠️ Use `try()` when accessing deeply nested error properties that may not exist. Without it, accessing `error.errorMessage.payload.errorDescription` throws if `payload` is not an object.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## File Naming Convention
|
|
346
|
+
|
|
347
|
+
| Convention | Example | Use For |
|
|
348
|
+
| ------------ | ------------------------------ | -------------------------------------------- |
|
|
349
|
+
| `kebab-case` | `transform-order-response.dwl` | Transform files |
|
|
350
|
+
| `camelCase` | `salesforceUtility.dwl` | Module files (matches DWL import convention) |
|
|
351
|
+
| `kebab-case` | `country-map.dwl` | Lookup files |
|
|
352
|
+
|
|
353
|
+
> **Note:** Module filenames use `camelCase` because the DWL import path must match the filename exactly (`import dwl::modules::salesforceUtility`). Renaming to kebab-case would break imports.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Checklist
|
|
358
|
+
|
|
359
|
+
- [ ] No inline DWL exceeding 5 lines — externalize to `.dwl` files
|
|
360
|
+
- [ ] Shared logic in `dwl/modules/` with full import path
|
|
361
|
+
- [ ] `output application/java` used for connector input transforms
|
|
362
|
+
- [ ] `output application/json` used for HTTP response transforms
|
|
363
|
+
- [ ] Type coercion applied before passing data to SF connector
|
|
364
|
+
- [ ] Lookup modules always null-guarded
|
|
365
|
+
- [ ] Array normalization before connector operations
|
|
366
|
+
- [ ] `.dwl` files have Javadoc-style header comments
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
**See also:** [Connector Patterns](connector-patterns.md) · [Documentation Standards](documentation-standards.md)
|