88

I am passing an uuid in using the Context and WithValue to subsequent functions that handle this *http.request. This uuid is was passed in the authorization header to a REST call to identify a person. The authorization token is verified and needs to accessible to check if the call is itself is authorized.

I used:

ctx := context.WithValue(r.Context(), string("principal_id"), *id)

But golint complains:

should not use basic type string as key in context.WithValue

What is the best option that could be used to retrieve this key that is not a basic type like a simple string?

snorberhuis
  • 3,108
  • 2
  • 22
  • 29

4 Answers4

117

Just use a key type:

type key int

const (
    keyPrincipalID key = iota
    // ...
)

Since you've defined a separate type, it will never collide. Even if you have two packages, pkg1.key(0) != pkg2.key(0).

See also: Go Blog about key collisions in context.

TheHippo
  • 61,720
  • 15
  • 75
  • 100
Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • 3
    What if I'm passing the context to a different module (where the unexported `key` is not available)? – endorama Aug 06 '19 at 17:05
  • 1
    @endorama You must provide exported getters and maybe setters. `PrincipalIDFromCtx(context.Context) (int64)` and `CtxWithPrincipalID(context.Context, int64) (context.Context)`. – Ainar-G Aug 06 '19 at 17:15
  • 1
    Should these methods go in pkg1, pkg2 or both? I see possible cycle dependencies if only in pkg1 or pkg2. Having such method in both makes sense, but that produces duplicated code. – endorama Aug 07 '19 at 19:18
18

Use type struct{} much better.

type ctxKey struct{} // or exported to use outside the package

ctx = context.WithValue(ctx, ctxKey{}, 123)
fmt.Println(ctx.Value(ctxKey{}).(int) == 123) // true

Reference: https://golang.org/pkg/context/#WithValue

The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys. To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables' static type should be a pointer or interface.

F566
  • 475
  • 4
  • 8
5

I achieve the above by doing the following and feel it's pretty clean

package util

import "context"

type contextKey string

func (c contextKey) String() string {
    return string(c)
}

var (
    // ContextKeyDeleteCaller var
    ContextKeyDeleteCaller = contextKey("deleteCaller")
    // ContextKeyJobID var
    ContextKeyJobID contextKey
)

// GetCallerFromContext gets the caller value from the context.
func GetCallerFromContext(ctx context.Context) (string, bool) {
    caller, ok := ctx.Value(ContextKeyDeleteCaller).(string)
    return caller, ok
}

// GetJobIDFromContext gets the jobID value from the context.
func GetJobIDFromContext(ctx context.Context) (string, bool) {
    jobID, ok := ctx.Value(ContextKeyJobID).(string)
    return jobID, ok
}

..and then set on context by,

ctx := context.WithValue(context.Background(), util.ContextKeyDeleteCaller, "Kafka Listener")

..get value from context by,

caller, ok := util.GetCallerFromContext(ctx)
if !ok {
    dc.log.Warn("could not get caller from context")
}
fmt.Println("value is:", caller) // will be 'Kafka Listener'

and can print out value of key by doing,

fmt.Println("Key is:", ContextKeyDeleteCaller.String())
RuNpiXelruN
  • 1,850
  • 2
  • 17
  • 23
-11

Sharing a brief answer for the above question. GitHub Link In short, context.WithValue() needs interface{} type as keys and values.

I hope this helps. Thank you.

yogesh_desai
  • 449
  • 9
  • 21