如何在1.18中使用golang泛型来简化代码片段?

How can I use golang generics in 1.18 to simplify the code snippet?

我一直在使用由 OpenAPI generator 在单独的包中生成的 2 个 SDK,它们共享在不同包中重复的相同代码 foobar:

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"`
    ...
}

另外,有一个应用程序我同时使用了两个 SDK:foobar 并从错误列表中提取第一个错误,我尝试将错误转换为每个 SDK 的错误类型并且因此必须复制代码。有没有办法使用支持 generics1.18 来简化它?

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
}

首先,我想我可以在我的应用程序代码中将这些常见类型重新声明为接口,例如:

type GenericOpenAPIError interface {
    Model() interface{}
}
...
type Failure interface {
    GetErrors() []Error
}
type DetailedError interface {
    GetDetail() string
}

然后直接转换到这些接口,这样的做法合理吗?

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()
        }
    }
}

当我尝试测试它时,好像

if failure2, ok2 := mainErrorModel.(Failure); ok2 {

这次选角不成功。我该如何调试它?这个问题似乎与 GetErrors is a method on pointer 的面有关:

func (o *Failure) GetErrors() []Error {...}

而且我在这里不使用指针:

if failure2, ok2 := mainErrorModel.(Failure); ok2 {

好像有关系

您可以使用类型断言将 mainErrorModel 更改为 struct with no pointer,然后将该结构更改为 struct with pointer,最后您可以再次更改为类型 Failure 接口类型断言。

这就是例子。

// 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))
}