Skip to content

Commit 957dcd8

Browse files
committed
improved templating and removed recursive json templating
1 parent 01b0271 commit 957dcd8

File tree

2 files changed

+171
-85
lines changed

2 files changed

+171
-85
lines changed

internals/proxy/middlewares/template.go

Lines changed: 48 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ package middlewares
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"io"
76
"net/http"
87
"net/url"
9-
"regexp"
108
"strconv"
11-
"strings"
12-
"text/template"
139

10+
"github.com/codeshelldev/secured-signal-api/utils"
1411
log "github.com/codeshelldev/secured-signal-api/utils/logger"
15-
query "github.com/codeshelldev/secured-signal-api/utils/query"
12+
"github.com/codeshelldev/secured-signal-api/utils/query"
1613
request "github.com/codeshelldev/secured-signal-api/utils/request"
14+
"github.com/codeshelldev/secured-signal-api/utils/templating"
1715
)
1816

1917
type TemplateMiddleware struct {
@@ -39,7 +37,11 @@ func (data TemplateMiddleware) Use() http.Handler {
3937
if !body.Empty {
4038
var modified bool
4139

42-
bodyData, modified = templateJSON(body.Data, VARIABLES)
40+
bodyData, modified, err = TemplateBody(body.Data, VARIABLES)
41+
42+
if err != nil {
43+
log.Error("Error Templating JSON: ", err.Error())
44+
}
4345

4446
if modified {
4547
modifiedBody = true
@@ -49,7 +51,11 @@ func (data TemplateMiddleware) Use() http.Handler {
4951
if req.URL.RawQuery != "" {
5052
var modified bool
5153

52-
req.URL.RawQuery, bodyData, modified = templateQuery(bodyData, req.URL, VARIABLES)
54+
req.URL.RawQuery, bodyData, modified, err = TemplateQuery(req.URL, bodyData, VARIABLES)
55+
56+
if err != nil {
57+
log.Error("Error Templating Query: ", err.Error())
58+
}
5359

5460
if modified {
5561
modifiedBody = true
@@ -76,128 +82,85 @@ func (data TemplateMiddleware) Use() http.Handler {
7682

7783
req.Body = io.NopCloser(bytes.NewReader(body.Raw))
7884

79-
req.URL.Path, _ = templatePath(req.URL, VARIABLES)
80-
81-
next.ServeHTTP(w, req)
82-
})
83-
}
84-
85-
func renderTemplate(name string, tmplStr string, data any) (string, error) {
86-
tmpl, err := template.New(name).Parse(tmplStr)
87-
88-
if err != nil {
89-
return "", err
90-
}
91-
var buf bytes.Buffer
92-
93-
err = tmpl.Execute(&buf, data)
94-
95-
if err != nil {
96-
return "", err
97-
}
98-
return buf.String(), nil
99-
}
100-
101-
func templateJSON(data map[string]interface{}, variables map[string]interface{}) (map[string]interface{}, bool) {
102-
var modified bool
103-
104-
for k, v := range data {
105-
str, ok := v.(string)
85+
if req.URL.Path != "" {
86+
var modified bool
10687

107-
if ok {
108-
re, err := regexp.Compile(`{{\s*\.([A-Za-z_][A-Za-z0-9_]*)\s*}}`)
88+
req.URL.Path, modified, err = TemplatePath(req.URL, VARIABLES)
10989

11090
if err != nil {
111-
log.Error("Error while Compiling Regex: ", err.Error())
91+
log.Error("Error Templating Path: ", err.Error())
11292
}
11393

114-
matches := re.FindAllStringSubmatch(str, -1)
115-
116-
if len(matches) > 1 {
117-
for i, tmplStr := range matches {
118-
119-
tmplKey := matches[i][1]
94+
if modified {
95+
log.Debug("Applied Path Templating: ", req.URL.Path)
96+
}
97+
}
12098

121-
variable, err := json.Marshal(variables[tmplKey])
99+
next.ServeHTTP(w, req)
100+
})
101+
}
122102

123-
if err != nil {
124-
log.Error("Could not decode JSON: ", err.Error())
125-
break
126-
}
103+
func TemplateBody(data map[string]interface{}, VARIABLES any) (map[string]interface{}, bool, error) {
104+
var modified bool
127105

128-
data[k] = strings.ReplaceAll(str, string(variable), tmplStr[0])
129-
}
106+
templatedData, err := templating.RenderJSONTemplate("body", data, VARIABLES)
130107

131-
modified = true
132-
} else if len(matches) == 1 {
133-
tmplKey := matches[0][1]
108+
if err != nil {
109+
return data, false, err
110+
}
134111

135-
data[k] = variables[tmplKey]
112+
beforeStr := utils.ToJson(templatedData)
113+
afterStr := utils.ToJson(data)
136114

137-
modified = true
138-
}
139-
}
140-
}
115+
modified = beforeStr == afterStr
141116

142-
return data, modified
117+
return templatedData, modified, nil
143118
}
144119

145-
func templatePath(reqUrl *url.URL, VARIABLES interface{}) (string, bool) {
120+
func TemplatePath(reqUrl *url.URL, VARIABLES any) (string, bool, error) {
146121
var modified bool
147122

148123
reqPath, err := url.PathUnescape(reqUrl.Path)
149124

150125
if err != nil {
151-
log.Error("Error while Escaping Path: ", err.Error())
152-
return reqUrl.Path, modified
126+
return reqUrl.Path, modified, err
153127
}
154128

155-
reqPath, err = renderTemplate("path", reqPath, VARIABLES)
129+
reqPath, err = templating.RenderNormalizedTemplate("path", reqPath, VARIABLES)
156130

157131
if err != nil {
158-
log.Error("Could not Template Path: ", err.Error())
159-
return reqUrl.Path, modified
132+
return reqUrl.Path, modified, err
160133
}
161134

162135
if reqUrl.Path != reqPath {
163-
log.Debug("Applied Path Templating: ", reqPath)
164-
165136
modified = true
166137
}
167138

168-
return reqPath, modified
139+
return reqPath, modified, nil
169140
}
170141

171-
func templateQuery(data map[string]interface{}, reqUrl *url.URL, VARIABLES interface{}) (string, map[string]interface{}, bool) {
142+
func TemplateQuery(reqUrl *url.URL, data map[string]interface{}, VARIABLES any) (string, map[string]interface{}, bool, error) {
172143
var modified bool
173144

174145
decodedQuery, _ := url.QueryUnescape(reqUrl.RawQuery)
175146

176-
log.Debug("Decoded Query: ", decodedQuery)
177-
178-
templatedQuery, _ := renderTemplate("query", decodedQuery, VARIABLES)
179-
180-
modifiedQuery := reqUrl.Query()
147+
templatedQuery, _ := templating.RenderNormalizedTemplate("query", decodedQuery, VARIABLES)
181148

182-
queryData := query.ParseRawQuery(templatedQuery)
149+
originalQueryData := reqUrl.Query()
183150

184-
for key, value := range queryData {
185-
keyWithoutPrefix, found := strings.CutPrefix(key, "@")
151+
addedData := query.ParseTypedQuery(templatedQuery, "@")
186152

187-
if found {
188-
data[keyWithoutPrefix] = query.ParseTypedQuery(value)
153+
for key, val := range addedData {
154+
data[key] = val
189155

190-
modifiedQuery.Del(key)
191-
}
156+
originalQueryData.Del(key)
192157
}
193158

194-
reqRawQuery := modifiedQuery.Encode()
159+
reqRawQuery := originalQueryData.Encode()
195160

196161
if reqUrl.Query().Encode() != reqRawQuery {
197-
log.Debug("Applied Query Templating: ", templatedQuery)
198-
199162
modified = true
200163
}
201164

202-
return reqRawQuery, data, modified
165+
return reqRawQuery, data, modified, nil
203166
}

utils/templating/templating.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package templating
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"regexp"
8+
"strings"
9+
"text/template"
10+
)
11+
12+
func normalize(value interface{}) string {
13+
switch str := value.(type) {
14+
case []string:
15+
return "[" + strings.Join(str, ",") + "]"
16+
17+
case []interface{}:
18+
items := make([]string, len(str))
19+
20+
for i, item := range str {
21+
items[i] = fmt.Sprintf("%v", item)
22+
}
23+
24+
return "[" + strings.Join(items, ",") + "]"
25+
default:
26+
return fmt.Sprintf("%v", value)
27+
}
28+
}
29+
30+
func normalizeJSON(value interface{}) string {
31+
jsonBytes, err := json.Marshal(value)
32+
33+
if err != nil {
34+
return "INVALID:JSON"
35+
}
36+
37+
return "<<" + string(jsonBytes) + ">>"
38+
}
39+
40+
func ParseTemplate(templt *template.Template, tmplStr string, variables any) (string, error) {
41+
tmpl, err := templt.Parse(tmplStr)
42+
43+
if err != nil {
44+
return "", err
45+
}
46+
var buf bytes.Buffer
47+
48+
err = tmpl.Execute(&buf, variables)
49+
50+
if err != nil {
51+
return "", err
52+
}
53+
return buf.String(), nil
54+
}
55+
56+
func RenderTemplate(name string, tmplStr string, variables any) (string, error) {
57+
templt := template.New(name)
58+
59+
return ParseTemplate(templt, tmplStr, variables)
60+
}
61+
62+
func CreateTemplateWithFunc(name string, funcMap template.FuncMap) (*template.Template) {
63+
return template.New(name).Funcs(funcMap)
64+
}
65+
66+
func RenderJSONTemplate(name string, data map[string]interface{}, variables any) (map[string]interface{}, error) {
67+
jsonBytes, err := json.Marshal(data)
68+
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
tmplStr := string(jsonBytes)
74+
75+
re, err := regexp.Compile(`{{\s*\.(\w+)\s*}}`)
76+
77+
// Add normalize() to be able to remove Quotes from Arrays
78+
if err == nil {
79+
tmplStr = re.ReplaceAllString(tmplStr, "{{normalize .$1}}")
80+
}
81+
82+
templt := CreateTemplateWithFunc(name, template.FuncMap{
83+
"normalize": normalizeJSON,
84+
})
85+
86+
jsonStr, err := ParseTemplate(templt, tmplStr, variables)
87+
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
// Remove the Quotes around "<<[item1,item2]>>"
93+
re, err = regexp.Compile(`"<<(.*?)>>"`)
94+
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
jsonStr = re.ReplaceAllString(jsonStr, "$1")
100+
101+
err = json.Unmarshal([]byte(jsonStr), &data)
102+
103+
if err != nil {
104+
return nil, err
105+
}
106+
107+
return data, nil
108+
}
109+
110+
func RenderNormalizedTemplate(name string, tmplStr string, variables any) (string, error) {
111+
re, err := regexp.Compile(`{{\s*\.(\w+)\s*}}`)
112+
113+
// Add normalize() to normalize arrays to [item1,item2]
114+
if err == nil {
115+
tmplStr = re.ReplaceAllString(tmplStr, "{{normalize .$1}}")
116+
}
117+
118+
templt := CreateTemplateWithFunc(name, template.FuncMap{
119+
"normalize": normalize,
120+
})
121+
122+
return ParseTemplate(templt, tmplStr, variables)
123+
}

0 commit comments

Comments
 (0)