-
Notifications
You must be signed in to change notification settings - Fork 388
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into feat/fomo3d
- Loading branch information
Showing
60 changed files
with
3,470 additions
and
6,733 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/p/demo/avl/rolist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Package rolist provides a read-only wrapper for list.List with safe value transformation. | ||
// | ||
// It is useful when you want to expose a read-only view of a list while ensuring that | ||
// the sensitive data cannot be modified. | ||
// | ||
// Example: | ||
// | ||
// // Define a user structure with sensitive data | ||
// type User struct { | ||
// Name string | ||
// Balance int | ||
// Internal string // sensitive field | ||
// } | ||
// | ||
// // Create and populate the original list | ||
// privateList := list.New() | ||
// privateList.Append(&User{ | ||
// Name: "Alice", | ||
// Balance: 100, | ||
// Internal: "sensitive", | ||
// }) | ||
// | ||
// // Create a safe transformation function that copies the struct | ||
// // while excluding sensitive data | ||
// makeEntrySafeFn := func(v interface{}) interface{} { | ||
// u := v.(*User) | ||
// return &User{ | ||
// Name: u.Name, | ||
// Balance: u.Balance, | ||
// Internal: "", // omit sensitive data | ||
// } | ||
// } | ||
// | ||
// // Create a read-only view of the list | ||
// publicList := rolist.Wrap(list, makeEntrySafeFn) | ||
// | ||
// // Safely access the data | ||
// value := publicList.Get(0) | ||
// user := value.(*User) | ||
// // user.Name == "Alice" | ||
// // user.Balance == 100 | ||
// // user.Internal == "" (sensitive data is filtered) | ||
package rolist | ||
|
||
import ( | ||
"gno.land/p/demo/avl/list" | ||
) | ||
|
||
// IReadOnlyList defines the read-only operations available on a list. | ||
type IReadOnlyList interface { | ||
Len() int | ||
Get(index int) interface{} | ||
Slice(startIndex, endIndex int) []interface{} | ||
ForEach(fn func(index int, value interface{}) bool) | ||
} | ||
|
||
// ReadOnlyList wraps a list.List and provides read-only access. | ||
type ReadOnlyList struct { | ||
list *list.List | ||
makeEntrySafeFn func(interface{}) interface{} | ||
} | ||
|
||
// Verify interface implementations | ||
var _ IReadOnlyList = (*ReadOnlyList)(nil) | ||
var _ IReadOnlyList = (interface{ list.IList })(nil) // is subset of list.IList | ||
|
||
// Wrap creates a new ReadOnlyList from an existing list.List and a safety transformation function. | ||
// If makeEntrySafeFn is nil, values will be returned as-is without transformation. | ||
func Wrap(list *list.List, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyList { | ||
return &ReadOnlyList{ | ||
list: list, | ||
makeEntrySafeFn: makeEntrySafeFn, | ||
} | ||
} | ||
|
||
// getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value | ||
func (rol *ReadOnlyList) getSafeValue(value interface{}) interface{} { | ||
if rol.makeEntrySafeFn == nil { | ||
return value | ||
} | ||
return rol.makeEntrySafeFn(value) | ||
} | ||
|
||
// Len returns the number of elements in the list. | ||
func (rol *ReadOnlyList) Len() int { | ||
return rol.list.Len() | ||
} | ||
|
||
// Get returns the value at the specified index, converted to a safe format. | ||
// Returns nil if index is out of bounds. | ||
func (rol *ReadOnlyList) Get(index int) interface{} { | ||
value := rol.list.Get(index) | ||
if value == nil { | ||
return nil | ||
} | ||
return rol.getSafeValue(value) | ||
} | ||
|
||
// Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive), | ||
// with all values converted to a safe format. | ||
func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []interface{} { | ||
values := rol.list.Slice(startIndex, endIndex) | ||
if values == nil { | ||
return nil | ||
} | ||
|
||
result := make([]interface{}, len(values)) | ||
for i, v := range values { | ||
result[i] = rol.getSafeValue(v) | ||
} | ||
return result | ||
} | ||
|
||
// ForEach iterates through all elements in the list, providing safe versions of the values. | ||
func (rol *ReadOnlyList) ForEach(fn func(index int, value interface{}) bool) { | ||
rol.list.ForEach(func(index int, value interface{}) bool { | ||
return fn(index, rol.getSafeValue(value)) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package rolist | ||
|
||
import ( | ||
"testing" | ||
|
||
"gno.land/p/demo/avl/list" | ||
) | ||
|
||
func TestExample(t *testing.T) { | ||
// User represents our internal data structure | ||
type User struct { | ||
ID string | ||
Name string | ||
Balance int | ||
Internal string // sensitive internal data | ||
} | ||
|
||
// Create and populate the original list | ||
l := &list.List{} | ||
l.Append( | ||
&User{ | ||
ID: "1", | ||
Name: "Alice", | ||
Balance: 100, | ||
Internal: "sensitive_data_1", | ||
}, | ||
&User{ | ||
ID: "2", | ||
Name: "Bob", | ||
Balance: 200, | ||
Internal: "sensitive_data_2", | ||
}, | ||
) | ||
|
||
// Define a makeEntrySafeFn that: | ||
// 1. Creates a defensive copy of the User struct | ||
// 2. Omits sensitive internal data | ||
makeEntrySafeFn := func(v interface{}) interface{} { | ||
originalUser := v.(*User) | ||
return &User{ | ||
ID: originalUser.ID, | ||
Name: originalUser.Name, | ||
Balance: originalUser.Balance, | ||
Internal: "", // Omit sensitive data | ||
} | ||
} | ||
|
||
// Create a read-only view of the list | ||
roList := Wrap(l, makeEntrySafeFn) | ||
|
||
// Test retrieving and verifying a user | ||
t.Run("Get User", func(t *testing.T) { | ||
// Get user from read-only list | ||
value := roList.Get(0) | ||
if value == nil { | ||
t.Fatal("User at index 0 not found") | ||
} | ||
|
||
user := value.(*User) | ||
|
||
// Verify user data is correct | ||
if user.Name != "Alice" || user.Balance != 100 { | ||
t.Errorf("Unexpected user data: got name=%s balance=%d", user.Name, user.Balance) | ||
} | ||
|
||
// Verify sensitive data is not exposed | ||
if user.Internal != "" { | ||
t.Error("Sensitive data should not be exposed") | ||
} | ||
|
||
// Verify it's a different instance than the original | ||
originalUser := l.Get(0).(*User) | ||
if user == originalUser { | ||
t.Error("Read-only list should return a copy, not the original pointer") | ||
} | ||
}) | ||
|
||
// Test slice functionality | ||
t.Run("Slice Users", func(t *testing.T) { | ||
users := roList.Slice(0, 2) | ||
if len(users) != 2 { | ||
t.Fatalf("Expected 2 users, got %d", len(users)) | ||
} | ||
|
||
for _, v := range users { | ||
user := v.(*User) | ||
if user.Internal != "" { | ||
t.Error("Sensitive data exposed in slice") | ||
} | ||
} | ||
}) | ||
|
||
// Test ForEach functionality | ||
t.Run("ForEach Users", func(t *testing.T) { | ||
count := 0 | ||
roList.ForEach(func(index int, value interface{}) bool { | ||
user := value.(*User) | ||
if user.Internal != "" { | ||
t.Error("Sensitive data exposed during iteration") | ||
} | ||
count++ | ||
return false | ||
}) | ||
|
||
if count != 2 { | ||
t.Errorf("Expected 2 users, got %d", count) | ||
} | ||
}) | ||
} | ||
|
||
func TestNilMakeEntrySafeFn(t *testing.T) { | ||
// Create a list with some test data | ||
l := &list.List{} | ||
originalValue := []int{1, 2, 3} | ||
l.Append(originalValue) | ||
|
||
// Create a ReadOnlyList with nil makeEntrySafeFn | ||
roList := Wrap(l, nil) | ||
|
||
// Test that we get back the original value | ||
value := roList.Get(0) | ||
if value == nil { | ||
t.Fatal("Value not found") | ||
} | ||
|
||
// Verify it's the exact same slice (not a copy) | ||
retrievedSlice := value.([]int) | ||
if &retrievedSlice[0] != &originalValue[0] { | ||
t.Error("Expected to get back the original slice reference") | ||
} | ||
} | ||
|
||
func TestReadOnlyList(t *testing.T) { | ||
// Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation | ||
makeEntrySafeFn := func(value interface{}) interface{} { | ||
return value.(string) + "_readonly" | ||
} | ||
|
||
l := &list.List{} | ||
l.Append("value1", "value2", "value3") | ||
|
||
roList := Wrap(l, makeEntrySafeFn) | ||
|
||
tests := []struct { | ||
name string | ||
index int | ||
expected interface{} | ||
}{ | ||
{"ExistingIndex0", 0, "value1_readonly"}, | ||
{"ExistingIndex1", 1, "value2_readonly"}, | ||
{"NonExistingIndex", 3, nil}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
value := roList.Get(tt.index) | ||
if value != tt.expected { | ||
t.Errorf("For index %d, expected %v, got %v", tt.index, tt.expected, value) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.