Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
bobgroup-go-utils
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Bob Public Utils
bobgroup-go-utils
Merge requests
!23
New function to format audit logs in json recursively
Code
Review changes
Check out branch
Download
Patches
Plain diff
Expand sidebar
Merged
New function to format audit logs in json recursively
new-audit-log-formatting-function
into
main
Overview
0
Commits
2
Pipelines
0
Changes
1
Merged
Ruaan Burger
requested to merge
new-audit-log-formatting-function
into
main
3 years ago
Overview
0
Commits
2
Pipelines
0
Changes
1
0
0
Merge request reports
Compare
main
main (base)
and
latest version
latest version
33c85547
2 commits,
3 years ago
1 file
+
150
−
4
Inline
Compare changes
Side-by-side
Inline
Show whitespace changes
Show one file at a time
audit/audit.go
+
150
−
4
View file @ 33c85547
Edit in single-file editor
Open in Web IDE
Show full file
package
audit
import
(
"reflect"
"regexp"
"strings"
"encoding/json"
"github.com/r3labs/diff/v2"
"gitlab.com/uafrica/go-utils/reflection"
"gitlab.com/uafrica/go-utils/string_utils"
"reflect"
"regexp"
"strconv"
"strings"
)
type
FieldChange
struct
{
@@ -111,6 +112,151 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
return
changes
,
nil
}
// GetAllChanges Returns the diff, structured in json, recursively
// Be warned, here be dragons. Debug this first to understand how it works
func
GetAllChanges
(
original
interface
{},
new
interface
{})
(
map
[
string
]
interface
{},
error
)
{
changes
:=
map
[
string
]
interface
{}{}
changelog
,
err
:=
diff
.
Diff
(
original
,
new
)
if
err
!=
nil
{
return
changes
,
err
}
changesJson
:=
"{"
subArrays
:=
map
[
string
]
string
{}
for
_
,
change
:=
range
changelog
{
var
value
string
// Keep track of the core value
var
key
string
// Keep track of the key/s for this value
var
closing
string
// Keep track of the correct number of closing characters
for
pathIndex
:=
len
(
change
.
Path
)
-
1
;
pathIndex
>=
0
;
pathIndex
--
{
// If this is the first value, it's the "deepest" value of this change set, otherwise leave this var alone
// If this is set at the wrong point, other behaviour changes
if
pathIndex
+
1
==
len
(
change
.
Path
)
{
changedField
:=
FieldChange
{
From
:
change
.
From
,
To
:
change
.
To
,
}
request
,
err
:=
json
.
Marshal
(
changedField
)
if
err
!=
nil
{
return
nil
,
err
}
value
=
string
(
request
)
value
=
value
[
1
:
len
(
value
)
-
1
]
}
// If this "key" is integer-like, we handle it a little differently to ensure related data ends up
// together and formatted as you would expect JSON arrays to look
if
_
,
err
=
strconv
.
Atoi
(
change
.
Path
[
pathIndex
]);
err
==
nil
{
positionOfSubArray
:=
""
for
sub
:=
0
;
sub
<=
pathIndex
;
sub
++
{
positionOfSubArray
=
positionOfSubArray
+
change
.
Path
[
sub
]
}
// Add it to the placeholder data map used later on
subArrays
[
positionOfSubArray
]
=
subArrays
[
positionOfSubArray
]
+
key
+
value
+
closing
+
","
// Don't insert the same placeholder into the json multiple times
if
!
strings
.
Contains
(
changesJson
,
positionOfSubArray
)
{
value
=
positionOfSubArray
}
else
{
// Make sure this value doesn't end up populated explicitly
value
=
""
}
}
else
{
// Safety net so we don't compare to a non existant value
if
pathIndex
<
len
(
change
.
Path
)
-
1
{
// If the value this one "contains" has a integer-like key, this is probably an array
if
_
,
err
=
strconv
.
Atoi
(
change
.
Path
[
pathIndex
+
1
]);
err
==
nil
{
key
=
"
\"
"
+
ToSnakeCase
(
change
.
Path
[
pathIndex
])
+
"
\"
: ["
closing
=
"]"
}
else
{
key
=
"
\"
"
+
ToSnakeCase
(
change
.
Path
[
pathIndex
])
+
"
\"
: {"
+
key
closing
=
closing
+
"}"
}
}
else
{
key
=
"
\"
"
+
ToSnakeCase
(
change
.
Path
[
pathIndex
])
+
"
\"
: {"
+
key
closing
=
closing
+
"}"
}
}
}
// Don't insert empty values (happens when multiple values within an array are set, see "placeholder" behavior)
if
value
!=
""
{
// Duplicate key prevention
keySplit
:=
strings
.
Split
(
key
,
": "
)
lastOccurrence
:=
0
lastMatched
:=
""
levelsDeep
:=
0
for
sIndex
,
split
:=
range
keySplit
{
// Trim leading character off split
if
split
[
0
:
1
]
==
"{"
||
split
[
0
:
1
]
==
"["
{
split
=
split
[
1
:
]
}
// The final value of keySplit might be empty ("")
if
split
!=
""
{
if
index
:=
strings
.
Index
(
changesJson
,
split
);
index
>
-
1
&&
changesJson
!=
"{"
{
// Prevent reverse traversal
if
index
>
lastOccurrence
{
lastOccurrence
=
index
lastMatched
=
split
levelsDeep
=
sIndex
}
}
}
}
// If the "key" is already present, handle it differently
if
lastOccurrence
>
0
{
// Strip parent keys that are already present
key
=
key
[
strings
.
Index
(
key
,
lastMatched
)
+
len
(
lastMatched
)
:
]
if
key
[
0
:
2
]
==
": "
{
key
=
key
[
2
:
]
}
// Strip the correct amount of closing tags
closing
=
closing
[
:
len
(
closing
)
-
levelsDeep
]
// Find the position of this key in the master string
position
:=
strings
.
Index
(
changesJson
,
lastMatched
)
+
len
(
lastMatched
)
+
3
// We're appending to an existing object, so strip the outermost wrapping layer
key
=
key
[
1
:
]
closing
=
closing
[
:
len
(
closing
)
-
1
]
// Place the value within the existing key
changesJson
=
changesJson
[
:
position
]
+
key
+
value
+
closing
+
", "
+
changesJson
[
position
:
]
}
else
{
// No value found, append it to the end
changesJson
=
changesJson
+
key
+
value
+
closing
+
", "
}
}
}
// Trim whitespace and strip trailing comma since we are done inserting at the back
changesJson
=
strings
.
TrimSpace
(
changesJson
)
if
changesJson
[
len
(
changesJson
)
-
1
:
]
==
","
{
changesJson
=
changesJson
[
:
len
(
changesJson
)
-
1
]
}
changesJson
=
changesJson
+
"}"
// Now we can go make sure the placeholders are populated with the data in
for
placeholderKey
,
placeholderValue
:=
range
subArrays
{
// Trim the trailing comma since we won't be adding any more stuff
placeholderValue
=
strings
.
TrimSpace
(
placeholderValue
)
if
placeholderValue
[
len
(
placeholderValue
)
-
1
:
]
==
","
{
placeholderValue
=
placeholderValue
[
:
len
(
placeholderValue
)
-
1
]
}
changesJson
=
strings
.
ReplaceAll
(
changesJson
,
placeholderKey
,
"{"
+
placeholderValue
+
"}"
)
}
// Now, hopefully, the json parsing will pass and we have a nicely formatted dataset
err
=
json
.
Unmarshal
([]
byte
(
changesJson
),
&
changes
)
if
err
!=
nil
{
return
nil
,
err
}
return
changes
,
nil
}
var
matchFirstCap
=
regexp
.
MustCompile
(
"(.)([A-Z][a-z]+)"
)
var
matchAllCap
=
regexp
.
MustCompile
(
"([a-z0-9])([A-Z])"
)
Loading