Skip to content

Go SDK: NullableString awkwardness #1198

Closed
@nickstenning

Description

@nickstenning

The way that the Go SDK handles optional strings is somewhat awkward, and while this ship may have sailed, @tasn asked me to provide some feedback here.

First of all, I totally understand that there's a need for a NullableString type of some kind. While idiomatic Go loves to use "zero values" including the empty string, other languages and APIs don't always work well with that, and so a string type that can also represent unset/JSON null is necessary.

But the implementation and use of NullableString in the Go SDK is awkward. Perhaps the most awkward aspect is that the helper function svix.NullableString a) takes a *string rather than a string, and b) returns not an openapi.NullableString but a *openapi.NullableString.

That means that the only way to create an openapi.NullableString from a static string literal in a single line, is the rather unwieldy *svix.NullableString(svix.String("mystring")).

The problem is compounded by the fact that openapi.NullableString is a private type, and as a result it is impossible to write 3rd-party code that manipulates it directly:

// This will not compile because the openapi package is internal.
func nullableString(s string) openapi.NullableString {
	return *svix.NullableString(&s)
}

What's particularly odd about the choice to return a pointer from svix.NullableString is that the data types that need nullable strings only ever want an openapi.NullableString, not a pointer to one, which leads to code like:

appIn := svix.ApplicationIn{
	...
	Uid: *svix.NullableString(svix.String("myuid"))
}

Suggested alternative

I think a much more Go-friendly implementation here would be to make the type something like database/sql's NullString (https://pkg.go.dev/database/sql#NullString).

package types

type NullableString {
	String string
	Valid bool
}

and then define your helper function as

package svix

func NullableString(s string) types.NullableString {
	return types.NullableString{
		String: s,
		Valid: true,
	}
}

That creates a situation where the zero-valued types.NullableString is null, because the zero value of Valid is false:

type ApplicationIn struct {
	Uid types.NullableString
}

app := ApplicationIn{}

fmt.Printf("%v\n", app.Uid.Valid) // Prints false

And constructing one to pass into a type is equally straightforward:

app := ApplicationIn{
	Uid: NullableString("my-app-id"),
}

fmt.Printf("%v\n", app.Uid.Valid) // Prints true

Here's another example to play around with: https://go.dev/play/p/jYGq7qCCC11

Metadata

Metadata

Assignees

Labels

lib/goGo client library

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions