From f04ec89021e1a52fe19b6350a6d074b18d4780a0 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Wed, 11 Sep 2024 05:05:55 -0700 Subject: [PATCH] feat: adding support for arbitrary field (#2717) Related to this [PR](https://github.com/gnolang/gno/pull/1983) and [Manfred's idea](https://github.com/gnolang/gno/pull/1983#discussion_r1644292721), this PR aims at adding possibility to add arbitrary field to profile. I keep the same fields list defined in the struct, they would be served as "standard/base" fields and all other arbitrary fields are considered as "extra".
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--------- Signed-off-by: Norman Meier Co-authored-by: Norman Meier Co-authored-by: n0izn0iz --- examples/gno.land/r/demo/profile/profile.gno | 67 ++++++++----- .../gno.land/r/demo/profile/profile_test.gno | 94 ++++++++++++------- 2 files changed, 104 insertions(+), 57 deletions(-) diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index cc7d80e016d..1318e19eaf3 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -1,11 +1,11 @@ package profile import ( - "errors" "std" "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,54 +57,61 @@ 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, } // 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 + updated := fields.Set(key, value) + + event := ProfileFieldCreated + if updated { + event = ProfileFieldUpdated } - key := addr.String() + ":" + field - fields.Set(key, value) + std.Emit(event, FieldType, StringField, field, value) - return nil + return updated } -func SetIntField(field string, value int) error { +func SetIntField(field string, value int) bool { addr := std.PrevRealm().Addr() + key := addr.String() + ":" + field + updated := fields.Set(key, value) - 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, FieldType, IntField, field, string(value)) - return nil + return updated } -func SetBoolField(field string, value bool) 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 errors.New("invalid bool field") + event := ProfileFieldCreated + if updated { + event = ProfileFieldUpdated } - key := addr.String() + ":" + field - fields.Set(key, value) + std.Emit(event, FieldType, BoolField, field, ufmt.Sprintf("%t", value)) - return nil + return updated } // Getters diff --git a/examples/gno.land/r/demo/profile/profile_test.gno b/examples/gno.land/r/demo/profile/profile_test.gno index 987632a594d..3947897289e 100644 --- a/examples/gno.land/r/demo/profile/profile_test.gno +++ b/examples/gno.land/r/demo/profile/profile_test.gno @@ -27,11 +27,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") @@ -50,9 +54,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) @@ -67,45 +75,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 @@ -116,3 +107,36 @@ func TestMultipleProfiles(t *testing.T) { uassert.Equal(t, "User One", name1) uassert.Equal(t, "User Two", name2) } + +func TestArbitraryStringField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set arbitrary string field + updated := SetStringField("MyEmail", "my@email.com") + uassert.Equal(t, updated, false) + + val := GetStringField(user1, "MyEmail", "") + uassert.Equal(t, val, "my@email.com") +} + +func TestArbitraryIntField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set arbitrary int field + updated := SetIntField("MyIncome", 100_000) + uassert.Equal(t, updated, false) + + val := GetIntField(user1, "MyIncome", 0) + uassert.Equal(t, val, 100_000) +} + +func TestArbitraryBoolField(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(user1)) + + // Set arbitrary int field + updated := SetBoolField("IsWinner", true) + uassert.Equal(t, updated, false) + + val := GetBoolField(user1, "IsWinner", false) + uassert.Equal(t, val, true) +}