Polymorphism with struct from outside my package - go

I'm trying to build a method that, using the Kubernetes client-go library, fetches and returns the actual Resources for a given *metav1.OwnerReference. I have this:
func fetchResource(ref *metav1.OwnerReference, options *RequestOptions) (*metav1.ObjectMeta, error) {
switch ref.Kind {
case "ReplicaSet":
return options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
case "Deployment":
return options.Clientset.AppsV1().Deployments(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
case "Job":
fallthrough
// more stuff...
default:
return nil, nil
}
}
This code does not compile because:
cannot use options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, (metav1.GetOptions literal)) (value of type *"k8s.io/api/apps/v1".ReplicaSet) as *"k8s.io/apimachinery/pkg/apis/meta/v1".ObjectMeta value in return statement
My guess was that since the documentation says that basically all resources embedd the metav1.ObjectMeta, I could use it as a return type.
I tried creating and returning an interface instead, but realized I can't implement it for types outside my package:
type K8sResource interface {
Name() string
Kind() string
OwnerReferences() []metav1.OwnerReference
}
func (pod *corev1.Pod) Name() string {
return pod.Name
}
func (pod *corev1.Pod) Kind() string {
return pod.Kind
}
func (pod *corev1.Pod) OwnerReferences() []metav1.OwnerReference {
return pod.OwnerReferences
}
This code does not compile because:
invalid receiver *"k8s.io/api/core/v1".Pod (type not defined in this package)
What would be the idiomatic and correct solution here?

If you want to return the imported types as an interface that they don't already implement, you can wrap them in types that do implement it.
For example:
type K8sResource interface {
Name() string
Kind() string
OwnerReferences() []metav1.OwnerReference
}
type replicaSet struct{ *v1.ReplicaSet }
func (s replicaSet) Name() string {
return s.ReplicaSet.Name
}
func (s replicaSet) Kind() string {
return s.ReplicaSet.Kind
}
func (s replicaSet) OwnerReferences() []metav1.OwnerReference {
return s.ReplicaSet.OwnerReferences
}
func fetchResource(ref *metav1.OwnerReference, options *RequestOptions) (K8sResource, error) {
switch ref.Kind {
case "ReplicaSet":
res, err := options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return replicaSet{res}, nil // wrap it up
case "Pod":
res, err := options.Clientset.AppsV1().Pods(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return pod{res}, nil // wrap it up
case "Job":
fallthrough
// more stuff...
default:
return nil, nil
}
}

Related

Go lang type assertion

I am trying to do dependency injection in golang with applying dependency inversion principle, so I have the following service
package account
import (
types "zaClouds/modules/account/domain/types"
"zaClouds/modules/shared"
)
type IPlanDomainService interface {
GetUsagePlanById(string) *shared.Result[types.UsagePlan]
}
type PlanDomainService struct {
usagePlanService types.IUsagePlanService
}
func (planDomainService *PlanDomainService) GetUsagePlanById(id string) *shared.Result[types.UsagePlan] {
result := &shared.Result[types.UsagePlan]{}
usagePlanResult := planDomainService.usagePlanService.GetPlanById(id)
if usagePlanResult.Err != nil {
result.Err = usagePlanResult.Err
return result
}
result.Data = usagePlanResult.Data
return result
}
func PlanDomainServiceFactory(usagePlanService types.IUsagePlanService) IPlanDomainService {
return &PlanDomainService{usagePlanService: usagePlanService}
}
as you can see, it accepts another service with type IUsagePlanService
and here is the interface for it
package account
import (
"zaClouds/modules/shared"
"github.com/shopspring/decimal"
)
type UsagePlan struct {
ID string
Title string
Description interface{}
PlanID string
Price decimal.Decimal
Duration int
Features map[string]map[string]string
}
type IUsagePlanService interface {
GetPlanById(string) *shared.Result[UsagePlan]
}
and here is the way I am injecting this service to domain service
func DiInit(usagePlanService interface{}) domainServices.IPlanDomainService {
domainServices.PlanDomainServiceFactory(types.IUsagePlanService(usagePlanService))
return domainServices.PlanDomainServiceFactory(usagePlanService.(types.IUsagePlanService))
}
as you can see, I am trying to do a type assertion but it doesn't work, and gives me the following error:
panic: interface conversion: *usagePlan.UsagePlanRepository is not account.IUsagePlanService: missing method GetPlanById
Edit
Here is the actual implementation for usagePlanService
type IUsagePlanRepository interface {
createClient(string) *http.Request
GetPlanById(string) *shared.Result[usagePlanRepoModels.UsagePlan]
}
type UsagePlanRepository struct {
plansEndpoint string
httpClient *http.Client
}
func (r *UsagePlanRepository) GetPlanById(id string) *shared.Result[usagePlanRepoModels.UsagePlan] {
result := &shared.Result[usagePlanRepoModels.UsagePlan]{}
req := r.createClient(id)
resp, err := r.httpClient.Do(req)
if err != nil {
log.Println("failed to load plan details \n[ERROR]", err)
result.Err = err
return result
}
defer func() {
bodyError := resp.Body.Close()
if bodyError != nil {
result.Err = bodyError
}
}()
if result.Err != nil {
return result
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
utils.Logger.Error("failed to load plan details \n[ERROR]", err, nil)
result.Err = err
return result
}
if resp.StatusCode >= 400 {
result.Err = errors.New(string(body))
utils.Logger.Info("getPlanById", string(body))
}
getUsagePlanResponse, foundError := usagePlanRepoModels.CreateGetUsagePlanResponse(body)
if foundError != nil {
result.Err = foundError
return result
}
result.Data = *getUsagePlanResponse
return result
}
When using an interface, you need to define all functions that you will use with the same name and signature as the implementation.
The error message you got indicates that the implementation and the interface are different.
The implementation is not shown in your question, but you defined the function for your interface like this: GetPlanById(string) *shared.Result[UsagePlan]. Any deviation from it will result in error. One common mistake is with the pointers. Adding or removing the * to the return type will incur in error if it differs from the original.
Edit:
Your interface should look like this:
type IUsagePlanService interface {
GetPlanById(id string) *shared.Result[usagePlanRepoModels.UsagePlan]
}
If your function is returning a private type, and you can change that, you should. If you cant change it, than you should create a function that wraps the function you are trying to abstract with the interface.

How does Golang accept uncertain values

Back-end return values are not fixed, sometimes:
{"application": {"instance": [{"instanceId": "v1"}, {"instanceId": "v2"}]}}
or sometimes:
{"application": {"instance": {"instanceId": "v"}}}
how should I take out the corresponding instanceId value?
package main
import (
"encoding/json"
"fmt"
)
type Application struct {
Application struct {
Instance json.RawMessage `json:"instance"`
} `json:"application"`
}
func main() {
a := `{"application": {"instance": {"instanceId": "v"}}}`
//a := `{"application": {"instance": [{"instanceId": "v1"}, {"instanceId": "v2"}]}} `
var p Application
errJson := json.Unmarshal([]byte(a), &p)
if errJson != nil {
fmt.Printf("errJson")
}
fmt.Printf("type:%T", p.Application.Instance)
}
Since the 2 value types clash (one a struct another a slice of structs) it gets messy to encapsulate this into a single type even using catch-all solutions like interface{}.
The simplest solution is present two distinct types and marshal into either to see which "works":
func unmarsh(body []byte) (*type1, *type2, error) {
var (
t1 type1
t2 type2
)
err := json.Unmarshal(body, &t1)
if err == nil {
return &t1, nil, nil
}
err = json.Unmarshal(body, &t2)
if err == nil {
return nil, &t2, nil
}
return nil, nil, err
}
and in your example the two types would be:
type type1 struct {
Application struct {
Instance []struct {
InstanceID string `json:"instanceId"`
} `json:"instance"`
} `json:"application"`
}
type type2 struct {
Application struct {
Instance struct {
InstanceID string `json:"instanceId"`
} `json:"instance"`
} `json:"application"`
}
Working example:
https://play.golang.org/p/Kma32gWfghb
A cleaner solution would be a custom unmarshaler:
type Instances []Instance
func (i *Instances) UnmarshalJSON(in []byte) error {
if len(in)>0 && in[0]=='[' {
var a []Instance
if err:=json.Unmarshal(in,&a); err!=nil {
return err
}
*i=a
return nil
}
var s Instance
if err:=json.Unmarshal(in,&s) ; err!=nil {
return err
}
*i=[]Instance{s}
return nil
}
This would unmarshal an object into a slice of 1.
A more compact solution is provided by #mkopriva:
func (i *Instances) UnmarshalJSON(in []byte) error {
if len(in) > 0 && in[0] == '[' {
return json.Unmarshal(in, (*[]Instance)(i))
}
*i = Instances{{}}
return json.Unmarshal(in, &(*i)[0])
}

How to create a mock inside the function under test

I have a set of functions, which uses the pool of objects. This pool has been mocked. It works fine in most of the cases. But in some functions i call the methods of objects from the pool. So i need to mock this objects too.
Lets say:
// ObjectGeter is a interface that is mocked
type ObjectGeter interface {
GetObject(id int) ObjectType, error
}
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
obj, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := obj.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
Is there a way to test whole this function? I can create interface SomeMethoder which wraps the SomeMethod(), but i can't find the way how to assign it to obj inside SomeFunc without changing the signature of GetObject to GetObject(id int) SomeMethoder,error.
Currently i see the one approach - testing by a parts.
The only solution i'v found without of changing of paradigm is a wrapper. It is pretty trivial but may be some one will need it once.
Originally i have some type:
type PoolType struct {...}
func (p *PoolType)GetObject(id int) (ObjectType, error) {...}
and interface, that wraps PoolType.GetObject and that i'v mocked.
Now i have the interface:
type SomeMethoder interface {
SomeMethod() (ResultType, error)
}
to wrap object returned by PoolType.GetObject().
To produce it i have interface:
type ObjectGeter interface {
GetObject(id int) (SomeMethoder, error)
}
and type
type MyObjectGeter struct {
pool *PoolType
}
func New(pool *PoolType) *MyObjectGeter {
return &MyObjectGeter{pool: pool}
}
func (p *MyObjectGeter)GetObject(id int) (SomeMethoder, error) {
return p.pool.GetObject(id)
}
that implements it.
So:
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
iface, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := iface.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
is called by
og := New(pool)
SomeFunc(og,id,otherArgument)
in real work.
After all to test whole SomeFunc i have to:
func TestSomeFuncSuccess (t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
objectGeter := mocks.NewMockObjectGeter(controller)
someMethoder := mocks.NewMockSomeMethoder(controller)
gomock.InOrder(
args.objectGeter.EXPECT().
GetObject(correctIdCOnst).
Return(someMethoder, nil),
args.someMethoder.EXPECT().
SomeMethod().
Return(NewResultType(...),nil).
Times(args.test.times[1]),
)
result, err := SomeFunc(objectGeter,correctIdCOnst,otherArgumentConst)
// some checks
}
So, the only untested part is MyObjectGeter.GetObject that is enough for me.

Strange behaviour when Unmarshalling into struct in Go

I'm developing a tool that can be implemented to simplify the process of creating simple CRUD operations/endpoints. Since my endpoints don't know what kind of struct they'll be receiving, I've created an interface that users can implement, and return an empty object to be filled.
type ItemFactory interface {
GenerateEmptyItem() interface{}
}
And the users would implement something like:
type Test struct {
TestString string `json:"testString"`
TestInt int `json:"testInt"`
TestBool bool `json:"testBool"`
}
func (t Test) GenerateEmptyItem() interface{} {
return Test{}
}
When the Test object gets created, its type is "Test", even though the func returned an interface{}. However, as soon as I try to unmarshal some json of the same format into it, it strips it of its type, and becomes of type "map[string]interface {}".
item := h.ItemFactory.GenerateEmptyItem()
//Prints "Test"
fmt.Printf("%T\n", item)
fmt.Println(reflect.TypeOf(item))
err := ConvertRequestBodyIntoObject(r, &item)
if err != nil {...}
//Prints "map[string]interface {}"
fmt.Printf("%T\n", item)
Func that unmarshalls item:
func ConvertRequestBodyIntoObject(request *http.Request, object interface{}) error {
body, err := ioutil.ReadAll(request.Body)
if err != nil {
return err
}
// Unmarshal body into request object
err = json.Unmarshal(body, object)
if err != nil {
return err
}
return nil
}
Any suggestions as to why this happens, or how I can work around it?
Thanks
Your question lacks an example showing this behavior so I'm just guessing this is what is happening.
func Generate() interface{} {
return Test{}
}
func GeneratePointer() interface{} {
return &Test{}
}
func main() {
vi := Generate()
json.Unmarshal([]byte(`{}`), &vi)
fmt.Printf("Generate Type: %T\n", vi)
vp := GeneratePointer()
json.Unmarshal([]byte(`{}`), vp)
fmt.Printf("GenerateP Type: %T\n", vp)
}
Which outputs:
Generate Type: map[string]interface {}
GenerateP Type: *main.Test
I suggest you return a pointer in GenerateEmptyItem() instead of the actual struct value which is demonstrated in the GenerateP() example.
Playground Example

Golang, Go : implicitly calling interface function?

http://play.golang.org/p/xjs-jwMsr7
I have this function
func (e *MyError) Error() string {
return fmt.Sprintf("AT %v, %s", e.When, e.What)
}
But
as you see below, I never called it but how come it is called in the final output?
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("AT %v, %s", e.When, e.What)
}
func run() error {
return &MyError{
time.Now(), "it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
fmt.Println and the other functions in pkg/fmt analyze the objects passed to it.
If it is an error, the function calls .Error() on the passed object and prints the string
returned by Error().
See the source for details. The code says:
switch v := p.field.(type) {
case error:
// ...
p.printField(v.Error(), verb, plus, false, depth)
return
// ...
}
The type of the passed object is checked in a type switch statement and in case of the object
implementing the error interface, v.Error() is used as value.

Resources