Check if any variable conforms any interface using generics in Go - go

I am writing an API using go-fiber, and I want to check, if passed JSON conforms an interface that I want to see. So I decided to use 1.18's feature - generics. Here is what I did, but it does not work due to type problem.
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) (I, error) {
if err := c.BodyParser(&format); err != nil {
return nil, err
}
return c.JSON(format), nil
}
The errors say
src/endpoints/v1/tasks.go:36:10: cannot use nil as I value in return statement
src/endpoints/v1/tasks.go:39:9: cannot use c.JSON(format) (value of type error) as type I in return statement
And I want to call the function like this:
type CreateTaskDF struct {
Target string `json:"target"`
Deepness int `json:"deepness"`
}
func CreateTask(c *fiber.Ctx) error {
data, err := checkDataConformsInterface[CreateTaskDF](&CreateTaskDF{}, c)
if err != nil {
log.Fatal(err)
}
// work with data here
...
How should I convert the return value in the function to make it work? Thanks!

It probably could work like this(if you do not consider any lib-based payload validators, which exist in almost every golang routing lib or web framework). So, to just validate your data you can use this:
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) bool {
if err := c.BodyParser(&format); err != nil {
return false
}
return true
}

So I came up with the following solution
func checkDataConformsInterface[I any](format *I, c *fiber.Ctx) error {
if err := c.BodyParser(&format); err != nil {
return err
}
err := c.JSON(format)
if err != nil {
return err
}
return nil
}
which can be called like
func CreateTask(c *fiber.Ctx) error {
parsedData := CreateTaskDF{}
err := checkDataConformsInterface[CreateTaskDF](&parsedData, c)
if err != nil {
c.SendStatus(400)
return c.SendString("Wrong data")
}
Please, point me the problems if any

Related

How can I return two different concrete types from a single method in Go 1.18?

Let say that I have this code:
type Type1 struct {
Name string `json:"name,omitempty"`
Path string `json:"path"`
File string `json:"file"`
Tag int `json:"tag"`
Num int `json:"num"`
}
func LoadConfiguration(data []byte) (*Type1, error) {
config, err := loadConf1(data)
if err != nil {
return nil, err
}
confOther, err := loadConfOther1()
if err != nil {
return nil, err
}
// do something with confOther
fmt.Println("confOther", confOther)
if confOther.Tag == 0 {
config.Num = 5
}
// do something with config attributes of type1
if config.Tag == 0 {
config.Tag = 5
}
if config.Num == 0 {
config.Num = 4
}
return config, nil
}
func loadConf1(bytes []byte) (*Type1, error) {
config := &Type1{}
if err := json.Unmarshal(bytes, config); err != nil {
return nil, fmt.Errorf("cannot load config: %v", err)
}
return config, nil
}
func loadConfOther1() (*Type1, error) {
// return value of this specific type
flatconfig := &Type1{}
// read a file as []byte
// written as a fixed array to simplify this example
fileContent := []byte{10, 22, 33, 44, 55}
if err := json.Unmarshal(fileContent, flatconfig); err != nil {
return nil, fmt.Errorf("cannot read config %v", err)
}
return flatconfig, nil
}
The only public function is LoadConfiguration.
It's based on a real code and It's used to read a json data as a specific struct. If something seems useless, it's because I simplified the original code.
The code above is ok, but now I want to create another struct type called "Type2" and re-use the same methods to read data into Type2 without copying and pasting everything.
type Type2 struct {
Name string `json:"name,omitempty"`
Path string `json:"path"`
Map *map[string]interface{} `json:"map"`
Other string `json:"other"`
}
Basically, I want to be able to call LoadConfiguration to get also Type2. I can accept to call a specific method like LoadConfiguration2, but I don't want to copy and paste also loadConf1 and loadConfOther1.
Is there a way to do that in an idiomatic way in Go 1.18?
Actually the code shown in your question doesn't do anything more than passing a type into json.Unmarshal and format an error so you can rewrite your function to behave just like it:
func LoadConfiguration(data []byte) (*Type1, error) {
config := &Type1{}
if err := loadConf(data, config); err != nil {
return nil, err
}
// ...
}
// "magically" accepts any type
// you could actually get rid of the intermediate function altogether
func loadConf(bytes []byte, config any) error {
if err := json.Unmarshal(bytes, config); err != nil {
return fmt.Errorf("cannot load config: %v", err)
}
return nil
}
In case the code actually does something more than just passing a pointer into json.Unmarshal, it can benefit from type parameters.
type Configurations interface {
Type1 | Type2
}
func loadConf[T Configurations](bytes []byte) (*T, error) {
config := new(T)
if err := json.Unmarshal(bytes, config); err != nil {
return nil, fmt.Errorf("cannot load config: %v", err)
}
return config, nil
}
func loadConfOther[T Configurations]() (*T, error) {
flatconfig := new(T)
// ... code
return flatconfig, nil
}
In these cases you can create a new pointer of either type with new(T) and then json.Unmarshal will take care of deserializing the content of the byte slice or file into it — provided the JSON can be actually unmarshalled into either struct.
The type-specific code in the top-level function should still be different, especially because you want to instantiate the generic functions with an explicit concrete type. So I advise to keep LoadConfiguration1 and LoadConfiguration2.
func LoadConfiguration1(data []byte) (*Type1, error) {
config, err := loadConf[Type1](data)
if err != nil {
return nil, err
}
confOther, err := loadConfOther[Type1]()
if err != nil {
return nil, err
}
// ... type specific code
return config, nil
}
However if the type-specific code is a small part of it, you can probably get away with a type-switch for the specific part, though it doesn't seem a viable option in your case. I would look like:
func LoadConfiguration[T Configuration](data []byte) (*T, error) {
config, err := loadConf[T](data)
if err != nil {
return nil, err
}
// let's pretend there's only one value of type parameter type
// type-specific code
switch t := config.(type) {
case *Type1:
// ... some *Type1 specific code
case *Type2:
// ... some *Type2 specific code
default:
// can't really happen because T is restricted to Configuration but helps catch errors if you extend the union and forget to add a corresponding case
panic("invalid type")
}
return config, nil
}
Minimal example playground: https://go.dev/play/p/-rhIgoxINTZ

How to return a string via interface{}

I have the following function. I want to return the *string via the object interface{} parameter. If json.Unmarshal fails
I tried a bunch of variations but still it's coming out blank from the calling function. Although the type showing on the outside for the object is "string*", although it's empty. How can I do this?
My actual code below. But, for simplicity here an even simpler version.
https://go.dev/play/p/nnsKZxvU42M
// UnmarshalObject decodes an object from binary data
func UnmarshalObject(data []byte, object interface{}) error {
err := json.Unmarshal(data, object)
if err != nil {
s := string(data)
object = &s
}
return nil
}
It's being called like this
func (connection *DbConnection) GetObject(bucketName string, key []byte, object interface{}) error {
// ...
err = UnmarshalObject(data, object)
return err
}
From this function
// DBVersion retrieves the stored database version.
func (service *Service) DBVersion() (int, error) {
var version string
err := service.connection.GetObject(BucketName, []byte(versionKey), &version)
if err != nil {
return 0, err
}
return strconv.Atoi(version)
}
In this case, Atoi fails because version is ""
Generally, use a type-switch to check if the interface is any of your expected types.
func UnmarshalObject(data []byte, object interface{}) error {
err := json.Unmarshal(data, object)
if err {
return error;
}
switch object.(type) {
case string:
// object is a string
case int:
// object is an integer
default:
// object is something you did not expect.
return fmt.Errorf("Unknown type %T", v)
}
return nil;
}
I figured it out and learned something along the way. This form works.
// UnmarshalObject decodes an object from binary data
func UnmarshalObject(data []byte, object interface{}) error {
err := json.Unmarshal(data, object)
if err != nil {
if s, ok := object.(*string); !ok {
return err
}
*s = string(data)
}
return nil
}

gomock, Go,mango package ,MongoMock

I am trying to mock the below method using gomock
func GetS(tenantName string) (*mgo.Session, error) {
ctx := apiContext.TContext{}
url, err := connectionURLList.get(tenantName)
if err != nil {
log.GenericWarning(ctx,
fmt.Sprintf("connection to %s not yet created, creating one: %v", tenantName, err), nil)
if err := connectMongo(tenantName); err == nil {
return GetS(tenantName) //singleton recursion to again call GetS
}
return nil, err
}
// ignoring error, expected we will always setting session in session map
session, _ := connectionList.get(url)
return session.Copy(), err
}
My Interface
type MongoManager interface {
GetS(tenantName string)
}
func TestGetS(t *testing.T) {
//var mgoCall *mgo.Session
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockManagerObj := mocks.NewMockMongoManager(mockCtrl)
mockManagerObj.EXPECT().GetS("cacargroup").Return(nil)
}
I am Getting the below error . Can someone help
$ go test
--- FAIL: TestGetS (0.00s)
mongoManager_test.go:20: missing call(s) to *mocks.MockMongoManager.GetS(is equal to cacargroup) /Users/charles/workspace/src/bitbucket.org/tekion/tbaas/mongoManager/mongoManager_test.go:16
mongoManager_test.go:20: aborting test due to missing call(s) FAIL exit status 1
You see actually the method in your interface implemented with return type of an error. But you are using like it returns nothing and chaining the implementation. Just remove the return type of GetS.
type fn func(string) (*mgo.Session, error)
type MongoManager interface {
NewFunction(GetS, "cascade")
}
func TestGetS(t *testing.T) {
//var mgoCall *mgo.Session
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockManagerObj := mocks.NewMockMongoManager(mockCtrl)
mockManagerObj.EXPECT().GetS("cacargroup").Return(nil)
}
Also you have to remove it from GetS function too
func NewFunction(GetS fn, value string){
GetS("cascade")
}
func GetS(tenantName string) (*mgo.Session, error){
ctx := apiContext.TContext{}
url, err := connectionURLList.get(tenantName)
if err != nil {
log.GenericWarning(ctx,
fmt.Sprintf("connection to %s not yet created, creating one: %v", tenantName, err), nil)
if err := connectMongo(tenantName); err == nil {
return GetS(tenantName) //singleton recursion to again call GetS
}
return nil, err
}
// ignoring error, expected we will always setting session in session map
session, _ := connectionList.get(url)
}

Idiomatic Go Happy Path

Suppose we have a function that returns some value and an error. What's the preferred way of handling the error and value declarations?
func example_a(data interface{}) (interface{}, error) {
var err error
var bytes []byte
if bytes, err = json.Marshal(data); err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
func example_b(data interface{}) (interface{}, error) {
if bytes, err := json.Marshal(data); err != nil {
return nil, err
} else {
// ...
return use(bytes), nil
}
}
func example_c(data interface{}) (result interface{}, err error) {
var bytes []byte
if bytes, err = json.Marshal(data); err != nil {
return
}
// ...
return use(bytes), nil
}
func example_d(data interface{}) (interface{}, error) {
bytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
func example_dream(data interface{}) (interface{}, error) {
if bytes, err ≡ json.Marshal(data); err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
Example A is clear, but it adds 2 extra lines. Moreover, I find that it's unclear why in this particular case we should use var, and at the same time := is not always appropriate. Then you want to reuse the err declaration somewhere down the line, and I'm not a big fan of splitting declaration and assignment.
Example B is using the if-declare-test language feature, which I surmise is encouraged, but at the same time you are forced to nest function continuation violating the happy-path principle, which too is encouraged.
Example C uses the named parameter return feature, which is something between A and B. Biggest problem here, is that if your code base is using styles B and C, then it's easy to mistake := and =, which can cause all kinds of issues.
Example D (added from suggestions) has for me the same kind of usage problem as C, because inevitably I run into the following:
func example_d(a, b interface{}) (interface{}, error) {
bytes, err := json.Marshal(a)
if err != nil {
return nil, err
}
bytes, err := json.Marshal(b) //Compilation ERROR
if err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
So depending on previous declarations I have to modify my code to either use := or =, which makes it harder to see and refactor.
Example Dream is what I kind of intuitively would have expected from GO - no nesting, and quick exit without too much verbosity and variable reuse. Obviously it doesn't compile.
Usually use() is inlined and repeats the pattern several times, compounding the nesting or split declaration issue.
So what's the most idiomatic way of handling such multiple returns and declarations? Is there a pattern I'm missing?
If you look at lots of Go code you will find the following to be the usual case:
func example(data interface{}) (interface{}, error) {
bytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
The declare and test if construct is nice in its place, but it is not generally apropriate here.

Golang most efficient way to invoke method`s together

im looking for the most efficient way to invoke couple of method
together.
Basically what im trying to to is invoke those method together and if something went wrong return error else return the struct Type.
This code is working but i can't get the struct type or error and im not sure if its the correct way.
go func()(struct,err) {
struct,err= sm.MethodA()//return struct type or error
err = sm.MethodB()//return error or nill
return struct,err
}()
In Go, it's idiomatic to return the two values and check for nil against the error
For example:
func myFunc(sm SomeStruct) (MyStruct, error) {
s, err := sm.MethodA()
if err != nil {
return nil, err
}
if err := sm.MethodB(); err != nil {
return nil, err
}
return s, nil
}
One thing to note, is that you're running your function in a goroutine. Any return value inside that goroutine won't be returned to your main goroutine.
In order to get the return values for that go routine you must use channels that will wait for the values.
In your case
errChan := make(chan error)
retChan := make(chan SomeStructType)
go func() {
myVal, err := sm.MethodA()
if err != nil {
errChan <- err
return
}
if err := sm.MethodB(); err != nil {
errChan <- err
return
}
retChan <- myVal
}()
select {
case err := <-errChan:
fmt.Println(err)
case val := <-retChan:
fmt.Printf("My value: %v\n", val)
}
You can mess around with it here to make more sense out of it:
http://play.golang.org/p/TtfFIZerhk

Resources