Default values and empty values for optional fields

First, some context -
I’m working against API that doesn’t have a distinction between empty (nil) values and default values. for example, If a string field won’t be fill in the creation request it will be returned as empty string at the creation (and in the read) response and not as a nil.
e.g. -

create request - {name: "name", description:""} 
 
create response - {name: "name", description:""}

but also

create request - {name: "name"} 

create response - {name: "name", description:""}

In the new terraform plugin framework there is a distinction between nil values and default values, for example nil string is not equal to empty string and nil Set is not equal to empty Set.
So it doesn’t matter which implementation I choose -

func stringToTypesString(s string) types.String {
	return types.StringValue(s)
}

Or

func stringToTypesString(s string) types.String {
	if s == "" {
		return types.StringNull()
	}
	return types.StringValue(s)
}

This will lead to an error in the provider:

Provider produced inconsistent result after apply

The first implementation will lead to an error in a case of optional field that wasn’t set, and the implementation will lead to an error in a case this field was set to default value (like empty string or empty slice).

What is your recommendation regarding working with such an API?

In a case like this, it is the provider’s job to know that the remote API does this, so that it produces a plan setting description to "" regardless of whether the user input is null or "".

It should suffice to simply set a default value of "" in your provider for the description argument.

1 Like

Before defaulting was easy I handled this situation with a validator which ensured the user-supplied string had a minimum length of 1. The user can only give null or a non-empty string.

Then, in Read() the provider writes empty strings as types.StringNull(), never saving an empty string to tfstate.

@maxb’s suggestion seems to be along the same lines, but makes null the impossible input, rather than empty string.

I wonder if there are usability implications for either of these patterns, like if an end user’s HCL populates a string based on some condition, etc…

The distinction between an empty set/list/map vs. a null set/list/map has the same implications, but may be tricker to default.

@hQnVyLRx, null is the only case where a default can be inserted by the provider, the provider is not allowed to override any configured values. Doing so would cause inconsistent plans for any resources referencing the changing value.

Providers using the legacy SDK unfortunately are allowed to do this in some cases for compatibility, so Terraform can only report the inconsistencies as warnings in the logs.

Interesting. I hadn’t thought of null as “inserted by the provider”, but rather “null is the actual configured value.”

Re-reading this thread, I think I hadn’t been drawing a clear enough distinction between “empty/null” and “default”, which of course don’t have to be the same thing.

Thanks!

In case it’s not clear, the null is not inserted by the provider, but rather it indicates the absence of a configuration value, in which case provider is allowed to insert a value if the schema is marked as computed.

From the core perspective, a “default” value is just a computed value which can be inserted during plan.

I’ve come across an interesting situation while working with an API that lacks differentiation between empty (nil) values and default values. Specifically, when utilizing the new Terraform plugin framework, I’ve noticed a clear distinction between nil values and default values. However, the API I’m working with treats both cases as empty values. This discrepancy is causing issues, as the framework expects consistency between the two. I’ve explored two different implementation approaches, one considering empty string as nil and the other treating it as a default value. However, both approaches lead to errors in certain scenarios: the first approach fails when encountering daily sports updates an optional field that wasn’t set, while the second approach triggers an error when the field is set to a default value like an empty string or an empty slice. I’m seeking recommendations and insights from the community on how best to handle this situation and maintain consistency within the Terraform workflow. Regards

I think you can just pick the behavior you like.

If you want to use null for an empty string, then record it that way during Read() and reject empty strings in the config with stringvalidator.LengthAtLeast(1).

If you want to use "" for an empty string, then record it that way during Read() and default null configurations with stringdefault.StaticString("")

I prefer the latter approach because rejecting empty string/set/list/map configurations due to input validation failures can be a hassle for whoever is writing the configs.