-
Notifications
You must be signed in to change notification settings - Fork 170
/
middleware.go
148 lines (119 loc) · 4.01 KB
/
middleware.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package middleware
import (
"context"
"errors"
"fmt"
"log"
"slices"
"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/pkg/ioc"
"github.com/spf13/pflag"
)
// Registration function that returns a constructed middleware
type ResolveFn func() Middleware
// Defines a middleware component
type Middleware interface {
Run(ctx context.Context, nextFn NextFn) (*actions.ActionResult, error)
}
type childActionKeyType string
var childActionKey childActionKeyType = "child-action"
// Middleware Run options
type Options struct {
container *ioc.NestedContainer
CommandPath string
Name string
Aliases []string
Flags *pflag.FlagSet
Args []string
}
func (o *Options) IsChildAction(ctx context.Context) bool {
value, ok := ctx.Value(childActionKey).(bool)
return ok && value
}
// Sets the container to be used for resolving middleware components
func (o *Options) WithContainer(container *ioc.NestedContainer) {
o.container = container
}
// Executes the next middleware in the command chain
type NextFn func(ctx context.Context) (*actions.ActionResult, error)
// Middleware runner stores middleware registrations and orchestrates the
// invocation of middleware components and actions.
type MiddlewareRunner struct {
chain []string
container *ioc.NestedContainer
}
// Creates a new middleware runner
func NewMiddlewareRunner(container *ioc.NestedContainer) *MiddlewareRunner {
return &MiddlewareRunner{
chain: []string{},
container: container,
}
}
// Executes the middleware chain for the specified action
func (r *MiddlewareRunner) RunAction(
ctx context.Context,
runOptions *Options,
actionName string,
) (*actions.ActionResult, error) {
chainLength := len(r.chain)
index := 0
var nextFn NextFn
// We need to get the actionContainer for the current executing scope
actionContainer := runOptions.container
if actionContainer == nil {
actionContainer = r.container
}
// Create a new context with the child container which will be leveraged on any child command/actions
ioc.RegisterInstance(actionContainer, runOptions)
// This recursive function executes the middleware chain in the order that
// the middlewares were registered. nextFn is passed into the middleware run
// allowing the middleware to choose to execute logic before and/or after
// the action. After we have executed all of the middlewares the action is run
// and the chain is unwrapped back out through the call stack.
nextFn = func(ctx context.Context) (*actions.ActionResult, error) {
if index < chainLength {
middlewareName := r.chain[index]
index++
var middleware Middleware
if err := actionContainer.ResolveNamed(middlewareName, &middleware); err != nil {
log.Printf("failed resolving middleware '%s' : %s\n", middlewareName, err.Error())
}
// It is an expected scenario that the middleware cannot be resolved
// due to missing dependency or other project configuration.
// In this case simply continue the chain with `nextFn`
if middleware == nil {
return nextFn(ctx)
}
log.Printf("running middleware '%s'\n", middlewareName)
return middleware.Run(ctx, nextFn)
} else {
var action actions.Action
if err := actionContainer.ResolveNamed(actionName, &action); err != nil {
if errors.Is(err, ioc.ErrResolveInstance) {
return nil, fmt.Errorf(
//nolint:lll
"failed resolving action '%s'. Ensure the ActionResolver is a valid go function that returns an `actions.Action` interface, %w",
actionName,
err,
)
}
return nil, err
}
return action.Run(ctx)
}
}
return nextFn(ctx)
}
// Registers middleware components that will be run for all actions
func (r *MiddlewareRunner) Use(name string, resolveFn any) error {
if err := r.container.RegisterNamedTransient(name, resolveFn); err != nil {
return err
}
if !slices.Contains(r.chain, name) {
r.chain = append(r.chain, name)
}
return nil
}
func WithChildAction(ctx context.Context) context.Context {
return context.WithValue(ctx, childActionKey, true)
}