Skip to content

Commit

Permalink
feat: adding support for arbitrary field (#2717)
Browse files Browse the repository at this point in the history
Related to this [PR](#1983) and
[Manfred's
idea](#1983 (comment)),
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".



<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] 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).
</details>

---------

Signed-off-by: Norman Meier <[email protected]>
Co-authored-by: Norman Meier <[email protected]>
Co-authored-by: n0izn0iz <[email protected]>
  • Loading branch information
3 people committed Sep 11, 2024
1 parent dd2d374 commit f04ec89
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 57 deletions.
67 changes: 45 additions & 22 deletions examples/gno.land/r/demo/profile/profile.gno
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package profile

import (
"errors"
"std"

"gno.land/p/demo/avl"
"gno.land/p/demo/mux"
"gno.land/p/demo/ufmt"
)

var (
fields = avl.NewTree()
router = mux.NewRouter()
)

// Standard fields
const (
DisplayName = "DisplayName"
Homepage = "Homepage"
Expand All @@ -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,
Expand All @@ -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
Expand Down
94 changes: 59 additions & 35 deletions examples/gno.land/r/demo/profile/profile_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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", "[email protected]")
uassert.Equal(t, updated, false)

val := GetStringField(user1, "MyEmail", "")
uassert.Equal(t, val, "[email protected]")
}

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)
}

0 comments on commit f04ec89

Please sign in to comment.