/
template.go
116 lines (102 loc) · 3.2 KB
/
template.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
// Package template provides a simple templating solution reusable in filters.
//
// (Note that the current template syntax is EXPERIMENTAL, and may change in
// the near future.)
package eskip
import (
"net"
"net/http"
"regexp"
"strings"
snet "github.com/zalando/skipper/net"
)
var placeholderRegexp = regexp.MustCompile(`\$\{([^{}]+)\}`)
// TemplateGetter functions return the value for a template parameter name.
type TemplateGetter func(string) string
// Template represents a string template with named placeholders.
type Template struct {
template string
placeholders []string
}
type TemplateContext interface {
PathParam(string) string
Request() *http.Request
Response() *http.Response
}
// New parses a template string and returns a reusable *Template object.
// The template string can contain named placeholders of the format:
//
// Hello, ${who}!
func NewTemplate(template string) *Template {
matches := placeholderRegexp.FindAllStringSubmatch(template, -1)
placeholders := make([]string, len(matches))
for index, placeholder := range matches {
placeholders[index] = placeholder[1]
}
return &Template{template: template, placeholders: placeholders}
}
// Apply evaluates the template using a TemplateGetter function to resolve the
// placeholders.
func (t *Template) Apply(get TemplateGetter) string {
if get == nil {
return t.template
}
result, _ := t.apply(get)
return result
}
// ApplyContext evaluates the template using template context to resolve the
// placeholders. Returns true if all placeholders resolved to non-empty values.
func (t *Template) ApplyContext(ctx TemplateContext) (string, bool) {
return t.apply(func(key string) string {
if h := strings.TrimPrefix(key, "request.header."); h != key {
return ctx.Request().Header.Get(h)
}
if q := strings.TrimPrefix(key, "request.query."); q != key {
return ctx.Request().URL.Query().Get(q)
}
if c := strings.TrimPrefix(key, "request.cookie."); c != key {
if cookie, err := ctx.Request().Cookie(c); err == nil {
return cookie.Value
}
return ""
}
switch key {
case "request.method":
return ctx.Request().Method
case "request.host":
return ctx.Request().Host
case "request.path":
return ctx.Request().URL.Path
case "request.rawQuery":
return ctx.Request().URL.RawQuery
case "request.source":
return snet.RemoteHost(ctx.Request()).String()
case "request.sourceFromLast":
return snet.RemoteHostFromLast(ctx.Request()).String()
case "request.clientIP":
if host, _, err := net.SplitHostPort(ctx.Request().RemoteAddr); err == nil {
return host
}
}
if ctx.Response() != nil {
if h := strings.TrimPrefix(key, "response.header."); h != key {
return ctx.Response().Header.Get(h)
}
}
return ctx.PathParam(key)
})
}
// apply evaluates the template using a TemplateGetter function to resolve the
// placeholders. Returns true if all placeholders resolved to non-empty values.
func (t *Template) apply(get TemplateGetter) (string, bool) {
result := t.template
missing := false
for _, placeholder := range t.placeholders {
value := get(placeholder)
if value == "" {
missing = true
}
result = strings.ReplaceAll(result, "${"+placeholder+"}", value)
}
return result, !missing
}