Как проверить, что возвращаемое значение функции удовлетворяет интерфейсу ошибки - PullRequest
1 голос
/ 03 марта 2020

Я хотел бы написать некоторый код, который проверяет методы структуры и делает на них определенные утверждения, например, что последнее, что они возвращают, должно быть error. Я пробовал следующий пример сценария:

import (
    "context"
    "reflect"
)

type Service struct {
    name string
}

func (svc *Service) Handle(ctx context.Context) (string, error) {
    return svc.name, nil
}

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()

        f.Out(f.NumOut() - 1).Implements(reflect.TypeOf(error))
    }
}

Однако, это дает

./main.go:23:51: type error is not an expression

Что вместо этого компилируются следующие две строки в конце:

    var err error
    f.Out(f.NumOut() - 1).Implements(reflect.TypeOf(err))

Однако это приводит к пани c:

panic: reflect: nil type passed to Type.Implements

Как правильно проверить, что последние аргументы реализуют интерфейс error? Другими словами, как мне получить reflect.Type интерфейса error?

Ответы [ 3 ]

3 голосов
/ 03 марта 2020

Если последнее возвращаемое значение "должно быть" и error не используют Implements, этого недостаточно, x реализует e не совпадает с x is e.

Просто проверьте имя типа и путь к пакету. Для предварительно объявленных типов, включая error, путь пакета представляет собой пустую строку.


Тип без ошибок, который реализует error.

type Service struct {
    name string
}

type sometype struct {}

func (sometype) Error() string { return "" }

func (svc *Service) Handle(ctx context.Context) (string, sometype) {
    return svc.name, sometype{}
}

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()
        rt := f.Out(f.NumOut() - 1)
        fmt.Printf("implements error? %t\n", rt.Implements(reflect.TypeOf((*error)(nil)).Elem()))
        fmt.Printf("is error? %t\n", rt.Name() == "error" && rt.PkgPath() == "")
    }
}

This выводит :

implements error? true
is error? false

Локально объявленный тип с именем error, который не реализует встроенный error.

type Service struct {
    name string
}

type error interface { Abc() }

func (svc *Service) Handle(ctx context.Context) (string, error) {
    return svc.name, nil
}

type builtin_error interface { Error() string }

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()
        rt := f.Out(f.NumOut() - 1)
        fmt.Printf("implements error? %t\n", rt.Implements(reflect.TypeOf((*builtin_error)(nil)).Elem()))
        fmt.Printf("is error? %t\n", rt.Name() == "error" && rt.PkgPath() == "")
    }
}

Это выходы :

implements error? false
is error? false

Фактический встроенный error.

type Service struct {
    name string
}

func (svc *Service) Handle(ctx context.Context) (string, error) {
    return svc.name, nil
}

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()
        rt := f.Out(f.NumOut() - 1)
        fmt.Printf("implements error? %t\n", rt.Implements(reflect.TypeOf((*error)(nil)).Elem()))
        fmt.Printf("is error? %t\n", rt.Name() == "error" && rt.PkgPath() == "")
    }
}

Это выводит :

implements error? true
is error? true
2 голосов
/ 03 марта 2020

Чтобы получить reflect.TypeOf из error без использования существующей ошибки, вы можете использовать эту однострочную строку:

reflect.TypeOf((*error)(nil)).Elem()

По сути, сначала он получает тип указателя на ошибку (*error), а затем Elem() «отклоняет» TypeOf от типа error.

Playground

2 голосов
/ 03 марта 2020

Используйте указатель на интерфейс и получите Elem, например:

f.Out(f.NumOut() - 1).Implements(reflect.TypeOf((*error)(nil)).Elem())
...