contextprovider

package module
v0.0.0-...-20c2f76 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2024 License: MIT Imports: 5 Imported by: 0

README

Go Reference

contextprovider

Package contextprovider enables simpler context passing without requiring a change in function signature. It also enables localized and type-safe value passing ensuring access only to the functions needing them.

Motivation

I am currently learning Go and I came across this very old blog post.

  1. Contexts are pervasive and require changes to function signature.
  2. Functions that do not care about a context still has to change its signature just so it can drill that context down another level.
  3. Intermediary functions that only pass the context down for the value consumption also has the access to those values.
  4. There's boilerplate involved in getting type-safe data back.

I wrote this package both as a way of learning by doing and also to see if the above pain points can be mitigated. This is purely an academic exercise and may look magical because it is. It uses reflect and runtime packages under the hood which adds a performance overhead. I do not have any production grade experience in writing Go so use the package at your own risk.

Provider and Receiver

Provider provides the context. Assume there are 2 functions foo(), bar(). foo is a provider which provides the context that is needed by bar.

foo would provide that context like so:

type userDefinedKey string
var key userDefinedKey =  "answer"
var answer int64 = 42

foo() {
  ctx := context.WithValue(context.Background(), key, value)
  contextprovider.Provide(ctx, bar)
}

and bar would consume that context like so:

bar() {
  ctx, ok, free := contextprovider.Inject()
}

See how there was no need to have a ctx context.Context parameter in bar?

If you only want to consume the value without context itself, there's a type-safe way of doing so:

bar() {
  val, ok, free := contextprovider.InjectValue[int64](key) // You want a int64 value associated with key
  fmt.Printf("%T %v", val, val) // int64 42
}
Example

Documentation

Overview

Package contextprovider enables simpler context passing without requiring a change in function signature. It also enables localized and type-safe value passing ensuring access only to the functions needing them.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContextValue

func ContextValue[T any](ctx context.Context, key any) (value T, ok bool)

ContextValue[T] is similar to InjectValue[T] except that you pass your own context

func FreeContext

func FreeContext(funcs ...any)

FreeContext frees the context provided for zero or more receiver functions. Usually you'd use the free functions returned by Inject / InjectValue[T] so you free it after consumption but this is sort of an escape-hatch in case you have a usecase where you want to free the context much later after consumption.

func Inject

func Inject() (ctx context.Context, ok bool, free func())

Inject should be called in the context-receiving function and it returns the context provided for it. If there's no context provided for the function, context.Background() will be returned and ok will be false. The free function should be used to clear the context if it is no longer needed.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/AmitJoki/contextprovider"
)

var timeoutInSeconds = 3

func main() {
	// This is a contrived example. You'd use top level functions in normal usage
	var provider, receiver func()
	receiver = func() {
		ctx, _, free := contextprovider.Inject()
		<-ctx.Done()
		fmt.Printf("Context cancelled after %v seconds", timeoutInSeconds)
		free()
	}
	provider = func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutInSeconds)*time.Second)
		context.AfterFunc(ctx, cancel) // to satisfy https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel
		contextprovider.Provide(ctx, receiver)
	}
	provider()
	receiver()
}
Output:

Context cancelled after 3 seconds

func InjectValue

func InjectValue[T any](key any) (value T, ok bool, free func())

InjectValue[T] should be called in the context-value-receiving function and it returns the value of type T for the given key. If there's no key and/or no value of type T associated with the key, the zero value of T will be returned and ok will be false. The free function should be used to clear the context only if there are no more values/context to be consumed.

Example
package main

import (
	"context"
	"fmt"

	"github.com/AmitJoki/contextprovider"
)

type userKey string

var loggedInKey userKey = "loggedInAt"
var value = true

func main() {
	// This is a contrived example. You'd use top level functions in normal usage
	var provider, receiver func()
	receiver = func() {
		loggedIn, _, free := contextprovider.InjectValue[bool](loggedInKey)
		fmt.Printf("Logged in: %v", loggedIn)
		free()
	}
	provider = func() {
		ctx := context.WithValue(context.Background(), loggedInKey, value)
		contextprovider.Provide(ctx, receiver)
	}
	provider()
	receiver()
}
Output:

Logged in: true

func Provide

func Provide(ctx context.Context, first any, rest ...any) error

Provide provides a non-nil context to one or more receiver functions. It returns an error if the context is nil or if any of the passed functions are not actually functions.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL