-
Notifications
You must be signed in to change notification settings - Fork 371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add realm r/demo/keystore #958
Changes from 2 commits
267053a
cac8a23
945b6a7
76b7131
61079bb
dd50037
292dc1e
8854a13
6d9ae81
1319b4a
ecccdeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package keystore | ||
|
||
import ( | ||
"std" | ||
"strings" | ||
|
||
"gno.land/p/demo/avl" | ||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
var data avl.Tree | ||
|
||
var ( | ||
BaseURL = "/r/demo/keystore" | ||
StatusOK = "ok" | ||
StatusNoUser = "user not found" | ||
StatusNotFound = "key not found" | ||
StatusNoWriteAccess = "no write access" | ||
StatusCouldNotExecute = "could not execute" | ||
) | ||
|
||
func init() { | ||
data = avl.Tree{} // user -> avl.Tree | ||
} | ||
|
||
// KeyStore stores the owner-specific avl.Tree | ||
type KeyStore struct { | ||
Owner std.Address | ||
Data avl.Tree | ||
} | ||
|
||
// Set will set a value to a key | ||
// requires write-access (original caller must be caller) | ||
func Set(k, v string) string { | ||
origOwner := std.GetOrigCaller() | ||
return set(origOwner.String(), k, v) | ||
} | ||
|
||
// set (private) will set a key to value | ||
// requires write-access (original caller must be caller) | ||
func set(owner, k, v string) string { | ||
origOwner := std.GetOrigCaller() | ||
if origOwner.String() != owner { | ||
return StatusNoWriteAccess | ||
} | ||
keystoreInterface, exists := data.Get(owner) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about extracting this out into a specific helper method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm of the mind to leave it - it only occurs twice in the code and moving out into a function may only reduce the code by a few lines. |
||
if !exists { | ||
data.Set(owner, &KeyStore{ | ||
Owner: origOwner, | ||
Data: avl.Tree{}, | ||
}) | ||
keystoreInterface, _ = data.Get(owner) | ||
} | ||
keystore := keystoreInterface.(*KeyStore) | ||
keystore.Data.Set(k, v) | ||
return StatusOK | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you setting the keystore, and then fetching it again from the map (avl)? Why not simply define a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are totally right, thanks for that. Its fixed in 61079bb |
||
} | ||
|
||
// Remove removes a key | ||
// requires write-access (original owner must be caller) | ||
func Remove(k string) string { | ||
origOwner := std.GetOrigCaller() | ||
return remove(origOwner.String(), k) | ||
} | ||
|
||
// remove (private) removes a key | ||
// requires write-access (original owner must be caller) | ||
func remove(owner, k string) string { | ||
origOwner := std.GetOrigCaller() | ||
if origOwner.String() != owner { | ||
return StatusNoWriteAccess | ||
} | ||
keystoreInterface, exists := data.Get(owner) | ||
if !exists { | ||
data.Set(owner, &KeyStore{ | ||
Owner: origOwner, | ||
Data: avl.Tree{}, | ||
}) | ||
keystoreInterface, _ = data.Get(owner) | ||
} | ||
keystore := keystoreInterface.(*KeyStore) | ||
_, removed := keystore.Data.Remove(k) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 61079bb |
||
if !removed { | ||
return StatusCouldNotExecute | ||
} | ||
return StatusOK | ||
} | ||
|
||
// Get returns a value for a key | ||
// read-only | ||
func Get(k string) string { | ||
origOwner := std.GetOrigCaller() | ||
return remove(origOwner.String(), k) | ||
} | ||
|
||
// get (private) returns a value for a key | ||
// read-only | ||
func get(owner, k string) string { | ||
keystoreInterface, exists := data.Get(owner) | ||
if !exists { | ||
return StatusNoUser | ||
} | ||
keystore := keystoreInterface.(*KeyStore) | ||
val, found := keystore.Data.Get(k) | ||
if !found { | ||
return StatusNotFound | ||
} | ||
return val.(string) | ||
} | ||
|
||
// Size returns size of database | ||
// read-only | ||
func Size() string { | ||
origOwner := std.GetOrigCaller() | ||
return size(origOwner.String()) | ||
} | ||
|
||
func size(owner string) string { | ||
keystoreInterface, exists := data.Get(owner) | ||
if !exists { | ||
return StatusNoUser | ||
} | ||
keystore := keystoreInterface.(*KeyStore) | ||
return ufmt.Sprintf("%d", keystore.Data.Size()) | ||
} | ||
|
||
// Render provides url access to the functions of the keystore | ||
// "" -> show all keystores listed by owner | ||
// "owner" -> show all keys for that owner's keystore | ||
// "owner:size" -> returns size of owner's keystore | ||
// "owner:get:key" -> show value for that key in owner's keystore | ||
// "owner:set:key:value" -> sets a key-value pair for owner's keystore (owner must be caller) | ||
// "owner:remove:key" -> removes key from owner keystore (owner must be caller) | ||
func Render(p string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, usage of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Was not aware that |
||
var response string | ||
args := strings.Split(p, ":") | ||
numArgs := len(args) | ||
if p == "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah! I'm not a huge fan of switches but I agree its nice here, fixed in 8854a13 |
||
if data.Size() > 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can simplify this code segment by: if data.Size() == 0 {
return "no databases"
}
data.Iterate(...)
// ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I do prefer that pattern. Fixed in 292dc1e |
||
data.Iterate("", "", func(key string, value interface{}) bool { | ||
ks := value.(*KeyStore) | ||
response += ufmt.Sprintf("- [%s](%s:%s) (%d keys)\n", ks.Owner, BaseURL, ks.Owner, ks.Data.Size()) | ||
return false | ||
}) | ||
} else { | ||
response = "no databases" | ||
} | ||
} else if numArgs == 1 { | ||
owner := args[0] | ||
keystoreInterface, exists := data.Get(owner) | ||
if !exists { | ||
return StatusNoUser | ||
} | ||
ks := keystoreInterface.(*KeyStore) | ||
i := 0 | ||
response += ufmt.Sprintf("# %s database\n\n", ks.Owner) | ||
ks.Data.Iterate("", "", func(key string, value interface{}) bool { | ||
response += ufmt.Sprintf("- %d [%s](%s:%s:get:%s)\n", i, key, BaseURL, ks.Owner, key) | ||
i++ | ||
return false | ||
}) | ||
} else if numArgs == 2 { | ||
owner := args[0] | ||
cmd := args[1] | ||
if cmd == "size" { | ||
return size(owner) | ||
} | ||
} else if numArgs == 3 { | ||
owner := args[0] | ||
cmd := args[1] | ||
key := args[2] | ||
if cmd == "get" { | ||
return get(owner, key) | ||
} else if cmd == "remove" { | ||
// remove will only work if caller is owner | ||
return remove(owner, key) | ||
} | ||
} else if numArgs == 4 { | ||
owner := args[0] | ||
cmd := args[1] | ||
key := args[2] | ||
val := args[3] | ||
if cmd == "set" { | ||
// set only works if caller is owner | ||
return set(owner, key, val) | ||
} | ||
} | ||
return response | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package keystore | ||
|
||
import ( | ||
"fmt" | ||
"std" | ||
"strings" | ||
"testing" | ||
|
||
"gno.land/p/demo/testutils" | ||
) | ||
|
||
func TestRender(t *testing.T) { | ||
const ( | ||
author1 std.Address = testutils.TestAddress("author1") | ||
author2 std.Address = testutils.TestAddress("author2") | ||
) | ||
|
||
tt := []struct { | ||
caller std.Address | ||
owner std.Address | ||
ps []string | ||
exp string | ||
}{ | ||
// can set database if the owner is the caller | ||
{author1, author1, []string{"set", "hello", "gno"}, StatusOK}, | ||
{author1, author1, []string{"size"}, "1"}, | ||
{author1, author1, []string{"set", "hello", "world"}, StatusOK}, | ||
{author1, author1, []string{"size"}, "1"}, | ||
{author1, author1, []string{"set", "hi", "gno"}, StatusOK}, | ||
{author1, author1, []string{"size"}, "2"}, | ||
// only owner can remove | ||
{author1, author1, []string{"remove", "hi"}, StatusOK}, | ||
{author1, author1, []string{"get", "hi"}, StatusNotFound}, | ||
{author1, author1, []string{"size"}, "1"}, | ||
// add back | ||
{author1, author1, []string{"set", "hi", "gno"}, StatusOK}, | ||
{author1, author1, []string{"size"}, "2"}, | ||
|
||
// different owner has different database | ||
{author2, author2, []string{"set", "hello", "universe"}, StatusOK}, | ||
// either author can get the other info | ||
{author1, author2, []string{"get", "hello"}, "universe"}, | ||
// either author can get the other info | ||
{author2, author1, []string{"get", "hello"}, "world"}, | ||
// cannot perform write operations is owner (author2) is not caller (author1) | ||
{author1, author2, []string{"set", "hello", "should not work"}, StatusNoWriteAccess}, | ||
{author1, author2, []string{"get", "hello"}, "universe"}, | ||
// anyone can view the databases | ||
{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys) | ||
- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`}, | ||
// anyone can view the keys in a database | ||
{author1, author2, []string{""}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database | ||
|
||
- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`}, | ||
} | ||
for _, tc := range tt { | ||
p := "" | ||
if len(tc.ps) > 0 { | ||
p = tc.owner.String() | ||
for i, psv := range tc.ps { | ||
p += ":" + psv | ||
} | ||
} | ||
p = strings.TrimSuffix(p, ":") | ||
t.Run(p, func(t *testing.T) { | ||
std.TestSetOrigCaller(tc.caller) | ||
act := strings.TrimSpace(Render(p)) | ||
if act != tc.exp { | ||
t.Errorf("%v -> '%s', got '%s', wanted '%s'", tc.ps, p, act, tc.exp) | ||
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can be constants
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, fixed in 76b7131