I'm trying refactor some code to use dependency injection for the Docker client library I use in my code. I created an interface with the method I want to be able to mock
type DockerClient interface {
Ping(context.Context) (types.Ping, error)
}
func NewDockerUtil() (*DockerUtil, error) {
var dockerClient *DockerClient
var err error
dockerClient, err = client.NewEnvClient() //Reports incompatible types in binary and unary expressions.
if err != nil {
return nil, err
}
return &DockerUtil{
Client: dockerClient,
}, nil
}
type DockerUtil struct{
Client *DockerClient
}
But when I try to assign it I get Reports incompatible types in binary and unary expressions. What exactly do I need to change?
Let's start with using interface{}, when you define interface don't use pointer definition, good read here.
type DockerUtil struct{
Client DockerClient
}
And moby Client implements lot of methods and you would like to do interface for selective methods.
Right way to do is via Type assertion. Good read Effective Go - Type assertions and Spec - Type assertion.
Note: try this code, I don't have docker env in my machine to test.
func NewDockerUtil() (*DockerUtil, error) {
dockerClient, err := client.NewEnvClient()
if err != nil {
return nil, err
}
return &DockerUtil{
Client: dockerClient.(DockerClient),
}, nil
}
Note:
Using DockerUtil.Client, you can call only Ping method since your interface DockerClient has definition of Ping method.
If you would like to call all the methods supported by client.Client later on then you have to do type assertion-
dockerClient := DockerUtil.Client.(*client.Client)
Related
I'm new to golang generics and have the following setup.
I've gathered loads of different kinds of reports.
Each report has enclosing fields
So I wrapped it in a ReportContainerImpl
I've used a type argument of [T Reportable] where the Reportable is defined as follows
type Reportable interface {
ExportDataPointReport | ImportDataPointReport | MissingDataPointReport | SensorThresoldReport
}
Each of the type in the type constraint is structs that is to be embedded in the container.
type ReportContainerImpl[T Reportable] struct {
LocationID string `json:"lid"`
Provider string `json:"pn"`
ReportType ReportType `json:"m"`
Body T `json:"body"`
}
I use a discriminator ReportType to determine the concrete type when Unmarshal.
type ReportType string
const (
ReportTypeExportDataPointReport ReportType = "ExportDataPointReport"
ReportTypeImportDataPointReport ReportType = "ImportDataPointReport"
ReportTypeMissingDataPointReport ReportType = "MissingDataPointReport"
ReportTypeSensorThresoldReport ReportType = "SensorThresoldReport"
)
Since go does not support type assertion for struct (only interfaces) it is not possible to cast the type when Unmarshal. Also go does not support pointer to the "raw" generic type. Hence, I've created a interface that the ReportContainerImpl implements.
type ReportContainer interface {
GetLocationID() string
GetProvider() string
GetReportType() ReportType
GetBody() interface{}
}
The problem I then get is that I cannot do type constrains on the return type in any form or shape and am back at "freetext semantics" on the GetBody() function to allow for type assertion when Unmarshal is done.
container, err := UnmarshalReportContainer(data)
if rep, ok := container.GetBody().(ExportDataPointReport); ok {
// Use the ReportContainerImpl[ExportDataPointReport] here...
}
Maybe I'm getting this wrong? - but however I do this, I always end up with somewhere needs a interface{} or to know the exact type before Unmarshal
Do you have a better suggestion how to solve this in a type (safer) way?
Cheers,
Mario :)
For completeness I add the UnmarshalReportContainer here
func UnmarshalReportContainer(data []byte) (ReportContainer, error) {
type Temp struct {
LocationID string `json:"lid"`
Provider string `json:"pn"`
ReportType ReportType `json:"m"`
Body *json.RawMessage `json:"body"`
}
var temp Temp
err := json.Unmarshal(data, &temp)
if err != nil {
return nil, err
}
switch temp.ReportType {
case ReportTypeExportDataPointReport:
var report ExportDataPointReport
err := json.Unmarshal(*temp.Body, &report)
return &ReportContainerImpl[ExportDataPointReport]{
LocationID: temp.LocationID,
Provider: temp.Provider,
ReportType: temp.ReportType,
Body: report,
}, err
// ...
}
}
but however I do this, I always end up with somewhere needs a interface{} or to know the exact type before Unmarshal
Precisely.
The concrete types needed to instantiate some generic type or function like ReportContainerImpl or UnmarshalReportContainer must be known at compile time, when you write the code. JSON unmarshalling instead occurs at run-time, when you have the byte slice populated with the actual data.
To unmarshal dynamic JSON based on some discriminatory value, you still need a switch.
Do you have a better suggestion how to solve this in a type (safer) way?
Just forgo parametric polymorphism. It's not a good fit here. Keep the code you have now with json.RawMessage, unmarshal the dynamic data conditionally in the switch and return the concrete structs that implement ReportContainer interface.
As a general solution — if, and only if, you can overcome this chicken-and-egg problem and make type parameters known at compile time, you can write a minimal generic unmarshal function like this:
func unmarshalAny[T any](bytes []byte) (*T, error) {
out := new(T)
if err := json.Unmarshal(bytes, out); err != nil {
return nil, err
}
return out, nil
}
This is only meant to illustrate the principle. Note that json.Unmarshal already accepts any type, so if your generic function actually does nothing except new(T) and return, like in my example, it is no different than "inlining" the entire thing as if unmarshalAny didn't exist.
v, err := unmarshalAny[SomeType](src)
functionally equivalent as
out := &SomeType{}
err := json.Unmarshal(bytes, out)
If you plan to put more logic in unmarshalAny, its usage may be warranted. Your mileage may vary; in general, don't use type parameters when it's not actually necessary.
I am trying to build an abstraction for an AWS service ( ECR ). Here is the code:
type ECR struct {
Client ecriface.ECRAPI
Ctx context.Context
}
// ECRCreate establishes aws session and creates a repo with provided input
func (e *ECR) ECRCreate(ecrInput *ecr.CreateRepositoryInput) {
result, err := e.Client.CreateRepositoryWithContext(e.Ctx, ecrInput)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ecr.ErrCodeServerException:
log.Errorln(ecr.ErrCodeServerException, aerr.Error())
case ecr.ErrCodeInvalidParameterException:
log.Errorln(ecr.ErrCodeInvalidParameterException, aerr.Error())
case ecr.ErrCodeInvalidTagParameterException:
log.Errorln(ecr.ErrCodeInvalidTagParameterException, aerr.Error())
case ecr.ErrCodeTooManyTagsException:
log.Errorln(ecr.ErrCodeTooManyTagsException, aerr.Error())
case ecr.ErrCodeRepositoryAlreadyExistsException:
log.Errorln(ecr.ErrCodeRepositoryAlreadyExistsException, aerr.Error())
case ecr.ErrCodeLimitExceededException:
log.Errorln(ecr.ErrCodeLimitExceededException, aerr.Error())
case ecr.ErrCodeKmsException:
log.Errorln(ecr.ErrCodeKmsException, aerr.Error())
default:
log.Errorln(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
log.Errorln(err.Error())
}
return
}
log.Infof("Result: %v", result)
}
To mock aws sdk create repository call:
type mockECRClient struct {
ecriface.ECRAPI
}
func (m *mockECRClient) CreateRepositoryWithContext(ctx aws.Context, input *ecr.CreateRepositoryInput, opts ...request.Option) (*ecr.CreateRepositoryOutput, error) {
createdAt := time.Now()
encryptionType := "AES256"
//awsMockAccount := "974589621236"
encryptConfig := ecr.EncryptionConfiguration{EncryptionType: &encryptionType}
imageScanConfig := input.ImageScanningConfiguration
mockRepo := ecr.Repository{
CreatedAt: &createdAt,
EncryptionConfiguration: &encryptConfig,
ImageScanningConfiguration: imageScanConfig,
}
mockRepoOuput := ecr.CreateRepositoryOutput{Repository: &mockRepo}
return &mockRepoOuput, nil
}
func TestECR_ECRCreate(t *testing.T) {
ctx := context.TODO()
mockSvc := &mockECRClient{}
scan := true
name := "Test1"
inputTest1 := ecr.CreateRepositoryInput{
RepositoryName: &name,
ImageScanningConfiguration: &ecr.ImageScanningConfiguration{ScanOnPush: &scan},
}
ecrTest := ECR{
mockSvc,
ctx,
}
ecrTest.ECRCreate(&inputTest1)
}
And this works. However, I am a bit confused around the usage of interface & composition here. ECRAPI is defined by ecriface package and I implement one of the signatures of the interface and still able to use mocked client mockSvc.
Questions:
How does this work? Is this idiomatic way of mocking interfaces?
What about the other methods of the ECRAPI interface? How are those taken care of ?
Is my understanding then correct, that we can define an interface with arbitrary number of method signatures, embed that interface into a struct and then pass the struct around where ever interface is expected. But then this means, I skip implementing other signatures of my interface?
I am thinking I am missing some important concept here, please advise!
The short answer is: this works because the function tested only calls the method you explicitly defined. It'll fail if it calls anything else.
This is how it happens:
The mockECRClient struct embeds the interface, so it has all the methods of that interface. However, to call any of those methods, you have to set that interface to an implementation:
x:=mockECRClient{}
x.ECRAPI=<real implementation of ECRAPI>
A call to x.Func() where Func is defined for ECRAPI will actually call x.ECRAPI.Func(). Since you didn't set ECRAPI, x.ECRAPI above is nil, and any method you call that uses the receiver will panic.
However, you defined a method for mockECRClient: CreateRepositoryWithContext. When you call x.CreateRepositoryWithContext, the method belongs to x and not to x.ECRAPI, and the receiver will be x, so it works.
I'm trying to create a layer on top of a third party library, in this case libchan. Here's an interface I've defined:
type ReceiverStream interface {
Receive(msg interface{}) error
}
type InboundTransport interface {
WaitReceiveChannel() (ReceiverStream, error)
}
The InboundTransport is meant to be a stand-in for a type Transport:
// libchan.go
type Transport interface {
// NewSendChannel creates and returns a new send channel. The receive
// end will get picked up on the remote end of the transport through
// the remote calling WaitReceiveChannel.
NewSendChannel() (Sender, error)
// WaitReceiveChannel waits for a new channel be created by the
// remote end of the transport calling NewSendChannel.
WaitReceiveChannel() (Receiver, error)
}
Just for context, this is the libchan.Receiver definition (please note that it matches my ReceiverStream:
// libchan.go
type Receiver interface {
// Receive receives a message sent across the channel from
// a sender on the other side of the underlying transport.
// Receive is expected to receive the same object that was
// sent by the Sender, any differences between the
// receive and send type should be handled carefully. It is
// up to the application to determine type compatibility, if
// the receive object is incompatible, Receiver will
// throw an error.
Receive(message interface{}) error
}
The Transport is returned by the libchan library here:
// libchan/session.go:62
func NewTransport(provider StreamProvider) libchan.Transport {
...
}
Since libchan.Transport and InboundTransport share a WaitReceiveChannel() (ReceiverStream, error) method, I figured I should be able to sub one for the other, like so:
func (ln SpdyListener) Accept(addr string) InboundTransport {
var listener net.Listener
var err error
listener, err = net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
c, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
p, err := spdy.NewSpdyStreamProvider(c, true)
if err != nil {
log.Fatal(err)
}
return spdy.NewTransport(p)
}
But I get an error:
cannot use spdy.NewTransport(p) (type libchan.Transport) as type InboundTransport in return argument:
libchan.Transport does not implement InboundTransport (wrong type for WaitReceiveChannel method)
have WaitReceiveChannel() (libchan.Receiver, error)
want WaitReceiveChannel() (ReceiverStream, error)
I assume that what this error means is that a type of ReceiverStream does not match libchan.Receiver, but I thought that golang interfaces were implicit, meaning that as long as the return type implements the same methods as the expected interface, it would pass compilation. Is there anything I can change so that I can superimpose a self-defined interface onto one returned by a third part library?
TLDR: A third party lib is returning an object of interface Transport. The Transport interface specifies a method WaitReceiveChannel(). I have a self-defined interface InboundTransport that also specifies WaitReceiveChannel(). The third-party method I'm calling returns an object that implements Transport by way of method WaitReceiveChannel(). I assumed that it would also implement InboundTransport since the latter also specifies a WaitReceiveChannel() of the same type. This isn't working. Why not?
As you already know interfaces in Go are satisfied implicitly.
But, as the error states,
WaitReceiveChannel() (libchan.Receiver, error)
and
WaitReceiveChannel() (ReceiverStream, error)
are two different method types, resulting in libchan.Transport not implicitly implementing InboundTransport.
To work around this you have to write a thin wrapper around libchan.Transport that implements the InboundTransport properly.
type TransportWrapper struct {
t *libchan.Transport
}
func (w *TransportWrapper) WaitReceiveChannel() (Receiver, error) {
return w.t.WaitReceiveChannel()
}
// ...
func (ln SpdyListener) Accept(addr string) InboundTransport {
var listener net.Listener
var err error
listener, err = net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
c, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
p, err := spdy.NewSpdyStreamProvider(c, true)
if err != nil {
log.Fatal(err)
}
return &TransportWrapper{spdy.NewTransport(p)}
}
Can somebody explain how can this happened?
I put interface as parameter in a function. While invoking this function I pass struct into it, but it didn't give me error. Here's the code
package main
import (
"fmt"
"github.com/myusername/gomodel/domain"
"github.com/myusername/gomodel/model"
)
func main() {
db := model.InitDB()
newFunc(db)
}
func newFunc(db domain.IUser) {
r, err := db.CreateUserTable()
if err != nil {
fmt.Println("error", err)
}
fmt.Println(r)
}
I've implemented the interface somewhere else in the code, because the program just work as the implemented interface expected to be.
IUser is an interface whose member is:
type IUser interface {
CreateUserTable() (sql.Result, error)
}
InitDB is a function to open the database and return struct of database:
type DB struct {
*sql.DB
}
//InitDB initializes the database
func InitDB() *DB {
db, err := sql.Open(dbDriver, dbName)
if err != nil {
log.Fatal("failed to initialize database: ",err)
}
err2 := db.Ping()
if err2 != nil {
log.Fatal(err2)
}
return &DB{db}
}
My question is: how can a function with a parameter type interface be passed a different type of parameter? And how is this working under the hood?
As per Golang Spec
An interface type specifies a method set called its interface. A
variable of interface type can store a value of any type with a method
set that is any superset of the interface. Such a type is said to
implement the interface.
This is because interface can be implemented as a wrapper to every type. Interface actually points to two things mainly one is the underlying type which is a struct here and other one is the value of that type which is a pointer to DB
You see newFunc is actually taking interface{} as an argument, So you can pass anything to it of type T which can be of primitive types too.
func main() {
db := model.InitDB()
newFunc(db)
}
So In case you want to get the underlying value you need to type assert. Interface works like a wrapper to the struct here and save its type and value which can be get using type assertion.
In order to determine whether a given type implements an interface using the reflect package, you need to pass a reflect.Type to reflect.Type.Implements(). How do you get one of those types?
As an example, trying to get the type of an uninitialized error (interface) type does not work (it panics when you to call Kind() on it)
var err error
fmt.Printf("%#v\n", reflect.TypeOf(err).Kind())
Do it like this:
var err error
t := reflect.TypeOf(&err).Elem()
Or in one line:
t := reflect.TypeOf((*error)(nil)).Elem()
Even Shaws response is correct, but brief. Some more details from the reflect.TypeOf method documentation:
// As interface types are only used for static typing, a common idiom to find
// the reflection Type for an interface type Foo is to use a *Foo value.
writerType := reflect.TypeOf((*io.Writer)(nil)).Elem()
fileType := reflect.TypeOf((*os.File)(nil)).Elem()
fmt.Println(fileType.Implements(writerType))
For googlers out there I just ran into the dreaded scannable dest type interface {} with >1 columns (XX) in result error.
Evan Shaw's answer did not work for me. Here is how I solved it. I am also using the lann/squirrel library, but you could easily take that out.
The solution really isn't that complicated, just knowing the magic combination of reflect calls to make.
The me.GetSqlx() function just returns an instance to *sqlx.DB
func (me *CommonRepo) Get(query sq.SelectBuilder, dest interface{}) error {
sqlst, args, err := query.ToSql()
if err != nil {
return err
}
// Do some reflection magic so that Sqlx doesn't hork on interface{}
v := reflect.ValueOf(dest)
return me.GetSqlx().Get(v.Interface(), sqlst, args...)
}
func (me *CommonRepo) Select(query sq.SelectBuilder, dest interface{}) error {
sqlst, args, err := query.ToSql()
if err != nil {
return err
}
// Do some reflection magic so that Sqlx doesn't hork on interface{}
v := reflect.ValueOf(dest)
return me.GetSqlx().Select(v.Interface(), sqlst, args...)
}
Then to invoke it you can do:
func (me *myCustomerRepo) Get(query sq.SelectBuilder) (rec Customer, err error) {
err = me.CommonRepo.Get(query, &rec)
return
}
func (me *myCustomerRepo) Select(query sq.SelectBuilder) (recs []Customer, err error) {
err = me.CommonRepo.Select(query, &recs)
return
}
This allows you to have strong types all over but have all the common logic in one place (CommonRepo in this example).