From 95d944613bffce1cee3783568ae229e116168ba4 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Sun, 29 Dec 2019 13:12:52 -0700 Subject: Export Replacer and use concrete type instead of interface The interface was only making things difficult; a concrete pointer is probably best. --- modules/caddyhttp/caddyauth/caddyauth.go | 2 +- modules/caddyhttp/fileserver/browse.go | 4 +- modules/caddyhttp/fileserver/browselisting.go | 2 +- modules/caddyhttp/fileserver/matcher.go | 4 +- modules/caddyhttp/fileserver/staticfiles.go | 4 +- modules/caddyhttp/headers/headers.go | 8 +- modules/caddyhttp/matchers.go | 12 +-- modules/caddyhttp/replacer.go | 2 +- modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go | 2 +- modules/caddyhttp/reverseproxy/hosts.go | 2 +- modules/caddyhttp/reverseproxy/reverseproxy.go | 4 +- modules/caddyhttp/rewrite/rewrite.go | 6 +- modules/caddyhttp/server.go | 2 +- modules/caddyhttp/staticerror.go | 2 +- modules/caddyhttp/staticresp.go | 2 +- modules/caddyhttp/templates/templates.go | 2 +- modules/caddyhttp/vars.go | 4 +- replacer.go | 108 +++++++++++----------- replacer_test.go | 90 +++++++++--------- 19 files changed, 128 insertions(+), 134 deletions(-) diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go index aefeec5..dc1dc99 100644 --- a/modules/caddyhttp/caddyauth/caddyauth.go +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -76,7 +76,7 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated")) } - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl.Set("http.authentication.user.id", user.ID) return next.ServeHTTP(w, r) diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go index aa8372e..e5e137f 100644 --- a/modules/caddyhttp/fileserver/browse.go +++ b/modules/caddyhttp/fileserver/browse.go @@ -52,7 +52,7 @@ func (fsrv *FileServer) serveBrowse(dirPath string, w http.ResponseWriter, r *ht } defer dir.Close() - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) // calling path.Clean here prevents weird breadcrumbs when URL paths are sketchy like /%2e%2e%2f listing, err := fsrv.loadDirectoryContents(dir, path.Clean(r.URL.Path), repl) @@ -87,7 +87,7 @@ func (fsrv *FileServer) serveBrowse(dirPath string, w http.ResponseWriter, r *ht return nil } -func (fsrv *FileServer) loadDirectoryContents(dir *os.File, urlPath string, repl caddy.Replacer) (browseListing, error) { +func (fsrv *FileServer) loadDirectoryContents(dir *os.File, urlPath string, repl *caddy.Replacer) (browseListing, error) { files, err := dir.Readdir(-1) if err != nil { return browseListing{}, err diff --git a/modules/caddyhttp/fileserver/browselisting.go b/modules/caddyhttp/fileserver/browselisting.go index b2349f8..9c7c4a2 100644 --- a/modules/caddyhttp/fileserver/browselisting.go +++ b/modules/caddyhttp/fileserver/browselisting.go @@ -27,7 +27,7 @@ import ( "github.com/dustin/go-humanize" ) -func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, repl caddy.Replacer) browseListing { +func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, repl *caddy.Replacer) browseListing { filesToHide := fsrv.transformHidePaths(repl) var ( diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 5ca97f2..6c1e880 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -126,7 +126,7 @@ func (m MatchFile) Validate() error { // - http.matchers.file.relative // - http.matchers.file.absolute func (m MatchFile) Match(r *http.Request) bool { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) rel, abs, matched := m.selectFile(r) if matched { repl.Set("http.matchers.file.relative", rel) @@ -140,7 +140,7 @@ func (m MatchFile) Match(r *http.Request) bool { // It returns the root-relative path to the matched file, the full // or absolute path, and whether a match was made. func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) root := repl.ReplaceAll(m.Root, ".") diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index f2722ba..d6cf4d6 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -106,7 +106,7 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error { } func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) filesToHide := fsrv.transformHidePaths(repl) @@ -293,7 +293,7 @@ func mapDirOpenError(originalErr error, name string) error { // transformHidePaths performs replacements for all the elements of // fsrv.Hide and returns a new list of the transformed values. -func (fsrv *FileServer) transformHidePaths(repl caddy.Replacer) []string { +func (fsrv *FileServer) transformHidePaths(repl *caddy.Replacer) []string { hide := make([]string, len(fsrv.Hide)) for i := range fsrv.Hide { hide[i] = repl.ReplaceAll(fsrv.Hide[i], "") diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go index ad6c08b..8dec42c 100644 --- a/modules/caddyhttp/headers/headers.go +++ b/modules/caddyhttp/headers/headers.go @@ -88,7 +88,7 @@ func (h Handler) Validate() error { } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) if h.Request != nil { h.Request.ApplyToRequest(r) @@ -182,7 +182,7 @@ type RespHeaderOps struct { } // ApplyTo applies ops to hdr using repl. -func (ops HeaderOps) ApplyTo(hdr http.Header, repl caddy.Replacer) { +func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) { // add for fieldName, vals := range ops.Add { fieldName = repl.ReplaceAll(fieldName, "") @@ -249,7 +249,7 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl caddy.Replacer) { // header which the standard library does not include with the // header map with all the others. This method mutates r.Host. func (ops HeaderOps) ApplyToRequest(r *http.Request) { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) // capture the current Host header so we can // reset to it when we're done @@ -285,7 +285,7 @@ func (ops HeaderOps) ApplyToRequest(r *http.Request) { // operations until WriteHeader is called. type responseWriterWrapper struct { *caddyhttp.ResponseWriterWrapper - replacer caddy.Replacer + replacer *caddy.Replacer require *caddyhttp.ResponseMatcher headerOps *HeaderOps wroteHeader bool diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 00f273e..170dbe7 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -118,7 +118,7 @@ func (m MatchHost) Match(r *http.Request) bool { reqHost = strings.TrimSuffix(reqHost, "]") } - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) outer: for _, host := range m { @@ -223,7 +223,7 @@ func (MatchPathRE) CaddyModule() caddy.ModuleInfo { // Match returns true if r matches m. func (m MatchPathRE) Match(r *http.Request) bool { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) return m.MatchRegexp.Match(r.URL.Path, repl) } @@ -380,7 +380,7 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // Match returns true if r matches m. func (m MatchHeaderRE) Match(r *http.Request) bool { for field, rm := range m { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) match := rm.Match(r.Header.Get(field), repl) if !match { return false @@ -652,10 +652,8 @@ func (mre *MatchRegexp) Validate() error { // Match returns true if input matches the compiled regular // expression in mre. It sets values on the replacer repl // associated with capture groups, using the given scope -// (namespace). Capture groups stored to repl will take on -// the name "http.matchers..." where -// is the name or number of the capture group. -func (mre *MatchRegexp) Match(input string, repl caddy.Replacer) bool { +// (namespace). +func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { matches := mre.compiled.FindStringSubmatch(input) if matches == nil { return false diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index c8fae79..09837be 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -26,7 +26,7 @@ import ( "github.com/caddyserver/caddy/v2" ) -func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.ResponseWriter) { +func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.ResponseWriter) { httpVars := func(key string) (string, bool) { if req != nil { // query string parameters diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go index c0dda29..9a424b0 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -148,7 +148,7 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) { // buildEnv returns a set of CGI environment variables for the request. func (t Transport) buildEnv(r *http.Request) (map[string]string, error) { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) var env map[string]string diff --git a/modules/caddyhttp/reverseproxy/hosts.go b/modules/caddyhttp/reverseproxy/hosts.go index e7c61fb..6e25c66 100644 --- a/modules/caddyhttp/reverseproxy/hosts.go +++ b/modules/caddyhttp/reverseproxy/hosts.go @@ -204,7 +204,7 @@ func (di DialInfo) String() string { // fillDialInfo returns a filled DialInfo for the given upstream, using // the given Replacer. Note that the returned value is not a pointer. -func fillDialInfo(upstream *Upstream, repl caddy.Replacer) (DialInfo, error) { +func fillDialInfo(upstream *Upstream, repl *caddy.Replacer) (DialInfo, error) { dial := repl.ReplaceAll(upstream.Dial, "") addr, err := caddy.ParseNetworkAddress(dial) if err != nil { diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 24389b2..238e86f 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -263,7 +263,7 @@ func (h *Handler) Cleanup() error { } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) // if enabled, buffer client request; // this should only be enabled if the @@ -507,7 +507,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia if h.Headers != nil && h.Headers.Response != nil { if h.Headers.Response.Require == nil || h.Headers.Response.Require.Match(res.StatusCode, rw.Header()) { - repl := req.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) h.Headers.Response.ApplyTo(rw.Header(), repl) } } diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go index 03c54b1..b875adb 100644 --- a/modules/caddyhttp/rewrite/rewrite.go +++ b/modules/caddyhttp/rewrite/rewrite.go @@ -91,7 +91,7 @@ func (rewr Rewrite) Validate() error { } func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) logger := rewr.logger.With( zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}), @@ -124,7 +124,7 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy // rewrite performs the rewrites on r using repl, which // should have been obtained from r, but is passed in for // efficiency. It returns true if any changes were made to r. -func (rewr Rewrite) rewrite(r *http.Request, repl caddy.Replacer, logger *zap.Logger) bool { +func (rewr Rewrite) rewrite(r *http.Request, repl *caddy.Replacer, logger *zap.Logger) bool { oldMethod := r.Method oldURI := r.RequestURI @@ -209,7 +209,7 @@ type replacer struct { } // do performs the replacement on r and returns true if any changes were made. -func (rep replacer) do(r *http.Request, repl caddy.Replacer) bool { +func (rep replacer) do(r *http.Request, repl *caddy.Replacer) bool { if rep.Find == "" || rep.Replace == "" { return false } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 60ce07e..0c30139 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -451,7 +451,7 @@ func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request { r = r.WithContext(c) // add error values to the replacer - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl.Set("http.error", err.Error()) if handlerErr, ok := err.(HandlerError); ok { repl.Set("http.error.status_code", strconv.Itoa(handlerErr.StatusCode)) diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go index 0ddebfe..306c3eb 100644 --- a/modules/caddyhttp/staticerror.go +++ b/modules/caddyhttp/staticerror.go @@ -51,7 +51,7 @@ func (StaticError) CaddyModule() caddy.ModuleInfo { } func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) statusCode := http.StatusInternalServerError if codeStr := e.StatusCode.String(); codeStr != "" { diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go index cc294d5..0ca2f43 100644 --- a/modules/caddyhttp/staticresp.go +++ b/modules/caddyhttp/staticresp.go @@ -86,7 +86,7 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) // close the connection after responding r.Close = s.Close diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index 6586302..d01b447 100644 --- a/modules/caddyhttp/templates/templates.go +++ b/modules/caddyhttp/templates/templates.go @@ -122,7 +122,7 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Request) error { var fs http.FileSystem if t.FileRoot != "" { - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) fs = http.Dir(repl.ReplaceAll(t.FileRoot, ".")) } diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index 6299dcf..cd0d8d0 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -40,7 +40,7 @@ func (VarsMiddleware) CaddyModule() caddy.ModuleInfo { func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { vars := r.Context().Value(VarsCtxKey).(map[string]interface{}) - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) for k, v := range t { keyExpanded := repl.ReplaceAll(k, "") valExpanded := repl.ReplaceAll(v, "") @@ -64,7 +64,7 @@ func (VarsMatcher) CaddyModule() caddy.ModuleInfo { // Match matches a request based on variables in the context. func (m VarsMatcher) Match(r *http.Request) bool { vars := r.Context().Value(VarsCtxKey).(map[string]string) - repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) for k, v := range m { keyExpanded := repl.ReplaceAll(k, "") valExpanded := repl.ReplaceAll(v, "") diff --git a/replacer.go b/replacer.go index cabe089..8ed6d24 100644 --- a/replacer.go +++ b/replacer.go @@ -23,20 +23,9 @@ import ( "time" ) -// Replacer can replace values in strings. -type Replacer interface { - Set(variable, value string) - Delete(variable string) - Map(ReplacerFunc) - ReplaceAll(input, empty string) string - ReplaceKnown(input, empty string) string - ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) - ReplaceFunc(input string, f ReplacementFunc) (string, error) -} - // NewReplacer returns a new Replacer. -func NewReplacer() Replacer { - rep := &replacer{ +func NewReplacer() *Replacer { + rep := &Replacer{ static: make(map[string]string), } rep.providers = []ReplacerFunc{ @@ -46,30 +35,44 @@ func NewReplacer() Replacer { return rep } -type replacer struct { +// Replacer can replace values in strings. +// A default/empty Replacer is not valid; +// use NewReplacer to make one. +type Replacer struct { providers []ReplacerFunc static map[string]string } // Map adds mapFunc to the list of value providers. // mapFunc will be executed only at replace-time. -func (r *replacer) Map(mapFunc ReplacerFunc) { +func (r *Replacer) Map(mapFunc ReplacerFunc) { r.providers = append(r.providers, mapFunc) } // Set sets a custom variable to a static value. -func (r *replacer) Set(variable, value string) { +func (r *Replacer) Set(variable, value string) { r.static[variable] = value } +// Get gets a value from the replacer. It returns +// the value and whether the variable was known. +func (r *Replacer) Get(variable string) (string, bool) { + for _, mapFunc := range r.providers { + if val, ok := mapFunc(variable); ok { + return val, true + } + } + return "", false +} + // Delete removes a variable with a static value // that was created using Set. -func (r *replacer) Delete(variable string) { +func (r *Replacer) Delete(variable string) { delete(r.static, variable) } // fromStatic provides values from r.static. -func (r *replacer) fromStatic(key string) (val string, ok bool) { +func (r *Replacer) fromStatic(key string) (val string, ok bool) { val, ok = r.static[key] return } @@ -77,14 +80,14 @@ func (r *replacer) fromStatic(key string) (val string, ok bool) { // ReplaceOrErr is like ReplaceAll, but any placeholders // that are empty or not recognized will cause an error to // be returned. -func (r *replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) { +func (r *Replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) { return r.replace(input, "", false, errOnEmpty, errOnUnknown, nil) } // ReplaceKnown is like ReplaceAll but only replaces // placeholders that are known (recognized). Unrecognized // placeholders will remain in the output. -func (r *replacer) ReplaceKnown(input, empty string) string { +func (r *Replacer) ReplaceKnown(input, empty string) string { out, _ := r.replace(input, empty, false, false, false, nil) return out } @@ -93,7 +96,7 @@ func (r *replacer) ReplaceKnown(input, empty string) string { // their values. All placeholders are replaced in the output // whether they are recognized or not. Values that are empty // string will be substituted with empty. -func (r *replacer) ReplaceAll(input, empty string) string { +func (r *Replacer) ReplaceAll(input, empty string) string { out, _ := r.replace(input, empty, true, false, false, nil) return out } @@ -102,11 +105,11 @@ func (r *replacer) ReplaceAll(input, empty string) string { // their values. All placeholders are replaced in the output // whether they are recognized or not. Values that are empty // string will be substituted with empty. -func (r *replacer) ReplaceFunc(input string, f ReplacementFunc) (string, error) { +func (r *Replacer) ReplaceFunc(input string, f ReplacementFunc) (string, error) { return r.replace(input, "", true, false, false, f) } -func (r *replacer) replace(input, empty string, +func (r *Replacer) replace(input, empty string, treatUnknownAsEmpty, errOnEmpty, errOnUnknown bool, f ReplacementFunc) (string, error) { if !strings.Contains(input, string(phOpen)) { @@ -138,48 +141,45 @@ func (r *replacer) replace(input, empty string, // trim opening bracket key := input[i+1 : end] - // try to get a value for this key, - // handle empty values accordingly - var found bool - for _, mapFunc := range r.providers { - if val, ok := mapFunc(key); ok { - found = true - if f != nil { - var err error - val, err = f(key, val) - if err != nil { - return "", err - } - } - if val == "" { - if errOnEmpty { - return "", fmt.Errorf("evaluated placeholder %s%s%s is empty", - string(phOpen), key, string(phClose)) - } else if empty != "" { - sb.WriteString(empty) - } - } else { - sb.WriteString(val) - } - break - } - } + // try to get a value for this key, handle empty values accordingly + val, found := r.Get(key) if !found { - // placeholder is unknown (unrecognized), handle accordingly - switch { - case errOnUnknown: + // placeholder is unknown (unrecognized); handle accordingly + if errOnUnknown { return "", fmt.Errorf("unrecognized placeholder %s%s%s", string(phOpen), key, string(phClose)) - case treatUnknownAsEmpty: + } else if treatUnknownAsEmpty { if empty != "" { sb.WriteString(empty) } - default: + } else { lastWriteCursor = i continue } } + // apply any transformations + if f != nil { + var err error + val, err = f(key, val) + if err != nil { + return "", err + } + } + + // write the value; if it's empty, either return + // an error or write a default value + if val == "" { + if errOnEmpty { + return "", fmt.Errorf("evaluated placeholder %s%s%s is empty", + string(phOpen), key, string(phClose)) + } else if empty != "" { + sb.WriteString(empty) + } + } else { + sb.WriteString(val) + } + // advance cursor to end of placeholder i = end lastWriteCursor = i + 1 diff --git a/replacer_test.go b/replacer_test.go index 88e83f5..42e9ee1 100644 --- a/replacer_test.go +++ b/replacer_test.go @@ -132,7 +132,7 @@ func TestReplacerSet(t *testing.T) { } func TestReplacerReplaceKnown(t *testing.T) { - rep := replacer{ + rep := Replacer{ providers: []ReplacerFunc{ // split our possible vars to two functions (to test if both functions are called) func(key string) (val string, ok bool) { @@ -204,7 +204,7 @@ func TestReplacerReplaceKnown(t *testing.T) { } func TestReplacerDelete(t *testing.T) { - rep := replacer{ + rep := Replacer{ static: map[string]string{ "key1": "val1", "key2": "val2", @@ -264,59 +264,55 @@ func TestReplacerMap(t *testing.T) { } func TestReplacerNew(t *testing.T) { - var tc = NewReplacer() + rep := NewReplacer() - rep, ok := tc.(*replacer) - if ok { - if len(rep.providers) != 2 { - t.Errorf("Expected providers length '%v' got length '%v'", 2, len(rep.providers)) - } else { - // test if default global replacements are added as the first provider - hostname, _ := os.Hostname() - os.Setenv("CADDY_REPLACER_TEST", "envtest") - defer os.Setenv("CADDY_REPLACER_TEST", "") + if len(rep.providers) != 2 { + t.Errorf("Expected providers length '%v' got length '%v'", 2, len(rep.providers)) + } else { + // test if default global replacements are added as the first provider + hostname, _ := os.Hostname() + os.Setenv("CADDY_REPLACER_TEST", "envtest") + defer os.Setenv("CADDY_REPLACER_TEST", "") - for _, tc := range []struct { - variable string - value string - }{ - { - variable: "system.hostname", - value: hostname, - }, - { - variable: "system.slash", - value: string(filepath.Separator), - }, - { - variable: "system.os", - value: runtime.GOOS, - }, - { - variable: "system.arch", - value: runtime.GOARCH, - }, - { - variable: "env.CADDY_REPLACER_TEST", - value: "envtest", - }, - } { - if val, ok := rep.providers[0](tc.variable); ok { - if val != tc.value { - t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val) - } - } else { - t.Errorf("Expected key '%s' to be recognized by first provider", tc.variable) + for _, tc := range []struct { + variable string + value string + }{ + { + variable: "system.hostname", + value: hostname, + }, + { + variable: "system.slash", + value: string(filepath.Separator), + }, + { + variable: "system.os", + value: runtime.GOOS, + }, + { + variable: "system.arch", + value: runtime.GOARCH, + }, + { + variable: "env.CADDY_REPLACER_TEST", + value: "envtest", + }, + } { + if val, ok := rep.providers[0](tc.variable); ok { + if val != tc.value { + t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val) } + } else { + t.Errorf("Expected key '%s' to be recognized by first provider", tc.variable) } } - } else { - t.Errorf("Expected type of replacer %T got %T ", &replacer{}, tc) } + } -func testReplacer() replacer { - return replacer{ +func testReplacer() Replacer { + return Replacer{ providers: make([]ReplacerFunc, 0), static: make(map[string]string), } -- cgit v1.2.3