From e02598a453341122f05dc6b2803490b84376b209 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 17 Jan 2023 15:26:33 +0000 Subject: [PATCH 1/4] pairing --- enterprise/audit/diff.go | 77 +++++++++++++++++++++----- enterprise/audit/diff_internal_test.go | 48 ++++++++++++++++ 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/enterprise/audit/diff.go b/enterprise/audit/diff.go index 05d46499b525a..f327fb08b5d9f 100644 --- a/enterprise/audit/diff.go +++ b/enterprise/audit/diff.go @@ -15,14 +15,52 @@ func structName(t reflect.Type) string { return t.PkgPath() + "." + t.Name() } +type FieldDiff struct { + FieldType reflect.StructField + LeftF reflect.Value + RightF reflect.Value +} + +func flattenStructFields(left, right any) []FieldDiff { + leftV := reflect.ValueOf(left) + rightV := reflect.ValueOf(right) + + allFields := []FieldDiff{} + rightT := rightV.Type() + + // Flatten the structure and all fields. + // Does not support named nested structs. + for i := 0; i < rightT.NumField(); i++ { + if !rightT.Field(i).IsExported() { + continue + } + + var ( + leftF = leftV.Field(i) + rightF = rightV.Field(i) + ) + + if rightT.Field(i).Anonymous { + // Loop through anonymous type for fields, + // append as top level fields for diffs. + allFields = append(allFields, flattenStructFields(leftF.Interface(), rightF.Interface())...) + continue + } + + // Single fields append as is. + allFields = append(allFields, FieldDiff{ + LeftF: leftF, + RightF: rightF, + FieldType: rightT.Field(i), + }) + } + return allFields +} + func diffValues(left, right any, table Table) audit.Map { var ( baseDiff = audit.Map{} - - leftV = reflect.ValueOf(left) - - rightV = reflect.ValueOf(right) - rightT = reflect.TypeOf(right) + rightT = reflect.TypeOf(right) diffKey = table[structName(rightT)] ) @@ -31,21 +69,34 @@ func diffValues(left, right any, table Table) audit.Map { panic(fmt.Sprintf("dev error: type %q (type %T) attempted audit but not auditable", rightT.Name(), right)) } - for i := 0; i < rightT.NumField(); i++ { - if !rightT.Field(i).IsExported() { - continue - } - + allFields := flattenStructFields(left, right) + fmt.Println("AllFields", allFields) + for _, field := range allFields { var ( - leftF = leftV.Field(i) - rightF = rightV.Field(i) + leftF = field.LeftF + rightF = field.RightF leftI = leftF.Interface() rightI = rightF.Interface() + ) + + // This is the field that is returning a blank string. + fmt.Printf("Number of fields for %s: %d\n", rightT.String(), rightT.NumField()) + // rightT.Field(i) - diffName = rightT.Field(i).Tag.Get("json") + var ( + diffName = field.FieldType.Tag.Get("json") ) +<<<<<<< Updated upstream +======= + // fmt.Println("rightT.Field(i)", rightT, rightT.Field(i), rightT.Field(i).Tag.Get("json")) + + // map[avatar_url:track id:track members:track name:track organization_id:ignore quota_allowance:track] + fmt.Println("DIFF KEY", diffKey) +>>>>>>> Stashed changes + // group + fmt.Println("DIFF NAME", diffName) atype, ok := diffKey[diffName] if !ok { panic(fmt.Sprintf("dev error: field %q lacks audit information", diffName)) diff --git a/enterprise/audit/diff_internal_test.go b/enterprise/audit/diff_internal_test.go index bf918a6f97c1d..e0893b0dd0034 100644 --- a/enterprise/audit/diff_internal_test.go +++ b/enterprise/audit/diff_internal_test.go @@ -97,6 +97,54 @@ func Test_diffValues(t *testing.T) { }, }) }) + t.Run("EmbeddedStruct", func(t *testing.T) { + t.Parallel() + + type Bar struct { + Baz int `json:"baz"` + Buzz string `json:"buzz"` + } + + type foo struct { + Bar + } + + table := auditMap(map[any]map[string]Action{ + &foo{}: { + "baz": ActionTrack, + "buzz": ActionTrack, + }, + // &Bar{}: { + // "baz": ActionTrack, + // "buzz": ActionTrack, + // }, + }) + + runDiffValuesTests(t, table, []diffTest{ + { + name: "SingleFieldChange", + left: foo{Bar: Bar{Baz: 1, Buzz: "before"}}, right: foo{Bar: Bar{Baz: 0, Buzz: "after"}}, + exp: audit.Map{ + "baz": audit.OldNew{Old: 1, New: 0}, + "buzz": audit.OldNew{Old: "before", New: "after"}, + }, + }, + // { + // name: "LeftNil", + // left: foo{Bar: Bar{Baz: 1}}, right: foo{Bar: pointer.StringPtr("baz")}, + // exp: audit.Map{ + // "bar": audit.OldNew{Old: "", New: "baz"}, + // }, + // }, + // { + // name: "RightNil", + // left: foo{Bar: pointer.StringPtr("baz")}, right: foo{Bar: nil}, + // exp: audit.Map{ + // "bar": audit.OldNew{Old: "baz", New: ""}, + // }, + // }, + }) + }) // We currently don't support nested structs. // t.Run("NestedStruct", func(t *testing.T) { From b4ecea315a28b1c8b3516f2e89d77878092483fe Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 17 Jan 2023 15:26:48 +0000 Subject: [PATCH 2/4] merge commit --- enterprise/audit/diff.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/enterprise/audit/diff.go b/enterprise/audit/diff.go index f327fb08b5d9f..f590fd7e861ed 100644 --- a/enterprise/audit/diff.go +++ b/enterprise/audit/diff.go @@ -87,13 +87,10 @@ func diffValues(left, right any, table Table) audit.Map { var ( diffName = field.FieldType.Tag.Get("json") ) -<<<<<<< Updated upstream -======= // fmt.Println("rightT.Field(i)", rightT, rightT.Field(i), rightT.Field(i).Tag.Get("json")) // map[avatar_url:track id:track members:track name:track organization_id:ignore quota_allowance:track] fmt.Println("DIFF KEY", diffKey) ->>>>>>> Stashed changes // group fmt.Println("DIFF NAME", diffName) From a116c59cc0b5f80b015b06199be335f117c0daec Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Jan 2023 10:16:31 -0600 Subject: [PATCH 3/4] Cleanup implementation and add unit tests - Unit tests for anonymously embedded structs - Add docs and refactor code around --- enterprise/audit/diff.go | 123 ++++++++++++++----------- enterprise/audit/diff_internal_test.go | 76 ++++++++++----- 2 files changed, 121 insertions(+), 78 deletions(-) diff --git a/enterprise/audit/diff.go b/enterprise/audit/diff.go index f590fd7e861ed..afa6d88f494f8 100644 --- a/enterprise/audit/diff.go +++ b/enterprise/audit/diff.go @@ -6,6 +6,7 @@ import ( "reflect" "github.com/google/uuid" + "golang.org/x/xerrors" "github.com/coder/coder/coderd/audit" "github.com/coder/coder/coderd/database" @@ -15,52 +16,12 @@ func structName(t reflect.Type) string { return t.PkgPath() + "." + t.Name() } -type FieldDiff struct { - FieldType reflect.StructField - LeftF reflect.Value - RightF reflect.Value -} - -func flattenStructFields(left, right any) []FieldDiff { - leftV := reflect.ValueOf(left) - rightV := reflect.ValueOf(right) - - allFields := []FieldDiff{} - rightT := rightV.Type() - - // Flatten the structure and all fields. - // Does not support named nested structs. - for i := 0; i < rightT.NumField(); i++ { - if !rightT.Field(i).IsExported() { - continue - } - - var ( - leftF = leftV.Field(i) - rightF = rightV.Field(i) - ) - - if rightT.Field(i).Anonymous { - // Loop through anonymous type for fields, - // append as top level fields for diffs. - allFields = append(allFields, flattenStructFields(leftF.Interface(), rightF.Interface())...) - continue - } - - // Single fields append as is. - allFields = append(allFields, FieldDiff{ - LeftF: leftF, - RightF: rightF, - FieldType: rightT.Field(i), - }) - } - return allFields -} - func diffValues(left, right any, table Table) audit.Map { var ( baseDiff = audit.Map{} rightT = reflect.TypeOf(right) + leftV = reflect.ValueOf(left) + rightV = reflect.ValueOf(right) diffKey = table[structName(rightT)] ) @@ -69,8 +30,14 @@ func diffValues(left, right any, table Table) audit.Map { panic(fmt.Sprintf("dev error: type %q (type %T) attempted audit but not auditable", rightT.Name(), right)) } - allFields := flattenStructFields(left, right) - fmt.Println("AllFields", allFields) + // allFields contains all top level fields of the struct. + allFields, err := flattenStructFields(leftV, rightV) + if err != nil { + // This should never happen. Only structs should be flattened. If an + // error occurs, an unsupported or non-struct type was passed in. + panic(fmt.Sprintf("dev error: failed to flatten struct fields: %v", err)) + } + for _, field := range allFields { var ( leftF = field.LeftF @@ -80,20 +47,10 @@ func diffValues(left, right any, table Table) audit.Map { rightI = rightF.Interface() ) - // This is the field that is returning a blank string. - fmt.Printf("Number of fields for %s: %d\n", rightT.String(), rightT.NumField()) - // rightT.Field(i) - var ( diffName = field.FieldType.Tag.Get("json") ) - // fmt.Println("rightT.Field(i)", rightT, rightT.Field(i), rightT.Field(i).Tag.Get("json")) - - // map[avatar_url:track id:track members:track name:track organization_id:ignore quota_allowance:track] - fmt.Println("DIFF KEY", diffKey) - // group - fmt.Println("DIFF NAME", diffName) atype, ok := diffKey[diffName] if !ok { panic(fmt.Sprintf("dev error: field %q lacks audit information", diffName)) @@ -193,6 +150,64 @@ func convertDiffType(left, right any) (newLeft, newRight any, changed bool) { } } +// fieldDiff has all the required information to return an audit diff for a +// given field. +type fieldDiff struct { + FieldType reflect.StructField + LeftF reflect.Value + RightF reflect.Value +} + +// flattenStructFields will return all top level fields for a given structure. +// Only anonymously embedded structs will be recursively flattened such that their +// fields are returned as top level fields. Named nested structs will be returned +// as a single field. +// Conflicting field names need to be handled by the caller. +func flattenStructFields(leftV, rightV reflect.Value) ([]fieldDiff, error) { + // Dereference pointers if the field is a pointer field. + if leftV.Kind() == reflect.Ptr { + leftV = derefPointer(leftV) + rightV = derefPointer(rightV) + } + + if leftV.Kind() != reflect.Struct { + return nil, xerrors.Errorf("%q is not a struct, kind=%s", leftV.String(), leftV.Kind()) + } + + var allFields []fieldDiff + rightT := rightV.Type() + + // Loop through all top level fields of the struct. + for i := 0; i < rightT.NumField(); i++ { + if !rightT.Field(i).IsExported() { + continue + } + + var ( + leftF = leftV.Field(i) + rightF = rightV.Field(i) + ) + + if rightT.Field(i).Anonymous { + // Anonymous fields are recursively flattened. + anonFields, err := flattenStructFields(leftF, rightF) + if err != nil { + return nil, xerrors.Errorf("flatten anonymous field %q: %w", rightT.Field(i).Name, err) + } + allFields = append(allFields, anonFields...) + continue + } + + // Single fields append as is. + allFields = append(allFields, fieldDiff{ + LeftF: leftF, + RightF: rightF, + FieldType: rightT.Field(i), + }) + } + return allFields, nil +} + // derefPointer deferences a reflect.Value that is a pointer to its underlying // value. It dereferences recursively until it finds a non-pointer value. If the // pointer is nil, it will be coerced to the zero value of the underlying type. diff --git a/enterprise/audit/diff_internal_test.go b/enterprise/audit/diff_internal_test.go index e0893b0dd0034..c3bac9f67c7b8 100644 --- a/enterprise/audit/diff_internal_test.go +++ b/enterprise/audit/diff_internal_test.go @@ -97,6 +97,7 @@ func Test_diffValues(t *testing.T) { }, }) }) + t.Run("EmbeddedStruct", func(t *testing.T) { t.Parallel() @@ -105,44 +106,71 @@ func Test_diffValues(t *testing.T) { Buzz string `json:"buzz"` } + type PtrBar struct { + Qux string `json:"qux"` + } + type foo struct { Bar + *PtrBar + TopLevel string `json:"top_level"` } table := auditMap(map[any]map[string]Action{ &foo{}: { - "baz": ActionTrack, - "buzz": ActionTrack, + "baz": ActionTrack, + "buzz": ActionTrack, + "qux": ActionTrack, + "top_level": ActionTrack, }, - // &Bar{}: { - // "baz": ActionTrack, - // "buzz": ActionTrack, - // }, }) runDiffValuesTests(t, table, []diffTest{ { - name: "SingleFieldChange", - left: foo{Bar: Bar{Baz: 1, Buzz: "before"}}, right: foo{Bar: Bar{Baz: 0, Buzz: "after"}}, + name: "SingleFieldChange", + left: foo{TopLevel: "top-before", Bar: Bar{Baz: 1, Buzz: "before"}, PtrBar: &PtrBar{Qux: "qux-before"}}, + right: foo{TopLevel: "top-after", Bar: Bar{Baz: 0, Buzz: "after"}, PtrBar: &PtrBar{Qux: "qux-after"}}, + exp: audit.Map{ + "baz": audit.OldNew{Old: 1, New: 0}, + "buzz": audit.OldNew{Old: "before", New: "after"}, + "qux": audit.OldNew{Old: "qux-before", New: "qux-after"}, + "top_level": audit.OldNew{Old: "top-before", New: "top-after"}, + }, + }, + { + name: "Empty", + left: foo{}, + right: foo{}, + exp: audit.Map{}, + }, + { + name: "NoChange", + left: foo{TopLevel: "top-before", Bar: Bar{Baz: 1, Buzz: "before"}, PtrBar: &PtrBar{Qux: "qux-before"}}, + right: foo{TopLevel: "top-before", Bar: Bar{Baz: 1, Buzz: "before"}, PtrBar: &PtrBar{Qux: "qux-before"}}, + exp: audit.Map{}, + }, + { + name: "LeftEmpty", + left: foo{}, + right: foo{TopLevel: "top-after", Bar: Bar{Baz: 1, Buzz: "after"}, PtrBar: &PtrBar{Qux: "qux-after"}}, + exp: audit.Map{ + "baz": audit.OldNew{Old: 0, New: 1}, + "buzz": audit.OldNew{Old: "", New: "after"}, + "qux": audit.OldNew{Old: "", New: "qux-after"}, + "top_level": audit.OldNew{Old: "", New: "top-after"}, + }, + }, + { + name: "RightNil", + left: foo{TopLevel: "top-before", Bar: Bar{Baz: 1, Buzz: "before"}, PtrBar: &PtrBar{Qux: "qux-before"}}, + right: foo{}, exp: audit.Map{ - "baz": audit.OldNew{Old: 1, New: 0}, - "buzz": audit.OldNew{Old: "before", New: "after"}, + "baz": audit.OldNew{Old: 1, New: 0}, + "buzz": audit.OldNew{Old: "before", New: ""}, + "qux": audit.OldNew{Old: "qux-before", New: ""}, + "top_level": audit.OldNew{Old: "top-before", New: ""}, }, }, - // { - // name: "LeftNil", - // left: foo{Bar: Bar{Baz: 1}}, right: foo{Bar: pointer.StringPtr("baz")}, - // exp: audit.Map{ - // "bar": audit.OldNew{Old: "", New: "baz"}, - // }, - // }, - // { - // name: "RightNil", - // left: foo{Bar: pointer.StringPtr("baz")}, right: foo{Bar: nil}, - // exp: audit.Map{ - // "bar": audit.OldNew{Old: "baz", New: ""}, - // }, - // }, }) }) From e3c604b9f296c13b10986cd4227098d94e9bafec Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Jan 2023 12:02:11 -0600 Subject: [PATCH 4/4] Nolint extra newline --- enterprise/audit/diff_internal_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/audit/diff_internal_test.go b/enterprise/audit/diff_internal_test.go index c3bac9f67c7b8..5df4b3b893d73 100644 --- a/enterprise/audit/diff_internal_test.go +++ b/enterprise/audit/diff_internal_test.go @@ -66,7 +66,6 @@ func Test_diffValues(t *testing.T) { }) }) - //nolint:revive t.Run("PointerField", func(t *testing.T) { t.Parallel() @@ -98,6 +97,7 @@ func Test_diffValues(t *testing.T) { }) }) + //nolint:revive t.Run("EmbeddedStruct", func(t *testing.T) { t.Parallel() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy