Skip to content

Commit

Permalink
clients/horizonclient: Support paths/strict-send and paths/strict-rec…
Browse files Browse the repository at this point in the history
…eive requests. (#2237)

* client/horizonclient: Add support for /paths/strict-send.

* Update clients/horizonclient/strict_send_paths_request.go

Co-Authored-By: Eric Saunders <[email protected]>

* Improve docs for StrictSendPathsRequest.

* Add support for paths/strict-receive.

* Use strict receive instead of paths in example.

Co-authored-by: Eric Saunders <[email protected]>
  • Loading branch information
abuiles and ire-and-curses committed Feb 25, 2020
1 parent f1d4c47 commit a5c11a6
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 7 deletions.
15 changes: 14 additions & 1 deletion clients/horizonclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,21 @@ func (c *Client) OrderBook(request OrderBookRequest) (obs hProtocol.OrderBookSum
return
}

// Paths returns the available paths to make a payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding.html
// Paths returns the available paths to make a strict receive path payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-receive.html
// This function is an alias for `client.StrictReceivePaths` and will be deprecated, use `client.StrictReceivePaths` instead.
func (c *Client) Paths(request PathsRequest) (paths hProtocol.PathsPage, err error) {
paths, err = c.StrictReceivePaths(request)
return
}

// StrictReceivePaths returns the available paths to make a strict receive path payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-receive.html
func (c *Client) StrictReceivePaths(request PathsRequest) (paths hProtocol.PathsPage, err error) {
err = c.sendRequest(request, &paths)
return
}

// StrictSendPaths returns the available paths to make a strict send path payment. See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-send.html
func (c *Client) StrictSendPaths(request StrictSendPathsRequest) (paths hProtocol.PathsPage, err error) {
err = c.sendRequest(request, &paths)
return
}
Expand Down
20 changes: 19 additions & 1 deletion clients/horizonclient/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,25 @@ func ExampleClient_Paths() {
DestinationAssetType: horizonclient.AssetType4,
SourceAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}
paths, err := client.Paths(pr)
paths, err := client.StrictReceivePaths(pr)
if err != nil {
fmt.Println(err)
return
}
fmt.Print(paths)
}

func ExampleClient_StrictSendPaths() {
client := horizonclient.DefaultPublicNetClient
// Find paths for USD->EUR
pr := horizonclient.StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX",
SourceAssetType: horizonclient.AssetType4,
DestinationAssets: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S",
}
paths, err := client.StrictSendPaths(pr)
if err != nil {
fmt.Println(err)
return
Expand Down
20 changes: 18 additions & 2 deletions clients/horizonclient/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,15 +335,31 @@ type OrderBookRequest struct {
Limit uint
}

// PathsRequest struct contains data for getting available payment paths from a horizon server.
// All parameters are required.
// PathsRequest struct contains data for getting available strict receive path payments from a horizon server.
// All the Destination related parameters are required and you need to include either
// SourceAccount or SourceAssets.
// See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-receive.html
type PathsRequest struct {
DestinationAccount string
DestinationAssetType AssetType
DestinationAssetCode string
DestinationAssetIssuer string
DestinationAmount string
SourceAccount string
SourceAssets string
}

// StrictSendPathsRequest struct contains data for getting available strict send path payments from a horizon server.
// All the Source related parameters are required and you need to include either
// DestinationAccount or DestinationAssets.
// See https://www.stellar.org/developers/horizon/reference/endpoints/path-finding-strict-send.html
type StrictSendPathsRequest struct {
DestinationAccount string
DestinationAssets string
SourceAssetType AssetType
SourceAssetCode string
SourceAssetIssuer string
SourceAmount string
}

// TradeRequest struct contains data for getting trade details from a horizon server.
Expand Down
1 change: 1 addition & 0 deletions clients/horizonclient/paths_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (pr PathsRequest) BuildURL() (endpoint string, err error) {
paramMap["destination_asset_issuer"] = pr.DestinationAssetIssuer
paramMap["destination_amount"] = pr.DestinationAmount
paramMap["source_account"] = pr.SourceAccount
paramMap["source_assets"] = pr.SourceAssets

queryParams := addQueryParams(paramMap)
if queryParams != "" {
Expand Down
59 changes: 56 additions & 3 deletions clients/horizonclient/paths_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ func TestPathsRequestBuildUrl(t *testing.T) {
DestinationAssetIssuer: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
DestinationAssetType: AssetType4,
SourceAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
SourceAssets: "COP:GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}

endpoint, err = pr.BuildURL()

// It should return valid assets endpoint and no errors
require.NoError(t, err)
assert.Equal(t, "paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM", endpoint)
assert.Equal(
t,
"paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&source_assets=COP%3AGDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
endpoint,
)

}

Expand All @@ -57,7 +62,7 @@ func TestPathsRequest(t *testing.T) {
"https://localhost/paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
).ReturnString(200, pathsResponse)

paths, err := client.Paths(pr)
paths, err := client.StrictReceivePaths(pr)
if assert.NoError(t, err) {
assert.IsType(t, paths, hProtocol.PathsPage{})
record := paths.Embedded.Records[0]
Expand All @@ -73,7 +78,55 @@ func TestPathsRequest(t *testing.T) {
"https://localhost/paths",
).ReturnString(400, badRequestResponse)

_, err = client.Paths(pr)
_, err = client.StrictReceivePaths(pr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "horizon error")
horizonError, ok := err.(*Error)
assert.Equal(t, ok, true)
assert.Equal(t, horizonError.Problem.Title, "Bad Request")
}

}

func TestStrictReceivePathsRequest(t *testing.T) {
hmock := httptest.NewClient()
client := &Client{
HorizonURL: "https://localhost/",
HTTP: hmock,
}

pr := PathsRequest{
DestinationAccount: "GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU",
DestinationAmount: "100",
DestinationAssetCode: "NGN",
DestinationAssetIssuer: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
DestinationAssetType: AssetType4,
SourceAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}

// orderbook for XLM/USD
hmock.On(
"GET",
"https://localhost/paths?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&destination_amount=100&destination_asset_code=NGN&destination_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&destination_asset_type=credit_alphanum4&source_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
).ReturnString(200, pathsResponse)

paths, err := client.StrictReceivePaths(pr)
if assert.NoError(t, err) {
assert.IsType(t, paths, hProtocol.PathsPage{})
record := paths.Embedded.Records[0]
assert.Equal(t, record.DestinationAmount, "20.0000000")
assert.Equal(t, record.DestinationAssetCode, "EUR")
assert.Equal(t, record.SourceAmount, "30.0000000")
}

// failure response
pr = PathsRequest{}
hmock.On(
"GET",
"https://localhost/paths",
).ReturnString(400, badRequestResponse)

_, err = client.StrictReceivePaths(pr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "horizon error")
horizonError, ok := err.(*Error)
Expand Down
35 changes: 35 additions & 0 deletions clients/horizonclient/strict_send_paths_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package horizonclient

import (
"fmt"
"net/url"

"github.com/stellar/go/support/errors"
)

// BuildURL creates the endpoint to be queried based on the data in the PathsRequest struct.
func (pr StrictSendPathsRequest) BuildURL() (endpoint string, err error) {
endpoint = "paths/strict-send"

// add the parameters to a map here so it is easier for addQueryParams to populate the parameter list
// We can't use assetCode and assetIssuer types here because the parameter names are different
paramMap := make(map[string]string)
paramMap["destination_assets"] = pr.DestinationAssets
paramMap["destination_account"] = pr.DestinationAccount
paramMap["source_asset_type"] = string(pr.SourceAssetType)
paramMap["source_asset_code"] = pr.SourceAssetCode
paramMap["source_asset_issuer"] = pr.SourceAssetIssuer
paramMap["source_amount"] = pr.SourceAmount

queryParams := addQueryParams(paramMap)
if queryParams != "" {
endpoint = fmt.Sprintf("%s?%s", endpoint, queryParams)
}

_, err = url.Parse(endpoint)
if err != nil {
err = errors.Wrap(err, "failed to parse endpoint")
}

return endpoint, err
}
117 changes: 117 additions & 0 deletions clients/horizonclient/strict_send_paths_request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package horizonclient

import (
"testing"

"github.com/stellar/go/support/http/httptest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestStrictSendPathsRequestBuildUrl(t *testing.T) {
pr := StrictSendPathsRequest{}
endpoint, err := pr.BuildURL()

// It should return no errors and paths endpoint
// Horizon will return an error though because there are no parameters
require.NoError(t, err)
assert.Equal(t, "paths/strict-send", endpoint)

pr = StrictSendPathsRequest{
SourceAmount: "100",
SourceAssetCode: "NGN",
SourceAssetIssuer: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
SourceAssetType: AssetType4,
DestinationAccount: "GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM",
}

endpoint, err = pr.BuildURL()

// It should return a valid endpoint and no errors
require.NoError(t, err)
assert.Equal(
t,
"paths/strict-send?destination_account=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&source_amount=100&source_asset_code=NGN&source_asset_issuer=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBM&source_asset_type=credit_alphanum4",
endpoint,
)

pr = StrictSendPathsRequest{
SourceAmount: "100",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX",
SourceAssetType: AssetType4,
DestinationAssets: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S,native",
}

endpoint, err = pr.BuildURL()

require.NoError(t, err)
assert.Equal(
t,
"paths/strict-send?destination_assets=EURT%3AGAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S%2Cnative&source_amount=100&source_asset_code=USD&source_asset_issuer=GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX&source_asset_type=credit_alphanum4",
endpoint,
)
}
func TestStrictSendPathsRequest(t *testing.T) {
hmock := httptest.NewClient()
client := &Client{
HorizonURL: "https://localhost/",
HTTP: hmock,
}

pr := StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
SourceAssetType: AssetType4,
DestinationAccount: "GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU",
}

hmock.On(
"GET",
"https://localhost/paths/strict-send?destination_account=GCLWGQPMKXQSPF776IU33AH4PZNOOWNAWGGKVTBQMIC5IMKUNP3E6NVU&source_amount=20&source_asset_code=USD&source_asset_issuer=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_asset_type=credit_alphanum4",
).ReturnString(200, pathsResponse)

paths, err := client.StrictSendPaths(pr)
assert.NoError(t, err)
assert.Len(t, paths.Embedded.Records, 3)

pr = StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
SourceAssetType: AssetType4,
DestinationAssets: "EUR:GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
}

hmock.On(
"GET",
"https://localhost/paths/strict-send?destination_assets=EUR%3AGDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_amount=20&source_asset_code=USD&source_asset_issuer=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_asset_type=credit_alphanum4",
).ReturnString(200, pathsResponse)

paths, err = client.StrictSendPaths(pr)
assert.NoError(t, err)
assert.Len(t, paths.Embedded.Records, 3)

pr = StrictSendPathsRequest{
SourceAmount: "20",
SourceAssetCode: "USD",
SourceAssetIssuer: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
SourceAssetType: AssetType4,
DestinationAssets: "EUR:GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
DestinationAccount: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN",
}

hmock.On(
"GET",
"https://localhost/paths/strict-send?destination_account=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&destination_assets=EUR%3AGDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_amount=20&source_asset_code=USD&source_asset_issuer=GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN&source_asset_type=credit_alphanum4",
).ReturnString(400, badRequestResponse)

_, err = client.StrictSendPaths(pr)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "horizon error")
horizonError, ok := err.(*Error)
assert.Equal(t, ok, true)
assert.Equal(t, horizonError.Problem.Title, "Bad Request")
}
}

0 comments on commit a5c11a6

Please sign in to comment.