[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-cashless2ecash] branch master updated: nonce2ecash: implement htt
From: |
gnunet |
Subject: |
[taler-cashless2ecash] branch master updated: nonce2ecash: implement http utility |
Date: |
Fri, 08 Mar 2024 22:13:05 +0100 |
This is an automated email from the git hooks/post-receive script.
joel-haeberli pushed a commit to branch master
in repository cashless2ecash.
The following commit(s) were added to refs/heads/master by this push:
new 59d26fb nonce2ecash: implement http utility
59d26fb is described below
commit 59d26fbea663abc3becdf1f8f45010e079a57e00
Author: Joel-Haeberli <haebu@rubigen.ch>
AuthorDate: Fri Mar 8 22:12:49 2024 +0100
nonce2ecash: implement http utility
---
README | 12 ++
nonce2ecash/go.mod | 4 +
nonce2ecash/go.sum | 4 +
nonce2ecash/pkg/common/codec.go | 43 +++++++
nonce2ecash/pkg/common/codec_test.go | 62 ++++++++++
nonce2ecash/pkg/common/http-util.go | 144 +++++++++++++++++++++++
nonce2ecash/pkg/common/model.go | 14 +--
nonce2ecash/pkg/taler-bank-integration/client.go | 131 ++++++++++++++++++++-
8 files changed, 401 insertions(+), 13 deletions(-)
diff --git a/README b/README
index d2c79c8..985c044 100644
--- a/README
+++ b/README
@@ -8,3 +8,15 @@ the exchange got the guarantee that the payment will
eventually reach the exchan
The flow establishes trust not with the final transaction, but the
authenticated guarantee, that the
terminal operator will eventually pay the amount to the bankaccount of the
exchange.
+
+
+The following tree describes the structure of the document and a rough
description of each directory
+```
+.
+├── data : contains sql
+├── docs : contains the thesis (LaTeX)
+├── infra : contains configs for running the components
+├── nonce2ecash : contains code of the nonce2ecash component
+├── schemaspy : contains erd using schemaspy
+└── specs : contains specifications
+```
diff --git a/nonce2ecash/go.mod b/nonce2ecash/go.mod
index 65d46d6..0292e31 100644
--- a/nonce2ecash/go.mod
+++ b/nonce2ecash/go.mod
@@ -1,3 +1,7 @@
module nonce2ecash
go 1.22.0
+
+require gotest.tools/v3 v3.5.1
+
+require github.com/google/go-cmp v0.5.9 // indirect
diff --git a/nonce2ecash/go.sum b/nonce2ecash/go.sum
new file mode 100644
index 0000000..7dd4ab5
--- /dev/null
+++ b/nonce2ecash/go.sum
@@ -0,0 +1,4 @@
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
+gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
diff --git a/nonce2ecash/pkg/common/codec.go b/nonce2ecash/pkg/common/codec.go
new file mode 100644
index 0000000..2799040
--- /dev/null
+++ b/nonce2ecash/pkg/common/codec.go
@@ -0,0 +1,43 @@
+package common
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+)
+
+type Codec[T any] interface {
+ httpApplicationContentHeader() string
+ encode(*T) (io.Reader, error)
+ decode(io.Reader) (*T, error)
+}
+
+type JsonCodec[T any] struct {
+ Codec[T]
+}
+
+func NewJsonCodec[T any]() Codec[T] {
+
+ return new(JsonCodec[T])
+}
+
+func (*JsonCodec[T]) HttpApplicationContentHeader() string {
+ return "application/json"
+}
+
+func (*JsonCodec[T]) Encode(body *T) (io.Reader, error) {
+
+ encodedBytes, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+
+ return bytes.NewReader(encodedBytes), err
+}
+
+func (*JsonCodec[T]) Decode(reader io.Reader) (*T, error) {
+
+ body := new(T)
+ err := json.NewDecoder(reader).Decode(body)
+ return body, err
+}
diff --git a/nonce2ecash/pkg/common/codec_test.go
b/nonce2ecash/pkg/common/codec_test.go
new file mode 100644
index 0000000..accb2aa
--- /dev/null
+++ b/nonce2ecash/pkg/common/codec_test.go
@@ -0,0 +1,62 @@
+package common_test
+
+import (
+ "bytes"
+ "fmt"
+ "nonce2ecash/pkg/common"
+ "testing"
+
+ "gotest.tools/v3/assert"
+)
+
+func TestJsonCodecRoundTrip(t *testing.T) {
+
+ type TestStruct struct {
+ A string
+ B int
+ C []string
+ D byte
+ E []byte
+ F *TestStruct
+ }
+
+ testObj := TestStruct{
+ "TestA",
+ 1,
+ []string{"first", "second"},
+ 'A',
+ []byte{0xdf, 0x01, 0x34},
+ &TestStruct{
+ "TestAA",
+ 2,
+ []string{"third", "fourth", "fifth"},
+ 'B',
+ []byte{0xdf, 0x01, 0x34},
+ nil,
+ },
+ }
+
+ jsonCodec := new(common.JsonCodec[TestStruct])
+
+ encodedTestObj, err := jsonCodec.Encode(&testObj)
+ if err != nil {
+ fmt.Println("error happened while encoding test obj",
err.Error())
+ t.FailNow()
+ }
+
+ encodedTestObjBytes := make([]byte, 200)
+ _, err = encodedTestObj.Read(encodedTestObjBytes)
+ if err != nil {
+ fmt.Println("error happened while encoding test obj to byte
array", err.Error())
+ t.FailNow()
+ }
+
+ encodedTestObjReader := bytes.NewReader(encodedTestObjBytes)
+ decodedTestObj, err := jsonCodec.Decode(encodedTestObjReader)
+ if err != nil {
+ fmt.Println("error happened while encoding test obj to byte
array", err.Error())
+ t.FailNow()
+ }
+
+ assert.DeepEqual(t, &testObj, decodedTestObj)
+}
diff --git a/nonce2ecash/pkg/common/http-util.go
b/nonce2ecash/pkg/common/http-util.go
new file mode 100644
index 0000000..fcdb950
--- /dev/null
+++ b/nonce2ecash/pkg/common/http-util.go
@@ -0,0 +1,144 @@
+package common
+
+import (
+ "errors"
+ "net/http"
+ "strings"
+)
+
+const HTTP_OK = 200
+const HTTP_NO_CONTENT = 204
+const HTTP_NOT_FOUND = 404
+const HTTP_CONFLICT = 409
+const HTTP_INTERNAL_SERVER_ERROR = 500
+
+// execute a GET request and parse body or retrieve error
+func HttpGetBodyOrError[T any](
+ req string,
+ pathParams map[string]string,
+ queryParams map[string]string,
+ codec Codec[T],
+) (*T, int, error) {
+
+ res, err := http.Get(formatUrl(req, pathParams, queryParams))
+ if err != nil {
+ return nil, -1, err
+ }
+
+ if codec == nil {
+ return nil, res.StatusCode, err
+ } else {
+ resBody, err := codec.decode(res.Body)
+ return resBody, res.StatusCode, err
+ }
+}
+
+// execute a POST request and parse response or retrieve error
+func HttpPostOrError[T any, R any](
+ req string,
+ pathParams map[string]string,
+ queryParams map[string]string,
+ body *T,
+ requestCodec Codec[T],
+ responseCodec Codec[R],
+) (*R, int, error) {
+
+ encodedBody, err := requestCodec.encode(body)
+ if err != nil {
+ return nil, -1, err
+ }
+
+ var res *http.Response
+ if body == nil {
+ if requestCodec == nil {
+ res, err = http.Post(
+ formatUrl(req, pathParams, queryParams),
+ "",
+ encodedBody,
+ )
+ } else {
+ return nil, -1, errors.New("invalid arguments - body
was not present but codec was defined")
+ }
+ } else {
+ if requestCodec == nil {
+ return nil, -1, errors.New("invalid arguments - body
was present but no codec was defined")
+ } else {
+ res, err = http.Post(
+ formatUrl(req, pathParams, queryParams),
+ requestCodec.httpApplicationContentHeader(),
+ encodedBody,
+ )
+ }
+ }
+
+ if err != nil {
+ return nil, -1, err
+ }
+
+ if responseCodec == nil {
+ return nil, res.StatusCode, err
+ }
+
+ resBody, err := responseCodec.decode(res.Body)
+ if err != nil {
+ return nil, -1, err
+ }
+
+ return resBody, res.StatusCode, err
+}
+
+// builds request URL containing the path and query
+// parameters of the respective parameter map.
+func formatUrl(
+ req string,
+ pathParams map[string]string,
+ queryParams map[string]string,
+) string {
+
+ return setUrlQuery(setUrlPath(req, pathParams), queryParams)
+}
+
+// Sets the parameters which are part of the url.
+// The function expects each parameter in the path to be prefixed
+// using a ':'. The function handles url as follows:
+//
+// /some/:param/tobereplaced -> ':param' will be replace with value.
+//
+// For replacements, the pathParams map must be supplied. The map contains
+// the name of the parameter with the value mapped to it.
+// The names MUST not contain the prefix ':'!
+func setUrlPath(
+ req string,
+ pathParams map[string]string,
+) string {
+
+ if pathParams == nil && len(pathParams) < 1 {
+ return req
+ }
+
+ var url = req
+ for k, v := range pathParams {
+
+ url = strings.Replace(url, ":"+k, v, 1)
+ }
+ return url
+}
+
+func setUrlQuery(
+ req string,
+ queryParams map[string]string,
+) string {
+
+ if queryParams == nil && len(queryParams) < 1 {
+ return req
+ }
+
+ var url = req + "?"
+ for k, v := range queryParams {
+
+ url = strings.Join([]string{url, k, "=", v, "&"}, "")
+ }
+
+ url, _ = strings.CutSuffix(url, "&")
+ return url
+}
diff --git a/nonce2ecash/pkg/common/model.go b/nonce2ecash/pkg/common/model.go
index b8a67da..747d324 100644
--- a/nonce2ecash/pkg/common/model.go
+++ b/nonce2ecash/pkg/common/model.go
@@ -1,16 +1,16 @@
package common
// https://docs.taler.net/core/api-common.html#hash-codes
-type WithdrawalIdentifier [32]byte
+type WithdrawalIdentifier string
// https://docs.taler.net/core/api-common.html#cryptographic-primitives
-type EddsaPublicKey [32]byte
+type EddsaPublicKey string
-type WithdrawalOperationStatus int
+type WithdrawalOperationStatus string
const (
- PENDING WithdrawalOperationStatus = iota
- SELECTED
- ABORTED
- CONFIRMED
+ PENDING WithdrawalOperationStatus = "pending"
+ SELECTED = "selected"
+ ABORTED = "aborted"
+ CONFIRMED = "confirmed"
)
diff --git a/nonce2ecash/pkg/taler-bank-integration/client.go
b/nonce2ecash/pkg/taler-bank-integration/client.go
index b329442..1793cdd 100644
--- a/nonce2ecash/pkg/taler-bank-integration/client.go
+++ b/nonce2ecash/pkg/taler-bank-integration/client.go
@@ -2,20 +2,139 @@ package talerbankintegration
import (
"errors"
+ "fmt"
"nonce2ecash/pkg/common"
)
+const WITHDRAWAL_ID_PATH_PARAM_NAME = "withdrawal_id"
+
+const WITHDRAWAL_OPERATION_API = "/withdrawal-operation"
+const WITHDRAWAL_OPERATION_BY_ID_API = WITHDRAWAL_OPERATION_API + "/:" +
WITHDRAWAL_ID_PATH_PARAM_NAME
+const WITHDRAWAL_OPERATION_ABORT_BY_ID_API = WITHDRAWAL_OPERATION_BY_ID_API +
"/abort"
+
+type TalerBankIntegration interface {
+ init(string)
+
+ withdrawalOperationStatus(common.WithdrawalIdentifier)
(*BankWithdrawalOperationStatus, error)
+ withdrawalOperationCreate(common.EddsaPublicKey, string)
(*BankWithdrawalOperationPostResponse, error)
+ withdrawalOperationAbort(common.WithdrawalIdentifier) error
+}
+
+type TalerBankIntegrationImpl struct {
+ TalerBankIntegration
+
+ exchangeBaseUrl string
+}
+
+// Initialize the taler bank integration implementation.
+// The exchangeBaseUrl will be used as target by the impl.
+func (tbi *TalerBankIntegrationImpl) init(exchangeBaseUrl string) {
+
+ tbi.exchangeBaseUrl = exchangeBaseUrl
+}
+
// check status of withdrawal
-func withdrawalOperationStatus(id common.WithdrawalIdentifier)
(*BankWithdrawalOperationStatus, error) {
- return nil, errors.New("not implemented yet")
+func (tbi *TalerBankIntegrationImpl) withdrawalOperationStatus(
+ id common.WithdrawalIdentifier,
+) (*BankWithdrawalOperationStatus, error) {
+
+ withdrawalOperationStatus, status, err := common.HttpGetBodyOrError(
+ tbi.exchangeBaseUrl+WITHDRAWAL_OPERATION_BY_ID_API,
+ map[string]string{WITHDRAWAL_ID_PATH_PARAM_NAME: string(id)},
+ nil,
+ common.NewJsonCodec[BankWithdrawalOperationStatus](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return withdrawalOperationStatus, nil
+ }
+
+ if status == common.HTTP_NOT_FOUND {
+
+ return nil, errors.New("HTTP 404 - The operation was not found")
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", status)
}
// send parameters for reserve to exchange core.
-func withdrawalOperationCreate(reservePubKey common.EddsaPublicKey,
exchangeBaseUrl string) (*BankWithdrawalOperationPostResponse, error) {
- return nil, errors.New("not implemented yet")
+func (tbi *TalerBankIntegrationImpl) withdrawalOperationCreate(
+ id common.WithdrawalIdentifier,
+ reservePubKey common.EddsaPublicKey,
+ exchangPayToAddress string,
+) (*BankWithdrawalOperationPostResponse, error) {
+
+ bankWithdrawalOperationPostResponse, status, err :=
common.HttpPostOrError(
+ tbi.exchangeBaseUrl+WITHDRAWAL_OPERATION_BY_ID_API,
+ map[string]string{WITHDRAWAL_ID_PATH_PARAM_NAME: string(id)},
+ nil,
+ &BankWithdrawalOperationPostRequest{
+ string(reservePubKey),
+ exchangPayToAddress,
+ },
+ common.NewJsonCodec[BankWithdrawalOperationPostRequest](),
+ common.NewJsonCodec[BankWithdrawalOperationPostResponse](),
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if status == common.HTTP_OK {
+
+ return bankWithdrawalOperationPostResponse, nil
+ }
+
+ if status == common.HTTP_NOT_FOUND {
+
+ return nil, errors.New("HTTP 404 - The operation was not found")
+ }
+
+ if status == common.HTTP_CONFLICT {
+
+ return nil, errors.New("HTTP 409 - conflict")
+ }
+
+ return nil, fmt.Errorf("HTTP %d - unexpected", status)
}
// abort withdrawal
-func withdrawalOperationAbort(id common.WithdrawalIdentifier) error {
- return errors.New("not implemented yet")
+func (tbi *TalerBankIntegrationImpl) withdrawalOperationAbort(
+ id common.WithdrawalIdentifier,
+) error {
+
+ _, status, err := common.HttpPostOrError[any, any](
+ tbi.exchangeBaseUrl+WITHDRAWAL_OPERATION_BY_ID_API,
+ map[string]string{WITHDRAWAL_ID_PATH_PARAM_NAME: string(id)},
+ nil,
+ nil,
+ nil,
+ nil,
+ )
+
+ if err != nil {
+ return err
+ }
+
+ if status == common.HTTP_NO_CONTENT {
+
+ return nil
+ }
+
+ if status == common.HTTP_NOT_FOUND {
+
+ return errors.New("HTTP 404 - The withdrawal operation was not
found")
+ }
+
+ if status == common.HTTP_CONFLICT {
+
+ return errors.New("HTTP 409 - The withdrawal operation has been
confirmed previously and can’t be aborted")
+ }
+
+ return fmt.Errorf("HTTP %d - unexpected", status)
}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-cashless2ecash] branch master updated: nonce2ecash: implement http utility,
gnunet <=