Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changesets/1789466351-99ef89f9.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
id: 1789466351-99ef89f9
features:
- core
targets:
- terraform
type: fix
bump: patch
description: apply path parameter schema defaults in ImportState when omitted from the JSON import ID
author: AshGodfrey
date: "2026-09-15"
92 changes: 70 additions & 22 deletions templates/templates/terraform/includes/generateImportState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,41 @@ function genIsZeroValue(
return undefined;
}

function templateImportDefaultLiteral(field: FieldDef): string | undefined {
const value = field.Default?.Value;

if (value === undefined || value === null || value === "null") {
return undefined;
}

switch (field.Type.Type.toString()) {

@cubic-dev-ai cubic-dev-ai Bot Sep 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When an import ID omits an enum, formatted date, or collection field with a schema default, this switch returns undefined, so ImportState does not apply the resource's default. Reuse the schema-default type dispatch, including enum underlying types and supported formatted or collection defaults, before deciding pointer-ness.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At templates/templates/terraform/includes/generateImportState.ts, line 231:

<comment>When an import ID omits an enum, formatted date, or collection field with a schema default, this switch returns `undefined`, so ImportState does not apply the resource's default. Reuse the schema-default type dispatch, including enum underlying types and supported formatted or collection defaults, before deciding pointer-ness.</comment>

<file context>
@@ -221,6 +221,41 @@ function genIsZeroValue(
+    return undefined;
+  }
+
+  switch (field.Type.Type.toString()) {
+    case "string":
+      return typeof value === "string"
</file context>
Fix with cubic

case "string":
return typeof value === "string"
? templateBuiltinString(value)
: undefined;
case "boolean":
return typeof value === "boolean" ? String(value) : undefined;
case "int32":
case "integer":
return typeof value === "number" && Number.isInteger(value)
? String(value)
: undefined;
case "float32":
case "number":
return typeof value === "number" ? String(value) : undefined;
default:
return undefined;
}
}

function isImportPointerField(field: FieldDef): boolean {
return (
field.Optional ||
field.Nullable ||
templateImportDefaultLiteral(field) !== undefined
);
}

function validateAndSet(
valSymbol: string,
hierarchy: string[],
Expand Down Expand Up @@ -257,10 +292,11 @@ function validateAndSet(

addGenImport("github.com/hashicorp/terraform-plugin-framework/path");

const check =
field.Optional || field.Nullable
? `${curSymbol} == nil`
: genIsZeroValue(accessorType, curSymbol);
const isPointer = isImportPointerField(field);
const check = isPointer
? `${curSymbol} == nil`
: genIsZeroValue(accessorType, curSymbol);
const defaultLiteral = templateImportDefaultLiteral(field);
const frameworkType = FrameworkTypeFromFieldDef(field);
const isGlobalField =
curHierarchy.length === 1 && sanitizedFieldName in globalFields;
Expand All @@ -276,11 +312,7 @@ function validateAndSet(

if (isGlobalField) {
frameworkType
.templateTerraformToSDKImports(
field.Type,
true,
field.Optional || field.Nullable,
)
.templateTerraformToSDKImports(field.Type, true, isPointer)
.forEach((importStr) => {
addGenImport(importStr);
});
Expand All @@ -291,7 +323,7 @@ function validateAndSet(
sanitizedFieldName,
field.Type,
true,
field.Optional || field.Nullable,
isPointer,
curSymbol,
`r.${sanitizedFieldName}`,
false,
Expand All @@ -302,22 +334,38 @@ function validateAndSet(
result.push(`if ${check} {`);
}

// Only include example hint if there's a real OAS-defined example
const hasExample = field.Type.Examples?.length > 0;
const fieldName = sanitizeTFStateName(curHierarchy);
if (hasExample) {
const exampleValue = FrameworkTypeFromTypeDef(
field.Type,
).templateExampleJSONValue(field.Type);
result.push(
`resp.Diagnostics.AddError("Missing required field", \`The field ${fieldName} is required but was not found in the json encoded ID. It's expected to be a value alike '${exampleValue}'\`)`,
if (defaultLiteral !== undefined) {
const defaultVar = getPluralizedVarSymbolName(
symbolManager,
sanitizedFieldName,
"Default",
);
} else {
result.push(
`resp.Diagnostics.AddError("Missing required field", \`The field ${fieldName} is required but was not found in the json encoded ID.\`)`,
`var ${defaultVar} ${sanitizeType(
field.Type,
false,
"",
)} = ${defaultLiteral}`,
);
result.push(`${curSymbol} = &${defaultVar}`);
} else {
// Only include example hint if there's a real OAS-defined example
const hasExample = field.Type.Examples?.length > 0;
if (hasExample) {
const exampleValue = FrameworkTypeFromTypeDef(
field.Type,
).templateExampleJSONValue(field.Type);
result.push(
`resp.Diagnostics.AddError("Missing required field", \`The field ${fieldName} is required but was not found in the json encoded ID. It's expected to be a value alike '${exampleValue}'\`)`,
);
} else {
result.push(
`resp.Diagnostics.AddError("Missing required field", \`The field ${fieldName} is required but was not found in the json encoded ID.\`)`,
);
}
result.push(`return`);
}
result.push(`return`);

if (isGlobalField) {
result.push(`}`);
Expand Down Expand Up @@ -414,7 +462,7 @@ function templateImportJSONStruct(requiredAttributes: TypeDef): string {
const structFieldTag = `\`json:"${attributeName}"\``;
const structFieldType = sanitizeType(
field.Type,
field.Optional || field.Nullable,
isImportPointerField(field),
"",
);

Expand Down
75 changes: 75 additions & 0 deletions tests/specs/review-terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,64 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/FrameworkTypeResponse'
/v0/import-defaulted-id/{workspace}:
parameters:
- name: workspace
description: Path parameter with a schema default, which import should apply when the field is omitted from the JSON import ID
in: path
required: true
schema:
type: string
default: default-workspace
post:
x-speakeasy-entity-operation: ImportDefaultedId#create
description: Create a new import defaulted id resource, whose read path includes a parameter with a schema default
operationId: create-import-defaulted-id
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ImportDefaultedIdRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ImportDefaultedIdResponse'
/v0/import-defaulted-id/{workspace}/{id}:
parameters:
- name: workspace
description: Path parameter with a schema default, which import should apply when the field is omitted from the JSON import ID
in: path
required: true
schema:
type: string
default: default-workspace
- name: id
in: path
required: true
schema:
type: string
delete:
x-speakeasy-entity-operation: ImportDefaultedId#delete
description: Delete an import defaulted id resource
operationId: delete-import-defaulted-id
responses:
'200':
description: OK
get:
x-speakeasy-entity-operation: ImportDefaultedId#read
description: Get an import defaulted id resource
operationId: get-import-defaulted-id
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ImportDefaultedIdResponse'
/v0/import-id-enum-string:
post:
x-speakeasy-entity-operation: ImportIdEnumString#create
Expand Down Expand Up @@ -9148,6 +9206,23 @@ components:
string_date_time:
type: string
format: date-time
ImportDefaultedIdRequest:
type: object
additionalProperties: false
properties:
requestBodyProperty:
type: string
ImportDefaultedIdResponse:
x-speakeasy-entity: ImportDefaultedId
type: object
additionalProperties: false
properties:
id:
type: string
workspace:
type: string
requestBodyProperty:
type: string
ImportIdEnumStringRequest:
type: object
additionalProperties: false
Expand Down
59 changes: 55 additions & 4 deletions zSDKs/terraform-provider-testing/.speakeasy/gen.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
lockVersion: 2.0.0
id: review-sdk-test-id
management:
docChecksum: 4739592738991be9857d1c4474c7aa95
docChecksum: 739b2271df74f1349764ffa9be65f5d1
docVersion: 0.0.1
speakeasyVersion: internal
generationVersion: internal
Expand Down Expand Up @@ -55,6 +55,8 @@ trackedFiles:
last_write_checksum: sha1:a21b63c90ce67e7b78e103fb57e8b55ba8ab4073
examples/data-sources/testing_framework_type/data-source.tf:
last_write_checksum: sha1:5962afcf4758bc7d18e755dc83cfc85948e89f9f
examples/data-sources/testing_import_defaulted_id/data-source.tf:
last_write_checksum: sha1:4e7ccb9842e6d6200fb3dc5f67e0403fbb9b8afd
examples/data-sources/testing_import_id_enum_string/data-source.tf:
last_write_checksum: sha1:434c8e3e33f4aea19b907934669a18bda4362087
examples/data-sources/testing_import_id_int32/data-source.tf:
Expand Down Expand Up @@ -287,6 +289,12 @@ trackedFiles:
last_write_checksum: sha1:5283f92195a9546994be1b73292ba87eb5b5d245
examples/resources/testing_framework_type/resource.tf:
last_write_checksum: sha1:24d7d1eebf90ad3fc482138f7afd7372e79abaa5
examples/resources/testing_import_defaulted_id/import-by-string-id.tf:
last_write_checksum: sha1:2b14b446e1202449eca89c67d6992a008ae1d22c
examples/resources/testing_import_defaulted_id/import.sh:
last_write_checksum: sha1:629893adb37c6602bc08e37da17deefc5e624aba
examples/resources/testing_import_defaulted_id/resource.tf:
last_write_checksum: sha1:64a87a249208581d7719bcf1397c22d84daf2f32
examples/resources/testing_import_id_enum_string/import-by-string-id.tf:
last_write_checksum: sha1:a01b97f0a7718d85908f69d4e61d54984be46d23
examples/resources/testing_import_id_enum_string/import.sh:
Expand Down Expand Up @@ -775,6 +783,14 @@ trackedFiles:
last_write_checksum: sha1:d44c355b22f26fd7b534fb458b1cbe81b80e1919
internal/provider/frameworktype_resource_sdk.go:
last_write_checksum: sha1:73f3268858f1bd5ae2d73fa868e569470b6088c3
internal/provider/importdefaultedid_data_source.go:
last_write_checksum: sha1:ca89c177bc9db069d15055bb244b87b594fcefd2
internal/provider/importdefaultedid_data_source_sdk.go:
last_write_checksum: sha1:2dc7ca66d940fc08b020d99bb890cbb30dd1c982
internal/provider/importdefaultedid_resource.go:
last_write_checksum: sha1:f7d51275873502fc931e10b0125c30b584a96786
internal/provider/importdefaultedid_resource_sdk.go:
last_write_checksum: sha1:d1c195b4403002647a9ec5d0bebcdfa9367d813f
internal/provider/importidenumstring_data_source.go:
last_write_checksum: sha1:e6b068f00e651ffc130fc382eb6c1c5e52ab5963
internal/provider/importidenumstring_data_source_sdk.go:
Expand Down Expand Up @@ -1048,7 +1064,7 @@ trackedFiles:
internal/provider/patch_resource_sdk.go:
last_write_checksum: sha1:9b583e2ac47191350373ce5a86408c118bd606b1
internal/provider/provider.go:
last_write_checksum: sha1:165aa656ebff0c9e83540c0fececc3cf4ab7eeeb
last_write_checksum: sha1:11ad9098df8c62b56d5aab92cde0913a063b03e3
internal/provider/reflect/diags.go:
last_write_checksum: sha1:ace8bc53054bb1d8ee8689acf3e4323de75a6297
internal/provider/reflect/doc.go:
Expand Down Expand Up @@ -2194,7 +2210,7 @@ trackedFiles:
internal/provider/xglobals_data_source_sdk.go:
last_write_checksum: sha1:9966b8bb938807ea6bdd5a577bc18916452fc5e6
internal/provider/xglobals_resource.go:
last_write_checksum: sha1:6b02d409441f40071659402765d62205490fa98c
last_write_checksum: sha1:5b19f7ccf451a0f49e1c1d7d3c60f72ce639baca
internal/provider/xglobals_resource_sdk.go:
last_write_checksum: sha1:f3e07ecf4e9205eb56c86f9ae86fc549e0b0e88f
internal/provider/xmatch_data_source.go:
Expand Down Expand Up @@ -2419,6 +2435,8 @@ trackedFiles:
last_write_checksum: sha1:715e571da7c0da9a9bf8d4d3ae3a313f623c643a
internal/sdk/models/operations/createframeworktype.go:
last_write_checksum: sha1:1473ee200106e11a09d436f415f66f8032147fec
internal/sdk/models/operations/createimportdefaultedid.go:
last_write_checksum: sha1:60a5ba597af07cc3b40c2394c52080329b8afbb6
internal/sdk/models/operations/createimportidenumstring.go:
last_write_checksum: sha1:e6aff15e1e450a453ee5a3114b439a1f3ec6ac72
internal/sdk/models/operations/createimportidint32.go:
Expand Down Expand Up @@ -2565,6 +2583,8 @@ trackedFiles:
last_write_checksum: sha1:ab4a1348806216f6464dc195515e22003e987d60
internal/sdk/models/operations/deleteframeworktype.go:
last_write_checksum: sha1:cb1ec4b73bcc82a659f952ab412d978c3378dffb
internal/sdk/models/operations/deleteimportdefaultedid.go:
last_write_checksum: sha1:2f1db30507ebd3ceee0bc7dd0cff9c65882ae70f
internal/sdk/models/operations/deleteimportidenumstring.go:
last_write_checksum: sha1:8dfef5bf1d30e610127bd0665fbf1ff5adedbfd3
internal/sdk/models/operations/deleteimportidint32.go:
Expand Down Expand Up @@ -2703,6 +2723,8 @@ trackedFiles:
last_write_checksum: sha1:8c3bcb9baa37be554d9fc5669e0a09b6fa536ea5
internal/sdk/models/operations/getframeworktype.go:
last_write_checksum: sha1:a766fb78f32a52bcca85fcbf206712c6ed0f9f00
internal/sdk/models/operations/getimportdefaultedid.go:
last_write_checksum: sha1:8c5a2555f541b83b80f04c7cecda72da7d1f8705
internal/sdk/models/operations/getimportidenumstring.go:
last_write_checksum: sha1:d71cb665d543afccd4dfcfee12af1605c62ec001
internal/sdk/models/operations/getimportidint32.go:
Expand Down Expand Up @@ -3097,6 +3119,10 @@ trackedFiles:
last_write_checksum: sha1:883b03b0b38369abef3d5ef1877aec8efc5e9472
internal/sdk/models/shared/globalenumstring.go:
last_write_checksum: sha1:67206cfaee16690785fa23dc881543e87fa45f0f
internal/sdk/models/shared/importdefaultedidrequest.go:
last_write_checksum: sha1:b8523186abdd14ed6fe995f5a9a93e7606c0a549
internal/sdk/models/shared/importdefaultedidresponse.go:
last_write_checksum: sha1:2096ad5ee71a7f34272f50ec141422097f43a7f1
internal/sdk/models/shared/importidenumstringrequest.go:
last_write_checksum: sha1:340524eb2b7b0d43485701dcbd8cf3ce9dc49951
internal/sdk/models/shared/importidenumstringresponse.go:
Expand Down Expand Up @@ -3540,7 +3566,7 @@ trackedFiles:
internal/sdk/retry/config.go:
last_write_checksum: sha1:102d1953fbd7e9f312c4442c71ccca2eaaeaa27d
internal/sdk/sdk.go:
last_write_checksum: sha1:f160394d83562721b762d238bbef07e10d1db48e
last_write_checksum: sha1:ada266de46e1918beeeeffb83fa61d056f52dcd2
internal/sdk/types/bigint.go:
last_write_checksum: sha1:49b004005d0461fb04b846eca062b070b0360b31
internal/sdk/types/date.go:
Expand Down Expand Up @@ -5951,4 +5977,29 @@ examples:
"200":
application/json: {"name": "<value>", "kind": "alpha", "credentials": {"client_id": "<id>"}}
delete-root-union-writeonly: {}
create-import-defaulted-id:
speakeasy-default-create-import-defaulted-id:
parameters:
path:
workspace: "default-workspace"
requestBody:
application/json: {}
responses:
"200":
application/json: {}
delete-import-defaulted-id:
speakeasy-default-delete-import-defaulted-id:
parameters:
path:
workspace: "default-workspace"
id: "<id>"
get-import-defaulted-id:
speakeasy-default-get-import-defaulted-id:
parameters:
path:
workspace: "default-workspace"
id: "<id>"
responses:
"200":
application/json: {}
examplesVersion: 1.0.2
8 changes: 8 additions & 0 deletions zSDKs/terraform-provider-testing/.speakeasy/logs/naming.log
Original file line number Diff line number Diff line change
Expand Up @@ -2508,6 +2508,14 @@ GetFrameworkTypeRequest (id: string)
GetFrameworkTypeResponse (ContentType: string, StatusCode: int32, RawResponse: response ...)
UpdateFrameworkTypeRequest (id: string, FrameworkTypeRequest: FrameworkTypeRequest)
UpdateFrameworkTypeResponse (ContentType: string, StatusCode: int32, RawResponse: response ...)
CreateImportDefaultedIdRequest (workspace: string, ImportDefaultedIdRequest: ImportDefaultedIdRequest)
ImportDefaultedIdRequest (requestBodyProperty: string)
CreateImportDefaultedIdResponse (ContentType: string, StatusCode: int32, RawResponse: response ...)
ImportDefaultedIdResponse (id: string, workspace: string, requestBodyProperty: string)
DeleteImportDefaultedIdRequest (workspace: string, id: string)
DeleteImportDefaultedIdResponse (ContentType: string, StatusCode: int32, RawResponse: response)
GetImportDefaultedIdRequest (workspace: string, id: string)
GetImportDefaultedIdResponse (ContentType: string, StatusCode: int32, RawResponse: response ...)
ImportIdEnumStringRequest (empty)
CreateImportIdEnumStringResponse (ContentType: string, StatusCode: int32, RawResponse: response ...)
ImportIdEnumStringResponse (id: enum)
Expand Down
Loading
Loading