Skip to content

Commit 34477f2

Browse files
authored
Merge pull request #65 from CodeShellDev/feat/improve-templating
feat: improve templating
2 parents 0681ca6 + 24569d1 commit 34477f2

File tree

7 files changed

+253
-98
lines changed

7 files changed

+253
-98
lines changed

.github/templates/README.template.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,15 @@ curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer API_T
123123

124124
If you are not comfortable / don't want to hardcode your Number for example and/or Recipients in you, may use **Placeholders** in your Request.
125125

126-
You can use [**Variable**](#variables) `{{.NUMBER}}` Placeholders and **Body** Placeholders `{{@data.key}}`.
126+
**How to use:**
127+
128+
| Type | Example | Note |
129+
| :--------------------- | :------------------ | :--------------- |
130+
| Body | `{{@data.key}}` | |
131+
| Header | `{{#Content_Type}}` | `-` becomes `_` |
132+
| [Variable](#variables) | `{{.VAR}}` | always uppercase |
133+
134+
**Where to use:**
127135

128136
| Type | Example |
129137
| :---- | :--------------------------------------------------------------- |
@@ -220,6 +228,47 @@ If you are using Environment Variables as an example you won't be able to specif
220228
> If you have a string that should not be turned into any other type, then you will need to escape all Type Denotations, `[]` or `{}` (also `-`) with a `\` **Backslash** (or Double Backslash).
221229
> An **Odd** number of **Backslashes** **escape** the character in front of them and an **Even** number leave the character **as-is**.
222230

231+
### Templating
232+
233+
Secured Signal API uses Golang's [Standard Templating Library](https://pkg.go.dev/text/template).
234+
This means that any valid Go template string will also work in Secured Signal API.
235+
236+
Go's templating library is used in the following features:
237+
238+
- [Message Templates](#message-templates)
239+
- [Placeholders](#placeholders)
240+
241+
This makes advanced [Message Templates](#message-templates) like this one possible:
242+
243+
```yaml
244+
settings:
245+
messageTemplate: |
246+
{{- $greeting := "Hello" -}}
247+
{{ $greeting }}, {{ @name }}!
248+
{{ if @age -}}
249+
You are {{ @age }} years old.
250+
{{- else -}}
251+
Age unknown.
252+
{{- end }}
253+
Your friends:
254+
{{- range @friends }}
255+
- {{ . }}
256+
{{- else }}
257+
You have no friends.
258+
{{- end }}
259+
Profile details:
260+
{{- range $key, $value := @profile }}
261+
- {{ $key }}: {{ $value }}
262+
{{- end }}
263+
{{ define "footer" -}}
264+
This is the footer for {{ @name }}.
265+
{{- end }}
266+
{{ template "footer" . -}}
267+
------------------------------------
268+
Content-Type: {{ #Content_Type }}
269+
Redacted Auth Header: {{ #Authorization }}
270+
```
271+
223272
### API Token(s)
224273

225274
During Authentication Secured Signal API will try to match the given Token against the list of Tokens inside of these Variables.
@@ -301,7 +350,8 @@ settings:
301350
Sent with Secured Signal API.
302351
```
303352

304-
Use `{{@data.key}}` to reference Body Keys and `{{.KEY}}` for Variables.
353+
Message Templates support [Standard Golang Templating](#templating).
354+
Use `@data.key` to reference Body Keys, `#Content_Type` for Headers and `.KEY` for Variables.
305355

306356
### Data Aliases
307357

internals/proxy/middlewares/message.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ func (data MessageMiddleware) Use() http.Handler {
4646
bodyData = body.Data
4747

4848
if messageTemplate != "" {
49-
newData, err := TemplateMessage(messageTemplate, bodyData, variables)
49+
headerData := request.GetReqHeaders(req)
50+
51+
newData, err := TemplateMessage(messageTemplate, bodyData, headerData, variables)
5052

5153
if err != nil {
5254
log.Error("Error Templating Message: ", err.Error())
@@ -83,13 +85,13 @@ func (data MessageMiddleware) Use() http.Handler {
8385
})
8486
}
8587

86-
func TemplateMessage(template string, data map[string]any, VARIABLES map[string]any) (map[string]any, error) {
87-
data["message_template"] = template
88+
func TemplateMessage(template string, bodyData map[string]any, headerData map[string]any, variables map[string]any) (map[string]any, error) {
89+
bodyData["message_template"] = template
8890

89-
data, _, err := TemplateBody(data, VARIABLES)
91+
data, _, err := TemplateBody(bodyData, headerData, variables)
9092

9193
if err != nil || data == nil {
92-
return data, err
94+
return bodyData, err
9395
}
9496

9597
data["message"] = data["message_template"]

internals/proxy/middlewares/template.go

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package middlewares
33
import (
44
"bytes"
55
"io"
6+
"maps"
67
"net/http"
78
"net/url"
89
"regexp"
910
"strconv"
11+
"strings"
1012

1113
jsonutils "github.com/codeshelldev/secured-signal-api/utils/jsonutils"
1214
log "github.com/codeshelldev/secured-signal-api/utils/logger"
@@ -42,7 +44,9 @@ func (data TemplateMiddleware) Use() http.Handler {
4244
if !body.Empty {
4345
var modified bool
4446

45-
bodyData, modified, err = TemplateBody(body.Data, variables)
47+
headerData := request.GetReqHeaders(req)
48+
49+
bodyData, modified, err = TemplateBody(body.Data, headerData, variables)
4650

4751
if err != nil {
4852
log.Error("Error Templating JSON: ", err.Error())
@@ -105,19 +109,23 @@ func (data TemplateMiddleware) Use() http.Handler {
105109
})
106110
}
107111

108-
func TemplateBody(data map[string]any, VARIABLES map[string]any) (map[string]any, bool, error) {
109-
var modified bool
110-
112+
func normalizeData(fromPrefix, toPrefix string, data map[string]any) (map[string]any, error) {
111113
jsonStr := jsonutils.ToJson(data)
112114

113115
if jsonStr != "" {
114-
re, err := regexp.Compile(`{{\s*\@([a-zA-Z0-9_.]+)\s*}}`)
116+
toVar, err := templating.TransformTemplateKeys(jsonStr, fromPrefix, func(re *regexp.Regexp, match string) string {
117+
return re.ReplaceAllStringFunc(match, func(varMatch string) string {
118+
varName := re.ReplaceAllString(varMatch, "$1")
119+
120+
return "." + toPrefix + varName
121+
})
122+
})
115123

116124
if err != nil {
117-
return data, false, err
125+
return data, err
118126
}
119127

120-
jsonStr = re.ReplaceAllString(jsonStr, "{{.$1}}")
128+
jsonStr = toVar
121129

122130
normalizedData, err := jsonutils.GetJsonSafe[map[string]any](jsonStr)
123131

@@ -126,16 +134,78 @@ func TemplateBody(data map[string]any, VARIABLES map[string]any) (map[string]any
126134
}
127135
}
128136

129-
templatedData, err := templating.RenderJSON("body", data, VARIABLES)
137+
return data, nil
138+
}
139+
140+
func prefixData(prefix string, data map[string]any) (map[string]any) {
141+
res := map[string]any{}
142+
143+
for key, value := range data {
144+
res[prefix + key] = value
145+
}
146+
147+
return res
148+
}
149+
150+
func cleanHeaders(headers map[string]any) map[string]any {
151+
cleanedHeaders := map[string]any{}
152+
153+
for key, value := range headers {
154+
cleanedKey := strings.ReplaceAll(key, "-", "_")
155+
156+
cleanedHeaders[cleanedKey] = value
157+
}
158+
159+
authHeader, ok := cleanedHeaders["Authorization"].([]string)
160+
161+
if !ok {
162+
authHeader = []string{"UNKNOWN REDACTED"}
163+
}
164+
165+
cleanedHeaders["Authorization"] = strings.Split(authHeader[0], ` `)[0] + " REDACTED"
166+
167+
return cleanedHeaders
168+
}
169+
170+
func TemplateBody(body map[string]any, headers map[string]any, VARIABLES map[string]any) (map[string]any, bool, error) {
171+
var modified bool
172+
173+
headers = cleanHeaders(headers)
174+
175+
// Normalize #Var and @Var to .header_key_Var and .body_key_Var
176+
normalizedBody, err := normalizeData("@", "body_key_", body)
177+
178+
if err != nil {
179+
return body, false, err
180+
}
181+
182+
normalizedBody, err = normalizeData("#", "header_key_", normalizedBody)
130183

131184
if err != nil {
132-
return data, false, err
185+
return body, false, err
133186
}
134187

135-
beforeStr := jsonutils.ToJson(templatedData)
136-
afterStr := jsonutils.ToJson(data)
188+
// Prefix Body Data with body_key_
189+
prefixedBody := prefixData("body_key_", normalizedBody)
190+
191+
// Prefix Header Data with header_key_
192+
prefixedHeaders := prefixData("header_key_", headers)
193+
194+
variables := VARIABLES
195+
196+
maps.Copy(variables, prefixedBody)
197+
maps.Copy(variables, prefixedHeaders)
137198

138-
modified = beforeStr == afterStr
199+
templatedData, err := templating.RenderJSON("body", normalizedBody, variables)
200+
201+
if err != nil {
202+
return body, false, err
203+
}
204+
205+
beforeStr := jsonutils.ToJson(body)
206+
afterStr := jsonutils.ToJson(templatedData)
207+
208+
modified = beforeStr != afterStr
139209

140210
return templatedData, modified, nil
141211
}
@@ -184,4 +254,4 @@ func TemplateQuery(reqUrl *url.URL, data map[string]any, VARIABLES any) (string,
184254
reqRawQuery := originalQueryData.Encode()
185255

186256
return reqRawQuery, data, modified, nil
187-
}
257+
}

tests/json_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ func TestJsonTemplating(t *testing.T) {
4444
"key2": 4,
4545
}
4646

47-
got, err := templating.RenderJSONTemplate("json", data, variables)
47+
got, err := templating.RenderDataKeyTemplateRecursive("", data, variables)
4848

4949
if err != nil {
50-
t.Error("Error Templating JSON: ", err.Error())
50+
t.Error("Error Templating JSON:\n", err.Error())
5151
}
5252

5353
expectedStr := jsonutils.ToJson(expected)

utils/request/request.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ func GetBody(req *http.Request) ([]byte, error) {
9595
return bodyBytes, nil
9696
}
9797

98+
func GetReqHeaders(req *http.Request) (map[string]any) {
99+
data := map[string]any{}
100+
101+
for key, value := range req.Header {
102+
data[key] = value
103+
}
104+
105+
return data
106+
}
107+
98108
func GetReqBody(w http.ResponseWriter, req *http.Request) (Body, error) {
99109
bytes, err := GetBody(req)
100110

utils/stringutils/stringutils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package safestrings
1+
package stringutils
22

33
import (
44
"regexp"

0 commit comments

Comments
 (0)