gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]