From 6de67d9809068ce5b08128babd71f4868614f4b4 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Tue, 20 Aug 2024 23:18:44 +0700 Subject: [PATCH 1/6] init: adding support for arbitrary field --- examples/gno.land/r/demo/profile/profile.gno | 55 +++++++++++++++++++ .../gno.land/r/demo/profile/profile_test.gno | 52 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index cc7d80e016d..31daa126d2f 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -6,11 +6,13 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/mux" + "gno.land/p/demo/json" ) var ( fields = avl.NewTree() router = mux.NewRouter() + arbitraryFields = avl.NewTree() // address => *avl.Tree (field => value) ) const ( @@ -91,6 +93,32 @@ func SetBoolField(field string, value bool) error { return nil } +func SetField(field string, value interface{}) error { + addr := std.PrevRealm().Addr() + + if _, ok := boolFields[field]; ok { + return SetBoolField(field, value.(bool)) + } + + if _, ok := intFields[field]; ok { + return SetIntField(field, value.(int)) + } + + if _, ok := stringFields[field]; ok { + return SetStringField(field, value.(string)) + } + + caller := addr.String() + userFields, exists := arbitraryFields.Get(caller) + if !exists { + userFields = avl.NewTree() + arbitraryFields.Set(caller, userFields) + } + + userFields.(*avl.Tree).Set(field, value) + return nil +} + // Getters func GetStringField(addr std.Address, field, def string) string { @@ -119,3 +147,30 @@ func GetIntField(addr std.Address, field string, def int) int { return def } + +func GetField(addr std.Address, field string, def interface{}) interface{} { + if _, ok := boolFields[field]; ok { + return GetBoolField(addr, field, def.(bool)) + } + + if _, ok := intFields[field]; ok { + return GetIntField(addr, field, def.(int)) + } + + if _, ok := stringFields[field]; ok { + return GetStringField(addr, field, def.(string)) + } + + caller := addr.String() + userFields, exists := arbitraryFields.Get(caller) + if !exists { + return nil + } + + val, exists := userFields.(*avl.Tree).Get(field) + if !exists { + return nil + } + + return val +} \ No newline at end of file diff --git a/examples/gno.land/r/demo/profile/profile_test.gno b/examples/gno.land/r/demo/profile/profile_test.gno index 987632a594d..e4a57284fda 100644 --- a/examples/gno.land/r/demo/profile/profile_test.gno +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -3,6 +3,7 @@ package profile import ( "std" "testing" + "errors" "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" @@ -116,3 +117,54 @@ func TestMultipleProfiles(t *testing.T) { uassert.Equal(t, "User One", name1) uassert.Equal(t, "User Two", name2) } + +func TestSetMultiFields(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set new arbitrary string field + err := SetField("NewStrField", "This is new string value") + uassert.NoError(t, err) + + strVal := GetField(user1, "NewStrField", "") + uassert.Equal(t, strVal , "This is new string value") + + // Set new arbitrary int field + err = SetField("NewIntField", 40) + uassert.NoError(t, err) + + intVal := GetField(user1, "NewIntField", 0) + uassert.Equal(t, intVal , 40) + + // Set new arbitrary bool field + err = SetField("NewBoolField", true) + uassert.NoError(t, err) + + boolVal := GetField(user1, "NewBoolField", false) + uassert.Equal(t, boolVal , true) + + // Set reserved field + err = SetField("DisplayName", "My Name") + uassert.NoError(t, err) + + reservedStrVal := GetStringField(user1, "DisplayName", "") + uassert.Equal(t, reservedStrVal , "My Name") + + // Get reserved field + err = SetIntField("Age", 40) + uassert.NoError(t, err) + + reservedIntVal := GetField(user1, "Age", 0) + uassert.Equal(t, reservedIntVal , 40) + + // Get string field that does not exist should return default string + inexistStrVal := GetField(user1, "InexistStrField", "default") + uassert.Equal(t, inexistStrVal , "default") + + // Get int field that does not exist should return default int + inexistIntVal := GetField(user1, "inexistIntField", 0) + uassert.Equal(t, inexistIntVal , 0) + + // Get bool field that does not exist should return default bool + inexistBoolVal := GetField(user1, "inexistBoolField", false) + uassert.Equal(t, inexistBoolVal , false) +} \ No newline at end of file From 65a0b389aabc213e1814bf99c0f04430a9726a26 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Tue, 20 Aug 2024 23:24:12 +0700 Subject: [PATCH 2/6] chore: fix lint + remove unused import --- examples/gno.land/r/demo/profile/profile.gno | 1 - examples/gno.land/r/demo/profile/profile_test.gno | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index 31daa126d2f..d84db03e811 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -6,7 +6,6 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/mux" - "gno.land/p/demo/json" ) var ( diff --git a/examples/gno.land/r/demo/profile/profile_test.gno b/examples/gno.land/r/demo/profile/profile_test.gno index e4a57284fda..f3b6805cc67 100644 --- a/examples/gno.land/r/demo/profile/profile_test.gno +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -167,4 +167,4 @@ func TestSetMultiFields(t *testing.T) { // Get bool field that does not exist should return default bool inexistBoolVal := GetField(user1, "inexistBoolField", false) uassert.Equal(t, inexistBoolVal , false) -} \ No newline at end of file +} From 40d6e4ecaca15984c1902a3e18c38485911be0d6 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Wed, 21 Aug 2024 22:56:35 +0700 Subject: [PATCH 3/6] feat: remove check for restricted fields to support arbitrary fields --- examples/gno.land/r/demo/profile/profile.gno | 93 ++++--------- .../gno.land/r/demo/profile/profile_test.gno | 124 +++++++----------- 2 files changed, 73 insertions(+), 144 deletions(-) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index d84db03e811..0ff701aa851 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -4,6 +4,7 @@ import ( "errors" "std" + "gno.land/p/demo/ufmt" "gno.land/p/demo/avl" "gno.land/p/demo/mux" ) @@ -54,68 +55,49 @@ var boolFields = map[string]bool{ // Setters -func SetStringField(field, value string) error { +func SetStringField(field, value string) bool { addr := std.PrevRealm().Addr() - if _, ok := stringFields[field]; !ok { - return errors.New("invalid string field") - } - key := addr.String() + ":" + field - fields.Set(key, value) - - return nil -} + updated := fields.Set(key, value) -func SetIntField(field string, value int) error { - addr := std.PrevRealm().Addr() - - if _, ok := intFields[field]; !ok { - return errors.New("invalid int field") + event := "ProfileFieldCreated" + if updated { + event = "ProfileFieldUpdated" } - key := addr.String() + ":" + field - fields.Set(key, value) + std.Emit(event, "type", "String", field, value) - return nil + return updated } -func SetBoolField(field string, value bool) error { +func SetIntField(field string, value int) bool { addr := std.PrevRealm().Addr() + key := addr.String() + ":" + field + updated := fields.Set(key, value) - if _, ok := boolFields[field]; !ok { - return errors.New("invalid bool field") + event := "ProfileFieldCreated" + if updated { + event = "ProfileFieldUpdated" } - key := addr.String() + ":" + field - fields.Set(key, value) + std.Emit(event, "type", "Int", field, string(value)) - return nil + return updated } -func SetField(field string, value interface{}) error { +func SetBoolField(field string, value bool) bool { addr := std.PrevRealm().Addr() + key := addr.String() + ":" + field + updated := fields.Set(key, value) - if _, ok := boolFields[field]; ok { - return SetBoolField(field, value.(bool)) - } - - if _, ok := intFields[field]; ok { - return SetIntField(field, value.(int)) - } - - if _, ok := stringFields[field]; ok { - return SetStringField(field, value.(string)) + event := "ProfileFieldCreated" + if updated { + event = "ProfileFieldUpdated" } - caller := addr.String() - userFields, exists := arbitraryFields.Get(caller) - if !exists { - userFields = avl.NewTree() - arbitraryFields.Set(caller, userFields) - } + std.Emit(event, "type", "Bool", field, ufmt.Sprintf("%t", value)) - userFields.(*avl.Tree).Set(field, value) - return nil + return updated } // Getters @@ -146,30 +128,3 @@ func GetIntField(addr std.Address, field string, def int) int { return def } - -func GetField(addr std.Address, field string, def interface{}) interface{} { - if _, ok := boolFields[field]; ok { - return GetBoolField(addr, field, def.(bool)) - } - - if _, ok := intFields[field]; ok { - return GetIntField(addr, field, def.(int)) - } - - if _, ok := stringFields[field]; ok { - return GetStringField(addr, field, def.(string)) - } - - caller := addr.String() - userFields, exists := arbitraryFields.Get(caller) - if !exists { - return nil - } - - val, exists := userFields.(*avl.Tree).Get(field) - if !exists { - return nil - } - - return val -} \ No newline at end of file diff --git a/examples/gno.land/r/demo/profile/profile_test.gno b/examples/gno.land/r/demo/profile/profile_test.gno index f3b6805cc67..3299d6b9078 100644 --- a/examples/gno.land/r/demo/profile/profile_test.gno +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -28,11 +28,15 @@ func TestStringFields(t *testing.T) { name := GetStringField(alice, DisplayName, "anon") uassert.Equal(t, "anon", name) - // Set - err := SetStringField(DisplayName, "Alice foo") - uassert.NoError(t, err) - err = SetStringField(Homepage, "https://example.com") - uassert.NoError(t, err) + // Set new key + updated := SetStringField(DisplayName, "Alice foo") + uassert.Equal(t, updated, false) + updated = SetStringField(Homepage, "https://example.com") + uassert.Equal(t, updated, false) + + // Update the key + updated = SetStringField(DisplayName, "Alice foo") + uassert.Equal(t, updated, true) // Get after setting name = GetStringField(alice, DisplayName, "anon") @@ -51,9 +55,13 @@ func TestIntFields(t *testing.T) { age := GetIntField(bob, Age, 25) uassert.Equal(t, 25, age) - // Set - err := SetIntField(Age, 30) - uassert.NoError(t, err) + // Set new key + updated := SetIntField(Age, 30) + uassert.Equal(t, updated, false) + + // Update the key + updated = SetIntField(Age, 30) + uassert.Equal(t, updated, true) // Get after setting age = GetIntField(bob, Age, 25) @@ -68,45 +76,28 @@ func TestBoolFields(t *testing.T) { uassert.Equal(t, false, hiring) // Set - err := SetBoolField(AvailableForHiring, true) - uassert.NoError(t, err) + updated := SetBoolField(AvailableForHiring, true) + uassert.Equal(t, updated, false) + + // Update the key + updated = SetBoolField(AvailableForHiring, true) + uassert.Equal(t, updated, true) // Get after setting hiring = GetBoolField(charlie, AvailableForHiring, false) uassert.Equal(t, true, hiring) } -func TestInvalidStringField(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(dave)) - - err := SetStringField(InvalidField, "test") - uassert.Error(t, err) -} - -func TestInvalidIntField(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(eve)) - - err := SetIntField(InvalidField, 123) - uassert.Error(t, err) -} - -func TestInvalidBoolField(t *testing.T) { - std.TestSetRealm(std.NewUserRealm(frank)) - - err := SetBoolField(InvalidField, true) - uassert.Error(t, err) -} - func TestMultipleProfiles(t *testing.T) { // Set profile for user1 std.TestSetRealm(std.NewUserRealm(user1)) - err := SetStringField(DisplayName, "User One") - uassert.NoError(t, err) + updated := SetStringField(DisplayName, "User One") + uassert.Equal(t, updated, false) // Set profile for user2 std.TestSetRealm(std.NewUserRealm(user2)) - err = SetStringField(DisplayName, "User Two") - uassert.NoError(t, err) + updated = SetStringField(DisplayName, "User Two") + uassert.Equal(t, updated, false) // Get profiles std.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1 @@ -118,53 +109,36 @@ func TestMultipleProfiles(t *testing.T) { uassert.Equal(t, "User Two", name2) } -func TestSetMultiFields(t *testing.T) { +func TestArbitraryStringField(t *testing.T) { std.TestSetRealm(std.NewUserRealm(user1)) - // Set new arbitrary string field - err := SetField("NewStrField", "This is new string value") - uassert.NoError(t, err) - - strVal := GetField(user1, "NewStrField", "") - uassert.Equal(t, strVal , "This is new string value") - - // Set new arbitrary int field - err = SetField("NewIntField", 40) - uassert.NoError(t, err) - - intVal := GetField(user1, "NewIntField", 0) - uassert.Equal(t, intVal , 40) - - // Set new arbitrary bool field - err = SetField("NewBoolField", true) - uassert.NoError(t, err) + // Set arbitrary string field + updated := SetStringField("MyEmail", "my@email.com") + uassert.Equal(t, updated, false) - boolVal := GetField(user1, "NewBoolField", false) - uassert.Equal(t, boolVal , true) - - // Set reserved field - err = SetField("DisplayName", "My Name") - uassert.NoError(t, err) + val := GetStringField(user1, "MyEmail", "") + uassert.Equal(t, val, "my@email.com") +} - reservedStrVal := GetStringField(user1, "DisplayName", "") - uassert.Equal(t, reservedStrVal , "My Name") +func TestArbitraryIntField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) - // Get reserved field - err = SetIntField("Age", 40) - uassert.NoError(t, err) + // Set arbitrary int field + updated := SetIntField("MyIncome", 100_000) + uassert.Equal(t, updated, false) - reservedIntVal := GetField(user1, "Age", 0) - uassert.Equal(t, reservedIntVal , 40) + val := GetIntField(user1, "MyIncome", 0) + uassert.Equal(t, val, 100_000) +} - // Get string field that does not exist should return default string - inexistStrVal := GetField(user1, "InexistStrField", "default") - uassert.Equal(t, inexistStrVal , "default") +func TestArbitraryBoolField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) - // Get int field that does not exist should return default int - inexistIntVal := GetField(user1, "inexistIntField", 0) - uassert.Equal(t, inexistIntVal , 0) + // Set arbitrary int field + updated := SetBoolField("IsWinner", true) + uassert.Equal(t, updated, false) - // Get bool field that does not exist should return default bool - inexistBoolVal := GetField(user1, "inexistBoolField", false) - uassert.Equal(t, inexistBoolVal , false) + val := GetBoolField(user1, "IsWinner", false) + uassert.Equal(t, val, true) } + From ca7ad45bd6fa0e9e7e653c33be30515edf27c234 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Thu, 22 Aug 2024 21:59:26 +0700 Subject: [PATCH 4/6] chore: remove unused imports --- examples/gno.land/r/demo/profile/profile.gno | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index 0ff701aa851..9ada24e0040 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -1,7 +1,6 @@ package profile import ( - "errors" "std" "gno.land/p/demo/ufmt" @@ -12,7 +11,6 @@ import ( var ( fields = avl.NewTree() router = mux.NewRouter() - arbitraryFields = avl.NewTree() // address => *avl.Tree (field => value) ) const ( From e2c6a3593f3246d2a10e14f99717e873e5529fd3 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Fri, 23 Aug 2024 22:18:06 +0700 Subject: [PATCH 5/6] chore: use constant instead of hardcoded strings --- examples/gno.land/r/demo/profile/profile.gno | 44 +++++++++++++------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index 9ada24e0040..1318e19eaf3 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -3,9 +3,9 @@ package profile import ( "std" - "gno.land/p/demo/ufmt" "gno.land/p/demo/avl" "gno.land/p/demo/mux" + "gno.land/p/demo/ufmt" ) var ( @@ -13,6 +13,7 @@ var ( router = mux.NewRouter() ) +// Standard fields const ( DisplayName = "DisplayName" Homepage = "Homepage" @@ -25,13 +26,28 @@ const ( InvalidField = "InvalidField" ) +// Events +const ( + ProfileFieldCreated = "ProfileFieldCreated" + ProfileFieldUpdated = "ProfileFieldUpdated" +) + +// Field types used when emitting event +const FieldType = "FieldType" + +const ( + BoolField = "BoolField" + StringField = "StringField" + IntField = "IntField" +) + func init() { router.HandleFunc("", homeHandler) router.HandleFunc("u/{addr}", profileHandler) router.HandleFunc("f/{addr}/{field}", fieldHandler) } -// list of supported string fields +// List of supported string fields var stringFields = map[string]bool{ DisplayName: true, Homepage: true, @@ -41,12 +57,12 @@ var stringFields = map[string]bool{ GravatarEmail: true, } -// list of support int fields +// List of support int fields var intFields = map[string]bool{ Age: true, } -// list of support bool fields +// List of support bool fields var boolFields = map[string]bool{ AvailableForHiring: true, } @@ -58,12 +74,12 @@ func SetStringField(field, value string) bool { key := addr.String() + ":" + field updated := fields.Set(key, value) - event := "ProfileFieldCreated" + event := ProfileFieldCreated if updated { - event = "ProfileFieldUpdated" + event = ProfileFieldUpdated } - std.Emit(event, "type", "String", field, value) + std.Emit(event, FieldType, StringField, field, value) return updated } @@ -73,12 +89,12 @@ func SetIntField(field string, value int) bool { key := addr.String() + ":" + field updated := fields.Set(key, value) - event := "ProfileFieldCreated" + event := ProfileFieldCreated if updated { - event = "ProfileFieldUpdated" + event = ProfileFieldUpdated } - std.Emit(event, "type", "Int", field, string(value)) + std.Emit(event, FieldType, IntField, field, string(value)) return updated } @@ -88,14 +104,14 @@ func SetBoolField(field string, value bool) bool { key := addr.String() + ":" + field updated := fields.Set(key, value) - event := "ProfileFieldCreated" + event := ProfileFieldCreated if updated { - event = "ProfileFieldUpdated" + event = ProfileFieldUpdated } - std.Emit(event, "type", "Bool", field, ufmt.Sprintf("%t", value)) + std.Emit(event, FieldType, BoolField, field, ufmt.Sprintf("%t", value)) - return updated + return updated } // Getters From 32778e03c6f20ac6e3b79be518b9ec7d8cf43223 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Wed, 11 Sep 2024 12:49:36 +0200 Subject: [PATCH 6/6] chore: fmt Signed-off-by: Norman Meier --- examples/gno.land/r/demo/profile/profile_test.gno | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/gno.land/r/demo/profile/profile_test.gno b/examples/gno.land/r/demo/profile/profile_test.gno index 3299d6b9078..3947897289e 100644 --- a/examples/gno.land/r/demo/profile/profile_test.gno +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -3,7 +3,6 @@ package profile import ( "std" "testing" - "errors" "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" @@ -141,4 +140,3 @@ func TestArbitraryBoolField(t *testing.T) { val := GetBoolField(user1, "IsWinner", false) uassert.Equal(t, val, true) } -