Problem
Any value that reaches a generated resource's nested-struct property without going through plain synth-time attribute assignment — a Lazy.anyValue({ produce: ... }) producer, or the raw argument to a provider-defined function call (provider::<name>::<function>) — bypasses that struct's generated xxxToTerraform mapper entirely, so it must be pre-shaped by hand (either hand-written in the Terraform schema's own snake_case, or run through the exported xxxToTerraform function yourself). This is currently undocumented in both places a user would hit it:
- Lazy producers.
concepts/constructs.mdx's only Lazy example (new Lazy({ produce: () => buckets }) as a TerraformOutput value) is a flat list-of-strings case that never needs struct mapping, so it doesn't demonstrate the requirement. api-reference/*/classes/lazy.mdx (all languages) is API-reference only, no usage guidance. concepts/tokens.mdx has no mention of Lazy, xxxToTerraform, or canInspect at all — the actual mechanism (canInspect() returns false for an unresolved token, so the struct mapper's early-return skips it, and the raw produce() result is substituted in later during whole-tree token resolution, never touching the mapper) isn't explained anywhere.
- Provider-defined functions.
concepts/functions.mdx covers only the built-in Fn.* set and explicitly scopes provider-defined functions out even of the function-availability-validation section ("Provider-defined functions (provider::<name>::<function>) are out of scope"). There's no conceptual doc for provider-defined functions at all yet, so there's nowhere this gotcha could currently be documented even if someone wanted to add it.
Concrete repro
Demonstrated in cdktn-provider-features-demo (l2-kinesis-stream): assigning a plain camelCase object as one branch of a provider::cfncompat::condition_if(...) call and passing it as a struct-typed resource attribute renders with the wrong (camelCase) keys unless it's first run through the resource's own generated xxxToTerraform mapper — e.g. kinesisStreamStreamEncryptionToTerraform({ encryptionType: "KMS", keyId: "alias/aws/kinesis" }) instead of hand-writing { encryption_type: "KMS", key_id: "alias/aws/kinesis" }. The wrapper has a real ergonomic edge too: its argument is the typed KinesisStreamStreamEncryption shape, so typos/wrong types are caught by tsc, unlike a raw any-typed provider-function argument.
Ask
- Add a
Lazy example to concepts/constructs.mdx (or tokens.mdx) that produces a nested struct property (not just a flat value), showing the required xxxToTerraform(...) wrap and briefly explaining why (canInspect()/token-resolution timing).
- Add a conceptual "Provider Functions" doc (
concepts/provider-functions.mdx or similar — functions.mdx currently only covers built-in Fn.*), including the same struct-argument gotcha, since it's the identical underlying cause.
cdktn-provider-features-demo's l2-kinesis-stream example is a live, working repro of both patterns side by side if useful as a doc example source.
Problem
Any value that reaches a generated resource's nested-struct property without going through plain synth-time attribute assignment — a
Lazy.anyValue({ produce: ... })producer, or the raw argument to a provider-defined function call (provider::<name>::<function>) — bypasses that struct's generatedxxxToTerraformmapper entirely, so it must be pre-shaped by hand (either hand-written in the Terraform schema's own snake_case, or run through the exportedxxxToTerraformfunction yourself). This is currently undocumented in both places a user would hit it:concepts/constructs.mdx's onlyLazyexample (new Lazy({ produce: () => buckets })as aTerraformOutputvalue) is a flat list-of-strings case that never needs struct mapping, so it doesn't demonstrate the requirement.api-reference/*/classes/lazy.mdx(all languages) is API-reference only, no usage guidance.concepts/tokens.mdxhas no mention ofLazy,xxxToTerraform, orcanInspectat all — the actual mechanism (canInspect()returnsfalsefor an unresolved token, so the struct mapper's early-return skips it, and the rawproduce()result is substituted in later during whole-tree token resolution, never touching the mapper) isn't explained anywhere.concepts/functions.mdxcovers only the built-inFn.*set and explicitly scopes provider-defined functions out even of the function-availability-validation section ("Provider-defined functions (provider::<name>::<function>) are out of scope"). There's no conceptual doc for provider-defined functions at all yet, so there's nowhere this gotcha could currently be documented even if someone wanted to add it.Concrete repro
Demonstrated in
cdktn-provider-features-demo(l2-kinesis-stream): assigning a plain camelCase object as one branch of aprovider::cfncompat::condition_if(...)call and passing it as a struct-typed resource attribute renders with the wrong (camelCase) keys unless it's first run through the resource's own generatedxxxToTerraformmapper — e.g.kinesisStreamStreamEncryptionToTerraform({ encryptionType: "KMS", keyId: "alias/aws/kinesis" })instead of hand-writing{ encryption_type: "KMS", key_id: "alias/aws/kinesis" }. The wrapper has a real ergonomic edge too: its argument is the typedKinesisStreamStreamEncryptionshape, so typos/wrong types are caught bytsc, unlike a rawany-typed provider-function argument.Ask
Lazyexample toconcepts/constructs.mdx(ortokens.mdx) that produces a nested struct property (not just a flat value), showing the requiredxxxToTerraform(...)wrap and briefly explaining why (canInspect()/token-resolution timing).concepts/provider-functions.mdxor similar —functions.mdxcurrently only covers built-inFn.*), including the same struct-argument gotcha, since it's the identical underlying cause.cdktn-provider-features-demo'sl2-kinesis-streamexample is a live, working repro of both patterns side by side if useful as a doc example source.