Using type assertions in Go with concrete types

As part of working to fix a bug with oapi-codegen
today, I've been trying to work out how to call the String()
method on a type, if it's set, without needing to use Reflection.
To start with, we're trying to covert a UUID
type (from Google's UUID library) to a string, from the following generated code:
// via github.com/google/uuid
type UUID = byte[16]
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// and then in oapi-codegen generated code
// PostPetTextBody defines parameters for PostPet.
type PostPetTextBody = UUID
// PostPetTextRequestBody defines body for PostPet for text/plain ContentType.
type PostPetTextRequestBody = PostPetTextBody
// NewPostPetRequestWithTextBody calls the generic PostPet builder with text/plain body
func NewPostPetRequestWithTextBody(server string, body PostPetTextRequestBody) (*http.Request, error) {
var bodyReader io.Reader
bodyReader = strings.NewReader(body)
return NewPostPetRequestWithBody(server, "text/plain", bodyReader)
}
This is failing to compile, as the byte[16]
can't be implicitly converted to a string
.
The best option here is that we'd like to write something like:
// https://pkg.go.dev/fmt#Stringer
if stringer, ok := body.(fmt.Stringer) ; ok {
bodyReader = strings.NewReader(stringer.String())
}
However, this hits a compilation error because we've got a concrete type (byte[16]
) that isn't an interface
:
./client.gen.go:130:16: invalid operation: body (variable of array type PostPetTextRequestBody) is not an interface
So how do we use a concrete type as if it's an interface
? I'm intentionally trying to avoid (expensive) Reflection calls, so that removes that as an option π€ - after a bit of noodling, I gave up and asked Claude Sonnet 3.7 Thinking, which replied with:
var bodyStr string
if stringer, ok := interface{}(body).(fmt.Stringer); ok {
bodyStr = stringer.String()
} else {
bodyStr = fmt.Sprintf("%v", body)
}
bodyReader = strings.NewReader(bodyStr)
The crux of it is this change to the code:
- if stringer, ok := body.(fmt.Stringer) ; ok {
+ if stringer, ok := interface{}(body).(fmt.Stringer); ok {
bodyReader = strings.NewReader(val.String())
}
It turns out that by wrapping the concrete type in an empty interface
, we can then check if it is an interface
type π