Skip to content
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

Boxes: Support GetApplicationBoxes #344

Merged
merged 20 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8a230af
Support for Box array transaction field
michaeldiamant Jun 30, 2022
85a345a
Point to go-algorand feature branch
michaeldiamant Jun 30, 2022
fcd2fe6
Boxes: Query algod Box by application ID and Box name
michaeldiamant Jul 1, 2022
289f2b4
Remove println
michaeldiamant Jul 1, 2022
273f5c8
Fix typo in Cucumber test references
michaeldiamant Jul 1, 2022
5e4e62a
Update test/docker/run_docker.sh
michaeldiamant Jul 1, 2022
309afb3
Remove unused code intended for child branch
michaeldiamant Jul 1, 2022
459265e
Merge branch 'box_txn_field' into algod_GetApplicationBoxByName
michaeldiamant Jul 1, 2022
0c6a8d0
Re-add Box model
michaeldiamant Jul 1, 2022
9be96ec
Merge branch 'algod_GetApplicationBoxByName' of github.com:algorand/g…
michaeldiamant Jul 1, 2022
af2a45d
Merge branch 'feature/box-storage' into algod_GetApplicationBoxByName
michaeldiamant Jul 6, 2022
93887f4
Update stale serialize comment reference
michaeldiamant Jul 6, 2022
dcd54a6
Boxes: Add convenience methods for encoding Box names
michaeldiamant Jul 7, 2022
a0b9342
Merge branch 'feature/box-storage' into box_encoding_convenience_methods
michaeldiamant Jul 8, 2022
216cd1b
Add missed doc comment
michaeldiamant Jul 8, 2022
abdc71a
Boxes: Support GetApplicationBoxes
michaeldiamant Jul 9, 2022
a4b33d2
Add convenience method for BoxDescriptor
michaeldiamant Jul 9, 2022
3158dc1
Merge branch 'feature/box-storage' into boxes_response_rework
michaeldiamant Jul 13, 2022
9e79bb0
Fix updated Cucumber unit test
michaeldiamant Jul 13, 2022
18cba69
Revert to feature branch following upstream merge
michaeldiamant Jul 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/v2/algod/algod.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ func (c *Client) GetApplicationByID(applicationId uint64) *GetApplicationByID {
return &GetApplicationByID{c: c, applicationId: applicationId}
}

func (c *Client) GetApplicationBoxes(applicationId uint64) *GetApplicationBoxes {
return &GetApplicationBoxes{c: c, applicationId: applicationId}
}

func (c *Client) GetApplicationBoxByName(applicationId uint64) *GetApplicationBoxByName {
return &GetApplicationBoxByName{c: c, applicationId: applicationId}
}
Expand Down
40 changes: 40 additions & 0 deletions client/v2/algod/getApplicationBoxes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package algod

import (
"context"
"fmt"

"github.com/algorand/go-algorand-sdk/client/v2/common"
"github.com/algorand/go-algorand-sdk/client/v2/common/models"
)

// GetApplicationBoxesParams contains all of the query parameters for url serialization.
type GetApplicationBoxesParams struct {

// Max max number of box names to return. If max is not set, or max == 0, returns
// all box-names.
Max uint64 `url:"max,omitempty"`
}

// GetApplicationBoxes given an application ID, it returns the box names of that
// application. No particular ordering is guaranteed.
type GetApplicationBoxes struct {
c *Client

applicationId uint64

p GetApplicationBoxesParams
}

// Max max number of box names to return. If max is not set, or max == 0, returns
// all box-names.
func (s *GetApplicationBoxes) Max(Max uint64) *GetApplicationBoxes {
s.p.Max = Max
return s
}

// Do performs the HTTP request
func (s *GetApplicationBoxes) Do(ctx context.Context, headers ...*common.Header) (response models.BoxesResponse, err error) {
err = s.c.get(ctx, &response, fmt.Sprintf("/v2/applications/%s/boxes", common.EscapeParams(s.applicationId)...), s.p, headers)
return
}
7 changes: 7 additions & 0 deletions client/v2/common/models/box_descriptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package models

// BoxDescriptor box descriptor describes a Box.
type BoxDescriptor struct {
// Name base64 encoded box name
Name []byte `json:"name"`
}
7 changes: 7 additions & 0 deletions client/v2/common/models/boxes_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package models

// BoxesResponse box names of an application
type BoxesResponse struct {
// Boxes
Boxes []BoxDescriptor `json:"boxes"`
}
9 changes: 9 additions & 0 deletions encoding/box_query_encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ func EncodeBoxForBoxQuery(b models.Box) (string, error) {
return EncodeBytesForBoxQuery(decoded), nil
}

func EncodeBoxDescriptorForBoxQuery(bd models.BoxDescriptor) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(string(bd.Name))
if err != nil {
return "", err
}

return EncodeBytesForBoxQuery(decoded), nil
}

// EncodeBoxReferenceForBoxQuery provides a convenience method to string encode box names for use with Box search APIs (e.g. GetApplicationBoxByName).
func EncodeBoxReferenceForBoxQuery(br types.BoxReference) string {
return EncodeBytesForBoxQuery(br.Name)
Expand Down
9 changes: 9 additions & 0 deletions encoding/box_query_encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ func TestEncode(t *testing.T) {
actual,
)

// Encode BoxDescriptor
bd := models.BoxDescriptor{Name: []byte(base64.StdEncoding.EncodeToString(e.source))}
actual, err = EncodeBoxDescriptorForBoxQuery(bd)
require.NoError(t, err)
require.Equal(t,
e.expectedEncoding,
actual,
)

// Encode BoxReference
require.Equal(t,
e.expectedEncoding,
Expand Down
20 changes: 16 additions & 4 deletions test/algodclientv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,19 @@ func AlgodClientV2Context(s *godog.Suite) {
s.Step(`^we make an Account Information call against account "([^"]*)" with exclude "([^"]*)"$`, weMakeAnAccountInformationCallAgainstAccountWithExclude)
s.Step(`^we make an Account Asset Information call against account "([^"]*)" assetID (\d+)$`, weMakeAnAccountAssetInformationCallAgainstAccountAssetID)
s.Step(`^we make an Account Application Information call against account "([^"]*)" applicationID (\d+)$`, weMakeAnAccountApplicationInformationCallAgainstAccountApplicationID)
s.Step(`^we make a GetApplicationBoxByName call for applicationID (\d+) with encoded box name "([^"]*)"$`, weMakeAGetApplicationBoxByNameCallForApplicationIDWithEncodedBoxName)
s.Step(`^we make a GetApplicationBoxByName call for applicationID (\d+) with encoded box name "([^"]*)"$`,
func(appId int, encodedBoxName string) error {
return withClient(func(c algod.Client) {
_, _ = c.GetApplicationBoxByName(uint64(appId)).Name(encodedBoxName).Do(context.Background())
})
})
s.Step(`^we make a GetApplicationBoxes call for applicationID (\d+) with max (\d+)$`,
func(appId int, max int) error {
return withClient(func(c algod.Client) {
_, _ = c.GetApplicationBoxes(uint64(appId)).Max(uint64(max)).Do(context.Background())
})
},
)
s.BeforeScenario(func(interface{}) {
globalErrForExamination = nil
})
Expand Down Expand Up @@ -257,12 +269,12 @@ func weMakeAnAccountApplicationInformationCallAgainstAccountApplicationID(accoun
return nil
}

func weMakeAGetApplicationBoxByNameCallForApplicationIDWithEncodedBoxName(appId int, encodedBoxName string) error {
algodClient, err := algod.MakeClient(mockServer.URL, "")
func withClient(f func(client algod.Client)) error {
c, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
return err
}

_, _ = algodClient.GetApplicationBoxByName(uint64(appId)).Name(encodedBoxName).Do(context.Background())
f(*c)
return nil
}
78 changes: 77 additions & 1 deletion test/applications_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
Expand Down Expand Up @@ -331,6 +332,12 @@ func parseAppArgs(appArgsString string) (appArgs [][]byte, err error) {
return nil, fmt.Errorf("failed to convert %s to bytes", arg)
}
resp[idx] = buf.Bytes()
case "b64":
d, err := (base64.StdEncoding.DecodeString(typeArg[1]))
if err != nil {
return nil, fmt.Errorf("failed to b64 decode arg = %s", arg)
}
resp[idx] = d
default:
return nil, fmt.Errorf("Applications doesn't currently support argument of type %s", typeArg[0])
}
Expand Down Expand Up @@ -914,6 +921,28 @@ func checkRandomElementResult(resultIndex int, input string) error {
return nil
}

// decodeBoxName parses the encoding scheme to return the corresponding byte representation
func decodeBoxName(encodedBoxName string) ([]byte, error) {
split := strings.Split(encodedBoxName, ":")
if len(split) != 2 {
return nil, errors.New("encodedBoxName (" + encodedBoxName + ") does not match expected format")
}
encoding, encoded := split[0], split[1]
switch encoding {
case "str":
return []byte(encoded), nil
case "b64":
d, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return nil, fmt.Errorf("failed to b64 decode arg = %s", encoded)
}

return d, nil
default:
return nil, errors.New("unsupported encoding = " + encoding)
}
}

func theContentsOfTheBoxWithNameShouldBeIfThereIsAnErrorItIs(encodedBoxName, boxContents, errStr string) error {

box, err := algodV2client.GetApplicationBoxByName(applicationId).Name(encodedBoxName).Do(context.Background())
Expand Down Expand Up @@ -962,5 +991,52 @@ func ApplicationsContext(s *godog.Suite) {
s.Step(`^The (\d+)th atomic result for randomInt\((\d+)\) proves correct$`, checkRandomIntResult)
s.Step(`^The (\d+)th atomic result for randElement\("([^"]*)"\) proves correct$`, checkRandomElementResult)

s.Step(`^the contents of the box with name "([^"]*)" should be "([^"]*)"\. If there is an error it is "([^"]*)"\.$`, theContentsOfTheBoxWithNameShouldBeIfThereIsAnErrorItIs)
s.Step(`^the contents of the box with name "([^"]*)" in the current application should be "([^"]*)"\. If there is an error it is "([^"]*)"\.$`, theContentsOfTheBoxWithNameShouldBeIfThereIsAnErrorItIs)

s.Step(`^the current application should have the following boxes "([^"]*)"\.$`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this pattern of passing an anonymous function - it's more similar to the annotations in the other sdks. It might make error messages a little more complicated due to an unnamed function on the call stack but that's not cause for concern imo.

func(encodedBoxesRaw string) error {
var expectedNames [][]byte
if len(encodedBoxesRaw) > 0 {
encodedBoxes := strings.Split(encodedBoxesRaw, ",")
expectedNames = make([][]byte, len(encodedBoxes))
for i, b := range encodedBoxes {
expected, err := decodeBoxName(b)
if err != nil {
return err
}
expectedNames[i] = expected
}
}

r, err := algodV2client.GetApplicationBoxes(applicationId).Do(context.Background())
if err != nil {
return err
}

actualNames := make([][]byte, len(r.Boxes))
for i, b := range r.Boxes {
actualNames[i] = b.Name
}

if len(expectedNames) != len(actualNames) {
return fmt.Errorf("expected and actual box names length do not match: %v != %v", len(expectedNames), len(actualNames))
}

contains := func(elem []byte, xs [][]byte) bool {
for _, x := range xs {
if bytes.Equal(elem, x) {
return true
}
}
return false
}

for _, e := range expectedNames {
if !contains(e, actualNames) {
return fmt.Errorf("expected and actual box names do not match: %v != %v", expectedNames, actualNames)
}
}

return nil
})
}