Improve godoc for contexts

This commit is contained in:
Matthew Holt 2019-05-17 08:48:12 -06:00
parent 1f0c061ce3
commit 1a20fe330e
2 changed files with 58 additions and 12 deletions

View File

@ -10,12 +10,30 @@ import (
"github.com/mholt/certmagic" "github.com/mholt/certmagic"
) )
// Context is a type which defines the lifetime of modules that
// are loaded and provides access to the parent configuration
// that spawned the modules which are loaded. It should be used
// with care and only wrapped with derivation functions from
// the standard context package if you don't need the Caddy
// specific features. These contexts are cancelled when the
// lifetime of the modules loaded from it are over.
//
// Use NewContext() to get a valid value (but most modules will
// not actually need to do this).
type Context struct { type Context struct {
context.Context context.Context
moduleInstances map[string][]interface{} moduleInstances map[string][]interface{}
cfg *Config cfg *Config
} }
// NewContext provides a new context derived from the given
// context ctx. Normally, you will not need to call this
// function unless you are loading modules which have a
// different lifespan than the ones for the context the
// module was provisioned with. Be sure to call the cancel
// func when the context is to be cleaned up so that
// modules which are loaded will be properly unloaded.
// See standard library context package's documentation.
func NewContext(ctx Context) (Context, context.CancelFunc) { func NewContext(ctx Context) (Context, context.CancelFunc) {
newCtx := Context{moduleInstances: make(map[string][]interface{}), cfg: ctx.cfg} newCtx := Context{moduleInstances: make(map[string][]interface{}), cfg: ctx.cfg}
c, cancel := context.WithCancel(ctx.Context) c, cancel := context.WithCancel(ctx.Context)
@ -36,6 +54,14 @@ func NewContext(ctx Context) (Context, context.CancelFunc) {
return newCtx, wrappedCancel return newCtx, wrappedCancel
} }
// LoadModule decodes rawMsg into a new instance of mod and
// returns the value. If mod.New() does not return a pointer
// value, it is converted to one so that it is unmarshaled
// into the underlying concrete type. If mod.New is nil, an
// error is returned. If the module implements Validator or
// Provisioner interfaces, those methods are invoked to
// ensure the module is fully configured and valid before
// being used.
func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) { func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
modulesMu.Lock() modulesMu.Lock()
mod, ok := modules[name] mod, ok := modules[name]
@ -74,7 +100,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
} }
if validator, ok := val.(Validator); ok { if validator, ok := val.(Validator); ok {
err := validator.Validate(ctx) err := validator.Validate()
if err != nil { if err != nil {
if cleanerUpper, ok := val.(CleanerUpper); ok { if cleanerUpper, ok := val.(CleanerUpper); ok {
err2 := cleanerUpper.Cleanup() err2 := cleanerUpper.Cleanup()
@ -91,6 +117,17 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
return val, nil return val, nil
} }
// LoadModuleInline loads a module from a JSON raw message which decodes
// to a map[string]interface{}, where one of the keys is moduleNameKey
// and the corresponding value is the module name as a string, which
// can be found in the given scope.
//
// This allows modules to be decoded into their concrete types and
// used when their names cannot be the unique key in a map, such as
// when there are multiple instances in the map or it appears in an
// array (where there are no custom keys). In other words, the key
// containing the module name is treated special/separate from all
// the other keys.
func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) { func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
moduleName, err := getModuleNameInline(moduleNameKey, raw) moduleName, err := getModuleNameInline(moduleNameKey, raw)
if err != nil { if err != nil {

View File

@ -136,23 +136,32 @@ func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, err
return moduleName, nil return moduleName, nil
} }
// Validator is implemented by modules which can verify that their
// configurations are valid. This method will be called after New()
// instantiations of modules (if implemented). Validation should
// always be fast (imperceptible running time) and an error should
// be returned only if the value's configuration is invalid.
type Validator interface {
Validate(Context) error
}
// Provisioner is implemented by modules which may need to perform // Provisioner is implemented by modules which may need to perform
// some additional "setup" steps immediately after being loaded. // some additional "setup" steps immediately after being loaded.
// This method will be called after Validate() (if implemented). // Provisioning should be fast (imperceptible running time). If
// any side-effects result in the execution of this function (e.g.
// creating global state, any other allocations which require
// garbage collection, opening files, starting goroutines etc.),
// be sure to clean up properly by implementing the CleanerUpper
// interface to avoid leaking resources.
type Provisioner interface { type Provisioner interface {
Provision(Context) error Provision(Context) error
} }
// TODO: different name... // Validator is implemented by modules which can verify that their
// configurations are valid. This method will be called after
// Provision() (if implemented). Validation should always be fast
// (imperceptible running time) and an error should be returned only
// if the value's configuration is invalid.
type Validator interface {
Validate() error
}
// CleanerUpper is implemented by modules which may have side-effects
// such as opened files, spawned goroutines, or allocated some sort
// of non-local state when they were provisioned. This method should
// deallocate/cleanup those resources to prevent memory leaks. Cleanup
// should be fast and efficient.
type CleanerUpper interface { type CleanerUpper interface {
Cleanup() error Cleanup() error
} }