Custom Transforms in SGNL allow you to modify the structure and content of responses from the SGNL Access API and Search API, enabling you to tailor the output to meet the specific requirements of your Protected Systems. While SGNL has predefined transforms for platforms like Okta and Entra ID, Custom Transforms provide complete flexibility to format responses for any integration scenario.
With Custom Transforms, you can:
You can find information about the SGNL Access API here. One key difference however, when using transforms – you will need to append transform=true
as a query parameter to your request.
So if you’re requesting an Access API response without transforms from the access api https://wholesalechips.sgnlapis.cloud/access/v2/evaluations
, to see a transformed response, you should send the request to https://wholesalechips.sgnlapis.cloud/access/v2/evaluations?transform=true
.
Below is a simple example of a custom transform that modifies an Access API response to return the Principal ID and an array of decisions:
{
"PrincipalId": "{{.principalId}}",
"Decisions": [
{{- range $i, $item := .decisions}}{{if $i}},{{end}}
{
"assetId": "{{$item.assetId}}",
"allowOrDeny": "{{$item.decision}}"
}
{{- end}}
]
}
This transform converts the standard SGNL Access API response into a simpler format that includes just the Principal ID and a list of decision objects containing the asset ID and the allow/deny decision.
SGNL’s Custom Transforms support a wide range of template functions that you can use to manipulate data. Below are the categories of functions available:
Function | Usage Example | Description |
---|---|---|
uppercase | {{uppercase .Name}} | Converts string to uppercase |
lowercase | {{lowercase .Name}} | Converts string to lowercase |
trim | {{trim .Value}} | Trims whitespace |
substring | {{substring .Text 0 5}} | Gets substring |
replace | {{replace .Text “a” “b”}} | Replaces substrings |
split | {{split .Text “,”}} | Splits string into slice |
join | {{join .Slice “,”}} | Joins slice into string |
contains | {{contains .Text “foo”}} | Checks substring presence |
hasPrefix | {{hasPrefix .Text “pre”}} | Checks prefix |
hasSuffix | {{hasSuffix .Text “end”}} | Checks suffix |
padLeft | {{padLeft .Text 10 " “}} | Pads string on the left |
padRight | {{padRight .Text 10 " “}} | Pads string on the right |
Function | Usage Example | Description |
---|---|---|
abs | {{abs .Value}} | Absolute value |
floor | {{floor .Value}} | Floor value |
ceil | {{ceil .Value}} | Ceiling value |
round | {{round .Value}} | Rounded value |
formatNumber | {{formatNumber .Value}} | Formats number |
add | {{add 1 2}} | Addition |
sub | {{sub 5 3}} | Subtraction |
mul | {{mul 2 3}} | Multiplication |
div | {{div 10 2}} | Division |
Function | Usage Example | Description |
---|---|---|
sort | {{sort .Numbers}} | Sorts array |
reverse | {{reverse .Numbers}} | Reverses array |
distinct | {{distinct .Numbers}} | Removes duplicates |
count | {{count .Numbers}} | Gets length |
first | {{first .Numbers}} | First element |
last | {{last .Numbers}} | Last element |
filter | {{filter .Numbers fn}} | Filters array |
map | {{map .Numbers fn}} | Maps function over array |
append | {{append .Numbers 4}} | Appends value |
slice | {{slice .Numbers 1 3}} | Slices array |
createSlice | {{createSlice 1 2 3}} | Creates array |
flatten | {{flatten .Nested}} | Flattens nested arrays |
zip | {{zip .Arr1 .Arr2}} | Zips arrays |
groupBy | {{groupBy .Arr “Field”}} | Groups by field |
orderBy | {{orderBy .Arr “Field”}} | Orders by field |
Function | Usage Example | Description |
---|---|---|
merge | {{merge .Obj1 .Obj2}} | Merges objects |
pick | {{pick .Obj “Field”}} | Picks fields |
omit | {{omit .Obj “Field”}} | Omits fields |
Function | Usage Example | Description |
---|---|---|
path | {{path .Obj “a.b.c”}} | Gets nested value |
at | {{at .Arr 2}} | Gets array element |
keys | {{keys .Obj}} | Gets object keys |
values | {{values .Obj}} | Gets object values |
entries | {{entries .Obj}} | Gets object entries |
get | {{get .Obj “key”}} | Gets value by key |
set | {{set .Obj “key” “val”}} | Sets value by key |
Function | Usage Example | Description |
---|---|---|
default | {{default .Value “fallback”}} | Uses fallback if value is empty |
Function | Usage Example | Description |
---|---|---|
toJSON | {{toJSON .Obj}} | Converts to JSON string |
fromJSON | {{fromJSON .JSONString}} | Parses JSON string |
{
"principalIdentifier": "{{.principalId}}",
"access": {
{{- if gt (count .decisions) 0}}
"status": "{{if eq (first .decisions).decision "Allow"}}granted{{else}}denied{{end}}",
"resources": [
{{- range $i, $item := .decisions}}{{if $i}},{{end}}
{
"resourceId": "{{$item.assetId}}",
"permitted": {{if eq $item.decision "Allow"}}true{{else}}false{{end}},
"timestamp": "{{.issuedAt}}"
}
{{- end}}
]
{{- else}}
"status": "no_decisions",
"resources": []
{{- end}}
},
"metadata": {
"evaluationTime": "{{.evaluationDuration}}ms",
"transformedAt": "{{.issuedAt}}"
}
}
{
"query": "{{.query}}",
"results": [
{{- range $i, $item := .results}}{{if $i}},{{end}}
{
"id": "{{$item.id}}",
"type": "{{$item.type}}",
"attributes": {{toJSON (pick $item.attributes "name" "email" "department")}}
}
{{- end}}
],
"summary": {
"totalCount": {{count .results}},
"filteredCount": {{count (filter .results (lambda "item" "eq item.type \"user\""))}}
}
}
When creating Custom Transforms, consider the following best practices:
Test thoroughly: Validate your transforms with different API responses to ensure they handle all possible scenarios.
Handle edge cases: Include conditional logic to handle empty arrays, null values, or unexpected data structures.
Keep it simple: Create transforms that are as simple as possible while meeting your requirements. Complex transforms can be difficult to maintain.
Document your transforms: Add comments to your transform templates to explain complex logic or non-obvious transformations.
Use template functions: Leverage the wide variety of template functions to simplify your transforms and avoid reinventing the wheel.
Consider performance: Very complex transforms may impact response times. Optimize your transforms for performance when necessary.
SGNL’s Custom Transforms are well-suited for generating structured JSON outputs. Below is a detailed guide on using Go templates effectively in your SGNL transforms.
When working with templates for JSON generation in SGNL, you’ll commonly use these key features:
Variable Substitution
{{.principalId}}
, {{.evaluationDuration}}
{{.decisions[0].assetId}}
Conditional Statements
{{- if .description}} ... {{- end}}
{{- if eq .decision "Allow"}} ... {{- else}} ... {{- end}}
Loops
{{- range $index, $decision := .decisions}} ... {{- end}}
{{- if $index}},{{end}}
(for comma management in JSON arrays)Whitespace Control
{{-
and -}}
control whitespace before and after template actionsComments
{{/* This is a comment */}}
- not rendered in outputHere’s a more comprehensive example that demonstrates the power of SGNL templates for generating complex JSON structures:
{{- /* Example transform template for SGNL Access API */ -}}
{
"company": "SGNL",
"principal": {
"id": "{{.principalId}}",
"type": "{{default .principalType "user"}}"
},
"accessDecisions": {
"timestamp": "{{{{.issuedAt}}}}",
"evaluationMs": {{.evaluationDuration}},
{{- if gt (count .decisions) 0}}
"status": "{{if eq (first .decisions).decision "Allow"}}granted{{else}}denied{{end}}",
"assets": [
{{- range $index, $decision := .decisions}}
{{- if $index}},{{end}}
{
"id": "{{$decision.assetId}}",
{{- if $decision.action}}
"action": "{{$decision.action}}",
{{- end}}
"permitted": {{if eq $decision.decision "Allow"}}true{{else}}false{{end}},
"metadata": {
"policyIds": [
{{- range $pIndex, $policy := $decision.policies}}
{{- if $pIndex}},{{end}}
"{{$policy.policyId}}"
{{- end}}
],
"timestamp": "{{{{.issuedAt}}}}"
}
}
{{- end}}
]
{{- else}}
"status": "no_decisions",
"assets": []
{{- end}}
}
}
This example shows a transform that:
default
function for fallback valuesHandling Commas in Arrays: Use the index in range loops to avoid invalid trailing commas:
{{- range $index, $item := .items}}
{{- if $index}},{{end}}
{ "item": "{{$item}}" }
{{- end}}
Escaping Special Characters: Be mindful of escaping quotes and special characters in JSON strings:
"description": "{{replace .description "\"" "\\\""}}"
Null Values: Handle potential null values with conditional statements or the default
function:
"department": "{{default .department "Unassigned"}}"
Boolean Values: Ensure proper JSON boolean formatting:
"active": {{if .active}}true{{else}}false{{end}}
Nested Objects: For complex nested structures, consider breaking them into logical sections with whitespace control:
"user": {
{{- if .userName}}
"name": "{{.userName}}",
{{- end}}
"roles": [
{{- range $index, $role := .userRoles}}
{{- if $index}},{{end}}
"{{$role}}"
{{- end}}
]
}