I've been using 2 SDKs that were generated by OpenAPI generator in a separate packages that share an identical code that is duplicated in different packages foo
, bar
:
package foo
// there's the exact same piece of code under another package bar
// GenericOpenAPIError Provides access to the body, error and model on returned errors.
type GenericOpenAPIError struct {
...
model interface{}
}
// Model returns the unpacked model of the error
func (e GenericOpenAPIError) Model() interface{} {
return e.model
}
// Failure Provides information about problems encountered while performing an operation.
type Failure struct {
// List of errors which caused this operation to fail
Errors []Error `json:"errors"`
}
// GetErrors returns the Errors field value
func (o *Failure) GetErrors() []Error {...}
// Error Describes a particular error encountered while performing an operation.
type Error struct {
...
// A human-readable explanation specific to this occurrence of the problem.
Detail *string `json:"detail,omitempty"`
...
}
Separately, there's an app where I use both SDKs: foo
, bar
and extract the first error from a list of errors where I try to cast an error to each of SDK's error types and have to duplicate the code because of it. Is there a way to simplify it using 1.18
that supports generics?
import (
foo "..."
bar "..."
)
func getErrorMessage(err error) (string) {
result := err.Error()
if fooError, ok1 := err.(foo.GenericOpenAPIError); ok1 {
if fooFailure, ok2 := fooError.Model().(foo.Failure); ok1 {
fooFailureErrors := fooFailure.GetErrors()
// it's guaranteed to be non-empty and .Detail != nil
result = *fooFailureErrors[0].Detail
}
}
if barError, ok1 := err.(bar.GenericOpenAPIError); ok2 {
if barFailure, ok2 := barError.Model().(bar.Failure); ok2 {
barFailureErrors := barFailure.GetErrors()
// it's guaranteed to be non-empty and .Detail != nil
result = *barFailureErrors[0].Detail
}
}
return result
}
To start, I was thinking I could redeclare these common types as interfaces in my app's code like:
type GenericOpenAPIError interface {
Model() interface{}
}
...
type Failure interface {
GetErrors() []Error
}
type DetailedError interface {
GetDetail() string
}
and then cast directly to these interfaces, is that a reasonable approach?
if mainError, ok1 := err.(GenericOpenAPIError); ok1 {
var mainErrorModel = mainError.Model()
if failure2, ok2 := mainErrorModel.(Failure); ok2 {
var ff = failure2.GetErrors()
if len(ff) > 0 {
result := ff[0].GetDetail()
}
}
}
When I try to test it, it seems like
if failure2, ok2 := mainErrorModel.(Failure); ok2 {
this casting is not successful. How can I debug it? The problem seems to be related to the face that GetErrors
is a method on pointer:
func (o *Failure) GetErrors() []Error {...}
and I don't use pointers here:
if failure2, ok2 := mainErrorModel.(Failure); ok2 {
This question seems to be related.
CodePudding user response:
You can change mainErrorModel
to struct with no pointer
with type assertion, then change that struct to struct with pointer
, and finaly you can change again to type Failure
interface with type assertion.
this is the example.
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
)
type GenericOpenAPIError interface {
Model() any
}
type Failure interface {
GetErrors() string
}
type GenericOpenAPI struct {
model any
}
func (g *GenericOpenAPI) Model() any {
return g.model
}
func (g *GenericOpenAPI) Error() string {
return "goae error"
}
type A struct {
b string
}
func (a *A) GetErrors() string {
return a.b
}
type B struct {
c string
}
func (b *B) GetErrors() string {
return b.c
}
type GetErrores interface {
A | B
}
func getErrorMessage[T GetErrores](err error) string {
if mainError, ok1 := err.(GenericOpenAPIError); ok1 {
var mainErrorModel = mainError.Model()
if t, ok := mainErrorModel.(T); ok {
if f, ok := any(&t).(Failure); ok { // must change &t to any then type assert to Failure to tell app that there is a method GetErrors()
return f.GetErrors()
}
}
}
return err.Error()
}
func main() {
var err any
err = &GenericOpenAPI{A{b: "this is error a"}}
e, ok := err.(error)
if !ok {
panic("not ok!")
}
fmt.Println(getErrorMessage[A](e))
// change model to B
err = &GenericOpenAPI{B{c: "this is error b"}}
e, ok = err.(error)
if !ok {
panic("not ok!")
}
fmt.Println(getErrorMessage[B](e))
}