src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

stack.go (5409B)


      1 package middleware
      2 
      3 import (
      4 	"context"
      5 	"io"
      6 	"strings"
      7 )
      8 
      9 // Stack provides protocol and transport agnostic set of middleware split into
     10 // distinct steps. Steps have specific transitions between them, that are
     11 // managed by the individual step.
     12 //
     13 // Steps are composed as middleware around the underlying handler in the
     14 // following order:
     15 //
     16 //   Initialize -> Serialize -> Build -> Finalize -> Deserialize -> Handler
     17 //
     18 // Any middleware within the chain may choose to stop and return an error or
     19 // response. Since the middleware decorate the handler like a call stack, each
     20 // middleware will receive the result of the next middleware in the chain.
     21 // Middleware that does not need to react to an input, or result must forward
     22 // along the input down the chain, or return the result back up the chain.
     23 //
     24 //   Initialize <- Serialize -> Build -> Finalize <- Deserialize <- Handler
     25 type Stack struct {
     26 	// Initialize prepares the input, and sets any default parameters as
     27 	// needed, (e.g. idempotency token, and presigned URLs).
     28 	//
     29 	// Takes Input Parameters, and returns result or error.
     30 	//
     31 	// Receives result or error from Serialize step.
     32 	Initialize *InitializeStep
     33 
     34 	// Serialize serializes the prepared input into a data structure that can be consumed
     35 	// by the target transport's message, (e.g. REST-JSON serialization)
     36 	//
     37 	// Converts Input Parameters into a Request, and returns the result or error.
     38 	//
     39 	// Receives result or error from Build step.
     40 	Serialize *SerializeStep
     41 
     42 	// Build adds additional metadata to the serialized transport message
     43 	// (e.g. HTTP's Content-Length header, or body checksum). Decorations and
     44 	// modifications to the message should be copied to all message attempts.
     45 	//
     46 	// Takes Request, and returns result or error.
     47 	//
     48 	// Receives result or error from Finalize step.
     49 	Build *BuildStep
     50 
     51 	// Finalize performs final preparations needed before sending the message. The
     52 	// message should already be complete by this stage, and is only alternated
     53 	// to meet the expectations of the recipient (e.g. Retry and AWS SigV4
     54 	// request signing)
     55 	//
     56 	// Takes Request, and returns result or error.
     57 	//
     58 	// Receives result or error from Deserialize step.
     59 	Finalize *FinalizeStep
     60 
     61 	// Deserialize reacts to the handler's response returned by the recipient of the request
     62 	// message. Deserializes the response into a structured type or error above
     63 	// stacks can react to.
     64 	//
     65 	// Should only forward Request to underlying handler.
     66 	//
     67 	// Takes Request, and returns result or error.
     68 	//
     69 	// Receives raw response, or error from underlying handler.
     70 	Deserialize *DeserializeStep
     71 
     72 	id string
     73 }
     74 
     75 // NewStack returns an initialize empty stack.
     76 func NewStack(id string, newRequestFn func() interface{}) *Stack {
     77 	return &Stack{
     78 		id:          id,
     79 		Initialize:  NewInitializeStep(),
     80 		Serialize:   NewSerializeStep(newRequestFn),
     81 		Build:       NewBuildStep(),
     82 		Finalize:    NewFinalizeStep(),
     83 		Deserialize: NewDeserializeStep(),
     84 	}
     85 }
     86 
     87 // ID returns the unique ID for the stack as a middleware.
     88 func (s *Stack) ID() string { return s.id }
     89 
     90 // HandleMiddleware invokes the middleware stack decorating the next handler.
     91 // Each step of stack will be invoked in order before calling the next step.
     92 // With the next handler call last.
     93 //
     94 // The input value must be the input parameters of the operation being
     95 // performed.
     96 //
     97 // Will return the result of the operation, or error.
     98 func (s *Stack) HandleMiddleware(ctx context.Context, input interface{}, next Handler) (
     99 	output interface{}, metadata Metadata, err error,
    100 ) {
    101 	h := DecorateHandler(next,
    102 		s.Initialize,
    103 		s.Serialize,
    104 		s.Build,
    105 		s.Finalize,
    106 		s.Deserialize,
    107 	)
    108 
    109 	return h.Handle(ctx, input)
    110 }
    111 
    112 // List returns a list of all middleware in the stack by step.
    113 func (s *Stack) List() []string {
    114 	var l []string
    115 	l = append(l, s.id)
    116 
    117 	l = append(l, s.Initialize.ID())
    118 	l = append(l, s.Initialize.List()...)
    119 
    120 	l = append(l, s.Serialize.ID())
    121 	l = append(l, s.Serialize.List()...)
    122 
    123 	l = append(l, s.Build.ID())
    124 	l = append(l, s.Build.List()...)
    125 
    126 	l = append(l, s.Finalize.ID())
    127 	l = append(l, s.Finalize.List()...)
    128 
    129 	l = append(l, s.Deserialize.ID())
    130 	l = append(l, s.Deserialize.List()...)
    131 
    132 	return l
    133 }
    134 
    135 func (s *Stack) String() string {
    136 	var b strings.Builder
    137 
    138 	w := &indentWriter{w: &b}
    139 
    140 	w.WriteLine(s.id)
    141 	w.Push()
    142 
    143 	writeStepItems(w, s.Initialize)
    144 	writeStepItems(w, s.Serialize)
    145 	writeStepItems(w, s.Build)
    146 	writeStepItems(w, s.Finalize)
    147 	writeStepItems(w, s.Deserialize)
    148 
    149 	return b.String()
    150 }
    151 
    152 type stackStepper interface {
    153 	ID() string
    154 	List() []string
    155 }
    156 
    157 func writeStepItems(w *indentWriter, s stackStepper) {
    158 	type lister interface {
    159 		List() []string
    160 	}
    161 
    162 	w.WriteLine(s.ID())
    163 	w.Push()
    164 
    165 	defer w.Pop()
    166 
    167 	// ignore stack to prevent circular iterations
    168 	if _, ok := s.(*Stack); ok {
    169 		return
    170 	}
    171 
    172 	for _, id := range s.List() {
    173 		w.WriteLine(id)
    174 	}
    175 }
    176 
    177 type stringWriter interface {
    178 	io.Writer
    179 	WriteString(string) (int, error)
    180 	WriteRune(rune) (int, error)
    181 }
    182 
    183 type indentWriter struct {
    184 	w     stringWriter
    185 	depth int
    186 }
    187 
    188 const indentDepth = "\t\t\t\t\t\t\t\t\t\t"
    189 
    190 func (w *indentWriter) Push() {
    191 	w.depth++
    192 }
    193 
    194 func (w *indentWriter) Pop() {
    195 	w.depth--
    196 	if w.depth < 0 {
    197 		w.depth = 0
    198 	}
    199 }
    200 
    201 func (w *indentWriter) WriteLine(v string) {
    202 	w.w.WriteString(indentDepth[:w.depth])
    203 
    204 	v = strings.ReplaceAll(v, "\n", "\\n")
    205 	v = strings.ReplaceAll(v, "\r", "\\r")
    206 
    207 	w.w.WriteString(v)
    208 	w.w.WriteRune('\n')
    209 }