Unsure how to overwrite a callback to adjust unexported field - go

What I'd like to do is overwrite some boolean values on a given object, such as:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.bareSession.State.TrackRoles = false;
manager.bareSession.State.TrackPresences = false;
// more stuff happens down here
}
However bareSession is an unexported field, so I need to do this differently from what I'm gathering. I've come across some approaches using reflection but I'd like to learn the best practices approach to doing this.
In my specific case, it looks like the library I'm using offers a method to accomplish this. I've been tinkering with how to overwrite/define such a method but can't seem to figure out how to go about this.
What's the ideal approach to defining this SessionFunc() to customize the session the way I'm trying to?

I don't know the context of that library, so I'm not sure if what I'll write here makes sense for you :) But by looking at the API, SessionFunc is a func(token string) (*discordgo.Session, error), i.e., a function which receives a string and returns a Session and an error. So you can make something like this to override it:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = func(token string) (*discordgo.Session, error) {
// use "token"
// if invalid:
if token == "" {
// return an error
return nil, fmt.Errorf("invalid token")
}
// otherwise, return a valid session
return &discordgo.Session{}
}
// more stuff happens down here
}
The code is obviously very generic, but the main idea is that you need to define that function with that exact header, and implement it. I don't know how you can, for example, evaluate the token parameter or create a discordgo.Session. Or how you can configure the TrackRoles or TrackPresences values by using SessionFunc. That's very specific for that library only, but I guess it makes more sense to you than to me :)
You could also define a regular function elsewhere with that exact header:
func createNewSession(token string) (*discordgo.Session, error) {
// use "token"
// if invalid:
if token == "" {
// return an error
return nil, fmt.Errorf("invalid token")
}
// otherwise, return a valid session
return &discordgo.Session{}
}
And set it with:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = createNewSession
// more stuff happens down here
}
Both approaches work the same way.
Reflection is [almost] never the recommended way to do those things, if the library creators made those properties private, they shouldn't be changed/accessed from outside.

This will not allow you to change bareSession. But if you need to derive a new *discordgo.Session with custom parameters you can do something similar to as follows.
func MySessionFunc(m *dshardmanager.Manager) dshardmanager.SessionFunc {
return func(token string) (*discordgo.Session, error) {
//Call default Session allocator
s, err := m.StdSessionFunc(token)
if err != nil {
return nil, err
}
//Then, change its exported fields
s.State.TrackRoles = false
s.TrackPresences = false
return s, nil
}
}
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = MySessionFunc(manager)
}

Related

How can I duplicate entry keys and show it in the same log with Uber Zap?

I would like to duplicate entry keys like caller with another key name method and show both in the log...
{"level":"error", "caller: "testing/testing.go:1193", "method": "testing/testing.go:1193", "message": "foo"}
Any ideas?
You can't change the fields of a zapcore.Entry. You may change how it is marshalled, but honestly adding ghost fields to a struct is a bad hack. What you can do is use a custom encoder, and append to []zapcore.Field a new string item with a copy of the caller. In particular, the default output of the JSON encoder is obtained from Caller.TrimmedPath():
type duplicateCallerEncoder struct {
zapcore.Encoder
}
func (e *duplicateCallerEncoder) Clone() zapcore.Encoder {
return &duplicateCallerEncoder{Encoder: e.Encoder.Clone()}
}
func (e *duplicateCallerEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// appending to the fields list
fields = append(fields, zap.String("method", entry.Caller.TrimmedPath()))
return e.Encoder.EncodeEntry(entry, fields)
}
Note that the above implements Encoder.Clone(). See this for details: Why custom encoding is lost after calling logger.With in Uber Zap?
And then you can use it by either constructing a new Zap core, or by registering the custom encoder. The registered constructor embeds a JSONEncoder into your custom encoder, which is the default encoder for the production logger:
func init() {
// name is whatever you like
err := zap.RegisterEncoder("duplicate-caller", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) {
return &duplicateCallerEncoder{Encoder: zapcore.NewJSONEncoder(config)}, nil
})
// it's reasonable to panic here, since the program can't initialize
if err != nil {
panic(err)
}
}
func main() {
cfg := zap.NewProductionConfig()
cfg.Encoding = "duplicate-caller"
logger, _ := cfg.Build()
logger.Info("this is info")
}
The above replicates the initialization of a production logger with your custom config.
For such a simple config, I prefer the init() approach with zap.RegisterEncoder. It makes it faster to refactor code, if needed, and/or if you place this in some other package to begin with. You can of course do the registration in main(); or if you need additional customization, then you may use zap.New(zapcore.NewCore(myCustomEncoder, /* other args */))
You can see the full program in this playground: https://go.dev/play/p/YLDXbdZ-qZP
It outputs:
{"level":"info","ts":1257894000,"caller":"sandbox3965111040/prog.go:24","msg":"this is info","method":"sandbox3965111040/prog.go:24"}

Generic function for fetching different models in REST API

I am writing a library in Go for using the Strava API. It's a simple API to expose the various objects (athlete, activity, and so on) that make up Strava's data. I am struggling to come up with a way that separates the mechanics of making a request so it can be reused to fetch the various different objects in the API. What I have so far:
type Model interface {
Url() *url.URL
Data() interface{} // pointer to location of unmarshaled response
}
// an activity (run, bike ride, etc)
type Activity struct {
Id int64 `json:"id"`
Name string `json:"name"`
Distance float64 `json:"distance"`
// ...
}
func (a *Activity) Url() *url.URL {
return url.Parse(fmt.Sprintf("https://www.strava.com/api/v3/activities/%d", a.Id))
}
func (a *Activity) Data() interface{} {
return a
}
// gear (shoes, bike, etc)
type Gear struct {
Id string `json:"id"`
Name string `json:"name"`
}
func (g *Gear) Url() *url.URL {
return url.Parse(fmt.Sprintf("https://www.strava.com/api/v3/gear/%s", g.Id))
}
func (g *Gear) Data() interface{} {
return g
}
// a page of activities
type ActivityPage struct {
AthleteId int64
PageNum int
Activities []Activity
}
func (p *ActivityPage) Url() *url.URL {
return url.Parse(fmt.Sprintf("https://www.strava.com/api/v3/athletes/%d/activities?page=%d&per_page=%d", p.AthleteId, p.PageNum, perPage))
}
func (p *ActivityPage) Data() interface{} {
return &p.Activities
}
type Client struct {
hc *http.Client
}
// error handling omitted
func (c *Client) fetch(m Model) error {
data, _ := c.fetchUrl(m.Url())
json.Unmarshal(data, m.Data())
return nil
}
func (c *Client) fetchUrl(u *url.URL) ([]byte, error) {
req := &http.Request{
// omit access token
Method: "GET",
URL: u,
}
resp, _ := c.hc.Do(req)
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
The Data() pointer is needed because the GET /athlete/activities endpoint returns a list of Activitys, rather than a specific model. ActivityPage is therefore a bit of a hack — an object that carries the data needed to build the URL along with a place to put the results. For cases where a GET returns a model the Data() pointer is just the object.
With this code, I can do:
client := Client{}
activity := Activity{Id: 1234}
client.fetch(activity)
fmt.Print(activity.Name)
page := ActivityPage{AthleteId: 1, PageNum: 1}
client.fetch(page)
fmt.Print(len(page.Activities))
But this feels.. icky. I don't like partially constructing the object and passing it to fetch() to be finished off, or that fetch doesn't actually return anything except an error on failure. The Data() pointer is a hack.
AIUI, interfaces are a way to write code that can work with objects of different types, but I feel like I want the inverse — to have some code (a Fetch() method or something) that is inherited by all objects with a certain trait.
How can I make this cleaner? I realise this is kind of open-ended so I'm more than happy to refine what the exact question is as appropriate. Are there canonical examples of building a REST client in Go? (I haven't found anything compelling so far)
this is a standard workaround typical for go, you pass a pointer to data you want to be modified by reflection, standard lib is built like that, you can at most make method to accept pointer to data and url directly to be more verbose and not interface. It will make it at least match cleaner what will get modified just from looking at api calls for user like:
func (c *Client) fetch(url string, responceBuffer interface{}) error {
data, err := c.fetchUrl(url)
if err != nil {
return err
}
return json.Unmarshal(data, responceBuffer)
}

Bind Query inside middleware

I'm trying to write a "Binder" middleware that will validate any request query using a struct type with gin bindings/validators
So for example, let's say I have an endpoint group called /api/subject which requires the query string to have a subject code and an ID that will be validated using the following struct (called entity.Subject):
type Subject struct {
Code string `binding:"required,alphanum"`
ID string `binding:"required,alphanum,len=4"`
}
That's just one example, but I'd like to be able to pass any struct type to this middleware, because I'd like to access the query data on future handlers without worrying about query validation.
So I tried something like this:
func Binder(t reflect.Type) gin.HandlerFunc {
return func(c *gin.Context) {
obj := reflect.New(t).Elem().Interface()
if err := c.BindQuery(&obj); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Set(t.Name(), obj)
}
}
And added this middleware like so:
apiGroup := router.Group("/api")
{
// other subgroups/endpoints
// ...
subjectGroup := apiGroup.Group("/subject", middleware.Binder(reflect.TypeOf(entity.Subject{})))
}
And later on, in another handler function, let's say GetSubject, I want to access the subject data passed by doing c.MustGet("Subject").(entity.Subject)
But this isn't working =(, when I print obj, it's just an empty interface, how would I do this?
I managed to do something similar!
I created the following middleware
var allowedTypes = []binding.Binding{
binding.Query,
binding.Form,
binding.FormPost,
binding.FormMultipart,
}
func Bind(name string, data interface{}, bindingType binding.Binding) gin.HandlerFunc {
return func(ctx *gin.Context) {
ok := false
for _, b := range allowedTypes {
if b == bindingType {
ok = true
}
}
if !ok {
ctx.AbortWithError(
http.StatusInternalServerError,
fmt.Errorf("Bind function only allows %v\n", allowedTypes),
)
}
_ = ctx.MustBindWith(data, bindingType)
ctx.Set(name, data)
}
}
Remember to pass a pointer to your desired type in the call, like so:
router.GET("/something", Bind("Object", &myObject, binding.Query))
I restricted only to a few binding types because they allow ShouldBind to be called multiple times, whereas JSON, XML and others consume the Request body.
This way you can pass multiple Bind middlewares and if the validation fails it automatically aborts with http.StatusBadRequest

how to write a golang validation function so that client is able to handle elegantly?

type Request struct{
A string
B string
C string
D string
//...
}
func validator(req *Request)error{
if req.A == "" && req.B != ""{
return errors.New("Error 1 !!")
}
//...
}
I have some existing code like above which is already being used so I can not change the function signature.
I am writing a caller function which has to throttle some types of errors.
All existing errors are created using either errors.New("some string") or fmt.Errorf("some string").
What I can do is
if err.Error() == "Error 1 !!" {
return nil
}
But this is not ideal. If on the server side, the message changes, client side breaks.
I thought about naming all the errors on the server side like:
const ErrorType1 = "Error 1 !!"
But it's difficult to name each error.
Any better solutions?
error is an interface, so you can dynamically check - using type assertions - some specific types and act accordingly.
Here's a code snippet that may be useful (playground link):
package main
import (
"errors"
"fmt"
)
type MyError struct {
msg string
id int
}
func (me MyError) Error() string {
return me.msg
}
func checkErr(e error) {
if me, ok := e.(MyError); ok {
fmt.Println("found MyError", me)
} else {
fmt.Println("found error", e)
}
}
func main() {
checkErr(errors.New("something bad"))
checkErr(MyError{msg: "MyError bad"})
}
The first line in checkErr is the ticket here - it checks if e is of some special underlying type.
While the use of typed errors surely has its place and can be used, there are different approaches.
As for the validation part, I prefer not to reinvent the wheel and use [govalidator][gh:govalidator]. Custom validations can be easily added and if your validation needs are not rather complicated, it may already give you what you need.
However, as per the second part your question consists of, the elegant handling of errors, there is an alternative to an implementation of the Error interface: predefined variables which you can directly compare, as shown in the switch statement in the example program below.
package main
import (
"errors"
"log"
"github.com/asaskevich/govalidator"
)
// Error Examples
var (
NoUrlError = errors.New("Not an URL")
NotLowerCaseError = errors.New("Not all lowercase")
)
func init() {
govalidator.SetFieldsRequiredByDefault(true)
}
// Request is your rather abstract domain model
type Request struct {
A string `valid:"-"`
B string `valid:"uppercase"`
// Note the custom error message
C string `valid:"url,lowercase~ALL lowercase!!!"`
D string `valid:"length(3|10),lowercase"`
E string `valid:"email,optional"`
}
// Validate takes the whole request and validates it against the struct tags.
func (r Request) Validate() (bool, error) {
return govalidator.ValidateStruct(r)
}
// ValidC does a custom validation of field C.
func (r Request) ValidC() (bool, error) {
if !govalidator.IsURL(r.C) {
return false, NoUrlError
}
if !govalidator.IsLowerCase(r.C) {
return false, NotLowerCaseError
}
return true, nil
}
func main() {
// Setup some Requests
r1 := Request{C: "asdf"}
r2 := Request{C: "http://www.example.com"}
r3 := Request{C: "http://WWW.EXAMPLE.com"}
r4 := Request{B: "HELLO", C: "http://world.com", D: "foobarbaz", E: "you#example.com"}
for i, r := range []Request{r1, r2, r3, r4} {
log.Printf("=== Request %d ===", i+1)
log.Printf("\tValidating struct:")
// Validate the whole struct...
if isValid, err := r.Validate(); !isValid {
log.Printf("\tRequest %d is invalid:", i+1)
// ... and iterate over the validation errors
for k, v := range govalidator.ErrorsByField(err) {
log.Printf("\t\tField %s: %s", k, v)
}
} else {
log.Printf("\t\tValid!")
}
log.Println("\tValidating URL")
valid, e := r.ValidC()
if !valid {
switch e {
// Here you got your comparison against a predefined error
case NoUrlError:
log.Printf("\t\tRequest %d: No valid URL!", i)
case NotLowerCaseError:
log.Printf("\t\tRequest %d: URL must be all lowercase!", i)
}
} else {
log.Printf("\t\tValid!")
}
}
}
Imho, a custom implementation only makes sense if you want to add behavior. But then, this would first call for a custom interface and an according implementation as a secondary necessity:
package main
import (
"errors"
"log"
)
type CustomReporter interface {
error
LogSomeCoolStuff()
}
type PrefixError struct {
error
Prefix string
}
func (p PrefixError) LogSomeCoolStuff() {
log.Printf("I do cool stuff with a prefix: %s %s", p.Prefix, p.Error())
}
func report(r CustomReporter) {
r.LogSomeCoolStuff()
}
func isCustomReporter(e error) {
if _, ok := e.(CustomReporter); ok {
log.Println("Error is a CustomReporter")
}
}
func main() {
p := PrefixError{error: errors.New("AN ERROR!"), Prefix: "Attention -"}
report(p)
isCustomReporter(p)
}
Run on playground
So, in short: If you want to make sure that the user can identify the kind of error use a variable, say yourpackage.ErrSomethingWentWrong. Only if you want to ensure a behavior implement a custom interface. Creating the type just for positively identifying a semantic value is not the way to do it. Again, imho.
I do it like:
normal cases:
request is well formated and server side handle it well.
status :200, body:{"message":"success"}
client bad request:
client sent a bad request , maybe lack of args.It should be fixed by your client mate, and avoid appearing when online.
status:400, body: {"message":"error reasson"}
client normal request but not success:
maybe users use api to get award times more than default value.The request is normal but should be limit.
status:200, body: {"message":"success", "tip":"Dear,you've finish it", "tip_id":1}
server inner error:
some bugs or unnavoided error happened on server side.
status:500 body: {"message":"error_stack_trace", "error_id":"XKZS-KAKZ-SKD-ZKAQ"}
Above all, client should divide response.status into three possible values(200,400,500) and has different handle ways.
On 200 case, show anythings client want or tip.
On 400 case, show message.
On 500 case, show error_id.

Multiple values in single-value context

Due to error handling in Go, I often end up with multiple values functions. So far, the way I have managed this has been very messy and I am looking for best practices to write cleaner code.
Let's say I have the following function:
type Item struct {
Value int
Name string
}
func Get(value int) (Item, error) {
// some code
return item, nil
}
How can I assign a new variable to item.Value elegantly. Before introducing the error handling, my function just returned item and I could simply do this:
val := Get(1).Value
Now I do this:
item, _ := Get(1)
val := item.Value
Isn't there a way to access directly the first returned variable?
In case of a multi-value return function you can't refer to fields or methods of a specific value of the result when calling the function.
And if one of them is an error, it's there for a reason (which is the function might fail) and you should not bypass it because if you do, your subsequent code might also fail miserably (e.g. resulting in runtime panic).
However there might be situations where you know the code will not fail in any circumstances. In these cases you can provide a helper function (or method) which will discard the error (or raise a runtime panic if it still occurs).
This can be the case if you provide the input values for a function from code, and you know they work.
Great examples of this are the template and regexp packages: if you provide a valid template or regexp at compile time, you can be sure they can always be parsed without errors at runtime. For this reason the template package provides the Must(t *Template, err error) *Template function and the regexp package provides the MustCompile(str string) *Regexp function: they don't return errors because their intended use is where the input is guaranteed to be valid.
Examples:
// "text" is a valid template, parsing it will not fail
var t = template.Must(template.New("name").Parse("text"))
// `^[a-z]+\[[0-9]+\]$` is a valid regexp, always compiles
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
Back to your case
IF you can be certain Get() will not produce error for certain input values, you can create a helper Must() function which would not return the error but raise a runtime panic if it still occurs:
func Must(i Item, err error) Item {
if err != nil {
panic(err)
}
return i
}
But you should not use this in all cases, just when you're sure it succeeds. Usage:
val := Must(Get(1)).Value
Go 1.18 generics update: Go 1.18 adds generics support, it is now possible to write a generic Must() function:
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
This is available in github.com/icza/gog, as gog.Must() (disclosure: I'm the author).
Alternative / Simplification
You can even simplify it further if you incorporate the Get() call into your helper function, let's call it MustGet:
func MustGet(value int) Item {
i, err := Get(value)
if err != nil {
panic(err)
}
return i
}
Usage:
val := MustGet(1).Value
See some interesting / related questions:
How to pass multiple return values to a variadic function?
Return map like 'ok' in Golang on normal functions
Yes, there is.
Surprising, huh? You can get a specific value from a multiple return using a simple mute function:
package main
import "fmt"
import "strings"
func µ(a ...interface{}) []interface{} {
return a
}
type A struct {
B string
C func()(string)
}
func main() {
a := A {
B:strings.TrimSpace(µ(E())[1].(string)),
C:µ(G())[0].(func()(string)),
}
fmt.Printf ("%s says %s\n", a.B, a.C())
}
func E() (bool, string) {
return false, "F"
}
func G() (func()(string), bool) {
return func() string { return "Hello" }, true
}
https://play.golang.org/p/IwqmoKwVm-
Notice how you select the value number just like you would from a slice/array and then the type to get the actual value.
You can read more about the science behind that from this article. Credits to the author.
No, but that is a good thing since you should always handle your errors.
There are techniques that you can employ to defer error handling, see Errors are values by Rob Pike.
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
return ew.err
}
In this example from the blog post he illustrates how you could create an errWriter type that defers error handling till you are done calling write.
No, you cannot directly access the first value.
I suppose a hack for this would be to return an array of values instead of "item" and "err", and then just do
item, _ := Get(1)[0]
but I would not recommend this.
How about this way?
package main
import (
"fmt"
"errors"
)
type Item struct {
Value int
Name string
}
var items []Item = []Item{{Value:0, Name:"zero"},
{Value:1, Name:"one"},
{Value:2, Name:"two"}}
func main() {
var err error
v := Get(3, &err).Value
if err != nil {
fmt.Println(err)
return
}
fmt.Println(v)
}
func Get(value int, err *error) Item {
if value > (len(items) - 1) {
*err = errors.New("error")
return Item{}
} else {
return items[value]
}
}
Here's a generic helper function with assumption checking:
func assumeNoError(value interface{}, err error) interface{} {
if err != nil {
panic("error encountered when none assumed:" + err.Error())
}
return value
}
Since this returns as an interface{}, you'll generally need to cast it back to your function's return type.
For example, the OP's example called Get(1), which returns (Item, error).
item := assumeNoError(Get(1)).(Item)
The trick that makes this possible: Multi-values returned from one function call can be passed in as multi-variable arguments to another function.
As a special case, if the return values of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order.
This answer borrows heavily from existing answers, but none had provided a simple, generic solution of this form.

Resources