Go API Documentation

github.com/caddyserver/caddy/v2/modules/caddyhttp/map

No package summary is available.

Package

Files: 2. Third party imports: 0. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Vars

var (
	_	caddy.Provisioner		= (*Handler)(nil)
	_	caddy.Validator			= (*Handler)(nil)
	_	caddyhttp.MiddlewareHandler	= (*Handler)(nil)
)

Types

Handler

Handler implements a middleware that maps inputs to outputs. Specifically, it compares a source value against the map inputs, and for one that matches, it applies the output values to each destination. Destinations become placeholder names.

Mapped placeholders are not evaluated until they are used, so even for very large mappings, this handler is quite efficient.

type Handler struct {
	// Source is the placeholder from which to get the input value.
	Source	string	`json:"source,omitempty"`

	// Destinations are the names of placeholders in which to store the outputs.
	// Destination values should be wrapped in braces, for example, {my_placeholder}.
	Destinations	[]string	`json:"destinations,omitempty"`

	// Mappings from source values (inputs) to destination values (outputs).
	// The first matching, non-nil mapping will be applied.
	Mappings	[]Mapping	`json:"mappings,omitempty"`

	// If no mappings match or if the mapped output is null/nil, the associated
	// default output will be applied (optional).
	Defaults	[]string	`json:"defaults,omitempty"`
}

Mapping

Mapping describes a mapping from input to outputs.

type Mapping struct {
	// The input value to match. Must be distinct from other mappings.
	// Mutually exclusive to input_regexp.
	Input	string	`json:"input,omitempty"`

	// The input regular expression to match. Mutually exclusive to input.
	InputRegexp	string	`json:"input_regexp,omitempty"`

	// Upon a match with the input, each output is positionally correlated
	// with each destination of the parent handler. An output that is null
	// (nil) will be treated as if it was not mapped at all.
	Outputs	[]any	`json:"outputs,omitempty"`

	re	*regexp.Regexp
}

Functions

func (*Handler) Provision

Provision sets up h.

func (h *Handler) Provision(_ caddy.Context) error {
	for j, dest := range h.Destinations {
		if strings.Count(dest, "{") != 1 || !strings.HasPrefix(dest, "{") {
			return fmt.Errorf("destination must be a placeholder and only a placeholder")
		}
		h.Destinations[j] = strings.Trim(dest, "{}")
	}

	for i, m := range h.Mappings {
		if m.InputRegexp == "" {
			continue
		}
		var err error
		h.Mappings[i].re, err = regexp.Compile(m.InputRegexp)
		if err != nil {
			return fmt.Errorf("compiling regexp for mapping %d: %v", i, err)
		}
	}

	// TODO: improve efficiency even further by using an actual map type
	// for the non-regexp mappings, OR sort them and do a binary search

	return nil
}

Cognitive complexity: 12, Cyclomatic complexity: 7

Uses: fmt.Errorf, regexp.Compile, strings.Count, strings.HasPrefix, strings.Trim.

func (*Handler) Validate

Validate ensures that h is configured properly.

func (h *Handler) Validate() error {
	nDest, nDef := len(h.Destinations), len(h.Defaults)
	if nDef > 0 && nDef != nDest {
		return fmt.Errorf("%d destinations != %d defaults", nDest, nDef)
	}

	seen := make(map[string]int)
	for i, m := range h.Mappings {
		// prevent confusing/ambiguous mappings
		if m.Input != "" && m.InputRegexp != "" {
			return fmt.Errorf("mapping %d has both input and input_regexp fields specified, which is confusing", i)
		}

		// prevent duplicate mappings
		input := m.Input
		if m.InputRegexp != "" {
			input = m.InputRegexp
		}
		if prev, ok := seen[input]; ok {
			return fmt.Errorf("mapping %d has a duplicate input '%s' previously used with mapping %d", i, input, prev)
		}
		seen[input] = i

		// ensure mappings have 1:1 output-to-destination correspondence
		nOut := len(m.Outputs)
		if nOut != nDest {
			return fmt.Errorf("mapping %d has %d outputs but there are %d destinations defined", i, nOut, nDest)
		}
	}

	return nil
}

Cognitive complexity: 13, Cyclomatic complexity: 9

Uses: fmt.Errorf.

func (Handler) CaddyModule

CaddyModule returns the Caddy module information.

func (Handler) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:	"http.handlers.map",
		New:	func() caddy.Module { return new(Handler) },
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 1

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	// defer work until a variable is actually evaluated by using replacer's Map callback
	repl.Map(func(key string) (any, bool) {
		// return early if the variable is not even a configured destination
		destIdx := slices.Index(h.Destinations, key)
		if destIdx < 0 {
			return nil, false
		}

		input := repl.ReplaceAll(h.Source, "")

		// find the first mapping matching the input and return
		// the requested destination/output value
		for _, m := range h.Mappings {
			output := m.Outputs[destIdx]
			if output == nil {
				continue
			}
			outputStr := caddy.ToString(output)

			// evaluate regular expression if configured
			if m.re != nil {
				var result []byte
				matches := m.re.FindStringSubmatchIndex(input)
				if matches == nil {
					continue
				}
				result = m.re.ExpandString(result, outputStr, input, matches)
				return string(result), true
			}

			// otherwise simple string comparison
			if input == m.Input {
				return repl.ReplaceAll(outputStr, ""), true
			}
		}

		// fall back to default if no match or if matched nil value
		if len(h.Defaults) > destIdx {
			return repl.ReplaceAll(h.Defaults[destIdx], ""), true
		}

		return nil, true
	})

	return next.ServeHTTP(w, r)
}

Cognitive complexity: 16, Cyclomatic complexity: 8

Uses: slices.Index.

Private functions

func init

init ()
References: httpcaddyfile.RegisterHandlerDirective.

func parseCaddyfile

parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax:

map [<matcher>] <source> <destinations...> {
    [~]<input> <outputs...>
    default    <defaults...>
}

If the input value is prefixed with a tilde (~), then the input will be parsed as a regular expression.

The Caddyfile adapter treats outputs that are a literal hyphen (-) as a null/nil value. This is useful if you want to fall back to default for that particular output.

The number of outputs for each mapping must not be more than the number of destinations. However, for convenience, there may be fewer outputs than destinations and any missing outputs will be filled in implicitly.

parseCaddyfile (h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
References: httpcaddyfile.WasReplacedPlaceholderShorthand, strings.HasPrefix.


Tests

Files: 1. Third party imports: 0. Imports from organisation: 0. Tests: 1. Benchmarks: 0.

Test functions

TestHandler

References: caddyhttp.HandlerFunc, caddyhttp.NewTestReplacer, context.WithValue, http.MethodGet, http.NewRequest, http.Request, http.ResponseWriter, httptest.NewRecorder, reflect.DeepEqual.